注:因為畢業論文需要用到相關知識,借着 TF 2.0 發布的時機,重新撿起深度學習。在此,也推薦一下優達學城與 TensorFlow 合作發布的TF 2.0入門課程,下面的例子就來自該課程。
原文發布於博客園:https://www.cnblogs.com/Belter/p/10626418.html
本文中所有代碼都在文末第二個鏈接中,轉載請注明出處!
機器學習與深度學習
深度學習是機器學習的一個分支,當下也是該領域發展最快、最受關注的一個分支。上周剛剛公布的2018年圖靈獎就頒發給了對深度學習的發展做出了重要貢獻的三位科學家:Yoshua Bengio、Geoffrey Hinton 和 Yann LeCun。相對於傳統的機器學習算法,深度學習最大的區別在於:在訓練的過沖中,由神經網絡自主學習數據的表示,而不需要人工做特征工程。
之前寫了多篇與機器學習有關的博文,以下幾篇與深度學習有關,可以幫助理解基礎知識:
因為深度學習是機器學習的一個分支,與傳統的機器學習方法有非常多的相似之處。概括如下:
- 解決的主要問題:分類(二分類或多分類),回歸,聚類;
- 學習的基本過程:在有監督學習(分類和回歸問題)中,都是輸入“數據 + 標簽(答案)”,獲得“算法”(從數據到答案的某種映射關系);
- 訓練過程都是對代價函數的優化過程;
- 都面臨過擬合的挑戰,因此都需要正則化方面的技術;
- 都可以使用梯度下降法來優化參數.
當然深度學習中,也有一些有別於傳統機器學習的主題:
- 網絡的結構:層數,每一層的神經單元數;
- 激活函數:作用於隱藏層神經元,使得數據的變化過程從線性到非線性;
- 反向傳播:用於多層神經網絡中誤差(或梯度)從網絡的輸出層向輸入層傳播;
- 其他特殊網絡結構:CNN 中的卷積、長短期記憶網絡、編碼器和解碼器等.
多層感知機
多層感知機(multilayer percepton, MLP)也叫深度前饋網絡或前饋神經網絡,是典型的深度學習模型。其目的是近似某個函數$f^*$。當前饋神經網絡被拓展成包含反饋連接時,它們被稱為循環神經網絡(recurrent neural network, RNN)。
關於多層感知機的基礎知識,可以參考我之前的博客——【機器學習】神經網絡實現異或(XOR),或下面的兩篇博主"python27"的學習筆記:
一個簡單的例子
雖然接觸到深度學習的知識已經很長時間了,但是在平時遇到分類或是回歸相關的問題,還是會優先選擇傳統的機器學習算法。有時候是因為數據量比較小,但是還有一個比較重要的原因是自己實踐太少,對於深度學習這個工具,用起來還不順手。之前學習的時候,在TensorFlow、Pytouch和Keras之間很難做出選擇。現在TF 2.0借鑒Keras中的API風格,既保持了性能的優勢,也更加簡單易學。加上最近剛剛發布的課程,因此就決定選擇TF 2.0作為自己以后練習深度學習算法的工具。
將攝氏度轉換成華氏度
溫度的不同單位之間的轉換有明確的定義,知道其中一種單位的溫度值可以通過下面的公式精確的計算出另一種單位下的溫度值。
$$ f = c \times 1.8 + 32 $$
如果我們已知該公式,將該公式寫成一個函數,輸入為$c$,輸出為$f$,那么這就是一個入門級的編程練習題。
但是假如我們現在知道幾個數值對,即輸入和答案,該公式並不知道。此時就可以利用機器學習的方法,求出一個近似的從$c$到$f$的映射關系。
下面是代碼:
1 from __future__ import absolute_import, division, print_function 2 import tensorflow as tf 3 4 import numpy as np 5 6 # 輸入的攝氏溫度以及其對應的華氏溫度,前面兩個故意寫錯了 7 celsius_q = np.array([-40, -10, 0, 8, 15, 22, 38], dtype=float) 8 fahrenheit_a = np.array([-40, 14, 32, 46, 59, 72, 100], dtype=float) 9 10 for i,c in enumerate(celsius_q): 11 print("{} degrees Celsius = {} degrees Fahrenheit".format(c, fahrenheit_a[i])) 12 13 # 輸入后連接了只有一個神經單元的隱藏層 14 l0 = tf.keras.layers.Dense(units=1, input_shape=[1]) 15 16 # 將網絡層添加到序列模型中 17 model = tf.keras.Sequential([l0]) 18 19 # 編譯神經網絡模型 20 model.compile(loss='mean_squared_error', 21 optimizer=tf.keras.optimizers.Adam(0.1)) 22 23 # 訓練模型 24 history = model.fit(celsius_q, fahrenheit_a, epochs=500, verbose=False) 25 print("Finished training the model")
在這個例子中,包括以下要素:
- 訓練數據:一共有7個樣本(輸入 + 答案),有兩個樣本的值不准確(在實際項目中,有可能是因為測量或記錄導致的誤差);
- 層(layers):神經網絡是以層為基本組成單位的,每層有兩個最基本的參數units和activation,分別表示該層神經單元的個數和激活函數(這里沒有設置第二個參數就表示不對該層的神經單元進行任何變換);
- 層的類型:層的不同類型,代表了層之間不同的連接方式,這里使用的是全連接層(Dense),表示各層之間所有點都直接相連;
- 模型:組織不同“層”的方式,這里使用的序列模型(Sequential)按照層的順序從輸入到輸出疊加各個層;
- 編譯(compile):訓練之前必須編譯,在編譯的時候需要指定兩個非常重要的參數——loss和optimizer,loss就是代價函數,optimizer是訓練模型的方法;
- 訓練模型(fit):必要參數為輸入數據,對於的標簽(答案),迭代次數(epochs)等.
上面15行和18行通常會寫在一起:
model = tf.keras.Sequential([ tf.keras.layers.Dense(units=1, input_shape=[1]) ])
模型訓練的過程中,損失函數的值會隨着訓練次數發生變化。這些變化由fit函數返回,記錄在history中。畫出來如下:
import matplotlib.pyplot as plt plt.xlabel('Epoch Number') plt.ylabel("Loss Magnitude") plt.plot(history.history['loss'])
圖1:loss隨着訓練次數的增加而減小
這個模型非常簡單,如果不算輸入層,整個模型只有1層:輸入層連接了只有一個神經單元的層l0,該層處理之后的數據直接作為輸出值。
因此該模型的網絡結構如下:
圖2:上面例子中神經網絡的結構
上圖中的l0只有一個神經單元,另外還有一個偏置單元(輸入值x上方的藍色點,除了輸入層外,每層默認都會有一個作用於該層的偏置單元)。因此,從輸入到輸出可以表示為:
$$y = x*w + b$$
神經網絡的結構本質上是對最終模型的一種假設,這里的假設恰好與真實模型完全相同:線性模型,兩個參數。但是在完全不知道真實模型的情況下,大部分時候模型的容量可能都遠超過了真實模型,表現在參數多(模型的自由度大),結構復雜,添加了非線性變換的激活函數,因此容易導致過擬合。
訓練完成后,可以查看模型各層的參數(偏置單元雖然畫在了輸入層上方,但還是算作l0層的參數):
print("These are the layer variables: {}".format(l0.get_weights()))
下面是輸出:
These are the layer variables: [array([[1.8220955]], dtype=float32), array([29.117624], dtype=float32)]
第一個值(1.822,x的參數)與真實值1.8非常接近,第二個值(29.118,偏置單元的取值)與真實值32也比較接近。由此可見,雖然訓練數據中包含兩個誤差比較大的點,但是最終模型還是比較准確的找到了這兩組數據之間的基本規律:$y = 1.82x + 29.12$
訓練好模型之后,可以使用下面的方法預測新值:
print(model.predict([100.0]))
輸出為"[[211.32718]]",該值與真實值212非常接近。
以上就是假設在不知道溫度兩種單位之間的轉換公式的情況下,通過非常簡單的神經網絡學習到兩組數據之間關系的過程。
學習到與真實的轉換公式如此接近的結果,強烈依賴於最開始設定的網絡結構(一種先驗,對最終模型的假設)。在不知道真實模型的情況下,通常會假設一個比較復雜的網絡結構。此時模型參數多,自由度大,模型的容量和搜索空間也是遠超過了真實模型,此時真實模型一定被包含其中,但不一定能找到,或者找到非常接近全局最優解的局部最優解。
下面是另一種網絡結構的假設:
1 l0 = tf.keras.layers.Dense(units=4, input_shape=[1]) 2 l1 = tf.keras.layers.Dense(units=4) 3 l2 = tf.keras.layers.Dense(units=1) 4 model = tf.keras.Sequential([l0, l1, l2]) 5 model.compile(loss='mean_squared_error', optimizer=tf.keras.optimizers.Adam(0.1)) 6 model.fit(celsius_q, fahrenheit_a, epochs=500, verbose=False) 7 print("Finished training the model") 8 print(model.predict([100.0])) 9 print("Model predicts that 100 degrees Celsius is: {} degrees Fahrenheit".format(model.predict([100.0]))) 10 print("These are the l0 variables: {}".format(l0.get_weights())) 11 print("These are the l1 variables: {}".format(l1.get_weights())) 12 print("These are the l2 variables: {}".format(l2.get_weights()))
輸出如下:
Finished training the model [[211.74747]] Model predicts that 100 degrees Celsius is: [[211.74747]] degrees Fahrenheit These are the l0 variables: [array([[ 0.45070484, -0.611826 , 0.1898421 , 0.0913914 ]], dtype=float32), array([ 3.4355907, -3.5666099, -2.7151687, 3.45835 ], dtype=float32)] These are the l1 variables: [array([[ 0.22470416, 1.2858697 , 0.81416523, 0.359274 ], [ 0.38035178, -0.86285967, 0.34824482, -0.30044237], [-0.16603428, -0.5080208 , -0.27835467, -0.6047108 ], [-0.5545173 , 0.9019376 , 0.19518667, -0.2925983 ]], dtype=float32), array([-3.1969943, 3.4892511, 1.7240645, 3.4472196], dtype=float32)] These are the l2 variables: [array([[-0.30633917], [ 1.4714689 ], [ 0.31956905], [ 0.41886073]], dtype=float32), array([3.3831294], dtype=float32)]
可以看到新模型預測100攝氏度的結果也非常接近其真實的華氏溫度。此時模型的結構如下:
圖3:第二個模型的網絡結構
不算輸入層,這個模型一共有3層。其中兩個隱藏層各自含有4個神經單元(藍色點表示每層的偏置單元)。除了輸入層外,其他每個神經單元的取值都由直接與之相連的點(及對應的參數)決定:例如l1層從上往下數第2個橙色點的取值由上一層4個點(及對應的參數)的取值和偏置單元的取值決定。可以概括的表示如下:
$$y' = x·w + b$$
其中$x$表示上一層的所有神經單元構成的向量,$w$是連接$y'$與上一層所有單元的參數,$b$是偏置單元。因為這里還是沒有設置激活函數,因此每一層中的各個單元都是上一層所有單元取值的線性組合。
雖然第二個模型也能很好的完成預測任務,但是與真實的轉換公式之間差異巨大,而且可解釋性也變差了。
Reference
https://cn.udacity.com/course/intro-to-tensorflow-for-deep-learning--ud187
https://github.com/OnlyBelter/examples/blob/master/courses/udacity_intro_to_tensorflow_for_deep_learning/l02c01_celsius_to_fahrenheit.ipynb