CNN圖像分類 入門
本次入門學習的項目是CNN圖像分類的花卉識別
通過使用五種各五百張不同種類的花卉圖片進行模型訓練
訓練結果如下:
預測成功率大概在64%左右(與訓練集過少還是有一些關系的)

預測結果如下:

代碼部分
訓練代碼解釋部分:
- 模型導入:
# -*- coding:uft-8
import glob
import os
import cv2
import tensorflow as tf
from tensorflow.keras import layers, optimizers, datasets, Sequential
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
#模型導入模塊
from keras.models import load_model
# 數據集的地址 改為你自己的
path = './flower_photos/'
# 縮放圖片大小為100*100
w = 100
h = 100
# c = 3
- 定義圖片讀取函數
# 定義函數read_img,用於讀取圖像數據,並且對圖像進行resize格式統一處理
def read_img(path):
# 創建層級列表cate,用於對數據存放目錄下面的數據文件夾進行遍歷,os.path.isdir用於判斷文件是否是目錄,然后對是目錄文件的文件進行遍歷
cate=[path+x for x in os.listdir(path) if os.path.isdir(path+x)]
# 創建保存圖像的空列表
imgs=[]
# 創建用於保存圖像標簽的空列表
labels=[]
# enumerate函數用於將一個可遍歷的數據對象組合為一個索引序列,同時列出數據和下標,一般用在for循環當中
# enumerate(cate) -> (0, './flower_photos/daisy') 索引+路徑
for idx,folder in enumerate(cate):
# 利用glob.glob函數搜索每個層級文件下面符合特定格式“/*.jpg”進行遍歷 匹配
for im in glob.glob(folder+'/*.jpg'):
#opencv讀取圖片,僅僅是讀一下
# 利用imread函數讀取每一張被遍歷的圖像
img=cv2.imread(im)
# 利用resize函數對每張img圖像進行大小縮放,統一處理為大小為w*h(即100*100)的圖像
img=cv2.resize(img,(w,h))
# 將每張經過處理的圖像數據保存在之前創建的imgs空列表當中,最終所有的圖片都塞到了imgs列表中
imgs.append(img)
# 將每張經過處理的圖像的標簽數據保存在之前創建的labels列表當中 ---這里的標簽僅僅是index 0123標號
labels.append(idx)
# 利用np.asarray函數對生成的imgs和labels列表數據進行轉化,之后轉化成數組數據(imgs轉成浮點數型,labels轉成整數型)
return np.asarray(imgs,np.float32),np.asarray(labels,np.int32)
- data、label數據填充
# 將read_img函數處理之后的數據定義為樣本數據data和標簽數據label
data,label=read_img(path)
# 查看樣本數據的大小
print("shape of data:",data.shape)
# 查看標簽數據的大小
print("shape of label:",label.shape)
# 查看標簽數組的內容,發現數組僅僅是01234的那種標簽 用於區別圖片屬於哪個文件夾
print(label)
- 數據集切分、對數據進行標准化處理
# 保證生成的隨機數具有可預測性,即相同的種子(seed值)所產生的隨機數是相同的,這里是數是可以隨便取的
seed = 785
#生成隨機種子
np.random.seed(seed)
#切分數據集
#train_test_split函數按照用戶指定的比例,隨機將樣本集合划分為訓練集和測試集,並返回划分好的訓練集和測試集數據
#data 待划分的樣本特征集合--數組型的,這就解釋了為啥上面要通np.asarry進行處理
#label 待划分的樣本標簽
#test_size 若在0-1之間,為測試集樣本數目與原始樣本數目之比;若為整數,則是測試集樣本的數目 說白了就是拿出多少的測試集去讓機器自己訓練自己
#x_train 划分出的訓練集數據 x_val 划分出的測試集數據 一般為x_test
#y_train 划分出的訓練集標簽 y_val 划分出的測試集標簽
x_train, x_val, y_train, y_val = train_test_split(data, label, test_size=0.20, random_state=seed)
# 對數據作 標准化處理
# 由於圖像的RGB色彩是最大不過(255,255,255)所以得除255 歸一到[0,1]之間,由於之間轉換成浮點數了,所以有小數出現 ---歸一化
x_train = x_train / 255
x_val = x_val / 255
#print("hello",x_train)
- 創建圖像標簽列表
#創建圖像標簽列表
flower_dict = {0:'daisy',1:'dandelion',2:'rose',3:'sunflowers',4:'tulips'}
- 模型制作(靈魂部分)
#https://www.cnblogs.com/wj-1314/p/9579490.html 參考博客
#Sequential是Keras中的序貫模型
#序貫模型是函數式模型的簡略版,為最簡單的線性、從頭到尾的結構順序,不分叉,是多個網絡層的線性堆疊
#Keras實現了很多層,包括core核心層,Convolution卷積層、Pooling池化層等非常豐富有趣的網絡結構
#用法:可以將層的列表傳遞給Sequential的構造函數,來創建一個Sequential模型
#個人預測---所以這個方法可以套用別人的模型進行訓練
#創建模型。模型包括3個卷積層和三個RELU激活函數,兩個池化層
model = Sequential([
#調用layer.Con2D()創建了一個卷積層。32表示kernel的數量。padding=“same”表示填充輸入以使輸出具有與原始輸入相同的長度,使用RELU函數
#tf.keras.layers.Conv2D()參數解析
#第一個參數 filters 過濾器個數/卷積核個數 與卷積后的輸出通道數一樣,這里的通道數就是32
#第二個參數 kernel_size卷積核尺寸 一般為3*3或5*5 這里的尺寸是height*width,若長寬一樣也可以直接寫一個整數3或者5
#卷積后:卷積后的height,width的計算公式如下
#滑動步長為strides,卷積核的尺寸為S,輸入的尺寸為P,padding = "valid"
# height =width = (P-S)/strides +1,
# 輸入形狀為20×20,卷積核為3×3,滑動步長為1,所以輸出為(20-3)/1 +1 =18
#第三個參數 滑動步長strides 默認為(1,1) 橫向和縱向滑動均為1 可以結合上述式子進行計算輸出形狀
#第四個參數 默認padding="valid" 邊緣不填充 另一個取值為"same"表示邊緣用0填充,如果為"same"則輸出的形狀為height=width=P/strides
#第五個參數 data_format='channels_first'/'channels_last'輸入數據的格式 ,設置輸入數據中通道數是第一個還是最后一個,默認為最后一個
#第六個參數 activation = "relu" 激活函數 ,相當於經過卷積輸出后,再經過一次激活函數,常見的激活函數有relu,softmax,selu等
layers.Conv2D(32, kernel_size=[5, 5], padding="same", activation=tf.nn.relu),
#調用layers.MaxPool2D()創建最大池化層,步長為2,padding=“same”表示填充輸入以使輸出具有與原始輸入相同的長度。
layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),
#利用dropout隨機丟棄25%的神經元,防止過擬合,默認為0.5
layers.Dropout(0.35),
#繼續添加兩個卷積層和一個最大池化層 卷積核數64 尺寸為3*3
layers.Conv2D(64, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
layers.Conv2D(64, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
#最大池化層
layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),
#利用dropout隨機丟棄神經元
layers.Dropout(0.35),
# 繼續添加兩個卷積層和一個最大池化層
layers.Conv2D(128, kernel_size=[3, 3], padding="same", activation=tf.nn.relu),
layers.MaxPool2D(pool_size=[2, 2], strides=2, padding='same'),
#利用dropout隨機丟棄25%的神經元
layers.Dropout(0.35),
#Flatten層用來將輸入“壓平”,即把多維的輸入一維化,常用在從卷積層到全連接層的過渡
layers.Flatten(),
#調用layers.Dense()創建全連接層
#輸出節點數為512 256 激活函數為relu 常用的激活函數罷了,ReLU處理了它的sigmoid、tanh中常見的梯度消失問題
layers.Dense(512, activation=tf.nn.relu),
layers.Dense(256, activation=tf.nn.relu),
#添加全連接層,最后輸出每個分類(5)的數值
#softmax用於多分類過程中,它將多個神經元的輸出,映射到(0,1)區間內,由於只有五個類,所以輸出結點為5
#我估計這里的輸出節點數(維數)和激活函數是有關系的
layers.Dense(5, activation='softmax')
])
#使用Adam優化器,優化模型參數。lr(learning rate, 學習率) 老師給的參考=0.00001
#https://keras.io/zh/optimizers/ 優化器參考
opt = optimizers.Adam(learning_rate=0.001)
#編譯模型以供訓練。metrics=['accuracy']即評估模型在訓練和測試時的性能的指標。
#optimizer 優化器
#loss 損失函數 模型試圖最小化的目標函數 損失函數可以根據最后添加全連接層的激活函數進行修改
#metrics 評估標准 默認為metrics = ['accuracy']
model.compile(optimizer=opt,
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
- 模型訓練
#fix 函數 https://keras.io/zh/models/sequential/
#訓練模型,決定訓練集和驗證集,batch size:進行梯度下降時每個batch包含的樣本數(以多少個樣本為一個batch進行一個迭代)。
#verbose:日志顯示,0為不在標准輸出流輸出日志信息,1為輸出進度條記錄,2為每個epoch輸出一行記錄
#validation_data=(x_val, y_val) 用來評估損失,以及在每輪結束時的任何模型度量指標
model.fit(x_train, y_train, epochs=40, validation_data=(x_val, y_val),batch_size=200, verbose=2)
#輸出模型的結構和參數量
model.summary()
-
輸出日志
-
模型保存/載入函數(當你的模型訓練完的時候,可以保存起來,到時候可以直接載入直接使用,省的每次都需要重新訓練一遍)
#保存模型
model.save('./Model/flower_fin.h5')
#載入模型
model = load_model("./Model/flower_fin.h5")
預測代碼解釋部分:
- 讀取圖像
# 測試圖像的地址 (改為自己的)
path_test = './TestImages/'
#
# 利用glob.glob函數搜索每個層級文件下面符合特定格式“/*.jpg”進行遍歷
# glob.glob函數並不是按順序從1-n來掃描的,是一種亂序掃描,第一個掃到1.jpg,第二個就掃到了10.jpg,並不像常人想的那種從1-2-3順序掃描,
# 因此下面順序輸出的時候會出現圖片和預測結果對不上的問題
# 創建保存圖像的空列表
print(glob.glob(path_test+'/*.jpg'))
correction = glob.glob(path_test+'/*.jpg')
print(correction[0])
imgs=[]
for im in glob.glob(path_test+'/*.jpg'): # 利用glob.glob函數搜索每個層級文件下面符合特定格式“/*.jpg”進行遍歷
img=cv2.imread(im)
img=cv2.resize(img,(w,h))
imgs.append(img) # 將每張經過處理的圖像數據保存在之前創建的imgs空列表當中
imgs = np.asarray(imgs,np.float32) # 列表轉換為數組 和上面的一樣
print("shape of data:",imgs.shape)
- 導入模型,進行預測
#將圖像導入模型進行預測 導入測試集圖像
prediction = model.predict_classes(imgs)
# prediction = np.argmax(model.predict(imgs), axis=-1)
#繪制預測圖像
print(prediction)
print(np.size(prediction))
for i in range(np.size(prediction)):
#打印每張圖像的預測結果
print("第",i+1,"朵花預測:"+flower_dict[prediction[i]]) #預測返回的01234的標簽,通過列表將他變成花名
# img = plt.imread(path_test+"test"+str(i+1)+".jpg") #讀取正確圖像
#由於glob函數讀圖是亂序讀的,所以得修正一下
img = plt.imread(correction[i])
plt.imshow(img)
plt.show() #繪制
個人總結
1.通過loss、accuracy、val_loss、val_accruacy分析來優化--入門版
- loss:訓練集損失值
- accuracy:訓練集准確率
- val_loss:測試集損失值
- val_accruacy:測試集准確率
分析准則:
train loss 不斷下降,test loss不斷下降,說明網絡仍在學習;(最好的)
train loss 不斷下降,test loss趨於不變,說明網絡過擬合;(max pool或者正則化)
train loss 趨於不變,test loss不斷下降,說明數據集100%有問題;(檢查dataset)
train loss 趨於不變,test loss趨於不變,說明學習遇到瓶頸,需要減小學習率或批量數目;(減少學習率)
train loss 不斷上升,test loss不斷上升,說明網絡結構設計不當,訓練超參數設置不當,數據集經過清洗等問題。(最不好的情況)
參考博客 https://www.cnblogs.com/Timeouting-Study/p/12591448.html
2.提高圖像分類准確率----進階版
由於原博客的作者已經寫的很清楚了,這里就只貼出鏈接以供學習參考
(PS:我個人覺得,還是通過增大數據集來訓練的方法對新手還是友好一些,例如這個花卉識別由於圖片每類就五六百張圖片,導致准確率不大高。但是我去用類似的模型拿各有上萬張圖片的貓狗圖片的時候,准確率能夠達到百分之八十多,所以通過增大數據集還是一個可以用的方法)
參考博客 https://blog.csdn.net/weixin_38208741/article/details/79285632
3.個人認為較好的一些CNN概念介紹博客
4.TensoFlow-gpu版本安裝(個人留坑)
在訓練貓狗識別的時候,發現模型在迭代一次的時候就需要三分鍾,如果迭代次數過多的話,就會需要很長時間,如果以后需要嘗試其他項目的時候,可能會有一些不方便,決定在有時間了裝上GPU版本的TensoFlow。
安裝參考博客:https://blog.csdn.net/qq_43529415/article/details/100847887
CNN貓狗識別參考:https://blog.csdn.net/Einstellung/article/details/82773170
花卉識別項目:https://github.com/1103270775/Machine-learning_project