python学习日03[连载],Pytorch,Day03



先分解整个项目的工作步骤,后面再把相关原理与注意事项补上。主要项目每一步太多细节,需要马上记录。
总的文件目录是这样
在这里插入图片描述
思路:
在这里插入图片描述
在这里插入图片描述

文件说明:RMB_data是存放数据,test_data是存放测试集样本,rmb_split是从原数据集中按比例切分后的训练集、验证集和测试集的集合文件,model是存放LeNet的脚本,tools是存放定义Mydataset装载数据类方法,split_dataset.py是用来切分数据集,train_lenet是训练网络脚本

一、数据准备

数据集准备,这里不限制什么数据,只需要是二分类特点的数据就好。

1、划分数据集

split_dataset

step1 导包

import os
import random
import shutil

step2 判断新建数据集文件是否存在

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

step3 划分数据集以及建立文件夹路径

random.seed(1)
dataset_dir=os.path.join('RMB_data')
split_dir=os.path.join('rmb_split')
train_dir=os.path.join(split_dir,'train')  #输出 rmb_split\\train
valid_dir=os.path.join(split_dir,'valid')
test_dir=os.path.join(split_dir,'test')

注意这里的data_dir路径一定要对,可以通过以下步骤检验,如果输出为空,说明路径错误

step4 划分三个集合的配比

train_pct=0.8
valid_pct=0.1
test_pct=0.1

step5 查看路径下分类文件情况

for root,dirs,files in os.walk(dataset_dir):
    for sub_dir in dirs:
        #1  100两个类的文件夹
        imgs=os.listdir(os.path.join(root,sub_dir)) #读取每个类下的样本
        imgs=list(filter(lambda x:x.endswith('.jpg'),imgs)) #筛选jpg
        random.shuffle(imgs)#打乱图片
        img_count=len(imgs) #100

step6 计算各个数据集容量

        train_point=int(img_count*train_pct) #80
        valid_point=int(img_count*(train_pct+valid_pct)) #90

这里计算原理是,通过valid_point-train_point=validset的数量,其实我觉得好像也可以直接valid_point=int(img_count*valid_pct),不太知道它这里相减的用意是什么,但是看到后面这一步,我就懂了,原来是为了更好区分验证集和测试集的划分条件

step7 根据既定数据量划分数据集路径

        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)

图像标号小于80,归入测试集,80-90之间,归入验证集,之外的归入测试集

step8 组装数据集路径

       makedir(out_dir)#重新分配好样本后,生成新的样本集
       target_path=os.path.join(out_dir,imgs[i]) #rmb_split\train\100\07GG9EL5.jpg
       src_path=os.path.join(dataset_dir,sub_dir,imgs[i]) #RMB_data\1\0G7ZDUOL.jpg
       shutil.copy(src_path,target_path)

shutil.copy(src_path,target_path)这个是将src_path里面的文件内容复制到target_path里面当中

2、在训练脚本关联数据集以及数据预处理

train_lenet.py

import os
import random
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
#下面这两个是引用其他两个文件包model和tools,后面阐述
from model.lenet import LeNet
from tools.my_dataset import RMBDataset

step1 设置超参

ef set_seed(seed=1):
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)


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

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

设置了最大epoch数量,批次数量,学习率,log_interval是每隔10个batch输出一次

step2 设置数据路径

split_dir = os.path.join("rmb_split")
train_dir = os.path.join(split_dir, "train")
valid_dir = os.path.join(split_dir, "valid")

step3 数据预处理

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

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),
])

这里对样本作出变形,随机裁剪以及正则化

3、编写装载数据类

step1 先实例化MyDataset实例

# 构建MyDataset实例
train_data = RMBDataset(data_dir=train_dir, transform=train_transform)
valid_data = RMBDataset(data_dir=valid_dir, transform=valid_transform)

还记得train_dir吗,它是上面第二点里面的step2里面已经设置好了

step2 编写MyDataset类实例

tools/my_dataset.py

import os
import random
from PIL import Image
from torch.utils.data import Dataset

random.seed(1)
rmb_label={"1":0,"100":1}

step1 初始化参数

class RMBDataset(Dataset):
    def __init__(self,data_dir,transform=None):
        '''
        :param data_dir: str,数据集所在路径
        :param transform: torch.transform,数据预处理
        '''
        self.label_name={"1":0,"100":1}
        self.data_info=self.get_img_info(data_dir)
        #data_info存储所有图片路径和标签,在DataLoader中通过index读取样本
        self.transform=transform

step2 绑定数据集与其索引

    def __getitem__(self, index):
        path_img,label=self.data_info[index] #通过index来索取数据
        img=Image.open(path_img).convert('RGB') #0~255

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

这里的convert(‘RGB’)如果不转换,则会读出来的图像是RGBA四通道

step3 获取数据集文件的数量

    def __len__(self):
        #查看样本数据量
        return len(self.data_info)

从step2和step3可以看出都和data_info有关系,而这个东西就是关联数据集的方法或者是接口吧

step4 获取数据集详情

   @staticmethod #表示类可以调用这个方法
    def get_img_info(data_dir):
        data_info=list()
        for root,dirs,_ in os.walk(data_dir):
            for sub_dir in dirs:
            #类1和类100
                img_names=os.listdir(os.path.join(root,sub_dir))
                img_names=list(filter(lambda x:x.endswith('.jpg'),img_names))

                #遍历图片
                for i in range(len(img_names)):
                    img_name=img_names[i]
                    path_img=os.path.join(root,sub_dir,img_name)
                    label=rmb_label[sub_dir] 
                    data_info.append((path_img,int(label)))
        return data_info

img_name=img_names[i] #获取图片列表下的每一张图片的名字,如:01EIM65B.jpg
label=rmb_label[sub_dir] #通过前面rmb_label字典,来存储每张图片的对应的标签

step5 构建dataLoader,装载数据集

train_lenet.py

train_loader = DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)
valid_loader = DataLoader(dataset=valid_data, batch_size=BATCH_SIZE)

二、构建网络

model/lenet.py

import torch.nn as nn
import torch.nn.functional as F

详细版

class LeNet(nn.Module):
    def __init__(self,classes):
        super(LeNet,self).__init__()
        self.conv1=nn.Conv2d(3,6,5)
        self.conv2=nn.Conv2d(6,16,5)
        self.fc1=nn.Linear(16*5*5,120)
        self.fc2=nn.Linear(120,84)
        self.fc3=nn.Linear(84,classes)

    def forward(self,x):
        out=F.relu(self.conv1(x))
        out=F.max_pool2d(out,2)
        out=F.relu(self.conv2(out))
        out=F.max_pool2d(out,2)
        out=out.view(out.size(0),-1)  #是将图像扁平化,从而输入全连接层
        out=F.relu(self.fc1(out))
        out=F.relu(self.fc2(out))
        out=self.fc3(out)
        return out

在这里插入图片描述
初始化参数

    def initialize_weights(self):
        for m in self.modules():
            if isinstance(m,nn.Conv2d):
                nn.init.xavier_normal_(m.weight.data)
                if m.bias is not None:
                    m.bias.data.zero_()
                elif isinstance(m,nn.BatchNorm2d):
                    m.weight.data.fill_(1)
                    m.bias.data.zero_()
                elif isinstance(m,nn.Linear):
                    nn.init.normal_(m.weight.data,0,0.1)
                    m.bias.data.zero_()

isinstance() 函数来判断一个对象是否是一个已知的类型,此处是判定m是否输入各自层的实例,如果是的话就对各自层参数执行初始化方法
网络流水线版

class LeNet2(nn.Module):
    def __init__(self,classes):
        super(LeNet2,self).__init__()
        self.features=nn.Sequential(
            nn.Conv2d(3,6,5),
            nn.ReLU(),
            nn.MaxPool2d(2,2),
            nn.Conv2d(6,16,5),
            nn.ReLU(),
            nn.MaxPool2d(2,2)
        )
        self.classifier=nn.Sequential(
            nn.Linear(16*5*5,120),
            nn.ReLU(),
            nn.Linear(120,84),
            nn.ReLU(),
            nn.Linear(84,classes)
        )

    def forward(self,x):
        x=self.features(x)
        x=x.view(x.size()[0],-1)
        x=self.classifier(x)
        return x

三、实例化网络

net = LeNet(classes=2)
net.initialize_weights()

四、确定损失函数和优化器

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

五、进行训练

for epoch in range(MAX_EPOCH):

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

    net.train()
    #下面是1个epoch的训练
    for i, data in enumerate(train_loader):

        # forward
        inputs, labels = data
        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()  # 更新学习率

{:0>3}是指3位数,初始值为000,并从右边(个位)开始叠加

 # 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()

            valid_curve.append(loss_val/valid_loader.__len__())
            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, correct_val / total_val))

correct_val += (predicted == labels).squeeze().sum().numpy()这里的意思是将符合标签的结果值进行压缩,默认是压缩成一维,然后进行计数统计,最后转成numpy格式

Training:Epoch[000/010] Iteration[010/010] Loss: 0.6972 Acc:52.50%
Valid:    Epoch[000/010] Iteration[002/002] Loss: 1.3560 Acc:70.00%
Training:Epoch[001/010] Iteration[010/010] Loss: 0.6612 Acc:69.38%
Valid:    Epoch[001/010] Iteration[002/002] Loss: 1.3355 Acc:75.00%
Training:Epoch[002/010] Iteration[010/010] Loss: 0.5834 Acc:72.50%
Valid:    Epoch[002/010] Iteration[002/002] Loss: 1.0189 Acc:100.00%
Training:Epoch[003/010] Iteration[010/010] Loss: 0.3345 Acc:98.75%
Valid:    Epoch[003/010] Iteration[002/002] Loss: 0.3785 Acc:100.00%
Training:Epoch[004/010] Iteration[010/010] Loss: 0.0754 Acc:100.00%
Valid:    Epoch[004/010] Iteration[002/002] Loss: 0.0285 Acc:100.00%
Training:Epoch[005/010] Iteration[010/010] Loss: 0.0051 Acc:100.00%
Valid:    Epoch[005/010] Iteration[002/002] Loss: 0.0020 Acc:100.00%
Training:Epoch[006/010] Iteration[010/010] Loss: 0.0020 Acc:100.00%
Valid:    Epoch[006/010] Iteration[002/002] Loss: 0.0009 Acc:100.00%
Training:Epoch[007/010] Iteration[010/010] Loss: 0.0012 Acc:100.00%
Valid:    Epoch[007/010] Iteration[002/002] Loss: 0.0002 Acc:100.00%
Training:Epoch[008/010] Iteration[010/010] Loss: 0.0003 Acc:100.00%
Valid:    Epoch[008/010] Iteration[002/002] Loss: 0.0001 Acc:100.00%
Training:Epoch[009/010] Iteration[010/010] Loss: 0.0004 Acc:100.00%
Valid:    Epoch[009/010] Iteration[002/002] Loss: 0.0001 Acc:100.00%

六、可视化

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
 # 由于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()

在这里插入图片描述

七、测试

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
test_dir = os.path.join(BASE_DIR, "test_data")

这里的BASE_DIR是获取当前目录的总路径,用来拼接测试集路径用的

test_data = RMBDataset(data_dir=test_dir, transform=valid_transform)
valid_loader = DataLoader(dataset=test_data, batch_size=1)

装载Dataloader

for i, data in enumerate(valid_loader):
    # forward
    inputs, labels = data
    outputs = net(inputs)
    _, predicted = torch.max(outputs.data, 1)

    rmb = 1 if predicted.numpy()[0] == 0 else 100
    print("模型获得{}元".format(rmb))

八、相关问题

在这里插入图片描述

九、涉及知识点

对于数据处理可以做一下步骤
在这里插入图片描述
在这里插入图片描述
样本总数:80 Batchsize: 8
1 Epoch=10 Iteration
如果不能被整除
样本总数:87 Batchsize: 8
1 Epoch=10 Iteration =>droplast=True
1 Epoch=11 Iteration =>droplast=False
在这里插入图片描述
数据读取机制:
在这里插入图片描述
在这里插入图片描述
在for循环里面会遍历DataLoader里面的数据,
以下部分是pytorch自带的:


进入这个DataLoader容器后,会根据是否使用单进程机制还是多进程机制来选择相应的DataLoaderIter,然后会使用Sampler获取索引Index,拿到索引后,传给DatasetFetcher


然后在DatasetFetcher里面会调用我们自定义的Dataset类,Dataset会根据我们给定的索引,在getitem当中从硬盘中读取img,label,在读取了一个batchsize的量后,

调用collate_fn进行封装|(pytorch自带),整理成一个batch Data的形式,返回到主函数里面,得到index和data,从而可以输入到网络中进行训练

题外话:
焦虑的心情油然而生,不仅是课程内容难了,而且论文还是没有进展,而且在做数据处理时候,还是遇到了阻力。不过庆幸的是,我终于醒悟到,这些事情本身就是要独立完成的,不要指望外力的帮助,有外力协助当然最好不过,但是如果没有的情况下,也是需要独立地想办法解决。课程总结的坚持是否还有意义呢?我觉得是有的,毕竟修炼需要不断重复,为的是以后能更顺畅地看代码,改代码。其实每次我都会想,这次的入坑是否会后悔,不知道。