1.第六天今天是該系列的第六篇文章,我們通過(guò)上一次的學(xué)習(xí),基本上完成了模型模塊,也學(xué)會(huì)了如何搭建網(wǎng)絡(luò)模型,下面進(jìn)入損失函數(shù)的模塊,但是在這之前,先來(lái)看看常用的權(quán)值初始化方法,這是網(wǎng)絡(luò)模型搭建好之后的一個(gè)非常重要的步驟,正確的權(quán)值初始化可以加速模型的收斂,不恰當(dāng)?shù)臋?quán)值初始化導(dǎo)致輸出層的輸出過(guò)大或者過(guò)小,最終導(dǎo)致梯度爆炸或者消失,使得模型無(wú)法訓(xùn)練,這里會(huì)深層剖析權(quán)重初始化的重要性,會(huì)學(xué)習(xí)適用于飽和激活函數(shù) tanh 等的 Xavier 初始化方法和非飽和激活函數(shù) relu 等的 Kaiming 初始化方法(這些在實(shí)踐中非常常用,但是有時(shí)候并不知道用這個(gè)背后的原因),學(xué)習(xí)完了這個(gè),然后再正式整理關(guān)于各種損失函數(shù)的一些知識(shí),這里會(huì)學(xué)習(xí) 18 種損失函數(shù)的原理及使用,最后會(huì)對(duì)這 18 種損失函數(shù)梳理一下,得知道什么樣的任務(wù)有哪些損失函數(shù)可用。通過(guò)這篇文章,可以打通權(quán)值初始化和損失函數(shù)的任督二脈。 「大綱如下:」
下面依然是一個(gè)思維導(dǎo)圖把知識(shí)拎起來(lái),方便后面的速查: 2.權(quán)值初始化在網(wǎng)絡(luò)模型搭建完成之后,對(duì)網(wǎng)絡(luò)中的權(quán)重進(jìn)行合適的初始化是非常重要的一個(gè)步驟, 初始化好了,比如正好初始化到模型的最優(yōu)解附近,那么模型訓(xùn)練起來(lái)速度也會(huì)非常的快, 但如果初始化不好,離最優(yōu)解很遠(yuǎn),那么模型就需要更多次迭代,有時(shí)候還會(huì)引發(fā)梯度消失和爆炸現(xiàn)象, 所以正確的權(quán)值初始化還是非常重要的,下面我們就來(lái)看看常用的權(quán)值初始化的方法,但是在這之前,先了解一下什么是梯度消失和梯度爆炸現(xiàn)象。 2.1 梯度的消失和爆炸我們以上一篇的一個(gè)圖來(lái)看一下梯度消失和爆炸現(xiàn)象 看上面這個(gè)圖, 假設(shè)我們要算的梯度,我們根據(jù)鏈?zhǔn)椒▌t應(yīng)該是下面這樣: 這樣我們就會(huì)發(fā)現(xiàn) 梯度的求解過(guò)程中會(huì)用到上一層神經(jīng)元的輸出值 ,那么這時(shí)候,如果 的輸出值非常小,那么 的梯度也會(huì)非常小,這時(shí)候就有可能造成梯度消失的現(xiàn)象,尤其是當(dāng)網(wǎng)絡(luò)層很多的時(shí)候,這種連乘一個(gè)數(shù)非常小,就會(huì)導(dǎo)致越乘越小,后面的層里面就容易發(fā)現(xiàn)梯度消失。而當(dāng) 非常大的時(shí)候,當(dāng)然也就會(huì)發(fā)生梯度爆炸。 一旦發(fā)生梯度消失或者爆炸,就會(huì)導(dǎo)致模型無(wú)法訓(xùn)練,而如果想避免這個(gè)現(xiàn)象,我們就得「控制網(wǎng)絡(luò)輸出層的一個(gè)尺度范圍,也就是不能讓它太大或者太小」。那么我們?cè)趺纯刂七@個(gè)網(wǎng)絡(luò)輸出層的尺度呢?那就是通過(guò)合理的初始化權(quán)重了。我們下面從代碼切入,進(jìn)行理解吧: 我們建立一個(gè) 100 層的多層感知機(jī),每一層 256 個(gè)神經(jīng)元,我們使用上面學(xué)習(xí)的 ModuleList 進(jìn)行建立: class MLP(nn.Module): def __init__(self, neural_num, layers): super(MLP, self).__init__() self.linears = nn.ModuleList([nn.Linear(neural_num, neural_num, bias=False) for i in range(layers)]) self.neural_num = neural_num # 正向傳播 def forward(self, x): for (i, linear) in enumerate(self.linears): x = linear(x) print('layer:{}, std:{}'.format(i, x.std())) if torch.isnan(x.std()): print('output is nan in {} layers'.format(i)) break return x # 權(quán)值初始化,我們這里使用標(biāo)準(zhǔn)正態(tài) def initialize(self): for m in self.modules(): if isinstance(m, nn.Linear): nn.init.normal_(m.weight.data) # normal: mean=0, std=1# 用一下網(wǎng)絡(luò)layer_nums = 100neural_nums = 256batch_size = 16net = MLP(neural_nums, layer_nums)net.initialize()inputs = torch.randn((batch_size, neural_nums)) # normal: mean=0, std=1output = net(inputs)print(output) 這個(gè)結(jié)果可以發(fā)現(xiàn),在 35 層的時(shí)候,神經(jīng)網(wǎng)絡(luò)的輸出就成了 nan,這說(shuō)明網(wǎng)絡(luò)出現(xiàn)了問(wèn)題,導(dǎo)致后面輸出的值太大了,當(dāng)然我們還沒(méi)有反向傳播,根據(jù)上面的權(quán)重推導(dǎo)的公式,后面的這些如果為 nan 了之后,反向傳播的時(shí)候,這些權(quán)重根本就沒(méi)法進(jìn)行更新,會(huì)發(fā)生梯度爆炸現(xiàn)象。 這就是有時(shí)候我們?cè)谟?xùn)練網(wǎng)絡(luò)的時(shí)候,最后結(jié)果全是 nan 的原因,這往往可能是權(quán)重初始化的不當(dāng)導(dǎo)致的。 可是,這是為啥呢?為啥我初始化權(quán)重不當(dāng)了會(huì)影響到網(wǎng)絡(luò)的輸出呢?剛才不是還說(shuō)是網(wǎng)絡(luò)的輸出影響的權(quán)重梯度嗎?那是反向傳播的時(shí)候,而正向傳播的時(shí)候,權(quán)重肯定要影響到每一層的輸出啊。我們推導(dǎo)一下上面這個(gè)過(guò)程中每一層輸出的方差是如何變化的就明白了。 下面先進(jìn)行一個(gè)方差的公式推導(dǎo): 借助三個(gè)基本公式: 那么 若 ,則 好了, 那么我們看看神經(jīng)網(wǎng)絡(luò)里面每一層輸出的方差計(jì)算: 還是這個(gè)網(wǎng)絡(luò),我們看第一層第一個(gè)神經(jīng)元的方差應(yīng)該怎么算: 這里我們的輸入數(shù)據(jù)和權(quán)重都初始化的均值為 0,方差為 1 的標(biāo)準(zhǔn)正態(tài)。這樣經(jīng)過(guò)一個(gè)網(wǎng)絡(luò)層就發(fā)現(xiàn)方差擴(kuò)大了 n 倍。而我們上面用了 100 個(gè)網(wǎng)絡(luò)層,那么這個(gè)方差會(huì)指數(shù)增長(zhǎng),所以我們后面才會(huì)出現(xiàn)輸出層方差 nan 的情況。 那么我們?cè)趺唇鉀Q這種情況呢?那很簡(jiǎn)單,讓網(wǎng)絡(luò)層的輸出方差保持尺度不變就可以了,可是怎么做呢?分析一下網(wǎng)絡(luò)層的輸出方差: 我們發(fā)現(xiàn),每一層的輸出方差會(huì)和每一層神經(jīng)元個(gè)數(shù),前一層輸出方差和本層權(quán)重的方差有關(guān),如果想讓方差的尺度不變,因?yàn)檫@里都是連乘,有個(gè)方法就是讓每一層輸出方差都是 1,也就是 ,這樣后面多層相乘,那么也不會(huì)變這個(gè)尺度。怎么做呢?首先,每一層神經(jīng)元個(gè)數(shù)沒(méi)法變,而前一層輸出方差是 1 又涉及到了方差, 所以這里能變得就是權(quán)重的方差: 這樣,我們權(quán)重在初識(shí)的時(shí)候,方差如果是 的話,每一層的輸入方差都是 1,這樣方差就不會(huì)導(dǎo)致 nan 的情況發(fā)生了。在上面代碼中改一句話:
這樣就會(huì)發(fā)現(xiàn),不會(huì)出現(xiàn)nan的情況了: 「所以我們只要采用恰當(dāng)?shù)臋?quán)值初始化方法,就可以實(shí)現(xiàn)多層神經(jīng)網(wǎng)絡(luò)的輸出值的尺度維持在一定范圍內(nèi), 這樣在反向傳播的時(shí)候,就有利于緩解梯度消失或者爆炸現(xiàn)象的發(fā)生」 當(dāng)然,上面的網(wǎng)絡(luò)只是一個(gè)線性網(wǎng)絡(luò),在實(shí)際中我們還得考慮激活函數(shù)的存在,我們從上面的前向傳播中加一個(gè)激活函數(shù)再看一下結(jié)果: 那么,具有激活函數(shù)的時(shí)候,怎么對(duì)權(quán)重進(jìn)行初始化呢? 2.2 Xavier初始化「方差一致性」:保持?jǐn)?shù)據(jù)尺度范圍維持在恰當(dāng)范圍,通常方差為 1。如果有了激活函數(shù)之后,我們應(yīng)該怎么對(duì)權(quán)重初始化呢? 2010 年 Xavier 發(fā)表了一篇文章,詳細(xì)探討了如果有激活函數(shù)的時(shí)候,如何進(jìn)行權(quán)重初始化,當(dāng)然它也是運(yùn)用的方差一致性原則,但是它這里「考慮的是飽和激活函數(shù)」,如 sigmoid,tanh。文章中有個(gè)這樣的公式推導(dǎo),從而得到我們權(quán)重的方差: 這里的 、 分別指的輸入層和輸出層神經(jīng)元個(gè)數(shù)。通常 Xavier 采用均勻分布對(duì)權(quán)重進(jìn)行初始化,那么我們可以推導(dǎo)一下均勻分布的上限和下限: 我們讓上面的兩個(gè) 相等就會(huì)得到 這就是 Xavier 初始化方法,那么在代碼中怎么用呢?還是上面的那個(gè)代碼例子,我們?cè)趨?shù)初始化里面用 Xavier 初始化權(quán)重: def initialize(self): for m in self.modules(): if isinstance(m, nn.Linear): # Xavier初始化權(quán)重 tanh_gain = nn.init.calculate_gain('tanh') nn.init.xavier_uniform_(m.weight.data, gain=tanh_gain) 這里面用到了一個(gè)函數(shù)nn.init.calculate_gain(nonlinearity, param=None)這個(gè)函數(shù)的作用是計(jì)算激活函數(shù)的「方差變化尺度」,怎么理解這個(gè)方差變化尺度呢?其實(shí)就是輸入數(shù)據(jù)的方差除以經(jīng)過(guò)激活函數(shù)之后的輸出數(shù)據(jù)的方差。nonlinearity 表示激活函數(shù)的名稱(chēng),如tanh。param 表示激活函數(shù)的參數(shù),如 Leaky ReLU 的negative_slop。(這里不用也行,但得知道這個(gè)方法)。這時(shí)候再來(lái)看一下最后的結(jié)果: 「所以Xavier權(quán)重初始化,有利于緩解帶有sigmoid,tanh的這樣的飽和激活函數(shù)的神經(jīng)網(wǎng)絡(luò)的梯度消失和爆炸現(xiàn)象?!?/span> 但是,2012年 AlexNet 出現(xiàn)之后,非飽和函數(shù) relu 也用到了神經(jīng)網(wǎng)絡(luò)中,而 Xavier 初始化對(duì)于 relu 就不好使了,不信我們看看: 2.3 Kaiming 初始化這個(gè)依然是考慮的方差一致性原則,「針對(duì)的激活函數(shù)是 ReLU 及其變種」。經(jīng)過(guò)公示推導(dǎo),最后的權(quán)值標(biāo)準(zhǔn)差是這樣的: 那么 Kaiming 初始化權(quán)重方法怎么用呢?
我們可以看一下結(jié)果: 所以從上面的學(xué)習(xí)中,我們對(duì)權(quán)值的初始化有了清晰的認(rèn)識(shí),發(fā)現(xiàn)了權(quán)重初始化對(duì)于模型的重要性,不好的權(quán)重初始化方法會(huì)引起輸出層的輸出值過(guò)大過(guò)小,從而引發(fā)梯度的消失或者爆炸,最終導(dǎo)致我們的模型無(wú)法訓(xùn)練。所以我們?nèi)绻刖徑膺@種現(xiàn)象,就得控制輸出層的值的范圍尺度,就得采取合理的權(quán)重初始化方法。 2.4 十種權(quán)重初始化方法Pytorch 里面提供了很多權(quán)重初始化的方法,可以分為下面的四大類(lèi):
好了,到了這里,模型模塊才算得上結(jié)束,下面我們就進(jìn)行下一個(gè)模塊的學(xué)習(xí),損失函數(shù)模塊,在這里面學(xué)習(xí)各種損失函數(shù)的原理及應(yīng)用場(chǎng)景。 3.損失函數(shù)這一部分分為三大塊, 首先看一下?lián)p失函數(shù)到底是干嘛的?然后學(xué)習(xí)非常常用的損失函數(shù)交叉熵,最后再看看其他的幾個(gè)重要損失函數(shù)。 3.1 損失函數(shù)初步介紹損失函數(shù):衡量模型輸出與真實(shí)標(biāo)簽的差異。而我們談?chuàng)p失函數(shù)的時(shí)候,往往會(huì)有三個(gè)概念:損失函數(shù),代價(jià)函數(shù),目標(biāo)函數(shù)。你知道這仨到底啥區(qū)別嗎?還是以為這仨就是一個(gè)概念?
而我們一般都是在衡量模型輸出和真實(shí)標(biāo)簽的差異的時(shí)候,往往都直接成損失函數(shù)。但是我們得知道這哥仨不是一回事。我們下面看一下Pytorch中的損失函數(shù)的真實(shí)面目: 我們發(fā)現(xiàn)了啥? 原來(lái)_Loss也是繼承于Module,這個(gè)在模型創(chuàng)建的時(shí)候就已經(jīng)很熟悉了,也具體介紹過(guò), 既然_Loss也是繼承于這個(gè)類(lèi),那么就得先想起來(lái)肯定_Loss也有那 8 個(gè)參數(shù)字典了,然后這里面是設(shè)置一個(gè)reduction這個(gè)參數(shù)。下面我們?cè)僖匀嗣駧哦诸?lèi)的實(shí)驗(yàn)中的交叉熵?fù)p失為例子,看看損失函數(shù)是如何創(chuàng)建和使用的,背后的運(yùn)行機(jī)制又是什么?哈哈哈,下面就得來(lái)一波調(diào)試了。這次是損失函數(shù)的學(xué)習(xí),所以我們?cè)诙x損失函數(shù)和使用損失函數(shù)的地方打上斷點(diǎn),并且開(kāi)始 debug: 程序運(yùn)行到第一個(gè)斷點(diǎn)處,我們步入,就到了 loss.py 文件中的一個(gè) class CrossEntropyLoss(_WeightedLoss):交叉熵?fù)p失類(lèi)的__init__方法, 這里發(fā)現(xiàn)交叉熵?fù)p失函數(shù)繼承_WeightedLoss這個(gè)類(lèi): 我們繼續(xù)步入,就到了class _WeightedLoss(_Loss):這個(gè)類(lèi)里面,就會(huì)發(fā)現(xiàn)這個(gè)類(lèi)繼承_Loss, 那么我們繼續(xù)步入,就到了_Loss這個(gè)類(lèi)里面去,會(huì)發(fā)現(xiàn)這個(gè)繼承Module,那么現(xiàn)在就明白了,損失函數(shù)的初始化方法和模型其實(shí)類(lèi)似,也是調(diào)用Module的初始化方法,最終會(huì)有 8 個(gè)屬性字典, 然后就是設(shè)置了一個(gè)reduction這個(gè)參數(shù)。初始化就是這樣子了,學(xué)過(guò)了 nn.Module 之后,這里都比較好理解。 那么下面看看使用過(guò)程中的運(yùn)行機(jī)制:我們到第二個(gè)斷點(diǎn),然后步入,我們知道既然這個(gè)損失函數(shù)也是一個(gè) Module,那么在調(diào)用的時(shí)候肯定也是調(diào)用的 forward 方法了,還真的是這樣,它也有一個(gè) forward 的函數(shù)的: 看這里也是調(diào)用的 forward 函數(shù),我們把程序運(yùn)行到 547 行,再次步入,看看損失函數(shù)的 forward 長(zhǎng)啥樣: 我們模型構(gòu)建里面 forward 里面寫(xiě)的是各個(gè)模塊的拼接方式,而損失函數(shù)的 forward 里面調(diào)用了 F 里面的各種函數(shù),我們 Ctrl 然后點(diǎn)擊這個(gè)函數(shù),看看這個(gè)交叉熵?fù)p失函數(shù)到底長(zhǎng)啥樣: 這個(gè)是底層計(jì)算了,不再往下了,我們退回去。 這就是損失函數(shù)的初始化和使用方法的內(nèi)部運(yùn)行機(jī)制了。從上面我們發(fā)現(xiàn)了損失函數(shù)其實(shí)也是一個(gè) Module, 那么既然是 Module,初始化依然是有 8 個(gè)屬性字典,使用的方法依然是定義在了 forward 函數(shù)中。下面我們就詳細(xì)的學(xué)習(xí)一個(gè)非常重要的函數(shù),也是上面例子里面的函數(shù)nn.CrossEntropyLoss, 這個(gè)在分類(lèi)任務(wù)中很常用, 所以下面得詳細(xì)的說(shuō)說(shuō)。 3.2 交叉熵?fù)p失 CrossEntropyLossnn.CrossEntropyLoss: nn.LogSortmax() 與 nn.NLLLoss() 結(jié)合,進(jìn)行交叉熵計(jì)算。
在詳細(xì)介紹這些參數(shù)用法之前,得先說(shuō)說(shuō)這里的交叉熵?fù)p失函數(shù),這個(gè)并不是公式意義上的交叉熵?fù)p失函數(shù),而是有一些不同之處。還記得普通的交叉熵?fù)p失函數(shù)嗎? 表示數(shù)據(jù)的原始分布, 表示模型輸出的分布,交叉熵?fù)p失衡量?jī)蓚€(gè)分布之間的差異程度,交叉熵越低,說(shuō)明兩個(gè)分布越近。這里的一個(gè)不同就是先用nn.LogSoftmax()把模型的輸出值歸一化成了概率分布的形式,然后是單個(gè)樣本的輸出,并且沒(méi)有求和符號(hào)。 具體的下面會(huì)解釋?zhuān)墙忉屩埃孟让靼滓粋€(gè)問(wèn)題,就是為什么交叉熵可以衡量?jī)蓚€(gè)分布的差異,這個(gè)到底是個(gè)什么東西?這就不得不提到相對(duì)熵, 而想了解相對(duì)熵,就得先明白熵的概念,而如果想明白熵,就得先知道自信息,好吧,成功懵逼。下面我們先看看這些都是啥吧: 首先從熵開(kāi)始,這是信息論之父香農(nóng)從熱力學(xué)借鑒來(lái)的名詞,用來(lái)描述事件的不確定性,一個(gè)事物不確定性越大,熵就越大。比如明天會(huì)下雨這個(gè)熵就比明天太陽(yáng)從東邊升起這個(gè)熵要大。那么熵的公式長(zhǎng)這樣: 原來(lái)這個(gè)熵是自信息的一個(gè)期望, 那么就得先看看自信息是什么東西?下面是自信息的公式: 這個(gè)比較好理解了,就是一個(gè)事件發(fā)生的概率,然后取對(duì)數(shù)再取反。也就是一個(gè)事件如果發(fā)生的概率越大,那么自信息就會(huì)少。所有事件發(fā)生的概率都很大,那么熵就會(huì)小,則事件的不確定性就小??磦€(gè)圖就好理解了: 這是一個(gè)兩點(diǎn)分布的一個(gè)信息熵,可以看到,當(dāng)概率是 0.5 的時(shí)候熵最大,也就是事件的不確定性最大,熵大約是 0.69。這個(gè)數(shù)是不是很熟悉?因?yàn)檫@個(gè)在二分類(lèi)模型中經(jīng)常會(huì)碰到,模型訓(xùn)練壞了的時(shí)候,或者剛訓(xùn)練的時(shí)候,我們就會(huì)發(fā)現(xiàn) Loss 值也可能是 0.69,這時(shí)候就說(shuō)模型目前沒(méi)有任何的判斷能力。這就是信息熵的概念。 相對(duì)熵又稱(chēng)為 KL 散度,用來(lái)衡量?jī)蓚€(gè)分布之間的差異,也就是兩個(gè)分布之間的距離,但是不是一個(gè)距離函數(shù),因?yàn)榫嚯x函數(shù)有對(duì)稱(chēng)性,也就是 p 到 q 的距離等于 q 到 p 的距離。而這里的相對(duì)熵不具備這樣的對(duì)稱(chēng)性, 如果看過(guò)我寫(xiě)的生成對(duì)抗原理推導(dǎo)那篇博客的話,那里面也有 KL 散度這個(gè)概念,并且可以通過(guò)組合這個(gè)得到一個(gè)既能夠衡量分布差異也有對(duì)稱(chēng)性的一個(gè)概念叫做 JS 散度。這里先不說(shuō)了,看看這個(gè)公式: 這里的P是數(shù)據(jù)的真實(shí)分布,Q 是模型輸出的分布,這里就是用 Q 的分布去逼近 P 的分布。所以這不具備對(duì)稱(chēng)性。 好了信息熵和相對(duì)熵都說(shuō)了,就可以引出交叉熵了。其實(shí)「交叉熵=信息熵+相對(duì)熵」, 公式如下: 什么?沒(méi)看出交叉熵等于上面兩個(gè)熵之和嗎?那么我們把相對(duì)熵化簡(jiǎn)一下子: 這樣看出來(lái)了吧。 所以,根據(jù)上面的推導(dǎo)我們得到: 在機(jī)器學(xué)習(xí)模型中,我們最小化交叉熵,其實(shí)就是最小化相對(duì)熵,因?yàn)槲覀冇?xùn)練集取出來(lái)之后就是固定的了,熵就是一個(gè)常數(shù)。 好了,我們已經(jīng)知道了交叉熵是衡量?jī)蓚€(gè)分布之間的距離,一個(gè)差異。所以這里使用 softmax,就可以將一個(gè)輸出值轉(zhuǎn)換到概率取值的一個(gè)范圍。我們看看這里的交叉熵?fù)p失函數(shù)是怎么計(jì)算的: 這里的 x 就是我們輸出的概率值,class 就是某一個(gè)類(lèi)別,在括號(hào)里面執(zhí)行了一個(gè) softmax,把某個(gè)神經(jīng)元的輸出歸一化成了概率取值,然后 -log 一下,就得到了交叉熵?fù)p失函數(shù)。我們可以對(duì)比一下我們的交叉熵公式: 由于是某個(gè)樣本,那么 已經(jīng)是 1 了,畢竟取出來(lái)了已經(jīng)。而是某個(gè)樣本,所以也不用求和符號(hào)。 這就是用 softmax 的原因了,把模型的輸出值轉(zhuǎn)成概率分布的形式,這樣就得到了交叉熵?fù)p失函數(shù)。 好了,這里就可以說(shuō)一說(shuō)那些參數(shù)的作用了, 第一個(gè)參數(shù)weight, 各類(lèi)別的 loss 設(shè)置權(quán)值, 如果類(lèi)別不均衡的時(shí)候這個(gè)參數(shù)很有必要了,加了之后損失函數(shù)變成這樣: 這樣,就是如果我們想讓模型更關(guān)注某一類(lèi)的話,就可以把這一類(lèi)的權(quán)值設(shè)置的大一點(diǎn)。第二個(gè)參數(shù)ignore_index, 這個(gè)是表示某個(gè)類(lèi)別不去計(jì)算 loss。而關(guān)于第三個(gè)參數(shù)reduction, 有三個(gè)計(jì)算模式 none/sum/mean, 上面已經(jīng)說(shuō)了,下面我們從代碼中看看這三個(gè)的區(qū)別: # fake datainputs = torch.tensor([[1, 2], [1, 3], [1, 3]], dtype=torch.float) # 這里就是模型預(yù)測(cè)的輸出, 這里是兩個(gè)類(lèi),可以看到模型輸出是數(shù)值,我們得softmax一下轉(zhuǎn)成分布target = torch.tensor([0, 1, 1], dtype=torch.long) # 這里的類(lèi)型必須是long, 兩個(gè)類(lèi)0和1# 三種模式的損失函數(shù)loss_f_none = nn.CrossEntropyLoss(weight=None, reduction='none')loss_f_sum = nn.CrossEntropyLoss(weight=None, reduction='sum')loss_f_mean = nn.CrossEntropyLoss(weight=None, reduction='mean')# forwardloss_none = loss_f_none(inputs, target)loss_sum = loss_f_sum(inputs, target)loss_mean = loss_f_mean(inputs, target)# viewprint('Cross Entropy Loss:\n ', loss_none, loss_sum, loss_mean)## 結(jié)果:Cross Entropy Loss: tensor([1.3133, 0.1269, 0.1269]) tensor(1.5671) tensor(0.5224) 這樣可以看到,none 模式下是輸出三個(gè)損失,sum 下是三個(gè)損失求和,mean 下是三個(gè)損失求平均。這里還要注意一下這里的 target, 「這個(gè)是每個(gè)樣本給出屬于哪一個(gè)類(lèi)即可,類(lèi)型是 torch.long, 為什么要強(qiáng)調(diào)這個(gè),我們下面會(huì)學(xué)習(xí)二分類(lèi)交叉熵?fù)p失,是交叉熵?fù)p失函數(shù)的特例,那里的 target 更要注意,對(duì)比起來(lái)更容易理解」 下面我們?cè)偻ㄟ^(guò)代碼看看加上 weight 的損失: 這里可以發(fā)現(xiàn),給類(lèi)別加上權(quán)值之后,對(duì)應(yīng)樣本的損失就會(huì)相應(yīng)的加倍,這里重點(diǎn)是了解一下這個(gè)加上權(quán)之后, mean 模式下怎么計(jì)算的損失:其實(shí)也很簡(jiǎn)單,我們?nèi)齻€(gè)樣本,第一個(gè)權(quán)值為 1, 后兩個(gè)權(quán)值為 2, 所以分母不再是 3 個(gè)樣本,而是 1+2+2, 畢竟后兩個(gè)樣本權(quán)為 2, 一個(gè)樣本頂?shù)谝粋€(gè)的這樣的 2 個(gè)。所以 「mean 模式下求平均不是除以樣本的個(gè)數(shù),而是樣本所占的權(quán)值的總份數(shù)」。 3.2.1 還有幾個(gè)交叉熵?fù)p失函數(shù)的特例「1 nn.NLLoss」 在上面的交叉熵?fù)p失中,我們發(fā)現(xiàn)這個(gè)是softmax和NLLoss的組合,那么這里的nn.NLLLoss是何物???交叉熵?fù)p失里面還有個(gè)這個(gè)東西,其實(shí)這個(gè)東西不要被這個(gè)名字給迷惑了, 這個(gè)就是實(shí)現(xiàn)了一個(gè)負(fù)號(hào)的功能:nn.NLLoss: 實(shí)現(xiàn)負(fù)對(duì)數(shù)似然函數(shù)里面的負(fù)號(hào)功能 下面看看這個(gè)東西到底干啥用, 我這樣測(cè)試了一下: 這個(gè)損失函數(shù),就是根據(jù)真實(shí)類(lèi)別去獲得相應(yīng)的 softmax 之后的概率結(jié)果,然后取反就是最終的損失。還別說(shuō),真能反應(yīng)模型好壞,因?yàn)榈谝粋€(gè)類(lèi)分錯(cuò)了,所以損失就大,看到?jīng)]。 「2 nn.BCELoss」 這個(gè)是交叉熵?fù)p失函數(shù)的特例,二分類(lèi)交叉熵。注意:輸入值取值在 [0,1] 這里的參數(shù)和上面的一樣,也不說(shuō)了, 看看這個(gè)計(jì)算公式吧: 邏輯回歸的時(shí)候,是不是就是這個(gè)公式啊?我們看看代碼中這個(gè)怎么用: 這里首先注意的點(diǎn)就是 target,這里可以發(fā)現(xiàn)和交叉熵那里的標(biāo)簽就不一樣了,首先是類(lèi)型是 float,每個(gè)樣本屬于哪一類(lèi)的時(shí)候要寫(xiě)成獨(dú)熱的那種形式,這是因?yàn)榭磽p失函數(shù)的計(jì)算公式也能看到,每個(gè)神經(jīng)元一一對(duì)應(yīng)的去計(jì)算 loss,而不是一個(gè)整的神經(jīng)元向量去計(jì)算 loss,看結(jié)果也會(huì)發(fā)現(xiàn)有 8 個(gè) loss,因?yàn)槊總€(gè)神經(jīng)元都一一去計(jì)算 loss,根據(jù) inputs,這里是兩個(gè)神經(jīng)元的。 「3 nn.BCEWithLogitsLoss」 這個(gè)函數(shù)結(jié)合了 Sigmoid 與二分類(lèi)交叉熵,注意事項(xiàng):網(wǎng)絡(luò)最后不加sigmoid函數(shù) 這里的參數(shù)多了一個(gè)pow_weight, 這個(gè)是平衡正負(fù)樣本的權(quán)值用的, 對(duì)正樣本進(jìn)行一個(gè)權(quán)值設(shè)定。比如我們正樣本有 100 個(gè),負(fù)樣本有 300 個(gè),那么這個(gè)數(shù)可以設(shè)置為 3,在類(lèi)別不平衡的時(shí)候可以用。 計(jì)算公式如下: 這里了就是加了個(gè) sigmoid。 3.3 剩余的 14 種損失函數(shù)介紹「1 nn.L1Loss」 這個(gè)用于回歸問(wèn)題,用來(lái)計(jì)算inputs與target之差的絕對(duì)值 上面的 size_average 和 reduce 不用再關(guān)注,即將淘汰。而 reduction 這個(gè)三種模式,其實(shí)和上面的一樣。 「2 nn.MSE」 這個(gè)也是用于回歸問(wèn)題,計(jì)算inputs與target之差的平方 「3 nn.SmoothL1Loss」 這是平滑的L1Loss(回歸問(wèn)題) 那么這個(gè)平滑到底是怎么體現(xiàn)的呢? 采用這種平滑的損失函數(shù)可以減輕離群點(diǎn)帶來(lái)的影響。 「4 nn.PoissonNLLLoss」 功能:泊松分布的負(fù)對(duì)數(shù)似然損失函數(shù),分類(lèi)里面如果發(fā)現(xiàn)數(shù)據(jù)的類(lèi)別服從泊松分布,可以使用這個(gè)損失函數(shù)
「5 nn.KLDivLoss」 功能:計(jì)算 KLD, KL 散度,相對(duì)熵,注意:需要提前將輸入計(jì)算 log-probabilities,如通過(guò) nn.logsoftmax() 其實(shí)這個(gè)已經(jīng)在上面交叉熵的時(shí)候說(shuō)完了。上面的 Pytorch 里面的計(jì)算和我們?cè)瓉?lái)公式里面的計(jì)算還有點(diǎn)不太一樣,所以我們得自己先 logsoftmax(),完成轉(zhuǎn)換為分布然后轉(zhuǎn)成對(duì)數(shù)才可以。這里的 reduction 還多了一種計(jì)算模式叫做 batchmean,是按照 batchsize 的大小求平均值。 「6 nn.MarginRankingLoss」 功能:計(jì)算兩個(gè)向量之間的相似度,用于排序任務(wù)。特別說(shuō)明,該方法計(jì)算兩組數(shù)據(jù)之間的差異,也就是每個(gè)元素兩兩之間都會(huì)計(jì)算差異,返回一個(gè) n*n 的 loss 矩陣。類(lèi)似于相關(guān)性矩陣那種。 margin 表示邊界值,x1 與 x2 之間的差異值。這里的計(jì)算公式如下:
這個(gè)地方看一下代碼理解吧還是: 「7 nn.MultiLabelMarginLoss」 功能:多標(biāo)簽邊界損失函數(shù), 這是一個(gè)多標(biāo)簽分類(lèi),就是一個(gè)樣本可能屬于多個(gè)類(lèi),和多分類(lèi)任務(wù)還不一樣。(多標(biāo)簽問(wèn)題) 這個(gè)的計(jì)算公式如下: 這里的 i 取值從 0 到輸出的維度減 1,j 取值也是 0 到 y 的維度減 1,對(duì)于所有的 i 和 j,i 不等于 y[j],也就是標(biāo)簽所在的神經(jīng)元去減掉那些非標(biāo)簽所在的神經(jīng)元,這說(shuō)的啥?一臉懵逼,還是看代碼理解一下吧: 我們看上面這個(gè)代碼,假設(shè)我們有一個(gè)訓(xùn)練樣本,輸出層 4 個(gè)神經(jīng)元,也就是 4 分類(lèi)的問(wèn)題,前向傳播后,神經(jīng)網(wǎng)絡(luò)的四個(gè)神經(jīng)元的輸出分別是 [0.1, 0.2, 0.4, 0.8],而這個(gè)樣本的真實(shí)標(biāo)簽是 [0, 3, -1, -1], 首先解釋這是啥意思,就是說(shuō)這個(gè)樣本屬于第 0 類(lèi)和第 3 類(lèi),這個(gè)地方必須是 torch.long 型,并且必須和輸出神經(jīng)元個(gè)數(shù)一樣,屬于哪幾類(lèi)寫(xiě)前面,不夠長(zhǎng)度的用 -1 填補(bǔ)。使用多標(biāo)簽邊界損失函數(shù)的時(shí)候,具體計(jì)算就是下面那樣: 我們的輸入樣本屬于 0 和 3 這兩類(lèi),不屬于 1 和 2, 那么就根據(jù)上面那個(gè)公式,后面那部分是標(biāo)簽所在的神經(jīng)元減去標(biāo)簽不不在的神經(jīng)元, 比如標(biāo)簽在第0個(gè)神經(jīng)元:
應(yīng)該差不多明白這個(gè)過(guò)程了,可以為啥要這么做呢? 這個(gè)意思就是說(shuō)我們希望「標(biāo)簽所在的神經(jīng)元要比非標(biāo)簽所在的神經(jīng)元的輸出值要盡量的大」,當(dāng)這個(gè)差大于 1 了, 我們根據(jù)max(0, 1-差值), 才發(fā)現(xiàn)不會(huì)有損失產(chǎn)生, 當(dāng)這個(gè)差值小或者非標(biāo)簽所在的神經(jīng)元比標(biāo)簽所在神經(jīng)元大的時(shí)候,都會(huì)產(chǎn)生損失。所以上面那個(gè)例子,我們想讓第 0 個(gè)神經(jīng)元的值要比第 1 個(gè),第二個(gè)大一些,第 3 個(gè)神經(jīng)元的值要比第 1 個(gè),第 2 個(gè)大一些,這才能說(shuō)明這個(gè)樣本屬于第 0 類(lèi)和第 3 類(lèi),才是我們想要的結(jié)果啊。有沒(méi)有一點(diǎn) hinge loss 的意思?只不過(guò)那里是多分類(lèi),而這里是多標(biāo)簽分類(lèi),感覺(jué)思想差不多。 「8 nn.SoftMarginLoss」 功能:計(jì)算二分類(lèi)的 logistic 損失(二分類(lèi)問(wèn)題) 計(jì)算公式如下: 「9 nn.MultiLabelSortMarginLoss」 功能:SoftMarginLoss 多標(biāo)簽版本 (多標(biāo)簽問(wèn)題) 之類(lèi)的 weight,表示各類(lèi)別的 loss 設(shè)置權(quán)值。計(jì)算公式如下: 這個(gè)理解起來(lái)也不是那么好理解,也是看看代碼怎么計(jì)算:我們這里是一個(gè)三分類(lèi)的任務(wù),輸入的這個(gè)樣本屬于第二類(lèi)和第三類(lèi): 「10 nn.MultiMarginLoss(hingLoss)」 功能:計(jì)算多分類(lèi)的折頁(yè)損失(多分類(lèi)問(wèn)題) 這里的 p 可選 1 或者 2,margin 表示邊界值。計(jì)算公式如下: 這里的 x, y 是 0 - 神經(jīng)元個(gè)數(shù)減 1,并且對(duì)于所以 i 和 j,i 不等于 y[j]。這里就類(lèi)似于 hing loss 了,這里的 x[y] 表示標(biāo)簽所在的神經(jīng)元,x[i] 表示非標(biāo)簽所在的神經(jīng)元。還是先看個(gè)例子,了解一下這個(gè)計(jì)算過(guò)程,然后借著這個(gè)機(jī)會(huì)也說(shuō)一說(shuō) hing loss 吧: 這個(gè)其實(shí)和多標(biāo)簽邊界損失函數(shù)的原理差不多,只不過(guò)那里是一個(gè)樣本屬于多個(gè)類(lèi),需要每個(gè)類(lèi)都這樣算算,而這里一個(gè)樣本屬于 1 個(gè)類(lèi),只計(jì)算一次即可。這個(gè)其實(shí)就是我們的 hinge loss 損失,我們可以看一下: 這個(gè)地方的原理啥的就先不推了: 假如我們現(xiàn)在有三個(gè)類(lèi)別,而得分函數(shù)計(jì)算某張圖片的得分為 ,而實(shí)際結(jié)果是第一類(lèi)( )。假設(shè) ,這個(gè)就是上面的 margin,那么上面的公式就把錯(cuò)誤類(lèi)別 () 都遍歷了一遍,求值加和: 這個(gè)損失和交叉熵?fù)p失是不同的兩種評(píng)判標(biāo)準(zhǔn),這個(gè)損失聚焦于分類(lèi)錯(cuò)誤的與正確類(lèi)別之間的懲罰距離越小越好,而交叉熵?fù)p失聚焦分類(lèi)正確的概率分布越大越好。 「11 nn.TripletMarginLoss」 功能:計(jì)算三元組損失,人臉驗(yàn)證中常用 這里的 p 表示范數(shù)的階。計(jì)算公式: 三元組在做這么個(gè)事情, 我們?cè)谧鋈四樧R(shí)別訓(xùn)練模型的時(shí)候,往往需要把訓(xùn)練集做成三元組 (A, P, N), A 和 P 是同一個(gè)人,A 和 N 不是同一個(gè),然后訓(xùn)練我們的模型 我們想讓模型把 A 和 P 看成一樣的,也就是爭(zhēng)取讓 A 和 P 之間的距離小,而 A 和 N 之間的距離大,那么我們的模型就能夠進(jìn)行人臉識(shí)別任務(wù)了。 「12 nn.HingeEmbeddingLoss」 功能:算兩個(gè)輸入的相似性,常用于非線性 embedding 和半監(jiān)督學(xué)習(xí)。特別注意,輸入的x應(yīng)為兩個(gè)輸入之差的絕對(duì)值, 也就是手動(dòng)計(jì)算兩個(gè)輸入的差值 計(jì)算公式如下: 「13 nn.CosineEmbeddingLoss」 功能:采用余弦相似度計(jì)算兩個(gè)輸入的相似性,常用于半監(jiān)督學(xué)習(xí)和 embedding 這里的 margin 可取值 [-1, 1],推薦為 [0,0.5]。計(jì)算公式如下: 之所以用 cos, 希望關(guān)注于這兩個(gè)輸入方向上的一個(gè)差異,而不是距離上的差異,cos 函數(shù)如下: 「14 nn.CTCLoss」 功能:計(jì)算 CTC 損失, 解決時(shí)序類(lèi)數(shù)據(jù)的分類(lèi) blank: blank label, zeor_infinity: 無(wú)窮大的值或者梯度置 0,這個(gè)使用起來(lái)比較復(fù)雜,所以具體的可以看看官方文檔。 到這里,18 種損失函數(shù)就介紹完了,哇,太多了,這哪能記得住啊, 所以我們可以對(duì)這些損失函數(shù)從任務(wù)的角度分分類(lèi),到時(shí)候看看是什么任務(wù),然后看看有哪些損失函數(shù)可以用,再去查具體用法就可以啦。我這邊是這樣分的:
4.總結(jié)今天的內(nèi)容就到這里了,這次整理的內(nèi)容還是比較多的,主要分為兩大塊:權(quán)重初始化和損失函數(shù)的介紹, 第一塊里面有 10 中權(quán)重初始化方法,而第二塊里面18種損失函數(shù)。哇,這個(gè)知識(shí)量還是很大的,當(dāng)然我們其實(shí)并不需要都記住,只知道有哪些方法,具體什么時(shí)候用就行了,這個(gè)系列的目的也不是要求一下子都會(huì)了, 而是先有個(gè)框架出來(lái)??焖偈崂硪槐榘桑?/span>
好了,損失函數(shù)模塊到這里就結(jié)束了,后面進(jìn)入優(yōu)化器部分, 我們還是那個(gè)流程:數(shù)據(jù)模塊 -> 模型模塊 -> 損失函數(shù)模塊 -> 優(yōu)化器 -> 迭代訓(xùn)練。我們已經(jīng)完成了3個(gè)模塊的學(xué)習(xí),馬上就要看到曙光,再堅(jiān)持一下, rush ;) |
|