1. 程式人生 > 其它 >TensorFlow筆記(一)

TensorFlow筆記(一)

第一章 神經網路計算過程及模型搭建

1 人工智慧三學派

我們常說的人工智慧,就是讓機器具備人的思維和意識。人工智慧主要有三個學派,即行為主義、符號主義連線主義

行為主義:是基於控制論的,是在構建感知、動作的控制系統。單腳站立是行為主義一個典型例子,通過感知要摔倒的方向,控制兩隻手的動作,保持身體的平衡。這就構建了一個感知、動作的控制系統,是典型的行為主義。

符號主義:基於算數邏輯表示式。即在求解問題時,先把問題描述為表示式,再求解表示式。例如在求解某個問題時,利用 if case 等條件語句和若干計算公式描述出來,即使用了符號主義的方法,如專家系統。符號主義是能用公式描述的人工智慧,它讓計算機具備了理性思維。

連線主義:仿造人腦內的神經元連線關係,使人類不僅具備理性思維,還具備無法用公式描述的感性思維,如對某些知識產生記憶。

圖 1.1 展示了人腦中的一根神經元,其中紫色部分為樹突,其作為神經元的輸入。黃色部分為軸突,其作為神經元的輸出。人腦就是由 860 億個這樣的神經元首尾相接組成的網路。

圖1.1 神經元示意圖

基於連線主義的神經網路模仿上圖的神經元,使計算機具有感性思維。圖1.2展示了從出生到成年,人腦中神經網路的變化。

圖1.2 人腦神經網路變化示意圖

隨著我們的成長,大量的資料通過視覺、聽覺湧入大腦,使我們的神經網路連線,也就是這些神經元連線線上的權重發生了變化,有些線上的權重增強了,有些線上的權重減弱了。如圖1.3所示。

圖1.3 神經網路權重變化示意圖

2 神經網路設計過程

我們要用計算機模仿剛剛說到的神經網路連線關係,讓計算機具備感性思維。

首先,需要準備資料,資料量越大越好,要構成特徵和標籤對。如要識別貓,就要有大量貓的圖片和這個圖片是貓的標籤,構成特徵標籤對。

隨後,搭建神經網路的網路結構,並通過反向傳播,優化連線的權重,直到模型的識別準確率達到要求,得到最優的連線權重,把這個模型儲存起來。

最後,用儲存的模型,輸入從未見過的新資料,它會通過前向傳播,輸出概率值,概率值最大的一個,就是分類或預測的結果。圖2.1展示了搭建與使用神經網路模型的流程。

圖2.1 搭建與使用神經網路示意圖

2.1 資料集介紹

本講中採用鳶尾花資料集,此資料集包含鳶尾花花萼長、花萼寬、花瓣長、花瓣寬及對應的類別。其中前4個屬性作為輸入特徵,類別作為標籤,0代表狗尾草鳶尾,1代表雜色鳶尾,2代表弗吉尼亞鳶尾。人們通過對資料進行分析總結出了規律:通過測量花的花萼長、花萼寬、花瓣長、花瓣寬,可以得出鳶尾花的類別(如:花萼長>花萼寬且花瓣長/花瓣寬>2 ,則雜色鳶尾)。
由上述可知,可通過if與case語句構成專家系統,進行判別分類。在本講中,採用搭建神經網路的辦法對其進行分類,即將鳶尾花花萼長、花萼寬、花瓣長、花瓣寬四個輸入屬性喂入搭建好的神經網路,網路優化引數得到模型,輸出分類結果。

2.2 網路搭建與訓練

本講中,我們搭建包含輸入層與輸出層的神經網路模型,通過對輸入值乘權值,並於偏置值求和的方式得到輸出值,圖示如下。

圖2.2 鳶尾花神經網路簡要模型

由圖2.2可知輸出y = x*w+b,即所有的輸入x乘以各自線上的權重w求和加上偏置項b得到輸出y。由2.1部分對資料集的介紹可知,輸入特徵x形狀應為(1,4)即1行4列,輸出y形狀應為(1,3)即1行3列,w形狀應為(4,3)即4行3列,b形狀應為(3, )即有3個偏置項。
搭建好基本網路後,需要輸入特徵資料,並對線上權重w與偏置b進行初始化。搭建的神經網路如圖2.3所示,w,b初始化矩陣如圖2.4所示。在這裡,我們輸入標籤為0的狗尾草鳶尾。

圖2.3 鳶尾花神經網路展開模型

圖2.4 權重與偏置初始化矩陣

有了輸入資料與線上權重等資料,即可按照y = x*w+b方式進行前向傳播,計算過程如圖2.5所示。

圖2.5 前向傳播計算過程

圖2.5中輸出y中,1.01代表0類鳶尾得分,2.01代表1類鳶尾得分,-0.66代表2類鳶尾得分。通過輸出y可以看出數值最大(可能性最高)的是1類鳶尾,而不是標籤0類鳶尾。這是由於最初的引數w和b是隨機產生的,現在輸出的結果是蒙的。

為了修正這一結果,我們用損失函式,定義預測值y和標準答案(標籤) y_的差距,損失函式可以定量的判斷當前這組引數w和b的優劣,當損失函式最小時,即可得到最優w的值和b的值。

損失函式的定義有多種方法,均方誤差就是一種常用的損失函式,它計算每個前向傳播輸出y和標準答案_y的差求平方再求和再除以n求平均值,表徵了網路前向傳播推理結果和標準答案之間的差距。

通過上述對損失函式的介紹,其目的是尋找一組引數w和b使得損失函式最小。為達成這一目的,我們採用梯度下降的方法。損失函式的梯度表示損失函式

對各引數求偏導後的向量,損失函式梯度下降的方向,就是是損失函式減小的方向。梯度下降法即沿著損失函式梯度下降的方向,尋找損失函式的最小值,從而得到最優的引數。梯度下降法涉及的公式如下

上式中,lr表示學習率,是一個超引數,表徵梯度下降的速度。如學習率設定過小,引數更新會很慢,如果學習率設定過大,引數更新可能會跳過最小值。

上述梯度下降更新的過程為反向傳播,下面通過例子感受反向傳播。利用如下公式對引數w進行更新。

設損失函式為(w+1)^2,則其對w的偏導數為2w+2。設w在初始化時被隨機初始化為5,學習率設定為0.2。則我們可按上述公式對w進行更新:

第一次引數為5,按上式計算即5-0.2×(2×5+2)=2.6。

同理第二次計算得到引數為1.16,第三次計算得到引數為0.296……

畫出損失函式(w+1)^2的影象,可知w=-1時損失函式最小,我們反向傳播優化引數的目的即為找到這個使損失函式最小的w=-1值。

3 TensorFlow2.X 基本概念與常見函式

3.1 基本概念

TensorFlow中的Tensor表示張量,是多維陣列、多維列表,用階表示張量的維數。0階張量叫做標量,表示的是一個單獨的數,如123;1階張量叫作向量,表示的是一個一維陣列如[1,2,3];2階張量叫作矩陣,表示的是一個二維陣列,它可以有i行j列個元素,每個元素用它的行號和列號共同索引到,如在[[1,2,3],[4,5,6],[7,8,9]]中,2的索引即為第0行第1列。張量的階數與方括號的數量相同,0個方括號即為0階張量,1個方括號即為1階張量。故張量可以表示0階到n階的陣列。也可通過reshape的方式得到更高維度陣列,舉例如下:

c = np.arange(24).reshape(2,4,3)
print(c)

輸出結果:

[[[ 0 1 2] [ 3 4 5] [ 6 7 8] [ 9 10 11]]
[[12 13 14] [15 16 17] [18 19 20] [21 22 23]]]

TensorFlow中資料型別包括32位整型(tf.int32)、32位浮點(tf.float32)、64位浮點(tf.float64)、布林型(tf.bool)、字串型(tf.string)

建立張量有若干種不同的方法:
(1) 利用tf.constant(張量內容,dtype=資料型別(可選)),第一個引數表示張量內容,第二個引數表示張量的資料型別。舉例如下:

a = tf.constant([1, 5], dtype=tf.int64)
print("a:", a)
print("a.dtype:", a.dtype)
print("a.shape:", a.shape)

輸出結果為:

a: tf.Tensor([1 5], shape=(2,), dtype=int64)
a.dtype: <dtype: 'int64'>
a.shape: (2,)

即會輸出張量內容、形狀與資料型別,shape中數字為2,表示一維張量裡有2個元素。

注:去掉dtype項,不同電腦環境不同導致預設值不同,可能導致後續程式bug
(2) 很多時候資料是由numpy格式給出的,此時可以通過如下函式將numpy格式化為Tensor格式:tf. convert_to_tensor(資料名,dtype=資料型別(可選))。舉例如下:

import tensorflow as tf
import numpy as np

a = np.arange(0, 5)
b = tf.convert_to_tensor(a, dtype=tf.int64)
print("a:", a)
print("b:", b)

輸出結果為:

a: [0 1 2 3 4]
b: tf.Tensor([0 1 2 3 4], shape=(5,), dtype=int64)

(3) 可採用不同函式建立不同值的張量。如用tf. zeros(維度)建立全為0的張量,tf.ones(維度)建立全為1的張量,tf. fill(維度,指定值)建立全為指定值的張量。其中維度引數部分,如一維則直接寫個數,二維用[行,列]表示,多維用[n,m,j..]表示。舉例如下:

a = tf.zeros([2, 3])
b = tf.ones(4)
c = tf.fill([2, 2], 9)
print("a:", a)
print("b:", b)
print("c:", c)

輸出結果:

a: tf.Tensor([[0. 0. 0.] [0. 0. 0.]], shape=(2, 3), dtype=float32)
b: tf.Tensor([1. 1. 1. 1.], shape=(4,), dtype=float32)
c: tf.Tensor([[9 9] [9 9]], shape=(2, 2), dtype=int32)

可見,tf.zeros([2,3])建立了一個二維張量,第一個維度有兩個元素,第二個維度有三個元素,元素的內容全是0;tf.ones(4)建立了一個一維張量,裡邊有4個元素,內容全是1;tf.fill([2,2],9)建立了一個兩行兩列的二維張量,第一個維度有兩個元素,第二個維度也有兩個元素,內容都是9。

(4) 可採用不同函式建立符合不同分佈的張量。如用tf. random.normal (維度,mean=均值,stddev=標準差)生成正態分佈的隨機數,預設均值為0,標準差為1;用tf. random.truncated_normal (維度,mean=均值,stddev=標準差)生成截斷式正態分佈的隨機數,能使生成的這些隨機數更集中一些,如果隨機生成資料的取值在(μ-2σ,μ+2σ),之外則重新進行生成,保證了生成值在均值附近;利用tf. random. uniform(維度,minval=最小值,maxval=最大值),生成指定維度的均勻分佈隨機數,用minval給定隨機數的最小值,用maxval給定隨機數的最大值,最小、最大值是前閉後開區間。舉例如下:

d = tf.random.normal([2, 2], mean=0.5, stddev=1)
print("d:", d)
e = tf.random.truncated_normal([2, 2], mean=0.5, stddev=1)
print("e:", e)
f = tf.random.uniform([2, 2], minval=0, maxval=1)
print("f:", f)

d: tf.Tensor([[ 2.8951766 0.34715778] [-0.88545835 0.1141389 ]], shape=(2, 2), dtype=float32)
e: tf.Tensor([[ 1.3894703 1.226865 ] [ 0.6791599 -0.03111815]], shape=(2, 2), dtype=float32)

f: tf.Tensor([[1.3811994e-01 8.8101661e-01] [1.9073486e-04 9.7648060e-01]], shape=(2, 2), dtype=float32)

3.2 常用函式

(1)利用tf.cast (張量名,dtype=資料型別)強制將Tensor轉換為該資料型別;利用tf.reduce_min (張量名)計算張量維度上元素的最小值;利用tf.reduce_max (張量名)計算張量維度上元素的最大值。舉例如下:

x1 = tf.constant([1., 2., 3.], dtype=tf.float64)
print("x1:", x1)
x2 = tf.cast(x1, tf.int32)
print("x2", x2)
print("minimum of x2:", tf.reduce_min(x2))
print("maxmum of x2:", tf.reduce_max(x2))

輸出結果:

x1: tf.Tensor([1. 2. 3.], shape=(3,), dtype=float64)
x2 tf.Tensor([1 2 3], shape=(3,), dtype=int32)
minimum of x2: tf.Tensor(1, shape=(), dtype=int32)
maxmum of x2: tf.Tensor(3, shape=(), dtype=int32)

(2) 可用tf.reduce_mean (張量名,axis=操作軸)計算張量沿著指定維度的平均值;可用f.reduce_sum (張量名,axis=操作軸)計算張量沿著指定維度的和,如不指定axis,則表示對所有元素進行操作。其中維度可按圖3.1理解。

圖3.1 維度定義

由上圖可知對於一個二維張量,如果axis=0表示縱向操作(沿經度方向) ,axis=1 表示橫向操作(沿緯度方向)。舉例如下:

x = tf.constant([[1, 2, 3], [2, 2, 3]])
print("x:", x)
print("mean of x:", tf.reduce_mean(x))  # 求x中所有數的均值
print("sum of x:", tf.reduce_sum(x, axis=1))  # 求每一行的和

輸出結果:

x: tf.Tensor(
[[1 2 3]
[2 2 3]], shape=(2, 3), dtype=int32)
mean of x: tf.Tensor(2, shape=(), dtype=int32)
sum of x: tf.Tensor([6 7], shape=(2,), dtype=int32)

(3) 可利用tf.Variable(initial_value,trainable,validate_shape,name)函式可以將變數標記為“可訓練”的,被它標記了的變數,會在反向傳播中記錄自己的梯度資訊。其中initial_value預設為None,可以搭配tensorflow隨機生成函式來初始化引數;trainable預設為True,表示可以後期被演算法優化的,如果不想該變數被優化,即改為False;validate_shape預設為True,形狀不接受更改,如果需要更改,validate_shape=False;name預設為None,給變數確定名稱。舉例如下:
w = tf.Variable(tf.random.normal([2, 2], mean=0, stddev=1)),表示首先隨機生成正態分佈隨機數,再給生成的隨機數標記為可訓練,這樣在反向傳播中就可以通過梯度下降更新引數w了。

(4) 利用TensorFlow中函式對張量進行四則運算。利用tf.add (張量1,張量2)實現兩個張量的對應元素相加;利用tf.subtract (張量1,張量2)實現兩個張量的對應元素相減;利用tf.multiply (張量1,張量2)實現兩個張量的對應元素相乘;利用tf.divide (張量1,張量2)實現兩個張量的對應元素相除。注:只有維度相同的張量才可以做四則運算,舉例如下:

a = tf.ones([1, 3])
b = tf.fill([1, 3], 3.)
print("a:", a)
print("b:", b)
print("a+b:", tf.add(a, b))
print("a-b:", tf.subtract(a, b))
print("a*b:", tf.multiply(a, b))
print("b/a:", tf.divide(b, a))

輸出結果:

a: tf.Tensor([[1. 1. 1.]], shape=(1, 3), dtype=float32)
b: tf.Tensor([[3. 3. 3.]], shape=(1, 3), dtype=float32)
a+b: tf.Tensor([[4. 4. 4.]], shape=(1, 3), dtype=float32)
a-b: tf.Tensor([[-2. -2. -2.]], shape=(1, 3), dtype=float32)
a*b: tf.Tensor([[3. 3. 3.]], shape=(1, 3), dtype=float32)
b/a: tf.Tensor([[3. 3. 3.]], shape=(1, 3), dtype=float32)

(5) 利用TensorFlow中函式對張量進行冪次運算。可用tf.square (張量名)計算某個張量的平方;利用tf.pow (張量名,n次方數)計算某個張量的n次方;利用tf.sqrt (張量名)計算某個張量的開方。舉例如下:

a = tf.fill([1, 2], 3.)
print("a:", a)
print("a的平方:", tf.pow(a, 3))
print("a的平方:", tf.square(a))
print("a的開方:", tf.sqrt(a))

輸出結果:

a: tf.Tensor([[3. 3.]], shape=(1, 2), dtype=float32)
a的平方: tf.Tensor([[27. 27.]], shape=(1, 2), dtype=float32)
a的平方: tf.Tensor([[9. 9.]], shape=(1, 2), dtype=float32)
a的開方: tf.Tensor([[1.7320508 1.7320508]], shape=(1, 2), dtype=float32)

(6) 可利用tf.matmul(矩陣1,矩陣2)實現兩個矩陣的相乘。舉例如下:

a = tf.ones([3, 2])
b = tf.fill([2, 3], 3.)
print("a*b:", tf.matmul(a, b))

輸出結果:tf.Tensor([[6. 6. 6.] [6. 6. 6.] [6. 6. 6.]], shape=(3, 3), dtype=float32),即a為一個3行2列的全1矩陣,b為2行3列的全3矩陣,二者進行矩陣相乘。

(7) 可利用tf.data.Dataset.from_tensor_slices((輸入特徵, 標籤))切分傳入張量的第一維度,生成輸入特徵/標籤對,構建資料集,此函式對Tensor格式與Numpy格式均適用,其切分的是第一維度,表徵資料集中資料的數量,之後切分batch等操作都以第一維為基礎。舉例如下:

features = tf.constant([12, 23, 10, 17])
labels = tf.constant([0, 1, 1, 0])
dataset = tf.data.Dataset.from_tensor_slices((features, labels))
for element in dataset:
    print(element)

(<tf.Tensor: shape=(), dtype=int32, numpy=12>, <tf.Tensor: shape=(), dtype=int32, numpy=0>)
(<tf.Tensor: shape=(), dtype=int32, numpy=23>, <tf.Tensor: shape=(), dtype=int32, numpy=1>)
(<tf.Tensor: shape=(), dtype=int32, numpy=10>, <tf.Tensor: shape=(), dtype=int32, numpy=1>)
(<tf.Tensor: shape=(), dtype=int32, numpy=17>, <tf.Tensor: shape=(), dtype=int32, numpy=0>)

即將輸入特徵12和標籤0對應,產生配對;將輸入特徵23和標籤1對應,產生配對……

(8) 可利用tf.GradientTape( )函式搭配with結構計算損失函式在某一張量處的梯度,舉例如下:

with tf.GradientTape() as tape:
    x = tf.Variable(tf.constant(3.0))
    y = tf.pow(x, 2)
grad = tape.gradient(y, x)
print(grad)

輸出結果:tf.Tensor(6.0, shape=(), dtype=float32) 在上例中,損失函式為\(w^2\)\(w\)當前取值為3,故計算方式為

(9) 可利用enumerate(列表名)函式枚舉出每一個元素,並在元素前配上對應的索引號,常在for迴圈中使用。舉例如下:

seq = ['one', 'two', 'three']
for i, element in enumerate(seq):
    print(i, element)

輸出結果:

0 one
1 two
2 three

(10)可用tf.one_hot(待轉換資料,depth=幾分類)函式實現用獨熱碼錶示標籤,在分類問題中很常見。標記類別為為1和0,其中1表示是,0表示非。如在鳶尾花分類任務中,如果標籤是1,表示分類結果是1雜色鳶尾,其用把它用獨熱碼錶示就是0,1,0,這樣可以表示出每個分類的概率:也就是百分之0的可能是0狗尾草鳶尾,百分百的可能是1雜色鳶尾,百分之0的可能是弗吉尼亞鳶尾。舉例如下:

classes = 3
labels = tf.constant([1, 0, 2])  # 輸入的元素值最小為0,最大為2
output = tf.one_hot(labels, depth=classes)
print("result of labels1:", output)

輸出結果:

result of labels1: tf.Tensor(
[[0. 1. 0.]
[1. 0. 0.]
[0. 0. 1.]], shape=(3, 3), dtype=float32)

索引從0開始,待轉換資料中各元素值應小於depth,若帶轉換元素值大於等於depth,則該元素輸出編碼為[0, 0 … 0, 0]。即depth確定列數,待轉換元素的個數確定行數。舉例如下:

classes = 3
labels = tf.constant([1, 0, 4])  #輸入的元素值4超出depth-1 
output = tf.one_hot(labels, depth=classes)
print("result of labels1:", output)

輸出結果:

result of labels1: tf.Tensor(
[[0. 1. 0.]
 [1. 0. 0.]
 [0. 0. 0.]], shape=(3, 3), dtype=float32)

即元素4對應的輸出編碼為[0. 0. 0.]。

(11)可利用tf.nn.softmax( )函式使前向傳播的輸出值符合概率分佈,進而與獨熱碼形式的標籤作比較,其計算公式為

其中\(y_i\)是前向傳播的輸出。在前一部分,我們得到了前向傳播的輸出值,分別為1.01、2.01、-0.66,通過上述計算公式,可計算對應的概率值:

上式中,0.256表示為0類鳶尾的概率是25.6%,0.695表示為1類鳶尾的概率是69.5%,0.048表示為2類鳶尾的概率是4.8%。程式實現如下:

y = tf.constant([1.01, 2.01, -0.66])
y_pro = tf.nn.softmax(y)

print("After softmax, y_pro is:", y_pro)  # y_pro 符合概率分佈

輸出結果:

After softmax, y_pro is: tf.Tensor([0.25598174 0.6958304 0.0481878 ], shape=(3,), dtype=float32)

與上述計算結果相同。

(12)可利用assign_sub對引數實現自更新。使用此函式前需利用tf.Variable定義變數w為可訓練(可自更新),舉例如下:

x = tf.Variable(4)
x.assign_sub(1)
print("x:", x)  # 4-1=3

輸出結果:x: <tf.Variable 'Variable:0' shape=() dtype=int32, numpy=3>

即實現了引數w自減1。注:直接呼叫tf.assign_sub會報錯,要用w.assign_sub。

(13)可利用tf.argmax (張量名,axis=操作軸)返回張量沿指定維度最大值的索引,維度定義與圖3.1一致。舉例如下:

test = np.array([[1, 2, 3], [2, 3, 4], [5, 4, 3], [8, 7, 2]])
print("test:\n", test)
print("每一列的最大值的索引:", tf.argmax(test, axis=0))  # 返回每一列最大值的索引
print("每一行的最大值的索引", tf.argmax(test, axis=1))  # 返回每一行最大值的索引

輸出結果:

test:
[[1 2 3]
[2 3 4]
[5 4 3]
[8 7 2]]

每一列的最大值的索引: tf.Tensor([3 3 1], shape=(3,), dtype=int64)
每一行的最大值的索引 tf.Tensor([2 2 0 0], shape=(4,), dtype=int64)

4 程式實現鳶尾花資料集分類

4.1 資料集回顧

先回顧鳶尾花資料集,其提供了150組鳶尾花資料,每組包括鳶尾花的花萼長、花萼寬、花瓣長、花瓣寬4個輸入特徵,同時還給出了這一組特徵對應的鳶尾花類別。類別包括狗尾鳶尾、雜色鳶尾、弗吉尼亞鳶尾三類, 分別用數字0、1、2表示。使用此資料集程式碼如下:

from sklearn import datasets
# 匯入資料,分別為輸入特徵和標籤
x_data = datasets.load_iris().data
y_data = datasets.load_iris().target

即從sklearn包中匯出資料集,將輸入特徵賦值給x_data變數,將對應標籤賦值給y_data變數。

4.2 程式實現

我們用神經網路實現鳶尾花分類僅需要三步:

(1)準備資料,包括資料集讀入、資料集亂序,把訓練集和測試集中的資料配成輸入特徵和標籤對,生成train和test即永不相見的訓練集和測試集;

(2)搭建網路,定義神經網路中的所有可訓練引數;

(3)優化這些可訓練的引數,利用巢狀迴圈在with結構中求得損失函式loss對每個可訓練引數的偏導數,更改這些可訓練引數,為了檢視效果,程式中可以加入每遍歷一次資料集顯示當前準確率,還可以畫出準確率acc和損失函式loss的變化曲線圖。以上部分的完整程式碼與解析如下:

(1) 資料集讀入:

from sklearn import datasets
# 匯入資料,分別為輸入特徵和標籤
x_data = datasets.load_iris().data
y_data = datasets.load_iris().target

(2) 資料集亂序:

# 隨機打亂資料(因為原始資料是順序的,順序不打亂會影響準確率)
# seed: 隨機數種子,是一個整數,當設定之後,每次生成的隨機數都一樣(為方便教學,以保每位同學結果一致)
np.random.seed(116)  # 使用相同的seed,保證輸入特徵和標籤一一對應
np.random.shuffle(x_data)
np.random.seed(116)
np.random.shuffle(y_data)
tf.random.set_seed(116)

(3) 資料集分割成永不相見的訓練集和測試集:

# 將打亂後的資料集分割為訓練集和測試集,訓練集為前120行,測試集為後30行
x_train = x_data[:-30]
y_train = y_data[:-30]
x_test = x_data[-30:]
y_test = y_data[-30:]

(4) 配成[輸入特徵,標籤]對,每次喂入一小撮(batch):

# from_tensor_slices函式使輸入特徵和標籤值一一對應。(把資料集分批次,每個批次batch組資料)
train_db = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(32)
test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(32)

上述四小部分程式碼實現了資料集讀入、資料集亂序、將資料集分割成永不相見的訓練集和測試集、將資料配成[輸入特徵,標籤]對。人類在認識這個世界的時候資訊是沒有規律的,雜亂無章的湧入大腦的,所以喂入神經網路的資料集也需要被打亂順序。(2)部分實現了讓資料集亂序,因為使用了同樣的隨機種子,所以打亂順序後輸入特徵和標籤仍然是一一對應的。(3)部分將打亂後的前120個數據取出來作為訓練集,後30個數據作為測試集,為了公正評判神經網路的效果,訓練集和測試集沒有交集。(4)部分使用from_tensor_slices把訓練集的輸入特徵和標籤配對打包,將每32組輸入特徵標籤對打包為一個batch,在喂入神經網路時會以batch為單位喂入。

(5) 定義神經網路中所有可訓練引數:

# 生成神經網路的引數,4個輸入特徵故,輸入層為4個輸入節點;因為3分類,故輸出層為3個神經元
# 用tf.Variable()標記引數可訓練
# 使用seed使每次生成的隨機數相同(方便教學,使大家結果都一致,在現實使用時不寫seed)
w1 = tf.Variable(tf.random.truncated_normal([4, 3], stddev=0.1, seed=1))
b1 = tf.Variable(tf.random.truncated_normal([3], stddev=0.1, seed=1))

(6) 巢狀迴圈迭代,with結構更新引數,顯示當前loss:

lr = 0.1  # 學習率為0.1
train_loss_results = []  # 將每輪的loss記錄在此列表中,為後續畫loss曲線提供資料
test_acc = []  # 將每輪的acc記錄在此列表中,為後續畫acc曲線提供資料
epoch = 500  # 迴圈500輪
loss_all = 0  # 每輪分4個step,loss_all記錄四個step生成的4個loss的和

# 訓練部分
for epoch in range(epoch):  #資料集級別的迴圈,每個epoch迴圈一次資料集
    for step, (x_train, y_train) in enumerate(train_db):  #batch級別的迴圈 ,每個step迴圈一個batch
        with tf.GradientTape() as tape:  # with結構記錄梯度資訊
            y = tf.matmul(x_train, w1) + b1  # 神經網路乘加運算
            y = tf.nn.softmax(y)  # 使輸出y符合概率分佈(此操作後與獨熱碼同量級,可相減求loss)
            y_ = tf.one_hot(y_train, depth=3)  # 將標籤值轉換為獨熱碼格式,方便計算loss和accuracy
            loss = tf.reduce_mean(tf.square(y_ - y))  # 採用均方誤差損失函式mse = mean(sum(y-out)^2)
            loss_all += loss.numpy()  # 將每個step計算出的loss累加,為後續求loss平均值提供資料,這樣計算的loss更準確
        # 計算loss對各個引數的梯度
        grads = tape.gradient(loss, [w1, b1])

        # 實現梯度更新 w1 = w1 - lr * w1_grad    b = b - lr * b_grad
        w1.assign_sub(lr * grads[0])  # 引數w1自更新
        b1.assign_sub(lr * grads[1])  # 引數b自更新

    # 每個epoch,列印loss資訊
    print("Epoch {}, loss: {}".format(epoch, loss_all/4))
    train_loss_results.append(loss_all / 4)  # 將4個step的loss求平均記錄在此變數中
    loss_all = 0  # loss_all歸零,為記錄下一個epoch的loss做準備

    # 測試部分
    # total_correct為預測對的樣本個數, total_number為測試的總樣本數,將這兩個變數都初始化為0
    total_correct, total_number = 0, 0
    for x_test, y_test in test_db:
        # 使用更新後的引數進行預測
        y = tf.matmul(x_test, w1) + b1
        y = tf.nn.softmax(y)
        pred = tf.argmax(y, axis=1)  # 返回y中最大值的索引,即預測的分類
        # 將pred轉換為y_test的資料型別
        pred = tf.cast(pred, dtype=y_test.dtype)
        # 若分類正確,則correct=1,否則為0,將bool型的結果轉換為int型
        correct = tf.cast(tf.equal(pred, y_test), dtype=tf.int32)
        # 將每個batch的correct數加起來
        correct = tf.reduce_sum(correct)
        # 將所有batch中的correct數加起來
        total_correct += int(correct)
        # total_number為測試的總樣本數,也就是x_test的行數,shape[0]返回變數的行數
        total_number += x_test.shape[0]
    # 總的準確率等於total_correct/total_number
    acc = total_correct / total_number
    test_acc.append(acc)
    print("Test_acc:", acc)
    print("--------------------------")

(7) 計算當前引數前向傳播後的準確率,顯示當前準確率acc:

 # 測試部分
    # total_correct為預測對的樣本個數, total_number為測試的總樣本數,將這兩個變數都初始化為0
    total_correct, total_number = 0, 0
    for x_test, y_test in test_db:
        # 使用更新後的引數進行預測
        y = tf.matmul(x_test, w1) + b1
        y = tf.nn.softmax(y)
        pred = tf.argmax(y, axis=1)  # 返回y中最大值的索引,即預測的分類
        # 將pred轉換為y_test的資料型別
        pred = tf.cast(pred, dtype=y_test.dtype)
        # 若分類正確,則correct=1,否則為0,將bool型的結果轉換為int型
        correct = tf.cast(tf.equal(pred, y_test), dtype=tf.int32)
        # 將每個batch的correct數加起來
        correct = tf.reduce_sum(correct)
        # 將所有batch中的correct數加起來
        total_correct += int(correct)
        # total_number為測試的總樣本數,也就是x_test的行數,shape[0]返回變數的行數
        total_number += x_test.shape[0]
    # 總的準確率等於total_correct/total_number
    acc = total_correct / total_number
    test_acc.append(acc)
    print("Test_acc:", acc)
    print("--------------------------")

(8) acc / loss視覺化:

# 繪製 loss 曲線
plt.title('Loss Function Curve')  # 圖片標題
plt.xlabel('Epoch')  # x軸變數名稱
plt.ylabel('Loss')  # y軸變數名稱
plt.plot(train_loss_results, label="$Loss$")  # 逐點畫出trian_loss_results值並連線,連線圖示是Loss
plt.legend()  # 畫出曲線圖標
plt.show()  # 畫出影象

# 繪製 Accuracy 曲線
plt.title('Acc Curve')  # 圖片標題
plt.xlabel('Epoch')  # x軸變數名稱
plt.ylabel('Acc')  # y軸變數名稱
plt.plot(test_acc, label="$Accuracy$")  # 逐點畫出test_acc值並連線,連線圖示是Accuracy
plt.legend()
plt.show()

上述兩部分完成了對準確率的計算並可視化準確率與loss。(7)部分前向傳播計算出y,使其符合概率分佈並找到最大的概率值對應的索引號,調整資料型別與標籤一致,如果預測值和標籤相等則correct變數自加一,準確率即預測對了的數量除以測試集中的資料總數。(9)部分可將計算出的準確率畫成曲線圖,通過設定圖示題、設定x軸名稱、設定y軸名稱,標出每個epoch時的準確率並畫出曲線,可用同樣方法畫出loss曲線。結果圖如圖4.1與4.2。

圖4.1 訓練過程loss曲線

圖4.2 訓練過程準確率曲線