Pytorch 模型训练步骤

Pytorch 模型训练步骤

一、数据处理

首先,一般要将原始数据按8:1:1的比例划分为训练集(train set),验证集(valid set),测试集(test set)。
训练集:用于训练的样本集合,主要用来训练神经网络的参数
验证集:用于验证模型性能的样本集合,不同神经网络在训练集上训练结束后,通过验证集来比较判断各个模型的性能,来选择(人工训练)最优的超参数
测试集:用于早完成神经网络训练过程后,为了客观评价模型在其未见过(未曾影响普通参数和超参数选择)的数据上的性能

代码示例(数据集划分)

# coding: utf-8
"""
    将原始数据集划分为训练集、验证集和测试集
"""

import os
import glob
import random
import shutil


dataset_dir = os.path.join("..","..","Data","dataset")
train_dir = os.path.join("..", "..", "Data", "train")
valid_dir = os.path.join("..", "..", "Data", "valid")
test_dir = os.path.join("..", "..", "Data", "test")


train_per = 0.8
valid_per = 0.1
test_per = 0.1

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



if __name__ == '__main__':


        imgs_list = glob.glob(os.path.join(dataset_dir, '*.png'))
        random.seed(666)
        random.shuffle(imgs_list)
        imgs_num = len(imgs_list)

        train_point = int(imgs_num * train_per)
        valid_point = int(imgs_num * (train_per + valid_per))

        for i in range(imgs_num):
            if i < train_point:
                out_dir = os.path.join(train_dir)
            elif i < valid_point:
                out_dir = os.path.join(valid_dir)
            else:
                out_dir = os.path.join(test_dir)

            makedir(out_dir)
            out_path = os.path.join(out_dir, os.path.split(imgs_list[i])[-1])
            shutil.copy(imgs_list[i], out_path)

        print('train:{}, valid:{}, test:{}'.format(train_point, valid_point-train_point, imgs_num-valid_point))

制作图像数据索引

读取图片数据路径和标签,并把它保存到txt文件中
代码示例:


# coding:utf-8
import os

'''
   将数据集生成对应的txt文件
'''

train_txt_path = os.path.join("..", "..", "Data", "train.txt")
train_dir = os.path.join("..", "..", "Data", "train")

valid_txt_path = os.path.join("..", "..", "Data", "valid.txt")
valid_dir = os.path.join("..", "..", "Data", "valid")


def gen_txt(txt_path, img_dir):
    f = open(txt_path, 'w')


    i_dir = os.path.join(img_dir)  
    img_list = os.listdir(i_dir)  
    for i in range(len(img_list)):
        if not img_list[i].endswith('png'):  
            continue
        label = img_list[i].split('_')[0]
        img_path = os.path.join(i_dir, img_list[i])
        line = img_path + ' ' + label + '\n'
        f.write(line)
    f.close()


if __name__ == '__main__':
    gen_txt(train_txt_path, train_dir)
    gen_txt(valid_txt_path, valid_dir)

自定义数据集

步骤如下:
1、通过MyDataset创建一个实例(继承torch.utils.data.dataset),初始化txt;
2、初始化Dataloader,将1中自己建立的数据集导入到数据加载器上,从而使Dataloader拥有图片的路径;
3、在进行每一个iteration时,才读取一个batch的图片数据,enumerate()函数会返回可迭代数据的的一个“元素”(包含一个batch的图片数据和标签);
4、class DataLoader()中再调用class_DataloaderIter()
5、在_Dataloaderiter()类中会跳到_next_(self)函数,在该函数中通过indices = next(self.sample_iter)获取一个batch的indices,再通过
batch= self.collate_fn([self.dataset[i]] for i in indices])获取一个batch的数据,在batch=self.collate_fn([self.dataset[i] for i in indices])中会调用self.collate_fn函数;
6、self.collate_fn中华会调用MyDataset类中的_getitem_()函数,在_getitem_()中通过Image.open(fn).convert(‘RGB’)来读取图片;
7.通过Image.open(fn).convert(‘RGB’)读取图片之后,会对图片进行预处理,例如均值,标准差,随机裁剪等等一系列操作;
8、将图片数据转换为Variable类型,然后称为模型真正的输入;

另一种方法对于比较简单的数据集来说可以直接用datasets.ImageFolder来建立自己的数据集,之后再导入到数据加载器上。

Datasets.ImageFloder(root,transforms = None, target_transforms = None, loader = default_loader)
ImageFolder假设所有的文件按文件夹保存,每个文件夹下存储同一个类别的图片,文件夹名为类名;
它主要有四个参数:
root:在root指定的路径下寻找图片
transform:对PIL Image进行的转换操作,transform的输入是使用loader读取图片的返回对象
target_transform:对label的转换
loader:给定路径后如何读取图片,默认读取为RGB格式的PIL Image对象
label是按照文件夹名顺序排序后存成字典,即{类名:类序号(0开始)},一般来说最好直接将文件夹命名为从0开始的数字,这样会和ImageFolder实际的label一致,如果不是这种命名规范,建议看看self.class_to_idx属性以了解label和文件夹名的映射关系。

二、模型

模型定义:

  1. Pytorch模型的定义都需要继承nn.module这个类,在初始化方法中引入父类初始化方法
  2. 在__init__()中定义并初始化网络,(如conv、pooling、Linear、BatchNorm等)全部进行初始化设置,只负责创建组件,并不决定组件之间的关系
  3. 在前向传播forward(self,x)函数中实现批数据的前向传播逻辑,只要在nn.Module的子类中定义了forward()函数,backward()函数就会被自动实现(一般情况下我们定义的参数是可导的,但是如果自定义操作不可导,就需要我们手动实现backward()函数)使用时直接初始化该类,传入输入即可的得到输出

nn.Sequential
  它也继承自Module类,它是一个顺序容器,见torch源码/torch/nn/modules/container.py;在模型构建时,它会将组件按照一定顺序保存起来;
 三种形式:
1.简易形式:

model = nn.Sequential(
                  nn.Conv2d(1,20,5),
                  nn.ReLU(),
                  nn.Conv2d(20,64,5),
                  nn.ReLU()
                )

2.带层名称形式:

import torch.nn as nn
from collections import OrderedDict
model = nn.Sequential(OrderedDict([
                  ('conv1', nn.Conv2d(1,20,5)),
                  ('relu1', nn.ReLU()),
                  ('conv2', nn.Conv2d(20,64,5)),
                  ('relu2', nn.ReLU())
                ]))

3.通过add_module()方法的形式:

import torch.nn as nn
from collections import OrderedDict
model = nn.Sequential()
model.add_module("conv1",nn.Conv2d(1,20,5))
model.add_module('relu1', nn.ReLU())
model.add_module('conv2', nn.Conv2d(20,64,5))
model.add_module('relu2', nn.ReLU())

模型初始化:

具体可参考

def weights_init_normal(m):
    classname = m.__class__.__name__
    if classname.find("Conv") != -1:
        torch.nn.init.normal_(m.weight.data, 0.0, 0.02)
    elif classname.find("BatchNorm2d") != -1:
        torch.nn.init.normal_(m.weight.data, 1.0, 0.02)
        torch.nn.init.constant_(m.bias.data, 0.0)

模型finetune:

finetune 就相当于给模型进行初始化,其流程共用三步:
保存模型,拥有一个预训练模型;
加载模型,把预训练模型中的权值取出来;
初始化,将权值对应的“放”到新模型中;

# load params
pretrained_dict = torch.load('net_params.pkl')
# 获取当前网络的dict
net_state_dict = net.state_dict()
# 剔除不匹配的权值参数
pretrained_dict_1 = {k: v for k, v in pretrained_dict.items() if k in net_state_dict}
# 更新新模型参数字典
net_state_dict.update(pretrained_dict_1)
# 将包含预训练模型参数的字典"放"到新模型中
net.load_state_dict(net_state_dict)

设置学习率

为不同层设置不同的学习率,主要通过优化器对多个参数组进行设置不同的参数。分别进行设置学习率。

ignored_params = list(map(id, net.fc3.parameters()))#返回的是parameters 的 内存地址
base_params = filter(lambda p: id(p) not in ignored_params, net.parameters())# 返回 base params 的 内存地址

以上两句将f3层的参数 net.fc3.parameters()从原始参数net.parameters()中剥离出来,其中ignored_params是f3层的参数,base_params是剥离了f3层之后的剩余层的参数,分别为两部分设置不同的学习率。

params = [
    {"params": base_params},
    {"params": net.fc3.parameters(), "lr": 0.001},
]
optimizer = torch.optim.SGD(params, lr=0.0001, momentum=0.9, weight_decay=1e-4)

三、损失函数

常见的回归问题损失函数有绝对值损失、平方损失、Huber损失(SmoothL1Loss)

L1loss:

计算output和target之差的绝对值,用于回归,可选返回同维度的tensor或者是一个标量。
torch.nn.L1Loss(size_average=None, reduce=None)
参数:
reduce(bool)–返回值是否为标量,默认为True
size_average(bool)–当reduce=True时有效。当为True时,返回的loss为平均值;当为False时,返回的各样本的loss之和。

SmoothL1Loss:

当误差在(-1,1)上时为L2损失,其他情况下为L1损失,应用于回归问题,其最大的特点是对离群点、噪声不敏感,具有较强的鲁棒性。
torch.nn.SmoothL1Loss(size_average=None,reduce=None,reduction=‘elementwise_mean’)
参数:
reduce(bool)–返回值是否为标量,默认为True
size_average(bool)–当reduce=True时有效。当为True时,返回的loss为平均值;当为False时,返回的各样本的loss之和。

交叉熵损失:

二分类时可称Logistic Loss。交叉熵损失是先将 input经过softmax激活函数,将向量“归一化”成概率形式,然后再与target计算严格意义上交叉熵损失。
在多分类任务中,经常采用 softmax 激活函数+交叉熵损失函数,因为交叉熵描述了两个概
率分布的差异,然而神经网络输出的是向量,并不是概率分布的形式。所以需要 softmax
激活函数将一个向量进行“归一化”成概率分布的形式,再采用交叉熵损失函数计算 loss。
torch.nn.CrossEntropyLoss(weight=None, size_average=None, ignore_index=-100, reduce=None, reduction=‘elementwise_mean’)
参数:
weight(Tensor)- 为每个类别的 loss 设置权值,常用于类别不均衡问题。weight 必须是 float类型的 tensor,其长度要于类别 C 一致,即每一个类别都要设置有 weight。
size_average(bool)- 当 reduce=True 时有效。为 True 时,返回的 loss 为平均值;为 False时,返回的各样本的 loss 之和。
reduce(bool)- 返回值是否为标量,默认为 True
ignore_index(int)- 忽略某一类别,不计算其 loss,其 loss 会为 0,并且,在采用size_average 时,不会计算那一类的 loss,除的时候的分母也不会统计那一类的样本。

  • 1
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值