解锁PyTorch:从原理到实战,损失函数全解析
PyTorch 是由 Facebook 人工智能研究团队开发的开源深度学习框架,在深度学习领域中占据着重要地位,其受欢迎程度仅次于 TensorFlow。它以 Python 为基础,继承了 Python 简洁易用的特性,使得开发者能够快速上手。PyTorch 采用动态计算图机制,与传统的静态计算图框架不同,其计算图在运行时构建,这赋予了开发者极大的灵活性。在研究和实验阶段,开发者可以方便地修改网络
深度学习中的损失函数

在深度学习的庞大体系中,损失函数无疑占据着举足轻重的地位。它就像是模型训练过程中的指南针,引导着模型不断优化,以达到更好的性能。简单来说,损失函数的作用是衡量模型预测值与真实值之间的差异 。在模型训练时,模型会基于输入数据进行预测,随后将预测结果与真实标签相对比,通过损失函数计算出两者之间的差距。这个差距以一个数值的形式呈现,即损失值。若损失值较大,表明模型预测结果与真实值偏差较大,模型需要调整优化;反之,若损失值较小,则说明模型预测较为准确,性能表现良好。
以图像分类任务为例,假设我们有一个模型用于识别猫和狗的图像。当输入一张猫的图片时,模型可能输出一个概率分布,比如预测为猫的概率是 0.3,预测为狗的概率是 0.7。而真实标签表明这张图片就是猫,即真实值为 1(假设猫的标签为 1,狗的标签为 0)。此时,通过损失函数(如交叉熵损失函数)就能计算出模型预测与真实值之间的差异,这个差异值会指导模型在后续的训练中如何调整参数,以提高对猫和狗图像分类的准确性。
在深度学习中,损失函数对模型训练和优化的重要性体现在多个方面。它为模型的优化提供了明确的方向。通过最小化损失函数,模型能够不断调整自身的参数,使得预测值尽可能接近真实值。在神经网络中,参数的调整是通过反向传播算法实现的,而损失函数的梯度则是反向传播的关键驱动力。损失函数还可以帮助我们评估模型的性能。在训练过程中,监控损失值的变化可以让我们了解模型是否在学习,是否出现过拟合或欠拟合等问题。如果损失值在训练集上不断下降,但在验证集上却逐渐上升,这可能是过拟合的信号,需要采取相应的措施,如增加数据量、使用正则化技术等。
PyTorch 作为一款广泛应用的深度学习框架,在损失函数的实现上具有诸多优势。它提供了丰富多样的损失函数类,涵盖了常见的回归、分类等任务所需的损失函数,如均方误差损失函数(MSELoss)、交叉熵损失函数(CrossEntropyLoss)、二元交叉熵损失函数(BCELoss)等 ,方便开发者根据不同的任务需求进行选择。PyTorch 的损失函数实现具有高度的灵活性和可定制性。开发者不仅可以直接使用框架提供的损失函数,还可以根据具体问题自定义损失函数,以满足特殊的需求。这种灵活性使得 PyTorch 能够适应各种复杂的深度学习场景。此外,PyTorch 的损失函数在计算效率上也表现出色,借助其强大的张量计算能力和 GPU 加速支持,能够快速地计算损失值和梯度,大大提高了模型的训练速度。
PyTorch 损失函数基础
(一)PyTorch 简介
PyTorch 是由 Facebook 人工智能研究团队开发的开源深度学习框架,在深度学习领域中占据着重要地位,其受欢迎程度仅次于 TensorFlow 。它以 Python 为基础,继承了 Python 简洁易用的特性,使得开发者能够快速上手。PyTorch 采用动态计算图机制,与传统的静态计算图框架不同,其计算图在运行时构建,这赋予了开发者极大的灵活性。在研究和实验阶段,开发者可以方便地修改网络结构和参数,而无需像静态计算图框架那样重新构建整个计算图。在调试过程中,动态计算图使得错误定位更加容易,因为可以直接在定义图的代码行进行断点检查,就如同调试普通 Python 代码一样。
在深度学习模型的训练中,计算效率至关重要。PyTorch 对 GPU 加速提供了强大的支持,能够充分利用 GPU 的并行计算能力,显著提升模型的训练速度。它还支持多 GPU 和分布式训练,使得开发者可以在大规模数据集上进行高效的模型训练。例如,在训练大型的图像识别模型或自然语言处理模型时,通过多 GPU 并行计算,可以大大缩短训练时间,提高研究和开发的效率。此外,PyTorch 拥有一个活跃的社区,社区中提供了大量的文档、教程、代码示例以及预训练模型。这些资源不仅有助于开发者快速掌握 PyTorch 的使用方法,还能促进技术的交流和创新,推动 PyTorch 在各个领域的广泛应用。
(二)损失函数的基本概念
损失函数在模型训练中扮演着核心角色,它的主要作用是衡量模型预测值与真实值之间的差异。在模型训练的过程中,我们的目标是不断调整模型的参数,使得损失函数的值最小化,从而使模型的预测结果尽可能接近真实值。从数学角度来看,损失函数是一个将模型预测值和真实值映射为一个标量的函数,这个标量就是损失值,它反映了模型预测的准确程度。
根据不同的应用场景和任务类型,损失函数可以分为多种类型,其中常见的有回归损失函数、分类损失函数和排序损失函数。回归损失函数主要用于回归任务,衡量预测值与真实值之间的数值差异。均方误差损失函数(MSELoss)是回归任务中常用的损失函数之一,它通过计算预测值与真实值之差的平方的平均值来衡量损失,公式为\(MSE = \frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y}_i)^2\),其中\(n\)是数据样本数量,\(y_i\)是真实值,\(\hat{y}_i\)是预测值。这种损失函数对较大的误差给予更大的惩罚,因为误差的平方会使较大的误差更加突出,从而促使模型在训练时更关注那些预测误差较大的数据点。
分类损失函数则用于分类任务,衡量模型预测的类别与真实类别的差异。交叉熵损失函数(CrossEntropyLoss)是分类任务中极为常用的损失函数。在多分类问题中,其公式为\(H(p, q) = -\sum_{i=1}^{n} \sum_{j=1}^{k} p_{ij} \log q_{ij}\),其中\(n\)是数据样本数量,\(k\)是类别数量,\(p_{ij}\)是样本\(i\)属于类别\(j\)的真实概率,\(q_{ij}\)是模型预测样本\(i\)属于类别\(j\)的概率。交叉熵损失函数通过衡量真实概率分布与预测概率分布之间的差异,来指导模型的训练,当模型预测的概率分布与真实概率分布越接近时,交叉熵损失值越小。
排序损失函数主要应用于排序任务,如信息检索、推荐系统等,其目的是使模型预测的排序结果尽可能接近真实的排序。在信息检索中,我们希望相关度高的文档在排序结果中排在前面,排序损失函数可以衡量模型预测的文档排序与真实相关度排序之间的差异,通过最小化排序损失函数,模型能够学习到更准确的排序模式,从而提高排序的准确性和性能 。
常见损失函数详解
(一)回归损失函数
1. L1 损失(平均绝对误差,MAE)
L1 损失,也被称为平均绝对误差(Mean Absolute Error,MAE) ,其数学公式为\(L_1 = \frac{1}{n}\sum_{i=1}^{n}|x_i - y_i|\),其中\(x_i\)表示预测值,\(y_i\)表示真实值,\(n\)为样本数量。该损失函数的原理是计算预测值与真实值之间绝对差的平均值,以此衡量模型预测值与真实值之间的误差。
在房价预测任务中,若真实房价为 500 万元,模型预测值分别为 480 万元、520 万元、490 万元,样本数量为 3。通过 L1 损失公式计算可得:\(L_1 = \frac{|480 - 500| + |520 - 500| + |490 - 500|}{3} = \frac{20 + 20 + 10}{3} = \frac{50}{3} \approx 16.67\)(万元) ,这个结果反映了模型预测值与真实值之间的平均误差程度。
L1 损失的特点在于对异常值具有较好的鲁棒性。由于它计算的是绝对误差,异常值不会因为平方运算而对损失值产生过大的影响。在一个包含房屋面积与房价关系的数据集里,如果存在个别面积异常大但房价并非按比例增长的豪宅数据(异常值),使用 L1 损失训练模型时,这些异常值对模型参数更新的影响相对较小,模型能够更专注于学习大多数正常数据的规律,从而提高模型的稳定性和泛化能力。然而,L1 损失也存在一定的局限性,其梯度在零点处不可导,这可能会对一些基于梯度下降的优化算法产生影响,导致模型收敛速度变慢 。
2. L2 损失(均方误差,MSE)
L2 损失,即均方误差(Mean Squared Error,MSE) ,其数学公式为\(L_2 = \frac{1}{n}\sum_{i=1}^{n}(x_i - y_i)^2\) ,同样\(x_i\)为预测值,\(y_i\)为真实值,\(n\)是样本数量。它通过计算预测值与真实值之间平方差的平均值来衡量模型的误差。
在股票价格预测场景中,若真实股价在某一时刻为 100 元,模型的三次预测值分别为 98 元、105 元、95 元。利用 L2 损失公式计算:\(L_2 = \frac{(98 - 100)^2 + (105 - 100)^2 + (95 - 100)^2}{3} = \frac{(-2)^2 + 5^2 + (-5)^2}{3} = \frac{4 + 25 + 25}{3} = \frac{54}{3} = 18\) ,该值体现了模型预测股价与真实股价之间的误差情况。
L2 损失在大部分回归问题中应用广泛,这是因为它具有良好的数学性质,其梯度连续且可导,便于使用梯度下降等优化算法进行模型训练。在训练神经网络时,通过反向传播算法计算 L2 损失的梯度,能够快速准确地更新模型参数,使模型朝着减小损失值的方向优化。但 L2 损失对异常值比较敏感,因为误差的平方会放大异常值的影响。在上述股票价格预测例子中,如果出现一次由于特殊事件导致股价异常波动的情况(如突发重大利好消息使股价瞬间翻倍),这个异常值对应的平方误差会非常大,从而对模型的训练产生较大干扰,可能导致模型过度调整参数以拟合这个异常值,而忽略了其他正常数据的分布规律,使模型在整体数据上的泛化能力下降 。
3. Smooth L1 损失
Smooth L1 损失是一种结合了 L1 损失和 L2 损失优点的损失函数 。其数学表达式为:\( \mathrm{SmoothL1}(x,y) = \begin{cases} 0.5(x - y)^2 & \text{if } |x - y| \lt 1 \\ |x - y| - 0.5 & \text{otherwise} \end{cases} \)
其中\(x\)为预测值,\(y\)为真实值。当预测值与真实值之间的误差较小时(即\(|x - y| \lt 1\)),Smooth L1 损失的表现类似于 L2 损失,对误差进行平方处理,这样可以更细致地调整模型,使模型在误差较小时能够更精确地逼近真实值;而当误差较大时(\(|x - y| \geq 1\)),它的行为类似于 L1 损失,对误差进行线性处理,从而减少异常值对损失值的影响,提高模型的鲁棒性。
在目标检测任务中的 Bounding Box 回归问题里,Smooth L1 损失有着重要应用。在使用 Faster R-CNN 模型检测图像中的物体时,需要预测物体的边界框位置。由于图像中可能存在各种复杂的场景和物体,预测的边界框与真实边界框之间难免会存在误差。如果使用 L2 损失,当出现较大的预测误差(如检测到的物体位置与实际位置偏差较大)时,L2 损失会因为平方项的存在而产生非常大的梯度,导致模型参数更新幅度过大,训练过程不稳定。而如果使用 L1 损失,虽然对异常值有较好的鲁棒性,但在误差较小时,其梯度为常数,不利于模型进行精细的调整。Smooth L1 损失则很好地解决了这个问题,在误差较小时,它能像 L2 损失一样帮助模型进行精确调整;在误差较大时,又能像 L1 损失一样保持鲁棒性,从而使模型在 Bounding Box 回归任务中能够更准确、稳定地预测物体的位置 。
(二)分类损失函数
1. 交叉熵损失(Cross Entropy Loss)
交叉熵损失常用于多分类问题,用于衡量模型预测值与真实值之间的概率分布差异。其数学公式为\(L = -\sum_{i=1}^{n}y_i \log(\hat{y}_i)\) ,其中\(n\)为类别数,\(y_i\)表示真实标签中第\(i\)类的概率(通常为 0 或 1,代表样本是否属于该类别),\(\hat{y}_i\)表示模型预测样本属于第\(i\)类的概率。该公式的原理是通过计算真实概率分布与预测概率分布之间的差异,来指导模型的训练。当模型预测的概率分布与真实概率分布越接近时,交叉熵损失值越小,说明模型的预测效果越好。
在图像分类任务中,假设我们要将图像分为猫、狗、兔子三类。对于一张实际为猫的图像,模型预测它属于猫、狗、兔子的概率分别为 0.8、0.1、0.1,而真实标签中属于猫的概率为 1,属于狗和兔子的概率为 0。根据交叉熵损失公式计算:\(L = - (1 \times \log(0.8) + 0 \times \log(0.1) + 0 \times \log(0.1)) \approx - \log(0.8) \approx 0.223\) 。如果模型预测的概率分布更接近真实分布,如预测为猫的概率为 0.95,狗和兔子的概率分别为 0.025,那么交叉熵损失值会更小:\(L = - (1 \times \log(0.95) + 0 \times \log(0.025) + 0 \times \log(0.025)) \approx - \log(0.95) \approx 0.051\) ,表明模型的预测准确性更高。
在多分类任务中,通常会结合 softmax 激活函数使用交叉熵损失。softmax 函数将模型的原始输出转换为概率分布,其公式为\(\sigma(z)_j = \frac{e^{z_j}}{\sum_{k=1}^{K}e^{z_k}}\) ,其中\(z\)是模型的原始输出向量,\(K\)为类别数,\(\sigma(z)_j\)表示第\(j\)类的概率。通过 softmax 函数,模型的输出可以被解释为各个类别的概率,然后再通过交叉熵损失函数计算损失,从而实现多分类任务的训练和优化 。
2. 负对数似然损失(Negative Log - Likelihood Loss,NLLLoss)
负对数似然损失主要用于多分类问题,它要求网络最后一层使用 softmax 作为激活函数,将输出值映射为每个类别的概率值。其数学公式为\(L = - \sum_{i=1}^{n} \log(\hat{y}_{i,y_i})\) ,其中\(n\)是样本数量,\(\hat{y}_{i,j}\)表示模型预测第\(i\)个样本属于第\(j\)类的概率,\(y_i\)表示第\(i\)个样本的真实类别标签。该损失函数的特性是惩罚预测准确但概率不高的情况。
假设在一个文本分类任务中,要将文本分为体育、娱乐、科技三类。对于一篇属于体育类的文本,模型预测它属于体育类的概率为 0.6,属于娱乐类和科技类的概率分别为 0.2 和 0.2。根据负对数似然损失公式计算:\(L = - \log(0.6) \approx 0.511\) 。如果模型预测它属于体育类的概率提高到 0.9,那么损失值会变为:\(L = - \log(0.9) \approx 0.105\) ,可以看出,当模型预测正确类别的概率越高时,负对数似然损失值越小。这是因为负对数似然损失希望模型能够对正确的类别给出较高的预测概率,从而提高分类的准确性 。
3. 二元交叉熵损失(Binary Cross Entropy Loss,BCELoss)
二元交叉熵损失专门用于二分类任务,用于衡量预测概率与真实标签之间的差异。其数学公式为\(L = - [y \log(\hat{y}) + (1 - y) \log(1 - \hat{y})]\) ,其中\(y\)为真实标签(0 或 1),\(\hat{y}\)为模型预测的概率值(取值范围在 0 到 1 之间)。在二分类模型中,比如垃圾邮件检测任务,若一封邮件实际为垃圾邮件(\(y = 1\)),模型预测它是垃圾邮件的概率为 0.8(\(\hat{y} = 0.8\)),则根据公式计算损失值为:\(L = - [1 \times \log(0.8) + (1 - 1) \times \log(1 - 0.8)] = - \log(0.8) \approx 0.223\) ;若模型预测它是垃圾邮件的概率为 0.2(\(\hat{y} = 0.2\)),损失值则为:\(L = - [1 \times \log(0.2) + (1 - 1) \times \log(1 - 0.2)] = - \log(0.2) \approx 1.609\) ,可以看出预测概率与真实标签相差越大,损失值越大。
使用二元交叉熵损失时,模型的输入需为概率分布,因此通常在模型的最后一层接 Sigmoid 函数,将模型的输出转换为 0 到 1 之间的概率值,再通过二元交叉熵损失计算损失,以指导模型在二分类任务中的训练 。
4. BCEWithLogitsLoss
BCEWithLogitsLoss 将 Sigmoid 层集成到了二元交叉熵损失(BCELoss)中,其优势在于数值更稳定。它直接对模型的 logits(即未经过激活函数处理的原始输出)进行处理,无需在最后经过 Sigmoid 缩放。在深度学习中,当模型输出的 logits 值较大或较小时,直接计算 Sigmoid 函数可能会导致数值溢出或下溢问题,而 BCEWithLogitsLoss 通过将 Sigmoid 操作与交叉熵损失计算融合在一起,利用 log - sum - exp 技巧来实现数值稳定,避免了这些问题的发生 。在一个医疗诊断的二分类模型中,若模型输出的 logits 值很大,如果先计算 Sigmoid 函数再计算交叉熵损失,可能会因为 Sigmoid 函数在数值较大时的计算不稳定而导致结果不准确;而使用 BCEWithLogitsLoss 则可以直接对 logits 进行处理,得到更稳定、准确的损失值,从而更有效地指导模型的训练 。
(三)其他损失函数
1. KL 散度损失(KLDivLoss)
KL 散度损失,即 Kullback - Leibler 散度损失,用于衡量两个概率分布之间的差异 。其数学公式为\(D_{KL}(P||Q) = \sum_{i}P(i)\log\frac{P(i)}{Q(i)}\) ,其中\(P\)和\(Q\)是两个概率分布,\(P(i)\)和\(Q(i)\)分别表示分布\(P\)和\(Q\)在事件\(i\)上的概率。该损失函数的原理是计算用分布\(Q\)来近似分布\(P\)时所损失的信息,KL 散度值越小,说明两个分布越接近。
在连续输出分布空间上进行直接回归时,KL 散度损失有着重要应用。在变分自编码器(VAE)中,假设我们希望潜在变量的分布\(Q(z|x)\)尽可能接近先验分布\(P(z)\)(通常为先验高斯分布),就可以使用 KL 散度损失来衡量这两个分布之间的差异。通过最小化 KL 散度损失,VAE 能够学习到合理的潜在变量分布,使得从潜在空间中采样生成的数据具有与原始数据相似的特征 。假设\(P(z)\)是标准正态分布\(N(0, 1)\),\(Q(z|x)\)是模型预测的潜在变量分布,通过计算 KL 散度损失,模型可以不断调整参数,使\(Q(z|x)\)更接近\(P(z)\) ,从而实现对数据的有效编码和解码 。
2. Hinge Embedding 损失
Hinge Embedding 损失常用于分类问题,特别是在确定两个输入是否不同或相似的场景中。其数学公式为\(L = \max(0, 1 - y \cdot d)\) ,其中\(y\)为标签,取值为 1 或 - 1,\(d\)为两个输入之间的距离度量(如欧氏距离、余弦距离等)。当\(y = 1\)时,表示两个输入相似,此时希望\(d\)尽可能小,使得\(1 - y \cdot d\)小于等于 0,损失为 0;当\(y = - 1\)时,表示两个输入不同,此时希望\(d\)尽可能大,同样使\(1 - y \cdot d\)小于等于 0,损失为 0。
在学习非线性嵌入或半监督学习任务中,Hinge Embedding 损失也发挥着重要作用。在半监督学习中,有少量的标注数据和大量的未标注数据。通过 Hinge Embedding 损失,可以利用未标注数据中的相似性信息,将相似的数据点映射到相近的嵌入空间中,不同的数据点映射到较远的位置,从而学习到数据的内在结构,提高模型在分类任务中的性能 。例如,在图像相似性判断任务中,对于两张相似的图像,标签\(y = 1\),模型通过调整参数,使两张图像的特征向量之间的距离\(d\)减小,以满足\(1 - y \cdot d \leq 0\),降低损失值;对于两张不同的图像,标签\(y = - 1\),模型则使特征向量之间的距离\(d\)增大,达到同样的目的 。
3. Margin Ranking 损失
Margin Ranking 损失用于计算预测输入之间的相对距离,其数学公式为\(L = \max(0, -y \cdot (x_1 - x_2) + \text{margin})\) ,其中\(y\)为标签张量,取值为 1 或 - 1,\(x_1\)和\(x_2\)是两个输入的预测值,\(\text{margin}\)是一个超参数,表示期望的间隔。当\(y = 1\)时,意味着希望\(x_1\)大于\(x_2\),且\(x_1 - x_2\)至少要大于\(\text{margin}\),此时若\(-y \cdot (x_1 - x_2) + \text{margin} \leq 0\),损失为 0;当\(y = - 1\)时,希望\(x_2\)大于\(x_1\),且\(x_2 - x_1\)至少要大于\(\text{margin}\) ,同样若\(-y \cdot (x_1 - x_2) + \text{margin} \leq 0\),损失为 0。
在排名问题中,Margin Ranking 损失有着广泛的应用。在推荐系统中,需要对物品进行排序,以向用户展示最相关的物品。假设我们有两个物品\(A\)和\(B\),对于某个用户,若物品\(A\)与用户的相关性更高(标签\(y = 1\)),则希望模型预测的物品\(A\)的得分\(x_1\)大于物品\(B\)的得分\(x_2\),并且两者的差值要大于设定的\(\text{margin}\) ,通过最小化 Margin Ranking 损失,模型可以学习到正确的物品排序模式,提高推荐系统的准确性和性能 。
PyTorch 损失函数实战
(一)代码示例
1. 回归任务示例
在回归任务中,我们以简单的线性回归为例,展示如何使用 PyTorch 实现并选择合适的损失函数。假设我们有一组数据,用于预测房屋价格,房屋面积作为输入特征,价格作为目标值。
首先,导入必要的库:
import torch
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt
生成一些模拟数据,假设房屋面积在 100 到 300 平方米之间,价格与面积呈线性关系,并添加一些随机噪声:
# 生成数据
np.random.seed(42)
num_samples = 100
area = np.random.uniform(100, 300, num_samples).reshape(-1, 1)
price = 1000 * area + 50000 + np.random.normal(0, 10000, num_samples).reshape(-1, 1)
# 转换为PyTorch张量
area_tensor = torch.from_numpy(area).float()
price_tensor = torch.from_numpy(price).float()
定义线性回归模型,使用 PyTorch 的nn.Module类:
class LinearRegression(nn.Module):
def __init__(self):
super(LinearRegression, self).__init__()
self.linear = nn.Linear(1, 1) # 输入特征维度为1,输出维度为1
def forward(self, x):
return self.linear(x)
model = LinearRegression()
选择损失函数,这里我们分别展示使用 L1 损失(nn.L1Loss)和 L2 损失(nn.MSELoss)的情况,同时定义优化器,使用随机梯度下降(SGD)优化器:
# 使用L1损失
criterion_l1 = nn.L1Loss()
# 使用L2损失
criterion_l2 = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.001)
进行模型训练,分别使用 L1 损失和 L2 损失进行 100 个 epoch 的训练,并记录损失值变化:
# 使用L1损失训练
losses_l1 = []
for epoch in range(100):
optimizer.zero_grad()
outputs = model(area_tensor)
loss = criterion_l1(outputs, price_tensor)
loss.backward()
optimizer.step()
losses_l1.append(loss.item())
# 使用L2损失训练
model = LinearRegression() # 重新初始化模型
optimizer = torch.optim.SGD(model.parameters(), lr=0.001)
losses_l2 = []
for epoch in range(100):
optimizer.zero_grad()
outputs = model(area_tensor)
loss = criterion_l2(outputs, price_tensor)
loss.backward()
optimizer.step()
losses_l2.append(loss.item())
最后,输出损失值变化,并观察模型在回归任务中的表现:
# 绘制损失值变化曲线
plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
plt.plot(losses_l1, label='L1 Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('L1 Loss during Training')
plt.legend()
plt.subplot(1, 2, 2)
plt.plot(losses_l2, label='L2 Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('L2 Loss during Training')
plt.legend()
plt.show()
通过上述代码,我们可以看到模型在使用不同损失函数时的训练过程,L1 损失对异常值更鲁棒,L2 损失则对整体误差的平方和更敏感,通过观察损失值的变化,可以评估模型在回归任务中的性能表现 。
2. 分类任务示例
以图像分类任务为例,我们使用 CIFAR - 10 数据集,该数据集包含 10 个不同类别的 60000 张彩色图像。我们使用预训练的 ResNet18 模型,并对其进行微调以适应 CIFAR - 10 数据集的分类任务。
首先,导入必要的库和数据集:
import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim import lr_scheduler
from torchvision import datasets, models, transforms
import time
import os
import copy
对数据进行预处理,包括随机裁剪、水平翻转、转换为张量以及归一化:
# 数据预处理
data_transforms = {
'train': transforms.Compose([
transforms.RandomResizedCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
]),
'val': transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
]),
}
加载 CIFAR - 10 数据集,分别创建训练集和验证集的数据加载器:
data_dir = 'data/cifar10'
image_datasets = {x: datasets.CIFAR10(root=data_dir, train=(x == 'train'),
download=True, transform=data_transforms[x])
for x in ['train', 'val']}
dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=4,
shuffle=(x == 'train'), num_workers=4)
for x in ['train', 'val']}
dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'val']}
class_names = image_datasets['train'].classes
加载预训练的 ResNet18 模型,并修改其最后一层全连接层,以适应 10 个类别的分类任务:
# 加载预训练模型
model_ft = models.resnet18(pretrained=True)
num_ftrs = model_ft.fc.in_features
model_ft.fc = nn.Linear(num_ftrs, 10)
将模型移动到 GPU 上(如果可用),定义交叉熵损失函数和优化器,使用随机梯度下降(SGD)优化器,并设置学习率调整策略:
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model_ft = model_ft.to(device)
criterion = nn.CrossEntropyLoss()
optimizer_ft = optim.SGD(model_ft.parameters(), lr=0.001, momentum=0.9)
exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=7, gamma=0.1)
进行模型训练和验证,记录训练过程中的损失值和准确率:
def train_model(model, criterion, optimizer, scheduler, num_epochs=25):
since = time.time()
best_model_wts = copy.deepcopy(model.state_dict())
best_acc = 0.0
for epoch in range(num_epochs):
print(f'Epoch {epoch + 1}/{num_epochs}')
print('-' * 10)
# 每个epoch都有训练和验证阶段
for phase in ['train', 'val']:
if phase == 'train':
model.train() # 设置为训练模式
else:
model.eval() # 设置为评估模式
running_loss = 0.0
running_corrects = 0
# 迭代数据
for inputs, labels in dataloaders[phase]:
inputs = inputs.to(device)
labels = labels.to(device)
# 梯度清零
optimizer.zero_grad()
# 前向传播
# 如果是训练阶段,记录计算图
with torch.set_grad_enabled(phase == 'train'):
outputs = model(inputs)
_, preds = torch.max(outputs, 1)
loss = criterion(outputs, labels)
# 训练阶段,反向传播和优化
if phase == 'train':
loss.backward()
optimizer.step()
scheduler.step()
# 统计损失和正确预测的数量
running_loss += loss.item() * inputs.size(0)
running_corrects += torch.sum(preds == labels.data)
epoch_loss = running_loss / dataset_sizes[phase]
epoch_acc = running_corrects.double() / dataset_sizes[phase]
print(f'{phase} Loss: {epoch_loss:.4f} Acc: {epoch_acc:.4f}')
# 保存最好的模型
if phase == 'val' and epoch_acc > best_acc:
best_acc = epoch_acc
best_model_wts = copy.deepcopy(model.state_dict())
time_elapsed = time.time() - since
print(f'Training complete in {time_elapsed // 60:.0f}m {time_elapsed % 60:.0f}s')
print(f'Best val Acc: {best_acc:4f}')
# 加载最好的模型权重
model.load_state_dict(best_model_wts)
return model
调用训练函数,开始训练模型:
model_ft = train_model(model_ft, criterion, optimizer_ft, exp_lr_scheduler, num_epochs=25)
通过上述代码,我们展示了在图像分类任务中,如何使用预训练模型、定义交叉熵损失函数和优化器,以及如何进行模型的训练和验证,通过观察训练过程中的损失值和准确率变化,可以评估模型在分类任务中的性能 。
(二)参数调整与优化
损失函数的参数对模型训练有着重要影响,以交叉熵损失函数(nn.CrossEntropyLoss)为例,它有weight和reduction等参数 。weight参数用于为每个类别分配权重,当数据集存在类别不平衡问题时,通过调整weight可以使模型更加关注样本较少的类别,从而提高模型在这些类别上的性能。假设在一个图像分类任务中,类别 A 有 1000 个样本,类别 B 只有 100 个样本,为了使模型对类别 B 也能有较好的分类效果,可以将类别 B 的权重设置得比类别 A 高,如weight = torch.tensor([1.0, 5.0]) ,这样模型在计算损失时,会对类别 B 的分类错误给予更大的惩罚,促使模型更好地学习类别 B 的特征 。
reduction参数则指定了损失的计算方式,有'none'、'mean'和'sum'三种取值。'none'表示不进行降维,返回每个样本的损失值,这在需要对每个样本的损失进行单独分析时很有用,如在样本选择或异常值检测中;'mean'会对损失进行平均,返回一个标量值,这是最常用的设置,适用于大多数模型训练场景,它可以使模型在训练时考虑整个批次样本的平均损失情况;'sum'则返回所有样本损失的总和,在某些特定场景下,如需要计算整个数据集的总损失时会用到 。在训练一个多分类模型时,如果使用reduction='mean',模型在每次反向传播时会根据平均损失来更新参数,而如果使用reduction='sum',则会根据总损失来更新参数,这两种方式可能会导致模型训练的收敛速度和最终性能有所不同 。
在优化损失函数时,结合不同的优化算法可以显著提高模型性能。随机梯度下降(SGD)是一种常用的优化算法,它在每次迭代中随机选择一个小批量样本计算梯度并更新参数,具有计算效率高、易于实现的优点,但也存在收敛速度较慢、容易陷入局部最优解的问题 。在训练一个简单的神经网络时,使用 SGD 优化器,学习率设置为 0.01,可能需要较多的迭代次数才能使损失值收敛到一个较低的水平。而 Adam 优化算法则结合了动量法和自适应学习率的思想,它能够自适应地调整每个参数的学习率,在处理大规模数据集和复杂模型时表现出色,具有更快的收敛速度和更好的稳定性 。在训练一个深度卷积神经网络时,使用 Adam 优化器,模型往往能够在较少的迭代次数内达到较好的性能,且不容易陷入局部最优解。在实际应用中,需要根据数据集的特点、模型的复杂度以及计算资源等因素,选择合适的优化算法和损失函数参数,以实现模型性能的最优化 。
损失函数的选择与应用技巧
(一)根据任务类型选择损失函数
在深度学习中,不同的任务类型需要选择与之相适配的损失函数,以确保模型能够准确地学习和预测。回归任务旨在预测连续的数值,如房价预测、股票价格预测等,对数值准确性要求较高 。在这类任务中,L1 损失(平均绝对误差,MAE)和 L2 损失(均方误差,MSE)是常用的选择。L1 损失计算预测值与真实值之间绝对差的平均值,对异常值具有较好的鲁棒性,因为它不会像 L2 损失那样对异常值进行平方放大,所以在数据中存在异常值时,L1 损失能使模型更专注于学习大多数正常数据的规律,从而提高模型的稳定性和泛化能力 。在一个包含房屋面积与房价关系的数据集里,如果存在个别面积异常大但房价并非按比例增长的豪宅数据(异常值),使用 L1 损失训练模型时,这些异常值对模型参数更新的影响相对较小 。
而 L2 损失通过计算预测值与真实值之间平方差的平均值来衡量误差,其梯度连续且可导,便于使用梯度下降等优化算法进行模型训练,在大部分回归问题中应用广泛。在训练神经网络时,通过反向传播算法计算 L2 损失的梯度,能够快速准确地更新模型参数,使模型朝着减小损失值的方向优化 。但 L2 损失对异常值比较敏感,因为误差的平方会放大异常值的影响,可能导致模型过度调整参数以拟合异常值,而忽略了其他正常数据的分布规律,使模型在整体数据上的泛化能力下降 。
分类任务的目标是将输入数据划分到不同的类别中,如图像分类、文本分类等。交叉熵损失(Cross Entropy Loss)是分类任务中极为常用的损失函数,它通过衡量真实概率分布与预测概率分布之间的差异,来指导模型的训练,当模型预测的概率分布与真实概率分布越接近时,交叉熵损失值越小。在多分类问题中,通常会结合 softmax 激活函数使用交叉熵损失,softmax 函数将模型的原始输出转换为概率分布,使得模型的输出可以被解释为各个类别的概率,然后再通过交叉熵损失函数计算损失,从而实现多分类任务的训练和优化 。
负对数似然损失(Negative Log - Likelihood Loss,NLLLoss)也常用于多分类问题,它要求网络最后一层使用 softmax 作为激活函数,将输出值映射为每个类别的概率值,该损失函数惩罚预测准确但概率不高的情况,希望模型能够对正确的类别给出较高的预测概率,从而提高分类的准确性 。二元交叉熵损失(Binary Cross Entropy Loss,BCELoss)则专门用于二分类任务,用于衡量预测概率与真实标签之间的差异,使用时通常在模型的最后一层接 Sigmoid 函数,将模型的输出转换为 0 到 1 之间的概率值,再通过二元交叉熵损失计算损失,以指导模型在二分类任务中的训练 。
排序任务在信息检索、推荐系统等领域有着广泛应用,其目的是使模型预测的排序结果尽可能接近真实的排序。Margin Ranking 损失常用于排序问题,它通过计算预测输入之间的相对距离,来指导模型学习正确的排序模式。在推荐系统中,对于用户的物品推荐排序,Margin Ranking 损失可以使模型学习到用户对不同物品的偏好顺序,将用户可能更感兴趣的物品排在前面,从而提高推荐系统的准确性和性能 。
(二)处理不平衡数据
在分类任务中,数据不平衡是一个常见的问题,即不同类别的样本数量存在较大差异。这种情况下,模型往往会偏向于样本数量较多的类别,而对样本数量较少的类别表现不佳。为了解决数据不平衡问题,可以使用加权损失函数,通过为不同类别设置不同的权重,来调整模型对于不同类别的关注程度。在一个图像分类任务中,假设类别 A 有 1000 个样本,类别 B 只有 100 个样本,为了使模型对类别 B 也能有较好的分类效果,可以将类别 B 的权重设置得比类别 A 高 ,这样模型在计算损失时,会对类别 B 的分类错误给予更大的惩罚,促使模型更好地学习类别 B 的特征 。
在 PyTorch 中,对于二元分类问题,torch.nn.BCEWithLogitsLoss函数可以通过向损失函数添加weight参数来处理不平衡数据 。weight参数是一个大小为 2(对应正类和负类)的张量,通过设置不同的权重值,为正类和负类分配不同的权重 。还可以使用pos_weight参数,它是一个标量,表示正类的权重,相当于将weight参数设置为[1, pos_weight],其中负类的权重为 1 。如果同时指定weight和pos_weight参数,pos_weight参数优先于正类的权重 。
对于多分类问题,在使用torch.nn.CrossEntropyLoss时,可以通过设置weight参数来为每个类别分配权重 。类别权重的计算方式可以根据具体情况而定,一种常见的方法是类别 i 的权重 = 样本总数 / (类别 i 中的样本数 * 类别数) ,通过这种方式,样本数量较少的类别会获得较高的权重,从而使模型更加关注这些类别 。还可以结合label_smoothing参数来使用,label_smoothing用于平滑 one - hot 编码目标值,以鼓励模型对其预测不太自信并防止过度拟合训练数据 。weight参数主要用于处理数据不平衡问题,而label_smoothing参数主要用于防止过拟合,两者可以一起使用来解决多类分类问题中的类不平衡和过度拟合问题 。
总结与展望
(一)总结
在深度学习的广阔领域中,PyTorch 作为一款强大的深度学习框架,为我们提供了丰富多样的损失函数,这些损失函数在模型训练中扮演着至关重要的角色。从回归任务中的 L1 损失、L2 损失和 Smooth L1 损失,到分类任务里的交叉熵损失、负对数似然损失、二元交叉熵损失以及 BCEWithLogitsLoss,再到其他如 KL 散度损失、Hinge Embedding 损失和 Margin Ranking 损失等,每一种损失函数都有其独特的原理、特点和适用场景 。
在回归任务中,L1 损失对异常值具有较好的鲁棒性,能够使模型更专注于学习正常数据的规律,从而提高模型的稳定性和泛化能力;L2 损失则由于其良好的数学性质,便于使用梯度下降等优化算法进行模型训练,但对异常值比较敏感。Smooth L1 损失巧妙地结合了两者的优点,在误差较小时像 L2 损失一样帮助模型进行精确调整,在误差较大时又像 L1 损失一样保持鲁棒性 。
在分类任务方面,交叉熵损失通过衡量真实概率分布与预测概率分布之间的差异,成为多分类任务中极为常用的损失函数,通常与 softmax 激活函数配合使用;负对数似然损失主要用于多分类问题,惩罚预测准确但概率不高的情况;二元交叉熵损失专门针对二分类任务,衡量预测概率与真实标签之间的差异;BCEWithLogitsLoss 将 Sigmoid 层集成到二元交叉熵损失中,实现了数值稳定 。
在实际应用中,选择合适的损失函数是至关重要的。我们需要根据任务类型来选择损失函数,回归任务优先考虑 L1 损失、L2 损失等,分类任务则根据具体情况选择交叉熵损失等相应的损失函数。当面对数据不平衡问题时,可以采用加权损失函数等方法来处理,以提高模型在少数类上的性能。还需要对损失函数的参数进行合理调整,并结合不同的优化算法,以实现模型性能的最优化 。
(二)展望
随着深度学习技术的不断发展,损失函数的研究和应用也将迎来新的突破。未来,针对复杂任务设计更有效的损失函数将成为一个重要的研究方向。在多模态数据融合的任务中,如同时处理图像和文本信息的任务,现有的损失函数可能无法充分挖掘多模态数据之间的内在联系,因此需要设计专门的损失函数来更好地融合和利用这些数据,提高模型的性能 。
结合自适应优化算法实现更智能的模型训练也是未来的发展趋势之一。自适应优化算法能够根据模型的训练状态和数据特点,自动调整优化参数,如学习率等。将自适应优化算法与损失函数相结合,可以使模型在训练过程中更加智能地调整参数,提高训练效率和模型的泛化能力。在训练大规模的神经网络时,自适应优化算法可以根据不同层的参数更新情况,动态地调整学习率,避免某些层的参数更新过快或过慢,从而提高模型的整体性能 。
鼓励读者在深度学习实践中不断探索和创新损失函数的应用。随着深度学习应用场景的不断拓展,新的问题和挑战也会不断涌现。读者可以根据具体的问题需求,尝试对现有损失函数进行改进或设计全新的损失函数,以推动深度学习技术在各个领域的更广泛应用和发展 。相信在未来,损失函数的研究和应用将为深度学习的发展注入新的活力,带来更多的创新和突破 。
更多推荐


所有评论(0)