10 神经网络的损失函数

一、机器学习中的优化思想

在之前的学习中,我们建立神经网络时总是先设定好 与 的值(或者由我们调用的PyTorch类帮助我们随机生成权重向量 ),接着通过加和求出 ,再在 上嵌套sigmoid或者softmax函数,最终获得神经网络的输出。我们的代码及计算流程,总是从神经网络的左侧向右侧计算的。之前我们提到过,这是神经网络的正向传播过程。但很明显,这并不是神经网络算法的全流程,这个流程虽然可以输出预测结果,但却无法保证神经网络的输出结果与真实值接近。

image.png
1690627733188.jpg

之前我们在线性回归上走过这个全流程。对线性回归,我们的损失函数是SSE,优化算法是最小二乘法和梯度下降法,两者都是对机器学习来说非常重要的优化算法。但遗憾的是,最小二乘法作为入门级优化算法,有较多的假设和先决条件,不足以应对神经网络需要被应用的各种复杂环境。梯度下降法应用广泛,不过也有很多问题需要改进。接下来,我将主要以分类深层神经网络为例来介绍神经网络中所使用的入门级损失函数及优化算法。

二、回归:误差平方和SSE

1690627896485.png
from torch.nn import MSELoss #类
yhat = torch.randn(size=(50,),dtype=torch.float32)
y = torch.randn(size=(50,),dtype=torch.float32)
criterion =MSELoss() #实例化
loss = criterion(yhat,y)
loss #没有设置随机数种子,所以每次运行的数字都会不一致
#在MSELoss中有重要的参数,reduction
#当reduction = "mean" (默认也是mean),则输出MSE
#当reduction = "sum",则输出SSE
criterion = MSELoss(reduction = "mean") #实例化
criterion(yhat,y)
criterion = MSELoss(reduction = "sum")
criterion(yhat,y)

三、二分类交叉熵损失函数

1690708203573.jpg

1 极大似然估计求解二分类交叉熵损失

1690709832499.png
1690709995795.png
1690710644993.png
1690710849608.jpg

现在,我们已经将模型拟合中的“最小化损失”问题,转换成了对函数求解极值的问题。这就是一个,基于逻辑回归的返回值 的概率性质以及极大似然估计得出的损失函数。在这个函数上,我们只要追求最小值,就能让模型在训练数据上的拟合效果最好,损失最低。
在极大似然估计中,我们只要在对数似然函数上对权重 求导,再令导数为0,就可以求解出最合适的,但是对于像交叉熵这样复杂的损失函数,加上神经网络中复杂的权重组合,令所有权重的导数为0并一个个求解方程的难度很大。因此我们要使用优化算法,这部分我们下一章展开来聊。

2 用tensor实现二分类交叉熵损失

现在,让我们在PyTorch中来实现二分类交叉熵损失函数。首先使用基本的tensor方法来试试看,以加深我们对二分类交叉熵损失的印象:

import torch
import time
N = 3*pow(10,3)
torch.random.manual_seed(420)
X = torch.rand((N,4),dtype=torch.float32)
w = torch.rand((4,1),dtype=torch.float32,requires_grad=True)
y = torch.randint(low=0,high=2,size=(N,1),dtype=torch.float32)
zhat = torch.mm(X,w)
sigma = torch.sigmoid(zhat)
Loss = -(1/N)*torch.sum((1-y)*torch.log(1-sigma)+y*torch.log(sigma))

注意,在写损失函数这样的复杂函数时,除了普通的加减乘除以外的全部计算,都要使用torch中的函数,因为tensor的运算速度是远远超过普通Python代码,甚至是NumPy的。你可以试着比较在样本量为300W时,以下两行代码运行的时间差异:

#你可以试着比较在样本量为300W时,以下两行代码运行的时间差异。这段代码不需要GPU。
#如果你的电脑内存或计算资源有限,可以试着将样本量调小为30W或3W
N = 3*pow(10,6)
torch.random.manual_seed(420)
X = torch.rand((N,4),dtype=torch.float32)
w = torch.rand((4,1),dtype=torch.float32,requires_grad=True)
y = torch.randint(low=0,high=2,size=(N,1),dtype=torch.float32)
zhat = torch.mm(X,w)
sigma = torch.sigmoid(zhat)
start = time.time()
L1 = -(1/N)*torch.sum((1-y)*torch.log(1-sigma)+y*torch.log(sigma))
now = time.time() #seconds
print(now - start)
start = time.time()
L2 = -(1/N)*sum((1-y)*torch.log(1-sigma)+y*torch.log(sigma))
now = time.time() #seconds
print(now - start)

从运行结果来看,除了加减乘除,我们应该尽量避免使用任何Python原生的计算方法。如果可能的话,让PyTorch处理一切。

3 用PyTorch中的类实现二分类交叉熵损失

在PyTorch当中,我们有多种方式可以调用二分类交叉熵损失函数。

1690722574869.png
import torch.nn as nn
#调用nn模块下的类
criterion = nn.BCELoss() #实例化
loss = criterion(sigma,y)
loss
criterion2 = nn.BCEWithLogitsLoss() #实例化
loss = criterion2(zhat,y)
loss

可以看出,两个类的结果是一致的。根据PyTorch官方的公告,他们更推荐使用BCEWithLogitsLoss这个内置了sigmoid函数的类。内置的sigmoid函数可以让精度问题被缩小(因为将指数运算包含在了内部),以维持算法运行时的稳定性,即是说当数据量变大、数据本身也变大时,BCELoss类产生的结果可能有精度问题。所以,当我们的输出层使用sigmoid函数时,我们就可以使BCEWithLogitsLoss作为损失函数。

与MSELoss相同,二分类交叉熵的类们也有参数reduction,默认是”mean“,表示求解所有样本平均的损失,也可换为”sum”,要求输出整体的损失。以及,还可以使用选项“none”,表示不对损失结果做任何聚合运算,直接输出每个样本对应的损失矩阵。

criterion2 = nn.BCEWithLogitsLoss(reduction = "mean")
loss = criterion2(zhat,y)
loss
criterion2 = nn.BCEWithLogitsLoss(reduction = "sum")
loss = criterion2(zhat,y)
loss
criterion2 = nn.BCEWithLogitsLoss(reduction = "none")
loss = criterion2(zhat,y)
loss

第二种方法很少用,我们了解一下即可:

方法2:functional库中的计算函数
function F.binary_cross_entropy_with_logits
function F.binary_cross_entropy

和nn中的类们相似,名称中带有Logits的是内置了sigmoid功能的函数,没有带Logits的,是只包含交叉熵损失的函数。对于含有sigmoid功能的函数,我们需要的输入是zhat与标签,不含sigmoid的函数我们则需要输入sigma与标签。同样的,这两个函数对输入有严格的要求,输入的预测值必须与标签结构一致、数据类型一致。我们来看看他们的运行结果:

from torch.nn import functional as F
#直接调用functional库中的计算函数
F.binary_cross_entropy_with_logits(zhat,y)
F.binary_cross_entropy(sigma,y)

在这里,两个函数的运行结果是一致的。同样的,PyTorch官方推荐的是内置sigmoid功能的函数binary_cross_entropy_with_logits。通常来说,我们都使用类,不使用函数。虽然代码会因此变得稍稍有点复杂,但为了代码的稳定性与日后维护,使用类是更好的选择。当然,在进行代码演示和快速测算的时候,使用函数或者类都没有问题。

四、多分类交叉熵损失函数

1 由二分类推广到多分类

1690722948342.png
1690723006593.png
1690723095321.png
1690723138975.png
1690724838600.png

2 用PyTorch实现多分类交叉熵损失

在PyTorch中实现交叉熵函数的时候,有两种办法:调用logsoftmax和NLLLoss实现

import torch
import torch.nn as nn
N = 3*pow(10,2)
torch.random.manual_seed(420)
X = torch.rand((N,4),dtype=torch.float32)
w = torch.rand((4,3),dtype=torch.float32,requires_grad=True)

#定义y时应该怎么做?应该设置为矩阵吗?
y = torch.randint(low=0,high=3,size=(N,),dtype=torch.float32)
zhat = torch.mm(X,w)
#从这里开始调用softmax和NLLLoss
logsm = nn.LogSoftmax(dim=1) #实例化
logsigma = logsm(zhat)
criterion = nn.NLLLoss() #实例化
#由于交叉熵损失需要将标签转化为独热形式,因此不接受浮点数作为标签的输入
#对NLLLoss而言,需要输入logsigma
criterion(logsigma,y.long())

更加简便的方法是:
直接调用CrossEntropyLoss

criterion = nn.CrossEntropyLoss()
#对打包好的CorssEnrtopyLoss而言,只需要输入zhat
criterion(zhat,y.long())

可以发现,两种输出方法得到的损失函数结果是一致的。与其他损失函数一致,CrossEntropyLoss也有参数reduction,可以设置为mean、sum以及None,大家可以自行尝试其代码并查看返回结果。
无论时二分类还是多分类,PyTorch都提供了包含输出层激活函数和不包含输出层激活函数的类两种选择。在实际神经网络建模中,类可以被放入定义好的Model类中去构建神经网络的结构,因此是否包含激活函数,就需要由用户来自行选择。

  • 重视展示网络结构和灵活性,应该使用不包含输出层激活函数的类

通常在Model类中,init中层的数量与forward函数中对应的激活函数的数量是一致的,如果我们使用内置sigmoid/logsoftmax功能的类来计算损失函数,forward函数在定义时就会少一层(输出层),
网络结构展示就不够简单明了,对于结构复杂的网络而言,结构清晰就更为重要。同时,如果激活函数是单独写的,要修改激活函数就变得很容易,如果混在损失函数中,要修改激活函数时就得改掉整个损失函数的代码,不利于维护。

  • 重视稳定性和运算精度,使用包含输出层激活函数的类

如果在一个Model中,很长时间我们都不会修改输出层的激活函数,并且模型的稳定运行更为要紧,我
们就使用内置了激活函数的类来计算损失函数。同时,就像之前提到的,内置激活函数可以帮助我们推升运算的精度。

因此,选择哪种损失函数的实现方式,最终还要看我们的需求。
有了损失函数,我们终于要开始进行求解了。下一部分我们来讲解神经网络的入门级优化算法:小批量随机梯度下降。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,110评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,443评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,474评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,881评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,902评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,698评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,418评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,332评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,796评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,968评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,110评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,792评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,455评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,003评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,130评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,348评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,047评论 2 355

推荐阅读更多精彩内容