pytorch通过继承nn.Module类,定义子模块的实例化和前向传播,实现深度学习模型的搭建。其构建代码如下:
import torch
import torch.nn as nn
class Model(nn.Module):
def __init__(self, *kargs): # 定义类的初始化函数,...是用户的传入参数
super(Model, self).__init__()#调用父类nn.Module的初始化方法
... # 根据传入的参数来定义子模块
def forward(self, *kargs): # 定义前向计算的输入参数,...一般是张量或者其他的参数
ret = ... # 根据传入的张量和子模块计算返回张量
return ret
import torch
import torch.nn as nn
class LinearModel(nn.Module):
def __init__(self, ndim):
super(LinearModel, self).__init__()
self.ndim = ndim#输入的特征数
self.weight = nn.Parameter(torch.randn(ndim, 1)) # 定义权重
self.bias = nn.Parameter(torch.randn(1)) # 定义偏置
def forward(self, x):
# 定义线性模型 y = Wx + b
return x.mm(self.weight) + self.bias
实例化方法如下:
lm = LinearModel(5) # 定义线性回归模型,特征数为5
x = torch.randn(4, 5) # 定义随机输入,迷你批次大小为4
lm(x) # 得到每个迷你批次的输出
对于model.train()和model.eval()用法和区别进一步可以参考:《Pytorch:model.train()和model.eval()用法和区别》
对于上面定义的线性模型来举例:
lm.named_parameters() # 获取模型参数(带名字)的生成器
#
list(lm.named_parameters()) # 转换生成器为列表
[('weight',
Parameter containing:
tensor([[-1.0407],
[ 0.0427],
[ 0.4069],
[-0.7064],
[-1.1938]], requires_grad=True)),
('bias',
Parameter containing:
tensor([-0.7493], requires_grad=True))]
lm.parameters() # 获取模型参数(不带名字)的生成器
list(lm.parameters()) # 转换生成器为列表
[Parameter containing:
tensor([[-1.0407],
[ 0.0427],
[ 0.4069],
[-0.7064],
[-1.1938]], requires_grad=True),
Parameter containing:
tensor([-0.7493], requires_grad=True)]
lm.cuda()#模型参数转到GPU上
list(lm.parameters()) # 转换生成器为列表
model.train()是保证BN层能够用到每一批数据的均值和方差。对于Dropout,model.train()是在训练中随机去除神经元,用一部分网络连接来训练更新参数。如果被删除的神经元(叉号)是唯一促成正确结果的神经元。一旦我们移除了被删除的神经元,它就迫使其他神经元训练和学习如何在没有被删除神经元的情况下保持准确。这种dropout提高了最终测试的性能,但它对训练期间的性能产生了负面影响,因为网络是不全的
在测试时添加model.eval()。model.eval()是保证BN层能够用全部训练数据的均值和方差,即测试过程中要保证BN层的均值和方差不变(model.eval()时,框架会自动把BN和Dropout固定住,不会取平均,直接使用在训练阶段已经学出的mean和var值)
pytorch损失函数有两种形式:
import torch
import torch.nn as nn
mse = nn.MSELoss() # 初始化平方损失函数模块
#class torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean')
t1 = torch.randn(5, requires_grad=True) # 随机生成张量t1
tensor([ 0.6582, 0.0529, -0.9693, -0.9313, -0.7288], requires_grad=True)
t2 = torch.randn(5, requires_grad=True) # 随机生成张量t2
tensor([ 0.8095, -0.3384, -0.9510, 0.1581, -0.1863], requires_grad=True)
mse(t1, t2) # 计算张量t1和t2之间的平方损失函数
tensor(0.3315, grad_fn=<MseLossBackward>)
t1s = torch.sigmoid(t1)
t2 = torch.randint(0, 2, (5, )).float() # 随机生成0,1的整数序列,并转换为浮点数
bce=torch.nn.BCELoss()
(t1s, t2) # 计算二分类的交叉熵
bce_logits = nn.BCEWithLogitsLoss() # 使用交叉熵对数损失函数
bce_logits(t1, t2) # 计算二分类的交叉熵,可以发现和前面的结果一致
N=10 # 定义分类数目
t1 = torch.randn(5, N, requires_grad=True) # 随机产生预测张量
t2 = torch.randint(0, N, (5, )) # 随机产生目标张量
t1s = torch.nn.functional.log_softmax(t1, -1) # 计算预测张量的LogSoftmax
nll = nn.NLLLoss() # 定义NLL损失函数
nll(t1s, t2) # 计算损失函数
ce = nn.CrossEntropyLoss() # 定义交叉熵损失函数
ce(t1, t2) # 计算损失函数,可以发现和NLL损失函数的结果一致
完整文档参考:《torch.optim 》
以波士顿房价问题举例,构建SGD优化器。第一个参数是模型的参数生成器(lm.parameters()调用),第二个参数是学习率。训练时通过 optim.step()进行优化计算。
from sklearn.datasets import load_boston
boston = load_boston()
lm = LinearModel(13)
criterion = nn.MSELoss()
optim = torch.optim.SGD(lm.parameters(), lr=1e-6) # 定义优化器
data = torch.tensor(boston["data"], requires_grad=True, dtype=torch.float32)
target = torch.tensor(boston["target"], dtype=torch.float32)
for step in range(10000):
predict = lm(data) # 输出模型预测结果
loss = criterion(predict, target) # 输出损失函数
if step and step % 1000 == 0 :
print("Loss: {:.3f}".format(loss.item()))
optim.zero_grad() # 清零梯度
loss.backward() # 反向传播
optim.step()
torch.optim.SGD(params,lr=<required parameter>,momentum=0,
dampening=0,weight_decay=0,nesterov=False)
#momentum:动量因子
#dampening:动量抑制因子
#nesterov:设为True时使用nesterov动量
torch.optim.Adagrad(
params,lr=0.01,lr_decay=0,weight_decay=0,
initial_accumulator_value=0,eps=1e-10)
#lr_decay:学习率衰减速率
#weight_decay:权重衰减
#initial_accumulator_value:梯度初始累加值
对不同参数指定不同的学习率:
optim.SGD([
{'params': model.base.parameters()},
{'params': model.classifier.parameters(), 'lr': 1e-3}
], lr=1e-2, momentum=0.9)
这意味着model.base的参数将使用 的默认学习率1e-2, model.classifier的参数将使用 的学习率1e-3,0.9所有参数将使用动量 。
scheduler = StepLR(optimizer, step_size=30, gamma=0.1)
#没经过30的个迭代周期,学习率降为原来的0.1倍。每个epoch之后学习率都会衰减。
or epoch in range(100):
train(...)
validate(...)
scheduler.step()
大多数学习率调度器都可以称为背靠背(也称为链式调度器)。结果是每个调度器都被一个接一个地应用于前一个调度器获得的学习率。
例子:
model = [Parameter(torch.randn(2, 2, requires_grad=True))]
optimizer = SGD(model, 0.1)
scheduler1 = ExponentialLR(optimizer, gamma=0.9)
scheduler2 = MultiStepLR(optimizer, milestones=[30,80], gamma=0.1)
for epoch in range(20):
for input, target in dataset:
optimizer.zero_grad()
output = model(input)
loss = loss_fn(output, target)
loss.backward()
optimizer.step()
scheduler1.step()
scheduler2.step()
本节也可以参考《编写transformers的自定义pytorch训练循环(Dataset和DataLoader解析和实例代码)》
4.1 DataLoader参数
PyTorch 数据加载实用程序的核心是torch.utils.data.DataLoader 类。它代表一个 Python 可迭代的数据集,支持:
train_loader = DataLoader(dataset=train_data, batch_size=6, shuffle=True ,num_workers=4)
test_loader = DataLoader(dataset=test_data, batch_size=6, shuffle=False,num_workers=4)
下面看看dataloader代码:
class DataLoader(object):
def __init__(self, dataset, batch_size=1, shuffle=False, sampler=None,
batch_sampler=None, num_workers=0, collate_fn=None,
pin_memory=False, drop_last=False, timeout=0,
worker_init_fn=None,*, prefetch_factor=2,persistent_workers=False)
self.dataset = dataset
self.batch_size = batch_size
self.num_workers = num_workers
self.collate_fn = collate_fn
self.pin_memory = pin_memory
self.drop_last = drop_last
self.timeout = timeout
self.worker_init_fn = worker_init_fn
想用随机抽取的模式加载输入,可以设置 sampler 或 batch_sampler。如何定义抽样规则,可以看sampler.py脚本,或者这篇帖子:《一文弄懂Pytorch的DataLoader, DataSet, Sampler之间的关系》
DataLoader 构造函数最重要的参数是dataset,它表示要从中加载数据的数据集对象。PyTorch 支持两种不同类型的数据集:
其构造方法如下:
class Dataset(object):
def __getitem__(self, index):
# index: 数据缩索引(整数,范围为0到数据数目-1)
# ...
# 返回数据张量
def __len__(self):
# 返回数据的数目
# ...
主要重写两个方法:
更具体的可以参考《torch.utils.data.Dataset》
class MyIterableDataset(torch.utils.data.IterableDataset):
def __init__(self, start, end):
super(MyIterableDataset).__init__()
assert end > start, \
"this example code only works with end >= start"
self.start = start
self.end = end
def __iter__(self):
worker_info = torch.utils.data.get_worker_info()
if worker_info is None: # 单进程数据载入
iter_start = self.start
iter_end = self.end
else: # 多进程,分割数据
#根据不同工作进程序号worker_id,设置不同进程数据迭代器取值范围。保证不同进程获取不同的迭代器。
per_worker = int(math.ceil((self.end - self.start) \
/ float(worker_info.num_workers)))
worker_id = worker_info.id
iter_start = self.start + worker_id * per_worker
iter_end = min(iter_start + per_worker, self.end)
return iter(range(iter_start, iter_end))
更多详细信息,请参阅IterableDataset
将根据shufflea的参数自动构建顺序或混洗采样器DataLoader。或者,用户可以使用该sampler参数来指定一个自定义Sampler对象,该对象每次都会生成下一个要获取的索引/键。
一次Sampler生成批量索引列表的自定义可以作为batch_sampler参数传递。也可以通过batch_size和 drop_last参数启用自动批处理。
经由参数 batch_size,drop_last和batch_sampler,DataLoader支持批处理数据
当启用自动批处理时,每次都会使用数据样本列表调用 collate_fn。预计将输入样本整理成一个批次,以便从数据加载器迭代器中产生。
例如,如果每个数据样本由一个 3 通道图像和一个完整的类标签组成,即数据集的每个元素返回一个元组 (image, class_index),则默认 collate_fn 将此类元组的列表整理成单个元组一个批处理图像张量和一个批处理类标签张量。特别是,默认 collate_fn 具有以下属性:
它总是预先添加一个新维度作为批次维度。
它会自动将 NumPy 数组和 Python 数值转换为 PyTorch 张量。
它保留了数据结构,例如,如果每个样本是一个字典,它输出一个具有相同键集但批量张量作为值的字典(如果值不能转换为张量,则为列表)。列表 s、元组 s、namedtuple s 等也是如此。
用户可以使用自定义 collate_fn 来实现自定义批处理,例如,沿着除第一个维度之外的维度进行整理,填充各种长度的序列,或添加对自定义数据类型的支持。
torch.save(obj, f, pickle_module=pickle, pickle_protocol=2)
torch.load(f, map_location=None, pickle_module=pickle, **pickle_load_args)
torch.save参数
torch.load函数
如果模型保存在GPU中,而加载的当前计算机没有GPU,或者GPU设备号不对,可以使用map_location=‘cpu’。
PyTorch默认有两种模型保存方式:
torch.save(model.state_dict(), PATH)
model = TheModelClass(*args, **kwargs)
model.load_state_dict(torch.load(PATH))
model.eval()
以 Python `pickle 模块的方式来保存模型。这种方法的缺点是:
torch.save(model, PATH)
# 模型类必须在此之前被定义
model = torch.load(PATH)
model.eval()
一个模型的检查点代码如下:
torch.save({
'epoch': epoch,
'model_state_dict': model.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'loss': loss,
...
}, PATH)
加载
model = TheModelClass(*args, **kwargs)
optimizer = TheOptimizerClass(*args, **kwargs)
checkpoint = torch.load(PATH)
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
epoch = checkpoint['epoch']
loss = checkpoint['loss']
model.eval()#或model.train()
或者是:
save_info = { # 保存的信息
"iter_num": iter_num, # 迭代步数
"optimizer": optimizer.state_dict(), # 优化器的状态字典
"model": model.state_dict(), # 模型的状态字典
}
# 保存信息
torch.save(save_info, save_path)
# 载入信息
save_info = torch.load(save_path)
optimizer.load_state_dict(save_info["optimizer"])
model.load_state_dict(sae_info["model"])
torch.save({
'modelA_state_dict': modelA.state_dict(),
'modelB_state_dict': modelB.state_dict(),
'optimizerA_state_dict': optimizerA.state_dict(),
'optimizerB_state_dict': optimizerB.state_dict(),
...
}, PATH)
加载
modelA = TheModelAClass(*args, **kwargs)
modelB = TheModelBClass(*args, **kwargs)
optimizerA = TheOptimizerAClass(*args, **kwargs)
optimizerB = TheOptimizerBClass(*args, **kwargs)
checkpoint = torch.load(PATH)
modelA.load_state_dict(checkpoint['modelA_state_dict'])
modelB.load_state_dict(checkpoint['modelB_state_dict'])
optimizerA.load_state_dict(checkpoint['optimizerA_state_dict'])
optimizerB.load_state_dict(checkpoint['optimizerB_state_dict'])
modelA.eval()
modelB.eval()
当保存一个模型由多个 torch.nn.Modules 组成时,例如GAN(对抗生成网络)、sequence-to-
sequence (序列到序列模型), 或者是多个模 型融合, 可以采用与保存常规检查点相同的方法。
换句话说,保存每个模型的 state_dict 的字典和相对应的优化器。如前所述,可以通 过简单地
将它们附加到字典的方式来保存任何其他项目,这样有助于恢复训练。
pip install tensorflow-tensorboard
pip install tensorboard
安装完之后import tensorboard时报错ImportError: TensorBoard logging requires TensorBoard version 1.15 or above
试了几种方法。最后关掉ipynb文件,新建一个ipynb文件复制代码运行就好了。
from sklearn.datasets import load_boston
from torch.utils.tensorboard import SummaryWriter
import torch
import torch.nn as nn
#定义线性回归模型
class LinearModel(nn.Module):
def __init__(self, ndim):
super(LinearModel, self).__init__()
self.ndim = ndim
self.weight = nn.Parameter(torch.randn(ndim, 1)) # 定义权重
self.bias = nn.Parameter(torch.randn(1)) # 定义偏置
def forward(self, x):
# 定义线性模型 y = Wx + b
return x.mm(self.weight) + self.bias
boston = load_boston()
lm = LinearModel(13)
criterion = nn.MSELoss()
optim = torch.optim.SGD(lm.parameters(), lr=1e-6)
data = torch.tensor(boston["data"], requires_grad=True, dtype=torch.float32)
target = torch.tensor(boston["target"], dtype=torch.float32)
writer = SummaryWriter() # 构造摘要生成器,定义TensorBoard输出类
for step in range(10000):
predict = lm(data)
loss = criterion(predict, target)
writer.add_scalar("Loss/train", loss, step) # 输出损失函数
writer.add_histogram("Param/weight", lm.weight, step) # 输出权重直方图
writer.add_histogram("Param/bias", lm.bias, step) # 输出偏置直方图
if step and step % 1000 == 0 :
print("Loss: {:.3f}".format(loss.item()))
optim.zero_grad()
loss.backward()
optim.step()
writer.close()
训练完之后,在当前目录下面会生成一个文件夹runs。runs下面还有一个文件夹(名字和训练时间、主机名称有关)
writer = SummaryWriter(log_dir=None, comment='',
purge_step=None, max_queue=10, flush_secs=120, filename_suffix='')
add_scalar(tag, scalar_value, global_step=None, walltime=None)
于在tensorboard中加入loss,其中常用参数有:
- tag:不同图表的标签,如下图所示的Train_loss。
- scalar_value:标签的值,浮点数
- global_step:当前迭代步数,标签的x轴坐标
- walltime:迭代时间函数。如果不传入,方法内部使用time.time()返回一个浮点数代表时间
writer.add_scalar('Train_loss', loss, (epoch*epoch_size + iteration))
add_scalars(main_tag, tag_scalar_dict, global_step=None, walltime=None)
和上一个方法类似,通过传入一个主标签(main_tag),然后传入键值对是标签和标量值的一个字典(tag_scalar_dict),对每个标量值进行显示。
显示张量分量的直方图和对应的分布
add_histogram(tag, values, global_step=None, bins='tensorflow', walltime=None, max_bins=None)
传入pytorch模块及输入,显示模块对应的计算图
if Cuda:
graph_inputs = torch.from_numpy(np.random.rand(1,3,input_shape[0],input_shape[1])).type(torch.FloatTensor).cuda()
else:
graph_inputs = torch.from_numpy(np.random.rand(1,3,input_shape[0],input_shape[1])).type(torch.FloatTensor)
writer.add_graph(model, (graph_inputs,))
显示准确率-召回率曲线(Prediction-Recall Curve)。
add_pr_curve(tag, labels, predictions, global_step=None, num_thresholds=127,
weights=None, walltime=None)
完成tensorboard文件的生成后,可在命令行调用该文件,tensorboard网址。
#打开cmd命令
tensorboard --logdir=.\Chapter2\runs --bind_all
#TensorBoard 2.2.2 at http://DESKTOP-OHLNREI:6006/ (Press CTRL+C to quit)
右上方三个依次是:
权重分布和直方图应该是随着训练一直变化,直到分布稳定。如果一直没有变化,可能模型结构有问题或者反向传播有问题。
Scalars:这个面板是最常用的面板,主要用于将神经网络训练过程中的acc(训练集准确率)val_acc(验证集准确率),loss(损失值),weight(权重)等等变化情况绘制成折线图。