AI的工程基础1-最优化算法
数据准备import torch # 固定随机种子使得运行结果可以稳定复现 torch.manual_seed(1024) # 产生训练用的数据 x_origin torch.linspace(100, 300, 200) # 将变量X归一化否则梯度下降法很容易不稳定 x (x_origin - torch.mean(x_origin)) / torch.std(x_origin) epsilon torch.randn(x.shape) y 10 * x 5 epsilontorch.linspace使用 torch.linspace 函数时需要指定三个主要参数起始值start、结束值end和步数steps。步数决定了张量中将包含多少个元素。例如创建一个从3到10的张量包含5个元素可以使用以下代码import torch # 创建一个从3到10包含5个元素的张量 tensor torch.linspace(3, 10, steps5) print(tensor) 输出将是 tensor([ 3.0000, 4.7500, 6.5000, 8.2500, 10.0000])参数解释start张量的起始值。end张量的结束值。steps张量中元素的数量。dtype可选参数用于指定返回张量的数据类型。device可选参数用于指定张量所在的设备如CPU或GPU。requires_grad可选参数指定是否需要计算梯度。torch.std在PyTorch中标准差是通过 torch.std 函数计算的它可以计算张量tensor的标准差。标准差是衡量数据集中数值分散程度的统计量它是方差的平方根。在PyTorch中标准差的计算考虑了贝塞尔校正Bessels correction这意味着在计算样本标准差时默认会使用 n-1 而不是 n 作为分母。import torch # 创建一个张量 a torch.tensor([[0.2035, 1.2959, 1.8101, -0.4644], [1.5027, -0.3270, 0.5905, 0.6538], [-1.5745, 1.3330, -0.5596, -0.6548], [0.1264, -0.5080, 1.6420, 0.1992]]) # 计算每行的标准差并保持维度 std_a torch.std(a, dim1, keepdimTrue) print(std_a)输出将是每行元素标准差的张量。参数解释input输入张量。dim要减少的维度可以是单个维度、维度列表或 None。correction样本大小与样本自由度之间的差异默认为贝塞尔校正即 correction1。keepdim输出张量是否保留 dim 维度。out输出张量。模型定义# 为了使用PyTorch的高层封装函数我们通过继承Module类来定义函数 class Linear(torch.nn.Module): def __init__(self): 定义线性回归模型的参数a, b super().__init__() self.a torch.nn.Parameter(torch.zeros(())) self.b torch.nn.Parameter(torch.zeros(())) def forward(self, x): 根据当前的参数估计值得到模型的预测结果 参数 ---- x torch.tensor变量x 返回 ---- y_pred torch.tensor模型预测值 return self.a * x self.b def string(self): 输出当前模型的结果 return fy {self.a.item():.2f} * x {self.b.item():.2f}forward我们在定义自已的网络的时候需要继承nn.Module类并重新实现构造函数__init__和forward这两个方法。但有一些注意技巧1一般把网络中具有可学习参数的层如全连接层、卷积层等放在构造函数__init__()中当然我也可以吧不具有参数的层也放在里面2一般把不具有可学习参数的层(如ReLU、dropout、BatchNormanation层)可放在构造函数中也可不放在构造函数中如果不放在构造函数__init__里面则在forward方法里面可以使用nn.functional来代替3forward方法是必须要重写的它是实现模型的功能实现各个层之间的连接关系的核心。super().__init__()super().__init__() 是 Python 中用于调用父类初始化方法的语法常用于继承场景确保子类能够正确初始化父类的属性和方法。class Parent: def __init__(self): print(Parent initialized) class Child(Parent): def __init__(self): super().__init__() # 调用父类的 __init__ 方法 print(Child initialized) child Child()输出:Parent initializedChild initialized继承父类初始化逻辑通过 super().__init__()子类可以调用父类的 __init__ 方法避免重复代码。多继承支持在多继承中super() 遵循方法解析顺序MRO确保所有父类的初始化方法都被正确调用。Python中的f字符串f字符串formatted string literals是Python 3.6引入的一种字符串格式化方法它使得字符串格式化更加简便和高效。基本用法在f字符串中可以在大括号 {} 内放入任何有效的Python表达式这些表达式会在运行时被求值并替换为其结果。name Alice print(fHello, {name}!) # 输出Hello, Alice!表达式求值与函数调用f字符串的大括号内可以填入表达式或调用函数Python会求出其结果并填入返回的字符串内。import math print(fThe value of pi is approximately {math.pi:.2f}.) # 输出The value of pi is approximately 3.14多行f字符串f字符串还可以用于多行字符串。name Eric age 27 msg ( fHello!\n fIm {name}.\n fIm {age}. ) print(msg) # 输出 # Hello! # Im Eric # Im 27自定义格式可以使用格式说明符对数字进行格式化例如对齐、宽度、符号、补零、精度等。number 1234.5678 print(f{number:,.2f}) # 输出1,234.57创建实例、参数设置与使用第三方库训练# 定义模型 model Linear() # 确定最优化算法 learning_rate 0.1 optimizer torch.optim.SGD(model.parameters(), lrlearning_rate) for t in range(20): # 根据当前的参数估计值得到模型的预测结果 # 也就是调用forward函数 y_pred model(x) # 计算损失函数 loss (y - y_pred).pow(2).mean() # 将上一次的梯度清零 optimizer.zero_grad() # 计算损失函数的梯度 loss.backward() # 迭代更新模型参数的估计值 optimizer.step() print(fStep {t 1}, Loss: {loss: .2f}; Result: {model.string()})SGDtorch.optim.SGD 是PyTorch中用于实现随机梯度下降Stochastic Gradient DescentSGD的类它是深度学习中常用的优化算法之一。SGD通过迭代更新模型参数来最小化目标函数通常用于训练神经网络。在PyTorch中要使用SGD优化器首先需要创建一个SGD对象并将模型的参数传递给它。然后在训练循环中执行前向传播、计算损失、执行反向传播并调用优化器的step()方法来更新参数。例如import torch import torch.optim as optim # 定义模型和损失函数 model torch.nn.Linear(10, 1) criterion torch.nn.MSELoss() # 定义优化器 optimizer optim.SGD(model.parameters(), lr0.01, momentum0.9, weight_decay0.0001) # 在训练循环中使用优化器 for epoch in range(epochs): # 前向传播 output model(input_data) loss criterion(output, target) # 反向传播和优化 optimizer.zero_grad() loss.backward() optimizer.step()SGD的参数解释params必须参数需要优化的参数张量的迭代器例如模型的参数model.parameters()。lr必须参数学习率控制每次参数更新的步长。momentum默认值为0动量用于加速SGD收敛引入了上一步梯度的指数加权平均。dampening默认值为0阻尼项用于减缓动量的速度。weight_decay默认值为0权重衰减也称为L2正则化项用于防止过拟合。nesterov默认值为FalseNesterov动量采用Nesterov动量更新规则。自定义训练# 利用代码实现PyTorch封装的梯度下降法 model Linear() for t in range(20): # 根据当前的参数估计值得到模型的预测结果 # 也就是调用forward函数 y_pred model(x) # 计算损失函数 loss (y - y_pred).pow(2).mean() # 计算损失函数的梯度 loss.backward() with torch.no_grad(): for param in model.parameters(): # 迭代更新模型参数的估计值等同于optimizer.step() param - learning_rate * param.grad # 将梯度清零等同于optimizer.zero_grad() param.grad None print(fStep {t 1}, Loss: {loss: .2f}; Result: {model.string()})loss是需要减小的所以是减去梯度。torch.no_grad()在 PyTorch 中with torch.no_grad 是一个上下文管理器用于在特定代码块中禁用自动求导功能。这在模型评估或推断时非常有用因为在这些情况下不需要计算梯度从而可以提高代码执行效率。以下是一个简单的示例展示了如何使用 with torch.no_gradimport torch # 创建一些张量并设置 requires_gradTrue x torch.randn(10, 5, requires_gradTrue) y torch.randn(10, 5, requires_gradTrue) z torch.randn(10, 5, requires_gradTrue) # 在 with torch.no_grad 语句块中进行计算 with torch.no_grad(): w x y z print(w.requires_grad) # 输出: False print(w.grad_fn) # 输出: None print(w.requires_grad) # 输出: False在上述代码中即使 x、y 和 z 的 requires_grad 属性为 True在 with torch.no_grad 语句块中计算得到的新张量 w 的 requires_grad 属性也会被自动设置为 False且 grad_fn 为 None。代码跟踪与尝试复现import torch torch.manual_seed(1024) x_oritorch.linspace(100,300,200) x(x_ori-torch.mean(x_ori))/torch.std(x_ori) etorch.randn(x.shape) y10*x5e class Linear(torch.nn.Module): def __init__(self): super().__init__() self.atorch.nn.Parameter(torch.zeros(())) self.btorch.nn.Parameter(torch.zeros(())) def forward(self,x): return self.a*xself.b def string(self): return fy{self.a.item():.2f}*x{self.b.item():.2f} modelLinear() lr0.1 optimizertorch.optim.SGD(model.parameters(),lrlr) for i in range(20): y_pmodel(x) loss(y-y_p).pow(2).mean() optimizer.zero_grad() loss.backward() optimizer.step() print(fStep {i 1}, Loss: {loss: .2f}; Result: {model.string()}) modelLinear() for i in range(20): y_pmodel(x) loss(y-y_p).pow(2).mean() loss.backward() with torch.no_grad(): for param in model.parameters(): param-lr*param.grad param.gradNone print(fStep {i 1}, Loss: {loss: .2f}; Result: {model.string()})结果Step 1, Loss: 125.25; Result: y1.98*x1.02 Step 2, Loss: 80.72; Result: y3.56*x1.83 Step 3, Loss: 52.16; Result: y4.83*x2.49 Step 4, Loss: 33.84; Result: y5.85*x3.01 Step 5, Loss: 22.10; Result: y6.66*x3.42 Step 6, Loss: 14.57; Result: y7.31*x3.76 Step 7, Loss: 9.74; Result: y7.83*x4.03 Step 8, Loss: 6.64; Result: y8.25*x4.24 Step 9, Loss: 4.66; Result: y8.59*x4.41 Step 10, Loss: 3.38; Result: y8.85*x4.55 Step 11, Loss: 2.56; Result: y9.07*x4.66 Step 12, Loss: 2.04; Result: y9.24*x4.74 Step 13, Loss: 1.71; Result: y9.38*x4.81 Step 14, Loss: 1.49; Result: y9.49*x4.87 Step 15, Loss: 1.35; Result: y9.58*x4.91 Step 16, Loss: 1.26; Result: y9.65*x4.95 Step 17, Loss: 1.21; Result: y9.71*x4.98 Step 18, Loss: 1.17; Result: y9.75*x5.00 Step 19, Loss: 1.15; Result: y9.79*x5.02 Step 20, Loss: 1.13; Result: y9.82*x5.03 Step 1, Loss: 125.25; Result: y1.98*x1.02 Step 2, Loss: 80.72; Result: y3.56*x1.83 Step 3, Loss: 52.16; Result: y4.83*x2.49 Step 4, Loss: 33.84; Result: y5.85*x3.01 Step 5, Loss: 22.10; Result: y6.66*x3.42 Step 6, Loss: 14.57; Result: y7.31*x3.76 Step 7, Loss: 9.74; Result: y7.83*x4.03 Step 8, Loss: 6.64; Result: y8.25*x4.24 Step 9, Loss: 4.66; Result: y8.59*x4.41 Step 10, Loss: 3.38; Result: y8.85*x4.55 Step 11, Loss: 2.56; Result: y9.07*x4.66 Step 12, Loss: 2.04; Result: y9.24*x4.74 Step 13, Loss: 1.71; Result: y9.38*x4.81 Step 14, Loss: 1.49; Result: y9.49*x4.87 Step 15, Loss: 1.35; Result: y9.58*x4.91 Step 16, Loss: 1.26; Result: y9.65*x4.95 Step 17, Loss: 1.21; Result: y9.71*x4.98 Step 18, Loss: 1.17; Result: y9.75*x5.00 Step 19, Loss: 1.15; Result: y9.79*x5.02 Step 20, Loss: 1.13; Result: y9.82*x5.03拓展广播机制广播机制Broadcasting是指在进行运算时自动扩展较小的张量以匹配较大的张量的尺寸而不需要复制数据。这种机制可以提高运算效率节省内存。广播机制允许两个形状不同的张量进行运算。其基本规则如下每个张量至少有一个维度。从末尾维度开始遍历两个张量的每个对应维度需要满足以下条件之一 维度相等。 其中一个维度为1。 其中一个维度不存在^3^。如果两个张量是“可广播的”则计算过程遵循以下规则在较小张量前面增加维度使其维度与较大张量相等。计算结果的维度值取两个张量中较大的那个值。扩展维度的过程是将数值进行复制。以下是一些使用广播机制的示例代码 import torch # 示例1两个张量的维度相同 a torch.rand(4, 32, 14, 14) b torch.rand(4, 32, 14, 14) print((a b).shape) # 输出torch.Size([4, 32, 14, 14]) # 示例2一个张量的某些维度为1 a torch.rand(4, 32, 14, 14) b torch.rand(1, 32, 1, 1) print((a b).shape) # 输出torch.Size([4, 32, 14, 14]) # 示例3一个张量的维度较小 a torch.rand(4, 32, 14, 14) c torch.rand(32, 1, 1) print((a c).shape) # 输出torch.Size([4, 32, 14, 14])注意事项在使用广播机制时需要注意以下几点确保张量的维度符合广播规则否则会引发错误。避免在原地操作中使用广播因为原地操作不允许改变张量的形状。