1 PyTorch是什么?
PyTorch是一个开源的深度学习框架,由Meta公司(原名:Facebook)的人工智能团队开发和维护。它提供了一个灵活、动态的计算图计算模型,使得在深度学习领域进行实验和开发变得更加简单和直观。
2 Pytorch特点
动态计算图
PyTorch使用动态计算图,这意味着计算图在运行时构建的,而不是在编译时静态定义的。
自动求导(微分)
PyTorch提供了自动求导机制,称为Autograd。它能够自动计算张量(Tensor)的梯度,这对于训练神经网络和其他深度学习模型非常有用。
丰富的神经网络库
PyTorch 提供了丰富的神经网络库,包括各种各样的层,损失函数、优化器等。这些库使得构建和训练神经网络变得更加容易。
支持GPU加速
PyTorch 充分利用了GPU的并行计算能力,能够在GPU上高效地进行计算,加速模型训练过程。
3 Tensor是什么?
tensor是一种多维数组,类似于NumPy的ndarray。它是Pytorch中最基本的数据结构,用于存储和操作数据。
tensor可以是标量、向量、矩阵或者更高维度的数组,可以包含整数、浮点数或者其他数据类型的元素。
PyTorch的Tensor和NumPy的ndarray非常相似,但在设计和功能上有一些不同之处。主要的区别包括:GPU加速、自动求导、动态计算图。
在命令窗口中手动安装
pip install torch==2.5.1 -i https://pypi.tuna.tsinghua.edu.cn/simple
import torch
# 标量
scalar_tensor = torch.tensor(4.14)
print(scalar_tensor)
# 向量
vec_tensor = torch.tensor([1, 2, 4, 5, 6])
print(vec_tensor)
# 矩阵
mat_tensor = torch.tensor([[1, 2],
[3, 4]])
print(mat_tensor)
4 Tensor的存储机制
4.1 tensor
在PyTorch中,tensor包含了两个部分,即Storage和metadata。
Storage(存储):存储是tensor中包含的实际的底层缓冲区,它是一维数组,存储了tensor的元素值。不同tensor可能共享相同的存储,即使它们具有不同的形状和步幅。存储是一块连续的内存区域,实际上存储了tensor中的数据。
Metadata(元数据):元数据是tensor的描述性信息,包括tensor的形状、数据类型、步幅、存储偏移量、设备等。元数据提供了关于tensor的结构和属性信息,但并不包括tensor中的实际数据。元数据允许PyTorch知道如何正确地解释存储中的数据以及如何访问它们。
import torch
# 标量
scalar_tensor = torch.tensor(4.14)
print(scalar_tensor)
# 向量
vec_tensor = torch.tensor([1, 2, 4, 5, 6])
print(vec_tensor)
# 矩阵
mat_tensor = torch.tensor([[1, 2],
[3, 4]])
mat_tensor = torch.tensor([[1.0, 2],
[3, 4]])
print(mat_tensor)
print(mat_tensor.shape) # torch.Size([2, 2])
print(mat_tensor.dtype) # torch.float32
# 存储的内容,未来版本会更加底层和抽象
print(mat_tensor.storage())
# 取出一维数据
print(mat_tensor.storage().tolist())
# 偏移量
print(mat_tensor.storage_offset())
4.2 步长stride
步长指的是在每个维度上移动一个元素时在底层存储中需要跨越的元素数。
import torch # 导入了PyTorch库
# 先创建了一个tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
# 再变形为3*4
tensorA = torch.arange(12).reshape(3, 4)
"""
torch.arange(12):创建一个包含0到11的一维张量,共12个元素
.reshape(3, 4):将一维张量重新形状为3行4列的二维张量
结果是:[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]]
"""
print(tensorA) # 打印张量的内容,显示为3×4的矩阵。
print(tensorA.stride()) # (4, 1)
"""
stride()方法返回张量在每个维度上的步长
对于3×4矩阵,步长为(4, 1),表示:
在行方向上移动一行需要跳过4个元素
在列方向上移动一列需要跳过1个元素
"""
运行结果:
tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
(4, 1)
第一个数字4表示矩阵的行(第一个维度)上移动一个元素时需要跨越的存储单元数。因为矩阵的每行包含4个元素,所以每次沿着行移动一个元素需要跨越4个存储单元。
第二个数字1表示在矩阵的列(第二个维度)上移动一个元素时需要跨越的存储单元数,因为矩阵的列数是1,所以在列上移动一个元素时只需要跨越一个存储单元。
在PyTorch中,列的步长始终为1,这也符合绝大多数编程语言和框架的设计,这种存储方式使得在列方向上的访问非常高效,因为不需要跳过任何元素。这也是为什么在处理二维数组时,列操作通常比行操作更快的原因之一。
4.3 偏移offset
偏移是指从张量的第一个元素开始的索引位置。
import torch
# 先创建了一个tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
# 再变形为3*4
tensorA = torch.arange(12).reshape(3, 4)
print(tensorA)
# 选取第2行到第3行、第1列到第2列的元素,得到一个新的张量B
tensorB = tensorA[1:3, 0:2]
print(tensorB)
print(tensorB.storage_offset()) # 偏移4
4.4 连续性
Tensor的连续性指的是其元素在内存中按照其在张量中的顺序紧密存储,没有间隔。
import torch # 导入PyTorch库
# 先创建了一个tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
# 再变形为3*4
tensorA = torch.arange(12).reshape(3, 4)
"""
torch.arange(12):创建一个包含0到11的一维张量,共12个元素
.reshape(3, 4):将一维张量重新形状为3行4列的二维张量
结果是:[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]]
"""
print(tensorA)
print(tensorA.flatten()) # 展平
"""
flatten()方法将多维张量展平为一维张量
结果是:tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
注意:flatten()返回的是原张量的视图(view),而不是副本,这意味着它们共享底层数据
"""
print(tensorA.storage().tolist())
"""
.storage():访问张量的底层存储
.tolist():将存储转换为Python列表
结果是:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
这显示了张量在内存中的实际存储方式(一维数组)
"""
print(tensorA.is_contiguous()) # 是否连续:True
"""
is_contiguous():检查张量在内存中是否连续存储
返回True表示张量在内存中是连续存储的
连续存储意味着张量的元素在内存中按顺序排列,没有跳跃或间隔
"""
tensor也可以不连续存储,当对Tensor进行某些操作,如转置(transpose)时,可能会导致Tensor变得不连续。
import torch # 导入PyTorch库
# 先创建了一个tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
# 再变形为3*4
tensorA = torch.arange(12).reshape(3, 4)
"""
创建一个包含0到11的一维张量
将其重塑为3行4列的二维张量
初始形状:3×4矩阵,内存中是连续的
"""
tensorA = tensorA.transpose(0, 1)
"""
transpose(0, 1):交换第0维和第1维(即行和列)
将3×4矩阵转置为4×3矩阵
这是一个视图操作,不复制数据,只改变元数据(形状和步长)
"""
print(tensorA)
"""
tensor([[ 0, 4, 8],
[ 1, 5, 9],
[ 2, 6, 10],
[ 3, 7, 11]])
"""
print(tensorA.flatten()) # 展平
"""
flatten():将多维张量展平为一维
结果是:tensor([ 0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11])
注意:展平操作按照张量的逻辑顺序进行,而不是内存中的物理顺序
"""
print(tensorA.storage().tolist())
"""
访问张量的底层存储并转换为列表
结果是:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
这显示了张量在内存中的实际存储顺序,与转置前的顺序相同
"""
print(tensorA.is_contiguous()) # 是否连续:False
"""
检查张量在内存中是否连续存储
返回False表示张量在内存中不是连续存储的
这是因为转置操作改变了张量的步长(stride),导致内存访问不再连续
"""
连续的tensor优势
高效的内存访问:连续的张量在内存中占用一块连续的空间,这使得CPU可以高效地按顺序访问数据,减少了内存寻址的时间,从而提高了数据处理的速度。
优化的计算性能:在进行数学运算时,连续张量可以减少数据的移动和复制,因为数据已经按照计算所需的顺序排序排列,这样可以减少计算中的延迟,提高整体的计算性能。
其它:在不连续的tensor上进行view()操作会报错,view()可以手动创建视图,以便于内存重用。
import torch
# 先创建了一个tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
# 再变形为3*4
tensorA = torch.arange(12).reshape(3, 4)
tensorA = tensorA.transpose(0, 1)
print(tensorA)
print(tensorA.flatten()) # 展平
print(tensorA.storage().tolist())
print(tensorA.is_contiguous()) # 是否连续:False
# 报错!!!!!
# viewA = tensorA.view(1, 12) # 变成1行12列使用视图重用内存
# print(viewA)
import torch
# 先创建了一个tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
# 再变形为3*4
tensorA = torch.arange(12).reshape(3, 4)
tensorA = tensorA.transpose(0, 1)
# 强行转换成连续
tensorA = tensorA.contiguous()
print(tensorA)
print(tensorA.flatten()) # 展平
print(tensorA.storage().tolist())
print(tensorA.is_contiguous()) # 是否连续:True
viewA = tensorA.view(1, 12) # 变成1行12列使用视图重用内存
print(viewA)
结论:
通过contiguous()方法将不连续的张量转换为连续的张量。
如果tensor不是连续的,则会重新开辟一块内存空间保证数据是在内存中是连续的。
如果tensor是连续的,则contiguous()无操作
view的性能比reshape好,如果没有性能瓶颈可以选择reshape,因为reshape不需要关注是否连续
5 随机数种子
此代码有助于后续模型的一些复现,非常有用的小技巧。
import torch
random_tensor = torch.rand((3, 4))
print(random_tensor)
"""
每次运行结果都不一样
tensor([[0.2924, 0.1941, 0.9465, 0.3363],
[0.7984, 0.0809, 0.2171, 0.1827],
[0.4552, 0.6414, 0.7593, 0.0330]])
tensor([[0.0634, 0.4717, 0.2550, 0.8508],
[0.4667, 0.5600, 0.5351, 0.7288],
[0.7948, 0.8702, 0.3767, 0.4568]])
"""
# 种下一颗种子
seed = 8
torch.manual_seed(seed) # 播种
random_tensor = torch.rand((3, 4))
print(random_tensor)
"""
播种后,每次随机都一样
tensor([[0.5979, 0.8453, 0.9464, 0.2965],
[0.5138, 0.6443, 0.8991, 0.0141],
[0.5785, 0.1218, 0.9181, 0.6805]])
tensor([[0.5979, 0.8453, 0.9464, 0.2965],
[0.5138, 0.6443, 0.8991, 0.0141],
[0.5785, 0.1218, 0.9181, 0.6805]])
"""
PyTorch进行线性回归
在自求导线性回归中,我们需要先自定义参数,并且需要通过数学公式来对w和b进行求导,然后在反向传播过程中通过梯度下降的方式来更新参数,从而降低损失值。
本节使用框架实现线性回归算法时,我们就不需要实现这些具体的内容了(例如求导、梯度下降等),因为接下来学的三种框架(PyTorch、TensorFlow、PaddlePaddle)已经将具体代码实现了,比如反向传播的过程(也就是自动微分机制)。在使用框架实现线性回归算法时,我们更多的是准备好数据集,然后通过框架,将前向传播的内容做好,并且“选择”好反向传播过程中所用到的一些参数或者参数更新的方法(比如梯度下降)即可。
1 基于框架的线性回归
框架代码运行流程:
散点输入→定义前向模型→定义损失函数和优化器→开始迭代→显示频率设置→拟合线显示和输出
1.1 散点输入
有一些散点,将它们绘制在一个二维坐标中,其分布如下图所示:
1.2 定义前向模型
在y=wx+b中:
输入特征:x,只有一个因此本实验只能选择一维
输出特征:y,只有一个因此本实验只能选择一维
样本数量:用来训练模型的数据点数量。
如果样本数量是m,则可以把x认为是一个1*m的矩阵,即1行m列。
当有多个输入特征时,公式就变成了 ,x就是一个n*m的矩阵了。这些内容后续会学习,现在有个概念即可。
选择好输入特征和输出特征后,按照自求导线性回归的思路,我们应该进行w和b的初始化,给模型一个起始点,使其能够开始学习并逐渐优化参数。而在使用框架实现时,参数的初始化是框架自动处理的(随机值),所以就不用我们再去手动初始化w和b这两个参数了。
1.3 定义损失函数和优化器
在选择好前向的模型后,需要定义反向传播过程中用到的一些超参数。
1.损失函数
本实验为MSE(均方误差),这也是我们之前一直用到的损失函数,也可以使用一些其他的损失函数,后续会引入其他的损失函数。
2.优化器
优化器是参数的更新方式,用来降低损失函数值的,比如我们非常熟悉的梯度下降(GD)其实就是一种优化器,也叫做优化函数,关于优化器更加深入的内容会在后续的深度学习章节中介绍,这里不再过多叙述。
本例使用的SGD(随机梯度下降)是基于梯度下降实现的,在每个迭代(epoch)中,它随机选择多个小批量(batch_size,≤样本数)样本来计算梯度并更新参数。这种随机性有助于模型在参数空间中进行更广泛的探索,从而有可能跳出局部最小值,并在所有样本都被用于计算梯度后完成一个epoch。
3.学习率
大多数优化器确实需要一个控制步长大小的参数,例如梯度更新公式中,
w新=w旧-学习率*梯度
在深度学习的三种框架中,关于损失函数和优化器的代码实现都已经封装好了,只需要选择使用哪种即可。
1.4 迭代
定义迭代数值
1.5 显示频率设置
定义一个数值:迭代多少次显示一次
1.6 拟合线显示与输出
输出结果,可在代码最后增加显示图像代码以此显示图像
2 PyTorch进行线性回归
2.1 模型定义
2.1.1 Sequential
nn.Sequential()是pytorch的容器,按照顺序组合多个网络层,内部有forward方法,会定义前向传播的逻辑。应用于较少数据。
forward 方法是 PyTorch 神经网络模块的“计算核心”或“前向传播逻辑”。它定义了当数据输入到你的模块时,应该如何进行计算并输出结果。
# 1. 导入必要的库
import torch
import numpy as np
# 2. 准备数据
# 2.1 散点输入
data = [[-0.5, 7.7], [1.8, 98.5], [0.9, 57.8], [0.4, 39.2], [-1.4, -15.7], [-1.4, -37.3], [-1.8, -49.1], [1.5, 75.6],
[0.4, 34.0], [0.8, 62.3]]
# 转换为np数组
data = np.array(data) # 将列表转换为 NumPy 数组以便进行矩阵操作
print(data)
print(data.shape)
# 提取每个点的x_data和y_data
x_data = data[:, 0]
y_data = data[:, 1]
# 使用切片操作提取 x 和 y 坐标数据
print(x_data)
print(y_data)
# 3. 转换为 PyTorch 张量
# 将x_data 和 y_data 转换成tensor
x_train = torch.tensor(x_data, dtype=torch.float32) # 将 NumPy 数组转换为 PyTorch 张量
y_train = torch.tensor(y_data, dtype=torch.float32) # 将 NumPy 数组转换为 PyTorch 张量
# 指定数据类型为 torch.float32,这是深度学习常用的浮点精度
print(x_train)
print(y_train)
# 4. 定义模型
import torch.nn as nn
# 方案1:顺序容器——支持多参数网络,本例非常简单,只有一层
model = nn.Sequential(
nn.Linear(1, 1) # 顺序容器的网络内容:线性,1输入1输出
)
"""
nn.Linear(1, 1) 创建一个线性层,输入和输出维度都是1
nn.Sequential 是一个容器,可以按顺序组合多个层
"""
# 5. 定义损失函数和优化器
criterion = nn.MSELoss() # 均方误差
# 定义随机梯度下降的优化器
optimizer = torch.optim.SGD(
model.parameters(), # 模型参数
lr=0.01 # 学习率
)
"""
nn.MSELoss() 定义均方误差损失函数
torch.optim.SGD 使用随机梯度下降优化器
model.parameters() 获取模型中所有可训练参数
lr=0.01 设置学习率为0.01
"""
# 6. 数据维度调整
# unsqueeze可以增加维度,参数是增加的维度是哪一维度
print(x_train.unsqueeze(0)) # 一个样本点(行数),十个特征(列数)
print(x_train.unsqueeze(1)) # 十个样本点(行数),一个特征(列数)
"""
unsqueeze() 用于增加张量的维度
unsqueeze(0) 在第0维增加一个维度
unsqueeze(1) 在第1维增加一个维度
这里需要将一维的 x_train 转换为二维,因为线性层期望的输入形状是 [batch_size, input_features]
"""
# 7. 训练循环
# 开始迭代
epochs = 500 # 迭代次数
for n in range(1, epochs + 1):
# 7.1 前向传播
y_pre = model(x_train.unsqueeze(1)) # 参数是n*m的输入矩阵
# 计算损失函数
loss = criterion(
y_pre.squeeze(1), # 参数1:预测值(移除第二维,恢复一维)
y_train # 参数2: 真实值
)
# 清空之前优化器重存储的梯度参数,以便于本次epoch重新进行反向传播
optimizer.zero_grad()
"""
为什么需要清零梯度?
optimizer.zero_grad()
PyTorch 会累积梯度(即每次调用 .backward() 时,
梯度会加到之前的梯度上)。在大多数情况下,
我们希望每次迭代都基于当前批次的梯度来更新参数,
而不是累积所有批次的梯度。因此需要在每次迭代前清零梯度。
"""
# 7.2 反向传播
loss.backward()
# 7.3 显示频率设置
if n == 1 or n % 10 == 0:
# 遍历模型参数
for name, param in model.named_parameters():
# 如有,取出当前计算的梯度
if param.grad is not None:
print(f"梯度:{name}:{param.grad}")
# 如有,取出当前计算的w和b
if param.data is not None:
print(f"w和b的值:{param.data}")
# 每隔10次显示一次损失函数
print(f"epoch:{n},loss:{loss}")
print("-----------------------------------")
"""
前向传播:计算模型的预测值
计算损失:比较预测值和真实值
optimizer.zero_grad():清空之前的梯度,防止梯度累积
loss.backward():反向传播,计算梯度
optimizer.step():更新模型参数
"""
# 更新参数,例如w新 = 学习率与w旧的计算
optimizer.step()
代码运行结果:
2.1.2 ModuleList
nn.ModuleList() 和python的基础数据类型list类似,按照顺序,没有forward方法,不可以定义名字,可以用append加网络。应用于较多数据。
如果使用需要重写继承nn.Module
# 1. 导入必要的库
import torch
import numpy as np
# 2. 准备数据
# 2.1 散点输入
data = [[-0.5, 7.7], [1.8, 98.5], [0.9, 57.8], [0.4, 39.2], [-1.4, -15.7], [-1.4, -37.3], [-1.8, -49.1], [1.5, 75.6],
[0.4, 34.0], [0.8, 62.3]]
# 转换为np数组
data = np.array(data) # 将列表转换为 NumPy 数组以便进行矩阵操作
# 提取每个点的x_data和y_data
x_data = data[:, 0]
y_data = data[:, 1]
# 使用切片操作提取 x 和 y 坐标数据
print(x_data)
print(y_data)
# 3. 转换为 PyTorch 张量
# 将x_data 和 y_data 转换成tensor
x_train = torch.tensor(x_data, dtype=torch.float32) # 将 NumPy 数组转换为 PyTorch 张量
y_train = torch.tensor(y_data, dtype=torch.float32) # 将 NumPy 数组转换为 PyTorch 张量
# 指定数据类型为 torch.float32,这是深度学习常用的浮点精度
print(x_train)
print(y_train)
# 4. 定义模型
import torch.nn as nn
# ------------------------------------------------
# 方案2:继承nn.Module
class LinearModel(nn.Module): # 定义了一个名为 LinearModel 的新类
# 这个类继承自 PyTorch 的 nn.Module 基类,这是所有神经网络模块的基类
# 构造函数:初始化
def __init__(self): # __init__ 是 Python 类的构造函数,在创建类的实例时自动调用
# 调用父类的构造函数
super(LinearModel, self).__init__()
# super(LinearModel, self).__init__() 调用父类 nn.Module 的构造函数,
# 这是必需的,因为它会初始化一些内部状态
# ModuleList,支持多网络结构(本例只有一个网络层)
self.layers = nn.ModuleList(
[nn.Linear(1, 1)] # 参数为多个网络层结构,使用list传入
)
def forward(self, x):
"""
前向传播方法
:param x: 输入张量
:return: 网络层处理后的输出张量
"""
# 遍历列表多个网络层
for layer in self.layers:
x = layer(x)
return x
# ------------------------------------------------
# 创建模型对象
model = LinearModel()
# 3. 定义损失函数和优化器
criterion = nn.MSELoss() # 均方误差
# 定义随机梯度下降的优化器
optimizer = torch.optim.SGD(
model.parameters(), # 模型参数
lr=0.01 # 学习率
)
# unsqueeze可以增加维度,参数是增加的维度是哪一维度
print(x_train.unsqueeze(0)) # 一个样本点(行数),十个特征(列数)
print(x_train.unsqueeze(1)) # 十个样本点(行数),一个特征(列数)
# 4. 开始迭代
epochs = 500 # 迭代次数
for n in range(1, epochs + 1):
# 4.1 前向传播
y_pre = model(x_train.unsqueeze(1)) # 参数是n*m的输入矩阵
# print(y_pre) # 预测值
# 计算损失函数
loss = criterion(
y_pre.squeeze(1), # 参数1:预测值(移除第二维,恢复一维)
y_train # 参数2: 真实值
)
# 清空之前优化器重存储的梯度参数,以便于本次epoch重新进行反向传播
optimizer.zero_grad()
# 4.2 反向传播
loss.backward()
# 5. 显示频率设置
if n == 1 or n % 10 == 0:
# 遍历模型参数
for name, param in model.named_parameters():
# 如有,取出当前计算的梯度
if param.grad is not None:
print(f"梯度:{name}:{param.grad}")
# 如有,取出当前计算的w和b
if param.data is not None:
print(f"w和b的值:{param.data}")
# 每隔10次显示一次损失函数
print(f"epoch:{n},loss:{loss}")
print("-----------------------------------")
# 更新参数,例如w新 = 学习率与w旧的计算
optimizer.step()
1.3 ModuleDict
nn.ModuleDict(),和dict类似,不按照顺序,没有forward方法,可以定义每层的名字。
import torch
import numpy as np
# 1、散点输入
data = [[-0.5, 7.7], [1.8, 98.5], [0.9, 57.8], [0.4, 39.2], [-1.4, -15.7], [-1.4, -37.3], [-1.8, -49.1], [1.5, 75.6],
[0.4, 34.0], [0.8, 62.3]]
# 转换为np数组
data = np.array(data)
# 提取每个点的x_data和y_data
x_data = data[:, 0]
y_data = data[:, 1]
print(x_data)
print(y_data)
# 3. 转换为 PyTorch 张量
# 将x_data 和 y_data 转换成tensor,转换为 PyTorch 张量
x_train = torch.tensor(x_data, dtype=torch.float32)
y_train = torch.tensor(y_data, dtype=torch.float32)
print(x_train)
print(y_train)
# -------------------------------------------------------
# 定义前向模型
import torch.nn as nn
# 4. 定义模型(方案3:使用 nn.ModuleDict)
# 方案3:
class LinearModel(nn.Module): # 定义了一个名为 LinearModel 的新类,继承自 nn.Module
# 构造函数:初始化
def __init__(self): # __init__ 方法是类的构造函数,在创建实例时调用
# 调用父类的构造函数
super(LinearModel, self).__init__() # super(LinearModel, self).__init__() 调用父类的构造函数
# ModuleDict,支持多网络结构(本例只有一个网络层)
self.layers = nn.ModuleDict( # nn.ModuleDict 是一个字典容器,用于存储多个 nn.Module 子模块
{"Linear1": nn.Linear(1, 1)} # 参数为多个网络层结构,使用dict传入
) # 这里创建了一个包含单个线性层 nn.Linear(1, 1) 的字典,键为 "Linear1"
def forward(self, x):
# forward 方法定义了数据如何通过模型传播
"""
前向传播方法
:param x: 输入张量
:return: 网络层处理后的输出张量
"""
# 遍历列表多个网络层
for key in self.layers: # 方法内部遍历 self.layers 中的所有层,依次将输入传递给每一层
x = self.layers[key](x)
return x
# -------------------------------------------------------
# 创建模型对象
model = LinearModel() # 创建 LinearModel 类的一个实例
# 6. 定义损失函数和优化器
criterion = nn.MSELoss() # 均方误差
# 定义随机梯度下降的优化器
optimizer = torch.optim.SGD(
model.parameters(), # 模型参数
lr=0.01 # 学习率
)
# unsqueeze可以增加维度,参数是增加的维度是哪一维度
print(x_train.unsqueeze(0)) # 一个样本点(行数),十个特征(列数)
print(x_train.unsqueeze(1)) # 十个样本点(行数),一个特征(列数)
# 4. 开始迭代
epochs = 500 # 迭代次数
for n in range(1, epochs + 1):
# 4.1 前向传播
y_pre = model(x_train.unsqueeze(1)) # 参数是n*m的输入矩阵
# print(y_pre) # 预测值
# 计算损失函数
loss = criterion(
y_pre.squeeze(1), # 参数1:预测值(移除第二维,恢复一维)
y_train # 参数2: 真实值
)
# 清空之前优化器重存储的梯度参数,以便于本次epoch重新进行反向传播
optimizer.zero_grad()
# 4.2 反向传播
loss.backward()
# 5. 显示频率设置
if n == 1 or n % 10 == 0:
# 遍历模型参数
for name, param in model.named_parameters():
# 如有,取出当前计算的梯度
if param.grad is not None:
print(f"梯度:{name}:{param.grad}")
# 如有,取出当前计算的w和b
if param.data is not None:
print(f"w和b的值:{param.data}")
# 每隔10次显示一次损失函数
print(f"epoch:{n},loss:{loss}")
print("-----------------------------------")
# 更新参数,例如w新 = 学习率与w旧的计算
optimizer.step()
1.4 手动编写
"""最常用的网络结构,直接重写继承nn.Module"""
import torch
import numpy as np
# 1、散点输入
data = [[-0.5, 7.7], [1.8, 98.5], [0.9, 57.8], [0.4, 39.2], [-1.4, -15.7], [-1.4, -37.3], [-1.8, -49.1], [1.5, 75.6],
[0.4, 34.0], [0.8, 62.3]]
# 转换为np数组
data = np.array(data)
# 提取每个点的x_data和y_data
x_data = data[:, 0]
y_data = data[:, 1]
print(x_data)
print(y_data)
# 将x_data 和 y_data 转换成tensor
x_train = torch.tensor(x_data, dtype=torch.float32)
y_train = torch.tensor(y_data, dtype=torch.float32)
print(x_train)
print(y_train)
# 2. 定义前向模型
import torch.nn as nn
# -------------------------------------------------------
# 方案4:
class LinearModel(nn.Module):
# 构造函数:初始化
def __init__(self):
# 调用父类的构造函数
super(LinearModel, self).__init__()
# ModuleDict,支持多网络结构(本例只有一个网络层)
self.layer1 = nn.Linear(1, 1)
# self.layer2 = nn.Linear(1, 1) # 如果是两个网络层是这样的
def forward(self, x):
"""
前向传播方法
:param x: 输入张量
:return: 网络层处理后的输出张量
"""
# 多个网络层连接
x = self.layer1(x)
# x = self.layer2(x)
return x
# -------------------------------------------------------
# 创建模型对象
model = LinearModel()
# 3. 定义损失函数和优化器
criterion = nn.MSELoss() # 均方误差
# 定义随机梯度下降的优化器
optimizer = torch.optim.SGD(
model.parameters(), # 模型参数
lr=0.01 # 学习率
)
# unsqueeze可以增加维度,参数是增加的维度是哪一维度
print(x_train.unsqueeze(0)) # 一个样本点(行数),十个特征(列数)
print(x_train.unsqueeze(1)) # 十个样本点(行数),一个特征(列数)
# 4. 开始迭代
epochs = 500 # 迭代次数
for n in range(1, epochs + 1):
# 4.1 前向传播
y_pre = model(x_train.unsqueeze(1)) # 参数是n*m的输入矩阵
# print(y_pre) # 预测值
# 计算损失函数
loss = criterion(
y_pre.squeeze(1), # 参数1:预测值(移除第二维,恢复一维)
y_train # 参数2: 真实值
)
# 清空之前优化器重存储的梯度参数,以便于本次epoch重新进行反向传播
optimizer.zero_grad()
# 4.2 反向传播
loss.backward()
# 5. 显示频率设置
if n == 1 or n % 10 == 0:
# 遍历模型参数
for name, param in model.named_parameters():
# 如有,取出当前计算的梯度
if param.grad is not None:
print(f"梯度:{name}:{param.grad}")
# 如有,取出当前计算的w和b
if param.data is not None:
print(f"w和b的值:{param.data}")
# 每隔10次显示一次损失函数
print(f"epoch:{n},loss:{loss}")
print("-----------------------------------")
# 更新参数,例如w新 = 学习率与w旧的计算
optmizer.step()
拓展:
什么是梯度?
在深度学习中,梯度是一个向量,表示函数在某一点上的方向导数沿着该方向取得最大值。
简单来说,梯度指出了函数增长最快的方向。
在线性回归中,我们有两个参数:
w(权重)
b(偏置)
损失函数 L 对这两个参数的梯度表示为:
∂L/∂w:损失函数对权重的梯度
∂L/∂b:损失函数对偏置的梯度
梯度的作用
梯度指示了如何调整参数才能使损失函数减小:
如果梯度为正,减小参数值可以降低损失
如果梯度为负,增加参数值可以降低损失
代码中的梯度使用
# 计算梯度
loss.backward()
# 访问梯度
for name, param in model.named_parameters():
if param.grad is not None:
print(f"梯度:{name}:{param.grad}")
# loss.backward() 自动计算所有需要梯度的参数的梯度
# 梯度存储在参数的 .grad 属性中
# 优化器使用这些梯度来更新参数:optimizer.step()