目录

一.线性回归

二.softmax回归(*后续所有深度学习的基础)

三.关于保存模型


  • 机器学习模型中的关键要素是训练数据、损失函数、优化算法,还有模型本身。
  • 经典统计学习技术中的线性回归和softmax回归均为单层线性神经网络。
  • 最小化损失函数等价于执行极大似然估计(似然估计是统计学习中的概念)。
  • 能控制的有两个点:负梯度方向(损失函数)和学习率,这两个不同导致每次更新梯度的步伐不一致。

        建议先看基础后本篇:

深度学习入门:【基本概念】https://blog.csdn.net/weixin_51929697/article/details/149131379?spm=1011.2415.3001.5331

一.线性回归

  • 单层神经网络,属于回归问题。回归估计输出单连续值。
  • 损失函数:真实值与预测值均方损失(L2 loss)
  • 优化模型:小批量随机梯度下降算法

        线性回归模型用平方损失来衡量预测值和真实值差异,是对n维输入的加权,是深度学习模型研究中有显示解、凸的唯一。下面简单的线性模型可视为一个单层的神经网络。

        当预测值和真实值比较远,又不想更新太大时,考虑用L1 Loss,优点是梯度是 -1 or 1 的常数,缺点是零点处不可导,且出现正负数变化较大,不平衡性导致当预测值和真实值比较近时,即优化到末期时,变得不稳定。

        还有一类常见的损失函数Huber's 鲁棒性损失:

        矢量加速:

        梯度下降:梯度是上升最快的向量,负梯度就是下降最快的向量。下图中公式表示前一个w减去标量*损失函数。沿着反梯度方向下降,并更新参数。

        深度学习默认的求解方法: 小批量随机梯度下降。

        批量大小是第一个超参数,学习率\eta是第二个超参数。 梯度是求导中计算最贵的部分,所以要少走。学习率不能太大,也不能太小。 超参数的选择是重要问题,在优化算法那一节讲如何快速找到一个合适的区间。

        生成数据集,下图是前两个特征和标签。

  • 关于参数更新时减去的梯度是除以batch_size后的平均值:
def sgd(params, lr, batch_size): #@save

"""小批量随机梯度下降"""

with torch.no_grad():

for param in params:

param -= lr * param.grad / batch_size #更新参数,除以batch_size来得到平均值,因为梯度是batch_size个样本的和,所以需要除以batch_size。如果不在这除,就把学习率除以batch_size

param.grad.zero_() #清空梯度
  • (课程练习)如果样本个数不能被批量大小整除,data_iter函数的行为会有什么变化?

        以样本数量17为例,批量取3、4、5、8、17:

        总样本数: 17
        特征形状: torch.Size([17, 2])
        标签形状: torch.Size([17, 1])

        回答:
        1. 当样本数不能被批量大小整除时,最后一个批次会包含较少的样本
        2. 这不会影响训练的正确性,但可能影响训练效率
        3. 较大的批量大小通常有更好的内存使用效率
        4. 较小的批量大小提供更多的梯度更新机会
        5. 在实际应用中,这种不完整批次是正常且常见的 

  • 线性回归的简洁实现(略) 

      以上部分只运用了: (1)通过张量来进行数据存储和线性代数; (2)通过自动微分来计算梯度。 实际上,由于数据迭代器、损失函数、优化器和神经网络层很常用, 现代深度学习库也为我们实现了这些组件。

二.softmax回归(*后续所有深度学习的基础)

  • 单层神经网络,分类预测输出多离散类别:softmax名义上是回归,其实是分类问题,如区分cat和dog的图片,是离散的。不是问“多少”,而是问“哪一个”:
    • 某个电子邮件是否属于垃圾邮件文件夹?
    • 某个用户可能注册不注册订阅服务?
    • 某个图像描绘的是驴、狗、猫、还是鸡?
    • 某人接下来最有可能看哪部电影?
  • 向量表达:

        每个输出由所有的输入共同决定,因此虽然softmax()函数是非线性的,但softmax回归属于线性回归,且回归的输出层属于全连接层。

  • softmax()函数的最大贡献是:将未规范化的预测变换为非负数并且总和为1,同时让模型保持可导,即关心正确类别置信度远远大于其他非正确类的置信度。

        采用独热编码(one-hot encoding),即正确分类为1,其余非正确项均为0。

        首先对每个未规范化的预测求幂,这样可以确保输出非负。 为了确保最终输出的概率值总和为1,我们再让每个求幂后的结果除以它们的总和。

  • 损失函数:交叉熵损失(cross-entropy loss)为损失函数,只关心正确类别的预测概率值大小,而不关心其他非正确分类。
  • 为了提高计算效率并且充分利用GPU,我们通常会对小批量样本的数据执行矢量计算。

        假设我们读取了一个批量的样本X, 其中特征维度(输入数量)为d,批量大小为n。 此外,假设我们在输出中有q个类别。 那么小批量样本的特征为X\in \mathbb{R}^{n\times d }, 权重为W\in \mathbb{R}^{d\times q }, 偏置为b\in \mathbb{R}^{1\times q }。 softmax回归的矢量计算表达式为:

  • 模型预测与评估

        在训练softmax回归模型后,给出任何样本特征,我们可以预测每个输出类别的概率。 通常我们使用预测概率最高的类别作为输出类别。 如果预测与实际类别(标签)一致,则预测是正确的。 在接下来的实验中使用精度(accuracy)来评估模型的性能。精度等于正确预测数与预测总数之间的比率。        

  • 例子:Fashion-MNIST数据集

        Fashion-MNIST由10个类别的图像组成, 每个类别由训练数据集(train dataset)中的6000张图像 和测试数据集(test dataset)中的1000张图像组成。 因此,训练集和测试集分别包含60000和10000张图像。 测试数据集不会用于训练,只用于评估模型性能。

        定义load_data_fashion_mnist函数(详见本地http://localhost:8888/notebooks/chapter_linear-networks/image-classification-dataset.ipynb

        原始数据集中的每个样本都是28\times 28的图像。 本节[将展平每个图像,把它们看作长度为784的向量。]  

       ps*:输入必须是一个向量,所以要把图片拉长(展评),但是损失空间信息(卷积神经网络中再学习)

        定义softmax():   (用例子说明,数学上是成立的) 

def softmax(X):
    X_exp = torch.exp(X)
    partition = X_exp.sum(1, keepdim=True)
    return X_exp / partition  # 这里应用了广播机制

#例子
X = torch.normal(0, 1, (2, 5))
X_prob = softmax(X)
X_prob, X_prob.sum(1)

#输出:(tensor([[0.3255, 0.3890, 0.0672, 0.0787, 0.1396],
         [0.0789, 0.0136, 0.2172, 0.4900, 0.2003]]),
 tensor([1.0000, 1.0000]))

        定义损失函数:

        这里需要理解。回顾一下,交叉熵采用真实标签的预测概率的负对数似然。 这里我们不使用Python的for循环迭代预测(这往往是低效的), 而是通过一个运算符选择所有元素(本文中搜索“矢量加速”)。

        [创建一个数据样本y_hat,其中包含2个样本在3个类别的预测概率, 以及它们对应的标签y]

#ps*:有了y,我们知道在第一个样本中,第一类是正确的预测; 
#而在第二个样本中,第三类是正确的预测。 
#然后(使用y作为y_hat中概率的索引), 选择第一个样本中第一个类的概率和第二个样本中第三个类的概率。
y = torch.tensor([0, 2])
y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])
print(y_hat[[0, 1], y])
#输出:tensor([0.1000, 0.5000])

def cross_entropy(y_hat, y):
    return - torch.log(y_hat[range(len(y_hat)), y])
print(cross_entropy(y_hat, y))
#输出:tensor([2.3026, 0.6931])

        模型实现过程(本地http://localhost:8888/notebooks/chapter_linear-networks/softmax-regression-scratch.ipynb

        优化模型:小批量随机梯度下降算法

        迭代周期(num_epochs)和学习率(lr)都是可调节的超参数。 通过更改它们的值,我们可以提高模型的分类精度。

        (万众瞩目的)实验结果:[训练模型30个迭代周期,学习率0.1]

        (1)用本机CPU跑:

                原书是10个迭代周期,用原书代码改为30个迭代周期,结果:

                为了跑GPU,数据/模型/参数需要迁移到GPS devices,改了代码后,训练了2遍:

        (2)用本机GPU 跑:

        这里就要用到在之前《深度学习环境安装MACOS完整版(python版本过高不兼容d2l的问题解决)》中提到的验证方法二:GPU加速。

         小结:

  • 虽然准确率都只有80%,但是还是正确预测了,准确率是一个提升空间!
  • GPU方面,本次没有惊喜,也是一个提升空间!

三.关于保存模型

         这里介绍一个非常有用的经验之谈,保存训练好的模型,以防止……懂的都懂……无尽的等待。

        目标:在后续使用时,快速加载和使用模型,不用从头训练。为模型的迁移学习、部署和共享提供便利。

        保存模型的三种方法: 

      (原文链接,感谢博主贡献:https://blog.csdn.net/m0_69722969/article/details/147018326)

        (1)使用 torch.save() 保存完整模型:将模型对象直接传递给 torch.save() 函数,加载使用 torch.load() 函数。

import torch
import torch.nn as nn
 
# 定义一个简单的神经网络
class SimpleNet(nn.Module):
    def __init__(self):
        super(SimpleNet, self).__init__()
        self.fc = nn.Linear(10, 2)
    
    def forward(self, x):
        return self.fc(x)
 
# 创建模型实例并训练(假设已经训练完成)
model = SimpleNet()
 
# 保存整个模型
torch.save(model, 'whole_model.pth')


# 加载整个模型
loaded_model = torch.load('whole_model.pth')
 
# 测试模型
input_data = torch.randn(1, 10)
output = loaded_model(input_data)
print(output)

         (2)使用 model.state_dic() 提取模型参数,只保存模型参数(state_dict)。方便地将参数迁移到其他模型结构中。加载参数时,需要先定义模型结构,然后将参数加载到模型中。

# 提取并保存模型参数
torch.save(model.state_dict(), 'model_params.pth')

# 定义相同的模型结构
loaded_model = SimpleNet()
 
# 加载参数
loaded_model.load_state_dict(torch.load('model_params.pth'))
 
# 测试模型
input_data = torch.randn(1, 10)
output = loaded_model(input_data)
print(output)

         (3)使用 model.state_dic() 保存模型参数和模型训练状态(优化器的状态(如学习率、动量等))。这在需要从中断处继续训练时非常有用,加载训练状态时,可以从中断处继续训练。

# 定义模型和优化器
model = SimpleNet()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
 
# 假设已经训练了几轮
for epoch in range(10):
    # 训练代码
    pass
 
# 保存训练状态
torch.save({
    'epoch': epoch,
    'model_state_dict': model.state_dict(),
    'optimizer_state_dict': optimizer.state_dict(),
    'loss': loss,
}, 'training_state.pth')

# 加载训练状态
checkpoint = torch.load('training_state.pth')
 
# 定义相同的模型和优化器
model = SimpleNet()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
 
# 加载状态
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
start_epoch = checkpoint['epoch'] + 1
 
# 继续训练
for epoch in range(start_epoch, 20):
    # 训练代码
    pass

        遇到的问题及解决:

        设备兼容性:保存的模型在加载时可能会遇到设备(CPU/GPU)不匹配的问题。可以通过 map_location 参数指定加载到的设备。

        

loaded_model = torch.load('model_params.pth', map_location=torch.device('cpu'))

        模型结构一致性:只保存参数时,加载时必须确保模型结构与保存时一致,否则会报错。

        文件格式:PyTorch 默认使用 .pth 或 .pt 作为保存文件的扩展名,但你可以根据需要选择其他扩展名。

        定期保存:在长时间训练中,建议定期保存模型状态,以防止因意外中断导致的训练成果丢失。

        小结:

  • 保存整个模型:适合简单的场景,但不够灵活。

  • 只保存模型参数:推荐的方法,灵活且高效。

  • 保存训练状态:适合需要从中断处继续训练的场景。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐