TensorFlow是一個采用數據流圖,用於數值計算的開源軟件庫。自己接觸tensorflow比較的早,可是並沒有系統深入的學習過,現在TF在深度學習已經成了“標配”,所以打算系統的學習一遍。在本篇文章中主要介紹TF的基礎知識。。。
創建並運行圖###
首先創建 兩個變量
import tensorflow as tf
reset_graph()
x = tf.Variable(3, name="x")
y = tf.Variable(4, name="y")
f = x*x*y + y + 2
>>f
<tf.Tensor 'add_3:0' shape=() dtype=int32>
然而,f
中並沒有得到想要的結果,實際上上面的代碼並沒有真正的運行,它只是創建了一個計算圖(compution graph)
,並且定義的變量也並沒有被初始化,為了計算剛才定義的圖,我們需要開啟一個session
,然后初始化上面的變量。
sess = tf.Session()
sess.run(x.initializer)
sess.run(y.initializer)
result = sess.run(f)
sess.close() # 最終關閉這個session
>>print(result)
42
類似python的with open()
語法,我們還可以這樣寫
with tf.Session() as sess:
x.initializer.run() #sess.run(x.initializer)
y.initializer.run()
result = f.eval() #sess.run(f)
print(result)
注意上面代碼注釋的部分,這兩鍾方法是等價的,即
x.initializer.run() = tf.get_default_session().run(x.initializer)
選擇哪一種寫法主要取決於哪種方法簡單。那么如果我們由很多的變量,都需要進行初始化,再逐一初始化就顯得繁瑣了,這時候我們可以使用global_variables_initializer()
方法進行初始化。
init = tf.global_variables_initializer() # 准備init節點
with tf.Session() as sess:
init.run() #執行初始化動作
print(f.eval)
從上面的代碼可以看出,TF程序的運行過程分為兩個階段,
- 1.構建計算圖,構建能夠表示 機器學習模型的圖。
- 2.運行部分,通常是一個循環,重復地對訓練步驟進行評估,改善模型的參數。
管理計算圖###
當我們創建一個節點的時候, 被創建的節點自動的被添加到默認的計算圖中:
>>x.graph is tf.get_default_graph()
True
但是大多的時候,我們想分別管理相互獨立的graphs,這時候就要創建新的graph
graph = tf.Graph()
with graph.as_default():
x1 = tf.Variable(2)
print(x1.graph is graph) #True
print(x1.graph is tf.get_default_graph) #False
note:我們在使用Python shell試驗階段的時候,可能會出現輸出和我們的預期不一樣,這是因為多次運行導致默認的graph包含重復的nodes,一個解決方案是重啟shell,另外一個是使用tf.reset_default_graph()
。
節點的生命周期###
節點的生命周期也成為變量的生命周期,因為在TF中每一個變量在graph中都對應一個node,當我們創建一個node,TF會自動判斷該節點的依賴關系,例如下面這段代碼:
w = tf.constant(3)
x = w + 2
y = x + 5
z = x * 3
with tf.Session() as sess:
print(y.eval()) #10
print(z.eval()) #15
上面這段代碼定義了一個簡單的graph,並計算y和z的值,TF發現y依賴x、x依賴w。所以它依次計算w、x和y。再計算z的時候,發現需要計算x和w。最終這段代碼執行了兩次w和x。當執行完畢后所有的節點都被刪除
,除了Variable
值,variable的生命周期為整個session。也就是說variable的生命周期從initializer
開始,到sessionclose
結束。
上面這段代碼在正式的生產環境下效率是很低的,為了避免被重復計算,我們就需要告訴TF計算y和z在同一個graph中。下面是代碼:
with tf.Session() as sess:
y_val,z_val = sess.run([y,z]) ##
print(y_val)
print(z_val)
note:在單進程的TF程序中,多個session是不共用變量(數據)的,每一個session有着獨自的變量copy。在分布式TF程序中,變量是存儲在server,而不是在session中,所以多個session可以共享變量。
使用TF求解線性回歸###
1 正規方程求解####
在之前的文章使用sklearn進行數據挖掘介紹了使用sklean進行數據挖掘,這里我們使用TF來進行計算,不過為了方便我們直接使用sklean提供的數據集,跳過數據處理過程,直接使用正規方程(Normal Equation)方法求解\(\theta=(X^T\cdot X)^{-1}\cdot X^T\cdot y\)。類似Numpy,TF也提供了許多數據轉換的方法,在numpy數組被成為ndarray
,詳見掌握numpy,在TF中的多維數組被成為張量(tensors
)。
import numpy as np
from sklearn.datasets import fetch_california_housing
housing = fetch_california_housing()
m,n = housing.data.shape
data = np.c_[np.ones((m,1)),housing.data] #添加X0=1
X = tf.constant(data,dtype=tf.float32,name='X')
y = tf.constant(housing.target.reshape(-1,1),dtype=tf.float32,name='y')#轉為列向量(1D -> 2D)
X_T = tf.transpose(X)
theat = tf.matmul(tf.matmul(tf.matrix_inverse( tf.matmul(X_T,X)),X_T),y)
with tf.Session() as sess:
theat_hat = theat.eval()
print(theat_hat)
上面這段代碼可以完全使用Numpy替代,當然也可以使用sklearn的回歸方法,也是分分鍾搞定的事情,
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(housing.data, housing.target.reshape(-1, 1))
print(np.vstack((lin_reg.intercept_.reshape(-1, 1), lin_reg.coef_.T)))
使用TF的優勢是可以使用GPU進行運算。
note:reshape(-1,1)
的作用是將一維數組轉化為二維數組,參數-1表示unspecified
,表示會根據數組的長度作為這一維度的值。
2 使用批梯度下降求解####
上面使用的是正規方程求解,現在我們使用梯度下降方法求解,在求解之前我們需要現對數據做normalize,否則會導致收斂速度慢
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaled_data = scaler.fit_transform(housing.data)
data = np.c_[np.ones((m,1)),scaled_data] #添加X0=1
下面就是使用TF計算梯度下降了,最終的迭代公式為$ \theta^{'}=\frac{2}{m}X^T\cdot (X\cdot \theta-y) $,這里就不再贅述。
n_epoch = 100
learning_rate = 0.1
X = tf.constant(data,dtype=tf.float32,name='X')
y = tf.constant(housing.target.reshape(-1,1),dtype=tf.float32,name='y')
theta = tf.Variable(tf.random_uniform([n+1,1],-1,1),name='theta')
y_pred = tf.matmul(X,theta,name='prediction')
error = y_pred - y
mse = tf.reduce_mean(tf.square(error),name='mse') #Mean Squared Error
gradient = 2/m * tf.matmul(tf.transpose(X),error)
training_op = tf.assign(theta,theta - learning_rate * gradient)
init = tf.global_variables_initializer()
with tf.Session() as sess:
sess.run(init)
for epoch in range(n_epoch):
if epoch % 100 == 0:
print("Epoch", epoch, "MSE =", mse.eval())
sess.run(training_op)
>>print('best theta:',theta.eval())
Epoch 0 MSE = 9.16154
Epoch 100 MSE = 0.714501
Epoch 200 MSE = 0.566705
Epoch 300 MSE = 0.555572
Epoch 400 MSE = 0.548812
Epoch 500 MSE = 0.543636
Epoch 600 MSE = 0.539629
Epoch 700 MSE = 0.536509
Epoch 800 MSE = 0.534068
Epoch 900 MSE = 0.532147
'best theta:'
[[ 2.06855249],
[ 0.88740271],
[ 0.14401658],
[-0.34770882],
[ 0.36178368],
[ 0.00393812],
[-0.04269557],
[-0.66145277],
[-0.63752776]]
上面的代碼比較簡單,tf.random_uniform()
生成一個均勻分布,大小為(n+1,1),取值范圍(-1,1)。至於為什么n+1,是因為考慮到\(x_0=1\)。 tf.assign()
是創建一個新的節點,為variable更新值
2.1使用TF自動求導####
上面代碼通過手動計算損失函數導數的迭代公式計算出\(\theta\)的值,一個線性回歸手動算起來固然容易,但當模型為一個神經網絡再進行手動求導就會很吃力了。TF提供了自動求導功能,只需要將上面那段代碼的梯度部分替換成下面
gradient = tf.gradients(mse,[theta])[0]
上面的gradients()
方法能夠自動的將損失函數
針對參數進行求導(本例分別為\(mse\)和 \(\theta\)),
2.2使用優化器####
TF提供了計算梯度的方法,非常方便,不過還可以變得更加的方便。TF提供了許多優化方法,例如梯度下降優化器(Gradient Descent optimizer)。僅僅需要將gradient = 和training_op
替換為以下代碼:
optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)
training_op = optimizer.minimize(mse)
有許多的優化方法,例如MomentumOptimizer
等
3 向算法中輸入數據和mini-batch求導###
前面使用的是批梯度下降方法求解\(\theta\),這種方法比較適用與較小的數據集,如果數據集很大最好使用mini-batch梯度下降方法。我們需要將上面的代碼迭代部分的X
和y
替換為mini-batch,可以使用placeholder
來實現mini-batch,顧名思義這是使用占位符
的方法,它們並不參與運算,只有在你指定訓練的時候才輸出數據,舉一個例子:
A = tf.placeholder(tf.float32,shape=(None,3))
B = A + 5
with tf.Session() as sess:
test_b_1 = B.eval(feed_dict={A:[[1,2,3]]})
test_b_2 = B.eval(feed_dict={A:[[4,5,6],[7,8,9]]})
print(test_b_1) #[[ 6. 7. 8.]]
print(test_b_2) #[[ 9. 10. 11.] [ 12. 13. 14.]]
上面這段代碼使用placeholder()
創建一個占位符節點,並且指定其數值類型和輸入形狀,None
表示任意長度。接着又創建一個節點為B=A+5
。當計算B
的值時候,使用feed_dict
以字典的類型傳入到eval()
中。
實現mini-batch我們只需要修改少量代碼,首先我們需要先定義好參與迭代的X和y
X = tf.placeholder(tf.float32, shape=(None, n + 1), name="X")
y = tf.placeholder(tf.float32, shape=(None, 1), name="y")
然后定義需要迭代的次數、學習率、batch的大小以及batch的個數還有目標函數
learning_rate = 0.01
batch_size = 100
n_batches = int(np.ceil(m / batch_size))
theta = tf.Variable(tf.random_uniform([n + 1, 1], -1.0, 1.0, seed=42), name="theta") #X0
y_pred = tf.matmul(X, theta, name="predictions")
error = y_pred - y
mse = tf.reduce_mean(tf.square(error), name="mse")
optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)
training_op = optimizer.minimize(mse)
init = tf.global_variables_initializer()
最后就是計算過程,mini-batch逐個被送到訓練算法中
def fetch_batch(epoch, batch_index, batch_size):
np.random.seed(epoch * n_batches + batch_index)
indices = np.random.randint(m, size=batch_size)
X_batch = data[indices]
y_batch = housing.target.reshape(-1, 1)[indices]
return X_batch, y_batch
with tf.Session() as sess:
sess.run(init)
for epoch in range(n_epochs):#迭代的次數
for batch_index in range(n_batches):
X_batch, y_batch = fetch_batch(epoch, batch_index, batch_size)
sess.run(training_op, feed_dict={X: X_batch, y: y_batch})
best_theta = theta.eval()
模型的持久化###
類似sklearn,模型訓練好之后我們可以將model持久化,以備以后的使用TF提供了Saver()
方法,
init = tf.global_variables_initializer()
saver = tf.train.Saver()
with tf.Session() as sess:
sess.run(init)
for epoch in range(n_epochs):
if epoch % 100 == 0:
print("Epoch", epoch, "MSE =", mse.eval()) # 保存運行過程
save_path = saver.save(sess, "/tmp/my_model.ckpt")
sess.run(training_op)
best_theta = theta.eval()
save_path = saver.save(sess, "/tmp/my_model_final.ckpt")#保存最后的結果
模型的加載也是很簡單的:
with tf.Session() as sess:
saver.restore(sess, "/tmp/my_model_final.ckpt")
best_theta_restored = theta.eval()