當神經網路撞上薛定諤:混合密度網路入門
來自專欄 AI Butterfly
你一定聽說過『神經網路絡可以擬合任意連續函數』這句話。
沒錯,通過增加網路的隱藏層數量和隱藏層大小,你可以得到強大的學習網路,無論是二次三次函數,還是正弦餘弦,都可以用你的網路進行無限逼近。
好了,打住,今天我不是來教你逼近這種簡單函數的(這種內容應該在學習深度學習的第一天就已經解決了)。讓我們來考慮這個情況——當我們要擬合的『函數』,不止有一個值會怎樣?
嚴格來說,『多值函數』不是嚴謹的定義,良好定義的『函數』在其定義域內的每個輸入都對應一個輸出,而且只對應一個輸出[1]。然而實際上我們經常要處理一些多值問題,比如反三角函數(arcsin, arccos 等等),所以現在問題來了,當我們希望擬合的函數有多個輸出值的時候,我們的神經網路模型應該怎麼定義呢?
本文全文源碼以及可視化代碼由 jupyter notebook 編寫,文末有源代碼獲取方法。
第一個任務:單值函數擬合
讓我們先回憶單值函數是怎麼擬合的。
我們首先要設計一個函數,以產生點集,用於後面的擬合,我們選用的是正弦函數和線性函數的混合函數:
在生成數據的時候,還會加入一些隨機的雜訊。
NSAMPLE = 1000x_data = np.random.uniform(-10.5, 10.5, size=(1, NSAMPLE)).Tr_data = np.random.normal(size=(NSAMPLE, 1))y_data = np.sin(0.75 * x_data) * 7.0 + x_data * 0.5 + r_data
這些數據點可視化的結果是這樣的:
現在我們設計一個具有一個隱藏層的簡單網路進行擬合,我們希望用神經網路模型設計一個函數y = f(x)
,在一定區間上可以達到處處|f(x) - f(x)| < ε
,這個表達式是 L1 的形式,可以直接當做我們的 loss 函數。當然,我們也可以使用 L2 (平方差)來設計我們的 loss 函數。
我們將使用 tensorflow 來實現我們的網路:
# 輸入x = tf.placeholder(dtype=tf.float32, shape=[None,1])# 需要逼近的目標值y = tf.placeholder(dtype=tf.float32, shape=[None,1])# 定義一個 20 個節點的隱藏層hidden = tf.layers.dense(x, units=20, activation=tf.nn.tanh)# 輸出的預測值y_out = tf.layers.dense(hidden, units=1, activation=None)# 使用平方差的和作為損失值loss = tf.nn.l2_loss(y_out - y)# 定義優化器,目標是使損失值最小化train_step = tf.train.AdamOptimizer(learning_rate=0.1).minimize(loss)# 初始化網路sess = tf.Session()sess.run(tf.global_variables_initializer())# 訓練 1000 輪NEPOCH = 1000for i in range(NEPOCH): sess.run(train_step, feed_dict={x: x_data, y: y_data})
現在我們看一下結果,其中藍色是訓練數據,紅色是網路的輸出值。
可以看到,紅色的點幾乎完美地排成了一條階段上升的曲線。
交換坐標軸
現在我們進一步,將數據點的 x 軸與 y 軸交換,這樣我們就有了一個多值函數的輸入。在 python 中,交換兩個軸的數據非常簡單:
x_data, y_data = y_data, x_dataplt.figure(figsize=(8, 8))plt.plot(x_data, y_data, b*, alpha=0.3)plt.show()
現在我們的 x 可能會對應多個 y,如果再套用以前的方法,結果就不那麼理想了。
sess.run(tf.global_variables_initializer())NEPOCH = 1000for i in range(NEPOCH): sess.run(train_step, feed_dict={x: x_data, y: y_data}) x_test = np.arange(-10.5, 10.5, 0.1)x_test = np.reshape(x_test, newshape=(-1, 1))y_test = sess.run(y_out, feed_dict={ x: x_test })plt.figure(figsize=(8, 8))plt.plot(x_test, y_test, r*, x_data, y_data, b*, alpha=0.3)plt.show()
是的,我們原來的模型已經失效了,無論增加多少層,增大多少節點數,都不能擬合多值函數曲線。所以,現在我們應該怎麼辦?
混合密度網路:薛定諤的貓
在前面的代碼中,我們對於多值函數的預測走入了一個誤區:我們的神經網路最後的輸出是一個確定值,然而實際上我們需要的是多個『可能』的值。你也許會想,用神經網路輸出多個值並不難呀,只要定義最後的輸出層節點數大於 1 就可以了。是的,你可以定義一個多輸出的網路(比如 3),然後每次輸出 3 個預測值,然而這個網路的效果肯定是非常差的(你可以自己思考一下為什麼)。
現在我們換一種思路——假如我們輸出的不是一個值,而是目標值的一個『可能分布』,比如當 x=1 時,我們得到 y 有兩個取值 { 1, -1 },並且每個取值的概率都是 0.5。這就像薛定諤的那隻量子疊加態的貓一樣,我們得到的結果是一個概率分布,只有當我們進行一次『觀察』時,才會得到一個具體結果!
使用這個思想設計的網路就叫混合密度網路(Mixture Density Network),用處相當大。
你也許會問,概率究竟應該怎麼表示呢,難道是輸出一個類似 one-hot 表示的數組嗎?顯然我們不能使用 one-hot 來表示這個概率分布,因為我們輸出的值域是連續的浮點數,我們不可能用有限的數組來表達。這裡就要引入一個統計學裡面的很常見的概念了,就是高斯分布。
高斯分布的概率密度曲線表示為:
這裡面的參數只有兩個,一個是均值 mu,一個是標準差 simga,通過改變這兩個量,我們可以得到多樣的概率分布曲線。
而通過組合多個高斯概率分布,理論上我們可以逼近任意概率分布。比如將上面的三個分布按概率 1: 1: 1,混合為一個分布:
所以我們的思路就比較清晰了:我們要設計這麼一個網路,輸入 x ,輸出一個混合概率分布(即多個 mu 和 sigma 的組合值),而我們需要獲取真正的預測值的時候,就從這麼個混合概率分布中產生一個隨機值,多次取隨機值則可以得到所有 y 的可能值。混合概率網路的實現也很簡單,我們設計一個具有兩個隱藏層的網路,輸出層節點數為 12 * 3 個,可以表示為 12 個高斯分布的疊加,我們用前 12 個節點表示 12 個高斯分布疊加時各自的權重,而中間 12 個表示平均數 mu,最後 12 個表示標準差 sigma。
tf.reset_default_graph()x = tf.placeholder(shape=(None, 1), dtype=tf.float32, name=x)y = tf.placeholder(shape=(None, 1), dtype=tf.float32, name=y)mixture_size = 12hidden1 = tf.layers.dense(x, units=64, activation=tf.nn.relu, name=hidden1)hidden2 = tf.layers.dense(hidden1, units=128, activation=tf.nn.relu, name=hidden2)out = tf.layers.dense(hidden2, units=mixture_size * 3, activation=None, name=out)# 平均分割為 3 部分p, mu_out, sigma = tf.split(out, 3, 1)# 使用 softmax 保證概率和為 1p_out = tf.nn.softmax(p, name=prob_dist)# 使用 exp 保證標準差大於 0sigma_out = tf.exp(sigma, name=sigma)# 係數 1/(sqrt(2π))factor = 1 / math.sqrt(2 * math.pi)# 為了防止計算中可能出現除零的情況,當分母為零時,用一個極小值 epsilon 來代替epsilon = 1e-5tmp = - tf.square((y - mu_out)) / (2 * tf.square(tf.maximum(sigma_out, epsilon)))y_normal = factor * tf.exp(tmp) / tf.maximum(sigma_out, epsilon)
我們的 loss 函數不能是和之前一樣的平方差來表示,我們希望最大化真實的 y 值在混合概率密度中的概率,將經過標準化的 y_normal 與概率權重相乘,並取對數相加後取相反數,這個結果就是概率聯合分布的最大似然函數。
loss = tf.reduce_sum(tf.multiply(y_normal, p_out), axis=1, keep_dims=True)loss = -tf.log(tf.maximum(loss, epsilon))loss = tf.reduce_mean(loss)train_step = tf.train.AdamOptimizer(learning_rate=0.01).minimize(loss)
運行網路:
sess = tf.Session()sess.run(tf.global_variables_initializer())NEPOCH = 2500loss_vals = np.zeros(NEPOCH)for i in range(NEPOCH): _, loss_val = sess.run([train_step, loss], feed_dict={x : x_data, y: y_data}) loss_vals[i] = loss_val if i % 500 == 0: print({}/{} loss: {}.format(i, NEPOCH, loss_val))plt.figure(figsize=(8, 8))plt.plot(loss_vals)plt.show()
生成數據測試一下。
x_test = np.arange(-15, 15, 0.1)x_test = np.reshape(x_test, newshape=(-1, 1))p_val, sigma_val, mu_val = sess.run([p_out, sigma_out, mu_out], feed_dict={x: x_test})
注意,由於我們得到的是一個概率分布,所以還需要根據預測出的聯合概率密度來隨機生成具體的點。在測試中,我們對於每一個輸入 x 都成生 10 個隨機點。最終得到的生成圖像如下:
wow!完美,我們的混合密度網路真的擬合了這個多值函數,雖然有一點小瑕疵,實際上我自己通過增加節點數或者隱藏層後,生成的圖像非常好,你也可以動手試試。
本文代碼大量參考自[2]。如果對編程感興趣,歡迎加入我們的討論群:745478917,一起學習魔法。
以上全文以及可視化代碼已經製作成了 jupyter notebook 文件,可以關注公眾號『代碼律動codewaving』,回復 mdn 獲取下載連接。
[1] https://zh.wikipedia.org/wiki/%E5%A4%9A%E5%80%BC%E5%87%BD%E6%95%B0
[2] http://blog.otoro.net/2015/11/24/mixture-density-networks-with-tensorflow/
推薦閱讀:
※優化問題
※轉載:可測空間、測度空間及σ-代數
※集成學習
※概率論回顧
※語料庫語言學基礎知識:概率論2(連續變數、聯合分布)
TAG:神經網路 | 概率論 | TensorFlow |