pytorch 猫狗分类

Week2 任务1 猫狗分类

问题:

训练RMB二分类模型,熟悉数据读取机制,并且从kaggle中下载猫狗二分类训练数据,自己编写一个DogCatDataset,使得pytorch可以对猫狗二分类训练集进行读取。

数据下载地址:
(1) https://www.kaggle.com/c/dogs-vs-cats-redux-kernels-edition/data
(2) https://pan.baidu.com/s/1-0xHuViQz7lir6o1KxgJxg 密码:6837

写在前面:
本程序是根据上课的例题所修改,修改内容如下:
(1) 修改 split_dataset.py 程序,读取猫狗图片,并进行按test、train和valid进行图片准备。
(2) 修改transforms_methods.py程序,读取data_split文件夹中的图片。
(3) 修改transforms_methods.py程序,在训练过程中显示正在训练的图片。
(4) 修改my_dataset.py程序,改成猫狗图像数据读取,并进行了注释。
Tips:因为本人水平有限,并没有自己完全进行编写,先放在这儿,以后再慢慢进行修改,并上传。

使用方法:

(1) 将数据集下载后,对dogs-vs-cats-redux-kernels-edition.zip进行解压。
(2) 在程序文件夹中建个data文件夹,并在data文件夹中建个dogcatdata,并在该文件夹中建两个文件夹,分别是:cat和dog,并把解压后的图片按“猫”和狗进行分类后分别放到cat和dog文件夹中。
(3) 在程序文件夹中,再建两个文件夹,分别是tools和model,并把my_dataset.py和lenet.py两个文件分别放到tools和model文件夹中。
(4) 然后先执行split_dataset.py,再执行transforms_methods.py文件。
(5) 程序代码可以在资源中找到。

1. split_dataset.py

import os
import random
import shutil
BASE_DIR = os.path.dirname(os.path.abspath(__file__))


def makedir(new_dir):
    if not os.path.exists(new_dir):
        os.makedirs(new_dir)

if __name__ == '__main__':

    dataset_dir = os.path.abspath(os.path.join(BASE_DIR, "data", "dogcatdata"))
    split_dir = os.path.abspath(os.path.join(BASE_DIR, "data", "data_split"))
    train_dir = os.path.join(split_dir, "train")
    valid_dir = os.path.join(split_dir, "valid")
    test_dir = os.path.join(split_dir, "test")

    if not os.path.exists(dataset_dir):
        raise Exception("\n{} 不存在,请下载 猫狗数据集 放到\n{} 下,并解压即可".format(
            dataset_dir, os.path.dirname(dataset_dir)))

    train_pct = 0.8     # 8:1:1
    valid_pct = 0.1
    test_pct = 0.1

    # os.walk() 方法用于通过在目录树中游走输出在目录中的文件名,向上或者向下。
    for root, dirs, files in os.walk(dataset_dir):
        for sub_dir in dirs:
            # 每次只进一步文件夹,要么是猫,要么是狗,所以下面的img_count要么为猫的,要么为狗的
            # os.listdir() 方法用于返回指定的文件夹包含的文件或文件夹的名字的列表。
            imgs = os.listdir(os.path.join(root, sub_dir))
            # filter(function, iterable) 函数用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表。
            # 参数:function -- 判断函数, iterable -- 可迭代对象。
            # lambda匿名函数的格式:冒号前是参数,可以有多个,用逗号隔开,冒号右边的为表达式。
            # endswith() 用于判断字符串是否以指定后缀结尾,如果以指定后缀结尾返回True,否则返回False。
            # (filter(lambda x: x.endswith('.jpg'), imgs) 在imgs里所有文件中找到结尾为.jpg的文件。
            imgs = list(filter(lambda x: x.endswith('.jpg'), imgs))
            #  random.shuffle将序列的所有元素随机排序,shuffle()是不能直接访问的,需要导入 random 模块,然后通过 random 静态对象调用该方法。
            random.shuffle(imgs)
            img_count = len(imgs)

            train_point = int(img_count * train_pct) # 乘以0.1 第一条分界线
            valid_point = int(img_count * (train_pct + valid_pct)) # 乘以0.2 其值大于0.1的倍数,所以第2条线在0.1和0.2之间,不是0.1一定是0.2的倍数

            if img_count == 0:
                print("{}目录下,无图片,请检查".format(os.path.join(root, sub_dir)))
                import sys
                sys.exit(0)
            for i in range(img_count):
                if i < train_point:
                    # 连接两个或更多的路径名,与字符串连接相区别的是,该函数可以自动加\
                    out_dir = os.path.join(train_dir, sub_dir)
                elif i < valid_point:
                    out_dir = os.path.join(valid_dir, sub_dir)
                else:
                    out_dir = os.path.join(test_dir, sub_dir)

                makedir(out_dir)

                target_path = os.path.join(out_dir, imgs[i])
                src_path = os.path.join(dataset_dir, sub_dir, imgs[i])
                # 复制文件内容(不包含元数据)从src到dst
                shutil.copy(src_path, target_path)

            print('Class:{}, train:{}, valid:{}, test:{}'.format(sub_dir, train_point, valid_point-train_point,
                                                                 img_count-valid_point))
            print("已在 {} 创建划分好的数据\n".format(out_dir))

2. transforms_methods.py

import os
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
import numpy as np
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
import torchvision.transforms as transforms
import torch.optim as optim
from matplotlib import pyplot as plt
from tools.common_tools import set_seed, transform_invert


# assert语句:
# 用以检查某一条件是否为True,若该条件为False则会给出一个AssertionError。

# Lenet 卷积神经网络
# 将data 进行反transfrom操作
path_lenet = os.path.abspath(os.path.join(BASE_DIR, "model", "lenet.py"))
path_tools = os.path.abspath(os.path.join(BASE_DIR,  "tools", "common_tools.py"))
assert os.path.exists(path_lenet), "{}不存在,请将lenet.py文件放到 {}".format(path_lenet, os.path.dirname(path_lenet))
assert os.path.exists(path_tools), "{}不存在,请将common_tools.py文件放到 {}".format(path_tools, os.path.dirname(path_tools))

import sys
hello_pytorch_DIR = os.path.abspath(os.path.dirname(__file__))
sys.path.append(hello_pytorch_DIR)


from model.lenet import LeNet
from tools.my_dataset import CDDataset  #CDDataset 前面的 CD 代表 cat 和 dog
from tools.common_tools import set_seed

set_seed()  # 设置随机种子
rmb_label = {
     "cat": 0, "dog": 1}

# 参数设置
MAX_EPOCH = 10
BATCH_SIZE = 16
LR = 0.01
log_interval = 10
val_interval = 1

# ============================ step 1/5 数据 ============================
split_dir = os.path.abspath(os.path.join(BASE_DIR,"data", "data_split"))
if not os.path.exists(split_dir):
    raise Exception(r"数据 {} 不存在, 请生成数据".format(split_dir))
train_dir = os.path.join(split_dir, "train")
valid_dir = os.path.join(split_dir, "valid")

norm_mean = [0.485, 0.456, 0.406]
norm_std = [0.229, 0.224, 0.225]

# Compose里面的参数实际上就是个列表,而这个列表里面的元素就是你想要执行的transform操作。
train_transform = transforms.Compose([
    transforms.Resize((32, 32)),
    transforms.RandomCrop(32, padding=4),
    transforms.ToTensor(),
    transforms.Normalize(norm_mean, norm_std),
])

valid_transform = transforms.Compose([
    transforms.Resize((32, 32)),
    transforms.ToTensor(),
    transforms.Normalize(norm_mean, norm_std),
])

# 构建MyDataset实例
train_data = CDDataset(data_dir=train_dir, transform=train_transform)  # 用户自己创建的
valid_data = CDDataset(data_dir=valid_dir, transform=valid_transform)

# 构建DataLoder
# shuffle=True 每个样本顺序都是不一样的
train_loader = DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)
valid_loader = DataLoader(dataset=valid_data, batch_size=BATCH_SIZE)

# ============================ step 2/5 模型 ============================
# 卷积神经网络
net = LeNet(classes=2)
# 初始化初始化函数
net.initialize_weights()

# ============================ step 3/5 损失函数 ============================
criterion = nn.CrossEntropyLoss()                                                   # 选择损失函数

# ============================ step 4/5 优化器 ============================
optimizer = optim.SGD(net.parameters(), lr=LR, momentum=0.9)                        # 选择优化器
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)     # 设置学习率下降策略

# ============================ step 5/5 训练 ============================
train_curve = list()
valid_curve = list()

for epoch in range(MAX_EPOCH):

    loss_mean = 0.
    correct = 0.
    total = 0.

    net.train()
    for i, data in enumerate(train_loader):

        # forward
        inputs, labels = data

        img_tensor1 = inputs[0, ...]     # C H W  …代替了切片操作中前面所有的:
        img_tensor1 = transform_invert(img_tensor1, train_transform)
        plt.imshow(img_tensor1)
        plt.show()
        plt.pause(0.5)
        plt.close()

        outputs = net(inputs)

        # backward
        optimizer.zero_grad()
        loss = criterion(outputs, labels)
        loss.backward()

        # update weights
        optimizer.step()

        # 统计分类情况
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).squeeze().sum().numpy()

        # 打印训练信息
        loss_mean += loss.item()
        train_curve.append(loss.item())
        if (i+1) % log_interval == 0:
            loss_mean = loss_mean / log_interval
            print("Training:Epoch[{:0>3}/{:0>3}] Iteration[{:0>3}/{:0>3}] Loss: {:.4f} Acc:{:.2%}".format(
                epoch, MAX_EPOCH, i+1, len(train_loader), loss_mean, correct / total))
            loss_mean = 0.

    scheduler.step()  # 更新学习率

    # validate the model
    if (epoch+1) % val_interval == 0:

        correct_val = 0.
        total_val = 0.
        loss_val = 0.
        net.eval()
        with torch.no_grad():
            for j, data in enumerate(valid_loader):
                inputs, labels = data
                outputs = net(inputs)
                loss = criterion(outputs, labels)

                _, predicted = torch.max(outputs.data, 1)
                total_val += labels.size(0)
                correct_val += (predicted == labels).squeeze().sum().numpy()

                loss_val += loss.item()

            loss_val_epoch = loss_val / len(valid_loader)
            valid_curve.append(loss_val_epoch)
            print("Valid:\t Epoch[{:0>3}/{:0>3}] Iteration[{:0>3}/{:0>3}] Loss: {:.4f} Acc:{:.2%}".format(
                epoch, MAX_EPOCH, j+1, len(valid_loader), loss_val_epoch, correct_val / total_val))


train_x = range(len(train_curve))
train_y = train_curve

train_iters = len(train_loader)
valid_x = np.arange(1, len(valid_curve)+1) * train_iters*val_interval - 1  # 由于valid中记录的是epochloss,需要对记录点进行转换到iterations
valid_y = valid_curve

plt.plot(train_x, train_y, label='Train')
plt.plot(valid_x, valid_y, label='Valid')

plt.legend(loc='upper right')
plt.ylabel('loss value')
plt.xlabel('Iteration')
plt.show()

3.my_dataset.py

import numpy as np
import torch
import os
import random
from PIL import Image
from torch.utils.data import Dataset

random.seed(1)
cd_label = {
     "cat": 0, "dog": 1}
class CDDataset(Dataset):
    def __init__(self, data_dir, transform=None):
        """
        猫狗分类任务的Dataset
        :param data_dir: str, 数据集所在路径
        :param transform: torch.transform,数据预处理
        """
        # 初始化
        self.label_name = {
     "cat": 0, "dog": 1}
        self.data_info = self.get_img_info(data_dir)  # data_info存储所有图片路径和标签,在DataLoader中通过index读取样本
        self.transform = transform

    def __getitem__(self, index):
        path_img, label = self.data_info[index]
        img = Image.open(path_img).convert('RGB')     # 0~255 图像转为RGB通道

        if self.transform is not None:
            img = self.transform(img)   # 在这里做transform,转为tensor等等
        return img, label

    def __len__(self):
        return len(self.data_info)

    # 当某个方法不需要用到对象中的任何资源,将这个方法改为一个静态方法, 加一个@staticmethod
    # 获得图片的标签
    @staticmethod
    def get_img_info(data_dir):
        data_info = list()
        # 按照习惯,有时候单个独立下划线是用作一个名字,来表示某个变量是临时的或无关紧要的。
        # _ 表示这个需要3个参数,但是最后一个参数我们不想用,为了避免占用资源,就用 _ 代替。
        for root, dirs, _ in os.walk(data_dir):
            # 遍历类别
            for sub_dir in dirs:
                img_names = os.listdir(os.path.join(root, sub_dir))
                img_names = list(filter(lambda x: x.endswith('.jpg'), img_names))

                # 遍历图片
                # img_names是个列表,len就是列表中元素中的个数
                for i in range(len(img_names)):
                    img_name = img_names[i]
                    path_img = os.path.join(root, sub_dir, img_name)
                    # 根据名字来给猫或狗打标签
                    label = cd_label[sub_dir]
                    data_info.append((path_img, int(label)))

        return data_info

你可能感兴趣的:(pytorch作业,深度学习)