深度學習框架之Tensorflow2
Tensorflow2版本的介紹
Tensorflow(簡稱tf)是深度學習框架,大大簡化了建模的方法和步驟,把Keras Api當作核心,使用非常簡單,跨平臺,集成各種現成模型,eager mode使得調試起來不那么難受。
TensorFlow2的官方文檔
Tensorflow2版本的安裝
1.首先得提前安裝好Anaconda環境,可以訪問官方網站下載最新版。
先確定號conda中的python環境是否是Python 3.6–3.9直接的版本。
可以使用依次執行以下命令:
conda activate base # 激活conda中的虛擬環境
python --version # 查看虛擬環境中的python版本
執行結果:
C:\Users\24307>conda activate base(base) C:\Users\24307>python --version
Python 3.9.12(base) C:\Users\24307>
2.其次是安裝Tensorflow的第三方庫。
CPU版本:pip install tensorflow -i https://pypi.tuna.tsinghua.edu.cn/simple/
GPU版本:pip install tensorflow-gpu
# 安裝GPU版本要注意,需要配置號CUDA10版本
Tensorflow2的初體驗
導包
導入Tensorflow2,并且結合numpy包
# 導包
import tensorflow as tf
import numpy as np
查看版本
print(tf.__version__)
# 本篇使用的是2.18.0版本
# Eager Execution 模式
x = [[1,]]
m = tf.matmul(x,x) # 使用 TensorFlow 的 matmul 函數對 x 和自身進行矩陣乘法運算
print(m)
'''
<tf.Tensor: shape=(1, 1), dtype=int32, numpy=array([[1]], dtype=int32)>
'''
構建矩陣
x = tf.constant([[1,9],[3,6]])
print(x)
'''
<tf.Tensor: shape=(2, 2), dtype=int32, numpy=
array([[1, 9],[3, 6]], dtype=int32)>
'''
擴展:Tensor格式,里面可以是一個值,可以是一個向量,可以是一個矩陣,也可以是個多維數據。可以進行GPU加速計算的矩陣
與numpy進行交互
# 將Tensor轉成numpy類的數據
x = tf.constant([[1,9],[3,6]])
x.numpy()
'''
array([[1, 9],[3, 6]], dtype=int32)
'''
數據類型轉換
# 將數據轉成float32類型
x = tf.constant([[1,9],[3,6]])
x = tf.cast(x,tf.float32)
'''
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[1., 9.],[3., 6.]], dtype=float32)>
'''
numpy與Tensor的計算
# numpy 與 Tensor計算
x1 = np.ones([2,2])
x2 = tf.multiply(x1,2)
x2
'''
<tf.Tensor: shape=(2, 2), dtype=float64, numpy=
array([[2., 2.],[2., 2.]])>
'''
深度學習概述
深度學習解決的問題
首先來談談機器學習的流程:
- 數據獲取
- 特征工程
- 建立模型
- 評估與應用
其中,特征工程是四個步驟中,最難的一步。
機器學習絕大多數情況下是偏人工的,要人工的選擇數據,要人工的提取特征,要人工的選擇算法,再人工的得出結果。并不具備真正意義上的智能。
而深度學習一定程度上解決了機器學習中的人工的問題,可以讓網絡可以真正的學習, 什么樣的特征是比較合適的,該怎么組合比較合適 的,這是深度學習能達到的境界。
特征工程的作用:
- 數據特征決定了模型的上限
- 預處理和特征提取是最核心的
- 算法與參數選擇決定了如何逼近這個上限
深度學習的核心是神經網絡,而神經網絡是一個巨大的黑盒子,把原始數據拿到手之后,會做很多很多的操作,能夠自動的提取的各種的特性。深度學習是真正的能夠進行學習的。
深度學習缺陷,速度比不過機器學習。
深度學習的應用領域
深度學習的應用領域越來越廣泛,比如無人駕駛,計算機視覺,自然語言處理,人臉識別,醫學,變臉,分辨率重構……
而IMGENET提供了非常龐大的數據集,供人使用。
深度學習要應用到上百萬的數據集以上,不適合用少量數據集。
CIFAR-10 提供了10類標簽,50萬個訓練數據,1萬個測試數據,但是數據體檢大小均為32*32 ,比較小。比較適合個人筆記本測試學習使用。
計算機視覺任務
圖像分類任務:
假設我們有一系列的標簽:狗,貓,汽車,飛機等,讓計算機識別出哪個是貓類。
圖像在計算機眼中,就是一個矩陣,而矩陣內就是一些值。
一張圖像被表示成三維數組的形式,每個像素的值從0到255
例如:300*100*3,其中3表示顏色通道。
計算機視覺面臨的挑戰:照射角度,形狀改變,部分遮蔽,背景混入……
深度學習常規套路:
- 收集數據并給定標簽
- 訓練一個分類器
- 測試,評估
機器學習的套路亦是如此。
k近鄰計算流程
- 計算已知類別數據集中的點與當前點的距離
- 按照距離依次排序
- 選取與當前點距離最小的k個點
- 確定前k個點所在類別的出現概率
- 返回前k個點出現頻率最高的類別作為當前點預測分類
k近鄰不適合用來圖像分類
因為背景主導是一個最大的問題,k近鄰算法關注的卻是主體(主要成分)。
k近鄰算法
將背景相同的數據集放在一起,它不知道哪個是主體,哪個是背景,沒有一個學習的過程。
搭建神經網絡進行氣溫預測
這里提前準備了temp.csv
文件,里面存放著本次要案例測試的數據。
temp.csv中的字段解釋:
year:年
month : 月
day : 日
week : 星期
temp_2 : 前天的溫度
temp_1 : 昨天的溫度
average : 當日在歷史上的平均溫度
actual : 實際當前溫度(實際預測指標)
friend :
其中 year
, month
, day
, week
, temp_2
, temp_1
,average
均可以看作x,actual
看作y。
具體代碼
# 導包
import numpy as np
import pandas as pd
import matplotlib.pylot as plt
import tensorflow as tf
from tensorflow.keras import layers # 當構建模型的時候,用keras的api構建模型
import tensorflow.keras
import warnings
warngings.filterwarnings("ignore")
%matplotlib inline
# 讀取數據
features = pd.read_csv('temps.csv')
print(features.head())
print("數據維度:",features.shape)
# 處理時間數據(這個可以自己定義時間的格式)
import datetime
years = features['year']
months = features['month']
days = features['day']
# datetime格式
dates = [str(int(year)) + '-' + str(int(month)) + '-' + str(int(day)) for year, month, day in zip(years, months, days)]
dates = [datetime.datetime.strptime(date, '%Y-%m-%d') for date in dates]
# 繪制圖像
# 準備畫圖
# 指定默認風格
plt.style.use('fivethirtyeight')# 設置布局
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(nrows=2, ncols=2, figsize = (10,10))
fig.autofmt_xdate(rotation = 45)# 標簽值
ax1.plot(dates, features['actual'])
ax1.set_xlabel(''); ax1.set_ylabel('Temperature'); ax1.set_title('Max Temp')# 昨天
ax2.plot(dates, features['temp_1'])
ax2.set_xlabel(''); ax2.set_ylabel('Temperature'); ax2.set_title('Previous Max Temp')# 前天
ax3.plot(dates, features['temp_2'])
ax3.set_xlabel('Date'); ax3.set_ylabel('Temperature'); ax3.set_title('Two Days Prior Max Temp')# 我的逗逼朋友
ax4.plot(dates, features['friend'])
ax4.set_xlabel('Date'); ax4.set_ylabel('Temperature'); ax4.set_title('Friend Estimate')plt.tight_layout(pad=2)
好了,以上步驟都是準備階段。
數據預處理
數據歸一化
temp.csv
文件中有一個特殊的字段week,其他字段都是數值,只有week字段是字符的形式。
所以要week字符串 轉成 數值的形式,轉成數值的形式方式有很多種,比如用數值映射,dummies(是誰,誰標1)
# 做dummies,獨熱編碼
features = pd.get_dummies(features)
features.head(5)
指定好train_data 和train_labels值
# 標簽
labels = np.array(features['actual'])# 在特征中去掉標簽,將actual列去除
features = features.drop('actual',axis=1) # x# 單獨保存名字,以備后患
features_list = list(features.columns)# 轉換成合適的格式
features = np.array(features)print(features.shape) # 查看數據形狀
標準化操作
# 標準化操作
from sklearn import preprocessing
input_features = preprocessing.StandardScaler().fit_transform(features)print(input_features[0])
基于Keras構建網絡模型
按順序構造網絡模型
# y = wx + b 模型
model = tf.keras.Sequential()
# layers模塊中有許多層的實現,Dense是連接層
mode.add(layers.Dense(16)) # 第一個隱藏層
mode.add(layers.Dense(32)) # 第二個隱藏層
mode.add(layers.Dense(1)) # 輸出結果個數'''
像上面的16,32是自己指定的,但是沒有什么依據,也可以嘗試其他數值
'''
網絡模型訓練
常用參數:
activation
: 激活函數的選擇,一般常用relukernel_initializer
,bias_initializer
: 權重與偏置參數的初始化方法,有時候不收斂換種初始化就突然不好使了,就很神奇kernel_regularizer
,bias_regularizer
: 要不要加入正則化inputs
: 輸入,可以自己指定,也可以讓網絡自動選units
: 神經元個數
指定好優化器和損失函數等
一般用迭代的思想優化網絡模型
# compile相當于對網絡進行配置
model.compile(optimizer=tf.keras.optimizers.SGD(0.001),loss='mean_squared_error')# optimizer 指定優化器 SGD梯度下降
# loss 指定損失函數,要指定合適的損失函數# 不同的損失函數 對 最終的結果 影響會很大
開始訓練
model.fit(input_features,labels,validation_split=0.25,epochs=10,batch_size=64)'''
x: input_features 上面的標準化操作后的數據
y: labels 來自于上面的np.array(features['actual'])
驗證級 : validation_split=0.25 也就是25%
迭代次數(遍歷數據集的輪數):epochs
每次迭代的個數:batch_size
'''
訓練出來的數據,loss 表示訓練上的損失值,val_loss 表示驗證級損失值,當兩者的數據相差很大的時候,說明當前網絡模型過擬合了,當然要避免過擬合的情況。
# 將網絡模型從上到下 展示出來
model.summary()input_features.shape # (348,14) 14個特征'''
dense 第一層
dense_1 第二層
dense_2 第三層
parms 各層的值w1+b1 : 14*16+16 # 16表示隱層神經元 【240】
w2+b2 : 32*16+32 # 【544】
w3+b3 : 32*1+1 # 【33】 '''
以下是上述代碼的輸出結果:
Model : ”sequential“
Layer(type) Output Shape(輸出形狀) Param#(訓練參數) dense(Dense) multiple 240 dense_1 (Dense) multiple 544 dense_2 (Dense) multiple 33 Total params:817
Trainable params:817
Non-trainable params:0
更新初始化方法
model = tf.keras.Sequential()model.add(layers.Dense(16,kernel_initializer="random_normal"))
model.add(layers.Dense(32,kernel_initializer="random_normal"))
model.add(layers.Dense(1,kernel_initializer="random_normal"))'''
kernel_initializer=random_normal
指定權重為 隨機高斯分布
'''# 指定優化器和損失函數
model.compile(optimizer=tf.keras.optimizers.SGD(0.001),loss='mean_squared_error')# 開始訓練
model.fit(input_features,labels,validation_split=0.25,epochs=100,batch_size=64)
此時,loss 與 val_loss 相差就減少了。
加入正則化懲罰項
# kernel_regularizer=tf.keras.regularizers.l2(0.03) 加入正則化懲罰項
model = tf.keras.Sequential()
model.add(layers.Dense(16,kernel_initializer='random_normal',kernel_regularizer=tf.keras.regularizers.l2(0.03)))
model,add(layers.Dense(32,kernel_initializer='random_normal',kernel_regularizer=tf.keras.regularizers.l2(0.03)))
model.add(layers.Dense(1,kernel_initializer='random_normal',kernel_regularizer=tf.keras.regularizers.l2(0.03)))model.compile(optimizer=tf.keras.optimizers.SGD(0.001),loss='mean_squared_error')model.fit(input_features,labels,validation_split=0.25,epochs=100,batch_size=64)
可以發現fit出來的結果,loss 與 val_loss 相差比之前更小了
預測模型結果
當得到訓練好的權重參數,用權重參數 把三層網絡模型 走一遍
# input_features 其實就是 x 的值
predict = model.predict(input_features)
predict.shape # (348,1)
測試結果并進行展示
這步驟 可以忽略
# 數據處理
# 轉換日期格式
dates = [str(int(year)) + '-' + str(int(month)) + '-' + str(int(day)) for year, month, day in zip(years, months, days)]
dates = [datetime.datetime.strptime(date, '%Y-%m-%d') for date in dates]# 創建一個表格來存日期和其對應的標簽數值
true_data = pd.DataFrame(data = {'date': dates, 'actual': labels})# 同理,再創建一個來存日期和其對應的模型預測值
months = features[:, features_list.index('month')]
days = features[:, features_list.index('day')]
years = features[:, features_list.index('year')]test_dates = [str(int(year)) + '-' + str(int(month)) + '-' + str(int(day)) for year, month, day in zip(years, months, days)]test_dates = [datetime.datetime.strptime(date, '%Y-%m-%d') for date in test_dates]predictions_data = pd.DataFrame(data = {'date': test_dates, 'prediction': predict.reshape(-1)})
# 真實值
plt.plot(true_data['date'], true_data['actual'], 'b-', label = 'actual')# 預測值
plt.plot(predictions_data['date'], predictions_data['prediction'], 'ro', label = 'prediction')
plt.xticks(rotation = '60');
plt.legend()# 圖名
plt.xlabel('Date'); plt.ylabel('Maximum Temperature (F)'); plt.title('Actual and Predicted Values');
分類任務
準備數據集
這里是從網絡上下載mnist的數據集
%matplotlib inline
from pathlib import Path
import requestsDATA_PATH = Path("data")
PATH = DATA_PATH / "mnist"'''
parents=True : 遞歸創建所有必要的父級目錄
exist_ok=True :當目標目錄已存在時,不會拋出異常
'''
PATH.mkdir(parents=True, exist_ok=True)URL = "http://deeplearning.net/data/mnist/"
FILENAME = "mnist.pkl.gz"if not (PATH / FILENAME).exists():content = requests.get(URL + FILENAME).content(PATH / FILENAME).open("wb").write(content)
但是下載的速度比較慢,如果不愿意等待的話,那可以直接用已經下載好的壓縮包文件mnist.pkl.gz
讀取壓縮包中的數據
import pickle # 用于 序列化 和 反序列化 Python對象
import gzip # 用于讀寫gzip壓縮的文件#as_posix(),將路徑轉換成 POSIX 格式的字符串
with gzip.open((PATH / FILENAME).as_posix(), "rb") as f:((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding="latin-1")'''
x_train:訓練集的特征數據。
y_train:訓練集的標簽數據。
x_valid:驗證集的特征數據。
y_valid:驗證集的標簽數據。
_:表示忽略加載數據中的第三個部分(通常是測試集,但在這里被忽略)。'''
展示數據集
# 展示數據集
from matplotlib import pyplot
import numpy as nppyplot.imshow(x_train[0].reshape((28,28)),cmap="gray")
print(x_train.shape)
'''
(50000,784)
784是mnis數據集每個樣本的像素點個數
'''
構建網絡模型
# 導包
import tensorflow as tf
from tensorflow.keras import layers
# 創建Sequential模型,通過簡單的堆疊來構建模型
model = tf.keras.Sequential()
# Dense() 是全連接層(密集層)
model.add(layers.Dense(32,activation='relu')) # 第一層
model.add(layers.Dense(32,activation='relu')) # 第二層
model.add(layers.Dense(10,activation='softmax')) # 輸出層 使用Softmax激活函數
選擇損失函數和優化器
# Adam優化器,基于梯度下降的優化算法,0.005是學習率
# 當目標執行多類分類,并且標簽是以整型形式給出,就可以使用 SparseCategoricalCrossentropy (稀疏分類交叉熵)損失函數
# metrics 設置評估指標,在訓練過程中跟蹤稀疏分類(SparseCategoricalAccuracy)準確率作為性能評估指標
model.compile(optimizer=tf.keras.optimizers.Adam(0.005),loss=tf.keras.losses.SparseCategoricalCrossentropy(),metrics=[tf.keras.metrics.SparseCategoricalAccuracy()])
開始訓練
model.fit(x_train, # xy_train, # yepochs=5, # 訓練周期batch_size=64, # 批量大小validation_data=(x_valid, y_valid) # 驗證數據
)'''
validation_data=(x_valid, y_valid):
validation_data 提供了一組用于驗證的數據。在每個epoch結束時,模型將在這些驗證數據上進行評估,以監控模型在未見過的數據上的表現。
x_valid 是驗證數據的輸入特征。
y_valid 是驗證數據的真實標簽。
'''
Tensor.data模塊常用函數
轉成Tensor格式數據
在使用Tensorflow框架的時候,建議使用Tensor格式的數據,但是有些數據不是該格式,那就可以用tf.data模塊中的函數來轉成Tensor格式的數據。
# 隨便構建一組非Tensor的數據
import numpy as np
input_data = np.arange(16)
print(input_data)
# 將input_data 轉成Tensor個數的數據
dataset = tf.data.Dataset.from_tensor_slices(input_data)
# 循環遍歷Tensort數據
for data in dataset:print(data)
repeat操作
格式:.repeat(次數)
重復構建數據
# 重復構建input_data數據
dataset = tf.data.Dataset.from_tensor_slices(input_data)dataset = dataset.repeat(2) # 重復構建兩次# 循環遍歷
for data in dataset:print(data)
batch操作
設置每個Tensor中的數據數量
dataset = tf.data.Dataset.from_tensor_slices(input_data)
dataset = dataset.repeat(2).batch(4) # 每個Tensor中有4個數據for data in dataset:print(data)
shuffle 操作
shuffle翻譯是洗牌的意思,將Tensor數據中的數據順序打亂
# buffer_size(緩存區)必傳參數
dataset = tf.data.Dataset.from_tensor_slices(input_data).shuffle(buffer_size=10).batch(4)for data in dataset:print(data)
重新訓練
train = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train = train.batch(32)
train = train.repeat()valid = tf.data.Dataset.from_tensor_slices((x_valid, y_valid))
valid = valid.batch(32)
valid = valid.repeat() # 使用 .repeat() 會使數據集無限重復'''
train : 訓練集
validation_data : 驗證集
epochs : 訓練周期
steps_per_epoch : 每個周期訓練的步數,(每個步處理一個批次)
validation_data : 每個周期驗證的步數,(每個步處理一個批次)'''model.fit(train, epochs=5,steps_per_epoch=100, validation_data=valid,validation_steps=100)
練手——fashion數據集
fashion數據集簡介
Fashion MNIST 數據集由 Zalando (一家德國的時尚技術平臺) 提供,它旨在作為手寫數字識別任務(MNIST 數據集)的一個更復雜的替代品。Fashion MNIST 包含了 70,000 張 28x28 像素的灰度圖像,分為 10 個類別,每個類別有 7,000 張圖片。這些圖像是各種服裝和配飾的圖片,如T恤、褲子、鞋子等。
以下是 Fashion MNIST 數據集的一些關鍵特點:
- 圖像大小:每張圖像都是 28x28 像素。
- 圖像格式:灰度圖像,即每個像素值范圍從 0 到 255,其中 0 表示黑色,255 表示白色。
- 類別數量:10 個類別,每個類別對應一種特定的服裝或配飾。
- 數據劃分:數據集被劃分為訓練集和測試集,訓練集包含 60,000 張圖像,測試集包含 10,000 張圖像。
- 類別分布:每個類別在訓練集和測試集中都有均勻的分布。
類別說明
0
- T-shirt/top 上面的衣服1
- Trouser 下面的褲子2
- Pullover 冷天穿的上衣3
- Dress 連衣裙4
- Coat 外套5
- Sandal 涼鞋6
- Shirt 襯衫7
- Sneaker 運動鞋8
- Bag 包9
- Ankle boot 踝靴
Fashion MNIST 實際數據集中的圖像沒有背景色,是純灰度的。這些圖像通常以 numpy 數組的形式存儲,每個數組的形狀為 (28, 28),數組中的每個元素表示一個像素的灰度值。
準備 fashion 數據集
通過tensorflow 下載 fashion 數據集
# 通過tensorflow 來下載 fashion 數據集
from tensorflow import keras# 下載
fashion_mnist = keras.datasets.fashion_mnist
(train_images,train_labels),(test_images,test_labels) = fashion_mnist.load_data()'''
train_images: 訓練集的圖像數據,形狀為 (60000, 28, 28),表示 60,000 張 28x28 像素的灰度圖像。
train_labels: 訓練集的標簽數據,形狀為 (60000,),表示 60,000 個標簽,每個標簽是一個從 0 到 9 的整數。
test_images: 測試集的圖像數據,形狀為 (10000, 28, 28),表示 10,000 張 28x28 像素的灰度圖像。
test_labels: 測試集的標簽數據,形狀為 (10000,),表示 10,000 個標簽,每個標簽是一個從 0 到 9 的整數。
'''
fashion 數據集下載到了 C:\Users\24307.keras\datasets 目錄下
圖像數據 是指 全部的圖像
標簽數據 是指 全部圖像的分類,每個分類對應0-9之間的整數
數據預處理
將數據集中的數字標簽映射到人類可讀的類別名稱
在處理Fashion MNIST數據集時,通常會定義這樣一個class_names
列表來幫助解釋模型的預測結果。例如,當模型對一張圖片的預測結果為3時,可以通過class_names[3]
獲取到對應的類別名"Dress",從而知道模型預測這張圖片屬于"Dress"類別。
# 定義class_name字段 圖像分類
class_names = ['T-shirt/top','Trouser','Pullover','Dress','Coat','Sandal','Shirt','Sneaker','Bag','Ankle boot'
]# 定義class_names列表是為了將數據集中的數字標簽映射到人類可讀的類別名稱。
當然,可以嘗試的查看以下 訓練集中的圖片數據
# 嘗試 查看fashion 數據集中的 數據形狀
train_images.shape # (60000, 28, 28)
此時train_images[]
中的每個下標都代表著不同的服裝,比如說train_images[0]
就是一個鞋子,train_images[1]
就是一件上衣
繪制train_images中的圖像
# 繪制 fashion數據集中的 圖像
import matplotlib.pyplot as plt
plt.figure(figsize=(2,2)) # 準備畫布
plt.imshow(train_images[0]) # 在畫布繪制圖像
plt.colorbar() # 給圖像添加熱度條
# plt.grid(False) # 取消圖像中的網格線,默認是Tureplt.show() # 展示
數據歸一化
就是將 數據 轉換成 0到1之間的數
# 數據歸一化
train_images = train_images / 255.0
test_images = test_images / 255.0
繪制 train_labels中的 0-25之間的圖像
# 繪制 train_labels 中的 25張圖# 繪制畫布
plt.figure(figsize=(10,10))for i in range(25):plt.subplot(5,5,i+1) # 繪制子畫布plt.xticks([]) # x軸的刻度plt.yticks([]) # y軸的刻度plt.xlabel(class_names[train_labels[i]]) # 設置x軸標簽plt.grid(False) # 取消刻度plt.imshow(train_images[i],cmap=plt.cm.binary) # 展示子圖片
plt.show() # 展示所有圖片
構建網絡模型
'''
keras.Sequential() 用于線性堆疊網絡層(順序模型)
Flatten層 : 將輸入張量的所有非批量維度展平成一維的維度,
input_shape參數:指定輸入張量的形狀,不包括批量維度。
'''
model = keras.Sequential([keras.layers.Flatten(input_shape=(28,28)),keras.layers.Dense(128,activation="relu"),keras.layers.Dense(10,activation="softmax")
])
選擇模型構建三要素
選擇優化器和損失函數 和 評估指標
# 選擇Adam優化器 和 sparse_categorical_crossentropy損失函數 和 accuracy評估指標
model.compile(optimizer="adam",loss="sparse_categorical_crossentropy",metrics=['accuracy']
)
開始訓練
'''
x : 輸入數據
y : 目標數據(標簽)
batch_size : 每個批次的樣本數
epochs : 訓練的輪數
validation_data :驗證數據, 用于驗證的輸入數據和目標數據。在每個 epoch 結束時,模型會使用這些數據評估性能
'''
model.fit(train_images,train_labels,epochs=10)
以下是訓練出來的結果:
Epoch 1/10
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 5s 2ms/step - accuracy: 0.1027 - loss: 2.3028
Epoch 2/10
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 4s 2ms/step - accuracy: 0.1005 - loss: 2.3028
Epoch 3/10
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 4s 2ms/step - accuracy: 0.0989 - loss: 2.3027
Epoch 4/10
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 4s 2ms/step - accuracy: 0.0984 - loss: 2.3027
Epoch 5/10
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 4s 2ms/step - accuracy: 0.1013 - loss: 2.3026
Epoch 6/10
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 4s 2ms/step - accuracy: 0.0980 - loss: 2.3028
Epoch 7/10
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 4s 2ms/step - accuracy: 0.0998 - loss: 2.3027
Epoch 8/10
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 4s 2ms/step - accuracy: 0.0981 - loss: 2.3027
Epoch 9/10
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 4s 2ms/step - accuracy: 0.0962 - loss: 2.3028
Epoch 10/10
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 4s 2ms/step - accuracy: 0.0995 - loss: 2.3027
[100]:
<keras.src.callbacks.history.History at 0x2145e1e67d0>
以上的訓練參數是:
x/1875
表示 一共訓練1875個批次,當前是第x個批次
4s
表示每個epoch的訓練時間大約是4秒
2ms/step
表示每個批次的平均處理時間大約是2毫秒
accuracy
表示準確率
loss
表示損失
總體來看:
? loss損失值始終保持在較高的水平(2.3027 到 2.3028),這進一步證實了模型沒有有效地學習到數據的模式。
? accuracy準確率始終徘徊在10% 左右,這幾乎是隨機猜測的水平(10 個類別,隨機猜測的準確率約為 10%)。
這表明模型沒有學會從輸入數據中提取有用的特征。
評估操作
評估模型在測試集上的性能
# evaluate() 方法會計算模型在數據上的損失值 和 準確率
test_loss,test_acc = model.evaluate(test_images,test_labels,verbose=2)
print('\nTest accurary:',test_acc)'''test_images: 測試集的輸入數據。test_labels: 測試集的目標數據(標簽)verbose : 控制評估過程的輸出詳情度,0表示不輸出任何信息,1表示輸出進度條,2表示每個epoch輸出一行日志exaluate() 方法會返回一個包含損失值 和 準確率 的列表。通常,損失值是第一個元素,準確率是第二個元素。
'''
以下是本次案例的輸出結果
313/313 - 0s - 1ms/step - accuracy: 0.8799 - loss: 0.3474
# 處理了313次批次,模型的準確率為87.99%,意味著模型在測試集上正確分類了約87.99%的樣本,損失值為0.3474
# 損失值是模型預測值與實際值之間差異的度量,通常越低越好。Test accurary: 0.8798999786376953
# Test accurary是打印輸出時的 提示信息,測試集的準確率是 0.8798999786376953
總結
- 評估過程:
- 模型在測試集上進行了評估,共處理了 313 個批次。
- 每個批次的平均處理時間為 1 毫秒。
- 模型在測試集上的準確率為 87.99%,損失值為 0.3474。
- 打印的測試準確率:
- 打印出的測試準確率為
0.8798999786376953
,與評估過程中的0.8799
一致。
模型預測
'''
predict() 方法用于模型預測,對新的圖像數據進行預測,通過傳入的數據是 測試集 數據
該方法會返回 num_samples(測試集中的樣本數量) 和 num_classes(類別的數量,對于fashion數據集,就是10)'''
predictions = model.predict(test_images)
print(predictions[0]) # predictions[i] 是一個長度為 10 的一維數組,表示模型對第 i 個樣本屬于每個類別的概率分布。'''
model.predict(test_images) 會將 test_images 作為輸入,通過模型的各個層進行前向傳播,最終輸出每個樣本屬于每個類別的概率。
'''
找出模型認為可能性最大的數據
np.argmax
是 NumPy 庫中的一個函數,用于找到數組中最大值的索引。
# 找出 模型認為該樣本最有可能屬于的類別
np.argmax(predictions[0]) # np.int64(9)
對于fashion數據集來說,predictions[0]
中 的圖像 屬于 編號為9的類型,而編號為9對應 Ankle boot
也就是踝靴
。
預測模型的效果展示
其中藍色表示正確,紅色表示錯誤
定義函數,用來繪制 預測模型 的 結果
def plot_image(i, predictions_array, true_label, img):predictions_array, true_label, img = predictions_array, true_label[i], img[i]plt.grid(False)plt.xticks([])plt.yticks([])plt.imshow(img, cmap=plt.cm.binary)predicted_label = np.argmax(predictions_array)if predicted_label == true_label:color = 'blue'else:color = 'red'plt.xlabel("{} {:2.0f}% ({})".format(class_names[predicted_label],100*np.max(predictions_array),class_names[true_label]),color=color)def plot_value_array(i, predictions_array, true_label):predictions_array, true_label = predictions_array, true_label[i]plt.grid(False)plt.xticks(range(10))plt.yticks([])thisplot = plt.bar(range(10), predictions_array, color="#777777")plt.ylim([0, 1])predicted_label = np.argmax(predictions_array)thisplot[predicted_label].set_color('red')thisplot[true_label].set_color('blue')
預測案例一:
i = 0
plt.figure(figsize=(6,3))
plt.subplot(1,2,1)
plot_image(i, predictions[i], test_labels, test_images)
plt.subplot(1,2,2)
plot_value_array(i, predictions[i], test_labels)
plt.show()
可見上述的預測,是正確的,圖像是 踝靴,預測出來的也是 踝靴。
預測案例二:
i = 12
plt.figure(figsize=(6,3))
plt.subplot(1,2,1)
plot_image(i, predictions[i], test_labels, test_images)
plt.subplot(1,2,2)
plot_value_array(i, predictions[i], test_labels)
plt.show()
但是上述的預測,就有點問題,其實圖像是 運動鞋,但是預測出來的是 涼鞋
保存訓練好的網絡模型
保存整個模型
保存的內容
- 模型架構
- 保存模型的結構,包括所有層的配置和連接方式。
- 模型權重
- 保存模型在訓練過程中學到的權重參數。
- 優化器狀態
- 保存優化器的狀態,包括學習率、動量等,這樣在重新加載模型時可以從上次訓練的狀態繼續訓練。
整個模型包括架構、權重和優化器狀態
# 保存訓練好的模型
# model.save('fashion_model.h5') # 本地會多出一個fashion_model.h5文件,
model.save('fashion_model.keras') # h5格式的文件已經過時,現在建議用keras格式的文件'''
model: 你已經訓練好的 Keras 模型對象。
.h5 是 HDF5 文件格式,常用于保存 Keras 模型。
'''
網路架構轉JSON字符串
to_json()
將模型的配置(即模型的結構、層信息、層的配置等)序列化為一個 JSON 格式的字符串。
要注意的是,這個 JSON 字符串僅包含模型的配置信息,不包含模型的權重。如果你想要保存和加載包含權重的完整模型,你應該使用 model.save()
方法(推薦使用 .keras
格式或 tf.saved_model
格式)而不是 to_json()
。
將網絡架構 序列化成 JSON格式的字符串
# 只保存網絡架構
config = model.to_json()
print(config)
將序列化后的JSON字符串保存到 json 文件中
with open('config.json','w') as json:json.write(config)
讀取網絡架構
# 讀取保存的網路架構
'''models.model_from_json() 是 Keras 中用于從 JSON 格式的字符串重建模型架構的方法其中config參數 是 上面保存的網絡架構
'''
model = keras.models.model_from_json(config)
model.summary()
以下是輸出結果:
Model: "sequential_2"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ Layer (type) ┃ Output Shape ┃ Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ flatten_1 (Flatten) │ (None, 784) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_5 (Dense) │ (None, 128) │ 100,480 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_6 (Dense) │ (None, 10) │ 1,290 │
└──────────────────────────────────────┴─────────────────────────────┴─────────────────┘Total params: 203,542 (795.09 KB)Trainable params: 101,770 (397.54 KB)Non-trainable params: 0 (0.00 B)Optimizer params: 101,772 (397.55 KB)
權重參數的操作
權重參數(weight) 決定了模型如何對輸入數據進行變換以生成輸出。
**權重參數 **在訓練過程中通過優化算法(如梯度下降)不斷調整,以使模型能夠更好地擬合訓練數據并進行準確的預測。
權重參數定義 是神經網絡中連接不同神經元之間的數值。每個神經元的輸出是其輸入的加權和,再經過激活函數處理。
權重參數的作用:特征提取、決策邊界、非線性變換。
權重參數的HDF5文件是以 .weights.h5 結尾
獲取權重參數
weights = model.get_weights()
print(weights)
保存權重參數
model.save_weights('weights.weights.h5')
加載權重參數
# 加載 保存的權重參數
model.load_weights('weights.weights.h5')
讀取網絡模型
讀取已經訓練好了的網絡模型 fashion_model.keras,然后拿來進行 模型預測
準備數據
導包
import tensorflow as tf
from tensorflow.keras import layers
from tensorflow import keras
準備已經訓練好的網絡模型文件 和 fashion數據集
# 加載已經訓練好的 網絡模型 文件
model = keras.models.load_model("fashion_model.keras")
# 加載fashion數據集
fashion_mnist = keras.datasets.fashion_mnist # 通過 tensorflow 下載 fashion_mnsit 數據,下載到本地磁盤的 C:\Users\24307.keras\datasets 目錄下
(tarin_images,tarin_labels),(test_images,test_labels) = fashion_mnist.load_data() # 加載數據
數據預處理
將 測試集 和 訓練集的 數據進行歸一化 到 0-1區間的數
# 數據歸一化
tarin_images = tarin_images / 255.0
test_images = test_images / 255.0
預測模型
使用已經構建好的網絡模型 來 預測 fashion中的數據集
# 模型預測
predictions = model.predict(test_images)
# 查看模型預測后的部分結果
predictions[0]
'''
輸出結果:
array([2.0889627e-06, 1.0242358e-06, 4.6287091e-08, 1.1241977e-11,6.7687038e-06, 2.1740841e-03, 2.5636136e-06, 1.0373446e-02,8.4099341e-08, 9.8743975e-01], dtype=float32)
'''
展示預測結果
1.定義一個方法,用來繪制模型的圖像
import matplotlib.pyplot as plt
import numpy as npdef plot_image(i, predictions_array, true_label, img):predictions_array, true_label, img = predictions_array, true_label[i], img[i]plt.grid(False)plt.xticks([])plt.yticks([])plt.imshow(img, cmap=plt.cm.binary)predicted_label = np.argmax(predictions_array)if predicted_label == true_label:color = 'blue'else:color = 'red'plt.xlabel("{} {:2.0f}% ({})".format(class_names[predicted_label],100*np.max(predictions_array),class_names[true_label]),color=color)def plot_value_array(i, predictions_array, true_label):predictions_array, true_label = predictions_array, true_label[i]plt.grid(False)plt.xticks(range(10))plt.yticks([])thisplot = plt.bar(range(10), predictions_array, color="#777777")plt.ylim([0, 1])predicted_label = np.argmax(predictions_array)thisplot[predicted_label].set_color('red')thisplot[true_label].set_color('blue')class_names = ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat','Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']
2.用定義的方法,來繪制模型預測的圖像
num_rows = 5
num_cols = 3
num_images = num_rows*num_cols
plt.figure(figsize=(2*2*num_cols, 2*num_rows))
for i in range(num_images):plt.subplot(num_rows, 2*num_cols, 2*i+1)plot_image(i, predictions[i], test_labels, test_images)plt.subplot(num_rows, 2*num_cols, 2*i+2)plot_value_array(i, predictions[i], test_labels)
plt.tight_layout()
plt.show()
CNN卷積神經網絡
卷積神經網絡的概述
卷積神經網絡(CNN) 可以用于檢測任務,分類和檢索(比如淘寶的拍照搜索),超分辨率重構,醫學任務,無人駕駛,人臉識別
卷積神經網絡與傳統的神經網絡相比,減少了矩陣的大小,解決了訓練速度慢以及過擬合的情況
與傳統的神經網絡相比,卷積神經網絡輸入的數據是個三維的數據,也就是(h*w*c)。
也就是說傳統的神經網絡的輸入層是一個一位數據,即使是一張28*28大小的圖,也會被輸入成784,但是卷積神經網絡輸入的依然還是28*28*1的多維數據。
卷積神經網絡整體架構
輸入層 : 輸入的數據,例如 28*28
卷積層 : 提取特征
池化層 : 壓縮特性
全連接層 :通過權重參數,例如 784*24
卷積層
卷積的作用
將輸入的圖像分割成一小塊,然后再在這小塊的部分中,找出權重參數,計算出最佳的值。
卷積特征值計算方法
RGB圖像顏色通道,將分割出來的一小塊中的R通道、G通道、B通道分別做計算,最終將每個通道卷積完的結果加在一起。最終結果要+1
卷積層涉及參數
滑動窗口步長 :每次移動的單元格個數,圖像一般用步長1
卷積核尺寸 : 選擇區域的大小,一般情況情況3*3
邊緣填充 : 在區域的最外層加一圈0(zero padding)
卷積核個數 : 最終要得到特征圖的個數,一但確定,不能改變
卷積結果計算公式
長度:
H 2 = H 1 ? F H + 2 P S + 1 H2 = \dfrac{H_1-F_H+2P}{S} +1 H2=SH1??FH?+2P?+1
寬度:
W 2 = W 1 ? F w + 2 P S + 1 W2=\dfrac{W_1-F_w+2P}{S}+1 W2=SW1??Fw?+2P?+1
其中W1、H1表示輸入的寬度、長度;
W2、H2表示輸出特征圖的寬度、長度;
F表示卷積核長和寬的 大小;
S表示滑動窗口的步長;
P表示邊界填充(加幾圈0)
**提問:**如果輸入數據是32*32*3的圖像,用10個5*5*3的filter來進行卷積操作,指定步長為1,邊界填充為2,最終輸入的規模為?
答案:(32-5+2*2)/1+1=32,所以輸出規模為32*32*10,
經過卷積操作后也可以保持特征圖長度、寬度不變。
卷積參數共享
**提問:**數據依舊是32*32*3的圖像,繼續用10個5*\5*3的filter來進行卷積操作,所需的權重參數有多少個呢?
**回答:**5*5*3=75,表示每一個卷積核只需要75個參數,此時有10個不同的卷積核,就需要10*75=750個卷積核參數,不要忘記還有b參數,每個卷積核都有一個對應的偏置參數,最終只需要750+10=760個權重參數。
池化層
通過卷積層提取的特征數據,過于多,所以就需要池化層來壓縮,也就是選擇重要的特征留下,不重要的特征去除。
池化層沒有涉及到任何計算,只是做了篩選
最大池化(MAX POOLING)
把原始的輸入數據,劃分區域,在每個區域取出最大值。
整體網絡架構
網絡架構的流程
一次卷積(CONV),一次激活函數(RELU);
兩次卷積和RELU,一次池化(POOL);
經過多次上訴兩步驟,最終得到FC(全連接層),分類結果[一維特征向量,分類個數],例如,得到的輸入結果是32*32*3,那么一維特征向量就是10240。
帶參數計算的才能算一層,所以CONV(卷積)是一層,FC(全連接層)是一層,但是RELU和POOL就不能算是一層。
經典網絡架構特點
Alexnet 經典網絡架構
提取特征太多(11*11),步長太大(4),不加外層(padding)
VGG網絡架構
所有的卷積大小都是3*3
Resnet殘差網絡(推薦)
使用同等映射,當其中的卷積層不好,就直接跳過不好的卷積層,前一層和 不好的卷積層 連接,前一層再和后面的卷積層連接。
感受野
某一層的卷積層 能夠感受到 前一層的卷積層。
感受野越大越好。
如果堆疊3個3*3的卷積層,并且保持滑動窗口步長為1,其感受野就是7*7的了。
假設輸入大小都是h*w*c
,并且都使用c
個卷積核(得到c個特征圖),可以來計算以下其各自所需參數:
一個7*7卷積核所需參數:
C x (7 x 7 x C) = 49*C的平方
3個3*3卷積核所需參數:
3 x C x (3 x 3 x C) = 27 * C的平方
很明顯,堆疊小的卷積核所需的參數更少一些,并且卷積過程越多,特征提取也會越細致,加入的非線性變換也隨著增多,還不會增大權重數個數,這就是VGG網絡的基本出發點,用小的卷積來完成特征提取操作。
貓狗識別任務
貓狗識別任務大致流程:
- 數據預處理:圖像數據處理,準備訓練核驗證數據集
- 卷積網絡模型:構建網絡架構
- 過擬合問題:觀察訓練核驗證效果,針對過擬合問題提出解決辦法
- 數據增強:圖像數據增強方法與效果
- 遷移學習:深度學習必備訓練策略
準備數據
在data/cats_and_dogs目錄下,分別有兩個文件,訓練集(train) 和 驗證集(validation),而訓練集和驗證集文件中分別對應dogs和cats文件夾
導包工具包
import os
import warnings
warnings.filterwarnings("ignore")
import tensorflow as tf
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.preprocessing.imgae import ImageDataGenerator
指定好數據路徑(訓練和驗證)
# 數據所在文件夾
base_dir = './data/cats_and_dogs'
train_dir = os.path.join(base_dir, 'train')
validation_dir = os.path.join(base_dir, 'validation')# 訓練集
train_cats_dir = os.path.join(train_dir, 'cats')
train_dogs_dir = os.path.join(train_dir, 'dogs')# 驗證集
validation_cats_dir = os.path.join(validation_dir, 'cats')
validation_dogs_dir = os.path.join(validation_dir, 'dogs')
構建卷積神經網絡模型
構建幾層都可以,隨便玩。
如果用CPU訓練,可以把輸入設置的更小一些,一般輸入的大小決定了訓練速度。
一般的規格是 224*224*3,這種適合于GPU;
如果是CPU跑,建議構建 64*64*3的規格
Conv2D()
方法 卷積是針對于圖像數據,Conv3D 是針對于視頻數據。
以下是Conv2D()的參數列表:
filters : 特征圖的個數
kernel_size : 卷積核大小
strides : 步長,每次移動單元格的數量
padding : 邊界填充
model = tf.keras.models.Sequential(['''32:表示得到的特征圖(3,3) : 表示filter的h*w 是 3*3activation : 激活層,一般就指定relu就好input_shape : 如果不指定,會自動判斷,指定的話,強制改成指定矩陣的大小'''tf.keras.layers.Conv2D(32,(3,3),activation='relu',input_shape=(64,64,3)),tf.keras.layers.MaxPooling2D(2,2), # 池化層tf.keras.layers.Conv2D(64,(3,3),activation='relu'),tf.keras.layers.MaxPooling2D(2,2)tf.keras.layers.Conv2D(128,(3,3),activation='relu'),tf.keras.layers.MaxPooling2D(2,2),# 為全連接層準備 將數據拉長成一維向量tf.keras.layers.Flatten(),tf.keras.layers.Dense(512,activation='relu'),# 二分類sigmoid 就夠了tf.keras.layers.Dense(1,activation='sigmoid')
])
展示卷積后的網絡模型的數據
model.summary()
配置訓練器
'''
learning_rate=1e-4 學習率
'''
model.compile(loss='binary_crossentropy', # 指定損失函數optimizer=Adam(learning_rate=1e-4), # 指定優化器metrics=['acc'] # 評估
)
數據預處理
讀進來的數據會被自動轉換成tensor(float32)格式,分別準備訓練和驗證。
圖像數據歸一化到0-1區間。
#ImageDataGenerator數據生成器
# 把數據全部壓縮到0-1之間的數據
train_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)
# 讀取數據 generator(生成器)
'''使用了 flow_from_directory 方法從指定的訓練集文件夾中加載數據target_size=(64,64) 指定了圖像將被調整到的大小batch_size=20 表示每次迭代時將從磁盤讀取20個樣本class_mode='binary' 表明這是一個二分類任務'''# 訓練生成器
train_generator = train_datagen.flow_from_directory(train_dir, #文件夾路徑target_size = (64,64) # 指定resize成的大小batch_size=20, # 每次迭代拿出的數據量# 如果是one-hot就是categorical,二分類用binary就可以class_mode='binary'
)# 驗證生成器
validation_generator = test_datagen.flow_from_directory(validation_dir,target_size=(64,64),batch_size=20,class_mode='binary'
)
訓練網絡模型
直接使用fit
方法也可以,但是通常咱們不能把所有數據全部放入內存,fit_generator
相當于一個生成器,動態產生所需的batch數據。
steps_per_epoch
相當于給定一個停止條件,因為生成器會不斷產生batch數據,說白了就是它不知道一個epoch里需要執行多少個step。
生成器只要不讓其停止,就不會不斷的生成數據,fit_generator()
用什么就取什么
但是fit_generator()
在新的版本以及被廢棄了。
history = model.fit(tarin_generator, # 訓練生成器steps_per_epoch=100, # 迭代次數 # step_per_epoch = 數據量 / epochsepochs=20, # 輪次validation_data=validation_generator, # 驗證生成器validation_steps=50,verbose=2
)
以上的訓練結果并不好,雖然訓練集的準確率達到90%多,但是驗證集的準確率卻是74%左右,這也就是過擬合現象。
效果展示
import matplotlib.pyplot as plt
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']epochs = range(len(acc))plt.plot(epochs, acc, 'bo', label='Training accuracy')
plt.plot(epochs, val_acc, 'b', label='Validation accuracy')
plt.title('Training and validation accuracy')plt.figure()plt.plot(epochs, loss, 'bo', label='Training Loss')
plt.plot(epochs, val_loss, 'b', label='Validation Loss')
plt.title('Training and validation loss')
plt.legend()plt.show()
解決過擬合現象
通過 數據增強 來改變數據,解決過擬合現象。
修改上面的 數據歸一化 的步驟 代碼如下:
加入數據增強步驟
數據增強
# 對圖像進行各種變換
train_dategen = image.ImageDataGenerator(rescale=1./255,rotation_range=40,width_shift_range=0.2,height_shift_range=0.2,shear_range=0.2,zoom_range=0.2,horizontal_flip=True,fill_mode='nearest'
)test_datagen = ImageDataGenerator(rescale=1./255) # 測試集數據不做數據增強# ======================以下步驟是重復上面的后續步驟=============# 訓練生成器
train_generator = train_datagen.flow_from_directory(train_dir, #文件夾路徑target_size = (64,64) # 指定resize成的大小batch_size=20, # 每次迭代拿出的數據量# 如果是one-hot就是categorical,二分類用binary就可以class_mode='binary'
)# 驗證生成器
validation_generator = test_datagen.flow_from_directory(validation_dir,target_size=(64,64),batch_size=20,class_mode='binary'
)history = model.fit(tarin_generator, # 訓練生成器steps_per_epoch=100, # 迭代次數 # step_per_epoch = 數據量 / epochsepochs=20, # 輪次validation_data=validation_generator, # 驗證生成器validation_steps=50,verbose=2
)
再次展示數據
展示 加入 數據增強后的 圖像效果
import matplotlib.pyplot as plt
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']epochs = range(len(acc))plt.plot(epochs, acc, 'bo', label='Training accuracy')
plt.plot(epochs, val_acc, 'b', label='Validation accuracy')
plt.title('Training and validation accuracy')plt.figure()plt.plot(epochs, loss, 'bo', label='Training Loss')
plt.plot(epochs, val_loss, 'b', label='Validation Loss')
plt.title('Training and validation loss')
plt.legend()plt.show()
可以發現,過擬合現象有了一定的改進,雖然不是很多
數據增強
數據增強(Data Augmentation),對于輸入的圖像數據,進行翻轉,旋轉,變換,就會實現一張圖像擁有翻倍的數據量。
準備數據
導包
import matplotlib.pyplot as plt
from PIL import Image
%matplotlib inline
from keras.preprocessing import image
import keras.backend as K
import os
import glob
import numpy as np
展示輸入的數據
# 定義一個方法,用于繪圖
def print_result(path):name_list = glob.glob(path)fig = plt.figure(figsize=(12,16)) # 定義圖像大小for i in range(3):img = Image.open(name_list[i])sub_img = fig.add_subplot(131+i) # 添加子圖sub_img.imshow(img) # 顯示圖像
# 拿到圖片所在的路徑
img_path = './img/superman/*'
in_path = './img/'
out_path = './output/'
name_list = glob.glob(img_path)
print(name_list)
圖像數據的讀取和壓縮
'''
flow_from_directory() 從指定文件夾中加載數據
指定`target_size`后所有圖像都變為相同大小
'''
datagen = image.ImageDataGenerator() # 實例化ImageDataGenerator對象,對圖像進行后續操作# 告訴generator,要準備做的操作
gen_data = datagen.flow_from_directory(in_path, # 將./img/ 目錄下的 文件夾們 的數據分別讀取batch_size=1,shuffle=False,sava_to_dir=out_path+'resize', # 變換后 保存的路徑sava_prefix='gen',target_size=(224,224) # 指定變換后圖像的大小
)# 開始操作
for i in range(3):gen_data.next() # 一次next()執行一張圖像,這里有三張圖像就執行三次
gen_data
的輸出結果:
Found 3 images belonging to 1 classes.
一個類別是因為只有一個文件夾
展示縮小后的圖像
# 展示變換后的圖像
print_result(out_path+'resize/*') # print_result方法是上面自定義的繪圖方法
圖像數據變換
角度旋轉
首先在./out_path 目錄下,手動新建一個 rotation_range文件夾
dategen = image.ImageDataGenerator(rotation_range=45) # 指定旋轉角度 45°gen = image.ImageDataGenerator()data = gen.flow_from_directory( # 讀取數據in_path,batch_size=1,class_mode=None,shuffle=True, # 打亂圖像數據target_size=(224,224)
)np_data = np.concatenate([data.next() for i in range(data.n)])
datagen.fit(np_data)gen_data = datagen.flow_from_directory(in_path,batch_size=1,shuffle=False,save_to_dir=out_path+'rotation_range', # 自己在./out_path文件夾下手動創建一個rotation_range文件夾save_prefix='gen',target_size=(224,224)
)for i in range(3):gen_data.next() # 執行操作
執行上述代碼后,就會將旋轉之后的圖片保存到 ./out_path/rotation_range
目錄下。
展示旋轉后的圖像
'''
print_result(要展示的圖片所在路徑),上面自定義的繪圖方法
'''
# 展示 ./out_path/rotation_range 目錄下的所有圖像
print_result(out_path + 'rotation_range/*')
平移變換
首先在./out_path 目錄下,手動新建一個 shift2文件夾
datagen = image.ImageDataGenerator(width_shift_range=0.3, # 寬度比例值,可以是負數height_shift_range=0.3 # 長度比例值
)gen = image.ImageDataGenerator()data = gen.flow_from_directory( # 讀取數據in_path,batch_size=1,class_mode=None,shuffle=True, # 打亂數據target_size=(224,224) # 變換后的圖像大小
)np_data = np.concatenate([data.next() for i in range(data.n)])
datagen.fit(np_data)
gen_data = datagen.flow_from_driectory(in_path,batch_size=1,shullfe=False,save_to_dir=out_path+'shift2',save_prefix='gen',target_size=(224,224)
)# 對圖像進行操作
for i in range(3):gen_data.next()
展示平移后的圖像
'''
print_result(要展示的圖片所在路徑),上面自定義的繪圖方法
'''
# 展示 ./out_path/shift2 目錄下的所有圖像
print_result(out_path + 'shift2/*')
縮放
首先在./out_path 目錄下,手動新建一個 zoom文件夾
datagen = image.ImageDataGenerator(zoom_range=0.5) # 縮放比例 <1 縮小,>1 放大gen = image.ImageDataGenerator()data = gen.flow_from_directory( # 讀取數據in_path,batch_size=1,class_mode=None,shuffle=True, # 打亂數據target_size=(224,224) # 變換后的圖像大小
)np_data = np.concatenate([data.next() for i in range(data.n)])
datagen.fit(np_data)gen_data = datagen.flow_from_driectory(in_path,batch_size=1,shullfe=False,save_to_dir=out_path+'zoom',save_prefix='gen',target_size=(224,224)
)# 對圖像進行操作
for i in range(3):gen_data.next()
展示縮放后的圖像
'''
print_result(要展示的圖片所在路徑),上面自定義的繪圖方法
'''
# 展示 ./out_path/zoom 目錄下的所有圖像
print_result(out_path + 'zoom/*')
channel_shift顏色通道
首先在./out_path 目錄下,手動新建一個 channel文件夾
改變后的圖像,肉眼是看不出效果的
datagen = image.ImageDataGenerator(channel_shift_range=15) # 改變顏色通道
gen = image.ImageDataGenerator()data = gen.flow_from_directory(in_path,batch_size=1,class_Mode=None,shuffle=True,target_size=(224,224)
)np_data = np.concatenate([data.next() for i in range(data.n)])
datagen.fit(np.data)gen_data = gen,flow_from_directory(in_path,batch_size=1,shuffle=False,save_to_dir=out_path+'channel',save_prefix='gen',target_size=(224,224)
)# 執行操作
for i in range(3):gen_data.next()
展示 channel_shift 后的圖像
'''
print_result(要展示的圖片所在路徑),上面自定義的繪圖方法
'''
# 展示 ./out_path/channel 目錄下的所有圖像
print_result(out_path + 'channel/*')
翻轉
水平翻轉 和 垂直翻轉
首先在./out_path 目錄下,手動新建一個 horizontal文件夾
datagen = image.ImageDataGenerator(horizontal_flip=True) # 翻轉gen = image.ImageDataGenerator()
data = gen.flow_from_directory(in_path,batch_size=1,class_mode=None,shuffle=True,target_size(224,224)
)np_data = np.concatenate([data.next() for i in range(data.n)])
datagen.fit(np_data)gen_data = datagen.flow_from_driectory(in_path,batch_size=1,shuffle=False,save_to_dir=out_path + 'horizontal',save_prefit='gen',target_size=(224,224)
)# 開始執行操作
for in range(3):gen_data.next() # 每次next() 拿到的其實是矩陣數組
展示 翻轉后的圖像
'''
print_result(要展示的圖片所在路徑),上面自定義的繪圖方法
'''
# 展示 ./out_path/horizontal 目錄下的所有圖像
print_result(out_path + 'horizontal/*')
rescale歸一化變換
首先在./out_path 目錄下,手動新建一個 rescale文件夾
圖像數據的歸一化,將數據壓縮到0-1區間
變換后的數據,肉眼也是看不到效果的,但是可以查看 gen_data.next()
的數據會不一樣
datagen = image.ImageDataGenerator(rescale=1/255) # 歸一化gen = image.ImageDataGenerator()
data = gen.flow_from_directory(in_path,batch_size=1,class_mode=None,shuffle=True,target_size(224,224)
)np_data = np.concatenate([data.next() for i in range(data.n)])
datagen.fit(np_data)gen_data = datagen.flow_from_driectory(in_path,batch_size=1,shuffle=False,save_to_dir=out_path + 'rescale',save_prefit='gen',target_size=(224,224)
)# 開始執行操作
for in range(3):gen_data.next() # 每次next() 拿到的其實是矩陣數組
展示rescale后的圖像
'''
print_result(要展示的圖片所在路徑),上面自定義的繪圖方法
'''
# 展示 ./out_path/rescale 目錄下的所有圖像
print_result(out_path + 'rescale/*')
填充數據
首先在./out_path 目錄下,手動新建一個 fill_mode文件夾
在變換圖像后,會發現,有部分會空出來,這時候就需要填充數據了。
填充數據方法
- ‘constant’ : kkkkkkkk|abcd|kkkkkkkk (cval=k)
- 'nearest : aaaaaaaa|abcd|dddddddd
- ‘reflect’ : abcddcba|abcd|dcbaabcd
- ‘wrap’ : abcdabcd|abcd|abcdabcd
# 圖像缺失的的部分 拿 圖像本身 來填充
datagen = image.ImageDataGenerator(fill_mode='wrap',zoom_range[4,4])gen = image.ImageDataGenerator()
data = gen.flow_from_directory(in_path,batch_size=1,class_mode=None,shuffle=True,target_size(224,224)
)np_data = np.concatenate([data.next() for i in range(data.n)])
datagen.fit(np_data)gen_data = datagen.flow_from_driectory(in_path,batch_size=1,shuffle=False,save_to_dir=out_path + 'fill_mode',save_prefit='gen',target_size=(224,224)
)# 開始執行操作
for in range(3):gen_data.next() # 每次next() 拿到的其實是矩陣數組
展示填充數據后的圖像
'''
print_result(要展示的圖片所在路徑),上面自定義的繪圖方法
'''
# 展示 ./out_path/fill_mode 目錄下的所有圖像
print_result(out_path + 'fill_mode/*')
首先在./out_path 目錄下,手動新建一個 nearest文件夾
# 圖像缺失的的部分 拿 最近都 來填充
datagen = image.ImageDataGenerator(fill_mode='nearest',zoom_range[4,4])gen = image.ImageDataGenerator()
data = gen.flow_from_directory(in_path,batch_size=1,class_mode=None,shuffle=True,target_size(224,224)
)np_data = np.concatenate([data.next() for i in range(data.n)])
datagen.fit(np_data)gen_data = datagen.flow_from_driectory(in_path,batch_size=1,shuffle=False,save_to_dir=out_path + 'nearest',save_prefit='gen',target_size=(224,224)
)# 開始執行操作
for in range(3):gen_data.next() # 每次next() 拿到的其實是矩陣數組
展示填充數據后的圖像
'''
print_result(要展示的圖片所在路徑),上面自定義的繪圖方法
'''
# 展示 ./out_path/nearest 目錄下的所有圖像
print_result(out_path + 'nearest/*')
遷移學習
遷移學習概述
在訓練網絡的過程中,會遇到一種情況,手里的數據不是很多,數據少,意外著訓練出來的模型會過擬合。
數據量少,在訓練網絡的過程中,就要調節各種各樣的參數,做起實驗的花費大量時間,這也增加了成本。
為了讓訓練模型更簡單,所以就引入了遷移學習的概念。
所謂遷移學習,就是拿別人訓練好的網絡模型來使用。也就是拿別人的模型來做初始化。
要去找與本次任務 相類似的模型來用,既然用別人的模型,就要符合別人模型的訓練的參數,比如說圖像的大小,權重參數,輸入輸出格式要與人家模型一樣的。
遷移學習策略
聲明:以下提及 三模 均指:別人訓練好的模型。
-
A方案:在拿
三模
訓練自己模型的時候,通常會拿三模
中的卷積層拿過來,當作自己的權重參數初始化,在這基礎上,繼續訓練。拿
三模
中的部分卷積層,稱之為凍住,就是不修改該部分的卷積層 -
**B方案:**直接把
三模
當作自己權重參數的結果來使用,不進行訓練。
對于全連接層來說,需要自己訓練,對于前面的特征提取的操作進行參數微調操作。
當數據量小的時候,需要凍住的層數越多
當數據量中等的時候,就可以凍住前面部分的卷積層,最后的卷積層自己進行修改和訓練
當數據量大的時候,需要凍住的層數越小。
至于凍住多少層,沒有固定答案,基本上按照自己的數據量來,數據量較少,想要避免過擬合,最好直接拿三模
作為結果。
數據量大的時候,盡可能的而不凍住,而是僅僅拿來做初始化。
通俗的來說,有一場考試,分別由A和B兩個考生,其中A考生是自己一點一點做的,而B考生直接抄答案,并且稍微修改了點。
也就是說A考生就相當于自己從頭到尾來訓練模型,并沒有做遷移學習,而B考生是再別人的基礎上,進行稍微修改,就使用到了遷移學習。
由此可見,遷移學習的速度就很快
Resnet 殘差網絡原理
該網絡就是在訓練模型的時候,分兩個分支,一條分支按正常網絡模型走,另一條分支,就直接跳過正常的網絡模型架構,直接到全連接層。
遷移學習的案例
使用別人訓練好的模型 來 做貓狗識別的案例。
用人家訓練好的模型的權重參數來當作咱們的初始化;
一般全連接層需要自己訓練,可以選擇是否訓練別人訓練好的特征提取層。
數據準備
導包
import os
import warnings
warnings.filterwarnings("ignore")
import matplotlib.pylot as plt
import tensorflow as tf
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras import layers
from tensorflow.keras import Model
獲取數據路徑
base_dir = './data/cats_and_dogs'
train_dir = os.path.join(base_dir,'train')
validation_dir = os.path.join(base_dir,'validation')train_cats_dir = os.path.join(train_dir,'cats')
train_dogs_dir = os.path.join(train_dir,'dogs')validation_cats_dir = os.path.join(validation,'cats')
validation_dogs_dir = os.path.join(validation,'dogs')
使用內置訓練好的模型
tensorflow.keras.applications
模塊里面有很多現成訓練好的模型可以直接利用
比如說使用ResNet
# 導入模型
from tf.keras.applications.resnet import ResNet50
from tensorflow.keras.applications.resnet import ResNet101
from tensorflow.keras.applications.inception_v3 import InceptionV3s
上述導入模型的操作,僅僅是導入模型,并沒有拿到權重參數
# 下載模型的參數
pre_trained_model = ResNet101(input_shape=(75,75,3), # 輸入大小include_top=False, # 不要模型訓練好了的最后的全連接層weights='imagenet' # 權重參數,固定寫就好了
)
執行上面的代碼,在notebook當中下載,會很慢,然而,執行上面的代碼,下面會出現一段網址,將網址放在第三方下載工具下載,也許會快些,比如迅雷之類的。下載完之后,建議放在
C:\Users\24307\.keras\models
路徑下,文件名不要修改。
可以選擇訓練哪些層
# 選擇要訓練的哪些層
for layer in pre_trained_model.layers:layer.trainable = False
定義回調策略
# 自定義方法
class myCallback(tf.keras.callbacks.Callback):def on_epoch_end(self,epoch,logs={}):if(logs.get('acc')>0.95):print("\nReached 95% accuracy so cancelling training!")self.model.stop_training = True
開始訓練
from tensorflow.keras.optimizers import Adam# 為全連接層準備
# pre_trained_model.output 作為參數傳遞給Flatten層
# pre_trained_model.output是別人訓練好的模型的輸出層
x = layers.Flatten()(pre_trained_model.output)# 加入全連接層,這個需要從頭訓練的
x = layers.Dense(1024,activation='relu')(x)
x = layers.Dropout(0.2)(x)# 輸出層
x = layers.Dense(1,activation='sigmoid')(x)# 構建模型序列
# pre_trained_model.input是別人訓練好的模型的輸入層
# Model是上面導入的
model = Model(pre_trained_model.input,x)model.compile(optimizer=Adam(lr=0.001),loss='binary_crossentropt',metrics=['acc']
)
加入數據增強
# 對圖像進行各種變換
train_dategen = image.ImageDataGenerator(rescale=1./255,rotation_range=40,width_shift_range=0.2,height_shift_range=0.2,shear_range=0.2,zoom_range=0.2,horizontal_flip=True,
)test_datagen = ImageDataGenerator(rescale=1.0/255.) # 測試集數據不做數據增強# 訓練生成器
train_generator = train_datagen.flow_from_directory(train_dir, #文件夾路徑batch_size=20, # 每次迭代拿出的數據量# 如果是one-hot就是categorical,二分類用binary就可以class_mode='binary',target_size = (75,75) # 指定resize成的大小
)# 驗證生成器
validation_generator = test_datagen.flow_from_directory(validation_dir,batch_size=20,class_mode='binary'target_size=(75,75)
)
加入callback模塊訓練
# 調用上面自定義好的回調模塊myCallback
callbacks = myCallback()
history = model.fit(tarin_generator, # 訓練生成器validation_data=validation_generator, # 驗證生成器steps_per_epoch=100, # 迭代次數 # step_per_epoch = 數據量 / epochsepochs=100, # 輪次validation_steps=50,verbose=2,callbacks=[callbacks] # 加入callback模塊
)
繪圖展示
import matplotlib.pyplot as plt
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']epochs = range(len(acc))plt.plot(epochs, acc, 'b', label='Training accuracy')
plt.plot(epochs, val_acc, 'r', label='Validation accuracy')
plt.title('Training and validation accuracy')
plt.legend()plt.figure()plt.plot(epochs, loss, 'b', label='Training Loss')
plt.plot(epochs, val_loss, 'r', label='Validation Loss')
plt.title('Training and validation loss')
plt.legend()plt.show()
callback回調模塊
callback的作用
相當于一個監視器,在訓練過程中可以設置一些自定義項,比如提前停止,改變學習率等。
如果連續兩個epoch還沒降低就停止
callbacks = [tf.keras.callbacks.EarlyStopping(patience=2,monitor='val_loss'), # patience 容忍次數,monitor監聽指標tf.keras.callbacks.LearningRateScheduler,# 可以改變學習率tf.keras.callbacks.ModelCheckpoint,# 保存模型tf.keras.callbacks.Callback # 自定義方法
]
TFRecords
TFRecores概述
為了高效的讀取數據,可以將數據進行序列化存儲,這樣也便于網絡流式讀取數據。
TFRecords是一種比較常用的存儲二進制序列數據的方法。TFRecores可以加速網絡模型訓練。
tf.Example
類是一種將數據表示為{"string":value}
形式的message類型,Tensorflow經常使用tf.Example
來寫入、讀取TFRecords數據。
通常情況下,tf.Example中可以使用以下幾種格式:
- tf.train…BytesList:可以使用的類型包括string和byte
- tf.train.FloatList:可以使用的類型包括float和double
- tf.train.lnt64List:可以使用的類型包括enum,bool,int32,uint32,int64
轉化實例
def _bytes_feature(value):"""Returns a bytes_list from a string/byte."""if isinstance(value, type(tf.constant(0))):value = value.numpy() # BytesList won't unpack a string from an EagerTensor.return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))def _float_feature(value):"""Return a float_list form a float/double."""return tf.train.Feature(float_list=tf.train.FloatList(value=[value]))def _int64_feature(value):"""Return a int64_list from a bool/enum/int/uint."""return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))
# tf.train.BytesList
print(_bytes_feature(b'test_string'))
print(_bytes_feature('test_string'.encode('utf8')))# tf.train.FloatList
print(_float_feature(np.exp(1)))# tf.train.Int64List
print(_int64_feature(True))
print(_int64_feature(1))
TFRecord制作方法
創建tf.Example
def serialize_example(feature0, feature1, feature2, feature3):"""創建tf.Example"""# 轉換成相應類型feature = {'feature0': _int64_feature(feature0),'feature1': _int64_feature(feature1),'feature2': _bytes_feature(feature2),'feature3': _float_feature(feature3),}#使用tf.train.Example來創建example_proto = tf.train.Example(features=tf.train.Features(feature=feature))#SerializeToString方法轉換為二進制字符串return example_proto.SerializeToString()
舉個例子
# 數據量
n_observations = int(1e4)# 隨機創建數據# Boolean feature
feature0 = np.random.choice([False, True], n_observations)# Integer feature
feature1 = np.random.randint(0, 5, n_observations)# String feature
strings = np.array([b'cat', b'dog', b'chicken', b'horse', b'goat'])
feature2 = strings[feature1]# Float feature
feature3 = np.random.randn(n_observations)
保存到本地
filename = 'tfrecord-1' # 保存的文件名with tf.io.TFRecordWriter(filename) as writer:for i in range(n_observations):example = serialize_example(feature0[i], feature1[i], feature2[i], feature3[i])writer.write(example)
執行上述代碼,本地就會產生一個新的文件
加載TFRecored文件
filenames = [filename]# 讀取
raw_dataset = tf.data.TFRecordDataset(filenames)
print(raw_dataset)
圖像數據處理實例
導包
import os
import glob
from datetime import datetime
import cv2 # opencv的庫
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mping
%matplotlib inline
導入圖像數據
image_path = '../img/'
images = glob.glob(image_path + '*.jpg')for fname in images:image = mpimg.imread(fname)f, (ax1) = plt.subplots(1, 1, figsize=(8,8))f.subplots_adjust(hspace = .2, wspace = .05)ax1.imshow(image)ax1.set_title('Image', fontsize=20)
image_labels = {'dog': 0,'kangaroo': 1,
}
制作TFRecord
# 讀數據,binary格式
image_string = open('./img/dog.jpg', 'rb').read()
label = image_labels['dog']
def _bytes_feature(value):"""Returns a bytes_list from a string/byte."""if isinstance(value, type(tf.constant(0))):value = value.numpy() # BytesList won't unpack a string from an EagerTensor.return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))def _float_feature(value):"""Return a float_list form a float/double."""return tf.train.Feature(float_list=tf.train.FloatList(value=[value]))def _int64_feature(value):"""Return a int64_list from a bool/enum/int/uint."""return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))# 創建圖像數據的Example
def image_example(image_string, label):image_shape = tf.image.decode_jpeg(image_string).shapefeature = {'height': _int64_feature(image_shape[0]),'width': _int64_feature(image_shape[1]),'depth': _int64_feature(image_shape[2]),'label': _int64_feature(label),'image_raw': _bytes_feature(image_string),}return tf.train.Example(features=tf.train.Features(feature=feature))
#打印部分信息
image_example_proto = image_example(image_string, label)for line in str(image_example_proto).split('\n')[:15]:print(line)
print('...')
# 制作 `images.tfrecords`.image_path = './img/'
images = glob.glob(image_path + '*.jpg')
record_file = 'images.tfrecord'
counter = 0with tf.io.TFRecordWriter(record_file) as writer:for fname in images:with open(fname, 'rb') as f:image_string = f.read()label = image_labels[os.path.basename(fname).replace('.jpg', '')]# `tf.Example` tf_example = image_example(image_string, label)# 將`tf.example` 寫入 TFRecord writer.write(tf_example.SerializeToString())counter += 1print('Processed {:d} of {:d} images.'.format(counter, len(images)))print(' Wrote {} images to {}'.format(counter, record_file))
加載制作好的TFRecord
raw_train_dataset = tf.data.TFRecordDataset('images.tfrecord')
raw_train_dataset
example數據都進行了序列化,還需要解析以下之前寫入的序列化string
- tf.io.parse_single_example(example_proto, feature_description)函數可以解析單條example
# 解析的格式需要跟之前創建example時一致
image_feature_description = {'height': tf.io.FixedLenFeature([], tf.int64),'width': tf.io.FixedLenFeature([], tf.int64),'depth': tf.io.FixedLenFeature([], tf.int64),'label': tf.io.FixedLenFeature([], tf.int64),'image_raw': tf.io.FixedLenFeature([], tf.string),
}
現在看起來僅僅完成了一個樣本的解析,實際數據不可能一個個來寫吧,可以定義一個映射規則map函數
def parse_tf_example(example_proto):# 解析出來parsed_example = tf.io.parse_single_example(example_proto, image_feature_description)# 預處理x_train = tf.image.decode_jpeg(parsed_example['image_raw'], channels=3)x_train = tf.image.resize(x_train, (416, 416))x_train /= 255.lebel = parsed_example['label']y_train = lebelreturn x_train, y_traintrain_dataset = raw_train_dataset.map(parse_tf_example)
train_dataset
制作訓練集
num_epochs = 10train_ds = train_dataset.shuffle(buffer_size=10000).batch(2).repeat(num_epochs)
train_ds
for batch, (x, y) in enumerate(train_ds):print(batch, x.shape, y)
model = tf.keras.Sequential([tf.keras.layers.Flatten(),tf.keras.layers.Dense(2, activation='softmax')
])
model.compile(optimizer='adam',loss=tf.keras.losses.SparseCategoricalCrossentropy(),metrics=['accuracy'])
model.fit(train_ds, epochs=num_epochs)
遞歸神經網絡RNN
在傳統的神經網絡基礎上,將數據經過 隱層后 的得到的數據保留起來,與下一次輸入數據,共同經過隱層,通俗的講就是,數據會重復兩次經過隱層。
RNN網絡適用于自然語言文本處理。
RNN網絡一般取最后一層數據。
LSTM網絡
RNN網絡會把之前的結果全部記錄下,但是不是全部的記錄都有用,大部分的記錄到最后都會無效,當記錄多了,就會產生誤差,所以就引入了LSTM網絡,LSTM網絡做的事情,就是將部分數據進行遺忘。
通過一個控制參數?決定什么樣的信息會被保留,什么樣的信息會被遺忘。
詞向量模型Word2Vec
Word2Vec就是將詞轉化成向量化,并且包含順序、位置信息。
詞向量維度 在50至300,也就是50個數至300的個數。
通常,數據的維度越高,能提供的信息也就越多,從而計算結果的可靠性就更值得信賴。
詞向量的輸入輸出
將輸入的文本,在詞庫(語料庫)中查找對應的向量(Look up embeddings)。
語料庫大表一開始是隨機初始化來的,隨機構建初始化策略,隨機構建詞的向量,神經網絡在計算過程中,前向傳播計算損失函數,反向傳播通過損失函數來更新權重參數,此時在word2vec中,不光會更新整個神經網絡的權重矩陣,會連輸入的進行更新,相當于一開始詞庫大表是隨機初始化,隨著訓練的進行,每一次都會把輸入的詞進行更新,一點次數多了,每一個詞都會進行更新,神經網絡就學的越好。
訓練數據來源
數據可以跨文本,只要是正常邏輯,一切可以利用的文本,比如新聞,小說等都可以作為數據來源。
只要合乎說話邏輯,都可以作為數據來源。
比如說,“今天”這個詞,在不同的文章當中,它的意思都是一致的。
構建訓練數據
對于一段英文文本:
第一次訓練數據,可以選中前三個詞,第一個和第二個詞可以用作輸入數據,第三詞就用作輸出數據。
第二次訓練數據,就要選中第二個開始往后數三個詞,(對于整個文本來說)第二個詞和第三個詞就作為輸入數據,第四詞就作為輸出數據。
依次類推,第五個,第六個,第七個……
CBOW架構
cbow可以理解為構建出一段連續的文本。
比如,當前,拿到一段文本,要構建輸入數據,輸入是上下文,輸出(預測)是中間某個缺省的詞。
Skipgram結構
輸入是 文本中間缺省的詞,輸出(預測)是上下文。
如果語料庫稍微大一些,可能的結果簡直太多了,最后一層相當于softmax,計算起來十分耗時。
解決方案
本來要通過A來預測B,但是B的可能性太多了,所以干脆,將A和B都一起作為輸入參數,來判斷 B這個詞 在A后面的可能性有多大,取接近于1的詞,即target=1,
然后在此基礎上加入一些負樣本(Negative Sampling),也就是出現概率為0的詞(即target=0),負樣本是人為構建的。
詞向量訓練過程
初始化詞向量矩陣
通過神經網路反向傳播來計算更新,此時不光更新權重參數矩陣W,也會更新輸入數據。
基于Tensorflow實現word2vec
數據預處理
import collections
import os
import random
import urllib
import zipfileimport numpy as np
import tensorflow as tf
準備好訓練參數
# 訓練參數
learning_rate = 0.1 # 學習率
batch_size = 128
num_steps = 3000000 # 訓練總次數
display_step = 10000 # 損失值
eval_step = 200000 # 實驗效果# 測試樣例
eval_words = ['nine', 'of', 'going', 'hardware', 'american', 'britain']# Word2Vec 參數
embedding_size = 200 # 詞向量維度
max_vocabulary_size = 50000 # 語料庫詞語數
min_occurrence = 10 # 最小詞頻
skip_window = 3 # 左右各窗口大小
num_skips = 2 # 一次制作多少個輸入輸出對
num_sampled = 64 # 負采樣
加載訓練數據
# 加載訓練數據,其實什么數據都行
# 這是維基百科的數據
data_path = 'text8.zip'
with zipfile.ZipFile(data_path) as f:text_words = f.read(f.namelist()[0]).lower().split()
# 查看數據量大小
print(len(text_words))
# 創建一個計數器,計算每個詞出現了多少次
count = [('UNK', -1)] # 不在預料表中的詞,看作UNK
# 基于詞頻返回max_vocabulary_size個常用詞
# Counter內置庫計數器,默認做排序
count.extend(collections.Counter(text_words).most_common(max_vocabulary_size - 1)) # 將最常用的詞拿出來
# 查看統計好的數據
count[0:10]
設置min_occurrence參數
# 剔除掉出現次數少于'min_occurrence'的詞
for i in range(len(count) - 1, -1, -1):# 從start到end每次step多少if count[i][1] < min_occurrence:count.pop(i)else:# 判斷時,從小到大排序的,所以跳出時候剩下的都是滿足條件的break
詞-ID參數
# 計算語料庫大小
vocabulary_size = len(count)
# 每個詞都分配一個ID
word2id = dict()
for i, (word, _)in enumerate(count):word2id[word] = i
所有詞轉換成ID
data = list()
unk_count = 0
for word in text_words:# 全部轉換成idindex = word2id.get(word, 0)if index == 0:unk_count += 1data.append(index)
count[0] = ('UNK', unk_count)
id2word = dict(zip(word2id.values(), word2id.keys()))print("Words count:", len(text_words))
print("Unique words:", len(set(text_words)))
print("Vocabulary size:", vocabulary_size)
print("Most common words:", count[:10])
構建所需訓練數據
data_index = 0# 定義一個函數,用來不斷的生成輸入輸出的標簽
def next_batch(batch_size, num_skips, skip_window):global data_indexassert batch_size % num_skips == 0assert num_skips <= 2 * skip_windowbatch = np.ndarray(shape=(batch_size), dtype=np.int32) # 輸入數據labels = np.ndarray(shape=(batch_size, 1), dtype=np.int32) #標簽數據# get window size (words left and right + current one).span = 2 * skip_window + 1 #7為窗口,左3右3中間1 (拿到當前窗口)buffer = collections.deque(maxlen=span)#創建一個長度為7的隊列if data_index + span > len(data):#如果數據被滑完一遍了data_index = 0buffer.extend(data[data_index:data_index + span])#隊列里存的是當前窗口,例如deque([5234, 3081, 12, 6, 195, 2, 3134], maxlen=7)data_index += spanfor i in range(batch_size // num_skips):#num_skips表示取多少組不同的詞作為輸出,此例為2context_words = [w for w in range(span) if w != skip_window]#上下文就是[0, 1, 2, 4, 5, 6]words_to_use = random.sample(context_words, num_skips)#在上下文里隨機選2個候選詞for j, context_word in enumerate(words_to_use):#遍歷每一個候選詞,用其當做輸出也就是標簽batch[i * num_skips + j] = buffer[skip_window]#輸入都為當前窗口的中間詞,即3labels[i * num_skips + j, 0] = buffer[context_word]#用當前候選詞當做標簽if data_index == len(data):buffer.extend(data[0:span])data_index = spanelse:buffer.append(data[data_index])#之前已經傳入7個詞了,窗口要右移了,例如原來為[5234, 3081, 12, 6, 195, 2, 3134],現在為[3081, 12, 6, 195, 2, 3134, 46]data_index += 1data_index = (data_index + len(data) - span) % len(data)return batch, labels
with tf.device('/cpu:0'): # 隨機初始化語料庫,語料庫大小47135,維度是200embedding = tf.Variable(tf.random.normal([vocabulary_size, embedding_size])) #維度:47135, 200nce_weights = tf.Variable(tf.random.normal([vocabulary_size, embedding_size])) # 負采樣nce_biases = tf.Variable(tf.zeros([vocabulary_size]))
將索引轉成詞向量
# 通過`tf.nn.embedding_lookup` 函數將所以轉成詞向量
def get_embedding(x):with tf.device('/cpu:0'):# x是輸入的值,在embeddings語料庫中查找x的詞向量x_embed = tf.nn.embedding_lookup(embedding, x)return x_embed
損失函數定義
先分別計算出正樣本和采樣出的負樣本對應的output 和 label
在通過 sigmoid
cross
enrtopy
來計算output和label的損失值
def nce_loss(x_embed, y):with tf.device('/cpu:0'):y = tf.cast(y, tf.int64)loss = tf.reduce_mean(tf.nn.nce_loss(weights=nce_weights,biases=nce_biases,labels=y,inputs=x_embed,num_sampled=num_sampled,#采樣出多少個負樣本num_classes=vocabulary_size))return loss
評估模塊
測試觀察模塊
想看下,訓練好之后的模型,某個詞的向量 距離哪個 詞向量 比較近。
# Evaluation.
def evaluate(x_embed):with tf.device('/cpu:0'):# Compute the cosine similarity between input data embedding and every embedding vectorsx_embed = tf.cast(x_embed, tf.float32)x_embed_norm = x_embed / tf.sqrt(tf.reduce_sum(tf.square(x_embed)))#歸一化embedding_norm = embedding / tf.sqrt(tf.reduce_sum(tf.square(embedding), 1, keepdims=True), tf.float32)#全部向量的cosine_sim_op = tf.matmul(x_embed_norm, embedding_norm, transpose_b=True)#計算余弦相似度return cosine_sim_op# SGD
optimizer = tf.optimizers.SGD(learning_rate)
# 迭代優化
def run_optimization(x, y):with tf.device('/cpu:0'):with tf.GradientTape() as g:emb = get_embedding(x)loss = nce_loss(emb, y)# 計算梯度gradients = g.gradient(loss, [embedding, nce_weights, nce_biases])# 更新optimizer.apply_gradients(zip(gradients, [embedding, nce_weights, nce_biases]))
# 待測試的幾個詞
x_test = np.array([word2id[w.encode('utf-8')] for w in eval_words])# 訓練
for step in range(1, num_steps + 1):batch_x, batch_y = next_batch(batch_size, num_skips, skip_window)run_optimization(batch_x, batch_y)if step % display_step == 0 or step == 1:loss = nce_loss(get_embedding(batch_x), batch_y)print("step: %i, loss: %f" % (step, loss))# Evaluation.if step % eval_step == 0 or step == 1:print("Evaluation...")sim = evaluate(get_embedding(x_test)).numpy()for i in range(len(eval_words)):top_k = 8 # 返回前8個最相似的nearest = (-sim[i, :]).argsort()[1:top_k + 1]log_str = '"%s" nearest neighbors:' % eval_words[i]for k in range(top_k):log_str = '%s %s,' % (log_str, id2word[nearest[k]])print(log_str)
RNN文本分析任務
任務介紹
- 數據集構建:影評數據集進行情感分析
- 詞向量模型:加載訓練好的詞向量或者自己訓練都可以
- 序列網絡模型:訓練RNN模型進行識別
準備數據
在data
文件夾下有三個文件:
- test.txt : 測試集
- train.txt : 訓練集
- glove.6B.50d.txt : 語料庫
準備的數據是英文的,所以不用分詞,但是如果是中文的話,那就需要進行分詞。
數據預處理
訓練模型前,要先將文本信息轉換成計算機能夠識別的數值。
RNN模型所需的數據格式
- 第一維度:batch(每次要處理多少數據)
- 第二維度:最大長度(maxLength-1),超出部分截斷,少于部分用0填充
- 第三維度:每個詞 的 詞向量 的維度
導包
import os
import warnings
warnings.filterwarnings("ignore")
import tensorflow as tf
import numpy as np
import pprint
import logging
import time
from collections import Counter
from pathlib import Path
from tqdm import tqdm
數據映射表制作
讀取數據集
上面提前準備了關于 影評的數據(測試集和訓練集),這里就可以直接拿來用。
當然也可以讓tensorflow下載,執行以下代碼:
(x_train,y_train),(x_test,y_test) = tf.keras.datasets.imdb.load_data()
默認會下載到 C:\Users\24307\.keras\datasets
路徑下,下載的文件名是:imdb.npz
。
# 查看下訓練數據量
print(x_train.shape) # (25000,)
可以讀取訓練集中的部分數據
x_train[0]
此時讀進來的數據已經被tensorflow自動轉成ID映射的了。但是一般情況下,讀進來的數據都需要手動轉成ID映射。
自己制作ID映射
構建一個語料表,給每個詞對應一個索引,即可。
_word2idx = tf.keras.datasets.imdb.get_word_index() # 拿到原始的索引表word2idx = {w:i+3 for w,i in _word2idx.items()} # i+3 在每個詞向量后面留三個空位
word2idx['<pad>'] = 0 # 填充 當小于最大長度時,缺省部分填充0
word2idx['<start>'] = 1 # 開始詞 的索引
word2idx['<unk>'] = 2 # 樣本當中,特殊詞沒有對上索引,就用unk來表示
idx2word = {i: w for w,i in word2idx.items()}
按文本長度大小進行排序
# 自定義方法
def sort_by_len(x,y):x,y = np.asarray(x),np.asarray(y)idx = sorted(range(len(x)),key=lambda i : len(x[i]))
將中間結果保存到本地,萬一程序崩了,還得重來,這里保存的是文本數據,不是ID
也就是后期再讀取訓練模型,就直接從本地讀取好了,不用再下載一遍了
x_train, y_train = sort_by_len(x_train, y_train)
x_test, y_test = sort_by_len(x_test, y_test)def write_file(f_path, xs, ys):with open(f_path, 'w',encoding='utf-8') as f:for x, y in zip(xs, ys):f.write(str(y)+'\t'+' '.join([idx2word[i] for i in x][1:])+'\n')write_file('./data/train.txt', x_train, y_train)
write_file('./data/test.txt', x_test, y_test)
構建語料表
基于詞頻來進行統計。
比如說,拿到好多篇文章,將這些文章中的所有不重復的詞提取處理,并且統計出現次數。
counter = Counter()
with open('./data/train.txt',encoding='utf-8') as f:for line in f:line = line.rstrip()label, words = line.split('\t')words = words.split(' ')counter.update(words)# freq >= 10 小于10說明詞頻太低,就不要了,保留詞頻高的,也就是常用的詞
words = ['<pad>'] + [w for w, freq in counter.most_common() if freq >= 10]
print('Vocab Size:', len(words))Path('./vocab').mkdir(exist_ok=True)# 將語料表保存到本地 word.txt文件中
with open('./vocab/word.txt', 'w',encoding='utf-8') as f:for w in words:f.write(w+'\n')
得到新的word2id 映射表
word2idx = {}
with open('./vocab/word.txt',encoding='utf-8') as f:for i, line in enumerate(f):line = line.rstrip()word2idx[line] = i # 將索引 與 詞 對應
構建embedding層
可以基于網絡自己來訓練, 也可以直接加載別人訓練好的,一般都是加載預訓練模型。
推薦使用別人訓練好的,自己的訓練的模型可能不太好。
常用的語料庫數據集
加載已經下載好的語料庫模型
#做了一個大表,里面有20598個不同的詞,【20599*50】
embedding = np.zeros((len(word2idx)+1, 50)) # + 1 表示如果不在語料表中,就都是unknowwith open('./data/glove.6B.50d.txt',encoding='utf-8') as f: #下載好的count = 0for i, line in enumerate(f):if i % 100000 == 0:print('- At line {}'.format(i)) #打印處理了多少數據line = line.rstrip()sp = line.split(' ')word, vec = sp[0], sp[1:]if word in word2idx:count += 1embedding[word2idx[word]] = np.asarray(vec, dtype='float32') #將詞轉換成對應的向量
現在已經已經得到了每個詞索引所對應的向量,這里就保存在本地了。
print("[%d / %d] words have found pre-trained values"%(count, len(word2idx)))
np.save('./vocab/word.npy', embedding)
print('Saved ./vocab/word.npy')
數據生成器構造
數據生成器
- tf.data.Dataset.from_tensor_slices(tensor):將tensor沿其第一個維度切片,返回一個含有N個樣本的數據集,這樣做的問題就是需要將整個數據集整體傳入,然后切片建立數據集類對象,比較占內存。(不推薦)
- tf.data.Dataset.from_generator(data_generator,output_data_type,output_data_shape):從一個生成器中不斷讀取樣本
# 定義生成器函數
def data_generator(f_path, params):with open(f_path,encoding='utf-8') as f:print('Reading', f_path)for line in f:line = line.rstrip()label, text = line.split('\t')text = text.split(' ')x = [params['word2idx'].get(w, len(word2idx)) for w in text]#得到當前詞所對應的IDif len(x) >= params['max_len']:#截斷操作x = x[:params['max_len']]else:x += [0] * (params['max_len'] - len(x))#補齊操作y = int(label)yield x, y
def dataset(is_training, params):'''is_training:是否訓練'''_shapes = ([params['max_len']], ())_types = (tf.int32, tf.int32)if is_training:ds = tf.data.Dataset.from_generator(lambda: data_generator(params['train_path'], params),output_shapes = _shapes,output_types = _types,)ds = ds.shuffle(params['num_samples'])ds = ds.batch(params['batch_size'])ds = ds.prefetch(tf.data.experimental.AUTOTUNE)#設置緩存序列,根據可用的CPU動態設置并行調用的數量,說白了就是加速else:ds = tf.data.Dataset.from_generator(lambda: data_generator(params['test_path'], params),output_shapes = _shapes,output_types = _types,)ds = ds.batch(params['batch_size'])ds = ds.prefetch(tf.data.experimental.AUTOTUNE)return ds
自定義網絡模型
在tensorflow中,我們可以自定義模型,自定義網絡層,自定義模型等。
tf.nn.embedding_lookup
:將當前的輸入數據轉成對應的向量維度。
就是:將輸入的數據,轉換成id,然后再語料庫中去尋找對應的向量。
BiLSTM 雙向RNN模型
就是在單向的基礎上,封裝了一層api,僅此而已。
相當于從前到后和從后到前都有了,然后將得到的向量拼接起來。
首先自定義網絡模型Model
# 做定義模型,要繼承tf.keras.Model
class Model(tf.keras.Model):# 在自定義模型當中,會用到哪些層def __init__(self, params):super().__init__()self.embedding = tf.Variable(np.load('./vocab/word.npy'),dtype=tf.float32,name='pretrained_embedding',trainable=False,)self.drop1 = tf.keras.layers.Dropout(params['dropout_rate'])self.drop2 = tf.keras.layers.Dropout(params['dropout_rate'])self.drop3 = tf.keras.layers.Dropout(params['dropout_rate'])# rnn層可以自己看著加,加太多了,速度會變慢# Bidirectional 雙向self.rnn1 = tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(params['rnn_units'], return_sequences=True))self.rnn2 = tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(params['rnn_units'], return_sequences=True))self.rnn3 = tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(params['rnn_units'], return_sequences=False))self.drop_fc = tf.keras.layers.Dropout(params['dropout_rate'])self.fc = tf.keras.layers.Dense(2*params['rnn_units'], tf.nn.elu)self.out_linear = tf.keras.layers.Dense(2)# 定義 各層是如何組合在一起def call(self, inputs, training=False):if inputs.dtype != tf.int32:inputs = tf.cast(inputs, tf.int32)batch_sz = tf.shape(inputs)[0]rnn_units = 2*params['rnn_units']# self.embedding:詞向量表# inputs: 輸入值x = tf.nn.embedding_lookup(self.embedding, inputs)x = self.drop1(x, training=training)x = self.rnn1(x)x = self.drop2(x, training=training)x = self.rnn2(x)x = self.drop3(x, training=training)x = self.rnn3(x)x = self.drop_fc(x, training=training)x = self.fc(x)x = self.out_linear(x)return x
另外一個自定義模型,速度比較快,比較適合沒有用GPU的電腦
class Model(tf.keras.Model):def __init__(self, params):super().__init__()self.embedding = tf.Variable(np.load('./vocab/word.npy'),dtype=tf.float32,name='pretrained_embedding',trainable=False,)self.drop1 = tf.keras.layers.Dropout(params['dropout_rate'])self.drop2 = tf.keras.layers.Dropout(params['dropout_rate'])self.drop3 = tf.keras.layers.Dropout(params['dropout_rate'])self.rnn1 = tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(params['rnn_units'], return_sequences=True))self.rnn2 = tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(params['rnn_units'], return_sequences=True))self.rnn3 = tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(params['rnn_units'], return_sequences=True))self.drop_fc = tf.keras.layers.Dropout(params['dropout_rate'])self.fc = tf.keras.layers.Dense(2*params['rnn_units'], tf.nn.elu)self.out_linear = tf.keras.layers.Dense(2)def call(self, inputs, training=False):if inputs.dtype != tf.int32:inputs = tf.cast(inputs, tf.int32)batch_sz = tf.shape(inputs)[0]rnn_units = 2*params['rnn_units']x = tf.nn.embedding_lookup(self.embedding, inputs)x = tf.reshape(x, (batch_sz*10*10, 10, 50))x = self.drop1(x, training=training)x = self.rnn1(x)x = tf.reduce_max(x, 1)x = tf.reshape(x, (batch_sz*10, 10, rnn_units))x = self.drop2(x, training=training)x = self.rnn2(x)x = tf.reduce_max(x, 1)x = tf.reshape(x, (batch_sz, 10, rnn_units))x = self.drop3(x, training=training)x = self.rnn3(x)x = tf.reduce_max(x, 1)x = self.drop_fc(x, training=training)x = self.fc(x)x = self.out_linear(x)return x
設置參數
params = {'vocab_path': './vocab/word.txt','train_path': './data/train.txt','test_path': './data/test.txt','num_samples': 25000,'num_labels': 2,'batch_size': 32,'max_len': 1000, # 文本最大長度'rnn_units': 200, 'dropout_rate': 0.2,'clip_norm': 10., # 梯度截斷'num_patience': 3,'lr': 3e-4, # 學習率
}
定義方法,用來判斷進行提前停止
如果當前的模型 連續三次都沒有進步,那就停下來
def is_descending(history: list):history = history[-(params['num_patience']+1):]for i in range(1, len(history)):if history[i-1] <= history[i]:return Falsereturn True
word2idx = {}
with open(params['vocab_path'],encoding='utf-8') as f:for i, line in enumerate(f):line = line.rstrip()word2idx[line] = i
params['word2idx'] = word2idx
params['vocab_size'] = len(word2idx) + 1model = Model(params)
model.build(input_shape=(None, None))#設置輸入的大小,或者fit時候也能自動找到
#pprint.pprint([(v.name, v.shape) for v in model.trainable_variables])#鏈接:https://tensorflow.google.cn/api_docs/python/tf/keras/optimizers/schedules/ExponentialDecay?version=stable
#return initial_learning_rate * decay_rate ^ (step / decay_steps)# ExponentialDecay(學習率,每個多少次迭代,衰減值)
decay_lr = tf.optimizers.schedules.ExponentialDecay(params['lr'], 1000, 0.95)#相當于加了一個指數衰減函數
optim = tf.optimizers.Adam(params['lr'])
global_step = 0history_acc = []
best_acc = .0 # 最好的準確率t0 = time.time()
logger = logging.getLogger('tensorflow')
logger.setLevel(logging.INFO)
開始訓練文本分類模型
while True:# 訓練模型for texts, labels in dataset(is_training=True, params=params):with tf.GradientTape() as tape:#梯度帶,記錄所有在上下文中的操作,并且通過調用.gradient()獲得任何上下文中計算得出的張量的梯度logits = model(texts, training=True) # 預測結果loss = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=labels, logits=logits)# 計算損失loss = tf.reduce_mean(loss) # 計算損失的平均值optim.lr.assign(decay_lr(global_step)) # 更新學習率grads = tape.gradient(loss, model.trainable_variables) # 計算梯度值grads, _ = tf.clip_by_global_norm(grads, params['clip_norm']) #將梯度限制一下,有的時候回更新太猛,防止過擬合optim.apply_gradients(zip(grads, model.trainable_variables))#更新梯度if global_step % 50 == 0:logger.info("Step {} | Loss: {:.4f} | Spent: {:.1f} secs | LR: {:.6f}".format(global_step, loss.numpy().item(), time.time()-t0, optim.lr.numpy().item()))t0 = time.time()global_step += 1# 驗證集效果m = tf.keras.metrics.Accuracy() # 衡量標準for texts, labels in dataset(is_training=False, params=params):logits = model(texts, training=False) # 預測結果y_pred = tf.argmax(logits, axis=-1) # 預測出的類別m.update_state(y_true=labels, y_pred=y_pred) # 更新當前的準確率acc = m.result().numpy()logger.info("Evaluation: Testing Accuracy: {:.3f}".format(acc))history_acc.append(acc)if acc > best_acc:best_acc = acclogger.info("Best Accuracy: {:.3f}".format(best_acc))if len(history_acc) > params['num_patience'] and is_descending(history_acc):logger.info("Testing Accuracy not improved over {} epochs, Early Stop".format(params['num_patience']))break
CNN實現文本分類任務
這里使用卷積神經網絡來實現文本份分類任務。
這里得將文本轉換成多維的向量。
CNN文本分類
- 文本數據預處理,必須都是相同長度,相同向量維度
- 構建卷積模型,注意卷積核大小的設計
- 將卷積后的特征圖池化成一個特征
- 將多種特征拼接在一起,準備完成分類任務
準備數據集 和 導包
import warnings
warnings.filterwarnings("ignore")
import numpy as np
import matplotlib.pyplot as plt
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.preprocessing.sequence import pad_sequences # 填充操作num_features = 3000
sequence_length = 300 # 文章的長度
embedding_dimension = 100 # 把每個詞轉化成映射向量
(x_train, y_train), (x_test, y_test) = keras.datasets.imdb.load_data(num_words=num_features)
print(x_train.shape)
print(x_test.shape)
print(y_train.shape)
print(y_test.shape)
# 填充操作
x_train = pad_sequences(x_train, maxlen=sequence_length)
x_test = pad_sequences(x_test, maxlen=sequence_length)
print(x_train.shape)
print(x_test.shape)
print(y_train.shape)
print(y_test.shape)
# 多種卷積核,相當于單詞數
filter_sizes=[3,4,5] # 構造不同的卷積核大小
def convolution():inn = layers.Input(shape=(sequence_length, embedding_dimension, 1))#3維的cnns = []for size in filter_sizes:# filters:得到特征圖的個數conv = layers.Conv2D(filters=64, kernel_size=(size, embedding_dimension),strides=1, padding='valid', activation='relu')(inn)#需要將多種卷積后的特征圖池化成一個特征# sequence_length-size+1 : 特征圖長度pool = layers.MaxPool2D(pool_size=(sequence_length-size+1, 1), padding='valid')(conv)cnns.append(pool)# 將得到的特征拼接在一起outt = layers.concatenate(cnns)model = keras.Model(inputs=inn, outputs=outt)return modeldef cnn_mulfilter():model = keras.Sequential([layers.Embedding(input_dim=num_features, output_dim=embedding_dimension,input_length=sequence_length),layers.Reshape((sequence_length, embedding_dimension, 1)),convolution(),layers.Flatten(),layers.Dense(10, activation='relu'),layers.Dropout(0.2),layers.Dense(1, activation='sigmoid')])model.compile(optimizer=keras.optimizers.Adam(),loss=keras.losses.BinaryCrossentropy(),metrics=['accuracy'])return modelmodel = cnn_mulfilter()
model.summary()
訓練模型
history = model.fit(x_train, y_train, batch_size=64, epochs=5, validation_split=0.1)
展示數據
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.legend(['training', 'valiation'], loc='upper left')
plt.show()