Pytorch_神经网络基础
文章目录
加速训练的方法不一定会有1+1>2的效果,要具体问题具体分析!
API:torch.nn — PyTorch 2.1 documentation
神经网络实现的三大核心:
- 激活函数
torch.nn.functional — PyTorch 2.1 documentation
- 损失函数
torch.nn — PyTorch 2.1 documentation
1 激活函数
API:torch.nn.functional — PyTorch 2.1 documentation
常用的激活函数有 relu
, sigmoid
, tanh
, softplus
,可以直接在torch库中的torch.nn.functional调用,调用方式如下:
import torch.nn .functional as F
x = torch.linspace(-5, 5, 200) # x data (tensor), shape=(100, 1)
x = Variable(x)
F.relu(x)
F.sigmoid(x)
F.tanh(x)
F.softplus(x)
F.softmax(x) #通常用于多类别分类问题中。它的原理是将一个向量的元素转化为概率分布,使得每个元素的取值范围在0到1之间,并且所有元素的和为1
2 损失函数
3 优化器与训练方法
3.1 优化器
API:torch.optim — PyTorch 2.1 documentation
资料链接:CS231n Convolutional Neural Networks for Visual Recognition
3.2 优化方法及理论
3.2.1 分类与对比
方法 | 用法 | 优势 | 劣势 |
---|---|---|---|
随机梯度下降法 SGD:Stochastic Gradient Descent | optimizer = torch.optim.SGD(model.parameters(), lr=0.1) | ||
Momentum | optimizer = torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.9) | ||
AdaGrad | |||
RMSProp | torch.optim.RMSprop(net_RMSprop.parameters(), lr=LR, alpha=0.9) | ||
Adam | torch.optim.Adam(net_Adam.parameters(), lr=LR, betas=(0.9,0.99)) | 相较于前面,最优的方法 | |
方法 | 简约公式 | 用法 | 伪代码 |
---|---|---|---|
随机梯度下降法 SGD:Stochastic Gradient Descent |
x
=
x
−
α
⋅
∂
E
∂
x
x=x-\alpha·\frac{\partial E }{\partial x}
x=x−α⋅∂x∂E | 选择Loss减少最快的方向,负梯度方向 与梯度下降法的不同之处在于:SGD计算批次数据,GD计算全部数据 | |
动量 Momentum(b) |
m
=
b
⋅
m
−
α
⋅
∂
E
∂
x
m=b·m-\alpha·\frac{\partial E }{\partial x}
m=b⋅m−α⋅∂x∂E x = x + m x=x+m x=x+m | 选择Loss减少的方向,不一定要走最快的方向;寻优走下坡,动量b越大,越能摆脱局域最小值。 | |
自适应梯度法 AdaGrad:Adaptive Gradient |
v
=
v
+
(
∂
E
∂
x
)
2
v=v+(\frac{\partial E }{\partial x})^2
v=v+(∂x∂E)2 x = x − α ⋅ ∂ E ∂ x / v x=x-\alpha·\frac{\partial E }{\partial x}/\sqrt{v} x=x−α⋅∂x∂E/v | 每次参数更新都有不同的学习率 α / v \alpha/\sqrt{v} α/v,越学习,学习率越小;给错误方向添加阻力 | |
均方根传播算法 RMSProp(b):Root Mean Square Propagation |
v
=
b
⋅
v
+
(
1
−
b
)
⋅
(
∂
E
∂
x
)
2
v=b·v+(1-b)·(\frac{\partial E }{\partial x})^2
v=b⋅v+(1−b)⋅(∂x∂E)2 x = x − α ⋅ ∂ E ∂ x / v x=x-\alpha·\frac{\partial E }{\partial x}/\sqrt{v} x=x−α⋅∂x∂E/v | RMSProp约= Momentum+ AdaGrad | |
适应性矩估计法:Adam(b1,b2):adaptive moment estimation |
m
=
b
1
⋅
m
+
(
1
−
b
1
)
⋅
∂
E
∂
x
m=b_1·m+(1-b_1)·\frac{\partial E }{\partial x}
m=b1⋅m+(1−b1)⋅∂x∂E v = b 2 ⋅ v + ( 1 − b 2 ) ⋅ ( ∂ E ∂ x ) 2 v=b_2·v+(1-b_2)·(\frac{\partial E }{\partial x})^2 v=b2⋅v+(1−b2)⋅(∂x∂E)2 x = x − α ⋅ ∂ E ∂ x / v x=x-\alpha·\frac{\partial E }{\partial x}/\sqrt{v} x=x−α⋅∂x∂E/v | Adam= Momentum+ AdaGrad |
注意: E E E为损失函数, x x x为神经网络权重, α \alpha α为步长。
彼此关系:
S
G
D
⊆
m
o
m
e
n
t
u
m
⊆
R
M
S
P
r
o
p
S
G
D
⊆
m
o
m
e
n
t
u
m
⊆
A
d
a
m
SGD\subseteq momentum\subseteq RMSProp\\ SGD\subseteq momentum\subseteq Adam\\
SGD⊆momentum⊆RMSPropSGD⊆momentum⊆Adam
3.3 代码展示
此代码融合了多个网络同时训练和批训练的方法!!
optiSolution.py
#对比不同的优化器方法
import torch
import torch.utils.data as Data
import matplotlib.pyplot as plt
torch.manual_seed(1) # reproducible
LR = 0.01
BATCH_SIZE = 20 # 批训练的数据个数
EPOCH = 12 #整体数据训练次数
#建立数据库
x = torch.unsqueeze(torch.linspace(-1, 1, 1000, requires_grad=True), dim=1) # x data (tensor), shape=(1000, 1) unsqueeze升维
y = x.pow(2) + 0.2*torch.rand(x.size()) # noisy y data (tensor), shape=(100, 1)
# 先转换成 torch 能识别的 Dataset数据库(全部数据)
torch_dataset = Data.TensorDataset(x, y)
# 把 dataset 放入 DataLoader
loader = Data.DataLoader(
dataset=torch_dataset, # torch TensorDataset format
batch_size=BATCH_SIZE, # mini batch size
shuffle=True, # 要不要打乱数据 (打乱比较好)
# num_workers=2, # 多线程来读数据 CPU版本不支持
)
n_feature, n_hidden, n_output=1, 20, 1 #类的实例化
net_SGD = torch.nn.Sequential(
torch.nn.Linear(n_feature, n_hidden),
torch.nn.ReLU(), #激励函数的库不同 !!!
torch.nn.Linear(n_hidden, n_output)
)
net_Momentum = torch.nn.Sequential(
torch.nn.Linear(n_feature, n_hidden),
torch.nn.ReLU(), #激励函数的库不同 !!!
torch.nn.Linear(n_hidden, n_output)
)
net_RMSprop = torch.nn.Sequential(
torch.nn.Linear(n_feature, n_hidden),
torch.nn.ReLU(), #激励函数的库不同 !!!
torch.nn.Linear(n_hidden, n_output)
)
net_Adam = torch.nn.Sequential(
torch.nn.Linear(n_feature, n_hidden),
torch.nn.ReLU(), #激励函数的库不同 !!!
torch.nn.Linear(n_hidden, n_output)
)
#将所有网络放入list中
nets = [net_SGD, net_Momentum, net_RMSprop, net_Adam]
#神经网络训练过程
# optimizer 是训练的工具
opti_SGD = torch.optim.SGD(net_SGD.parameters(), lr=LR)
opti_Momentum = torch.optim.SGD(net_Momentum.parameters(), lr=LR, momentum= 0.8)
opti_RMSprop = torch.optim.RMSprop(net_RMSprop.parameters(), lr=LR, alpha=0.9)
opti_Adam = torch.optim.Adam(net_Adam.parameters(), lr=LR, betas=(0.9,0.99))
optimizers = [opti_SGD, opti_Momentum, opti_RMSprop, opti_Adam]
loss_func = torch.nn.MSELoss() # 预测值和真实值的误差计算公式 (均方差)
#记录误差
loss_record = [[],[],[],[]]
for epoch in range(EPOCH): # 训练所有!整套!数据 3 次 epoch是整体数据训练次数;step是数据拆分组数
for step, (batch_x, batch_y) in enumerate(loader): # 每一步 loader 释放一小批数据用来学习,可以设置是否乱序
# 打出来一些数据
# print('Epoch: ', epoch, '| Step: ', step, '| batch x: ',
# batch_x, '| batch y: ', batch_y)
#多个网络同时训练的方法!!!
for net, opti, l_record in zip(nets, optimizers, loss_record): #列表形式
out = net(batch_x) # 前向传播,喂给 net 训练数据 x, 输出预测值
loss = loss_func(out, batch_y) # 计算两者的误差:预测值在前,真实值在后
opti.zero_grad() # 清空上一步的梯度参数值(此步必须!!)
loss.backward(retain_graph=True) # 误差反向传播, 计算参数更新值 保留反向传播计算图
opti.step() # 参数更新,将参数更新值施加到 net 的 parameters 上
l_record.append(loss.data)
labels = ['SGD','Momentum', 'RMSprop', 'Adam']
for i, l_record in enumerate(loss_record):
plt.plot(l_record, label = labels[i])
plt.legend(loc='best')
plt.xlabel('step') #step = (1000/20)*12 =600
plt.ylabel('loss')
plt.ylim(0, 0.2)
plt.show()
4 回归:关系拟合
4.1 神经网络搭建
- 建立数据集
使用Torch中的随机生成简单训练数据集,y数据增加一些噪声。
- 建立神经网络
继承torch中的torch.nn.Module,. 先定义所有的层属性(__init__()
), 然后再一层层搭建(forward(x)
)层于层的关系链接。
- 训练网络
训练网络包括前向传播、后向传播、参数更新。
-
-
选择优化器和反向传播算法:
-
误差(损失)函数
torch.nn — PyTorch 2.1 documentation
回归问题是使用均方误差: torch**.nn.**MSELoss()
-
-
可视化训练过程
每5轮可视化一次,保持窗口停顿。
Matplotlib API:
Using Matplotlib — Matplotlib 3.8.2 documentation
4.2 代码展示
Regression.py
import torch
#import torch.autograd #variable变量
import torch.nn.functional as F #激励函数库
import matplotlib.pyplot as plt
#建立数据库
x = torch.unsqueeze(torch.linspace(-1, 1, 100, requires_grad=True), dim=1) # x data (tensor), shape=(100, 1) unsqueeze升维
y = x.pow(2) + 0.2*torch.rand(x.size()) # noisy y data (tensor), shape=(100, 1)
# 画图
# plt.scatter(x.data, y.data)
# plt.show()
#建立神经网络
class Net(torch.nn.Module): # 继承 torch 的 Module
def __init__(self, n_feature, n_hidden, n_output):
super(Net, self).__init__() # 继承 __init__ 功能
# 定义每层用什么样的形式
self.hidden = torch.nn.Linear(n_feature, n_hidden) # 隐藏层线性输出模块
self.predict = torch.nn.Linear(n_hidden, n_output) # 输出层线性输出模块
def forward(self, x): # 这同时也是 Module 中的 forward 功能
# 正向传播输入值, 神经网络分析出输出值
x = F.relu(self.hidden(x)) # 激励函数(隐藏层的非线性值)
x = self.predict(x) # 输出值
return x
net = Net(n_feature=1, n_hidden=10, n_output=1) #类的实例化
# print(net) # net 的结构 居然可以直接打印!
# """
# Net (
# (hidden): Linear (1 -> 10)
# (predict): Linear (10 -> 1)
# )
# """
#可视化
plt.ion() #设置其为实时显示
plt.show()
#神经网络训练过程
# optimizer 是训练的工具
optimizer = torch.optim.SGD(net.parameters(), lr=0.2) # 传入 net 的所有参数, 学习率,采用随机梯度下降法
loss_func = torch.nn.MSELoss() # 预测值和真实值的误差计算公式 (均方差)
for t in range(100):
prediction = net(x) # 前向传播,喂给 net 训练数据 x, 输出预测值
loss = loss_func(prediction, y) # 计算两者的误差:预测值在前,真实值在后
optimizer.zero_grad() # 清空上一步的梯度参数值(此步必须!!)
loss.backward(retain_graph=True) # 误差反向传播, 计算参数更新值 保留反向传播计算图
optimizer.step() # 参数更新,将参数更新值施加到 net 的 parameters 上
#print(t+1,loss.data)
#可视化
if t % 5 == 0:
plt.cla() #清空
plt.scatter(x.data, y.data)
plt.plot(x.data, prediction.data, 'r-', lw=5)
plt.text(0.5, 0, 'Loss=%.4f' % loss.data, fontdict={'size': 20, 'color': 'red'})
plt.pause(0.1)
plt.ioff()
plt.show()
5 分类:区分类别
5.1 神经网络搭建
- 建立数据集
- 建立神经网络
- 训练网络
多分类问题使用误差函数:交叉熵损失 torch.nn.CrossEntropyLoss()
计算的是各个类别的离散概率分布
分类的关键步骤:
prediction = torch.max(F.softmax(output), 1)[1] #返回最大值的索引位置
- 可视化训练过程
5.2 代码展示
Classification.py
import torch
import torch.nn.functional as F
import matplotlib.pyplot as plt
# 假数据
n_data = torch.ones(100, 2, requires_grad=True) # 数据的基本形态
x0 = torch.normal(2*n_data, 1) # 类型0 x data (tensor), shape=(100, 1)
y0 = torch.zeros(100) # 类型0 y data (tensor), shape=(100, )标签0
x1 = torch.normal(-2*n_data, 1) # 类型1 x data (tensor), shape=(100, 1)
y1 = torch.ones(100) # 类型1 y data (tensor), shape=(100, )标签1
# 注意 x, y 数据的数据形式是一定要像下面一样 (torch.cat 是在合并数据)
x = torch.cat((x0, x1), 0).type(torch.FloatTensor) # FloatTensor = 32-bit floating 二维数据
y = torch.cat((y0, y1), ).type(torch.LongTensor) # LongTensor = 64-bit integer 只有浮点数据可求梯度
# 画图
# plt.scatter(x.data[:, 0], x.data[:, 1], c=y.data, s=100, lw=0, cmap='RdYlGn')
# plt.show()
class Net(torch.nn.Module):
def __init__(self, n_feature, n_hidden, n_output): #大类的输入
super(Net, self).__init__() #规定动作,继承父类
self.hidden = torch.nn.Linear(n_feature, n_hidden)
self.predict = torch.nn.Linear(n_hidden, n_output)
def forward(self, x):
x = F.relu(self.hidden(x)) #本层调用
x = self.predict(x)
return x
#初始化网络
net = Net(n_feature=2, n_hidden= 10, n_output=2) #依据数据维度[0,1] 标签在第二类,为第二类
# print(net)
#训练网络
optimizer = torch.optim.SGD(net.parameters(), lr=0.002)
loss_func = torch.nn.CrossEntropyLoss() #交叉熵误差
plt.ion()
plt.show()
for t in range(100):
output = net(x) #shape = (200,2)
loss = loss_func(output, y)
optimizer.zero_grad() #清空上一批梯度计算器
loss.backward(retain_graph=True)
optimizer.step()
# print(torch.max(F.softmax(output), 1))
if t % 2 == 0:
plt.cla()
prediction = torch.max(F.softmax(output), 1)[1] #返回最大值的索引位置
pred_y = prediction.data.squeeze() #降维度
target_y = y.data
plt.scatter(x.data[:, 0], x.data[:, 1], c=pred_y, s=100, lw=0, cmap='RdYlGn')
accuracy = sum(pred_y == target_y)/200. # 预测中有多少和真实值一样
plt.text(1.5, -4, 'Accuracy=%.2f' % accuracy, fontdict={'size': 20, 'color': 'red'})
plt.pause(0.1)
plt.ioff()
plt.show()
6 快速搭建法
- 方法一
继承torch中的torch.nn.Module,. 先定义所有的层属性(__init__()
), 然后再一层层搭建(forward(x)
)层于层的关系链接。
其在forward()中才加入激励函数,优势在于可以更加个性化自己的前向传播过程。
class Net(torch.nn.Module):
def __init__(self, n_feature, n_hidden, n_output):
super(Net, self).__init__()
self.hidden = torch.nn.Linear(n_feature, n_hidden)
self.predict = torch.nn.Linear(n_hidden, n_output)
def forward(self, x):
x = F.relu(self.hidden(x)) #!!!!!
x = self.predict(x)
return x
net1 = Net(1, 10, 1) # 这是我们用这种方式搭建的 net1
"""
Net (
(hidden): Linear (1 -> 10)
(predict): Linear (10 -> 1)
)
"""
- 快速搭建法
将激励函数放到网络结构中,直接调用网络定义模块
net2 = torch.nn.Sequential(
torch.nn.Linear(1, 10),
torch.nn.ReLU(), #激励函数的库不同 !!!
torch.nn.Linear(10, 1)
)
"""
Sequential (
(0): Linear (1 -> 10)
(1): ReLU ()
(2): Linear (10 -> 1)
)
"""
7 保存提取
7.1 用法
使用**Torch.save()和Torch.load()**保存提取网络或者网络参数。
7.2 代码展示
Store.py
- 保存
torch.save(net1, 'net.pkl') # 保存整个网络 保存文件名称
torch.save(net1.state_dict(), 'net_params.pkl') # 只保存网络中的参数 ,不保存计算图(速度快, 占内存少)
- 加载
#加载整个网络
def restore_net():
# restore entire net1 to net2
net2 = torch.load('net.pkl')
prediction = net2(x)
#加载网络参数:首先要建立和Net1一致的网络结构 加载参数快于加载网络
def restore_params():
# 新建 net3
net3 = torch.nn.Sequential(
torch.nn.Linear(1, 10),
torch.nn.ReLU(),
torch.nn.Linear(10, 1)
)
# 将保存的参数复制到 net3
net3.load_state_dict(torch.load('net_params.pkl'))
prediction = net3(x)
8 批训练(DataLoader)
8.1 批训练方法
所有数据同时训练会非常缓慢,使用批次将数据划分为小块,可提升神经网络的训练速度。
批训练依赖:torch.utils.data库
torch.utils.data — PyTorch 2.1 documentation
使用**DataLoader
** ,它 torch 用来包装你的数据的工具。我们将 (numpy array 或其他) 数据形式装换成 Tensor, 然后再放进这个包装器中:
-
将全部数据放入TensorDataset数据库中
-
将数据封装到DataLoader中
- 数据训练
epoch为整体数据的训练次数,step为整体数据被拆分的批次数
s t e p = d a t a . s i z e / b a t c h _ s i z e step = data.size/batch\_size step=data.size/batch_size 向上取整
画loss-step曲线时,
s
t
e
p
=
[
d
a
t
a
.
s
i
z
e
b
a
t
c
h
_
s
i
z
e
]
∗
E
P
O
C
H
step = [\frac{data.size}{batch\_size}]*EPOCH
step=[batch_sizedata.size]∗EPOCH
如果不能被整除,例如十个数据点,batch_size=8,那么step=0时,训练8个数据;当Step=1时,训练2个数据。
批训练并不改变神经网络输入和输出x,y的维度和结构,也不影响神经网络的结构,改变的只是每次网络训练的数据量。
8.2 代码展示
Batchtrain.py
import torch
import torch.utils.data as Data
import matplotlib.pyplot as plt
torch.manual_seed(1) # reproducible
BATCH_SIZE = 20 # 批训练的数据个数
#建立数据库
x = torch.unsqueeze(torch.linspace(-1, 1, 100, requires_grad=True), dim=1) # x data (tensor), shape=(100, 1) unsqueeze升维
y = x.pow(2) + 0.2*torch.rand(x.size()) # noisy y data (tensor), shape=(100, 1)
# 1先转换成 torch 能识别的 Dataset数据库(全部数据)
torch_dataset = Data.TensorDataset(x, y)
# 2把 dataset 放入 DataLoader
loader = Data.DataLoader(
dataset=torch_dataset, # torch TensorDataset format
batch_size=BATCH_SIZE, # mini batch size
shuffle=True, # 要不要打乱数据 (打乱比较好)
# num_workers=2, # 多线程来读数据 CPU版本不支持
)
n_feature, n_hidden, n_output=1, 10, 1 #类的实例化
net = torch.nn.Sequential(
torch.nn.Linear(n_feature, n_hidden),
torch.nn.ReLU(), #激励函数的库不同 !!!
torch.nn.Linear(n_hidden, n_output)
)
#神经网络训练过程
# optimizer 是训练的工具
optimizer = torch.optim.SGD(net.parameters(), lr=0.2) # 传入 net 的所有参数, 学习率,采用随机梯度下降法
loss_func = torch.nn.MSELoss() # 预测值和真实值的误差计算公式 (均方差)
#333
for epoch in range(100): # 训练所有!整套!数据 3 次 epoch是整体数据训练次数;step是数据拆分组数
for step, (batch_x, batch_y) in enumerate(loader): # 每一步 loader 释放一小批数据用来学习,可以设置是否乱序
# 打出来一些数据
# print('Epoch: ', epoch, '| Step: ', step, '| batch x: ',
# batch_x, '| batch y: ', batch_y)
# 假设这里就是你训练的地方...
out = net(batch_x) # 前向传播,喂给 net 训练数据 x, 输出预测值
loss = loss_func(out, batch_y) # 计算两者的误差:预测值在前,真实值在后
optimizer.zero_grad() # 清空上一步的梯度参数值(此步必须!!)
loss.backward(retain_graph=True) # 误差反向传播, 计算参数更新值 保留反向传播计算图
optimizer.step() # 参数更新,将参数更新值施加到 net 的 parameters 上
prediction_y = net(x)
#可视化
if epoch % 2 == 0:
plt.cla()
plt.scatter(x.data, y.data)
plt.plot(x.data, prediction_y.data, 'r-', lw=5)
plt.text(0.5, 0, 'Loss=%.4f' % loss.data, fontdict={'size': 20, 'color': 'red'})
plt.pause(0.1)
plt.ion() #设置其为实时显示
plt.show()
9 过拟合(Overfitting)
9.1 回归分类中的过拟合
过拟合即拟合过头,机器学习出现“过度自信”的情况,过于追求把误差最小化,如下图。过拟合的结果在训练集内的数据表现十分好,但在训练集外的数据表现却很差。
9.2 解决方法
9.2.1 增加数据量
数据量增大后,可以提高拟合效果,扭曲的红线也会慢慢拉直。
9.2.2 正则化Weight_decay
9.2.2.1 原理
正则化包括L1,L2,L3,L4等正则化规则。在过拟合中,神经网络的参数一般非常大或非常小。正则化将神经网络的权重加入损失函数,如果权重过大,损失函数也会增大,实现一种惩罚机制。加入正则化后,loss下降的速度会变慢,准确率Accuracy的上升速度会变慢,并且未加入正则化模型的loss和Accuracy的浮动比较大(或者方差比较大),而加入正则化的模型训练loss和Accuracy较为平滑。随着正则化的权重 λ \lambda λ越大,表现得更加平滑。
正则化 | 公式 | |
---|---|---|
L1 | $(Y_{predict}-Y)^2+ | |
L2 | $(Y_{predict}-Y)^2+\lambda | |
Y p r e d i c t = W X c o s t = ( Y p r e d i c t − Y ) 2 L 1 : ( Y p r e d i c t − Y ) 2 + ∣ ∣ W ∣ ∣ 1 L 2 : ( Y p r e d i c t − Y ) 2 + λ ∣ ∣ W ∣ ∣ 2 2 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ Y_{predict}=WX\\ cost = (Y_{predict}-Y)^2\\ L1:(Y_{predict}-Y)^2+||W||_1\\ L2:(Y_{predict}-Y)^2+\lambda ||W||_2^2\\ ······· Ypredict=WXcost=(Ypredict−Y)2L1:(Ypredict−Y)2+∣∣W∣∣1L2:(Ypredict−Y)2+λ∣∣W∣∣22⋅⋅⋅⋅⋅⋅⋅
9.2.2.2 用法
参考:pytorch实现L2和L1正则化regularization的方法_torch loss函数怎么加正则化项-CSDN博客
- 在optimer中设置weight_decay( λ \lambda λ)参数,实现L2正则化
optimizer = optim.Adam(model.parameters(),lr=learning_rate,weight_decay=0.01)
#weight_decay (float, optional): weight decay (L2 penalty) (default: 0)
存在的问题:
(1)一般正则化,只是对模型的权重W参数进行惩罚,而偏置参数b是不进行惩罚的,而torch.optim的优化器weight_decay参数指定的权值衰减是对网络中的所有参数,包括权值w和偏置b同时进行惩罚。很多时候如果对b 进行L2正则化将会导致严重的欠拟合,因此这个时候一般只需要对权值w进行正则即可。
(2)根据正则化公式,加入正则化后,loss会变大。比如weight_decay=1的loss为10,那么weight_decay=100时,loss输出应该也提高100倍左右。而采用torch.optim的优化器的方法,采用loss_fun= nn.CrossEntropyLoss()进行计算loss,不管怎么改变weight_decay的大小,loss会跟之前没有加正则化的大小差不多。这是因为loss_fun损失函数没有把权重W的损失加上。(这不影响正则化起作用)
- 自定义正则化
(可行性未知)
#方法1:自定义实现正则化的Regularization类
# 检查GPU是否可用
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# device='cuda'
print("-----device:{}".format(device))
print("-----Pytorch version:{}".format(torch.__version__))
class Regularization(torch.nn.Module):
def __init__(self,model,weight_decay,p=2):
'''
:param model 模型
:param weight_decay:正则化参数
:param p: 范数计算中的幂指数值,默认求2范数,
当p=0为L2正则化,p=1为L1正则化
'''
super(Regularization, self).__init__()
if weight_decay <= 0:
print("param weight_decay can not <=0")
exit(0)
self.model=model
self.weight_decay=weight_decay
self.p=p
self.weight_list=self.get_weight(model)
self.weight_info(self.weight_list)
def to(self,device):
'''
指定运行模式
:param device: cude or cpu
:return:
'''
self.device=device
super().to(device)
return self
def forward(self, model):
self.weight_list=self.get_weight(model)#获得最新的权重
reg_loss = self.regularization_loss(self.weight_list, self.weight_decay, p=self.p)
return reg_loss
def get_weight(self,model):
'''
获得模型的权重列表
:param model:
:return:
'''
weight_list = []
for name, param in model.named_parameters():
if 'weight' in name:
weight = (name, param)
weight_list.append(weight)
return weight_list
def regularization_loss(self,weight_list, weight_decay, p=2):
'''
计算张量范数
:param weight_list:
:param p: 范数计算中的幂指数值,默认求2范数
:param weight_decay:
:return:
'''
# weight_decay=Variable(torch.FloatTensor([weight_decay]).to(self.device),requires_grad=True)
# reg_loss=Variable(torch.FloatTensor([0.]).to(self.device),requires_grad=True)
# weight_decay=torch.FloatTensor([weight_decay]).to(self.device)
# reg_loss=torch.FloatTensor([0.]).to(self.device)
reg_loss=0
for name, w in weight_list:
l2_reg = torch.norm(w, p=p)
reg_loss = reg_loss + l2_reg
reg_loss=weight_decay*reg_loss
return reg_loss
def weight_info(self,weight_list):
'''
打印权重列表信息
:param weight_list:
:return:
'''
print("---------------regularization weight---------------")
for name ,w in weight_list:
print(name)
print("---------------------------------------------------")
#使用
# 初始化正则化
if weight_decay>0:
reg_loss=Regularization(model, weight_decay, p=2).to(device)
else:
print("no regularization")
if weight_decay > 0:
loss = loss + reg_loss(model)
total_loss = loss.item()
9.2.3 Dropout机制
9.2.3.1 原理
专门用于神经网络的正则化方法。在训练时, 随机忽略掉一些神经元和神经联结(将这些权重置0) , 是这个神经网络变得”不完整”. 用一个不完整的神经网络训练一次;再次训练时,训练另一个不完整的神经网络。使得每次训练结果都不会依赖于其中某部分特定的神经元。
注意:只在训练net_overfitting.train()的时候开启Dropout,而测试的时候是不用Dropout的,测试使用全部神经元net_overfitting.eval()。
9.2.3.2 用法
API:torch.nn — PyTorch 2.1 documentation
在搭建神经网络时,在全连接层后加入稀疏矩阵块。实际操作是随机将权重张量的元素置0,抽象思维是在神经网络中加入稀疏层。
net_dropped = torch.nn.Sequential(
torch.nn.Linear(1, N_HIDDEN),
torch.nn.Dropout(0.5), # drop 50% of the neuron
torch.nn.ReLU(),
torch.nn.Linear(N_HIDDEN, N_HIDDEN),
torch.nn.Dropout(0.5), # drop 50% of the neuron
torch.nn.ReLU(),
torch.nn.Linear(N_HIDDEN, 1),
)
9.2.3.3 代码展示
Dropout.py
import torch
import matplotlib.pyplot as plt
torch.manual_seed(1) # reproducible
N_SAMPLES = 20
N_HIDDEN = 300
# training data
x = torch.unsqueeze(torch.linspace(-1, 1, N_SAMPLES,requires_grad=True), 1)
y = x + 0.3*torch.normal(torch.zeros(N_SAMPLES, 1), torch.ones(N_SAMPLES, 1))
# test data
test_x = torch.unsqueeze(torch.linspace(-1, 1, N_SAMPLES,requires_grad=True), 1)
test_y = test_x + 0.3*torch.normal(torch.zeros(N_SAMPLES, 1), torch.ones(N_SAMPLES, 1))
# # show data
# plt.scatter(x.data, y.data, c='magenta', s=50, alpha=0.5, label='train')
# plt.scatter(test_x.data, test_y.data, c='cyan', s=50, alpha=0.5, label='test')
# plt.legend(loc='upper left')
# plt.ylim((-2.5, 2.5))
# plt.show()
#无dropout
net_overfitting = torch.nn.Sequential(
torch.nn.Linear(1, N_HIDDEN),
torch.nn.ReLU(),
torch.nn.Linear(N_HIDDEN, N_HIDDEN),
torch.nn.ReLU(),
torch.nn.Linear(N_HIDDEN, 1),
)
#有Dropout
net_dropped = torch.nn.Sequential(
torch.nn.Linear(1, N_HIDDEN),
torch.nn.Dropout(0.5), # drop 50% of the neuron
torch.nn.ReLU(),
torch.nn.Linear(N_HIDDEN, N_HIDDEN),
torch.nn.Dropout(0.5), # drop 50% of the neuron
torch.nn.ReLU(),
torch.nn.Linear(N_HIDDEN, 1),
)
optimizer_ofit = torch.optim.Adam(net_overfitting.parameters(), lr=0.01)
optimizer_drop = torch.optim.Adam(net_dropped.parameters(), lr=0.01)
loss_func = torch.nn.MSELoss()
for t in range(500):
pred_ofit = net_overfitting(x)
pred_drop = net_dropped(x)
loss_ofit = loss_func(pred_ofit, y)
loss_drop = loss_func(pred_drop, y)
optimizer_ofit.zero_grad()
optimizer_drop.zero_grad()
loss_ofit.backward()
loss_drop.backward()
optimizer_ofit.step()
optimizer_drop.step()
# 接着上面来
if t % 10 == 0: # 每 10 步画一次图
# 将神经网络转换成测试形式, 画好图之后改回 训练形式
net_overfitting.eval() #改为测试模式
net_dropped.eval() # 因为 drop 网络在 train 的时候和 test 的时候参数不一样.
# plotting
plt.cla()
test_pred_ofit = net_overfitting(test_x)
test_pred_drop = net_dropped(test_x)
plt.scatter(x.data, y.data, c='magenta', s=50, alpha=0.3, label='train')
plt.scatter(test_x.data, test_y.data, c='cyan', s=50, alpha=0.3, label='test')
plt.plot(test_x.data, test_pred_ofit.data, 'r-', lw=3, label='overfitting')
plt.plot(test_x.data, test_pred_drop.data, 'b--', lw=3, label='dropout(50%)')
plt.text(0, -1.2, 'overfitting loss=%.4f' % loss_func(test_pred_ofit, test_y).data.numpy(), fontdict={'size': 20, 'color': 'red'})
plt.text(0, -1.5, 'dropout loss=%.4f' % loss_func(test_pred_drop, test_y).data.numpy(), fontdict={'size': 20, 'color': 'blue'})
plt.legend(loc='upper left'); plt.ylim((-2.5, 2.5));plt.pause(0.1)
# 将两个网络改回 训练形式
net_overfitting.train() #改为测试模式
net_dropped.train()
10 批标准化(Batch Normalization)
10.1 原理
参考论文:《Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift》
Pytorch学习笔记(8):正则化(L1、L2、Dropout)与归一化(BN、LN、IN、GN)_nn.instancenorm3d-CSDN博客
批标准化与数据标准化类似,其对每一批数据进行归一化,对每一层前向传播数据进行标准化,其可加快模型的收敛速度,并可一定程度缓解深度网络中的**“梯度弥散”问题**,使得训练模型更容易和稳定。BN能减少参数初始化的要求 (initialization)
如果没有标准化,在经过激励函数后,X之间较大的差距不能在WX中体现,神经网络识别能力减弱。例如:
W
X
=
0.1
∗
1
=
0.1
t
a
n
h
(
W
X
)
=
0.1
W
X
=
0.1
∗
20
=
2
t
a
n
h
(
W
X
)
=
0.96
W
X
=
0.1
∗
1000
=
100
t
a
n
h
(
W
X
)
=
1
无法区别
X
=
20
和
X
=
1000
WX=0.1*1=0.1\ tanh(WX)=0.1\\ WX=0.1*20=2\ tanh(WX)=0.96\\ WX=0.1*1000=100\ tanh(WX)=1\\ 无法区别X=20和X=1000
WX=0.1∗1=0.1 tanh(WX)=0.1WX=0.1∗20=2 tanh(WX)=0.96WX=0.1∗1000=100 tanh(WX)=1无法区别X=20和X=1000
而BN层可使得每一层值都在有效得传递下去。
神奇之处:
BN层自己有调节机制,包含反向操作(最后1步), 将 normalize 后的数据再扩展和平移。 让神经网络自己去学着使用和修改扩展参数 γ \gamma γ 和平移参数 β \beta β, 这样神经网络就能自己判断 normalization 操作是否有优化作用。如果没有起到作用, 就使用 γ \gamma γ 和 β \beta β来抵消一些 normalization 的操作.
10.2 用法
API:torch.nn — PyTorch 2.1 documentation
BN层添加在每一个全连接层和激励函数层之间。
##定义批标准化层
torch.nn.BatchNorm1d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, device=None, dtype=None)
#num_features:C一个样本特征维度,即m W(m,n)
#eps:分母修正项,为数值稳定性而加到分母上的值,一般设置比较小的数:1e的-5次方,防止除以0导致错误
#momentum:移动平均的动量值(通常设置为0.1)
#affine:是否需要反向学习映射affine transform(默认为打开)
#track_running_states:是训练状态,还是测试状态。(如果在训练状态,均值、方差需要重新估计;如果在测试状态,会采用当前的统计信息,均值、方差固定的,但训练时这两个数据是会根据batch发生改变。)
#使用
net = torch.nn.Sequential(
torch.nn.BatchNorm1d(1, eps=1e-05, momentum=0.1, affine=True), #标准化输入
torch.nn.Linear(n_feature, n_hidden),
torch.nn.BatchNorm1d(n_hidden, eps=1e-05, momentum=0.1, affine=True),
torch.nn.ReLU(), #激励函数的库不同 !!!
torch.nn.Linear(n_hidden, n_output)
)
pytorch定义了三种BatchNorm方法:
BatchNorm1d | BatchNorm2d | BatchNorm3d | |
---|---|---|---|
a 2D or 3D input | a 4D input | a 5D input | |
输入 | (N,C) or(N,C,L) | (N,C,H,W) | (N,C,D,H,W) |
输出 | (N,C) or (N,C,L) | (N,C,H,W) | (N,C,D,H,W) |
注意:N is the batch size, C is the number of features or channels, and L is the sequence length。
10.3 代码展示
Batchnormalization.py
激励函数前后与批标准化前后:
relu() | tanh() |
---|---|
import torch
from torch import nn
from torch.nn import init
import torch.utils.data as Data
import matplotlib.pyplot as plt
import numpy as np
# torch.manual_seed(1) # reproducible
# np.random.seed(1)
# Hyper parameters
N_SAMPLES = 2000
BATCH_SIZE = 64
EPOCH = 12
LR = 0.03
N_HIDDEN = 8
ACTIVATION = torch.tanh
B_INIT = -0.2 # use a bad bias constant initializer
# training data
train_x = torch.linspace(-7, 10, N_SAMPLES, requires_grad=True)[:, np.newaxis]
noise = torch.normal(0, 2, train_x.shape, requires_grad=True)
train_y = torch.square(train_x) - 5 + noise
# test data
test_x = torch.linspace(-7, 10, 200, requires_grad=True)[:, np.newaxis]
noise = torch.normal(0, 2, test_x.shape, requires_grad=True)
test_y = torch.square(test_x) - 5 + noise
train_dataset = Data.TensorDataset(train_x, train_y)
train_loader = Data.DataLoader(dataset=train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=2,)
# show data
# plt.scatter(train_x.data, train_y.data, c='#FF9359', s=50, alpha=0.2, label='train')
# plt.legend(loc='upper left')
class Net(nn.Module):
def __init__(self, batch_normalization=False):
super(Net, self).__init__()
self.do_bn = batch_normalization
self.fcs = [] #数据载体
self.bns = []
self.bn_input = nn.BatchNorm1d(1, momentum=0.5) # for input data
for i in range(N_HIDDEN): # build hidden layers and BN layers
input_size = 1 if i == 0 else 10
fc = nn.Linear(input_size, 10)
setattr(self, 'fc%i' % i, fc) # IMPORTANT set layer to the Module
self._set_init(fc) # parameters initialization
self.fcs.append(fc)
if self.do_bn:
bn = nn.BatchNorm1d(10, momentum=0.5)
setattr(self, 'bn%i' % i, bn) # IMPORTANT set layer to the Module
self.bns.append(bn)
self.predict = nn.Linear(10, 1) # output layer
self._set_init(self.predict) # parameters initialization
def _set_init(self, layer): #神经网络的初始化参数
init.normal_(layer.weight, mean=0., std=.1)
init.constant_(layer.bias, B_INIT)
def forward(self, x):
pre_activation = [x]
if self.do_bn: x = self.bn_input(x) # input batch normalization
layer_input = [x]
for i in range(N_HIDDEN):
x = self.fcs[i](x)
pre_activation.append(x)
if self.do_bn: x = self.bns[i](x) # batch normalization
x = ACTIVATION(x)
layer_input.append(x)
out = self.predict(x)
return out, layer_input, pre_activation
nets = [Net(batch_normalization=False), Net(batch_normalization=True)]
# print(*nets) # print net architecture
opts = [torch.optim.Adam(net.parameters(), lr=LR) for net in nets]
loss_func = torch.nn.MSELoss()
def plot_histogram(l_in, l_in_bn, pre_ac, pre_ac_bn):
for i, (ax_pa, ax_pa_bn, ax, ax_bn) in enumerate(zip(axs[0, :], axs[1, :], axs[2, :], axs[3, :])):
[a.clear() for a in [ax_pa, ax_pa_bn, ax, ax_bn]]
if i == 0:
p_range = (-7, 10);the_range = (-7, 10)
else:
p_range = (-4, 4);the_range = (-1, 1)
ax_pa.set_title('L' + str(i))
ax_pa.hist(pre_ac[i].data.numpy().ravel(), bins=10, range=p_range, color='#FF9359', alpha=0.5);ax_pa_bn.hist(pre_ac_bn[i].data.numpy().ravel(), bins=10, range=p_range, color='#74BCFF', alpha=0.5)
ax.hist(l_in[i].data.numpy().ravel(), bins=10, range=the_range, color='#FF9359');ax_bn.hist(l_in_bn[i].data.numpy().ravel(), bins=10, range=the_range, color='#74BCFF')
for a in [ax_pa, ax, ax_pa_bn, ax_bn]: a.set_yticks(());a.set_xticks(())
ax_pa_bn.set_xticks(p_range);ax_bn.set_xticks(the_range)
axs[0, 0].set_ylabel('PreAct');axs[1, 0].set_ylabel('BN PreAct');axs[2, 0].set_ylabel('Act');axs[3, 0].set_ylabel('BN Act')
plt.pause(0.01)
if __name__ == "__main__":
f, axs = plt.subplots(4, N_HIDDEN + 1, figsize=(10, 5))
plt.ion() # something about plotting
plt.show()
# training
losses = [[], []] # recode loss for two networks
for epoch in range(EPOCH):
print('Epoch: ', epoch)
layer_inputs, pre_acts = [], []
for net, l in zip(nets, losses):
net.eval() # set eval mode to fix moving_mean and moving_var
pred, layer_input, pre_act = net(test_x)
l.append(loss_func(pred, test_y).data.item())
layer_inputs.append(layer_input)
pre_acts.append(pre_act)
net.train() # free moving_mean and moving_var
plot_histogram(*layer_inputs, *pre_acts) # plot histogram
for step, (b_x, b_y) in enumerate(train_loader):
for net, opt in zip(nets, opts): # train for each network
pred, _, _ = net(b_x)
loss = loss_func(pred, b_y)
opt.zero_grad()
loss.backward()
opt.step() # it will also learns the parameters in Batch Normalization
plt.ioff()
# plot training loss
plt.figure(2)
plt.plot(losses[0], c='#FF9359', lw=3, label='Original')
plt.plot(losses[1], c='#74BCFF', lw=3, label='Batch Normalization')
plt.xlabel('step');plt.ylabel('test loss');plt.ylim((0, 2000));plt.legend(loc='best')
# evaluation
# set net to eval mode to freeze the parameters in batch normalization layers
[net.eval() for net in nets] # set eval mode to fix moving_mean and moving_var
preds = [net(test_x)[0] for net in nets]
plt.figure(3)
plt.plot(test_x.data.numpy(), preds[0].data.numpy(), c='#FF9359', lw=4, label='Original')
plt.plot(test_x.data.numpy(), preds[1].data.numpy(), c='#74BCFF', lw=4, label='Batch Normalization')
plt.scatter(test_x.data.numpy(), test_y.data.numpy(), c='r', s=50, alpha=0.2, label='train')
plt.legend(loc='best')
plt.show()