1. 寫在最前面
1.1 機器學習如何入門
關于機器學習如何入門可以參考機器學習該怎么入門?,相關課程可以參考吳恩達機器學習系列課程。
1.2 關于框架的選擇
無論是TensorFlow還是PyTorch都是非常優(yōu)秀的框架,關于兩者如何選擇,可以參2021 年了,TensorFlow 和 PyTorch 兩個深度學習框架地位又有什么變化嗎?,最高贊的結論是:
2021年學術圈 Pytorch會繼續(xù)稱霸天下,而Tensorflow引以為傲的工業(yè)界地位會進一步衰退,Pytorch會借助ONNX所帶來的落地能力在工業(yè)界逐漸搶走主導地位。
但是因為項目中使用的是TensorFlow, 所以這里選擇的是TensorFlow,每天是身在tf營,心在torch。關于Pytorch,可以參考PyTorch 中文教程 & 文檔。
1.3 tf1、tf2、keras與tf.keras
tf1與tf2的區(qū)別可以參考: https://www.tensorflow.org/guide/migrate/tf1_vs_tf2
- TensorFlow 1.X需要用戶使用tf.*里的API手動構建計算圖,然后用session.run()傳入輸入tensor并且計算某些輸出tensor。
- TensorFlow 2.X默認是Eager執(zhí)行模式,我們在定義一個Operation的時候會動態(tài)構造計算圖并且馬上計算。這樣的好處就是我們的代碼就像在執(zhí)行普通的Python代碼,Graph和Session等實現(xiàn)細節(jié)概念都被隱藏在后面了。
- Keras可以看成是一種深度學習框架的高階接口規(guī)范,它幫助用戶以更簡潔的形式定義和訓練深度學習網(wǎng)絡。隨著谷歌對Keras的收購,Keras庫2.3.0版本后也將不再進行更新,用戶應當使用tf.keras而不是使用pip安裝的Keras。
- tf.keras是在TensorFlow中以TensorFlow低階API為基礎實現(xiàn)的這種高階接口,它是Tensorflow的一個子模塊。
1.4 Tensorflow接口層次結構
最底層為硬件層,TensorFlow支持CPU、GPU或TPU加入計算資源池;
第二層為C++實現(xiàn)的內(nèi)核,kernel可以跨平臺分布運行;
第三層為Python實現(xiàn)的操作符,提供了封裝C++內(nèi)核的低級API指令,主要包括各種張量操作算子、計算圖、自動微分。如tf.Variable,tf.constant,tf.function, tf.GradientTape, tf.nn.softmax... 如果把模型比作一個房子,那么第三層API就是【模型之磚】;
第四層為Python實現(xiàn)的模型組件,對低級API進行了函數(shù)封裝,主要包括各種模型層,損失函數(shù),優(yōu)化器,數(shù)據(jù)管道,特征列等等。 如tf.keras.layers, tf.keras.losses, tf.keras.metrics, tf.keras.optimizers,tf.data.DataSet, tf.feature_column... 如果把模型比作一個房子,那么第四層API就是【模型之墻】。
第五層為Python實現(xiàn)的模型成品,一般為按照OOP方式封裝的高級API,主要為tf.keras.models提供的模型的類接口。 如果把模型比作一個房子,那么第五層API就是模型本身,即【模型之屋】。

2. Tensorflow基本概念
TensorFlow,顧名思義就是流動著的Tensor。首先,我們要搞清楚什么是tensor,它在哪里以何種方式流動?用最簡單的話來說,Tensor實際上就是一個多維數(shù)組(multidimensional array),是TF的主要數(shù)據(jù)結構。它們在一個或多個由節(jié)點(nodes)和邊(edges)組成的圖(graphs)中流動。邊代表的是tensors,節(jié)點代表的是對tensors的操作(operations)。tensors在圖中從一個節(jié)點流向另一個節(jié)點,每次經(jīng)過一個節(jié)點都會接受一次操作。
2.1 Tensor
1. tensor基本概念
tensor有兩個屬性:
- dtype: Tensor 存儲的數(shù)據(jù)的類型,可以為tf.float32、tf.int32、tf.string…
- shape: Tensor 存儲的多維數(shù)組中每個維度的數(shù)組中元素的個數(shù)
此外還有一個rank,是shape的另一種表達方式,即維數(shù)
# 0階張量,即標量,shape=0
t0 = tf.constant(3, dtype=tf.int32)
# 1階張量,即矢量,shape=[2]
t1 = tf.constant([3., 4.1, -.2], dtype=tf.float32)
# 2階張量,二維數(shù)組,shape=[2, 3]
t2 = tf.constant([['A', 'B'], ['C', 'D']], dtype=tf.string)
# 3階張量,三維數(shù)組,shape=[2, 1, 3],數(shù)據(jù)默認類型為整形
t3 = tf.constant([[[1, 3]], [[5, 7]]])
print打印tensor的屬性定義,tf.print(tensor)打印tensor的值,tf.print如果數(shù)量太多默認會省略中間部分,如果不想省略可以增加參數(shù)summarize=-1 。
> print(t0)
tf.Tensor(3, shape=(), dtype=int32)
> print(t1)
tf.Tensor([ 3. 4.1 -0.2], shape=(3,), dtype=float32)
> print(t2)
tf.Tensor(
[[b'A' b'B']
[b'C' b'D']], shape=(2, 2), dtype=string)
> print(t3)
tf.Tensor(
[[[1 3]]
[[5 7]]], shape=(2, 1, 2), dtype=int32)
> print(t2.get_shape())
(2, 2)
> tf.print(t0)
3
> tf.print(t1)
[3 4.1 -0.2]
> tf.print(t2)
[["A" "B"]
["C" "D"]]
> tf.print(t3)
[[[1 3]]
[[5 7]]]
> tf.print(tf.range(1, 11, delta=1, dtype=tf.float32))
[1 2 3 ... 8 9 10]
> tf.print(tf.range(1, 11, delta=1, dtype=tf.float32), summarize=-1)
[1 2 3 4 5 6 7 8 9 10]
2. tensor的創(chuàng)建
創(chuàng)建tensor的方法可以分為2種
- 使用tf自帶的函數(shù)直接創(chuàng)建,例如:
# create a zero filled tensor
t0 = tf.zeros([1, 2])
# [[0 0]]
# create a one filled tensor
t1 = tf.ones([1, 2])
# [[1 1]]
# create a constant filled tensor
t2 = tf.fill([1, 2], 42)
# [[42 42]]
# create a tensor out of an existing constant
t3 = tf.constant([1, 2, 3])
# [1 2 3]
# generate random numbers from a uniform distribution
t4 = tf.random.uniform([1, 2], minval=0, maxval=1)
# [[0.809502 0.184664249]]
# generate random numbers from a normal distribution
t5 = tf.random.normal([1, 2], mean=0.0, stddev=1.0)
# [[-0.717701077 -1.32147753]]
- 將Python對象(Numpy arrays, Python lists,Python scalars)轉成tensor,例如:
import numpy as np
x_data = np.array([[1., 2., 3.], [3., 2., 6.]])
tf.convert_to_tensor(x_data, dtype=tf.float32)
# [[1 2 3]
# [3 2 6]]
2.2 operation
operation代表對tensor的操作,如下圖所示:節(jié)點a接收了一個1-D tensor,該tensor從節(jié)點a流出后,分別流向了節(jié)點b和c,節(jié)點b執(zhí)行的是prod操作(5*3),節(jié)點c執(zhí)行的是sum操作(5+3)。當tensor從節(jié)點b流出時變成了15,從節(jié)點c流出時變成了8。此時,2個tensor又同時流入節(jié)點d,接受的是add操作(15+8),最后從節(jié)點d流出的tensor就是23。

相應的代碼為:
import tensorflow as tf
a = tf.constant([5, 3], name='input_a')
b = tf.reduce_prod(a, name='prod_b')
c = tf.reduce_sum(a, name='sum_c')
d = tf.add(b, c, name='add_d')
3. Tensorflow入門示例
正如大部分的編程語言入門從hello world!開始, tensorflow的入門以最簡單線性回歸(Linear Regression)開始。
線性回歸就是在坐標系中有很多點, 線性回歸的目的就是找到一條線使得這些點都在這條直線上或者直線的周圍。我們要對其進行線性回歸,則建立線性數(shù)學模型:
y=W?x+b
那么如何評估我們線性回歸出來的直線方程呢?我們需要建立一個損失模型(loss model)來評估模型的合理性:
loss=Σ(yn?y′n)2
3.1 構造數(shù)據(jù)集
利用函數(shù)Y = 2*X + 3并增加正態(tài)隨機構造數(shù)據(jù)集,相關代碼如下所示
import tensorflow as tf
w = 2
b = 3
X = tf.range(1, 11, delta=1, dtype=tf.float32)
Y = X * w + b + tf.random.normal([10,], mean = 0.0, stddev= 1.0)
構造好的數(shù)據(jù)集如下所示
X = [1 2 3 4 5 6 7 8 9 10]
Y = [5.69277859 5.68863153 7.6934948 10.7271547 13.6772203 14.6714172 15.488286 20.2158928 21.2844448 24.5952759]
如下圖所示,將Y = 2*X + 3(綠線部分)和X、Y(藍點)畫在同一圖像上,畫圖代碼如下所示:
from matplotlib import pyplot as plt
plt.plot(X, X * w + b, color='green')
plt.scatter(X, Y, color='blue')
plt.xlabel('x')
plt.ylabel('y')
plt.title("Data")
plt.ylim(ymin=0)
plt.show()

將數(shù)據(jù)集拆分為訓練數(shù)據(jù)集和評估數(shù)據(jù)集
# 訓練數(shù)據(jù)
x_train = [1.0, 3.0, 5.0, 7.0, 9.0]
y_train = [5.69277859, 7.6934948, 13.6772203, 15.488286, 21.2844448]
# 評估數(shù)據(jù)
x_eval = [2.0, 4.0, 6.0, 8.0, 10.0]
y_eval = [5.68863153, 10.7271547, 14.6714172, 20.2158928, 24.5952759]
3.2 訓練
訓練代碼如下所示
import tensorflow as tf
# create data
x_train = [1.0, 3.0, 5.0, 7.0, 9.0]
y_train = [5.69277859, 7.6934948, 13.6772203, 15.488286, 21.2844448]
# create tensorflow structure
W = tf.Variable(tf.random.uniform([1], -1.0, 1.0))
b = tf.Variable(tf.zeros([1]))
# alias: tf.losses.mse
loss = lambda: tf.keras.losses.MSE(y_train, W * x_train + b)
# alias: tf.optimizers.SGDS
optimizer = tf.keras.optimizers.SGD(learning_rate=0.004)
num_epoch = 6001
for step in range(num_epoch):
optimizer.minimize(loss, var_list=[W, b])
if step % 100 == 0:
print( "{} step, weights = {}, biases = {}, loss = {}".format(step, W.read_value(), b.read_value(), loss()) )
迭代過程如下所示
0 step, weights = [0.6796642], biases = [0.09973339], loss = 99.73298645019531
100 step, weights = [2.285599], biases = [0.8164981], loss = 2.1074492931365967
200 step, weights = [2.2274106], biases = [1.1977823], loss = 1.7347787618637085
300 step, weights = [2.1792789], biases = [1.5131716], loss = 1.4797898530960083
400 step, weights = [2.139465], biases = [1.7740546], loss = 1.3053206205368042
500 step, weights = [2.106532], biases = [1.989852], loss = 1.1859443187713623
600 step, weights = [2.0792909], biases = [2.1683528], loss = 1.104265809059143
700 step, weights = [2.0567577], biases = [2.3160055], loss = 1.0483779907226562
800 step, weights = [2.0381184], biases = [2.4381416], loss = 1.010138750076294
900 step, weights = [2.0227005], biases = [2.5391688], loss = 0.9839746356010437
1000 step, weights = [2.009947], biases = [2.622736], loss = 0.9660736322402954
1100 step, weights = [1.999398], biases = [2.6918607], loss = 0.9538244009017944
1200 step, weights = [1.9906719], biases = [2.74904], loss = 0.9454426765441895
1300 step, weights = [1.9834539], biases = [2.7963367], loss = 0.9397082328796387
……
5300 step, weights = [1.9489253], biases = [3.02259], loss = 0.9272836446762085
5400 step, weights = [1.9489214], biases = [3.0226138], loss = 0.9272834658622742
5500 step, weights = [1.948918], biases = [3.0226376], loss = 0.927282989025116
5600 step, weights = [1.9489162], biases = [3.0226493], loss = 0.9272829294204712
5700 step, weights = [1.9489162], biases = [3.0226493], loss = 0.9272829294204712
5800 step, weights = [1.9489162], biases = [3.0226493], loss = 0.9272829294204712
5900 step, weights = [1.9489162], biases = [3.0226493], loss = 0.9272829294204712
6000 step, weights = [1.9489162], biases = [3.0226493], loss = 0.9272829294204712
可以看到迭代到5600步時,loss已經(jīng)收斂了, 最終訓練出的結果是:W = 1.9489162, b = 3.0226493 ,與構造的數(shù)據(jù)用的 W = 2,b = 3已經(jīng)很接近了。將評估訓練集帶入模型,發(fā)現(xiàn)最終的損失也很小,說明訓練出的模型還是有很好的泛化性
import tensorflow as tf
x_eval = [2.0, 4.0, 6.0, 8.0, 10.0]
y_eval = [5.68863153, 10.7271547, 14.6714172, 20.2158928, 24.5952759]
W = tf.constant(1.9489162, dtype=tf.float32)
b = tf.constant(3.0226493, dtype=tf.float32)
tf.print(tf.keras.losses.MSE(y_eval, W * x_eval + b))
# 1.6869427
3.3 TensorBoad
TensorBoad是Google為TensorFlow開發(fā)了一款可視化工具,使用流程如下
- 建立文件夾存放 TensorBoard 的記錄文件;
- 實例化記錄器;
# 日志目錄
logdir = "/tmp/tensorflow"
# create the file writer object
writer = tf.summary.create_file_writer(logdir)
- 將參數(shù)(一般是標量)記錄到指定的記錄器中;
with writer.as_default():
tf.summary.scalar('training loss', loss(), step=step+1)
- 訪問 TensorBoard 的可視界面。
tensorboard --logdir /tmp/tensorflow
根據(jù)提示訪問http://localhost:6006/,就可以看到如下界面,可以看到前1000步之前l(fā)oss急劇下降,然后loss下降趨于平緩。

TensorBoad完整代碼請參考附錄1
參考文獻:
附錄
1. LinearRegression.py
import tensorflow as tf
# 日志目錄
logdir = "/tmp/tensorflow"
# create the file writer object
writer = tf.summary.create_file_writer(logdir)
# create data
x_train = [1.0, 3.0, 5.0, 7.0, 9.0]
y_train = [5.69277859, 7.6934948, 13.6772203, 15.488286, 21.2844448]
# create tensorflow structure
W = tf.Variable(tf.random.uniform([1], -1.0, 1.0))
b = tf.Variable(tf.zeros([1]))
# alias: tf.losses.mse
loss = lambda: tf.keras.losses.MSE(y_train, W * x_train + b)
# alias: tf.optimizers.SGDS
optimizer = tf.keras.optimizers.SGD(learning_rate=0.004)
num_epoch = 6001
for step in range(num_epoch):
optimizer.minimize(loss, var_list=[W, b])
# write the loss value
with writer.as_default():
tf.summary.scalar('training loss', loss(), step=step+1)
if step % 100 == 0:
print( "{} step, weights = {}, biases = {}, loss = {}".format(step, W.read_value(), b.read_value(), loss()))