python 手寫數字識別 (mnist庫)
import numpy as np import pandas as pd import tensorflow as tf from tensorflow.keras import layers import matplotlib.pyplot as plt '''1.打開數據集文件,並且讀取mnist數據''' data = np.load('mnist.npy') print(data.files) x_train = data['x_train'] y_train = data['y_train'] x_test = data['x_test'] y_test = data['y_test'] print(x_train.shape)
''' 對數據進行預處理,將圖像數據轉成四維數據,第一維度為batch_size,第二三維度為圖片大小, 第四個維度表示通道數目,mnist數據集為單色灰度圖像,所以值為1,如果是彩色則為3。 ''' '''2.數據預處理''' x_train4D = x_train.reshape(x_train.shape[0],28,28,1).astype('float32') #shape[0]表示有多少張圖片,即batch_size x_test4D = x_test.reshape(x_test.shape[0],28,28,1).astype('float32') #然后對x數據標准化處理,使取值范圍在0,1之間; 對y數據進行One-Hot編碼,將0~9映射成一組相同長度的01編碼 x_train4D = x_train4D/255 x_test4D = x_test4D/255 y_train01 = tf.keras.utils.to_categorical(y_train) y_test01 = tf.keras.utils.to_categorical(y_test)
'''3.搭建模型''' model = tf.keras.models.Sequential() #選擇keras模型庫中的線性堆疊模型Sequential() model.add(layers.Conv2D(filters=16, kernel_size=(5,5), padding='same', input_shape=(28,28,1),activation='relu')) model.add(layers.MaxPooling2D(pool_size=(2,2))) #每一層卷積層后面一定要池化,不然起不到降維度的作用,無參數,這一步只是為了濃縮數據 model.add(layers.Conv2D(filters=32, kernel_size=(5,5),padding='same', activation='relu')) #后面層其實可以根據前面層的輸出得到input_shape,所以可以不寫,但是每一個卷積層都必須寫激活函數和padding, # 因為后面層需要知道輸出數據的規模shape是多少 model.add(layers.MaxPooling2D(pool_size=(2,2))) # 此時28x28的圖片現在只有7x7 model.add(layers.Dropout(0.3)) #使百分之30的神經元失活,避免過擬合 model.add(layers.Flatten()) #轉為一維向量輸入 model.add(layers.Dense(128,activation='relu')) #后面再加隱藏層,128代表這一層有128個神經元,input_shape依然不用填,Dense()全連接層,只適用於一維數據 #所以前面需要加一個Flatten()層來講7x7的二維數據轉為一維數據輸入 model.add(layers.Dropout(0.5)) #卷積層和隱藏層都各需要一個dropout函數來防止過擬合。 model.add(layers.Dense(10, activation='softmax')) #輸出層,多分類問題用'softmax'激活函數,二分類問題用'sigmoid'激活函數 print(model.summary())
'''4.訓練''' model.compile(loss='categorical_crossentropy',optimizer='adam',metrics=['accuracy']) #交叉熵在多分類問題中常用,可以把細小的概率變化放大,而mse均方差適合擬合回歸,不適合分類問題,評估方式這里用的是准確度 train_history = model.fit(x=x_train4D,y=y_train01,validation_split=0.2,epochs=5,batch_size=300,verbose=2) #將百分之20的數據划分為測試數據,每批處理300張圖片,verbose = 2顯示訓練過程 #訓練時候需要輸入為4維的數據,評價或者預測時候,也需要輸入時四維的數據
'''5.顯示訓練結果''' print(train_history.history) #可知里面包含四個組數據,分別是loss, accuracy, val_loss, val_accuracy #自定義一個顯示隨着迭代次數增加,正確率變化的圖表 def show_train_history(train_history, train, validation): plt.plot(train_history.history[train]) # 繪制訓練數據的執行結果 plt.plot(train_history.history[validation]) # 繪制驗證數據的執行結果 plt.title('Train History') # 圖標題 plt.xlabel('epoch') # x軸標簽 plt.ylabel('accuracy') # y軸標簽 plt.legend(['train', 'validation'], loc='upper left') # 添加左上角圖例 show_train_history(train_history, 'accuracy', 'val_accuracy')
'''6.測試'''
#倒數第二步,在測試集上面進行測試
scores = model.evaluate(x_test4D, y_test01)
print('accuracy=', scores[1]) #scores[0] 表示loss值, scores[1]表示正確率
#用模型在測試集上進行預測
prediction = model.predict_classes(x_test4D)
#predict_classes會將預測的One-hot編碼轉化為一個數值0~9,得到的是一組預測值
#可視化結果
def plot_image_labels_prediction(images, labels, prediction, idx, nums=25):
fig = plt.figure(figsize=(12,14)) # 設置圖表大小
if nums > 25: nums = 25 # 最多顯示25張圖像
for i in range(0, nums):
ax = fig.add_subplot(5, 5, 1 + i) # 子圖生成
ax.imshow(images[idx], cmap='binary') # idx是為了方便索引所要查詢的圖像
title = 'label=' + str(labels[idx]) # 定義title方便圖像結果對應
if (len(prediction) > 0): # 如果有預測圖像,則顯示預測結果
title += ' prediction=' + str(prediction[idx])
ax.set_title(title, fontsize=10) # 設置圖像title
ax.set_xticks([]) # 無x刻度
ax.set_yticks([]) # 無y刻度
idx += 1
plt.show()
plot_image_labels_prediction(x_test,y_test,prediction,idx = 400)
下面一行表示的是測試集上的結果,可以看出每次訓練相同數據可能得到不同的參數,可能是因為dropout層的原因,
但是准確率都是98%以上,波動不會很大。我們訓練相同數據時候准確率達到95%就已經很高了,過高可能會存在過擬合問題。
后面是對結果的評價:(最后保存模型,方便下次再使用該模型)
#查找預測錯誤點 pretb = pd.crosstab(y_test,prediction,rownames=['label'],colnames=['predict']) df = pd.DataFrame({'label':y_test,'predict':prediction}) error = df[df['label']!=df['predict']] print(error)
#最后一步,根據結果調整參數,或者調整網絡結構,重復以上操作直到結果滿意(前提是模型正確)
可以看到這個模型在測試集上有122個數據預測錯誤,但是准確度很高,達到98.9%,准確度能否真實的反映模型好壞嗎?
我們需要看測試集上數據的分布,因此我們有了下一步:
freq = pd.Series(y_test).value_counts()/len(y_test)
print(freq) #得到0~9在測試集上出現的頻率,看是否數據不均衡
可以看出數據是十分均衡的。
另外我們繼續統計測試集上每個數字出錯的頻率,看哪一個數字預測錯的概率最大。
from scipy.stats import pearsonr,norm
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns data = pd.read_csv('out.csv') print(data) sns.set_context('paper') ax = sns.jointplot('label','predict',data = data,marginal_kws=dict(bins = 10, kde = True, fit = norm, fit_kws = {'color': 'r'}, rug = True),stat_func = pearsonr, space = 0, color = 'g',kind = 'scatter')
#kind = 'scatter', 'reg', 'resid', 'kde', 'hex' ax.plot_joint(sns.kdeplot, zorder = 0, n_levels = 6)#在前面一個圖的基礎上在加上核密度估計的聯合密度分布圖,通過plot_joint()實現。 plt.xticks(np.linspace(-2,12,15)) plt.yticks(np.linspace(-2,12,15)) plt.suptitle('label&predict\'s error') plt.show()
可以看出來在標簽為7,8,9的數字更容易識別錯,
錯看成0,1的概率最小,看成其他數字的概率基本一樣(見綠色kde曲線)。
我們可以后續在識別7,8,9這三個數字上對模型做進一步優化。
如何導出模型數據呢?
'''7.修改模型或者保存模型便於下次用新數據訓練'''
model.save('model.h5') #保存后會在生成一個model.h5文件
from tensorflow.keras.models import load_model model2 = load_model('model.h5') #對測試數據預處理后,再用這個模型進行評價 loss,accuracy = model2.evaluate(x_test4D, y_test01)
# 保存參數,載入參數 model2.save_weights('my_model_weights.h5') model2.load_weights('my_model_weights.h5') # 保存網絡結構,載入網絡結構 from keras.models import model_from_json json_string = model2.to_json() model2 = model_from_json(json_string) print(json_string) #json_string的值就是該網絡的結構,載入的時候只需要調用model_from_json(s) s為字符串可以通過讀json文件來得到