从零定义系列:
从零定义交叉熵损失函数(CrossEntropyLoss)
def cross_entropy(y_hat, y):
return -torch.log(y_hat[range(len(y_hat)), y])
简洁实现系列:
重要知识/思想/技巧:
自定义块
1. 块的基本结构
331(类) 3
import torch
from torch import nn
from torch.nn import functional as F
class MLP(nn.Module):
def __init__(self):
super().__init__()
self.hidden = nn.Linear(20, 256)
self.out = nn.Linear(256, 10)
def forward(self, X):
return self.out(F.relu(self.hidden(X)))
使用的时候只需:
net = MLP()
2. 块中的forward函数可以加入控制流
前向传播函数中也可以加入控制流
class FixedHiddenMLP(nn.Module):
def __init__(self):
super().__init__()
self.rand_weight = torch.rand((20, 20), requires_grad=False)
self.linear = nn.Linear(20, 20)
def forward(self, X):
X = self.linear(X)
X = F.relu(torch.mm(X, self.rand_weight) + 1)
X = self.linear(X)
while X.abs().sum > 1:
X /= 2
return X.sum()
以及这个最为代表性的3D影像项目中的net构建部分
class LunaModel(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv3d(1, 32, kernel_size=3, padding=1, bias=True)
self.relu1 = nn.ReLU()
self.maxpool1 = nn.MaxPool3d(2)
self.conv2 = nn.Conv3d(32, 64, kernel_size=3, padding=1, bias=True)
self.relu2 = nn.ReLU()
self.maxpool2 = nn.MaxPool3d(2)
self.flatten = nn.Flatten()
self.fc1 = nn.Linear(2048, 1024)
self.relu3 = nn.ReLU()
self.dropout = nn.Dropout(0.2)
self.fc2 = nn.Linear(1024, 2)
def forward(self, X):
# Dimensions of X => [BS, 1, 10, 18, 18]
X = self.maxpool1(self.relu1(self.conv1(X)))
X = self.maxpool2(self.relu2(self.conv2(X)))
X = self.flatten(X)
X = self.relu3(self.fc1(X))
X = self.dropout(X)
return self.fc2(X)
3. 神经网络块的灵活组织
nn.Sequential
可以十分灵活非常灵活特别灵活地把神经网络块排列成一个组合,变成一个神经网络块
class NestMLP(nn.module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(nn.Linear(20, 64), nn.ReLU(),
nn.Linear(64, 32), nn.ReLU())
self.linear = nn.Linear(32, 16)
def forward(self, X):
X = self.net(X)
X = self.linear(X)
return X
chimera = nn.Sequential(NestMLP(), nn.Linear(16, 20), FixedHiddenMLP())
技巧 / 工具系列:
Xavier 参数初始化
Xavier参数初始化可以增加数值稳定性,在pytorch框架下,可以如下简洁实现
nn.init.xavier_uniform_(self.fcl1.weight)
在定义神经网络块时,可以这样用在其中
class MLP(nn.Module):
def __init__(self):
super().__init__()
self.flatten = nn.Flatten()
self.fcl1 = nn.Linear(5, 256)
self.relu1 = nn.ReLU()
self.dropout1 = nn.Dropout(0.3)
nn.init.xavier_uniform_(self.fcl1.weight)
self.fcl2 = nn.Linear(256, 256)
self.relu2 = nn.ReLU()
self.dropout2 = nn.Dropout(0.2)
nn.init.xavier_uniform_(self.fcl2.weight)
self.fcl3 = nn.Linear(256, 1)
nn.init.xavier_uniform_(self.fcl3.weight)
def forward(self, X):
X = self.fcl1(X)
X = self.relu1(X)
X = self.dropout1(X)
X = self.fcl2(X)
X = self.relu2(X)
X = self.dropout2(X)
return self.fcl3(X)
jupyter notebook中显示cell运行时间
cell第一行写%%time
即可
jupyter notebook中执行终端命令
以!
开头,后面可以直接写终端命令(注意之间没有空格)
eg. !pip install tqdm
循环(用于训练中)显示进度条tqdm
首先import tqdm
库
from tqdm import tqdm
如果是在jupyter notebook里最好用这个(好看)
from tqdm.notebook import tqdm
(注意一定要这么写,不能只写import tqdm)
然后在训练过程中示例如下
for epoch in range(num_epochs):
for X, y in tqdm(data_iter):
y_hat = net.forward(X)
l = loss(y_hat.reshape(y.shape), y)
trainer.zero_grad()
l.sum().backward()
trainer.step()
print(f'epoch:{epoch + 1}, loss:{loss(net(features), labels).sum():.5f}')
延后初始化(defer initialization)
在神经网络中,延后初始化也是一种常见的技术。在 PyTorch 框架中,我们可以使用 torch.nn.Module
类的 lazy_init()
方法来实现延后初始化。
import torch
import torch.nn as nn
class MyModel(nn.Module):
def __init__(self):
super(MyModel, self).__init__()
self.linear = None
def lazy_init(self, input_size, output_size):
self.linear = nn.Linear(input_size, output_size)
def forward(self, x):
if self.linear is None:
# 延后初始化,只有在需要时才进行线性层的初始化
self.lazy_init(x.size(1), 10)
x = self.linear(x)
return x
在这个例子中,我们定义了一个名为 MyModel 的神经网络模型。模型中包含一个线性层,但在模型的初始化中并没有对该层进行初始化。相反,我们定义了一个 lazy_init() 方法,在该方法中对线性层进行了初始化。
在 forward() 方法中,我们首先检查线性层是否已经被初始化。如果没有被初始化,则调用 lazy_init() 方法对线性层进行初始化。然后将输入 x 传递给线性层,并返回线性层的输出。
通过这种方式,我们可以延迟对象的初始化,只有在需要时才进行对象创建或计算。这有助于减少内存使用和计算成本,从而提高神经网络的效率。
经验系列:
用l.sum().backward()和用l.mean().backward()的区别是什么
个人猜测两种损失Loss的计算方式要和不同的优化器Optimizer对应,因为根据梯度下降核心公式
θ
j
=
θ
j
−
α
∂
J
(
θ
)
∂
θ
j
\theta_j = \theta_j - \alpha\frac{\partial{J(\theta)}}{\partial{\theta_j}}
θj=θj−α∂θj∂J(θ)
J
(
θ
)
J(\theta)
J(θ)不同时,计算出的梯度更新值不同。
一问ChatGPT,果不其然:
l.sum().backward()和l.mean().backward()是两种不同的梯度计算方式。
- l.sum().backward()将所有损失值相加得到总损失,并计算总损失对模型参数的梯度。这种方式对应于在优化器中使用torch.optim.SGD等梯度下降算法,并且使用的是批次的总损失来更新模型参数。因此,如果使用l.sum().backward(),则需要将reduction参数设置为’none’,以便能够计算批次中每个样本的损失值。
- l.mean().backward()将所有损失值相加后取平均得到平均损失,并计算平均损失对模型参数的梯度。这种方式对应于在优化器中使用torch.optim.Adam等自适应学习率算法,因为它们可以自适应地调整学习率,从而更好地优化平均损失。
一般来说,如果使用批次的总损失来更新模型参数,应该使用l.sum().backward(),而如果使用平均损失来更新模型参数,则应该使用l.mean().backward()。但是,在某些情况下,根据具体情况选择使用哪种方式会更好。
nn.MSELoss(reduction=‘none’)中的reduction='none’是什么
在 PyTorch 中,当计算 loss 时,通常是将所有样本的损失值取平均,得到该 batch 的平均损失。而 reduction 参数则控制着如何对所有样本的损失进行合并,具体包括以下三种方式:
reduction='mean'
,将所有样本的损失值取平均,得到该 batch 的平均损失。reduction='sum'
,将所有样本的损失值加和,得到该 batch 的总损失。reduction='none'
,不进行任何处理,直接返回每个样本的损失值。这种方式通常用于需要对每个样本的损失进行单独处理的情况,例如带权重的损失计算。
因此,在 nn.MSELoss(reduction=‘none’) 中,损失函数直接返回每个样本的平方误差,而不是将它们取平均或加和。
assert float(torch.abs((Y1 - Y2)).sum()) < 1e-6这种方式来判断结果是否相等
这种方式被称为数值稳定性检查,它通过计算两个Tensor之间的绝对差的总和来检查它们是否足够相似。当两个Tensor具有相同的形状和元素值时,它们是相等的。但是,由于计算机的浮点精度限制,即使两个张量具有相同的形状和元素值,它们也可能有微小的舍入误差。因此,这种方式是为了确保两个Tensor非常接近并且这些差异可以忽略不计,即它们的总和应该小于某个足够小的阈值,例如1e-6。
在深度学习中,这种方式通常用于检查前向传递和反向传递是否正确,以确保模型的正确性和数值稳定性。
assert断言报错的时候显示自定义报错信息
如果你想让assert断言报错时显示自定义报错信息,可以在assert语句中添加一个字符串作为第二个参数,例如:
assert float(torch.abs((Y1 - Y2)).sum()) < 1e-6, "Y1 and Y2 are not equal"
这样,如果assert断言失败,会抛出AssertionError异常,并将自定义报错信息作为异常的错误信息。这样可以帮助你更好地理解为什么assert断言失败。