深度学习第三节(使用Pytorch构建神经网络)

神经网络通常包括输入层、隐藏层、输出层、激活函数、损失函数和学习率等基本组件。本节介绍在简单数据集上使用pytorch构建神经网络,利用张量对象操作和梯度值计算更新网络权重。

1.pytroch构建神经网络

​ 解决两个数字的相加问题

1.初始化数据集

1
2
3
import torch
x = [[1,2],[3,4],[5,6],[7,8]]
y = [[3],[7],[5],[11]]

2.将输入列表转换为张量对象,并转换为浮点对象,此外将输入X和输出Y数据点注册到device中

1
2
3
4
5
6
X = torch.tensor(x).float()
Y = torch.tensor(y).float()

device = 'cuda' if torch.cuda.is_available else 'cpu'
X = X.to(device)
Y = Y.to(device)

3.定义神经网络架构,导入torch.nn模块用于构建神经网络模型,创建MyNeuralNet,继承自nn.Module,nn.Module是所有神经网络模块的基类

1
2
3
4
5
6
7
8
9
10
from torch import nn
class MyNeuralNet(nn.Module):
def __init__(self):
super().__init__()

#定义神经网络中的网络层
self.input_to_hidden_layer = nn.Linear(2,8)
#全连接层以2个值作为输入并输出8个值,且包含与之关联的偏置参数
self.hidden_layer_activation = nn.ReLU()
self.hidden_to_output_layer = nn.Linear(8,1)

注释:使用__init__方法初始化神经网络的所有组件,调用super().__init__()可以利用nn.Module编写的所有预构建函数,初始化的组件将用于MyNeuraNet类中的不同方法。全连接层(self.input_to_hidden_layer),使用ReLU激活函数的隐藏层(self.hidden_layer_activation),最后也是一个全连接层(self.hidden_to_output_layer)

4.将初始化的神经网络组件连接在一起,并定义网络的前向传播方法forward:

1
2
3
4
5
def forward(self,x):
x = self.input_to_hidden_layer(x)
x = self.hidden_layer_activation(x)
x = self.hidden_to_output_layer(x)
return x

注意:必须使用forward作为前向传播的函数名,因为Pytorch保留此函数作为执行前向传播的方法,使用其他名称都会引发错误。

5.执行以下代码访问每个深井网络组件的初始权重

1
2
#创建MyNeuralNet对象的实例并将其注册到device
mynet = MyNeuralNet().to(device)
1
2
3
4
5
6
7
8
9
10
11
12
print(mynet.input_to_hidden_layer.weight)
#类似代码可以访问每一层的权重和偏置
#Parameter containing:
#tensor([[-0.4344, 0.3873],
[ 0.3148, 0.6663],
[ 0.5689, -0.5287],
[-0.6481, 0.1600],
[ 0.1827, -0.1818],
[-0.6489, 0.0926],
[ 0.1215, 0.4913],
[ 0.2923, -0.6248]], device='cuda:0', requires_grad=True)
#每次执行时输出的值并不相同,因为神经网络每次都使用随机值进行初始化,如果希望在执行相同代码时保持相同输出,需要在创建类对象的实例之前使用Torch中的manual_seed方法指定随机种子,torch.manual_seed(0)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
mynet.parameters()
#可以获得神经网络的所有参数,会返回一个生成器,通过生成器循环获取参数
for param in mynet.parameters():
print(param)

#代码结果如下:
Parameter containing:
tensor([[-0.1700, 0.1255],
[-0.6442, -0.0863],
[ 0.0079, -0.1979],
[-0.3271, -0.3621],
[-0.5230, 0.5031],
[-0.6461, -0.5630],
[ 0.3262, -0.0498],
[ 0.3311, 0.3402]], device='cuda:0', requires_grad=True)
Parameter containing:
tensor([ 0.0449, 0.5957, -0.0974, 0.3396, 0.2025, 0.6109, -0.4293, -0.0846],
device='cuda:0', requires_grad=True)
Parameter containing:
tensor([[ 0.3228, 0.2529, 0.3183, -0.2758, 0.2793, -0.0019, -0.2335, 0.3072]],
device='cuda:0', requires_grad=True)
Parameter containing:
tensor([0.0602], device='cuda:0', requires_grad=True)

该模型已经将张量注册为跟踪前向和反向传播所必须的特殊对象,在init初始化方法中定义nn神经网络层时会自动创建相应的张量并进行注册,也可以使用nn.parameter()函数手动注册这些参数。等价于如下代码:

1
2
3
4
5
6
7
8
9
10
11
class MyNeuralNet(nn.module):
def __init__(self):
super().__init__()
self.input_to_hidden_layer = nn.parameter(torch.rand(2,8))
self.hidden_layer_activation = nn.ReLU()
self.hidden_to_output_layer = nn.parameter(torch.rand(8,1))
def forward(self,x):
x = x @ self.input_to_hidden_layer
x = self.hidden_layer_activation(x)
x = x @ self.hidden_to_output_layer
return x

6.定义损失函数,由于需要预测连续输出,因此使用均方误差作为损失函数

1
loss_func = nn.MSELoss()

通过将输入值传递给神经网络对象,然后计算给定输入的损失函数值

1
2
3
4
5
6
7
8
_Y = mynet(X)
#根据给定输入通过神经网络计算输出值

loss_value = loss_func(_Y,Y)
#计算MSELoss值,注意在计算损失时,我们总是先传入预测结果,然后传入实际标记值

print(loss_value)
#tensor(33.2581, device='cuda:0', grad_fn=<MseLossBackward0>)

7.定义降低损失值的优化器,优化器的输入是与神经网络相对应的参数(权重和偏差)以及更新权重时的学习率。本节我们使用随机梯度下降(SGD)。

1
2
from torch.optim import SGD
opt = SGD(mynet.parameters(),lr = 0.001)

8.训练

​ 训练神经网络的标准套路,每个epoch都会清空梯度 → 前向传播算预测 → 算 loss → 反向传播算梯度 → 用优化器更新参数

1
2
3
4
5
6
7
8
9
10
11
12
#一个epoch
opt.zero_grad()
#清除掉上一次训练时累积的梯度值,PyTorch 默认梯度是累加的(因为有时候需要多次backward() 再合并更新),所以每一轮训练之前都要把梯度清零,否则梯度会“越积越多”,参数更新就会出问题。

loss_value = loss_func(mynet(X),Y)
#先用 mynet(X) 把输入数据跑一遍,得到预测值。然后拿预测值和真实标签 Y 做对比,算出误差(loss)。

loss_value.backward()
#自动求导,计算每个参数的梯度

opt.step()
#用刚才算好的梯度去更新参数。更新方式取决于优化器(比如 SGD, Adam),它会按公式调整权重,让 loss 变小。
1
2
3
4
5
6
7
8
#使用for循环重复执行上述步骤,执行50个epoch,此外loss_history列表存储每个epoch中的损失值。
loss_history = []
for _ in range(50):
opt.zero_grad()
loss_value = loss_func(mynet(X),Y)
loss_value.backward()
opt.step()
loss_history.append(loss_value.item())

9.绘制损失随epoch的变化情况

1
2
3
4
5
6
import matplotlib.pyplot as plt
plt.plot(loss_history)
plt.title('Loss Variation over increasing epochs')
plt.xlabel('epochs')
plt.ylabel('loss value')
plt.show()
image-20250913143328966

2.神经网络数据加载

​ 批大小(batch size)是神经网络中重要的超参数,批大小是指计算损失值或更新权重时考虑的数据样本数。假设数据集中有数百万个数据样本,一次将所有数据点用于一次权重更新并非最佳选择,因为内存可能无法容纳如此多数据。

1.导入加载数据和处理数据集的方法,导入数据将数据转换为浮点数,并将他们注册到相应设备中。

1
2
3
4
5
6
7
8
9
10
11
12
from torch.utils.data import Dataset,DataLoader
import torch
import torch.nn as nn

x = [[1,2],[3,4],[5,6],[7,8]]
y = [[3],[7],[11],[15]]
X = torch.tensor(x).float()
Y = torch.tensor(y).float()

device = 'cuda' if torch.cuda.is_available() else 'cpu'
X = X.to(device)
Y = Y.to(device)

2.创建数据集类MyDataset:

​ Dataset的三个方法:init(self,x,y),len(self)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MyDataset(Dataset):
#存储数据信息以便可以将一批(batch)数据点捆绑在一起(使用DataLoader),并通过一次前向和反向传播更新权重。

#接受输入和输出
def __init__(self,x,y):
self.x = x.clone().detach()
#避免梯度信息污染数据集
self.y = y.clone().detach()

#指定输入数据集的长度(__len__),(样本数)
def __len__(self):
return len(self.x)

#getitem方法用于获取指定数据样本
def __getitem__(self,ix):
return self.x[ix],self.y[ix]
#ix表示要从数据集获取的数据索引,(输入+标签)

3.创建自定义类的实例

1
ds = MyDataset(X,Y)

4.通过DataLoader传递数据集实例

1
dl = DataLoader(ds,batch_size=2,shuffle=True)

​ 指定从ds输入数据集中获取两个(batch_size=2)的随机样本(shuffle=True)数据点。

1
2
3
4
5
6
7
8
9
for x,y in dl:
print(x,y)
#输出
#tensor([[5., 6.],
[1., 2.]], device='cuda:0') tensor([[5.],
[3.]], device='cuda:0')
tensor([[3., 4.],
[7., 8.]], device='cuda:0') tensor([[ 7.],
[11.]], device='cuda:0')

​ 生成两组输入-输出,因为原始数据集中共有4个数据点,而指定的批大小为2

5.定义神经网络类

1
2
3
4
5
6
7
8
9
10
11
class MyNeuralNet(nn.Module):
def __init__(self):
super().__init__()
self.input_to_hidden_layer = nn.Linear(2,8)
self.hidden_layer_activation = nn.ReLU()
self.hidden_to_output_layer = nn.Linear(8,1)
def forward(self,x):
x = self.input_to_hidden_layer(x)
x = self.hidden_layer_activation(x)
x = self.hidden_to_output_layer(x)
return x

6.定义模型对象(mynet),损失函数(loss_func)和优化器(opt):

1
2
3
4
5
mynet = MyNeuralNet().to(device)
loss_func = nn.MSELoss()

from torch.optim import SGD
opt = SGD(mynet.parameters(),lr = 0.001)

7.循环遍历批数据点以最小化损失值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import time
loss_history = []
start = time.time()
for _ in range(50):
for data in dl:
x,y = data
opt.zero_grad()
loss_value = loss_func(mynet(x),y)
loss_value.backward()
opt.step()
loss_history.append(loss_value.item())
end = time.time()
print(end-start)
#0.31676459312438965

与上一节相比,每个epoch更新权重的次数是原来的2倍,因为本节中使用的批大小是2,而上一节批大小为4,即以此使用全部数据点。

2.1模型测试

​ 前面是如何在已知数据点上拟合数据,接下来介绍如何利用前面训练好的mynet模型中定义的前向传播方法来预测模型没有见过的数据点(测试数据)。

1.创建用于测试模型的数据点,并且将新数据点转换为张量浮点对象并注册到device中。

1
2
val_x = [[10,11]]
val_x = torch.tensor(val_x).float().to(device)

2.通过训练好的神经网络(mynet)传递张量对象,与通过模型执行前向传播的方法相同:

1
2
print(mynet(val_x))
#tensor([[20.9521]], device='cuda:0', grad_fn=<AddmmBackward0>)

2.2获取中间层的值

​ 在实际应用中,可能需要获取神经网络的中间层值,例如风格迁移和迁移学习等。pytorch提供了两种方式。

1.直接调用神经网络层,当成函数使用

1
print(mynet.hidden_layer_activation(mynet.input_to_hidden_layer(X)))

注意:必须按照模型输入、输出顺序调用相应神经网络层。input_to_hidden_layer的输出是hidden_layer_activation的输入。

2.forward方法指明想要查看的网络层

1
2
3
4
5
6
7
8
9
10
11
class MyNeuralNet(nn.Module):
def __init__(self):
super().__init__()
self.input_to_hidden_layer = nn.Linear(2,8)
self.hidden_layer_activation = nn.ReLU()
self.hidden_to_output_layer = nn.Linear(8,1)
def forward(self,x):
hidden1 = self.input_to_hidden_layer(x)
hidden2 = self.hidden_layer_activation(hidden1)
x = self.hidden_to_output_layer(hidden2)
return x,hidden2

通过以下代码访问隐藏层值,mynet的第0个索引输出的是网络前向传播的最终输出,而第一个索引输出的是隐藏层激活后的值。

1
print(mynet(X)[1])

3.使用Sequential类构建神经网络

​ 除非需要构建一个复杂的网络,否则只需要利用Sequential类并指定层与层堆叠的顺序即可搭建神经网络。本节继续使用简单数据集训练神经网络。

1.导入相关的库,定义使用的设备,定义数据集与数据集类(MyDataset)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import torch
import torch.nn as nn
import numpy as np
from torch.utils.data import Dataset,DataLoader
device = 'cuda' if torch.cuda.is_available() else 'cpu'

x = [[1,2],[3,4],[5,6],[7,8]]
y = [[3],[7],[11],[15]]

class MyDataset(Dataset):
def __init__(self,x,y):
self.x = torch.tensor(x).float().to(device)
self.y = torch.tensor(y).float().to(device)
def __len__(self):
return len(self.x)
def __getitem__(self,ix):
return self.x[ix],self.y[ix]

2.定义数据集ds和数据加载对象dl

1
2
ds = MyDataset(x,y)
dl = DataLoader(ds,batch_size = 2,shuffle = True)

3.使用nn模块中的Sequential类定义模型架构:

​ nn,Linear接受二维输入并为每个数据点提供八维输出,nn.ReLU在八维输出之上执行ReLU激活。最后使用nn.Linear接受八维输入并得到一维输出。

1
2
3
4
5
model = nn.Sequential(
nn.Linear(2,8),
nn.ReLU(),
nn.Linear(8,1)
).to(device)

4.打印模型的摘要summary,查看模型架构信息

需要安装torchsummary库,pip install torchsummary

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from torchsummary import summary

print(summart(model,(2,))
#打印模型摘要,函数接受模型名称以及模型输入大小(需要使用整数元组)作为参数

#结果如下
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Linear-1 [-1, 8] 24
ReLU-2 [-1, 8] 0
Linear-3 [-1, 1] 9
================================================================
Total params: 33
Trainable params: 33
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.00
Forward/backward pass size (MB): 0.00
Params size (MB): 0.00
Estimated Total Size (MB): 0.00
----------------------------------------------------------------
None

以第一层输出为例,形状为(-1,8),其中-1表示batch size,8表示对于每个数据点都会得到一个8维输出,得到形状为batch size×8的输出。

5.接下来定义损失函数loss_func和优化器opt并训练模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
loss_func = nn.MSELoss()
from torch.optim import SGD

opt = SGD(model.parameters(),lr = 0.001)

import time

loss_history = []
start = time.time()
for _ in range(50):
for ix,iy in dl:
opt.zero_grad()
loss_value = loss_func(model(ix),iy)
loss_value.backward()
opt.step()
loss_history.append(loss_value.item())
end = time.time()
print(end-start)
#0.18243002891540527

6.训练模型后,在验证数据集上预测值

定义预测数据集:

1
val = [[8,9],[10,11],[1.5,2.5]]

将验证数据转换为浮点数并转换为张量对象存储到device中,模型传递验证数据预测输出

1
2
3
4
5
6
7
val = torch.tensor(val).float()
print(model(val.to(device)))

#结果为
#tensor([[16.8575],
[20.7629],
[ 4.1649]], device='cuda:0', grad_fn=<AddmmBackward0>)

4.pytorch模型的保存与加载

​ 神经网络处理的一个重要方面就是在训练后保存和加载模型,保存模型之后,我们可以利用已经训练好的模型进行推断,只需要加载已经训练好的模型,而无需再次对其进行训练。

4.1模型保存所需组件

image-20250913164641777

4.2模型状态

image-20250913164817839

1
2
3
4
5
6
7
8
9
10
11
12
13
print(model.state_dict())
"""
OrderedDict([('0.weight', tensor([[ 0.8770, 0.1810],
[-0.2726, -0.5457],
[-0.3269, -0.5528],
[ 0.5133, -0.3993],
[ 0.6862, 0.6442],
[ 0.1152, -0.6341],
[ 0.3632, -0.3292],
[-0.1101, 0.1117]], device='cuda:0')), ('0.bias', tensor([-0.0707, -0.0403, -0.1827, -0.5141, 0.2934, 0.6227, -0.3822, -0.3792],
device='cuda:0')), ('2.weight', tensor([[ 0.6465, -0.2204, -0.2875, -0.1880, 0.9614, -0.1798, 0.0300, -0.2467]],
device='cuda:0')), ('2.bias', tensor([0.2132], device='cuda:0'))])
"""

4.3模型保存

image-20250913165023651

1
2
save_path = 'mymodel.pth'
torch.save(model.state_dict(),save_path)

4.4模型加载

加载模型首先需要初始化模型,然后从state_dict加载权重

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import torch
from torch import nn
device = 'cuda' if torch.cuda.is_available() else 'cpu'

model = nn.Sequential(
nn.Linear(2,8),
nn.ReLU(),
nn.Linear(8,1)
).to(device)
#使用与训练时相同的代码创建一个空模型

state_dict = torch.load('mymodel.pth')
#从磁盘加载模型并反序列化以创建一个OrderedDict值

model.load_state_dict(state_dict)
model.to(device)
#加载state_dict到模型中,并将其注册到device中,执行预测任务

val = [[8,9],[10,11],[1.5,2.5]]
val = torch.tensor(val).float()
print(model(val.to(device)))
#输出
#tensor([[16.7941],
[20.6560],
[ 4.2427]], device='cuda:0', grad_fn=<AddmmBackward0>)