训练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) 程序代码可以在资源中找到。
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))
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()
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