當前位置:
首頁 > 科技 > 周末AI課堂:深度學習中的熵 代碼篇

周末AI課堂:深度學習中的熵 代碼篇

AI課堂開講,就差你了!

很多人說,看了再多的文章,可是沒有人手把手地教授,還是很難真正地入門AI。為了將AI知識體系以最簡單的方式呈現給你,從這個星期開始,芯君邀請AI專業人士開設「周末學習課堂」——每周就AI學習中的一個重點問題進行深度分析,課程會分為理論篇和代碼篇,理論與實操,一個都不能少!

來,退出讓你廢寢忘食的遊戲頁面,取消只有胡吃海塞的周末聚會吧。未來你與同齡人的差異,也許就從每周末的這堂AI課開啟了!

讀芯術讀者交流群,請加小編微信號:zhizhizhuji。等你。後台回復「周末AI課堂」,查閱相關源代碼。

全文共2268字,預計學習時長5分鐘

我們已經通過指數分布和廣義線性模型,以及最大熵原則推導出softmax函數,旨在證明輸出單元的設計背後有著一般性的準則,不僅是我們可以使用這些函數,更是我們最好使用這些函數。在廣義線性模型中,用於二分類的sigmoid函數對應著伯努利分布,用於多分類的softmax函數對應著多項式分布,同時將最大熵模型應用到分類問題中,選取簡單的特徵函數,也可以自然的推導出相應的輸出單元。

同時,我們從條件熵的角度去理解相對熵,通過交叉熵給出了極大似然估計相同的形式,驗證了等價性。那麼為什麼我們在深度學習中面對分類問題時會採用交叉熵作為損失函數,而非採用均方誤差呢?甚至我在《從感知機到深度學習》代碼篇中就曾利用均方誤差作為損失函數,訓練過程也非常成功。

圖為前面課程《從感知機到深度學習》代碼篇的插圖,其中我們搭建了簡單的感知機,並採用MSE作為損失函數的訓練結果。

當時我在文章中說:

二分類很少使用MSE,但並非不可以使用,只是它並不具備準確率的一致性,在大多數問題上不如其他的損失函數表現優異,但在這裡為了簡單的闡釋道理,選用MSE這樣人人皆知,老少咸宜的損失函數更容易專註於感知機的實現。

為了更好的理解MSE為什麼不能做損失函數,以二分類問題為例,我們可以對對數似然,準確率,MSE作圖來直觀的理解替代損失函數的一致性:

import numpy as np

import matplotlib.pyplot as plt

import seaborn as sns

def mse(x):

return x**2

def log(x):

return np.log(1+np.exp(-x))

def hinge(x):

return [max(0,1-i) for i in x]

def exp(x):

return np.exp(-x)

def acc(x):

return [1 if i

a=np.linspace(-5,5,500)

sns.set(style="darkgrid")

plt.plot(a,hinge(a),label="Hinge Loss")

plt.plot(a,mse(a),label="MSE loss")

plt.plot(a,acc(a),label="Error")

plt.plot(a,log(a),label="Log loss")

plt.plot(a,exp(a),label="Exp Loss")

plt.ylim(0,10)

plt.title("Surrogate Loss")

plt.legend()

plt.show()

如圖,當我們採取MSE作為我們的Loss時,當我們沿著loss減小的方向進行優化時,可能會存在兩個方向,而錯誤率可能並未得到優化,這就是我們在《理解損失函數》中所提到的不一致性,而其他的損失函數確可以保證在優化自身的時候,錯誤率也得到優化。

但是,在上個世紀的八九十年代,均方誤差卻是較為流行的損失函數,後來才逐漸被交叉熵或者極大似然方法給出的損失函數所替代。在深度學習中,除了替代損失這樣本質上的原因,另一個重要的問題是,面對sigmoid和softmax函數形式的輸出單元,均方誤差會造成一定程度的梯度消失。

梯度消失的本質,是在於本來寄希望於梯度能夠傳遞的損失函數中的信息因為飽和的存在,不同的信息都表現為很微小的差別,信息就存在一定程度的丟失。我們已經在隱藏單元中見識到了梯度消失問題,而輸出單元也會有梯度消失問題。

以二分類的sigmoid函數為例,其MSE為:

我們對其求導是複合函數的求導,其中仍然存在sigmoid函數作為一個整體的導數,這樣就繼續回到了我們在《設計隱藏單元》中所講的,當輸入變得極端時的飽和區。但是在交叉熵中或者極大似然估計得出的函數中,Loss變為:

我們計算其梯度,就可以確定其梯度消失的情況沒有MSE那般嚴重。從數學上來說,無論是交叉熵還是極大似然估計,都存在對數操作,而sigmoid和softmax函數都存在指數項,對數操作正好使得抵消了指數項的放大作用。

我們接下來觀察實際的效果,仍然採用MNIST手寫識別數據,與我們上一節隱藏單元的代碼基本類似,我們在定義模型過程中添加控制損失函數的參數,我們分別利用MSE和交叉熵作為損失函數觀察其訓練效果:

import numpy as np

from keras.datasets import mnist

from keras.utils import to_categorical

from keras import models

from keras.layers import Dense

from keras import optimizers

(X_train,y_train),(X_test,y_test)=mnist.load_data()

train_labels = to_categorical(y_train)

test_labels = to_categorical(y_test)

X_train_normal = X_train.reshape(60000,28*28)

X_train_normal = X_train_normal.astype("float32") / 255

X_test_normal = X_test.reshape(10000, 28*28)

X_test_normal = X_test_normal.astype("float32") / 255

def normal_model(a,b):

model=models.Sequential()

model.add(Dense(512,activation=a,input_shape=(28*28,)))

model.add(Dense(256,activation=a))

model.add(Dense(128,activation=a))

model.add(Dense(64,activation=a))

model.add(Dense(10,activation="softmax"))

model.compile(optimizer=optimizers.SGD(momentum=0.9,nesterov=True),

loss=b,metrics=["accuracy"])

return(model)

w={}

for b in ["mse","categorical_crossentropy"]:

model_1=normal_model("sigmoid",b)

his=model_1.fit(X_train_normal,train_labels,

batch_size=128,validation_data=(X_test_normal,test_labels),

verbose=1,epochs=10)

w[b]=his.history

我們仍然採用了sigmoid函數作為隱藏單元,對每種損失函數仍然進行10個epochs的迭代,並且將最終的結果保存在一個字典裡面,我們可以依次將Loss隨著epochs的變化畫出:

import matplotlib.pyplot as plt

import seaborn as sns

sns.set(style="whitegrid")

plt.plot(range(10),w["mse"]["val_loss"],label="validation loss")

plt.plot(range(10),w["mse"]["loss"],label=" train loss")

plt.title("MSE Loss")

plt.legend()

如圖所示,選取MSE作為Loss,似乎只需要一次迭代就可以將Loss下降到非常低的水平。

接著,我們畫出交叉熵隨著epochs的變化:

......

plt.plot(range(10),w["categorical_crossentropy"]["val_loss"],label="validation loss")

plt.plot(range(10),w["categorical_crossentropy"]["loss"],label=" train loss")

......

如圖所示,選取交叉熵作為Loss,在迭代10次之後Loss仍然居高不下,而且似乎並沒有收斂的趨勢。

那麼根據這兩幅圖,我們是不是可以說,MSE比交叉熵優異得多呢?恰恰相反,因為我們選用了不同的Loss,所以比較Loss的高低是無效的,將MSE快速下降到一個較低的水平只是在說明,MSE作為損失函數的網路是很容易學習的,但不代表學習的效果好。因為減小MSE可能不是在優化錯誤率,所以我們在訓練結束後,觀察兩中損失函數的準確率:

plt.figure()

f=[]

g=[]

for a in w.keys():

f.append(a)

g.append(w[a]["val_acc"][-1])

sns.barplot(f,g)

plt.ylabel("Accuracy")

plt.title("MSE VS CrossEntropy")

plt.legend()

plt.show()

如圖,我們看到採用MSE進行優化的準確率遠遠低於採用交叉熵的準確率,恰恰說明在Loss選用不合理的情形下,即便訓練收斂也會得到一個糟糕的結果。

MSE作為損失函數所產生的糟糕的結果是否全部來源於此呢?為了驗證梯度消失對其糟糕結果的貢獻,我們可以將隱藏單元改為Relu繼續觀察效果,這樣做的原因在於,BP演算法是梯度流的傳播,隱藏單元的飽和性會放大梯度消失帶來的惡劣影響。如果換用Relu,MSE下的網路準確率會有提升的話,那麼就可以說明MSE的準確率低的來源之一使其可能造成的飽和性。

我們在原來代碼的基礎上修改:

w={}

for b in ["mse","categorical_crossentropy"]:

model_1=normal_model("relu",b)

his=model_1.fit(X_train_normal,train_labels,

batch_size=128,validation_data=(X_test_normal,test_labels),

verbose=1,epochs=10)

w[b]=his.history

如圖所示,在改變隱藏單元為Relu之後,MSE的準確率暴增,一方面說明了隱藏單元的重要性,另一方面則說明了,雖然能快速的迭代到 MSE Loss較低的點,很大程度上是因為飽和性而變得無法學習,我們在觀察Loss隨著epochs的圖像中要格外注意飽和的影響。

讀芯君開扒

課堂TIPS

? softmax函數的輸出和為1。一個單元輸出的增加就必然意味著其他單元的減少,所以在softmax函數的Loss function中,對不正確的高概率預測,其懲罰也變得非常大。同時這種和為1的機制在個結果的情形下,意味著我們不需要在輸出單元中設置n個參數,而只需要設置n-1個。

? 深度學習的分類問題,損失函數並不是唯一的,似乎看起來我們只能使用softmax作為輸出單元,交叉熵作為損失函數,這只是因為softmax的極大似然估計就會得到交叉熵,與其他無關。我們完全可以根據自己的需要設計相應的Loss和輸出單元。

留言 點贊 發個朋友圈

我們一起探討AI落地的最後一公里

作者:唐僧不用海飛絲

如需轉載,請後台留言,遵守轉載規範


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 讀芯術 的精彩文章:

周末AI課堂:深度學習中的熵 理論篇
今日芯聲 | 帶走了一代人青春的金庸,留下了這樣的遺願

TAG:讀芯術 |