https://www.wandouip.com/t5i183316/
引言
原來引用過一個段子,這里還要再引用一次。是關於蘋果的。大意是,蘋果發布了新的開發語言Swift,有非常多優秀的特征,於是很多時髦的程序員入坑學習。不料,經過一段頭腦體操一般的勤學苦練,發現使用Swift做開發,不僅要學習Swift,還要學習Swift2、Swift3、Swift4...
后來我發現,這個段子很有普遍性,並非僅僅蘋果如此,今天的TensorFlow 2.0也有點這樣的趨勢。以至於我不得不專門寫一個課程的續集,來面對使用新版本軟件開始機器學習的讀者。
事實上大多具有革命性的公司都是這樣,一方面帶來令人興奮的新特征,另一方面則是高企不落的學習成本。
《從鍋爐工到AI專家》一文中,已經對機器學習的基本概念做了很詳細的介紹。所以在這里我們就省掉閑言絮語,直接從TensorFlow2.0講起。
當然即便沒有看過這個系列,假設你對TensorFlow 1.x很熟悉,也可以直接通過閱讀本文,了解從TensorFlow 1.x遷移至2.x的知識。
如果你不了解機器學習的概念,試圖通過直接學習TensorFlow 2.0開始AI開發,那可能比較困難。TensorFlow只是工具。沒有技能,只憑工具,你恐怕無法踏上旅程。
安裝
截至本文寫作的時候,TensorFlow 2.0尚未正式的發布。pip倉庫中仍然是1.13穩定版。所以如果想開始TensorFlow 2.0的學習,需要指定版本號來安裝。此外由於Python2系列將於2020年元月停止官方維護,本文的示例使用Python3的代碼來演示:
$ pip3 install tensorflow==2.0.0-alpha0
(注: 上面$是Mac/Linux的提示符,假如用Windows,你看到的提示符應當類似C:\Users\Administrator>這樣子。)
如果希望使用GPU計算,安裝的預先准備會麻煩一些,請參考這篇文檔:https://www.tensorflow.org/install/gpu。主要是安裝CUDA/cuDNN等計算平台的工具包。其中CUDA可以使用安裝程序直接安裝。cuDNN是壓縮包,如果不打算自己編譯TensorFlow的話,放置到CUDA相同目錄會比較省事。
這里提醒一下,除非自己編譯TensorFlow,否則一定使用CUDA 10.0的版本,低了、高了都不成,因為官方的2.0.0-alpha0使用了CUDA 10.0的版本編譯。
此外TensorFlow的安裝請使用如下命令:
$ pip3 install tensorflow-gpu==2.0.0-alpha0
安裝完成后,可以在Python的交互模式,來確認TensorFlow正常工作:
$ python3
Python 3.7.2 (default, Feb 13 2019, 13:59:29)
[Clang 10.0.0 (clang-1000.11.45.5)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import tensorflow as tf
>>> tf.__version__
'2.0.0-alpha0'
>>>
本文中還會用到幾個第三方的python擴展庫,也是在機器學習中非常常用的,建議一起安裝:
$ pip3 install numpy matplotlib pillow pandas seaborn sklearn
第一個例子:房價預測
本示例中的源碼來自於《從鍋爐工到AI專家》系列2,使用了最簡單的線性函數來做房價預測。原始TensorFlow 1.x/ Python 2.x代碼如下:
#!/usr/bin/env python
# -*- coding=UTF-8 -*-
#本代碼在mac電腦,python2.7環境測試通過
#第一行是mac/Linux系統腳本程序的標志,表示從環境參量中尋找python程序解釋器來執行本腳本
#省去了每次在命令行使用 python <腳本名> 這樣的執行方式
#第二行表示本腳本文本文件存盤使用的代碼是utf-8,並且字符串使用的編碼也是utf-8,
#在本源碼中,這一點其實沒有什么區別,但如果需要中文輸出的時候,這一行就必須要加了。
#引入TensorFlow庫
import tensorflow as tf
#引入數值計算庫
import numpy as np
#使用 NumPy 生成假數據集x,代表房間的平米數,這里的取值范圍是0-1的浮點數,
#原因請看正文中的說明,屬於是“規范化”之后的數據
# 生成的數據共100個,式樣是100行,每行1個數據
x = np.float32(np.random.rand(100,1))
#我們假設每平米0.5萬元,基礎費用0.7萬,這個數值也是規范化之后的,僅供示例
#最終運行的結果,應當求出來0.5/0.7這兩個值代表計算成功
#計算最終房價y,x和y一同當做我們的樣本數據
# np.dot的意思就是向量x * 0.5
y = np.dot(x,0.5) + 0.7
#---------------------------------數據集准備完成
#以下使用TensorFlow構建數學模型,在這個過程中,
#直到調用.run之前,實際上都是構造模型,而沒有真正的運行。
#這跟上面的numpy庫每一次都是真正執行是截然不同的區別
# 請參考正文,我們假定房價的公式為:y=a*x+b
#tf.Variable是在TensorFlow中定義一個變量的意思
#我們這里簡單起見,人為給a/b兩個初始值,都是0.3,注意這也是相當於規范化之后的數值
b = tf.Variable(np.float32(0.3))
a = tf.Variable(np.float32(0.3))
#這是定義主要的數學模型,模型來自於上面的公式
#注意這里必須使用tf的公式,這樣的公式才是模型
#上面使用np的是直接計算,而不是定義模型
# TensorFlow的函數名基本就是完整英文,你應當能讀懂
y_value = tf.multiply(x,a) + b
# 這里是代價函數,同我們文中所講的唯一區別是用平方來取代求絕對值,
#目標都是為了得到一個正數值,功能完全相同,
#平方計算起來會更快更容易,這種方式也稱為“方差“
loss = tf.reduce_mean(tf.square(y_value - y))
# TensorFlow內置的梯度下降算法,每步長0.5
optimizer = tf.train.GradientDescentOptimizer(0.5)
# 代價函數值最小化的時候,代表求得解
train = optimizer.minimize(loss)
# 初始化所有變量,也就是上面定義的a/b兩個變量
init = tf.global_variables_initializer()
#啟動圖
sess = tf.Session()
#真正的執行初始化變量,還是老話,上面只是定義模型,並沒有真正開始執行
sess.run(init)
#重復梯度下降200次,每隔5次打印一次結果
for step in xrange(0, 200):
sess.run(train)
if step % 5 == 0:
print step, sess.run(loss),sess.run(a), sess.run(b)
代碼保留了原始的注釋,希望如果概念已經沒有問題的話,可以讓你不用跑回原文去看詳細講解。
程序使用numpy生成了一組樣本集,樣本集是使用線性函數生成的。隨后使用TensorFlow學習這些樣本,從而得到線性函數中未知的權重(Weight)和偏移(Bias)值。
原文中已經說了,這個例子並沒有什么實用價值,只是為了從基礎開始講解“機器學習”的基本原理。
使用2.0中的v1兼容包來沿用1.x代碼
TensorFlow 2.0中提供了tensorflow.compat.v1代碼包來兼容原有1.x的代碼,可以做到幾乎不加修改的運行。社區的contrib庫因為涉及大量直接的TensorFlow引用代碼或者自己寫的Python擴展包,所以無法使用這種模式。TensorFlow 2.0中也已經移除了contrib庫,這讓人很有點小遺憾的。
使用這種方式升級原有代碼,只需要把原有程序開始的TensorFlow引用:
import tensorflow as tf
替換為以下兩行就可以正常的繼續使用:
import tensorflow.compat.v1 as tf
tf.disable_v2_behavior()
其它代碼無需修改。個人覺得,如果是穩定使用中、並且沒有重構意願的代碼,這種方式算的上首選。
使用遷移工具來自動遷移1.x代碼到2.0
TensorFlow 2.0中提供了命令行遷移工具,來自動的把1.x的代碼轉換為2.0的代碼。工具使用方法如下(假設我們的程序文件名稱為first-tf.py):
tf_upgrade_v2 --infile first-tf.py --outfile first-tf-v2.py
遷移工具還可以對整個文件夾的程序做升級,請參考工具自身的幫助文檔。
使用遷移工具升級的代碼,實質上也是使用了tensorflow.compat.v1兼容包來提供在TensorFlow 2.0環境中執行1.x的代碼。這里貼出自動轉換后的新代碼供你對比參考:
#引入TensorFlow庫
import tensorflow as tf
#import tensorflow.compat.v1 as tf
tf.compat.v1.disable_v2_behavior()
#引入數值計算庫
import numpy as np
#使用 NumPy 生成假數據集x,代表房間的平米數,這里的取值范圍是0-1的浮點數,
#原因請看正文中的說明,屬於是“規范化”之后的數據
# 生成的數據共100個,式樣是100行,每行1個數據
x = np.float32(np.random.rand(100,1))
#我們假設每平米0.5萬元,基礎費用0.7萬,這個數值也是規范化之后的,僅供示例
#最終運行的結果,應當求出來0.5/0.7這兩個值代表計算成功
#計算最終房價y,x和y一同當做我們的樣本數據
# np.dot的意思就是向量x * 0.5
y = np.dot(x,0.5) + 0.7
#---------------------------------數據集准備完成
#以下使用TensorFlow構建數學模型,在這個過程中,
#直到調用.run之前,實際上都是構造模型,而沒有真正的運行。
#這跟上面的numpy庫每一次都是真正執行是截然不同的區別
# 請參考正文,我們假定房價的公式為:y=a*x+b
#tf.Variable是在TensorFlow中定義一個變量的意思
#我們這里簡單起見,人為給a/b兩個初始值,都是0.3,注意這也是相當於規范化之后的數值
b = tf.Variable(np.float32(0.3))
a = tf.Variable(np.float32(0.3))
#這是定義主要的數學模型,模型來自於上面的公式
#注意這里必須使用tf的公式,這樣的公式才是模型
#上面使用np的是直接計算,而不是定義模型
# TensorFlow的函數名基本就是完整英文,你應當能讀懂
y_value = tf.multiply(x,a) + b
# 這里是代價函數,同我們文中所講的唯一區別是用平方來取代求絕對值,
#目標都是為了得到一個正數值,功能完全相同,
#平方計算起來會更快更容易,這種方式也稱為“方差“
loss = tf.reduce_mean(input_tensor=tf.square(y_value - y))
# TensorFlow內置的梯度下降算法,每步長0.5
optimizer = tf.compat.v1.train.GradientDescentOptimizer(0.5)
# 代價函數值最小化的時候,代表求得解
train = optimizer.minimize(loss)
# 初始化所有變量,也就是上面定義的a/b兩個變量
init = tf.compat.v1.global_variables_initializer()
#啟動圖
sess = tf.compat.v1.Session()
#真正的執行初始化變量,還是老話,上面只是定義模型,並沒有真正開始執行
sess.run(init)
#重復梯度下降200次,每隔5次打印一次結果
for step in range(0, 200):
sess.run(train)
if step % 5 == 0:
print(step, sess.run(loss),sess.run(a), sess.run(b))
轉換之后,代碼中的注釋部分會完美的保留,喜歡用代碼來代替文檔的程序員可以放心。所有2.0中變更了的類或者方法,轉換工具將使用tensorflow.compat.v1中的對應類或方法來替代,比如:
optimizer = tf.compat.v1.train.GradientDescentOptimizer(0.5)
train = optimizer.minimize(loss)
init = tf.compat.v1.global_variables_initializer()
所以從本質上,這種方式跟第一種方法,采用tensorflow.compat.v1包作為tensorflow替代包的方式是完全相同的。
編寫原生的TensorFlow 2.0程序
推薦的演進方式,當然還是學習TensorFlow 2.0的相關特征,重構原有代碼為新版本代碼才是正路。平心而論,畢竟絕大多數系統的升級都是為了提供更多功能和降低使用門檻。TensorFlow 2.0也是大幅的降低了使用門檻的。大多數的工作比起1.x版本來,都能使用更少的代碼量來完成。
首先了解一下TensorFlow 2.0同1.x之間的重要區別:
- 在API層面的類、方法有了較大的變化,這個需要在使用中慢慢熟悉
- 取消了Session機制,每一條命令直接執行,而不需要等到Session.run
- 因為取消了Session機制,原有的數學模型定義,改為使用Python函數編寫。原來的feed_dict和tf.placeholder,成為了函數的輸入部分;原來的fetches,則成為了函數的返回值。
- 使用keras的模型體系對原有的TensorFlow API進行高度的抽象,使用更容易
- 使用tf.keras.Model.fit來替代原有的訓練循環。
正常情況下,最后一項tf.keras.Model.fit能夠大大的降低訓練循環的代碼量。但在本例中,我們模擬了一個現實中並不適用的例子,keras中並未對這種情形進行優化。所以在本例中反而無法使用tf.keras.Model.fit(實際上一定要使用也是可以的,不過要自定義模型,工作量更不划算)。因此本例中仍然要自己編寫訓練循環。並且因為2.0中API的變化,代碼更復雜了。不過相信我,等到比較正式應用中,使用神經網絡、卷積等常用算法,代碼就極大的簡化了。
使用TensorFlow 2.0原生代碼的程序代碼如下:
#!/usr/bin/env python3
#上面一行改為使用python3解釋本代碼
#引入python新版本的語言特征
from __future__ import absolute_import, division, print_function
#引入TensorFlow庫,版本2.0
import tensorflow as tf
#引入數值計算庫
import numpy as np
#使用 NumPy 生成假數據集x,代表房間的平米數,這里的取值范圍是0-1的浮點數,
#原因請看正文中的說明,屬於是“規范化”之后的數據
# 生成的數據共100個,式樣是100行,每行1個數據
x = np.float32(np.random.rand(100,1))
#我們假設每平米0.5萬元,基礎費用0.7萬,這個數值也是規范化之后的,僅供示例
#最終運行的結果,應當求出來0.5/0.7這兩個值代表計算成功
#計算最終房價y,x和y一同當做我們的樣本數據
y = np.dot(x,0.5) + 0.7
#---------------------------------數據集准備完成
# 請參考正文,我們假定房價的公式為:y=a*x+b
#定義tensorflow變量,a是權重,b是偏移
b = tf.Variable(np.float32(0.3))
a = tf.Variable(np.float32(0.3))
#以上代碼基本同tensorflow1.x版本一致
#以下有了區別
#使用python語言定義數學模型,模型來自於上面的公式
#上面使用np的是直接計算得到訓練樣本,而不是定義模型
#模型中並非必須使用tensorflow的計算函數來代替python的乘法運算
@tf.function
def model(x):
return a*x+b
#定義代價函數,也是python函數
def loss(predicted_y, desired_y):
return tf.reduce_sum(tf.square(predicted_y - desired_y))
# TensorFlow內置Adam算法,每步長0.1
optimizer = tf.optimizers.Adam(0.1)
# 還可以選用TensorFlow內置SGD(隨機最速下降)算法,每步長0.001
#不同算法要使用適當的步長,步長過大會導致模型無法收斂
#optimizer = tf.optimizers.SGD(0.001)
#重復梯度下降200次,每隔5次打印一次結果
for step in range(0, 200):
with tf.GradientTape() as t:
outputs = model(x) #進行一次計算
current_loss = loss(outputs, y) #得到當前損失值
grads = t.gradient(current_loss, [a, b]) #調整模型中的權重、偏移值
optimizer.apply_gradients(zip(grads,[a, b])) #調整之后的值代回到模型
if step % 5 == 0: #每5次迭代顯示一次結果
print( "Step:%d loss:%%%2.5f weight:%2.7f bias:%2.7f " %
(step,current_loss.numpy(), a.numpy(), b.numpy()))
程序在升級中所做的修改和特殊的處理,都使用注釋保留在了源碼中。我覺得這種方式比打散摘出來講解的能更透徹。
最后看一下新版程序的執行結果:
Step:0 loss:%25.78244 weight:0.4000000 bias:0.4000000
Step:5 loss:%2.71975 weight:0.7611420 bias:0.7740188
Step:10 loss:%3.09600 weight:0.6725605 bias:0.7224629
Step:15 loss:%0.87834 weight:0.4931822 bias:0.5800986
Step:20 loss:%1.24737 weight:0.4960071 bias:0.6186275
Step:25 loss:%0.22444 weight:0.5730734 bias:0.7264798
Step:30 loss:%0.47145 weight:0.5464076 bias:0.7252067
Step:35 loss:%0.09156 weight:0.4736322 bias:0.6712209
Step:40 loss:%0.14845 weight:0.4771673 bias:0.6866464
Step:45 loss:%0.06199 weight:0.5101752 bias:0.7255269
Step:50 loss:%0.03108 weight:0.4946054 bias:0.7112849
Step:55 loss:%0.04115 weight:0.4770990 bias:0.6918764
Step:60 loss:%0.00145 weight:0.4950625 bias:0.7060429
Step:65 loss:%0.01781 weight:0.5029647 bias:0.7096580
Step:70 loss:%0.00211 weight:0.4934593 bias:0.6963260
Step:75 loss:%0.00298 weight:0.4983235 bias:0.6982682
Step:80 loss:%0.00345 weight:0.5049748 bias:0.7031375
Step:85 loss:%0.00004 weight:0.5001755 bias:0.6976562
Step:90 loss:%0.00102 weight:0.5002422 bias:0.6978318
Step:95 loss:%0.00065 weight:0.5029225 bias:0.7010939
Step:100 loss:%0.00001 weight:0.5000774 bias:0.6990223
Step:105 loss:%0.00021 weight:0.4996552 bias:0.6993059
Step:110 loss:%0.00015 weight:0.5007215 bias:0.7008768
Step:115 loss:%0.00000 weight:0.4993480 bias:0.6997767
Step:120 loss:%0.00003 weight:0.4995552 bias:0.7000407
Step:125 loss:%0.00004 weight:0.5001000 bias:0.7004969
Step:130 loss:%0.00001 weight:0.4995880 bias:0.6998325
Step:135 loss:%0.00000 weight:0.4999941 bias:0.7000810
Step:140 loss:%0.00001 weight:0.5001197 bias:0.7000892
Step:145 loss:%0.00000 weight:0.4999250 bias:0.6998329
Step:150 loss:%0.00000 weight:0.5001498 bias:0.7000451
Step:155 loss:%0.00000 weight:0.5000388 bias:0.6999565
Step:160 loss:%0.00000 weight:0.4999948 bias:0.6999494
Step:165 loss:%0.00000 weight:0.5000526 bias:0.7000424
Step:170 loss:%0.00000 weight:0.4999576 bias:0.6999717
Step:175 loss:%0.00000 weight:0.4999971 bias:0.7000214
Step:180 loss:%0.00000 weight:0.4999900 bias:0.7000131
Step:185 loss:%0.00000 weight:0.4999775 bias:0.6999928
Step:190 loss:%0.00000 weight:0.5000094 bias:0.7000152
Step:195 loss:%0.00000 weight:0.4999923 bias:0.6999906
模型通過學習后,得到的結果是很接近我們的預設值的。
程序中還可以考慮使用隨機快速下降算法(SGD),你可以把當前的Adam算法使用注釋符屏蔽上,打開SGD算法的注釋屏蔽來嘗試一下。對於本例中的數據集來講,SGD的下降步長需要的更小,同樣循環次數下,求解的精度會低一些。可以看出對於本例,Adam算法顯然是更有效率的。而在TensorFlow的支持下,對於同樣的數據集和數學模型,變更學習算法會很容易。
(待續...)
相關文章

