商場網(wǎng)站開發(fā)教程搜索引擎優(yōu)化seo專員
文中程序以Tensorflow-2.6.0為例
部分概念包含筆者個人理解,如有遺漏或錯誤,歡迎評論或私信指正。
截圖和程序部分引用自北京大學(xué)機器學(xué)習(xí)公開課
TF2基礎(chǔ)常用函數(shù)
1、張量處理類
強制數(shù)據(jù)類型轉(zhuǎn)換:
a1 = tf.constant([1,2,3], dtype=tf.float64)
print(a1)
a2 = tf.cast(a1, tf.int64) # 強制數(shù)據(jù)類型轉(zhuǎn)換
print(a2)
查找數(shù)據(jù)中的最小值和最大值:
print(tf.reduce_min(a2), tf.reduce_max(a2))
上一行例子中是對整個張量查找,也按照一定的方向查找,只按照行或只按照列,這由axis
變量決定。通常axis=0
代表按列查找,axis=1
代表按行查找
a1 = tf.constant([[1,2,3],[2,3,4]])
print(a1)
print(tf.reduce_max(a1, axis=0)) # 按照列查找最大的行
print(tf.reduce_sum(a1,axis=1)) # 按照行計算各列的和
常見的張量檢索類函數(shù)在tf.reduce_xxx
可以查看
張量中數(shù)據(jù)的索引,可以按照行,或者按照列索引一個張量數(shù)據(jù)中的最大值和最小值
test = np.array([[1, 2, 3],[2, 3, 4],[5, 6, 7], [7, 8, 2]])
print(test)
print(tf.argmax(test, axis=0)) # 按列查找,找到每一列的最大值序列號
print(tf.argmax(test, axis=1)) # 按行查找,找到每一行的最大值序列號
隨機數(shù)生成,最常用的隨機數(shù)生成是正態(tài)分布和均勻分布,有時候后我們期望生成的隨機數(shù)在0到1之間,歸一化的數(shù)據(jù)有利于網(wǎng)絡(luò)的快速收斂。除了上一篇博客深度學(xué)習(xí)筆記(三) 中提及的tf中的隨機數(shù)張量生成,也可以便捷的使用numpy(后文使用np表示)提供的隨機數(shù)生成器,同時補充一點,大多數(shù)區(qū)間范圍性質(zhì)的函數(shù),輸入的區(qū)間都是前閉后開的,這點在tf np skl 甚至其他C++庫中都是成立的。
# 生成[0,1)內(nèi)的隨機數(shù)
rdm = np.random.RandomState(seed=1) # 定義隨機數(shù)生成器rdm,使用隨機數(shù)種子seed=1
usr_random1 = rdm.rand() # 無輸入維度時返回一個常量
usr_random2 = rdm.rand(2, 3) # 返回2行3列的隨機數(shù)矩陣
print("usr_random1= {} \r\n usr_random2= {}".format(usr_random1, usr_random2))
下面繼續(xù)歸納幾個np中常用的數(shù)組處理函數(shù)。這幾個np函數(shù)只做簡單說明,具體用法可以用到時再深度查閱資料
數(shù)組垂直疊加
# 數(shù)組垂直疊加
a1 = np.array([1, 2, 3])
a2 = np.array([4, 5, 6])
b = np.vstack((a1, a2))
print(b)
根據(jù)多組范圍和步長,生成多維數(shù)組,每組的起始結(jié)束和步長可以不同,最終輸出的列數(shù)會以最長的那組為準(zhǔn),行數(shù)等于總的組數(shù):
# np.mgrid[起始值:結(jié)束值:步長,起始值:結(jié)束值:步長, ...... ]
a1, a2 = np.mgrid[1:3:1, 3:6:0.5]
print(a1)
print(a2)
多維數(shù)組拉伸為一個維度,并且將多個數(shù)組對齊后一 一配對:
# a1 a2 續(xù)上一段代碼
b = np.c_[np.ravel(a1), np.ravel(a2)] # a1.ravel()執(zhí)行二維變一維拉伸,np.c_進行組合操作
print(b)
2、數(shù)學(xué)運算類
四則運算類:(注意:只有維度相同的數(shù)據(jù)才可以做四則運算,運算均是對應(yīng)位置元素進行計算,同時tf中,除非指定,默認(rèn)生成的張量數(shù)據(jù)時類型為int32或float32)
a1 = tf.constant([[1,2,3],[1,2,3]])
a2 = tf.constant([[2,3,4],[2,3,4]])
print(tf.add(a1, a2)) # 加
print(tf.subtract(a1, a2)) # 減
print(tf.multiply(a1, a2)) # 乘
print(tf.divide(a1, a1)) # 除
平方與開根號:(這里的計算同樣是對應(yīng)位置元素進行計算)
a1 = tf.fill([1,3], 3.) # 這里的指定值為3. 小數(shù)點是為了生成float32類型數(shù)據(jù)
print(a1)
print(tf.pow(a1, 3)) # 開三次方根,第二個參數(shù)就是開根的次數(shù)
print(tf.square(a1)) # 平方
print(tf.square(a1)) # 開方
張量的叉乘(向量積):
a = tf.ones([3, 2]) # 3行2列
b = tf.fill([2, 3], 3.) # 2行3列
print(tf.matmul(a, b)) # 矩陣叉乘得6行6列,叉乘的兩個矩陣,前者的列數(shù)必須和后者的行數(shù)相等
3、訓(xùn)練處理類
標(biāo)記訓(xùn)練參數(shù),網(wǎng)絡(luò)訓(xùn)練的過程實質(zhì)上最重要的就是更新網(wǎng)絡(luò)中的參數(shù),所以需要告知網(wǎng)絡(luò)中哪一個參數(shù)是可以被跟新的,這樣tensorflow框架會自動的在網(wǎng)絡(luò)反向傳播的過程中記錄每一層的梯度信息,便于處理。
# tf.Variable(初始值) 函數(shù)用于標(biāo)記可變參數(shù)
tf.Variable(tf.random.normal([2,2],mean=0,stddev=1))
標(biāo)簽/特征數(shù)據(jù)匹配,訓(xùn)練之前,預(yù)先準(zhǔn)備的特征數(shù)據(jù)和標(biāo)簽數(shù)據(jù)往往是區(qū)分開的,所以需要將他們一 一對應(yīng)上。將輸入數(shù)據(jù)的特征和標(biāo)簽對應(yīng)匹配,構(gòu)建出新的用于訓(xùn)練的變量:
# data = tf.data.Dataset.from_tensor_slices((特征數(shù)據(jù), 標(biāo)簽數(shù)據(jù))) 可以直接輸入numpy或者tensor格式的數(shù)據(jù)
features = tf.constant([12, 15, 20, 11]) # 特征數(shù)據(jù)
labels = tf.constant([0, 1, 1, 0]) # 標(biāo)簽
dataset = tf.data.Dataset.from_tensor_slices((features, labels)) # 對應(yīng)結(jié)合
for element in dataset:print(element) # 輸出
在上面的程序中from_tensor_slices()
函數(shù)要求兩個數(shù)據(jù)的第一個維度的大小必須相同即可,所以第一行的特征數(shù)據(jù)也可以改為:
features = tf.constant([[12,13], [15,16], [20,21], [10,11]]) # 第一個維度任然是4
記錄梯度,以及自動微分,在訓(xùn)練的過程中自動跟新參數(shù)是一個循環(huán)加反向傳播的過程,反向傳播時,我們需要知道每個網(wǎng)絡(luò)層中損失函數(shù)的梯度,在tf中可以使用上下文記錄器自動在迭代過程中記錄每個層的梯度信息。這主要由兩個函數(shù)組成tf.GradientTape()
函數(shù)起到上下文記錄的作用,用于記錄層信息,gradient()
函數(shù)用于求導(dǎo)即求梯度
with tf.GradientTape() as tape: # 記錄下兩行的層信息w = tf.Variable(tf.constant(3.0)) # 標(biāo)記可變參數(shù)loss = tf.pow(w, 2) # 設(shè)置損失函數(shù)類型
grad = tape.gradient(loss, w) # 損失函數(shù)對w求導(dǎo)
print(grad)
在上面的代碼中tf.pow(w, 2)
表示損失函數(shù)為 l o s s = w 2 loss = w^2 loss=w2 梯度求導(dǎo)后得到 ? w 2 ? w = 2 w \frac{\partial w^2}{\partial w} = 2w ?w?w2?=2w 由于初始的參數(shù)w為3.0,求導(dǎo)后結(jié)果為6.0,程序結(jié)果grad
為6。注意此處使用的with as
結(jié)構(gòu)中必須申明被導(dǎo)的變量,這樣才能正常生效記錄數(shù)據(jù)。
枚舉數(shù)據(jù),為了遍歷數(shù)據(jù)并逐個處理,使用python中內(nèi)置的enumerate(列表名)
進行數(shù)據(jù)的枚舉,通常配合for
使用。
# 枚舉列表
data = ['one', 'two', 'three']
for i, element in enumerate(data): # 返回的第一個是序列號,第二個是內(nèi)容print(i, element)
條件循環(huán),在tf訓(xùn)練輸出時,我們計算得到的結(jié)果有時需要和一個標(biāo)準(zhǔn)數(shù)據(jù)進行匹配判斷并根據(jù)判斷結(jié)果輸出數(shù)據(jù)。可以實現(xiàn)類似于C語言中的循環(huán)+三元操作符的效果。tf.where
函數(shù)的傳參依次是條件語句,真值返回,假值返回。
a = tf.constant([1, 2, 3, 4, 5])
b = tf.constant([0, 1, 3, 4, 5])
c = tf.where(tf.greater(a, b), a, b) # 如果條件為真返回第一個值,條件假返回第二個值
print(c)
上面的代碼中配合tf.greater
函數(shù)來比較大小,整行函數(shù)將會依次遍歷a和b中的元素,當(dāng)a>b為真時返回a,否則返回b
獨熱編碼,在分類的問題中我們還需要了解獨熱碼的概念,通常使用獨熱碼作為標(biāo)簽數(shù)據(jù),在被標(biāo)記的類別中1表示是,0表示非,可以通俗理解為:有幾類被分類數(shù)據(jù)獨熱碼就有幾個,每一類數(shù)據(jù)對應(yīng)一個的獨熱碼,類似譯碼器選址原理。
舉例,有3個類
那么第一類的獨熱碼是: 1 0 0
第2類的獨熱碼是: 0 1 0
第3類的獨熱碼是: 0 0 1
在tf中轉(zhuǎn)化獨熱碼:
classes = 4 # 標(biāo)簽數(shù)
labels = tf.constant([1, 0, 6 ,3]) # 輸入標(biāo)簽數(shù)據(jù)
output = tf.one_hot(labels, depth=classes) # 獨熱碼轉(zhuǎn)換,第一個變量為輸入的標(biāo)簽數(shù)據(jù),第二個為類別數(shù)
print(output)
上面使用了tf.one_hot()函數(shù)用來轉(zhuǎn)化獨熱碼,值得注意的是輸入的數(shù)據(jù)會自動的從小到大排序后再轉(zhuǎn)化對應(yīng)的獨熱碼。所以上面的程序輸出了
tf.Tensor(
[[0. 1. 0. 0.] # 對應(yīng)1[1. 0. 0. 0.] # 對應(yīng)0[0. 0. 0. 0.] # 對應(yīng)6[0. 0. 0. 1.]], # 對應(yīng)3shape=(4, 4), dtype=float32)
softmax()函數(shù),在網(wǎng)絡(luò)輸出的結(jié)果中,如果直接按照最終輸出的值判斷類型結(jié)果往往比較抽象。比如網(wǎng)絡(luò)最終會輸出一個矩陣[2.52, -3.1, 5.62],那么如何確定這個矩陣是對應(yīng)哪一個類別。這里我們需要通過歸一化和概率來判斷,假設(shè)這個輸出的三列矩陣分別對應(yīng)三個類別的得分?jǐn)?shù)值,那我們可以將三個值相加求和再分別除以各自來得到每個數(shù)的百分比占比。當(dāng)然在機器學(xué)習(xí)中softmax()
也是類似這樣做的,不過為了避免負(fù)數(shù)和特殊0值以及數(shù)據(jù)的連續(xù)性,引入指數(shù)函數(shù)輔助計算:
S o f t m a x ( y i ) = e y i ∑ j = 0 n e y i \mathit{Softmax(y_{i} )=\frac{e^{y_{i} } }{ {\textstyle \sum_{j=0}^{n}e^{y_{i} {\LARGE {\ } } } } } } Softmax(yi?)=∑j=0n?eyi??eyi?? 同時softmax()函數(shù)的輸出符合概率分布定義: ? x , P ( X = x ) ∈ [ 0 , 1 ] 且 ∑ x P ( X = x ) = 1 \mathit{{\LARGE } \forall x, P(X=x)\in [0, 1] 且\sum_{x}^{} P(X=x)=1 } ?x,P(X=x)∈[0,1]且x∑?P(X=x)=1 所以在上面的[2.52, -3.1, 5.62]例子中不難計算得到對應(yīng)結(jié)果為[0.256, 0.695, 0.048]
第二列最大,所以我們可以認(rèn)為這個輸出舉證表示第二類的可能性最大。綜上softmax()的屬性決定它大多數(shù)時候應(yīng)用在網(wǎng)絡(luò)的輸出位置。
y = tf.constant([1.01, 2.02, -1.11])
y_out = tf.nn.softmax(y)
print("data {}, after softmax is {}".format(y, y_out))
跟新權(quán)重參數(shù),在上面的程序中完成了數(shù)據(jù)的讀入,損失梯度計算那么計算過的結(jié)果就需要計時更新到權(quán)重上。值得注意,跟新參數(shù)之前一定要申明參數(shù)是可訓(xùn)練自更新的。通常計算得到梯度后直接跟新參數(shù)就可以完成一次反向傳播。
w = tf.Variable(4) # 申明可變參數(shù),并賦初值為4
w.assign_sub(1) # 對可變參數(shù)執(zhí)行一次自減跟新,傳入?yún)?shù)為被減數(shù)
print(w)
根據(jù)鳶尾花數(shù)據(jù)進行簡單的分類任務(wù)
軟件環(huán)境:
cuda = 11.2
python=3.7
numpy==1.19.5
matplotlib== 3.5.3
notebook==6.4.12
scikit-learn==1.2.0
tensorflow==2.6.0
分類時主要有以下幾步:
**1、加載數(shù)據(jù):**這里直接通過sklearn 包中自帶的數(shù)據(jù)進行舉例
# 導(dǎo)入所需模塊
import tensorflow as tf
from sklearn import datasets
from matplotlib import pyplot as plt
import numpy as np
# 導(dǎo)入數(shù)據(jù),分別為輸入特征和標(biāo)簽
x_data = datasets.load_iris().data
y_data = datasets.load_iris().target
2、打亂數(shù)據(jù)順序(由于這里的數(shù)據(jù)是直接加載已有數(shù)據(jù),所以先打亂,對于其他數(shù)據(jù)不一定要這步),分割數(shù)據(jù)為訓(xùn)練部分和測試部分。注意使用隨機數(shù)種子,這樣可以保證在不同設(shè)備和時間運行得到的結(jié)果是相同的。
# 隨機打亂數(shù)據(jù)(因為原始數(shù)據(jù)是順序的,順序不打亂會影響準(zhǔn)確率)
# seed: 隨機數(shù)種子,是一個整數(shù),當(dāng)設(shè)置之后,每次生成的隨機數(shù)都一樣
np.random.seed(116) # 使用相同的seed,保證輸入特征和標(biāo)簽一一對應(yīng)
np.random.shuffle(x_data)
np.random.seed(116)
np.random.shuffle(y_data)
tf.random.set_seed(116)
# 將打亂后的數(shù)據(jù)集分割為訓(xùn)練集和測試集,訓(xùn)練集為前120行,測試集為后30行
x_train = x_data[:-30]
y_train = y_data[:-30]
x_test = x_data[-30:]
y_test = y_data[-30:]
3、轉(zhuǎn)換數(shù)據(jù)類型格式,匹配特征數(shù)據(jù)和標(biāo)簽數(shù)據(jù),設(shè)置訓(xùn)練可變參數(shù),在輸入到tf中進行計算之前要先把np格式的數(shù)據(jù)轉(zhuǎn)化成tf格式。同時輸入的數(shù)據(jù)最好進行分組處理,這樣可以調(diào)整數(shù)據(jù)的吞吐量,適配不同性能的設(shè)備。同時由于數(shù)據(jù)簡單,只構(gòu)建了一個四輸入的單層神經(jīng)元模型。
# 轉(zhuǎn)換x的數(shù)據(jù)類型,否則后面矩陣相乘時會因數(shù)據(jù)類型不一致報錯
x_train = tf.cast(x_train, tf.float32)
x_test = tf.cast(x_test, tf.float32)# from_tensor_slices函數(shù)使輸入特征和標(biāo)簽值一一對應(yīng)。(把數(shù)據(jù)集分批次,每個批次batch組數(shù)據(jù))
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)# 生成神經(jīng)網(wǎng)絡(luò)的參數(shù),4個輸入特征故,輸入層為4個輸入節(jié)點;因為3分類,故輸出層為3個神經(jīng)元
# 用tf.Variable()標(biāo)記參數(shù)可訓(xùn)練
# 使用seed使每次生成的隨機數(shù)相同(方便教學(xué),使大家結(jié)果都一致,在現(xiàn)實使用時不寫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))
4、初始化超參數(shù),一般來說,常用到的超參數(shù)有學(xué)習(xí)率lr
、迭代次數(shù)epoch
、分組大小batch_size
lr = 0.1 # 學(xué)習(xí)率為0.1
train_loss_results = [] # 將每輪的loss記錄在此列表中,為后續(xù)畫loss曲線提供數(shù)據(jù)
test_acc = [] # 將每輪的acc記錄在此列表中,為后續(xù)畫acc曲線提供數(shù)據(jù)
epoch = 500 # 循環(huán)500輪
loss_all = 0 # 每輪分4個step,loss_all記錄四個step生成的4個loss的和
5、開始訓(xùn)練,訓(xùn)練過程中的主要流程為:開始迭代 > 根據(jù)bach分組加載數(shù)據(jù) > 開始記錄梯度信息 > 神經(jīng)元(層)執(zhí)行計算 > 計算結(jié)果softmax > 獨熱碼轉(zhuǎn)換 > 計算loss > 求導(dǎo)計算梯度 > 跟新參數(shù) > 繼續(xù)返回迭代循環(huán)
# 訓(xùn)練部分
for epoch in range(epoch): #數(shù)據(jù)集級別的循環(huán),每個epoch循環(huán)一次數(shù)據(jù)集for step, (x_train, y_train) in enumerate(train_db): #batch級別的循環(huán) ,每個step循環(huán)一個batchwith tf.GradientTape() as tape: # with結(jié)構(gòu)記錄梯度信息y = tf.matmul(x_train, w1) + b1 # 神經(jīng)網(wǎng)絡(luò)乘加運算y = tf.nn.softmax(y) # 使輸出y符合概率分布(此操作后與獨熱碼同量級,可相減求loss)y_ = tf.one_hot(y_train, depth=3) # 將標(biāo)簽值轉(zhuǎn)換為獨熱碼格式,方便計算loss和accuracyloss = tf.reduce_mean(tf.square(y_ - y)) # 采用均方誤差損失函數(shù)mse = mean(sum(y-out)^2)loss_all += loss.numpy() # 將每個step計算出的loss累加,為后續(xù)求loss平均值提供數(shù)據(jù),這樣計算的loss更準(zhǔn)確# 計算loss對各個參數(shù)的梯度grads = tape.gradient(loss, [w1, b1])# 實現(xiàn)梯度更新 w1 = w1 - lr * w1_grad b = b - lr * b_gradw1.assign_sub(lr * grads[0]) # 參數(shù)w1自更新b1.assign_sub(lr * grads[1]) # 參數(shù)b自更新
6、訓(xùn)練的同時在每個epoch中進行一次測試(實際訓(xùn)練時,若果測試輸出需要耗時較高,可以每10次進行一次測試),測試時首先執(zhí)行神經(jīng)元(層)計算,不用進行反向傳播,所以只需要根據(jù)softmax的輸出匹配到對應(yīng)的便簽上并統(tǒng)計正確值的數(shù)量。
# 測試部分# total_correct為預(yù)測對的樣本個數(shù), total_number為測試的總樣本數(shù),將這兩個變量都初始化為0total_correct, total_number = 0, 0for x_test, y_test in test_db:# 使用更新后的參數(shù)進行預(yù)測y = tf.matmul(x_test, w1) + b1y = tf.nn.softmax(y)pred = tf.argmax(y, axis=1) # 返回y中最大值的索引,即預(yù)測的分類# 將pred轉(zhuǎn)換為y_test的數(shù)據(jù)類型pred = tf.cast(pred, dtype=y_test.dtype)# 若分類正確,則correct=1,否則為0,將bool型的結(jié)果轉(zhuǎn)換為int型correct = tf.cast(tf.equal(pred, y_test), dtype=tf.int32)# 將每個batch的correct數(shù)加起來correct = tf.reduce_sum(correct)# 將所有batch中的correct數(shù)加起來total_correct += int(correct)# total_number為測試的總樣本數(shù),也就是x_test的行數(shù),shape[0]返回變量的行數(shù)total_number += x_test.shape[0]# 總的準(zhǔn)確率等于total_correct/total_numberacc = total_correct / total_numbertest_acc.append(acc)
7、輸出結(jié)果,可視化訓(xùn)練過程
# 繪制 loss 曲線
plt.title('Loss Function Curve') # 圖片標(biāo)題
plt.xlabel('Epoch') # x軸變量名稱
plt.ylabel('Loss') # y軸變量名稱
plt.plot(train_loss_results, label="$Loss$") # 逐點畫出trian_loss_results值并連線,連線圖標(biāo)是Loss
plt.legend() # 畫出曲線圖標(biāo)
plt.show() # 畫出圖像# 繪制 Accuracy 曲線
plt.title('Acc Curve') # 圖片標(biāo)題
plt.xlabel('Epoch') # x軸變量名稱
plt.ylabel('Acc') # y軸變量名稱
plt.plot(test_acc, label="$Accuracy$") # 逐點畫出test_acc值并連線,連線圖標(biāo)是Accuracy
plt.legend()
plt.show()
最后的輸出結(jié)果: