為什么交叉熵可以用於計算代價函數
通用的說,熵(Entropy)被用於描述一個系統中的不確定性(the uncertainty of a system)。在不同領域熵有不同的解釋,比如熱力學的定義和信息論也不大相同。
要想明白交叉熵(Cross Entropy)的意義,可以從熵(Entropy) -> KL散度(Kullback-Leibler Divergence) -> 交叉熵這個順序入手。當然,也有多種解釋方法[1]。
先給出一個"接地氣但不嚴謹"的概念表述:
- 熵:可以表示一個事件A的自信息量,也就是A包含多少信息。
- KL散度:可以用來表示從事件A的角度來看,事件B有多大不同。
- 交叉熵:可以用來表示從事件A的角度來看,如何描述事件B。
一句話總結的話:KL散度可以被用於計算代價,而在特定情況下最小化KL散度等價於最小化交叉熵。而交叉熵的運算更簡單,所以用交叉熵來當做代價。
我知道你現在看着有點暈,但請保持耐心繼續往下看。*為了通俗易懂,我沒有嚴格按照數學規范來命名概念,比如文中的"事件"指的是"消息",望各位嚴謹的讀者理解。
1. 什么是熵(Entropy)?
放在信息論的語境里面來說,就是一個事件所包含的信息量。我們常常聽到"這句話信息量好大",比如"昨天花了10萬,終於在西二環買了套四合院"。
這句話為什么信息量大?因為它的內容出乎意料,違反常理。由此引出:
- 越不可能發生的事件信息量越大,比如"我不會死"這句話信息量就很大。而確定事件的信息量就很低,比如"我是我媽生的",信息量就很低甚至為0。
- 獨立事件的信息量可疊加。比如"a. 張三今天喝了阿薩姆紅茶,b. 李四前天喝了英式早茶"的信息量就應該恰好等於a+b的信息量,如果張三李四喝什么茶是兩個獨立事件。
因此熵被定義為 , x指的不同的事件比如喝茶,
指的是某個事件發生的概率比如和紅茶的概率。對於一個一定會發生的事件,其發生概率為1,
,信息量為0。
2. 如何衡量兩個事件/分布之間的不同(一):KL散度
我們上面說的是對於一個隨機變量x的事件A的自信息量,如果我們有另一個獨立的隨機變量x相關的事件B,該怎么計算它們之間的區別?
此處我們介紹默認的計算方法:KL散度,有時候也叫KL距離,一般被用於計算兩個分布之間的不同。看名字似乎跟計算兩個點之間的距離也很像,但實則不然,因為KL散度不具備有對稱性。在距離上的對稱性指的是A到B的距離等於B到A的距離。
舉個不恰當的例子,事件A:張三今天買了2個土雞蛋,事件B:李四今天買了6個土雞蛋。我們定義隨機變量x:買土雞蛋,那么事件A和B的區別是什么?有人可能說,那就是李四多買了4個土雞蛋?這個答案只能得50分,因為忘記了"坐標系"的問題。換句話說,對於張三來說,李四多買了4個土雞蛋。對於李四來說,張三少買了4個土雞蛋。選取的參照物不同,那么得到的結果也不同。更嚴謹的說,應該是說我們對於張三和李四買土雞蛋的期望不同,可能張三天天買2個土雞蛋,而李四可能因為孩子滿月昨天才買了6個土雞蛋,而平時從來不買。
KL散度的數學定義:
-
對於離散事件我們可以定義事件A和B的差別為(2.1):
-
對於連續事件,那么我們只是把求和改為求積分而已(2.2)。
從公式中可以看出:
- 如果
,即兩個事件分布完全相同,那么KL散度等於0。
- 觀察公式2.1,可以發現減號左邊的就是事件A的熵,請記住這個發現。
-
如果顛倒一下順序求
,那么就需要使用B的熵,答案就不一樣了。所以KL散度來計算兩個分布A與B的時候是不是對稱的,有"坐標系"的問題,
換句話說,KL散度由A自己的熵與B在A上的期望共同決定。當使用KL散度來衡量兩個事件(連續或離散),上面的公式意義就是求 A與B之間的對數差 在 A上的期望值。
3. KL散度 = 交叉熵 - 熵?
如果我們默認了用KL散度來計算兩個分布間的不同,那還要交叉熵做什么?
事實上交叉熵和KL散度的公式非常相近,其實就是KL散度的后半部分(公式2.1):A和B的交叉熵 = A與B的KL散度 - A的熵。
對比一下這是KL散度的公式:
這是熵的公式:
這是交叉熵公式:
此處最重要的觀察是,如果S(A) 是一個常量,那么 ,也就是說KL散度和交叉熵在特定條件下等價。這個發現是這篇回答的重點。
同時補充交叉熵的一些性質:
- 和KL散度相同,交叉熵也不具備對稱性:
,此處不再贅述。
- 從名字上來看,Cross(交叉)主要是用於描述這是兩個事件之間的相互關系,對自己求交叉熵等於熵。即
,注意只是非負而不一定等於0。
*4. 另一種理解KL散度、交叉熵、熵的角度(選讀)- 可跳過
那么問題來了,為什么有KL散度和交叉熵兩種算法?為什么他們可以用來求分布的不同?什么時候可以等價使用?
一種信息論的解釋是:
- 熵的意義是對A事件中的隨機變量進行編碼所需的最小字節數。
- KL散度的意義是"額外所需的編碼長度"如果我們用B的編碼來表示A。
- 交叉熵指的是當你用B作為密碼本來表示A時所需要的"平均的編碼長度"。
對於大部分讀者,我覺得可以不用深入理解。感謝評論區@王瑞欣的指正,不知道為什么@不到他。
一些對比與觀察:
- KL散度和交叉熵的不同處:交叉熵中不包括"熵"的部分
- KL散度和交叉熵的相同處:a. 都不具備對稱性 b. 都是非負的
- 等價條件(章節3):當 固定不變時,那么最小化KL散度
等價於最小化交叉熵
。
既然等價,那么我們優先選擇更簡單的公式,因此選擇交叉熵。
5. 機器如何"學習"?
機器學習的過程就是希望在訓練數據上模型學到的分布 和真實數據的分布
越接近越好,那么我們已經介紹過了....怎么最小化兩個分布之間的不同呢?用默認的方法,使其KL散度最小!
但我們沒有真實數據的分布,那么只能退而求其次,希望模型學到的分布和訓練數據的分布 盡量相同,也就是把訓練數據當做模型和真實數據之間的代理人。假設訓練數據是從總體中獨立同步分布采樣(Independent and identically distributed sampled)而來,那么我們可以利用最小化訓練數據的經驗誤差來降低模型的泛化誤差。簡單說:
- 最終目的是希望學到的模型的分布和真實分布一致:
- 但真實分布是不可知的,我們只好假設 訓練數據 是從真實數據中獨立同分布采樣而來:
-
退而求其次,我們希望學到的模型分布至少和訓練數據的分布一致
由此非常理想化的看法是如果模型(左)能夠學到訓練數據(中)的分布,那么應該近似的學到了真實數據(右)的分布:
6. 為什么交叉熵可以用作代價?
接着上一點說,最小化模型分布 與
訓練數據上的分布 的差異 等價於 最小化這兩個分布間的KL散度,也就是最小化 。
比照第四部分的公式:
- 此處的A就是數據的真實分布:
- 此處的B就是模型從訓練數據上學到的分布:
巧的是,訓練數據的分布A是給定的。那么根據我們在第四部分說的,因為A固定不變,那么求 等價於求
,也就是A與B的交叉熵。得證,交叉熵可以用於計算"學習模型的分布"與"訓練數據分布"之間的不同。當交叉熵最低時(等於訓練數據分布的熵),我們學到了"最好的模型"。
但是,完美的學到了訓練數據分布往往意味着過擬合,因為訓練數據不等於真實數據,我們只是假設它們是相似的,而一般還要假設存在一個高斯分布的誤差,是模型的泛化誤差下線。
7. 總結
因此在評價機器學習模型時,我們往往不能只看訓練數據上的誤分率和交叉熵,還是要關注測試數據上的表現。如果在測試集上的表現也不錯,才能保證這不是一個過擬合或者欠擬合的模型。交叉熵比照誤分率還有更多的優勢,因為它可以和很多概率模型完美的結合。
所以邏輯思路是,為了讓學到的模型分布更貼近真實數據分布,我們最小化 模型數據分布 與 訓練數據之間的KL散度,而因為訓練數據的分布是固定的,因此最小化KL散度等價於最小化交叉熵。
因為等價,而且交叉熵更簡單更好計算,當然用它咯 ʕ•ᴥ•ʔ
Tensorflow交叉熵函數:cross_entropy
注意:tensorflow交叉熵計算函數輸入中的logits都不是softmax或sigmoid的輸出,而是softmax或sigmoid函數的輸入,因為它在函數內部進行sigmoid或softmax操作
tf.nn.sigmoid_cross_entropy_with_logits(_sentinel=None,labels=None, logits=None, name=None)
argument:
_sentinel:本質上是不用的參數,不用填
logits:一個數據類型(type)是float32或float64;
shape:[batch_size,num_classes],單樣本是[num_classes]
labels:和logits具有相同的type(float)和shape的張量(tensor),
name:操作的名字,可填可不填
output:
loss,shape:[batch_size,num_classes]
Note:
它對於輸入的logits先通過sigmoid函數計算,再計算它們的交叉熵,但是它對交叉熵的計算方式進行了優化,使得結果不至於溢出
它適用於每個類別相互獨立但互不排斥的情況:例如一幅圖可以同時包含一條狗和一只大象
output不是一個數,而是一個batch中每個樣本的loss,所以一般配合tf.reduce_mea(loss)使用
計算公式:
Python 程序:
importtensorflowas tf
import numpy asnp
def sigmoid(x):
return 1.0/(1+np.exp(-x))
# 5個樣本三分類問題,且一個樣本可以同時擁有多類
y = np.array([[1,0,0],[0,1,0],[0,0,1],[1,1,0],[0,1,0]]
logits = np.array([[12,3,2],[3,10,1],[1,2,5],[4,6.5,1.2],[3,6,1]])
y_pred = sigmoid(logits)
E1 = -y*np.log(y_pred)-(1-y)*np.log(1-y_pred)
print(E1) # 按計算公式計算的結果
sess =tf.Session()
y = np.array(y).astype(np.float64) # labels是float64的數據類型
E2 = sess.run(tf.nn.sigmoid_cross_entropy_with_logits(labels=y,logits=logits))
print(E2)
輸出的E1,E2結果相同
tf.nn.softmax_cross_entropy_with_logits(_sentinel=None, labels=None, logits=None, dim=-1, name=None)
argument:
_sentinel:本質上是不用的參數,不用填
logits:一個數據類型(type)是float32或float64;
shape:[batch_size,num_classes]
labels:和logits具有相同type和shape的張量(tensor),,是一個有效的概率,sum(labels)=1, one_hot=True(向量中只有一個值為1.0,其他值為0.0)
name:操作的名字,可填可不填
output:
loss,shape:[batch_size]
Note:
它對於輸入的logits先通過softmax函數計算,再計算它們的交叉熵,但是它對交叉熵的計算方式進行了優化,使得結果不至於溢出
它適用於每個類別相互獨立且排斥的情況,一幅圖只能屬於一類,而不能同時包含一條狗和一只大象
output不是一個數,而是一個batch中每個樣本的loss,所以一般配合tf.reduce_mean(loss)使用
計算公式:
Python程序:
importtensorflowas tf
import numpy asnp
def softmax(x):
sum_raw = np.sum(np.exp(x),axis=-1)
x1 = np.ones(np.shape(x))
for i inrange(np.shape(x)[0]):
x1[i] = np.exp(x[i])/sum_raw[i]
return x1
y = np.array([[1,0,0],[0,1,0],[0,0,1],[1,0,0],[0,1,0]])# 每一行只有一個1
logits =np.array([[12,3,2],[3,10,1],[1,2,5],[4,6.5,1.2],[3,6,1]])
y_pred =softmax(logits)
E1 = -np.sum(y*np.log(y_pred),-1)
print(E1)
sess = tf.Session()
y = np.array(y).astype(np.float64)
E2 = sess.run(tf.nn.softmax_cross_entropy_with_logits(labels=y,logits=logits))
print(E2)
輸出的E1,E2結果相同
tf.nn.sparse_softmax_cross_entropy_with_logits(_sentinel=None,labels=None,logits=None, name=None)
argument:
_sentinel:本質上是不用的參數,不用填
logits:一個數據類型(type)是float32或float64;
shape:[batch_size,num_classes]
labels: shape為[batch_size],labels[i]是{0,1,2,……,num_classes-1}的一個索引, type為int32或int64
name:操作的名字,可填可不填
output:
loss,shape:[batch_size]
Note:
它對於輸入的logits先通過softmax函數計算,再計算它們的交叉熵,但是它對交叉熵的計算方式進行了優化,使得結果不至於溢出
它適用於每個類別相互獨立且排斥的情況,一幅圖只能屬於一類,而不能同時包含一條狗和一只大象
output不是一個數,而是一個batch中每個樣本的loss,所以一般配合tf.reduce_mean(loss)使用
計算公式:
和tf.nn.softmax_cross_entropy_with_logits()一樣,只是要將labels轉換成tf.nn.softmax_cross_entropy_with_logits()中labels的形式
tf.nn.weighted_cross_entropy_with_logits(labels,logits, pos_weight, name=None)
計算具有權重的sigmoid交叉熵sigmoid_cross_entropy_with_logits()
argument:
_sentinel:本質上是不用的參數,不用填
logits:一個數據類型(type)是float32或float64;
shape:[batch_size,num_classes],單樣本是[num_classes]
labels:和logits具有相同的type(float)和shape的張量(tensor),
pos_weight:正樣本的一個系數
name:操作的名字,可填可不填
output:
loss,shape:[batch_size,num_classes]
計算公式:
GAN對抗生成網絡
上述這種博弈式的訓練過程,如果采用神經網絡作為模型類型,則被稱為生成式對抗網絡(GAN)。用數學語言描述整個博弈過程的話,就是:假設我們的生成模型是g(z),其中z是一個隨機噪聲,而g將這個隨機噪聲轉化為數據類型x,仍拿圖片問題舉例,這里g的輸出就是一張圖片。D是一個判別模型,對任何輸入x,D(x)的輸出是0-1范圍內的一個實數,用來判斷這個圖片是一個真實圖片的概率是多大。令Pr和Pg分別代表真實圖像的分布與生成圖像的分布,我們判別模型的目標函數如下:
類似的生成模型的目標是讓判別模型無法區分真實圖片與生成圖片,那么整個的優化目標函數如下:
這個最大最小化目標函數如何進行優化呢?最直觀的處理辦法就是分別對D和g進行交互迭代,固定g,優化D,一段時間后,固定D再優化g,直到過程收斂。
一個簡單的例子如下圖所示:假設在訓練開始時,真實樣本分布、生成樣本分布以及判別模型分別是圖中的黑線、綠線和藍線。可以看出,在訓練開始時,判別模型是無法很好地區分真實樣本和生成樣本的。接下來當我們固定生成模型,而優化判別模型時,優化結果如第二幅圖所示,可以看出,這個時候判別模型已經可以較好的區分生成數據和真實數據了。第三步是固定判別模型,改進生成模型,試圖讓判別模型無法區分生成圖片與真實圖片,在這個過程中,可以看出由模型生成的圖片分布與真實圖片分布更加接近,這樣的迭代不斷進行,直到最終收斂,生成分布和真實分布重合。
以上就是生成式對抗網絡的基本核心知識,下面我們看幾個在實際中應用的例子