一. 定义网络和损失函数
定义网络:(详解见往期文章)
import torch
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
# 定义卷积层和全连接层
self.conv1 = nn.Conv2d(1, 6, 5) # 输入通道数1,输出通道数6,卷积核大小5
self.conv2 = nn.Conv2d(6, 16, 5) # 输入通道数6,输出通道数16,卷积核大小5
self.fc1 = nn.Linear(16 * 5 * 5, 120) # 全连接层,输入大小16*5*5,输出大小120
self.fc2 = nn.Linear(120, 84) # 全连接层,输入大小120,输出大小84
self.fc3 = nn.Linear(84, 10) # 全连接层,输入大小84,输出大小10
def forward(self, x):
# 前向传播过程
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2)) # 激活函数和池化操作
x = F.max_pool2d(F.relu(self.conv2(x)), 2) # 激活函数和池化操作
x = x.view(-1, self.num_flat_features(x)) # 展平操作
x = F.relu(self.fc1(x)) # 激活函数
x = F.relu(self.fc2(x)) # 激活函数
x = self.fc3(x)
return x
def num_flat_features(self, x):
# 计算展平后的特征数
size = x.size()[1:] # 除去批大小维度的其余维度
num_features = 1
for s in size:
num_features *= s
return num_features
net = Net()
print(net)
损失函数:
criterion = nn.MSELoss() # 均方误差损失函数
二.生成输入数据和目标数据
## 生成随机输入数据和目标数据
input = torch.randn(1, 1, 32, 32) # 输入数据,形状为 [1, 1, 32, 32]
target = torch.randn(10) # 目标数据,形状为 [10]
target = target.view(1, -1) # 调整目标数据形状,与输出数据形状一致
以上代码的详解:
1. input = torch.randn(1, 1, 32, 32)
- 作用:生成一个随机的四维张量,模仿神经网络的输入数据(比如图片)。
- 形状说明:
[1, 1, 32, 32]
1
:批次(batch)大小,即一次只处理一张图像。1
:通道数(例如灰度图像只有一个通道)。32, 32
:图片的高和宽,像素尺寸为32x32。
总结:这是一个批次为1、单通道、大小为32x32的灰度图片。
2. target = torch.randn(10)
- 作用:生成一个随机的目标标签向量(通常用于训练的目标值)。
- 形状说明:
[10]
,是一维数组,有10个元素;代表有10个类别(比如10个不同的分类)
3. target = target.view(1, -1)
- 作用:调整目标数据的形状,使它与模型的输出形状匹配或符合输入的要求。
- 具体操作:
view(1, -1)
:将目标数据变成一个二维数组/张量,形状为[1, 10]
。-1
:target = target.view(1, -1)`将`target`张量重塑为形状为 (1, X),其中 X 是从`target`的原始大小推断出来的,而且方便进一步计算损失函数,如:方差,交叉熵等
三. 前向传播计算输出和损失
## 前向传播
output = net(input)
loss = criterion(output, target)
print(loss)
input输入到前面定义的神经网络net(),得到output;这一个过程即为前向传播
前文已经把target升维,就是为了现在计算损失函数(output和target相同格式)
四. 反向传播计算梯度
## 反向传播
net.zero_grad() # 清零梯度
loss.backward() # 计算梯度
-
1.
net.zero_grad()
:在每次反向传播之前,需要清零网络梯度。这是因为 PyTorch 的梯度是累加的,不清零会导致梯度值不断累加。 -
2.
loss.backward()
:调用backward()
方法计算损失值loss
关于网络参数的梯度。这个过程会自动计算每个参数的梯度值,并存储在参数的.grad
属性中 -
图一
五. 更新网络权重
在神经网络训练中,梯度下降用于根据损失函数的梯度信息更新网络的权重和偏置,从而使损失函数的值逐渐减小
梯度下降的基本原理
梯度下降的核心思想是:沿着损失函数梯度的反方向更新参数,因为梯度方向是损失函数增长最快的方向,而其反方向则是损失函数下降最快的方向。
数学表达示如下:(可视化分析更加直观,想看什么样的分析,欢迎评论区留言~)
可以使用简单的梯度下降更新规则:
## 简单的梯度下降更新
learning_rate = 0.01
for f in net.parameters():
f.data.sub_(f.grad.data * learning_rate)
或者使用优化器进行更新:
## 使用优化器
import torch.optim as optim
optimizer = optim.SGD(net.parameters(), lr=0.01) # 定义优化器
## 在训练迭代中
optimizer.zero_grad() # 清零梯度
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step() # 更新权重
虽然可以手动实现梯度下降,但现代深度学习pytorch框架提供了更高级的优化器(如 SGD、Adam 等),这些优化器在内部实现了更复杂的优化算法,通常比简单的梯度下降更有效。
tips:PyTorch 的 nn
包只支持小批量样本的输入。如果输入是一个单独的样本,可以使用 unsqueeze(0)
添加一个 “假的” 批大小维度。
## 添加批大小维度
input_single = torch.randn(1, 32, 32)
input_batch = input_single.unsqueeze(0) # 添加批大小维度
※个人对神经网络的理解:
1)神经网络就像是输入θ,先后经过一层一层函数h(θ)->g(α)->f(β)>L,
2)而反向传播就是按照链式法则依次求函数梯度(个人理解为导函数)如上图一
3)求完梯度后更新参数则是在让计算机沿着梯度下降最快方向找到损失函数loss最小值:
eg:假设你在一座山上,目标是找到山脚(即损失函数的最小值)梯度下降的过程可以类比为:
-
观察坡度:计算当前点的梯度,即观察山坡的坡度。
-
向下走一步:沿着坡度的反方向(即下坡方向)移动一步,步长由学习率决定。
-
重复:不断重复上述过程,直到到达山脚(即损失函数的最小值)。