add函数 pytorch_pytorch学习笔记[1]

本笔记主要学习的是《深度学习框架PyTorch:入门与实践》

本节的笔记是学习 文件组织架构模型定义

数据处理和加载

训练模型(Train&Validate)

训练过程的可视化

测试(Test/Inference)

另外程序还应该满足以下几个要求:模型需具有高度可配置性,便于修改参数、修改模型,反复实验

代码应具有良好的组织结构,使人一目了然

代码应具有良好的说明,使其他人能够理解

首先来看程序文件的组织结构:

├── checkpoints/

├── data/

│ ├── __init__.py

│ ├── dataset.py

│ └── get_data.sh

├── models/

│ ├── __init__.py

│ ├── AlexNet.py

│ ├── BasicModule.py

│ └── ResNet34.py

└── utils/

│ ├── __init__.py

│ └── visualize.py

├── config.py

├── main.py

├── requirements.txt

├── README.mdcheckpoints/: 用于保存训练好的模型,可使程序在异常退出后仍能重新载入模型,恢复训练

data/:数据相关操作,包括数据预处理、dataset实现等

models/:模型定义,可以有多个模型,例如上面的AlexNet和ResNet34,一个模型对应一个文件

utils/:可能用到的工具函数,在本次实验中主要是封装了可视化工具

config.py:配置文件,所有可配置的变量都集中在此,并提供默认值

main.py:主文件,训练和测试程序的入口,可通过不同的命令来指定不同的操作和参数

requirements.txt:程序依赖的第三方库

README.md:提供程序的必要说明

加载数据

Dataset提供数据集的封装,再使用Dataloader实现数据并行加载。写一个class继承Dataset。对于训练集,我们希望做一些数据增强处理,如随机裁剪、随机翻转、加噪声等,而验证集和测试集则不需要。将文件读取等费时操作放在__getitem__函数中,利用多进程加速。避免一次性将所有图片都读进内存,不仅费时也会占用较大内存,而且不易进行数据增强等操作。

import os

from PIL import Image

from torch.utils import data

import numpy as np

from torchvision import transforms as T

class DogCat(data.Dataset):

def __init__(self, root, transforms=None, train=True, test=False):

"""目标:获取所有图片地址,并根据训练、验证、测试划分数据"""

self.test = test

imgs = [os.path.join(root, img) for img in os.listdir(root)]

# test1: data/test1/8973.jpg

# train: data/train/cat.10004.jpg

if self.test:

imgs = sorted(imgs, key=lambda x: int(x.split('.')[-2].split('/')[-1]))

else:

imgs = sorted(imgs, key=lambda x: int(x.split('.')[-2]))

imgs_num = len(imgs)

# 划分训练、验证集,验证:训练 = 3:7

if self.test:

self.imgs = imgs

elif train:

self.imgs = imgs[:int(0.7*imgs_num)]

else :

self.imgs = imgs[int(0.7*imgs_num):]

if transforms is None:

# 数据转换操作,测试验证和训练的数据转换有所区别

normalize = T.Normalize(mean = [0.485, 0.456, 0.406],

std = [0.229, 0.224, 0.225])

# 测试集和验证集

if self.test or not train:

self.transforms = T.Compose([

T.Resize(224),

T.CenterCrop(224),

T.ToTensor(),

normalize

])

# 训练集

else :

self.transforms = T.Compose([

T.Resize(256),

T.RandomReSizedCrop(224),

T.RandomHorizontalFlip(),

T.ToTensor(),

normalize

])

def __getitem__(self, index):

"""返回一张图片的数据对于测试集,没有label,返回图片id,如1000.jpg返回1000"""

img_path = self.imgs[index]

if self.test:

label = int(self.imgs[index].split('.')[-2].split('/')[-1])

else:

label = 1 if 'dog' in img_path.split('/')[-1] else 0

data = Image.open(img_path)

data = self.transforms(data)

return data, label

def __len__(self):

"""返回数据集中所有图片的个数"""

return len(self.imgs)

使用

train_dataset = DogCat(opt.train_data_root, train=True)

trainloader = DataLoader(train_dataset,

batch_size = opt.batch_size,

shuffle = True,

num_workers = opt.num_workers)

for ii, (data, label) in enumerate(trainloader):

train()

utils文件夹:一些helper方法比如本例的可视化工具visdom的一些操作

import visdom

import time

import numpy as np

class Visualizer(object):

"""封装了visdom的基本操作,但是你仍然可以通过`self.vis.function`或者`self.function`调用原生的visdom接口比如self.text('hello visdom')self.histogram(t.randn(1000))self.line(t.arange(0, 10),t.arange(1, 11))"""

def __init__(self, env='default', **kwargs):

self.vis = visdom.Visdom(env=env, **kwargs)

# 画的第几个数,相当于横坐标

# 保存(’loss',23) 即loss的第23个点

self.index = {}

self.log_text = ''

def reinit(self, env='default', **kwargs):

"""修改visdom的配置"""

self.vis = visdom.Visdom(env=env, **kwargs)

return self

def plot_many(self, d):

"""一次plot多个@params d: dict (name, value) i.e. ('loss', 0.11)"""

for k, v in d.items():

self.plot(k, v)

def img_many(self, d):

for k, v in d.items():

self.img(k, v)

def plot(self, name, y, **kwargs):

"""self.plot('loss', 1.00)"""

x = self.index.get(name, 0)

self.vis.line(Y=np.array([y]), X=np.array([x]),

win=name,

opts=dict(title=name),

update=None if x == 0 else 'append',

**kwargs

)

self.index[name] = x + 1

def img(self, name, img_, **kwargs):

"""self.img('input_img', t.Tensor(64, 64))self.img('input_imgs', t.Tensor(3, 64, 64))self.img('input_imgs', t.Tensor(100, 1, 64, 64))self.img('input_imgs', t.Tensor(100, 3, 64, 64), nrows=10)!!! don't ~~self.img('input_imgs', t.Tensor(100, 64, 64), nrows=10)~~ !!!"""

self.vis.images(img_.cpu().numpy(),

win=name,

opts=dict(title=name),

**kwargs

)

def log(self, info, win='log_text'):

"""self.log({'loss':1, 'lr':0.0001})"""

self.log_text += ('[{time}]{info}
'.format(

time=time.strftime('%m%d_%H%M%S'),\

info=info))

self.vis.text(self.log_text, win)

def __getattr__(self, name):

"""自定义的plot,image,log,plot_many等除外self.function 等价于self.vis.function"""

return getattr(self.vis, name)

定义一些变量并给出默认值,可更新

class DefaultConfig(object):

env = 'default' # visdom 环境

model = 'AlexNet' # 使用的模型,名字必须与models/__init__.py中的名字一致

train_data_root = './data/train/' # 训练集存放路径

test_data_root = './data/test1' # 测试集存放路径

load_model_path = 'checkpoints/model.pth' # 加载预训练的模型的路径,为None代表不加载

batch_size = 128 # batch size

use_gpu = True # use GPU or not

num_workers = 4 # how many workers for loading data

print_freq = 20 # print info every N batch

debug_file = '/tmp/debug' # if os.path.exists(debug_file): enter ipdb

result_file = 'result.csv'

max_epoch = 10

lr = 0.1 # initial learning rate

lr_decay = 0.95 # when val_loss increase, lr = lr*lr_decay

weight_decay = 1e-4 # 损失函数

def parse(self, kwargs):

"""根据字典kwargs 更新 config参数"""

# 更新配置参数

for k, v in kwargs.items():

if not hasattr(self, k):

# 警告还是报错,取决于你个人的喜好

warnings.warn("Warning: opt has not attribut%s" %k)

setattr(self, k, v)

# 打印配置信息

print('user config:')

for k, v in self.__class__.__dict__.items():

if not k.startswith('__'):

print(k, getattr(self, k))opt = DefaultConfig()

new_config = {'lr':0.1,'use_gpu':False}

opt.parse(new_config)

结果:

user config:

env default

model AlexNet

train_data_root ./data/train/

test_data_root ./data/test1

load_model_path checkpoints/model.pth

batch_size 128

use_gpu False

num_workers 4

print_freq 20

debug_file /tmp/debug

result_file result.csv

max_epoch 10

lr 0.1

lr_decay 0.95

weight_decay 0.0001

parse >

requirements.txt

fire

numpy

torchvision

visdom

使用:

pip install -r requirements.txt

__init__.py

几乎每个文件夹下都有__init__.py,一个目录如果包含了__init__.py 文件,那么它就变成了一个包(package)。__init__.py可以为空,也可以定义包的属性和方法,但其必须存在,其它程序才能从这个目录中导入相应的模块或函数。例如在data/文件夹下有__init__.py,则在main.py 中就可以from data.dataset import DogCat。而如果在__init__.py中写入from .dataset import DogCat,则在main.py中就可以直接写为:from data import DogCat,或者import data; dataset = data.DogCat,相比于from data.dataset import DogCat更加便捷。

模型定义:models文件夹

BasicModule是对nn.Module的简易封装,提供快速加载和保存模型的接口。自定义模型一般继承BasicModule,然后实现自己的模型。

#__init__.py

from .AlexNet import AlexNet

from .ResNet34 import ResNet34

主函数就可以from models import AlexNet

在主程序main.py中,主要包含四个函数,其中三个需要命令行执行,main.py的代码组织结构如下:

def train(**kwargs):

"""训练"""

pass

def val(model, dataloader):

"""计算模型在验证集上的准确率等信息,用以辅助训练"""

pass

def test(**kwargs):

"""测试(inference)"""

pass

def help():

"""打印帮助的信息"""

print('help')

if __name__=='__main__':

import fire

fire.Fire()

先讲一下Fire

import fire

def add(x, y):

return x + y

def mul(**kwargs):

a = kwargs['a']

b = kwargs['b']

return a * b

if __name__ == '__main__':

fire.Fire()python example.py add 1 2 # 执行add(1, 2)

python example.py mul --a=1 --b=2 # 执行mul(a=1, b=2), kwargs={'a':1, 'b':2}

python example.py add --x=1 --y==2 # 执行add(x=1, y=2)

python main.py --args=xx运行相关函数

训练

训练的主要步骤如下:定义网络

定义数据

定义损失函数和优化器

计算重要指标

开始训练训练网络

可视化各种指标

计算在验证集上的指标

def train(**kwargs):

# 根据命令行参数更新配置

opt.parse(kwargs)

vis = Visualizer(opt.env)

# step1: 模型

model = getattr(models, opt.model)()

if opt.load_model_path:

model.load(opt.load_model_path)

if opt.use_gpu: model.cuda()

# step2: 数据

train_data = DogCat(opt.train_data_root,train=True)

val_data = DogCat(opt.train_data_root,train=False)

train_dataloader = DataLoader(train_data,opt.batch_size,

shuffle=True,

num_workers=opt.num_workers)

val_dataloader = DataLoader(val_data,opt.batch_size,

shuffle=False,

num_workers=opt.num_workers)

# step3: 目标函数和优化器

criterion = t.nn.CrossEntropyLoss()

lr = opt.lr

optimizer = t.optim.Adam(model.parameters(),

lr = lr,

weight_decay = opt.weight_decay)

# step4: 统计指标:平滑处理之后的损失,还有混淆矩阵

loss_meter = meter.AverageValueMeter()

confusion_matrix = meter.ConfusionMeter(2)

previous_loss = 1e100

# 训练

for epoch in range(opt.max_epoch):

loss_meter.reset()

confusion_matrix.reset()

for ii,(data,label) in enumerate(train_dataloader):

# 训练模型参数

input = Variable(data)

target = Variable(label)

if opt.use_gpu:

input = input.cuda()

target = target.cuda()

optimizer.zero_grad()

score = model(input)

loss = criterion(score,target)

loss.backward()

optimizer.step()

# 更新统计指标以及可视化

loss_meter.add(loss.data[0])

confusion_matrix.add(score.data, target.data)

if ii%opt.print_freq==opt.print_freq-1:

vis.plot('loss', loss_meter.value()[0])

# 如果需要的话,进入debug模式

if os.path.exists(opt.debug_file):

import ipdb;

ipdb.set_trace()

model.save()

# 计算验证集上的指标及可视化

val_cm,val_accuracy = val(model,val_dataloader)

vis.plot('val_accuracy',val_accuracy)

vis.log("epoch:{epoch},lr:{lr},loss:{loss},train_cm:{train_cm},val_cm:{val_cm}"

.format(

epoch = epoch,

loss = loss_meter.value()[0],

val_cm = str(val_cm.value()),

train_cm=str(confusion_matrix.value()),

lr=lr))

# 如果损失不再下降,则降低学习率

if loss_meter.value()[0] > previous_loss:

lr = lr * opt.lr_decay

for param_group in optimizer.param_groups:

param_group['lr'] = lr

previous_loss = loss_meter.value()[0]

验证

验证和训练时BN、dropout等层会有差异,所以要调整模式,验证时model.eval(),验证完成model.train()

def val(model,dataloader):

"""计算模型在验证集上的准确率等信息"""

# 把模型设为验证模式

model.eval()

confusion_matrix = meter.ConfusionMeter(2)

for ii, data in enumerate(dataloader):

input, label = data

val_input = Variable(input, volatile=True)

val_label = Variable(label.long(), volatile=True)

if opt.use_gpu:

val_input = val_input.cuda()

val_label = val_label.cuda()

score = model(val_input)

confusion_matrix.add(score.data.squeeze(), label.long())

# 把模型恢复为训练模式

model.train()

cm_value = confusion_matrix.value()

accuracy = 100. * (cm_value[0][0] + cm_value[1][1]) /\

(cm_value.sum())

return confusion_matrix, accuracy

测试:测试的代码与验证比较相似,但需要自己加载模型和数据。

def test(**kwargs):

opt.parse(kwargs)

# 模型

model = getattr(models, opt.model)().eval()

if opt.load_model_path:

model.load(opt.load_model_path)

if opt.use_gpu: model.cuda()

# 数据

train_data = DogCat(opt.test_data_root,test=True)

test_dataloader = DataLoader(train_data,\

batch_size=opt.batch_size,\

shuffle=False,\

num_workers=opt.num_workers)

results = []

for ii,(data,path) in enumerate(test_dataloader):

input = t.autograd.Variable(data,volatile = True)

if opt.use_gpu: input = input.cuda()

score = model(input)

probability = t.nn.functional.softmax\

(score)[:,1].data.tolist()

batch_results = [(path_,probability_) \

for path_,probability_ in zip(path,probability) ]

results += batch_results

write_csv(results,opt.result_file)

return results

帮助函数:展示程序使用案例,其中inspect方法,可以自动获取config的源代码。

def help():

"""打印帮助的信息: python file.py help"""

print("""usage : python{0} [--args=value,] := train | test | helpexample:python{0}train --env='env0701' --lr=0.01python{0}test --dataset='path/to/dataset/root/'python{0}helpavaiable args:""".format(__file__))

from inspect import getsource

source = (getsource(opt.__class__))

print(source)执行python main.py help

usage : python main.py [--args=value,]

:= train | test | help

example:

python main.py train --env='env0701' --lr=0.01

python main.py test --dataset='path/to/dataset/'

python main.py help

avaiable args:

class DefaultConfig(object):

env = 'default' # visdom 环境

model = 'AlexNet' # 使用的模型

train_data_root = './data/train/' # 训练集存放路径

test_data_root = './data/test1' # 测试集存放路径

load_model_path = 'checkpoints/model.pth' # 加载预训练的模型

batch_size = 128 # batch size

use_gpu = True # user GPU or not

num_workers = 4 # how many workers for loading data

print_freq = 20 # print info every N batch

debug_file = '/tmp/debug'

result_file = 'result.csv' # 结果文件

max_epoch = 10

lr = 0.1 # initial learning rate

lr_decay = 0.95 # when val_loss increase, lr = lr*lr_decay

weight_decay = 1e-4 # 损失函数

根据example展示的案例可以这样使用程序

# 训练模型

python main.py train

--train-data-root=data/train/

--lr=0.005

--batch-size=32

--model='ResNet34'

--max-epoch = 20

# 测试模型

python main.py test

--test-data-root=data/test1

--load-model-path='checkpoints/resnet34_00:23:05.pth'

--batch-size=128

--model='ResNet34'

--num-workers=12

# 打印帮助信息

python main.py help

参考:

你可能感兴趣的:(add函数,pytorch)