1. 程式人生 > >TensorFlow TensorFlow的基礎用法

TensorFlow TensorFlow的基礎用法

非線性方程 false run 從零學習 好的 江湖 git 接下來 相關


原文: TensorFlow 優化實踐

寫在前面的話

在前面一章中說到了TensorFlow的基礎用法,這一章作為一個進階來聊聊神經網絡的具體的結構和參數問題,包括:

前饋神經網絡
循環神經網絡
神經網絡參數
大概就這三個中心內容。當然每個部分展開又是很多東西,但是好在這裏只是聊聊工具的使用。所以大概還是一個章節的內容。

前饋神經網絡

前饋神經網絡包括全鏈接網絡與卷積神經網絡,這兩者其實並沒有大的區別,一般情況下可以將卷積神經網絡看做是一個特例。

全鏈接網絡與卷積網絡

霍金說過科普書裏加一個公式就會少一半讀者,所以這裏用公式來直觀的說明二者的共同點:

A=??????????111111111111111111111111111111111111??????????6×6

前面說到全鏈接網絡是一個矩陣相乘的過程:
y=ActiveFunction(x?A)

這是一個全鏈接網絡,x是一個一維向量,全鏈接網絡的一個問題就是參數量有點大,設想一下,如果x,y的維度均為1000的話那麽顯然所需矩陣A的參數數量輕而易舉的就到達了106個,對於序列數據(比如一維的聲音數據、二維的圖形數據)而言顯然其特征很大程度上可能只與其臨近的數據有關,這就使得權值向量在計算的時候可以將用不到的部分省略:
A=??????????1111111111111111??????????6×6

上面的矩陣代表在隱藏層計算過程中只計算與其臨近的三個點,其他部分不進行計算,這樣使得矩陣A的參數數量得到了有效的減少,另外由於序列數據或圖像數據在計算過程中特征的相似性,使得各個部分的特征具有重復性,這樣在計算中就可以利用同一套權值:
A=[131313]1×3

這就是所謂的權值共享,通過這個過程,就可以使得全連接層中的參數量極大的減少。這個就是全連接網絡和卷積網絡的共同點。提一下,上面那個卷積核心可以計算的特征是“滑動平均”。
接下來看下函數的傅裏葉展開:
enter image description here

enter image description here

這是函數的傅裏葉展開,這裏隱藏了卷積神經網絡的一個思想,如果將序列x看成一個函數的話那麽其特征可以用一系列的簡單函數去表示,而相應的權值就是三角函數與原函數的積分,這個積分的過程就可以看做是卷積神經網絡的卷積的過程,前面矩陣的例子中卷積核心只有一個,能拾取的特征為“均值”,而卷積核心可以有多個,每個卷積核心與原有序列x均做一次卷積的話所得到的就是對應每個特征的權值序列。傅裏葉展開的一個特點就是n越大越接近於原函數,那麽到卷積神經網絡這裏就可以將期描述為卷積的特征越多越可以更好的描述數據的特征。

用tensorflow來直觀的理解一下卷積的過程:

import tensorflow as tf
import numpy as np
kn=np.ones([3,1,2],dtype=np.float32)
kn[:,:,0]=1/3
kernel = tf.Variable(kn)
x = tf.Variable(np.ones([1,1000,1],dtype=np.float32))
y = tf.nn.conv1d(x,kernel,stride=1,padding="SAME")
sess=tf.Session()
sess.run(tf.global_variables_initializer())
print("Out:",sess.run(y))
print("Shape:",np.shape(sess.run(y)))
這是一個長為1000的一維向量,其特征只有一個最前面的1代表輸入數據個數為1,卷積核心維度為[3,1,2],3代表卷積核心大小,也就是前面所說的能覆蓋多少數據點,1代表輸入數據特征數量,2是輸出數據特征數量,可以看到,對於第0個特征其卷積核心的權值為13也就是求取均值,第二個卷積核心權值為1,也就是對三個數據進行相加,可以看下最終結果:

Out: [[[ 0.66666669 2. ]
[ 1. 3. ]
[ 1. 3. ]
...,
[ 1. 3. ]
[ 1. 3. ]
[ 0.66666669 2. ]]]
可以看到,輸出數據特征並沒有什麽違反直覺的地方,輸出為兩個特征,第一個特征為均值,第二個為相加,由於padding邊界設置為“SAME”,也就是從第一個點開始作為輸出點,這就使得超出部分的權值、數據賦為0。

當然在神經網絡之中前面Variable是需要自適應的變化調整以提取特定數據的特征。

tensorflow構建前饋神經網絡

用tensorflow構建前饋神經網絡總共分為三種方式:

第一種:直接構建

import tensorflow as tf
#卷積層
def conv1d_layer(input_tensor, kernel_size, feature=2, active_function="relu", name=‘conv1d‘):
activ={"relu":tf.nn.relu,"sigmoid":tf.nn.sigmoid,"tanh":tf.nn.tanh}
with tf.variable_scope(name):
shape = input_tensor.get_shape().as_list()
kernel = tf.get_variable(‘kernel‘,
(kernel_size, shape[-1], feature),
dtype=tf.float32,
initializer=tf.constant_initializer(0))
b = tf.get_variable(‘b‘,
[feature],
dtype=tf.float32,
initializer=tf.constant_initializer(0))
out = tf.nn.conv1d(input_tensor,
kernel,
stride=1,
padding=‘SAME‘) + b
return activ[active_function](out)
#全鏈接層
def full_layer(input_tensor, out_dim=2, active_function="relu", name=‘full‘):
activ={"relu":tf.nn.relu,"sigmoid":tf.nn.sigmoid,"tanh":tf.nn.tanh}
with tf.variable_scope(name):
shape = input_tensor.get_shape().as_list()
W = tf.get_variable(‘W‘,
(shape[1], out_dim),
dtype=tf.float32,
initializer=tf.constant_initializer(0))
b = tf.get_variable(‘b‘,
[out_dim],
dtype=tf.float32,
initializer=tf.constant_initializer(0))
out = tf.matmul(input_tensor,W) + b
return activ[active_function](out www.huihuang178.cn )
上面是用tensorflow函數構建的全鏈接網絡與卷積神經網絡。函數調用過程可以使用:

net=full_layer(xx, featrue=2, active_function="relu", name="full_connect_1")
net=conv1d_layer(net,3, feature=2, active_function="relu", name="conv1d_1")
1
2
這樣就構成了單一層神經網絡的構建。

對於多層神經網絡而言可以將輸出數據進行進行卷積和全鏈接操作:

net = conv1d_layer(net, 3,www.huachenguoj.com feature=2, active_function="relu", name="conv1d_1")
net = conv1d_layer(net, 3, feature=2, active_function="relu", name="conv1d_2")
net=conv1d_layer(net, 3, feature=2, active_function="relu", name="conv1d_3")
1
2
3
當然每層的name要不同,否則會報錯。

第二種:通過contrib高層次API

前面用一些基本的API構建過程需要自行的去寫一些函數,但是這個函數是可以通過contrib裏面的函數去進行簡單的構建的:

import tensorflow.contrib.slim as slim
net = slim.conv2d(net, 3, 1, www.cbl157.com scope=‘conv1d_1‘)
net = slim.flatten(net)
net = slim.flatten(net)
net = slim.fully_connected(net, 2,
activation_fn=tf.nn.relu,
scope=‘outes‘,
reuse=False)

可以看到通過slim高層次api實際上可以簡化整個網絡的構建過程。這裏有個reuse參數,是復用參數。

通過keras構建神經網絡

keras其實是一個構建神經網絡比較方便的函數庫,其底層計算可以放到tensorflow之中:

from keras.layers import Input, Dense, Conv1D
net = Input(shape=(1000,1))
net = Conv1D(kernel_size=3,filters=1,padding="same")(net)
net=Activation("relu")(net)
1
2
3
4
當然本章以天tensorflow為主,所以這裏keras僅供了解。其實現起來比tensorflow相對簡單。

循環神經網絡

循環神經網絡構建起來相對於前饋神經網絡來說比較麻煩,當然最麻煩的是其訓練過程。
前面說到全鏈接網絡的過程為:

y=ActiveFunction(x?A)

而循環神經網絡與上一步的輸入相關,其實只是乘以了一個上一步的矩陣:
ht=ActiveFunction(ht?1?A1+xt?A2)yt=ht?A3

這就是一個最簡單的循環神經網絡,在此之上可以構建多層的神經網絡,定義yt與xt之間的關系為:
yt=funct(xt)zt=funct(yt)

這是構建的多層的循環神經網絡。其他的循環神經網絡結構是在其上進行的修改。只是參數更多,比如LSTM。LSTM輸入中加入了一個C矩陣用於保存記憶。
yt,ct=LSTM(xt,ct?1)
tensorflow構建循環神經網絡

上面說到一個循環神經網絡的層可以通過如下的函數構建:

cell = tf.contrib.rnn.BasicLSTMCell(
hidden_size, forget_bias=0.0, state_is_tuple=True,
reuse=not is_training)
1
2
3
多層卷積神經網絡構建:

cell = tf.contrib.rnn.MultiRNNCell(
[cell for _ in range(num_www.8555388.cn layers)], state_is_tuple=True)
1
2
前面說到循環神經網絡的數據輸入是一步一步輸入的,因此這裏在進行數據輸入的過程中也需要一步一步的進行數據的輸入:

for time_step in range(num_steps):
if time_step > 0: tf.get_variable_scope().reuse_variables()
(cell_output, state) = cell(inputs[:, time_step, :], state)
1
2
3
這裏有兩個地方需要解釋,第一個是reuse的問題,因為在第0步之後的變量與前面的變量都是復用的,因此這裏需要將其改變為復用模式。第二個是state,這個state是LSTM神經網絡結構所特有的,其目的在於保存“記憶”,而記憶量的多少則隨著神經網絡任務變化而自適應的變化,因此稱之為“長短記憶”,這時cell_output作為輸出就可以用於後續處理了。可以看到RNN的構建過程相比於前饋神經網絡而言是比較復雜的,這其中最關鍵的是因為其輸入是“一步一步”的進行輸入的。

神經網絡參數選取

這一節內容的話就比較多了,而且有很多“玄學”的感覺,因為參數的選取都帶有一定的主觀的因素。

自由參數數量與過擬合問題

第一個問題就是參數數量,參數數量的增多會引起一個較大的問題就是過擬合問題,用曲線擬合的方式來了解:

import tensorflow as tf
import numpy as
#定義函數展開階數
N = 6
x = tf.placeholder(dtype=tf.float32,shape=[1,None])
y = tf.placeholder(dtype=tf.float32,shape=[1,None])
comp = []
#函數展開項
for itr in range(N):
comp.append(tf.pow(x, itr))
x_v = tf.concat(comp,axis=0)
#定義展開系數
A = tf.Variable(tf.zeros([1, N]))
y_new = tf.matmul(A, x_v)
#定義loss函數
loss = tf.reduce_sum(tf.square(y-y_new))
#用梯度叠代法求解
train_step = tf.train.GradientDescentOptimizer(0.0005).minimize(loss)

sess = tf.Session()
sess.run(tf.global_variables_initializer())
#叠代9000次
for itr in range(90000):
sess.run(train_step,
feed_dict={x:np.array([[-1, 0, 0.5773502691896258, 1, 1.5, 2]]),
y:np.array([[0, 0, -0.3849, 0, 1.875, 4]])})
print(sess.run(A.value()))
#圖形繪制
import matplotlib.pyplot as plt
import matplotlib as mpl
mpl.style.use(‘seaborn-darkgrid‘)
lin = np.array([np.linspace(-1, 2, 100)])
ly =sess.run(y_new, feed_dict={x: lin})
plt.plot(lin[0], ly[0])
plt.scatter([-1, 0, 0.5773502691896258, 1, 1.5, 2],
[0, 0, -0.3849, 0, 1.875, 4])
plt.show()
來看下輸出圖形:

enter image description here

可以看到曲線完美的通過了每一個點,但是這種情況可能並非是一個“好”的結果,因為其對於訓練數據擬合過於好了,對於訓練集以外的數據可能預測效果並不理想。將函數取值範圍從-5到5再來繪制圖形:

import matplotlib.pyplot as plt
import matplotlib as mpl
mpl.style.use(‘seaborn-darkgrid‘)
lin = np.array([np.linspace(-5, 5, 100)])
ly =sess.run(y_new, feed_dict={x: lin})
plt.plot(lin[0], ly[0])
plt.scatter([-1, 0, 0.5773502691896258, 1, 1.5, 2],
[0, 0, -0.3849, 0, 1.875, 4])
plt.show()
1
2
3
4
5
6
7
8
9
enter image description here

在訓練數據之外曲線變形比較大,這會影響對於曲線的擬合效果。另一方面來說也就是對於數據的預測效果不理想。在這裏如果減少參數數量:

N=3
1
enter image description here

此時可能並未通過每一個點,但是所用的自由參數個數只有三個,但是對於可能的數據分布形式:

enter image description www.tianhengyl1.com here

現在用‘二次曲線’可以對數據進行更好的預測,因此解決過擬合問題最根本的問題就是增加訓練集數量。

BATCHSIZE

這個是一次輸入多少數據用於訓練的問題,下面的梯度方向是基於二維曲面擬合的LMS方法所言的,可以看到:

enter image description here

隨機梯度帶有一定的隨機性,雖然按照概率角度來說是可以收斂的,但是顯然通過60個數據進行梯度的估計所帶來的方向預測更加準確,因此BATCHSIZE存在的一個意義就是進行更好的梯度方向的預測,當然如果用整個訓練集來估計梯度方向的話是更好的。但是這裏面臨一個效率和內存的問題。這裏用17層Inception網絡來做個表格,實驗機器就是E3非GPU版本:

BATCHSIZE 最大內存 單次叠代時間
50 1534 8.7S
100 2096 19.2S
200 3392 37.3s
我想這個表格已經能說明問題了,雖然可以更好的估計梯度方向,但是計算時間和內存的消耗是呈線性增長的。

梯度參數

梯度參數的選取總的來說對於叠代的影響是比大的,過大的梯度參數會使得叠代發散:

enter image description here

那個藍線,在計算過程中不僅沒有逐漸減少反而越來越大,想理解發散過程比較簡單:

train_step = tf.train.GradientDescentOptimizer(500).minimize(loss)
1
把曲線擬合的梯度參數選為500,我這裏選擇的是0.5來觀察loss函數:

enter image description here

這個發散是呈指數增加的,很快就超出機器的數值邊界。

比較大的梯度參數可以參考上面那個圖的紅線,在兩個點之間不停震蕩。而過小的梯度參數會使得收斂緩慢。比如狠心一些將梯度參數選為5×10?15
tf.train.GradientDescentOptimizer(5e-15)
1
這時:

enter image description here

收斂過程變得極為緩慢,或者說基本沒有變化。

理智的做法梯度步長隨著叠代不斷的減少。

BATCHNORM層

前面說過BP算法中一個過程就是乘以矩陣:有興趣參考文章

但隨著層數增多所乘的矩陣也在不斷的增多,那麽就會遇到梯度消失的問題,就像雞湯說的每天忘記百分之一,一百天後就剩下37%了,這是一個指數的問題,BP算法也是如此,因此引入了BATCHNORM層,以解決上述問題。tensorflow中有相應的代碼:

tf.contrib.layers.batch_norm
1
形象化

一般在討論純理論問題的時候很多人都有個很不好的習慣就是找一個物理映射,這種物理映射可以幫助理解問題,但是可能會在之後的學習中並不如直接的數學理解更有幫助。
那麽再來看下整個的循環神經網絡的結構:

yt=f(xt,xt?1)

或者說:
y=f(x,t)

這種形式的問題在包含乘法操作的情況下稱之為非線性問題。可能用一個比較廣為人知的詞就是混沌,RNN和CNN網絡均可以對混沌吸引子進行重建。
下面這個是一個典型的混沌吸引子:
enter image description here

方程描述:

dxdt=40.0(y?x)+0.16xzdydt=55.0x?xz+20.0ydzdt=1.833z+xy+0.65x2
那麽數值求解上述方程的過程實際上就是類似於RNN的過程。

那麽三體運動方程實際上也是一個非線性方程的問題,並且三體運動並非是完全不可預測的。

enter image description here

很多時候其表現出概率的特征,這是可以通過統計分析得到的。

有興趣的,可以參考數值模擬過程用神經網絡重建吸引子。

【GitChat達人課】

前端惡棍 · 大漠窮秋 :《Angular 初學者快速上手教程 》
Python 中文社區聯合創始人 · Zoom.Quiet :《GitQ: GitHub 入味兒 》
前端顏值擔當 · 余博倫:《如何從零學習 React 技術棧 》
GA 最早期使用者 · GordonChoi:《GA 電商數據分析實踐課》
技術總監及合夥人 · 楊彪:《Gradle 從入門到實戰》
混元霹靂手 · 江湖前端:《Vue 組件通信全揭秘》
知名互聯網公司安卓工程師 · 張拭心:《安卓工程師跳槽面試全指南》

TensorFlow TensorFlow的基礎用法