PyTorch深度学习神经网络基础:损失函数,反向传播,梯度优化
·
PyTorch深度学习Day03:神经网络基础与实战
前言
本文将详细介绍PyTorch中神经网络的构建、激活函数的使用、参数初始化以及自定义神经网络的实现。
1. 广播机制
在深度学习中,我们经常需要对不同形状的张量进行运算。NumPy和PyTorch都支持广播机制,让我们先了解一下这个概念。
1.1 广播机制原理
广播机制允许不同形状的数组进行运算,系统会自动将较小的数组扩展到与较大数组相同的形状。
import numpy as np
def test01():
a = np.array([0,1,2])
print("a-->",a.shape)
b = 2 # [2,2,2] (3,)
print("a+b-->",a+b) # 可以相加
# 解释:b会自动扩展形状为(3,)的数组
def test02():
# 案例一
a = np.array([[1,2,3],[4,5,6]])
b = np.array([1,1,1])
print("a-->\n",a,a.shape)
print("b-->",b,b.shape)
print("a+b-->",a+b)
# 解释:b.shape在左边补1 ->(1,3),然后1进行复制匹配(2,3)
def test04():
# 案例三
a = np.array([1, 2, 3, 4])
b = np.array([[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6]])
print('a-->\n', a, a.shape) # (4,)
print('b-->', b, b.shape) # (4, 3)
print('a+b-->', a + b) # 不可以相加
# 不满足广播条件。
广播规则:
- 从右往左比较维度
- 如果维度大小相等或其中一个为1,则可以广播
- 如果维度缺失,则在该维度前补1
2. 线性回归实战案例
让我们通过一个完整的线性回归案例来理解PyTorch神经网络的构建和训练过程。
2.1 构建数据集
import matplotlib
import torch
from sklearn.datasets import make_regression
import matplotlib.pyplot as plt
from torch import nn, optim
from torch.utils.data import TensorDataset, DataLoader
# 过滤警告
import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning, message=".*__array_wrap__.*")
plt.rcParams["font.sans-serif"] = ["SimHei"] # 设置字体为黑体
plt.rcParams["axes.unicode_minus"] = False # 正常显示负号
matplotlib.use("TkAgg")
def get_dataset():
x, y, coef = make_regression(n_samples=100,
n_features=1,
bias=2,
noise=10,
coef=True,
random_state=22)
# 数值张量化
tensor_x = torch.tensor(x, dtype=torch.float)
tensor_y = torch.tensor(y, dtype=torch.float)
print("tensor_x-->", tensor_x)
print("tensor_y-->", tensor_y)
return tensor_x, tensor_y, coef
2.2 数据可视化
def show_data():
x, y, coef = get_dataset()
# 画图:y = kx + b(k = coef,b=bias)
plt.scatter(x, y)
# 要么把tensor转换为numpy,要么把tensor前置
plt.plot(x.numpy(), coef * x.numpy() + 2)
plt.show()
2.3 构建模型
def make_model():
# 模型
model = nn.Linear(in_features=1, out_features=1)
# 损失函数
criterion = nn.MSELoss()
# 优化器
optimizer = optim.SGD(params=model.parameters(), lr=0.01)
return model, criterion, optimizer
2.4 模型训练
def train_model():
# 得到数据
x, y, coef = get_dataset()
x1, y1 = x, y
my_dataset = TensorDataset(x, y)
# 创建数据加载器
my_dataloader = DataLoader(dataset=my_dataset, batch_size=8, shuffle=True)
# 构建模型
model, criterion, optimizer = make_model()
# 准备轮次
epochs = 100
# 定义额外的参数,用于记录日志和画图
epoch_loss = [] # 记录每一轮次的损失,用于画图
total_loss = 0.0 # 总损失值
total_sample = 0.0 # 总样本数
# 模型训练
for epoch in range(epochs):
for x, y in my_dataloader:
# 在进行损失函数计算时,最好保证预测结果和真实结果的shape是一样的
y = y.reshape(-1, 1)
# 给模型喂数据
y_pred = model(x)
# 计算损失
loss = criterion(y_pred, y)
# 梯度清零
optimizer.zero_grad()
# 反向传播
loss.backward()
# 梯度更新
optimizer.step()
# 其他损失相关的计算操作
total_loss += loss.item()
total_sample += len(y)
# 记录每一轮的平均损失,用于画图
epoch_loss.append(total_loss / total_sample)
print("当前轮次:", epoch + 1, "当前平均损失:", total_loss / total_sample)
# 绘制损失曲线图
plt.plot(range(epochs), epoch_loss)
plt.grid()
plt.title("loss change line")
plt.show()
# 绘制拟合曲线图
plt.scatter(x1, y1)
# 绘制真实拟合曲线
plt.plot(x1, x1 * coef + 2, label="true")
# 绘制模型预测的拟合曲线
# detach():因为模型参数是挂在计算流图上,需要使用时要使用detach()剥离出来
plt.plot(x1, x1 * model.weight.detach().numpy() + model.bias.detach().numpy(), label="train")
plt.grid()
plt.title("train line")
plt.show()
3. 激活函数详解
激活函数是神经网络中非常重要的组成部分,它决定了神经元的输出。让我们详细学习几种常用的激活函数。
3.1 Sigmoid激活函数
Sigmoid函数将输入压缩到(0,1)区间,常用于二分类问题。
def dm01_sigmoid():
# 创建画布, 1行2列
fig, axes = plt.subplots(1, 2)
# 先画第一张图,sigmoid函数值域
x = torch.linspace(-20, 20, 1000)
y = torch.sigmoid(x)
axes[0].plot(x, y)
axes[0].grid()
# 再画第二张图,sigmoid函数导数
# 设置x可以求导
x = torch.linspace(-20, 20, 1000, requires_grad=True)
# .backward()创建梯度只能对标量使用,如果不是会报错
torch.sigmoid(x).sum().backward()
# 上面的报错是由于x被挂在了计算流图中,需要使用detach()取出
axes[1].plot(x.detach(), x.grad)
axes[1].grid()
plt.show()
Sigmoid特点:
- 输出范围:(0, 1)
- 平滑可导
- 容易产生梯度消失问题
3.2 Tanh激活函数
Tanh函数将输入压缩到(-1,1)区间,是Sigmoid的改进版本。
def dm02_tanh():
fig, axes = plt.subplots(1, 2)
# 先画第一张图,tanh函数值域
x = torch.linspace(-20, 20, 1000)
y = torch.tanh(x)
axes[0].plot(x, y)
axes[0].grid()
# 再画第二张图,tanh函数导数
x = torch.linspace(-20, 20, 1000, requires_grad=True)
torch.tanh(x).sum().backward()
axes[1].plot(x.detach(), x.grad)
axes[1].grid()
plt.show()
Tanh特点:
- 输出范围:(-1, 1)
- 零中心化
- 梯度消失问题比Sigmoid轻
3.3 ReLU激活函数
ReLU是目前最常用的激活函数,计算简单且能有效缓解梯度消失问题。
def dm03_relu():
fig, axes = plt.subplots(1, 2)
# 先画第一张图,relu函数值域
x = torch.linspace(-20, 20, 1000)
y = torch.relu(x)
axes[0].plot(x, y)
axes[0].grid()
# 再画第二张图,relu函数导数
x = torch.linspace(-20, 20, 1000, requires_grad=True)
torch.relu(x).sum().backward()
axes[1].plot(x.detach(), x.grad)
axes[1].grid()
plt.show()
ReLU特点:
- 计算简单,收敛快
- 解决梯度消失问题
- 可能产生"死神经元"问题
3.4 Softmax激活函数
Softmax函数将输入转换为概率分布,常用于多分类问题的输出层。
def dm04_softmax():
# 准备logits/score
scores = torch.tensor([[[0.2,0.02,0.15,0.15,1.3,0.5,0.06,1.1,0.05,3.75],
[0.3,0.02,0.15,0.15,1.3,0.5,0.06,1.1,0.05,3.75]],
[[0.2,0.02,0.15,0.15,1.3,0.5,0.06,1.1,0.05,3.75],
[0.3,0.02,0.15,0.15,1.3,0.5,0.06,1.1,0.05,3.75]]])
# 让logits的结果经过softmax
result = torch.softmax(scores, dim=1)
print("result-->", result)
# 结论:在计算softmax的时候,我们一般指定dim=-1
Softmax特点:
- 输出和为1的概率分布
- 常用于多分类输出层
- 数值稳定性需要注意
4. 参数初始化
参数初始化对神经网络的训练效果有重要影响。好的初始化方法可以加速收敛并提高模型性能。
def dm01_initial():
# 创建线性层
linear = nn.Linear(5, 3)
# 1.指定均匀初始化
# nn.init.uniform_(linear.weight)
# 2.指定正态分布初始化
nn.init.normal_(linear.weight)
# 3.指定全0初始化
nn.init.zeros_(linear.weight)
# 4.指定全1初始化
nn.init.ones_(linear.weight)
# 5.指定固定值初始化
nn.init.constant_(linear.weight, 10)
# 6.kaiming初始化
# kaiming 均匀分布
nn.init.kaiming_uniform_(linear.weight)
# kaiming 正态分布
nn.init.kaiming_normal_(linear.weight)
# 7.xavier初始化
# xavier均匀分布
nn.init.xavier_uniform_(linear.weight)
# xavier 正态分布
nn.init.xavier_normal_(linear.weight)
# 结论: 偏置一般初始化为0,权重要么选择kaiming初始化,要么选择xavier初始化
print("linear.weight-->", linear.weight)
初始化方法选择:
- 偏置:一般初始化为0
- 权重:推荐使用Kaiming或Xavier初始化
- Kaiming:适用于ReLU激活函数
- Xavier:适用于Sigmoid和Tanh激活函数
5. 自定义神经网络
在实际项目中,我们经常需要自定义神经网络结构。PyTorch提供了灵活的模块化设计。
5.1 自定义神经网络步骤
# 自定义神经网络步骤:
# 步骤一:继承nn.Module类
# 步骤二:实现2个方法:__init__()和forward()
5.2 完整实现
import torch
import torch.nn as nn
from torchsummary import summary
class MyModel(nn.Module):
# init方法
def __init__(self):
super().__init__()
# 定义第一层隐藏层
self.linear1 = nn.Linear(in_features=3, out_features=3)
nn.init.xavier_uniform_(self.linear1.weight)
nn.init.ones_(self.linear1.bias)
# 定义第二层隐藏层
self.linear2 = nn.Linear(in_features=3, out_features=2)
nn.init.kaiming_normal_(self.linear2.weight)
nn.init.ones_(self.linear2.bias)
# 定义输出层
self.out = nn.Linear(in_features=2, out_features=2)
# forward方法,方法名是固定的,不能写错
def forward(self, x):
# 数据经过第一个线性层
x = self.linear1(x)
# 使用sigmoid激活函数
x = torch.sigmoid(x)
# 数据经过第二个线性层
x = self.linear2(x)
# 使用relu激活函数
x = torch.relu(x)
# 数据经过输出层
x = self.out(x)
# 使用softmax激活函数,让输出层的结果经过softmax
x = torch.softmax(x, dim=-1)
return x
def train_model():
# 创建神经网络模型对象
model = MyModel()
print("model-->", model)
# 准备数据
input = torch.randn(3, 3) # 第一个是样本数,第二个是特征数量
print("input-->", input.shape)
# 给模型喂数据:模型对象名()
# 当我们给模型喂数据的时候,PyTorch框架会自动调用自定义模型的forward方法
output = model(input)
print("output-->", output.shape)
# 统计模型参数
summary(model=model, input_size=(3,))
5.3 模型结构分析
使用torchsummary可以查看模型的详细结构:
# 安装torchsummary
# pip install torchsummary
# 查看模型结构
summary(model=model, input_size=(3,))
6. 完整训练流程总结
通过今天的学习,我们掌握了PyTorch神经网络的完整构建和训练流程:
- 数据准备:使用
TensorDataset和DataLoader处理数据 - 模型构建:继承
nn.Module类自定义网络结构 - 激活函数:选择合适的激活函数
- 参数初始化:使用合适的初始化方法
- 损失函数:选择合适的损失函数
- 优化器:选择合适的优化算法
- 训练循环:前向传播、计算损失、反向传播、参数更新
7. 关键知识点总结
7.1 重要概念
- 广播机制:不同形状张量的运算规则
- 激活函数:Sigmoid、Tanh、ReLU、Softmax的特点和适用场景
- 参数初始化:Kaiming和Xavier初始化的选择
- 自定义网络:继承nn.Module的基本步骤
7.2 编程技巧
- 使用
detach()方法从计算图中分离张量 - 使用
requires_grad=True启用梯度计算 - 使用
zero_grad()清零梯度 - 使用
summary()查看模型结构
7.3 常见问题
- 梯度消失问题:选择合适的激活函数和初始化方法
- 形状不匹配:注意张量的维度对应关系
- 内存管理:及时使用
detach()释放不需要的梯度信息
参考资料:
- PyTorch官方文档
- 深度学习课程讲义
- 相关技术博客和论文
版权声明:本文仅供学习交流使用,转载请注明出处。
更多推荐


所有评论(0)