当前位置: 首页 > news >正文

4月17日复盘

4月17日复盘

八、自动微分

自动微分模块torch.autograd负责自动计算张量操作的梯度,具有自动求导功能。自动微分模块是构成神经网络训练的必要模块,可以实现网络权重参数的更新,使得反向传播算法的实现变得简单而高效。

1. 基础概念

  1. 张量

    Torch中一切皆为张量,属性requires_grad决定是否对其进行梯度计算。默认是 False,如需计算梯度则设置为True。

  2. 计算图

    torch.autograd通过创建一个动态计算图来跟踪张量的操作,每个张量是计算图中的一个节点,节点之间的操作构成图的边。

    在 PyTorch 中,当张量的 requires_grad=True 时,PyTorch 会自动跟踪与该张量相关的所有操作,并构建计算图。每个操作都会生成一个新的张量,并记录其依赖关系。当设置为 True 时,表示该张量在计算图中需要参与梯度计算,即在反向传播(Backpropagation)过程中会自动计算其梯度;当设置为 False 时,不会计算梯度。

    例如:
    z = x ∗ y l o s s = z . s u m ( ) z = x * y\\loss = z.sum() z=xyloss=z.sum()
    在上述代码中,x 和 y 是输入张量,即叶子节点,z 是中间结果,loss 是最终输出。每一步操作都会记录依赖关系:

    z = x * y:z 依赖于 x 和 y。

    loss = z.sum():loss 依赖于 z。

    这些依赖关系形成了一个动态计算图,如下所示:

    	  x       y\     /\   /\ /z||vloss
    

    叶子节点

    在 PyTorch 的自动微分机制中,叶子节点(leaf node) 是计算图中:

    • 由用户直接创建的张量,并且它的 requires_grad=True。
    • 这些张量是计算图的起始点,通常作为模型参数或输入变量。

    特征:

    • 没有由其他张量通过操作生成。
    • 如果参与了计算,其梯度会存储在 leaf_tensor.grad 中。
    • 默认情况下,叶子节点的梯度不会自动清零,需要显式调用 optimizer.zero_grad() 或 x.grad.zero_() 清除。

    如何判断一个张量是否是叶子节点?

    通过 tensor.is_leaf 属性,可以判断一个张量是否是叶子节点。

    x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)  # 叶子节点
    y = x ** 2  # 非叶子节点(通过计算生成)
    z = y.sum()print(x.is_leaf)  # True
    print(y.is_leaf)  # False
    print(z.is_leaf)  # False
    

    叶子节点与非叶子节点的区别

    特性叶子节点非叶子节点
    创建方式用户直接创建的张量通过其他张量的运算生成
    is_leaf 属性TrueFalse
    梯度存储梯度存储在 .grad 属性中梯度不会存储在 .grad,只能通过反向传播传递
    是否参与计算图是计算图的起点是计算图的中间或终点
    删除条件默认不会被删除在反向传播后,默认被释放(除非 retain_graph=True)

    detach():张量 x 从计算图中分离出来,返回一个新的张量,与 x 共享数据,但不包含计算图(即不会追踪梯度)。

    特点

    • 返回的张量是一个新的张量,与原始张量共享数据。
    • 对 x.detach() 的操作不会影响原始张量的梯度计算。
    • 推荐使用 detach(),因为它更安全,且在未来版本的 PyTorch 中可能会取代 data。
    x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
    y = x.detach()  # y 是一个新张量,不追踪梯度y += 1  # 修改 y 不会影响 x 的梯度计算
    print(x)  # tensor([1., 2., 3.], requires_grad=True)
    print(y)  # tensor([2., 3., 4.])
    
  3. 反向传播

    使用tensor.backward()方法执行反向传播,从而计算张量的梯度。这个过程会自动计算每个张量对损失函数的梯度。例如:调用 loss.backward() 从输出节点 loss 开始,沿着计算图反向传播,计算每个节点的梯度。

  4. 梯度

    计算得到的梯度通过tensor.grad访问,这些梯度用于优化模型参数,以最小化损失函数。

2. 计算梯度

使用tensor.backward()方法执行反向传播,从而计算张量的梯度

2.1 标量梯度计算

参考代码如下:

import torchdef test001():# 1. 创建张量:必须为浮点类型x = torch.tensor(1.0, requires_grad=True)# 2. 操作张量y = x ** 2# 3. 计算梯度,也就是反向传播y.backward()# 4. 读取梯度值print(x.grad)  # 输出: tensor(2.)if __name__ == "__main__":test001()
2.2 向量梯度计算

案例:

def test003():# 1. 创建张量:必须为浮点类型x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)# 2. 操作张量y = x ** 2# 3. 计算梯度,也就是反向传播y.backward()# 4. 读取梯度值print(x.grad)if __name__ == "__main__":test003()

错误预警:RuntimeError: grad can be implicitly created only for scalar outputs

由于 y 是一个向量,我们需要提供一个与 y 形状相同的向量作为 backward() 的参数,这个参数通常被称为 梯度张量(gradient tensor),它表示 y 中每个元素的梯度。

def test003():# 1. 创建张量:必须为浮点类型x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)# 2. 操作张量y = x ** 2# 3. 计算梯度,也就是反向传播y.backward(torch.tensor([1.0, 1.0, 1.0]))# 4. 读取梯度值print(x.grad)# 输出# tensor([2., 4., 6.])if __name__ == "__main__":test003()

我们也可以将向量 y 通过一个标量损失函数(如 y.mean())转换为一个标量,反向传播时就不需要提供额外的梯度向量参数了。这是因为标量的梯度是明确的,直接调用 .backward() 即可。

import torchdef test002():# 1. 创建张量:必须为浮点类型x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)# 2. 操作张量y = x ** 2# 3. 损失函数loss = y.mean()# 4. 计算梯度,也就是反向传播loss.backward()# 5. 读取梯度值print(x.grad)if __name__ == "__main__":test002()

调用 loss.backward() 从输出节点 loss 开始,沿着计算图反向传播,计算每个节点的梯度。

损失函数 l o s s = m e a n ( y ) = 1 n ∑ i = 1 n y i loss=mean(y)=\frac{1}{n}∑_{i=1}^ny_i loss=mean(y)=n1i=1nyi,其中 n=3。

对于每个 y i y_i yi,其梯度为 ∂ l o s s ∂ y i = 1 n = 1 3 \frac{∂loss}{∂y_i}=\frac{1}{n}=\frac13 yiloss=n1=31

对于每个 x i x_i xi,其梯度为:
∂ l o s s ∂ x i = ∂ l o s s ∂ y i × ∂ y i ∂ x i = 1 3 × 2 x i = 2 x i 3 \frac{∂loss}{∂x_i}=\frac{∂loss}{∂y_i}×\frac{∂y_i}{∂x_i}=\frac1{3}×2x_i=\frac{2x_i}3 xiloss=yiloss×xiyi=31×2xi=32xi
所以,x.grad 的值为: [ 2 × 1.0 3 , 2 × 2.0 3 , 2 × 3.0 3 ] = [ 2 3 , 4 3 , 2 ] ≈ [ 0.6667 , 1.3333 , 2.0000 ] [\frac{2×1.0}3, \frac{2×2.0}3, \frac{2×3.0}3]=[\frac23,\frac43,2]≈[0.6667,1.3333,2.0000] [32×1.0,32×2.0,32×3.0]=[32,34,2][0.6667,1.3333,2.0000]

2.3 多标量梯度计算

参考代码如下

import torchdef test003():# 1. 创建两个标量x1 = torch.tensor(5.0, requires_grad=True, dtype=torch.float64)x2 = torch.tensor(3.0, requires_grad=True, dtype=torch.float64)# 2. 构建运算公式y = x1**2 + 2 * x2 + 7# 3. 计算梯度,也就是反向传播y.backward()# 4. 读取梯度值print(x1.grad, x2.grad)# 输出:# tensor(10., dtype=torch.float64) tensor(2., dtype=torch.float64)if __name__ == "__main__":test003()
2.4 多向量梯度计算

代码参考如下

import torchdef test004():# 创建两个张量,并设置 requires_grad=Truex = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)y = torch.tensor([4.0, 5.0, 6.0], requires_grad=True)# 前向传播:计算 z = x * yz = x * y# 前向传播:计算 loss = z.sum()loss = z.sum()# 查看前向传播的结果print("z:", z)  # 输出: tensor([ 4., 10., 18.], grad_fn=<MulBackward0>)print("loss:", loss)  # 输出: tensor(32., grad_fn=<SumBackward0>)# 反向传播:计算梯度loss.backward()# 查看梯度print("x.grad:", x.grad)  # 输出: tensor([4., 5., 6.])print("y.grad:", y.grad)  # 输出: tensor([1., 2., 3.])if __name__ == "__main__":test004()

3. 梯度上下文控制

梯度计算的上下文控制和设置对于管理计算图、内存消耗、以及计算效率至关重要。下面我们学习下Torch中与梯度计算相关的一些主要设置方式。

3.1 控制梯度计算

梯度计算是有性能开销的,有些时候我们只是简单的运算,并不需要梯度

import torchdef test001():x = torch.tensor(10.5, requires_grad=True)print(x.requires_grad)  # True# 1. 默认y的requires_grad=Truey = x**2 + 2 * x + 3print(y.requires_grad)  # True# 2. 如果不需要y计算梯度-with进行上下文管理with torch.no_grad():y = x**2 + 2 * x + 3print(y.requires_grad)  # False# 3. 如果不需要y计算梯度-使用装饰器@torch.no_grad()def y_fn(x):return x**2 + 2 * x + 3y = y_fn(x)print(y.requires_grad)  # False# 4. 如果不需要y计算梯度-全局设置,需要谨慎torch.set_grad_enabled(False)y = x**2 + 2 * x + 3print(y.requires_grad)  # Falseif __name__ == "__main__":test001()
3.2 累计梯度

默认情况下,当我们重复对一个自变量进行梯度计算时,梯度是累加的

import torchdef test002():# 1. 创建张量:必须为浮点类型x = torch.tensor([1.0, 2.0, 5.3], requires_grad=True)# 2. 累计梯度:每次计算都会累计梯度for i in range(3):y = x**2 + 2 * x + 7z = y.mean()z.backward()print(x.grad)if __name__ == "__main__":test002()

输出结果:

tensor([1.3333, 2.0000, 4.2000])
tensor([2.6667, 4.0000, 8.4000])
tensor([ 4.0000,  6.0000, 12.6000])

思考:如果把 y = x**2 + 2 * x + 7放在循环外,会是什么结果?

会报错:

RuntimeError: Trying to backward through the graph a second time (or directly access saved tensors after they have already been freed). Saved intermediate values of the graph are freed when you call .backward() or autograd.grad(). Specify retain_graph=True if you need to backward through the graph a second time or if you need to access saved tensors after calling backward.

PyTorch 的自动求导机制在调用 backward() 时,会计算梯度并将中间结果存储在计算图中。默认情况下,这些中间结果在第一次调用 backward() 后会被释放,以节省内存。如果再次调用 backward(),由于中间结果已经被释放,就会抛出这个错误。

3.3 梯度清零

大多数情况下是不需要梯度累加的,奇葩的事情还是需要解决的~

import torchdef test002():# 1. 创建张量:必须为浮点类型x = torch.tensor([1.0, 2.0, 5.3], requires_grad=True)# 2. 累计梯度:每次计算都会累计梯度for i in range(3):y = x**2 + 2 * x + 7z = y.mean()# 2.1 反向传播之前先对梯度进行清零if x.grad is not None:x.grad.zero_()z.backward()print(x.grad)if __name__ == "__main__":test002()# 输出:
# tensor([1.3333, 2.0000, 4.2000])
# tensor([1.3333, 2.0000, 4.2000])
# tensor([1.3333, 2.0000, 4.2000])
3.4 案例1-求函数最小值

通过梯度下降找到函数最小值

import torch
from matplotlib import pyplot as plt
import numpy as npdef test01():x = np.linspace(-10, 10, 100)y = x ** 2plt.plot(x, y)plt.show()def test02():# 初始化自变量Xx = torch.tensor([3.0], requires_grad=True, dtype=torch.float)# 迭代轮次epochs = 50# 学习率lr = 0.1list = []for i in range(epochs):# 计算函数表达式y = x ** 2# 反向传播y.backward()# 梯度下降,不需要计算梯度,为什么?with torch.no_grad():x -= lr * x.grad# 梯度清零x.grad.zero_()print('epoch:', i, 'x:', x.item(), 'y:', y.item())list.append((x.item(), y.item()))# 散点图,观察收敛效果x_list = [l[0] for l in list]y_list = [l[1] for l in list]plt.scatter(x=x_list, y=y_list)plt.show()if __name__ == "__main__":test01()test02()

代码解释:

# 梯度下降,不需要计算梯度
with torch.no_grad():x -= lr * x.grad

如果去掉梯度控制会有什么结果?

代码中去掉梯度控制会报异常:

RuntimeError: a leaf Variable that requires grad is being used in an in-place operation.

因为代码中x是叶子节点(叶子张量),是计算图的开始节点,并且设置需要梯度。在pytorch中不允许对需要梯度的叶子变量进行原地操作。因为这会破坏计算图,导致梯度计算错误。

在代码中,x 是一个叶子变量(即直接定义的张量,而不是通过其他操作生成的张量),并且设置了 requires_grad=True,因此不能直接通过 -= 进行原地更新。

解决方法

为了避免这个错误,可以使用以下两种方法:

方法 1:使用 torch.no_grad() 上下文管理器

在更新参数时,使用 torch.no_grad() 禁用梯度计算,然后通过非原地操作更新参数。

with torch.no_grad():a -= lr * a.grad

方法 2:使用 data 属性或detach()

通过 x.data 访问张量的数据部分(不涉及梯度计算),然后进行原地操作。

x.data -= lr * x.grad

x.data返回一个与 a 共享数据的张量,但不包含计算图

特点

  • 返回的张量与原始张量共享数据。
  • 对 x.data 的操作是原地操作(in-place),可能会影响原始张量的梯度计算。
  • 不推荐使用 data,因为它可能会导致意外的行为(如梯度计算错误)。

能不能将代码修改为:

x = x - lr * x.grad

答案是不能,以上代码中=左边的x变量是由右边代码计算得出的,就不是叶子节点了,从计算图中被剥离出来后没有了梯度,执行

x.grad.zero_()

报错:AttributeError: ‘NoneType’ object has no attribute ‘zero_’

总结:以上方均不推荐,正确且推荐的做法是使用优化器,优化器后续会讲解。

3.5 案例2-函数参数求解
def test02():# 定义数据x = torch.tensor([1, 2, 3, 4, 5], dtype=torch.float)y = torch.tensor([3, 5, 7, 9, 11], dtype=torch.float)# 定义模型参数 a 和 b,并初始化a = torch.tensor([1], dtype=torch.float, requires_grad=True)b = torch.tensor([1], dtype=torch.float, requires_grad=True)# 学习率lr = 0.1# 迭代轮次epochs = 1000for epoch in range(epochs):# 前向传播:计算预测值 y_predy_pred = a * x + b# 定义损失函数loss = ((y_pred - y) ** 2).mean()# 反向传播:计算梯度loss.backward()# 梯度下降with torch.no_grad():a -= lr * a.gradb -= lr * b.grada.grad.zero_()b.grad.zero_()if (epoch + 1) % 10 == 0:print(f'Epoch [{epoch + 1}/{epochs}], Loss: {loss.item():.4f}')print(f'a: {a.item()}, b: {b.item()}')

代码逻辑:

在 PyTorch 中,所有的张量操作都会被记录在一个计算图中。对于代码:

y_pred = a * x + b
loss = ((y_pred - y) ** 2).mean()

计算图如下:

a → y_pred → loss
x ↗
b ↗
  • a 和 b 是需要计算梯度的叶子张量(requires_grad=True)。
  • y_pred 是中间结果,依赖于 a 和 b。
  • loss 是最终的标量输出,依赖于 y_pred。

当调用 loss.backward() 时,PyTorch 会从 loss 开始,沿着计算图反向传播,计算 loss 对每个需要梯度的张量(如 a 和 b)的梯度。

计算 loss 对 y_pred 的梯度
l o s s = ( ( y p r e d − y ) 2 ) . m e a n ( ) = 1 n Σ i = 1 n ( y _ p r e d i − y i ) 2 loss = ((y_pred - y)^ 2).mean()=\frac{1}{n}\Sigma_{i=1}^n(y\_pred_i - y_i)^2 loss=((ypredy)2).mean()=n1Σi=1n(y_prediyi)2
求损失函数关于 y_pred 的梯度(即偏导数组成的向量)。由于 loss 是 y_pred 的函数,我们需要对每个 y _ p r e d i y\_pred_i y_predi求偏导数,并将它们组合成一个向量。

应用链式法则和常数求导规则,对于每个 ( y _ p r e d i − y i ) 2 (y\_pred_i−y_i)^2 (y_prediyi)2 项,梯度向量的每个分量是:
∂ l o s s ∂ y _ p r e d i = 2 n ( y _ p r e d i − y i ) \frac{∂loss}{∂y\_pred_i} = \frac{2}{n} (y\_pred_i−y_i) y_prediloss=n2(y_prediyi)
将结果组合成一个向量,我们得到:
∂ l o s s ∂ y _ p r e d = [ 2 n ( y _ p r e d 1 − y 1 ) , 2 n ( y _ p r e d 2 − y 2 ) , . . . , 2 n ( y _ p r e d n − y n ) ] = 2 n ( y _ p r e d − y ) \frac{∂loss}{∂y\_pred} = [\frac{2}{n} (y\_pred_1−y_1), \frac{2}{n} (y\_pred_2−y_2),...,\frac{2}{n} (y\_pred_n−y_n)]\\ =\frac{2}{n} (y\_pred−y) y_predloss=[n2(y_pred1y1),n2(y_pred2y2),...,n2(y_prednyn)]=n2(y_predy)
其中n=5,y_pred和y均为向量。

计算 y_pred 对 a 和 b 的梯度:

y_pred = a * x + b

对 a 求导: ∂ y p r e d ∂ a = x \frac{∂y_pred}{∂a}=x aypred=x,x为向量

对 b 求导: ∂ y p r e d ∂ b = 1 \frac{∂y_pred}{∂b}=1 bypred=1

根据链式法则,loss 对 a 的梯度为:
∂ l o s s ∂ a = ∂ l o s s ∂ y _ p r e d ⋅ ∂ y _ p r e d ∂ a = 2 n ( y _ p r e d − y ) x \frac{∂loss}{∂a}=\frac{∂loss}{∂y\_pred}⋅\frac{∂y\_pred}{∂a} = \frac{2}{n} (y\_pred−y)x aloss=y_predlossay_pred=n2(y_predy)x
loss 对 b 的梯度为:
∂ l o s s ∂ b = ∂ l o s s ∂ y _ p r e d ⋅ ∂ y _ p r e d ∂ b = 2 n ( y _ p r e d − y ) \frac{∂loss}{∂b}=\frac{∂loss}{∂y\_pred}⋅\frac{∂y\_pred}{∂b} = \frac{2}{n} (y\_pred−y) bloss=y_predlossby_pred=n2(y_predy)
第一次迭代:

前向传播:

y_pred = a * x + b = [1*1 + 1, 1*2 + 1, 1*3 + 1, 1*4 + 1, 1*5 + 1] = [2, 3, 4, 5, 6]loss = ((y_pred - y) ** 2).mean() = ((2-3)^2 + (3-5)^2 + (4-7)^2 + (5-9)^2 + (6-11)^2) / 5 = (1 + 4 + 9 + 16 + 25) / 5 = 11.0

反向传播:

∂loss/∂y_pred = 2/5 * (y_pred - y) = 2/5 * [-1, -2, -3, -4, -5] = [-0.4, -0.8, -1.2, -1.6, -2.0]a.grad = ∂loss/∂a = ∂loss/∂y_pred * x = [-0.4*1, -0.8*2, -1.2*3, -1.6*4, -2.0*5] = [-0.4, -1.6, -3.6, -6.4, -10.0]

对 a.grad 求和(因为 a 是标量):a.grad = -0.4 -1.6 -3.6 -6.4 -10.0 = -22.0

b.grad = ∂loss/∂b = ∂loss/∂y_pred * 1 = [-0.4, -0.8, -1.2, -1.6, -2.0]

对 b.grad 求和(因为 b 是标量):b.grad = -0.4 -0.8 -1.2 -1.6 -2.0 = -6.0

梯度更新:

a -= lr * a.grad = 1 - 0.1 * (-22.0) = 1 + 2.2 = 3.2b -= lr * b.grad = 1 - 0.1 * (-6.0) = 1 + 0.6 = 1.6

代码运行结果:

Epoch [10/100], Loss: 3020.7896
Epoch [20/100], Loss: 1550043.3750
Epoch [30/100], Loss: 795369408.0000
Epoch [40/100], Loss: 408125767680.0000
Epoch [50/100], Loss: 209420457869312.0000
Epoch [60/100], Loss: 107459239932329984.0000
Epoch [70/100], Loss: 55140217861896667136.0000
Epoch [80/100], Loss: 28293929961149737992192.0000
Epoch [90/100], Loss: 14518387713533614273593344.0000
Epoch [100/100], Loss: 7449779870375595263567855616.0000
a: -33038608105472.0, b: -9151163924480.0

损失函数在训练过程中越来越大,表明模型的学习过程出现了问题。这是因为学习率(Learning Rate)过大,参数更新可能会“跳过”最优值,导致损失函数在最小值附近震荡甚至发散。

解决方法:调小学习率,将lr=0.01

代码运行结果:

Epoch [10/100], Loss: 0.0965
Epoch [20/100], Loss: 0.0110
Epoch [30/100], Loss: 0.0099
Epoch [40/100], Loss: 0.0092
Epoch [50/100], Loss: 0.0086
Epoch [60/100], Loss: 0.0081
Epoch [70/100], Loss: 0.0075
Epoch [80/100], Loss: 0.0071
Epoch [90/100], Loss: 0.0066
Epoch [100/100], Loss: 0.0062
a: 1.9492162466049194, b: 1.1833451986312866

可以看出loss损失函数值在收敛,a接近2,b接近1

将epochs=500

代码运行结果:

Epoch [440/500], Loss: 0.0006
Epoch [450/500], Loss: 0.0006
Epoch [460/500], Loss: 0.0005
Epoch [470/500], Loss: 0.0005
Epoch [480/500], Loss: 0.0005
Epoch [490/500], Loss: 0.0004
Epoch [500/500], Loss: 0.0004
a: 1.986896276473999, b: 1.0473089218139648

a已经无限接近2,b无限接近1

九、模型定义组件

模型(神经网络,深度神经网络,深度学习)定义组件帮助我们在 PyTorch 中定义、训练和评估模型等。

在进行模型训练时,有三个基础的概念我们需要颗粒度对齐下:

名词定义
Epoch使用训练集的全部数据对模型进行一次完整训练,被称为“一代训练”
Batch使用训练集中的一小部分样本对模型权重进行一次反向传播的参数更新,这一小部分样本被称为“一批数据”
Iteration使用一个Batch数据对模型进行一次参数更新的过程,被称为“一次训练”

1. 基本组件认知

先初步认知,他们用法基本一样的,后续在学习深度神经网络和卷积神经网络的过程中会很自然的学到更多组件!

官方文档:https://pytorch.org/docs/stable/nn.html

1.1 损失函数组件

PyTorch已内置多种损失函数,在构建神经网络时随用随取!

文档:https://pytorch.org/docs/stable/nn.html#loss-functions

常用损失函数举例:

1.均方误差损失(MSE Loss)

  • 函数: torch.nn.MSELoss

  • 公式:
    M S E = 1 N ∑ i = 1 N ( y i − y ^ i ) 2 MSE=\frac{1}{N}∑_{i=1}^N(y_i−\widehat{y}_i)^2 MSE=N1i=1N(yiy i)2

  • 适用场景: 通常用于回归任务,例如预测连续值。

  • 特点: 对异常值敏感,因为误差的平方会放大较大的误差。

2.L1 损失(L1 Loss)

也叫做MAE(Mean Absolute Error,平均绝对误差)

  • 函数: torch.nn.L1Loss

  • 公式:
    L 1 = 1 N ∑ i = 1 N ∣ y i − y ^ i ∣ L1=\frac{1}{N}∑_{i=1}^N|y_i−\widehat{y}_i| L1=N1i=1Nyiy i

  • 适用场景: 用于回归任务,对异常值的敏感性较低。

  • 特点: 比 MSE 更鲁棒,但计算梯度时可能不稳定。

3.交叉熵损失(Cross-Entropy Loss)

  • 函数: torch.nn.CrossEntropyLoss

  • 参数:reduction:mean-平均值,sum-总和

  • 公式:
    C E = − Σ i y i l o g ( y ^ i ) CE=−\Sigma_iy_ilog(\widehat{y}_i) CE=Σiyilog(y i)

  • 适用场景: 用于多分类任务,输入是未经 softmax 处理的 logits。

  • 特点: 自带 softmax 操作,适合分类任务,能够有效处理类别不平衡问题。

4.二元交叉熵损失(Binary Cross-Entropy Loss)

  • 函数: torch.nn.BCELoss 或 torch.nn.BCEWithLogitsLoss

  • 参数:reduction:mean-平均值,sum-总和

  • 公式:
    B C E = − 1 N Σ i [ y i l o g ( y ^ i ) + ( 1 − y i ) l o g ( 1 − y ^ i ) ] BCE=−\frac{1}{N}\Sigma_i[y_ilog(\widehat{y}_i)+(1−y_i)log(1−\widehat{y}_i)] BCE=N1Σi[yilog(y i)+(1yi)log(1y i)]

  • 适用场景: 用于二分类任务。

  • 特点: BCEWithLogitsLoss 更稳定,因为它结合了 Sigmoid 激活函数和 BCE 损失。

1.2 线性层组件

构建一个简单的线性层,后续还有卷积层(Convolution Layers)、池化层(Pooling layers)、激活(Non-linear Activations)、归一化等需要我们去学习和使用…

torch.nn.Linear(in_features, out_features, bias=True)

参数说明:

in_features:

  • 输入特征的数量(即输入数据的维度)。
  • 例如,如果输入是一个长度为 100 的向量,则 in_features=100。

out_features:

  • 输出特征的数量(即输出数据的维度)。
  • 例如,如果希望输出是一个长度为 50 的向量,则 out_features=50。

bias:

  • 是否使用偏置项(默认值为 True)。
  • 如果设置为 False,则不会学习偏置项。

nn.Linear 的作用

nn.Linear 执行以下线性变换:

o u t p u t = i n p u t ⋅ W T + b output=input⋅W^T+b output=inputWT+b

其中:

  • input是输入数据,形状为 (batch_size, in_features)。
  • W是权重矩阵,形状为 (out_features, in_features)。
  • b是偏置项,形状为 (out_features,)。
  • output是输出数据,形状为 (batch_size, out_features)。
import torch
import torch.nn as nndef test002():model = nn.Linear(20, 60)# input数据形状为:(batch_size,in_features),其中in_features要和Linear中的数量一致input = torch.randn(128, 20)output = model(input)print(output.size())if __name__ == "__main__":test002()
1.3 优化器方法

官方文档:https://pytorch.org/docs/stable/optim.html

这里牵涉到的API有:

  • optim.SGD():优化器方法;是 PyTorch 提供的随机梯度下降(Stochastic Gradient Descent, SGD)优化器。
  • model.parameters():模型参数获取;是一个生成器,用于获取模型中所有可训练的参数(权重和偏置)。
  • optimizer.zero_grad():梯度清零;
  • optimizer.step():参数更新;是优化器的核心方法,用于根据计算得到的梯度更新模型参数。优化器会根据梯度和学习率等参数,调整模型的权重和偏置。
import torch
import torch.nn as nn
import torch.optim as optim# 优化方法SGD的学习
def test003():model = nn.Linear(20, 60)criterion = nn.MSELoss()# 优化器:更新模型参数optimizer = optim.SGD(model.parameters(), lr=0.01)input = torch.randn(128, 20)output = model(input)# 计算损失及反向传播loss = criterion(output, torch.randn(128, 60))# 梯度清零optimizer.zero_grad()# 反向传播loss.backward()# 更新模型参数optimizer.step()print(loss.item())if __name__ == "__main__":test003()

注意:这里只是组件认识和用法演示,没有具体的模型训练功能实现

2. 数据加载器

分数据集和加载器2个步骤~

2.1 构建数据类
2.1.1 Dataset类

Dataset是一个抽象类,是所有自定义数据集应该继承的基类。它定义了数据集必须实现的方法。

必须实现的方法

  1. __len__: 返回数据集的大小
  2. __getitem__: 支持整数索引,返回对应的样本

在 PyTorch 中,构建自定义数据加载类通常需要继承 torch.utils.data.Dataset 并实现以下几个方法:

  1. _init_ 方法
    用于初始化数据集对象:通常在这里加载数据,或者定义如何从存储中获取数据的路径和方法。

    def __init__(self, data, labels):self.data = dataself.labels = labels
    
  2. _len_ 方法
    返回样本数量:需要实现,以便 Dataloader加载器能够知道数据集的大小。

    def __len__(self):return len(self.data)
    
  3. _getitem_ 方法
    根据索引返回样本:将从数据集中提取一个样本,并可能对样本进行预处理或变换。

    def __getitem__(self, index):sample = self.data[index]label = self.labels[index]return sample, label
    

​ 如果你需要进行更多的预处理或数据变换,可以在 _getitem_ 方法中添加额外的逻辑。

  • 整体参考代码如下
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader# 定义数据加载类
class CustomDataset(Dataset):def __init__(self, data, labels):"""初始化数据集:data: 样本数据(例如,一个 NumPy 数组或 PyTorch 张量):labels: 样本标签"""self.data = dataself.labels = labelsdef __len__(self):return len(self.data)def __getitem__(self, index):index = min(max(index, 0), len(self.data) - 1)sample = self.data[index]label = self.labels[index]return sample, labeldef test001():# 简单的数据集准备data_x = torch.randn(666, 20, requires_grad=True, dtype=torch.float32)data_y = torch.randn(data_x.shape[0], 1, dtype=torch.float32)dataset = CustomDataset(data_x, data_y)# 随便打印个数据看一下print(dataset[0])if __name__ == "__main__":test001()
2.1.2 TensorDataset类

TensorDatasetDataset的一个简单实现,它封装了张量数据,适用于数据已经是张量形式的情况。

特点

  1. 简单快捷:当数据已经是张量形式时,无需自定义Dataset类
  2. 多张量支持:可以接受多个张量作为输入,按顺序返回
  3. 索引一致:所有张量的第一个维度必须相同,表示样本数量

源码:

class TensorDataset(Dataset):def __init__(self, *tensors):# size(0)在python中同shape[0],获取的是样本数量# 用第一个张量中的样本数量和其他张量对比,如果全部相同则通过断言,否则抛异常assert all(tensors[0].size(0) == tensor.size(0) for tensor in tensors)self.tensors = tensorsdef __getitem__(self, index):return tuple(tensor[index] for tensor in self.tensors)def __len__(self):return self.tensors[0].size(0)

示例:

def test03():torch.manual_seed(0)# 创建特征张量和标签张量features = torch.randn(100, 5)  # 100个样本,每个样本5个特征labels = torch.randint(0, 2, (100,))  # 100个二进制标签# 创建TensorDatasetdataset = TensorDataset(features, labels)# 使用方式与自定义Dataset相同print(len(dataset))  # 输出: 100print(dataset[0])  # 输出: (tensor([...]), tensor(0))
2.2 数据加载器

在训练或者验证的时候,需要用到数据加载器批量的加载样本。

DataLoader 是一个迭代器,用于从 Dataset 中批量加载数据。它的主要功能包括:

  • 批量加载:将多个样本组合成一个批次。
  • 打乱数据:在每个 epoch 中随机打乱数据顺序。
  • 多线程加载:使用多线程加速数据加载。

创建DataLoader:

# 创建 DataLoader
dataloader = DataLoader(dataset,          # 数据集batch_size=10,    # 批量大小shuffle=True,     # 是否打乱数据num_workers=2     # 使用 2 个子进程加载数据
)

遍历:

# 遍历 DataLoader
# enumerate返回一个枚举对象(iterator),生成由索引和值组成的元组
for batch_idx, (samples, labels) in enumerate(dataloader):print(f"Batch {batch_idx}:")print("Samples:", samples)print("Labels:", labels)

案例:

import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader# 定义数据加载类
class CustomDataset(Dataset):#略......def test01():# 简单的数据集准备data_x = torch.randn(666, 20, requires_grad=True, dtype=torch.float32)data_y = torch.randn(data_x.size(0), 1, dtype=torch.float32)dataset = CustomDataset(data_x, data_y)# 构建数据加载器data_loader = DataLoader(dataset, batch_size=8, shuffle=True)for i, (batch_x, batch_y) in enumerate(data_loader):print(batch_x, batch_y)breakif __name__ == "__main__":test01()

相关文章:

  • Kettle和Canal
  • 【AI论文】Genius:一种用于高级推理的可泛化和纯无监督的自我训练框架
  • 使用FastAPI构建高效、优雅的RESTful API
  • 基于ssh密钥访问远程Linux
  • AI 数字短视频数字人源码开发的多元价值与深远意义​
  • 网络417 路由转发2 防火墙
  • 常见的VLAN划分方式和示例场景
  • [250417] Fedora 42 正式发布,搭载 Linux 6.14 内核和 GNOME 48 桌面环境
  • 扫雷-C语言版
  • 使用Qt multimedia模块实现简易的视频播放器
  • stm32-lm75、SPI
  • Jenkins 2.492.2 LTS 重置管理员密码
  • 科研新触角:松灵六轴臂重构具身智能生态
  • 在Ubuntu服务器上部署xinference
  • python入门:不同进制数据的表示方式,转换;数据类型的转换,隐式类型的转换
  • ServletRequestListener 的用法笔记250417
  • 日语学习-日语知识点小记-构建基础-JLPT-N4阶段(6):ながら 一边。。一边
  • NVIDIA 显卡
  • Python基础总结(六)之集合
  • 《如何用 Function 实现动态配置驱动的处理器注册机制?》
  • 特朗普执政百日集会吹嘘政绩,美国消费者信心指数跌至疫情以来最低
  • 专访丨青年作家杜梨:以动物的视角去观察这个世界
  • 五万吨级半潜船在沪完成装备装载
  • “中国游”带火“中国购”,“即买即退”让外国游客购物更丝滑
  • 王羲之《丧乱帖》在日本流传了1300年,将在大阪展23天
  • 新任浙江省委常委、组织部长杨荫凯到嘉兴南湖瞻仰红船