Pytorch搭建神经网络
PyTorch构建线性回归模型
创建数据集
import torch
from torch.utils.data import TensorDataset # 创建x和y张量数据集对象
from torch.utils.data import DataLoader # 创建数据集加载器
import torch.nn as nn # 损失函数和回归函数
from torch.optim import SGD # 随机梯度下降函数, 取一个训练样本算梯度值
from sklearn.datasets import make_regression # 创建随机样本, 工作中不使用
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
# todo: 1-创建线性回归样本 x y coef(w) b
def create_datasets():
x, y, coef = make_regression(n_samples=100, # 样本数
n_features=1, # 特征数
noise=10, # 标准差, 噪声, 样本离散程度
coef=True, # 返回系数, w
bias=14.5, # 截距 b
random_state=0)
print(type(x)) # <type 'numpy.ndarray'>
# 将数组转换成张量
x = torch.tensor(data=x)
y = torch.tensor(data=y)
# print('x->', x)
# print('y->', y)
# print('coef->', coef)
return x, y, coef
if __name__ == '__main__':
x, y, coef = create_datasets()
训练模型
# todo: 2-模型训练
def train(x, y, coef):
# 创建张量数据集对象
datasets = TensorDataset(x, y)
print('datasets->', datasets)
# 创建数据加载器对象
# dataset: 张量数据集对象
# batch_size: 每个batch的样本数
# shuffle: 是否打乱样本
dataloader = DataLoader(dataset=datasets, batch_size=16, shuffle=True)
print('dataloader->', dataloader)
# for batch in dataloader: # 每次遍历取每个batch样本
# print('batch->', batch) # [x张量对象, y张量对象]
# break
# 创建初始回归模型对象, 随机生成w和b, 元素类型为float32
# in_features: 输入特征数 1个
# out_features: 输出特征数 1个
model = nn.Linear(in_features=1, out_features=1)
print('model->', model)
# 获取模型对象的w和b参数
print('model.weight->', model.weight)
print('model.bias->', model.bias)
print('model.parameters()->', list(model.parameters()))
# 创建损失函数对象, 计算损失值
criterion = nn.MSELoss()
# 创建SGD优化器对象, 更新w和b
optimizer = SGD(params=model.parameters(), lr=0.01)
# 定义变量, 接收训练次数, 损失值, 训练样本数
epochs = 100
loss_list = [] # 存储每次训练的平均损失值
total_loss = 0.0
train_samples = 0
for epoch in range(epochs): # 训练100次
# 借助循环实现 mini-batch SGD 模型训练
for train_x, train_y in dataloader:
# 模型预测
# train_x->float64
# w->float32
y_pred = model(train_x.type(dtype=torch.float32)) # y=w*x+b
print('y_pred->', y_pred)
# 计算损失值, 调用损失函数对象
# print('train_y->', train_y)
# y_pred: 二维张量
# train_y: 一维张量, 修改成二维张量, n行1列
# 可能发生报错, 修改形状
# 修改train_y元素类型, 和y_pred类型一致, 否则发生报错
loss = criterion(y_pred, train_y.reshape(shape=(-1, 1)).type(dtype=torch.float32))
print('loss->', loss)
# 获取loss标量张量的数值 item()
# 统计n次batch的总MSE值
total_loss += loss.item()
# 统计batch次数
train_samples += 1
# 梯度清零
optimizer.zero_grad()
# 计算梯度值
loss.backward()
# 梯度更新 w和b更新
# step()等同 w=w-lr*grad
optimizer.step()
# 每次训练的平均损失值保存到loss列表中
loss_list.append(total_loss / train_samples)
print('每次训练的平均损失值->', total_loss / train_samples)
print('loss_list->', loss_list)
print('w->', model.weight)
print('b->', model.bias)
# 绘制每次训练损失值曲线变化图
plt.plot(range(epochs), loss_list)
plt.title('损失值曲线变化图')
plt.grid()
plt.show()
# 绘制预测值和真实值对比图
# 绘制样本点分布
plt.scatter(x, y)
# 获取1000个样本点
# x = torch.linspace(start=x.min(), end=x.max(), steps=1000)
# 计算训练模型的预测值
y1 = torch.tensor(data=[v * model.weight + model.bias for v in x])
# 计算真实值
y2 = torch.tensor(data=[v * coef + 14.5 for v in x])
plt.plot(x, y1, label='训练')
plt.plot(x, y2, label='真实')
plt.legend()
plt.grid()
plt.show()
if __name__ == '__main__':
x, y, coef = create_datasets()
train(x, y, coef)
人工神经网络介绍
什么是人工神经网络
- 仿生生物学神经网络的计算模型
- ANN(人工神经网络)->NN(神经网络)
如何构建人工神经网络
神经网络是由三个层, 每层由多个神经元构成
- 输入层: 输入样本的特征值, 一层
- 隐藏层: 提取复杂特征, 可以有多层
- 输出层: 输出y值, y预测值
人工神经网络内部状态值和激活值
神经元如何工作
- 内部状态值(加权求和值)
z=w1*x1+w2*x2+...+b
- 激活值
a=f(z)
激活函数介绍
激活函数作用
- 给神经网络模型中引入非线性因素
- 生产环境中,问题存在线性不可分情况
常见激活函数
-
sigmoid激活函数
- sigmoid激活值范围是[0, 1], 只有正信号, 没有负信号, 模型只能学习到正信号
- 加权求和值在[-6,6]范围, 计算激活值时分布到[0, 1], 否则激活值只能是0或1
- sigmoid激活函数导数值范围是[0,0.25], 加权求和值在[-6,6]范围, 激活值梯度才分布到[0, 0.25], 否则梯度为0
- 神经网络中梯度连乘, sigmoid激活函数梯度值很小, 接近0, 梯度消失
0.25*0.25*0.25*... - sigmoid一般在二分类输出层使用, 如果神经网络隐藏层在5层之内也可以考虑使用sigmoid


# sigmoid激活值: torch.sigmoid(x) import torch import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签 plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号 def dm01(): # 创建x值, 线性模型输出值作为激活函数的输入值 x = torch.linspace(-20, 20, 1000) # 计算激活值 y = torch.sigmoid(input=x) # 创建画布对象和坐标轴对象 _, axes = plt.subplots(1, 2) # 一行两列, 绘制两个子图 axes[0].plot(x, y) axes[0].grid() axes[0].set_title('sigmoid激活函数') # 创建x值,可以自动微分, 线性模型输出值作为激活函数的输入值 x = torch.linspace(-20, 20, 1000, requires_grad=True) torch.sigmoid(input=x).sum().backward() axes[1].plot(x.detach().numpy(), x.grad) axes[1].grid() axes[1].set_title('sigmoid激活函数') plt.show() if __name__ == '__main__': dm01() -
tanh激活函数
-
tanh激活值范围是[-1, 1], 既有正信号, 又有负信号, 激活值是以0对称, 模型可以学习到正负信号
-
加权求和值在[-3,3]范围, 计算激活值时分布到[-1, 1], 否则激活值只能是-1或1
-
tanh激活函数导数值范围是[0,1], 加权求和值在[-3,3]范围, 相比sigmoid激活导数更大, 模型收敛程度更快, 但是如果加权求和值大于3或小于-3, 也是会导致梯度消失, 最好分布在0附近(梯度值最大)
-
tanh激活函数可以在隐藏层使用, 不是优先选择, 浅层神经网络可以使用
-

-

# tanh激活值: torch.tanh(x) import torch import matplotlib.pyplot as plt from torch.nn import functional as F # F.sigmoid() # F.tanh() plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签 plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号 def dm01(): # 创建x值, 线性模型输出值作为激活函数的输入值 x = torch.linspace(-20, 20, 1000) # 计算激活值 y = torch.tanh(input=x) # 创建画布对象和坐标轴对象 _, axes = plt.subplots(1, 2) # 一行两列, 绘制两个子图 axes[0].plot(x, y) axes[0].grid() axes[0].set_title('tanh激活函数') # 创建x值,可以自动微分, 线性模型输出值作为激活函数的输入值 x = torch.linspace(-20, 20, 1000, requires_grad=True) torch.tanh(input=x).sum().backward() axes[1].plot(x.detach().numpy(), x.grad) axes[1].grid() axes[1].set_title('tanh激活函数') plt.show() if __name__ == '__main__': dm01() -
-
relu激活函数
-
relu激活值范围是[0, x], x是线性输出的正值, 只有正信号
-
加权求和值大于0, 也可以一部分小于0(神经元死亡, 防止过拟合)
-
relu激活函数导数值0或1, 如果线性输出大于0, 导数为1, 不会出现梯度消失情况, 模型收敛程度更快; 如果线性输出小于0, 神经元死亡, 防止过拟合(选择leaky relu或prelu)
-
relu激活函数优先选择, 计算复杂度最小, 大于0不存在梯度消失情况
-

-

# relu激活值: torch.relu(x) import torch import matplotlib.pyplot as plt from torch.nn import functional as F # F.sigmoid() # F.tanh() plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签 plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号 def dm01(): # 创建x值, 线性模型输出值作为激活函数的输入值 x = torch.linspace(-20, 20, 1000) # 计算激活值 y = torch.relu(input=x) # torch.leaky_relu() # torch.prelu() # 创建画布对象和坐标轴对象 _, axes = plt.subplots(1, 2) # 一行两列, 绘制两个子图 axes[0].plot(x, y) axes[0].grid() axes[0].set_title('relu激活函数') # 创建x值,可以自动微分, 线性模型输出值作为激活函数的输入值 x = torch.linspace(-20, 20, 1000, requires_grad=True) torch.relu(input=x).sum().backward() axes[1].plot(x.detach().numpy(), x.grad) axes[1].grid() axes[1].set_title('relu激活函数') plt.show() if __name__ == '__main__': dm01() -
-
softmax激活函数
- 多分类任务的输出层使用, 将输出层加权求和值转换成概率

import torch import pandas as pd def dm01(): # 创建输出层加权求和值 y = torch.tensor(data=[[0.2, 0.02, 0.15, 0.15, 1.3, 0.5, 0.06, 1.1, 0.05, 3.75], [0.2, 0.02, 0.15, 3.75, 1.3, 0.5, 0.06, 1.1, 0.05, 0.15]]) # softmax激活函数转换成概率值 # 1轴按列计算 # y_softmax = torch.softmax(input=y, dim=-1) y_softmax = torch.softmax(input=y, dim=1) print('y_softmax->', y_softmax) if __name__ == '__main__': dm01()
其他常见的激活函数:
如何选择激活函数
- 隐藏层
- 优选relu激活函数, 其次选leaky relu/prelu/tanh/sigmoid
- 尽量少使用sigmoid激活函数, 可以使用tanh激活函数代替sigmoid激活函数 浅层神经网络
- 输出层
- 二分类问题 sigmoid激活函数
- 多分类问题 softmax激活函数
- 回归问题 identity激活函数
参数初始化
参数初始化作用
- 参数->w和b, 创建初版模型时指定w和b的值
- 选择合适的参数, 计算得到的加权求和的值会落到激活函数合理的区间, 加快模型收敛速度以及增加不同学习特征值
常见参数初始化方法
-
随机初始化
-
全0、1初始化
-
固定值初始化
-
kaiming初始化
-
xavier初始化
import torch import torch.nn as nn # 随机参数初始化 def dm01(): # 创建线性层对象, 对线性层的权重进行初始化 # in_features: 输入神经元个数 # out_features: 输出神经元个数 linear1 = nn.Linear(in_features=5, out_features=8) linear2 = nn.Linear(in_features=8, out_features=10) # 均匀分布初始化,默认在(0,1)区间均匀分布, 可以通过a和b参数调整区间 nn.init.uniform_(linear1.weight) nn.init.uniform_(linear1.weight, a=-1/torch.sqrt(torch.tensor(5.0)), b=1/torch.sqrt(torch.tensor(5.0))) nn.init.uniform_(linear1.bias) print(linear1.weight) print(linear1.bias) # 正态分布参数初始化 def dm02(): # 创建线性层对象, 对线性层的权重进行初始化 # in_features: 输入神经元个数 # out_features: 输出神经元个数 linear1 = nn.Linear(in_features=5, out_features=8) linear2 = nn.Linear(in_features=8, out_features=10) # 均匀分布初始化 nn.init.normal_(linear1.weight) nn.init.normal_(linear1.bias) print(linear1.weight) print(linear1.bias) # nn.init.zeros_() # 全0初始化 # nn.init.ones_() # 全1初始化 # nn.init.constant_(val=0.1) # 全固定值初始化 # nn.init.kaiming_uniform_() # 凯明均匀分布初始化 # nn.init.kaiming_normal_() # 凯明正态分布初始化 # nn.init.xavier_uniform_() # xavier均匀分布初始化 # nn.init.xavier_normal_() # xavier正态分布初始化 if __name__ == '__main__': dm01() dm02()
如何选择参数初始化方法
- 浅层神经网络可以选择随机初始化
- 深层神经网络结合激活函数选择
- tanh激活函数 -> xavier初始化
- relu激活函数 -> kaiming初始化
神经网络搭建和参数计算
构建神经网络模型
import torch
import torch.nn as nn # 线性模型和初始化方法
# todo:1-创建类继承 nn.module类
class ModelDemo(nn.Module):
# todo:2-定义__init__构造方法, 构建神经网络
def __init__(self):
# todo:2-1 调用父类的__init__方法
super().__init__()
# todo:2-2 创建隐藏层和输出层 定义属性
# in_features: 输入特征数(上一层神经元个数)
# out_features: 输出特征数(当前层神经元个数)
self.linear1 = nn.Linear(in_features=3, out_features=3)
self.linear2 = nn.Linear(in_features=3, out_features=2)
self.output = nn.Linear(in_features=2, out_features=2)
# todo:2-3 对隐藏层进行参数初始化
# self.linear1.weight: 在类的内部调用对象属性
nn.init.xavier_normal_(tensor=self.linear1.weight)
nn.init.zeros_(tensor=self.linear1.bias)
nn.init.kaiming_normal_(tensor=self.linear2.weight, nonlinearity='relu')
nn.init.zeros_(tensor=self.linear2.bias)
# todo:3-定义前向传播方法 forward(方法名固定) 得到预测y值
def forward(self, x): # x->输入样本的特征值
# todo:3-1 第一层计算 加权求和值计算 激活值计算
x = torch.sigmoid(input=self.linear1(x))
# todo:3-2 第二层计算
x = torch.relu(input=self.linear2(x))
# todo:3-3 输出层计算 假设多分类问题
# dim=-1: 按行计算, 一个样本一个样本算
x = torch.softmax(input=self.output(x), dim=-1)
# 返回预测值
return x
if __name__ == '__main__':
# 创建神经网络模型对象
my_model = ModelDemo()
计算和查看模型参数
from torchsummary import summary # 查看模型结构和参数数量
# 创建模型预测函数
def train():
# todo:1-创建神经网络模型对象
my_model = ModelDemo()
print('my_model->', my_model)
# todo:2-构造数据集样本, 随机生成
data = torch.randn(size=(5, 3))
print('data->', data)
print('data.shape->', data.shape)
# todo:3-调用神经网络模型对象进行模型训练
output = my_model(data)
print('output->', output)
print('output.shape->', output.shape)
# todo:4-计算和查看模型参数
print(('====================计算和查看模型参数==================='))
# input_size: 样本的特征数
# batch_size: 批量训练的样本数
# summary(model=my_model, input_size=(3,), batch_size=5)
summary(model=my_model, input_size=(5, 3))
for name, param in my_model.named_parameters():
print('name->', name)
print('param->', param)
if __name__ == '__main__':
train()
损失函数
什么是损失函数
- 衡量模型参数质量的函数(评估模型)
- 对比模型预测值和真实值差异
- 指导通过梯度下降法计算梯度, 进行参数更新 loss.backward()
分类任务损失函数
多分类任务损失函数
-
适用于多分类问题
-
计算出损失值后续结合梯度下降法和反向传播算法, 进行梯度更新
-
nn.CrossEntropyLoss()交叉熵损失
-

-
其中: y是样本x属于某一个类别的真实概率(0或1),而f(x)是样本属于某一类别的预测分数 ,S是softmax激活函数,将属于某一类别的预测分数转换成概率
L用来衡量真实值y和预测值f(x)之间差异性的损失结果 -
最小化正确类别预测概率的对数的负值,即最大化正确类别预测概率的对数
# 适用于多分类
import torch
import torch.nn as nn
def dm01():
# 手动创建样本的真实y值
# y_true = torch.tensor(data=[[0, 1, 0], [1, 0, 0]], dtype=torch.float32)
y_true = torch.tensor(data=[1, 2])
print('y_true->', y_true.dtype)
# 手动创建样本的预测y值 -> 模型预测值
y_pred = torch.tensor(data=[[0.1, 0.8, 0.1], [0.7, 0.2, 0.1]], requires_grad=True, dtype=torch.float32)
# 创建多分类交叉熵损失对象
# reduction:损失值计算的方式, 默认mean 平均损失值
criterion = nn.CrossEntropyLoss(reduction='sum')
# 调用损失对象计算损失值
# 预测y 真实y
loss = criterion(y_pred, y_true)
print('loss->', loss)
if __name__ == '__main__':
dm01()
二分类任务损失函数
-
适用于二分类问题
-
nn.BCEloss()
-

-
y是样本x属于某一个类别的真实概率,而y^是样本属于某一类别的预测概率
-

# 适用于二分类
import torch
import torch.nn as nn
def dm01():
# 手动创建样本的真实y值
y_true = torch.tensor(data=[0, 1, 0], dtype=torch.float32)
print('y_true->', y_true.dtype)
# 手动创建样本的预测y值 -> 模型预测值
# 0.6901, 0.5459, 0.2469 -> sigmoid函数的激活值
y_pred = torch.tensor(data=[0.6901, 0.5459, 0.2469], requires_grad=True, dtype=torch.float32)
# 创建多分类交叉熵损失对象
# reduction:损失值计算的方式, 默认mean 平均损失值
criterion = nn.BCELoss()
# 调用损失对象计算损失值
# 预测y 真实y
loss = criterion(y_pred, y_true)
print('loss->', loss)
print('loss->', loss.requires_grad)
if __name__ == '__main__':
dm01()
回归任务损失函数
MAE回归损失函数
- loss=(|y真实-y预测| + …)/n
- 导数为-1或1, 0点位置不可导
- 也被称为L1 Loss,是以绝对误差作为距离
- 由于L1 loss具有稀疏性,为了惩罚较大的值,因此常常将其作为
正则项添加到其他loss中作为约束。 - L1 loss的最大问题是梯度在零点不平滑,导致会跳过极小值。

# 适用于回归任务
# MAE:导数为-1/1 0点位置不可导,一般取0作为导数
import torch
import torch.nn as nn
def dm01():
# 手动创建样本的真实y值
y_true = torch.tensor(data=[1.2, 1.5, 2.0], dtype=torch.float32)
print('y_true->', y_true.dtype)
# 手动创建样本的预测y值 -> 模型预测值
# 0.6901, 0.5459, 0.2469 -> sigmoid函数的激活值
y_pred = torch.tensor(data=[1.3, 1.7, 2.0], requires_grad=True, dtype=torch.float32)
# 创建回归任务MAE损失对象
# reduction:损失值计算的方式, 默认mean 平均损失值
criterion = nn.L1Loss()
# 调用损失对象计算损失值
# 预测y 真实y
loss = criterion(y_pred, y_true)
print('loss->', loss)
print('loss->', loss.requires_grad)
if __name__ == '__main__':
dm01()
MSE损失函数
- 数据集中有异常(噪声)样本, 影响结果, 误差放大, 容易产生梯度爆炸
- 任意位置可导, 越接近底部, 导数越小, 符合梯度下降套路, 可以找到最小值
# 适用于回归任务, 如果数据集有异常样本, 放大误差, 有可能导致梯度爆炸
# MSE: 任意位置都可导, 越接近底部, 导数越小
import torch
import torch.nn as nn
def dm01():
# 手动创建样本的真实y值
y_true = torch.tensor(data=[1.2, 1.5, 2.0], dtype=torch.float32)
print('y_true->', y_true.dtype)
# 手动创建样本的预测y值 -> 模型预测值
# 0.6901, 0.5459, 0.2469 -> sigmoid函数的激活值
y_pred = torch.tensor(data=[1.3, 1.7, 2.0], requires_grad=True, dtype=torch.float32)
# 创建回归任务MAE损失对象
# reduction:损失值计算的方式, 默认mean 平均损失值
criterion = nn.MSELoss()
# 调用损失对象计算损失值
# 预测y 真实y
loss = criterion(y_pred, y_true)
print('loss->', loss)
print('loss->', loss.requires_grad)
if __name__ == '__main__':
dm01()
Smooth L1损失函数
-
中和MAE和MSE, w小于-1或w大于1使用MAE, w在[-1,1]范围内使用MSE
-
不受异常值影响,不容易产生梯度爆炸,在任意位置可导,可以找到最小值
-
nn.SmoothL1()
-

-
其中:𝑥=f(x)−y 为真实值和预测值的差值。

从图中可以看出,该函数实际上就是一个分段函数
在[-1,1]之间实际上就是L2损失,这样解决了L1的不光滑问题
在[-1,1]区间外,实际上就是L1损失,这样就解决了离群点梯度爆炸的问题
# 适用于回归任务, 不受异常值影响
# smooth l1: 任意位置都可导, 越接近底部, 导数越小
import torch
import torch.nn as nn
def dm01():
# 手动创建样本的真实y值
y_true = torch.tensor(data=[1.2, 1.5, 2.0], dtype=torch.float32)
print('y_true->', y_true.dtype)
# 手动创建样本的预测y值 -> 模型预测值
# 0.6901, 0.5459, 0.2469 -> sigmoid函数的激活值
y_pred = torch.tensor(data=[1.3, 1.7, 2.0], requires_grad=True, dtype=torch.float32)
# 创建回归任务smoothl1损失对象
# reduction:损失值计算的方式, 默认mean 平均损失值
criterion = nn.SmoothL1Loss()
# 调用损失对象计算损失值
# 预测y 真实y
loss = criterion(y_pred, y_true)
print('loss->', loss)
print('loss->', loss.requires_grad)
if __name__ == '__main__':
dm01()
神经网络优化方法
梯度下降算法回顾
-
W = W - lr * grad
-
梯度下降算法是一种寻找最优网络参数的策略, 计算每次损失值对应的梯度, 进行参数更新
- BGD, 使用全部样本计算梯度, 计算量大
- SGD, 使用全部样本中随机一个样本计算梯度, 梯度可能不合理
- Min-Batch, 使用一批样本计算梯度, 计算量相对小, 梯度更加合理
反向传播算法(BP算法)
-
反向传播就是算当前损失值和参数的导数(梯度), 需要借助梯度连乘的方式

-
结合梯度下降算法更新参数 W1 = W0 - LR * 梯度 -> W0和LR已知, 梯度根据BP算法计算出
梯度下降优化方法
避免遇到鞍点以及局部最小值情况
指数移动加权平均
-
St=(1−β)Yt+βSt−1 S_t= (1-β)Y_t + βS_{t-1} St=(1−β)Yt+βSt−1
St表示指数加权平均值,Yt表示t时刻的值,β表示调节权重系数(一般是0.9),该值越大,平均数越平缓。
-
S100 =
0.1*Y100 + 0.1*0.9*Y99 + 0.1*0.9*0.9*Y98 + .... -
β值越大, 受当前时刻真实值越小, 一般0.9
-
距离越远的数字权重越小
动量算法Momentum
-
St=(1−β)gt+βSt−1 S_t = (1-β)g_t + βS_{t-1} St=(1−β)gt+βSt−1
s_t是当前时刻指数加权平均梯度值
s_t-1是历史指数加权平均梯度值
g_t是当前时刻的梯度值
β 是调节权重系数,通常取 0.9 或 0.99
η是学习率
w_t是当前时刻模型权重参数# 动量法计算梯度实际上计算的是当前时刻的指数移动加权平均梯度值 import torch from torch import optim def dm01(): # todo: 1-初始化权重参数 w = torch.tensor([1.0], requires_grad=True, dtype=torch.float32) # 自定义损失函数, 实际工作中调用不同任务的损失函数, 交叉熵损失/MSE损失... loss = ((w ** 2) / 2.0).sum() # todo: 2-创建优化器函数对象 SGD加入参数momentum->动量法 # momentum: 动量法, 一般0.9或0.99 optimizer = optim.SGD([w], lr=0.01, momentum=0.9) # todo: 3-计算梯度值 optimizer.zero_grad() loss.sum().backward() # todo: 4-更新权重参数 梯度更新 optimizer.step() print('w.grad->', w.grad) # 第二次计算 loss = ((w ** 2) / 2.0).sum() optimizer.zero_grad() loss.backward() optimizer.step() print('w.grad->', w.grad) if __name__ == '__main__': dm01()
AdaGrad

-
随着训练次数增加调整学习率, 开始学习率大, 后续学习率越来越小
# adagrad优化方法调整学习率, 随着训练次数增加, 学习率越来越小, 一开始的学习率比较大 import torch from torch import optim def dm01(): # todo: 1-初始化权重参数 w = torch.tensor([1.0], requires_grad=True, dtype=torch.float32) loss = ((w ** 2) / 2.0).sum() # todo: 2-创建优化器函数对象 Adagrad optimizer = optim.Adagrad([w], lr=0.01) # todo: 3-计算梯度值 optimizer.zero_grad() loss.sum().backward() # todo: 4-更新权重参数 梯度更新 optimizer.step() print('w.grad->', w.grad, w) # 第二次计算 loss = ((w ** 2) / 2.0).sum() optimizer.zero_grad() loss.backward() optimizer.step() print('w.grad->', w.grad, w) # 第三次计算 loss = ((w ** 2) / 2.0).sum() optimizer.zero_grad() loss.backward() optimizer.step() print('w.grad->', w.grad, w) if __name__ == '__main__': dm01()
RMSProp

-
对adagrap优化, 使用指数移动加权平均梯度代替累加梯度
# rmsprop优化方法调整学习率, 对adagrad方法进行优化, 使用指数移动加权平均梯度代替历史累加梯度 # 避免学习率过快减小, 导致后续模型收敛速度慢 import torch from torch import optim def dm01(): # todo: 1-初始化权重参数 w = torch.tensor([1.0], requires_grad=True, dtype=torch.float32) loss = ((w ** 2) / 2.0).sum() # todo: 2-创建优化器函数对象 RMSprop optimizer = optim.RMSprop([w], lr=0.01, alpha=0.9) # todo: 3-计算梯度值 optimizer.zero_grad() loss.sum().backward() # todo: 4-更新权重参数 梯度更新 optimizer.step() print('w.grad->', w.grad, w) # 第二次计算 loss = ((w ** 2) / 2.0).sum() optimizer.zero_grad() loss.backward() optimizer.step() print('w.grad->', w.grad, w) # 第三次计算 loss = ((w ** 2) / 2.0).sum() optimizer.zero_grad() loss.backward() optimizer.step() print('w.grad->', w.grad, w) if __name__ == '__main__': dm01()
Adam

-
修正梯度和学习率
# adam既优化学习率又优化梯度 adam=rmsprop+momentum import torch from torch import optim def dm01(): # todo: 1-初始化权重参数 w = torch.tensor([1.0], requires_grad=True, dtype=torch.float32) loss = ((w ** 2) / 2.0).sum() # todo: 2-创建优化器函数对象 # betas: 两个β值, 接收元组类型 optimizer = optim.Adam([w], lr=0.01, betas=(0.9, 0.99)) # todo: 3-计算梯度值 optimizer.zero_grad() loss.sum().backward() # todo: 4-更新权重参数 梯度更新 optimizer.step() print('w.grad->', w.grad, w) # 第二次计算 loss = ((w ** 2) / 2.0).sum() optimizer.zero_grad() loss.backward() optimizer.step() print('w.grad->', w.grad, w) # 第三次计算 loss = ((w ** 2) / 2.0).sum() optimizer.zero_grad() loss.backward() optimizer.step() print('w.grad->', w.grad, w) if __name__ == '__main__': dm01()
如何选择梯度下降优化方法
- SGD和momentum适用于简单任务或浅层神经网络模型
- Adam适用于复杂任务或数据量大
- AdaGrad和RMSprop适用于文本数据处理 nlp
学习率衰减优化方法
为什么进行学习率优化
- 根据模型训练次数, 手动调整学习率, 使模型前期收敛速度快, 后期收敛速度慢
等间隔学习率衰减
# 等间隔: 指定训练次数后修改学习率 lr=lr*gamma
import torch
from torch import optim
import matplotlib.pyplot as plt
def dm01():
# todo: 1-初始化参数
# lr epoch iteration
lr = 0.1
epoch = 200
iteration = 10
# todo: 2-创建数据集
# y_true x w
y_true = torch.tensor([0])
x = torch.tensor([1.0], dtype=torch.float32)
w = torch.tensor([1.0], requires_grad=True, dtype=torch.float32)
# todo: 3-创建优化器对象 动量法
optimizer = optim.SGD(params=[w], lr=lr, momentum=0.9)
# todo: 4-创建等间隔学习率衰减对象
# optimizer: 优化器对象
# step_size: 间隔, 指定训练次数后修改学习率
# gamma: 衰减系数 默认0.1 lr=lr*gamma
scheduer = optim.lr_scheduler.StepLR(optimizer=optimizer, step_size=50, gamma=0.5)
# todo: 5-创建两个列表, 收集训练次数, 收集每次训练lr
lr_list, epoch_list = [], []
# todo: 6-循环遍历训练次数
for i in range(epoch):
# todo: 7-获取每次训练的次数和lr保存到列表中
# scheduer.get_last_lr(): 获取最后lr
lr_list.append(scheduer.get_last_lr())
epoch_list.append(i)
# todo: 8-循环遍历, batch计算
for batch in range(iteration):
# 先算预测y值 wx, 计算损失值 (wx-y_true)**2
y_pred = w * x
loss = (y_pred - y_true) ** 2
# 梯度清零
optimizer.zero_grad()
# 梯度计算
loss.backward()
# 参数更新
optimizer.step()
# todo: 9-更新下一次训练的学习率
scheduer.step()
print('lr_list->', lr_list)
plt.plot(epoch_list, lr_list)
plt.xlabel("Epoch")
plt.ylabel("Learning rate")
plt.legend()
plt.show()
if __name__ == '__main__':
dm01()
指定间隔学习率衰减
-
前期模型训练设置比较多次数学习率更大, 中期设置相对少训练次数学习率小一些, 后期设置更少训练次数学习率更小一些
# 指定间隔: 通过步长列表指定训练次数后修改学习率 lr=lr*gamma import torch from torch import optim import matplotlib.pyplot as plt def dm01(): # todo: 1-初始化参数 # lr epoch iteration lr = 0.1 epoch = 200 iteration = 10 # todo: 2-创建数据集 # y_true x w y_true = torch.tensor([0]) x = torch.tensor([1.0], dtype=torch.float32) w = torch.tensor([1.0], requires_grad=True, dtype=torch.float32) # todo: 3-创建优化器对象 动量法 optimizer = optim.SGD(params=[w], lr=lr, momentum=0.9) # todo: 4-创建等间隔学习率衰减对象 # optimizer: 优化器对象 # milestones: 指定间隔列表, 指定训练次数后修改学习率 # gamma: 衰减系数 默认0.1 scheduer = optim.lr_scheduler.MultiStepLR(optimizer=optimizer, milestones=[50, 100, 160], gamma=0.5, last_epoch=-1) # todo: 5-创建两个列表, 收集训练次数, 收集每次训练lr lr_list, epoch_list = [], [] # todo: 6-循环遍历训练次数 for i in range(epoch): # todo: 7-获取每次训练的次数和lr保存到列表中 # scheduer.get_last_lr(): 获取最后lr lr_list.append(scheduer.get_last_lr()) epoch_list.append(i) # todo: 8-循环遍历, batch计算 for batch in range(iteration): # 先算预测y值 wx, 计算损失值 (wx-y_true)**2 y_pred = w * x loss = (y_pred - y_true) ** 2 # 梯度清零 optimizer.zero_grad() # 梯度计算 loss.backward() # 参数更新 optimizer.step() # todo: 9-更新下一次训练的学习率 scheduer.step() print('lr_list->', lr_list) plt.plot(epoch_list, lr_list) plt.xlabel("Epoch") plt.ylabel("Learning rate") plt.legend() plt.show() if __name__ == '__main__': dm01()
按指数学习率衰减
-
根据指数衰减, lr=lr * gamma^{epoch}
-
更符合梯度下降规律, 前期下降快, 中期缓慢, 后期更慢
# 指数间隔: 前期学习率衰减快, 中期慢, 后期更慢 lr=lr * gamma**epoch import torch from torch import optim import matplotlib.pyplot as plt def dm01(): # todo: 1-初始化参数 # lr epoch iteration lr = 0.1 epoch = 200 iteration = 10 # todo: 2-创建数据集 # y_true x w y_true = torch.tensor([0]) x = torch.tensor([1.0], dtype=torch.float32) w = torch.tensor([1.0], requires_grad=True, dtype=torch.float32) # todo: 3-创建优化器对象 动量法 optimizer = optim.SGD(params=[w], lr=lr, momentum=0.9) # todo: 4-创建等间隔学习率衰减对象 # optimizer: 优化器对象 # gamma: 衰减系数 设置大一些, 初始指数大 scheduer = optim.lr_scheduler.ExponentialLR(optimizer=optimizer, gamma=0.9) # todo: 5-创建两个列表, 收集训练次数, 收集每次训练lr lr_list, epoch_list = [], [] # todo: 6-循环遍历训练次数 for i in range(epoch): # todo: 7-获取每次训练的次数和lr保存到列表中 # scheduer.get_last_lr(): 获取最后lr lr_list.append(scheduer.get_last_lr()) epoch_list.append(i) # todo: 8-循环遍历, batch计算 for batch in range(iteration): # 先算预测y值 wx, 计算损失值 (wx-y_true)**2 y_pred = w * x loss = (y_pred - y_true) ** 2 # 梯度清零 optimizer.zero_grad() # 梯度计算 loss.backward() # 参数更新 optimizer.step() # todo: 9-更新下一次训练的学习率 scheduer.step() print('lr_list->', lr_list) plt.plot(epoch_list, lr_list) plt.xlabel("Epoch") plt.ylabel("Learning rate") plt.legend() plt.show() if __name__ == '__main__': dm01()

如何选择学习率衰减方法
- 优先选择指数学习率衰减方法
- 根据经验设置间隔选择指定间隔学习率衰减方法
- 简单模型选择等间隔学习率衰减方法
正则化方法
什么是正则
- 防止模型过拟合(训练集效果好, 测试集效果差), 提高模型泛化能力
- 一种防止过拟合, 提高模型泛化能力的策略
- L1正则: 需要通过手动写代码实现
- L2正则: SGD(weight_decay=)
- dropout
- BN
Dropout正则化
-
让神经元以p概率随机死亡, 每批次样本训练模型时, 死亡的神经元都是随机, 防止预测结果受某个神经元影响(防止过拟合)
-
p概率->[0.2, 0.5], 简单模型概率低, 复杂模型概率高
-
不失活的神经元计算结果除以(1-p), 让训练时输出结果和测试时(dropout不生效)结果一致
- 训练模型 -> model.train()
- 测试模型 -> model.eval()
-
dropout是在激活层后使用
import torch import torch.nn as nn # dropout随机失活: 每批次样本训练时,随机让一部分神经元死亡,防止一些特征对结果影响大(防止过拟合) def dm01(): # todo:1-创建隐藏层输出结果 # float(): 转换成浮点类型张量 t1 = torch.randint(low=0, high=10, size=(1, 4)).float() print('t1->', t1) # todo:2-进行下一层加权求和计算 linear1 = nn.Linear(in_features=4, out_features=4) l1 = linear1(t1) print('l1->', l1) # todo:3-进行激活值计算 output = torch.sigmoid(l1) print('output->', output) # todo:4-对激活值进行dropout处理 训练阶段 # p: 失活概率 dropout = nn.Dropout(p=0.4) d1 = dropout(output) print('d1->', d1) if __name__ == '__main__': dm01()
批量归一正则化(Batch Normalization)
-
计算每个batch样本的均值和标准差, 利用均值和标准差计算出标准化的值
-
每个batch的均值和标准差都不一样, 会引入噪声样本数据, 降低训练模型效果(防止过拟合)
-
引入两个自学习的γ和β参数, 让每层的样本分布不一样(每层的激活函数可以不一样)
-
加速模型训练效果, 数据分布越均匀, 加权求和结果落入到合理区间(导数最大),例如sisgmoid激活函数在[-3, 3]之间有较好的效果,如果没有标准化,模型可能收敛较慢。
-
训练时进行标准化, 测试时不进行标准化

"""
正则化: 每批样本的均值和方差不一样, 引入噪声样本
加快模型收敛: 样本标准化后, 落入激活函数的合理区间, 导数尽可能最大
"""
import torch
import torch.nn as nn
# nn.BatchNorm1d(): 处理一维样本, 每批样本数最少是2个, 否则无法计算均值和标准差
# nn.BatchNorm2d(): 处理二维样本, 图像(每个通道由二维矩阵组成), 计算二维矩阵每列均值和标准差
# nn.BatchNorm3d(): 处理三维样本, 视频
# 处理二维数据
def dm01():
# todo:1-创建图像样本数据集 2个通道,每个通道3*4列特征图, 卷积层处理的特征图样本
# 数据集只有一张图像, 图像是由2个通道组成(RGB), 每个通道由3*4像素矩阵
input_2d = torch.randn(size=(1, 2, 3, 4))
print('input_2d->', input_2d)
# todo:2-创建BN层, 标准化 ->一定是在激活函数前进行标准化
# num_features: 输入样本的通道数
# eps: 小常数, 避免除0
# momentum: 指数移动加权平均值
# affine: 默认True, 引入可学习的γ和β参数,对归一化后的数据进行缩放和平移
bn2d = nn.BatchNorm2d(num_features=2, eps=1e-5, momentum=0.1, affine=True)
ouput_2d = bn2d(input_2d)
print('ouput_2d->', ouput_2d)
# 处理一维数据
def dm02():
# 创建样本数据集
input_1d = torch.randn(size=(2, 2))
# 创建线性层
linear1 = nn.Linear(in_features=2, out_features=4)
l1 = linear1(input_1d)
print('l1->', l1)
# 创建BN层
bn1d = nn.BatchNorm1d(num_features=4) #num_features=4,是上一层的输出结果out_features=4
# 对线性层的结果进行标准化处理
output_1d = bn1d(l1)
print('output_1d->', output_1d)
if __name__ == '__main__':
# dm01()
dm02()
手机价格分类案例
案例需求
- 分类问题 0,1,2,3 四个类别
- 实现步骤
- 准备数据集 -> 数据集分割, 转换成张量数据集
- 构建神经网络模型 -> 继承nn.module
- 模型训练
- 模型评估
构建张量数据集
# 导入相关模块
import torch
from torch.utils.data import TensorDataset
from torch.utils.data import DataLoader
import torch.nn as nn
from torchsummary import summary
import torch.optim as optim
from sklearn.model_selection import train_test_split
import numpy as np
import pandas as pd
import time
# todo:1-构建数据集
def create_dataset():
print('===========================构建张量数据集对象===========================')
# todo:1-1 加载csv文件数据集
data = pd.read_csv('data/手机价格预测.csv')
print('data.head()->', data.head())
print('data.shape->', data.shape)
# todo:1-2 获取x特征列数据集和y目标列数据集
# iloc属性 下标取值
x, y = data.iloc[:, :-1], data.iloc[:, -1]
# 将特征列转换成浮点类型
x = x.astype(np.float32)
print('x->', x.head())
print('y->', y.head())
# todo:1-3 数据集分割 8:2
x_train, x_valid, y_train, y_valid = train_test_split(x, y, train_size=0.8, random_state=88)
# todo:1-4 数据集转换成张量数据集
# x_train,y_train类型是df对象, df不能直接转换成张量对象
# x_train.values():获取df对象的数据值, 得到numpy数组
# torch.tensor(): numpy数组对象转换成张量对象
train_dataset = TensorDataset(torch.tensor(data=x_train.values), torch.tensor(data=y_train.values))
valid_dataset = TensorDataset(torch.tensor(data=x_valid.values), torch.tensor(data=y_valid.values))
# todo:1-5 返回训练数据集, 测试数据集, 特征数, 类别数
# shape->(行数, 列数) [1]->元组下标取值
# np.unique()->去重 len()->去重后的长度 类别数
print('x.shape[1]->', x.shape[1])
print('len(np.unique(y)->', len(np.unique(y)))
return train_dataset, valid_dataset, x.shape[1], len(np.unique(y))
if __name__ == '__main__':
train_dataset, valid_dataset, input_dim, class_num = create_dataset()
构建分类神经网络模型
# todo:2-构建神经网络分类模型
class PhonePriceModel(nn.Module):
print('===========================构建神经网络分类模型===========================')
# todo:2-1 构建神经网络 __init__()
def __init__(self, input_dim, output_dim):
# 继承父类的构造方法
super().__init__()
# 第一层隐藏层
self.linear1 = nn.Linear(in_features=input_dim, out_features=128)
# 第二层隐藏层
self.linear2 = nn.Linear(in_features=128, out_features=256)
# 输出层
self.output = nn.Linear(in_features=256, out_features=output_dim)
# todo:2-2 前向传播方法 forward()
def forward(self, x):
# 第一层隐藏层计算
x = torch.relu(input=self.linear1(x))
# 第二层隐藏层计算
x = torch.relu(input=self.linear2(x))
# 输出层计算
# 没有进行softmax激活计算, 后续创建损失函数时使用CrossEntropyLoss=softmax+损失计算
output = self.output(x)
return output
# todo:3-模型训练
# todo:4-模型评估
if __name__ == '__main__':
# 创建张量数据集对象
train_dataset, valid_dataset, input_dim, class_num = create_dataset()
# 创建模型对象
model = PhonePriceModel(input_dim=input_dim, output_dim=class_num)
# 计算模型参数
# input_size: 输入层样本形状
summary(model, input_size=(16, input_dim))
模型训练
# todo:3-模型训练
def train(train_dataset, input_dim, class_num):
print('===========================模型训练===========================')
# todo:3-1 创建数据加载器 批量训练
dataloader = DataLoader(dataset=train_dataset, batch_size=8, shuffle=True)
# todo:3-2 创建神经网络分类模型对象, 初始化w和b
model = PhonePriceModel(input_dim=input_dim, output_dim=class_num)
print("======查看模型参数w和b======")
for name, parameter in model.named_parameters():
print(name, parameter)
# todo:3-3 创建损失函数对象 多分类交叉熵损失=softmax+损失计算
criterion = nn.CrossEntropyLoss()
# todo:3-4 创建优化器对象 SGD
optimizer = optim.SGD(params=model.parameters(), lr=1e-3)
# todo:3-5 模型训练 min-batch 随机梯度下降
# 训练轮数
num_epoch = 50
for epoch in range(num_epoch):
# 定义变量统计每次训练的损失值, 训练batch数
total_loss = 0.0
batch_num = 0
# 训练开始的时间
start = time.time()
# 批次训练
for x, y in dataloader:
# 切换模型模式
model.train()
# 模型预测 y预测值
y_pred = model(x)
# print('y_pred->', y_pred)
# 计算损失值
loss = criterion(y_pred, y)
# print('loss->', loss)
# 梯度清零
optimizer.zero_grad()
# 计算梯度
loss.backward()
# 更新参数 梯度下降法
optimizer.step()
# 统计每次训练的所有batch的平均损失值和和batch数
# item(): 获取标量张量的数值
total_loss += loss.item()
batch_num += 1
# 打印损失变换结果
print('epoch: %4s loss: %.2f, time: %.2fs' % (epoch + 1, total_loss / batch_num, time.time() - start))
# todo:3-6 模型保存, 将模型参数保存到字典, 再将字典保存到文件
torch.save(model.state_dict(), 'model/phone.pth')
if __name__ == '__main__':
# 创建张量数据集对象
train_dataset, valid_dataset, input_dim, class_num = create_dataset()
# 创建模型对象
# model = PhonePriceModel(input_dim=input_dim, output_dim=class_num)
# 计算模型参数
# input_size: 输入层样本形状
# summary(model, input_size=(16, input_dim))
# 模型训练
train(train_dataset=train_dataset, input_dim=input_dim, class_num=class_num)
模型评估
# todo:4-模型评估
def test(valid_dataset, input_dim, class_num):
# todo:4-1 创建神经网络分类模型对象
model = PhonePriceModel(input_dim=input_dim, output_dim=class_num)
# todo:4-2 加载训练模型的参数字典
model.load_state_dict(torch.load(f='model/phone.pth'))
# todo:4-3 创建测试集数据加载器
# shuffle: 不需要为True, 预测, 不是训练
dataloader = DataLoader(dataset=valid_dataset, batch_size=8, shuffle=False)
# todo:4-4 定义变量, 初始值为0, 统计预测正确的样本个数
correct = 0
# todo:4-5 按batch进行预测
for x, y in dataloader:
print('y->', y)
# 切换模型模式为预测模式
model.eval()
# 模型预测 y预测值 -> 输出层的加权求和值
output = model(x)
print('output->', output)
# 根据加权求和值得到类别, argmax() 获取最大值对应的下标就是类别 y->0,1,2,3
# dim=1:一行一行处理, 一个样本一个样本
y_pred = torch.argmax(input=output, dim=1)
print('y_pred->', y_pred)
# 统计预测正确的样本个数
print(y_pred == y)
# 对布尔值求和, True->1 False->0
print((y_pred == y).sum())
correct += (y_pred == y).sum()
print('correct->', correct)
# 计算预测精度 准确率
print('Acc: %.5f' % (correct.item() / len(valid_dataset)))
if __name__ == '__main__':
# 创建张量数据集对象
train_dataset, valid_dataset, input_dim, class_num = create_dataset()
# 创建模型对象
# model = PhonePriceModel(input_dim=input_dim, output_dim=class_num)
# 计算模型参数
# input_size: 输入层样本形状
# summary(model, input_size=(16, input_dim))
# 模型训练
# train(train_dataset=train_dataset, input_dim=input_dim, class_num=class_num)
# 模型评估
test(valid_dataset=valid_dataset, input_dim=input_dim, class_num=class_num)
网络性能优化
-
输入层数据进行标准化
-
# 数据标准化 transfer = StandardScaler() x_train = transfer.fit_transform(x_train) # 返回 numpy 数组 x_valid = transfer.transform(x_valid) # 返回 numpy 数组 -
神经网络层数增加, 神经元个数增加
-
class PhonePriceModel(nn.Module): def __init__(self, input_dims, output_dims): super().__init__() self.fc1 = nn.Linear(input_dims, 128) self.fc2 = nn.Linear(128, 256) self.fc3 = nn.Linear(256, 512) self.fc4 = nn.Linear(512, 128) self.output = nn.Linear(128, output_dims) def forward(self, x): x = torch.relu(self.fc1(x)) x = torch.relu(self.fc2(x)) x = torch.relu(self.fc3(x)) x = torch.relu(self.fc4(x)) x = self.output(x) return x -
梯度下降优化方法由SGD调整为Adam
-
学习率由1e-3调整为1e-4
-
optims = optim.Adam(model.parameters(),lr=1e-4) -
正则化
-
增加训练轮数
-
…
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)