tensorflow2------10-monkey && ResNet50 &&遷移學習

10-monkey-species 數據集是一個10類不同品種猴子的數據集,這個數據集是從kaggle平臺中下載到本地使用的,在這個分類猴子的數據集中我們使用resnet50模型來做遷移學習fine tune,並且最終實現向模型中輸入一張圖片能夠打印出該圖片屬於哪類猴子品種的結果。

import matplotlib as mpl #畫圖用的庫
import matplotlib.pyplot as plt
#下面這一句是爲了可以在notebook中畫圖
%matplotlib inline
import numpy as np
import sklearn   #機器學習算法庫
import pandas as pd #處理數據的庫   
import os
import sys
import time
import tensorflow as tf
 
from tensorflow import keras   #使用tensorflow中的keras
#import keras #單純的使用keras
 
print(tf.__version__)
print(sys.version_info)
for module in mpl, np, sklearn, pd, tf, keras:
    print(module.__name__, module.__version__)



2.0.0
sys.version_info(major=3, minor=6, micro=9, releaselevel='final', serial=0)
matplotlib 3.1.2
numpy 1.18.0
sklearn 0.21.3
pandas 0.25.3
tensorflow 2.0.0
tensorflow_core.keras 2.2.4-tf
physical_devices = tf.config.experimental.list_physical_devices('GPU')
assert len(physical_devices) > 0, "Not enough GPU hardware devices available"
tf.config.experimental.set_memory_growth(physical_devices[0], True)
train_dir  = "/home/galaxy/DeepLearning/DATASETS/10-Monkey-Species/training/training/"
valid_dir  = "/home/galaxy/DeepLearning/DATASETS/10-Monkey-Species/validation/validation/"
label_file = "/home/galaxy/DeepLearning/DATASETS/10-Monkey-Species/monkey_labels.txt"

print(os.path.exists(train_dir))
print(os.path.exists(valid_dir))
print(os.path.exists(label_file))

print(os.listdir(train_dir))
print(os.listdir(valid_dir))



True
True
True
['n9', 'n8', 'n3', 'n1', 'n5', 'n6', 'n2', 'n0', 'n7', 'n4']
['n9', 'n8', 'n3', 'n1', 'n5', 'n6', 'n2', 'n0', 'n7', 'n4']
labels = pd.read_csv(label_file, header=0)
print(labels)



   Label     Latin Name              Common Name                     \
0  n0         alouatta_palliata\t    mantled_howler                   
1  n1        erythrocebus_patas\t    patas_monkey                     
2  n2        cacajao_calvus\t        bald_uakari                      
3  n3        macaca_fuscata\t        japanese_macaque                 
4  n4       cebuella_pygmea\t        pygmy_marmoset                   
5  n5       cebus_capucinus\t        white_headed_capuchin            
6  n6       mico_argentatus\t        silvery_marmoset                 
7  n7      saimiri_sciureus\t        common_squirrel_monkey           
8  n8       aotus_nigriceps\t        black_headed_night_monkey        
9  n9       trachypithecus_johnii    nilgiri_langur                   

    Train Images    Validation Images  
0             131                  26  
1             139                  28  
2             137                  27  
3             152                  30  
4             131                  26  
5             141                  28  
6             132                  26  
7             142                  28  
8             133                  27  
9             132                  26 

 讀取文件夾中的圖片數據並生成相關的generator

##################################
####該模塊主要用於獲取文件夾下的圖片####
##################################

#resnet50使用的圖像寬高均爲224
#height      = 224
#width       = 224
height      = 128 #設置圖像被縮放的寬高
width       = 128
channels    = 3   #圖像通道數
batch_size  = 32
num_classes = 10

##########------------訓練集數據------------##########
#初始化一個訓練數據相關的generator
#具體用於 數據集中的圖片數據進行處理,可以對圖片數據進行歸一化、旋轉、翻轉等數據增強類操作
train_datagen = keras.preprocessing.image.ImageDataGenerator(
    preprocessing_function = keras.applications.resnet50.preprocess_input,#resnet50專門用來預處理圖像的函數,把圖像做歸一化到-1~1之間
    # 使用第一行preprocessing_function 就不需要 rescale
    #rescale           = 1./255, #放縮因子, 除以255是因爲圖片中每個像素點值範圍都在0~255之間
    rotation_range    = 40,  #圖片隨機轉動的角度範圍(-40 ~ 40)
    width_shift_range = 0.2, #值 < 1時,表示偏移的比例,即在 0~值 這個比例幅度之間進行偏移
    height_shift_range= 0.2, #值 > 1時,表示像素寬度,即該圖片的偏移幅度大小
    shear_range       = 0.2, #剪切強度
    zoom_range        = 0.2, #縮放強度
    horizontal_flip   = True,#水平隨機翻轉
    fill_mode         = 'nearest',#像素填充模式
)
#接下來讀取目錄下的圖片然後按照上面的數據增強相關操作對圖片進行處理
train_generator = train_datagen.flow_from_directory(train_dir, 
                                                    target_size = (height,width), #目錄下的圖片會被resize的大小
                                                    batch_size  = batch_size,
                                                    seed        = 7,#隨機種子,用於洗牌和轉換,隨便給個數即可
                                                    shuffle     = True,#False->則按字母數字順序對數據進行排序 True->打亂數據
                                                    class_mode  = "categorical", # 該參數決定了返回的標籤數組的形式
                                                    #classes     = 這個參數就是描述的 文件夾名與輸出標籤的對應關係
                                                   )
#classes:可選參數,爲子文件夾的列表,如['dogs','cats']默認爲None. 若未提供,則該類別列表將從directory下的子文件夾名稱/結構自動推斷。
#每一個子文件夾都會被認爲是一個新的類。(類別的順序將按照字母表順序映射到標籤值)。通過屬性class_indices可獲得文件夾名與類的序號的對應字典。

#使用生成器的.class_indices方法即可獲取模型默認的Labels序列,文件夾名與類的序號的對應字典
print(train_generator.class_indices)


##########------------驗證集數據------------##########
#初始化一個驗證數據相關的generator
#驗證數據集上不需要進行數據增強的相關操作,僅保留縮放即可,不然的話訓練集與驗證集的值的分佈會不同
valid_datagen = keras.preprocessing.image.ImageDataGenerator(
    preprocessing_function = keras.applications.resnet50.preprocess_input,#resnet50專門用來預處理圖像的函數,相當於歸一化,所以無需rescale
    #rescale           = 1./255, #放縮因子, 除以255是因爲圖片中每個像素點值範圍都在0~255之間
)
#接下來讀取目錄下的圖片然後按照上面的數據增強相關操作對圖片進行處理
valid_generator = valid_datagen.flow_from_directory(valid_dir, 
                                                    target_size = (height,width), #目錄下的圖片會被resize的大小
                                                    batch_size  = batch_size,
                                                    seed        = 7,#隨機種子,用於洗牌和轉換,隨便給個數即可
                                                    shuffle     = False,#不需要訓練所以不需要打亂數據
                                                    class_mode  = "categorical", # 該參數決定了返回的標籤數組的形式
                                                    )
#使用生成器的.class_indices方法即可獲取模型默認的Labels序列,文件夾名與類的序號的對應字典
print(valid_generator.class_indices)

train_num = train_generator.samples
valid_num = valid_generator.samples
print(train_num, valid_num)




Found 1098 images belonging to 10 classes.
{'n0': 0, 'n1': 1, 'n2': 2, 'n3': 3, 'n4': 4, 'n5': 5, 'n6': 6, 'n7': 7, 'n8': 8, 'n9': 9}
Found 272 images belonging to 10 classes.
{'n0': 0, 'n1': 1, 'n2': 2, 'n3': 3, 'n4': 4, 'n5': 5, 'n6': 6, 'n7': 7, 'n8': 8, 'n9': 9}
1098 272
for i in range(1):
    x,y = train_generator.next()
    print(x.shape, y.shape)
    print(y)
#因爲 class_mode 設置爲categorical,所以label標籤返回的是2D的one-hot編碼標籤(2-> one_hot -> [0, 0, 1])



(32, 128, 128, 3) (32, 10)
[[0. 0. 0. 0. 0. 0. 0. 0. 1. 0.]
 [0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]
 [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
 [0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]
 [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]
 [0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
 [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]
 [0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
 [0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]
 [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
 [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
 [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]]

 使用resnet50做遷移學習

#使用resnet50做遷移學習

'''
#1.這裏ResNet50層當做一層,只有最後一層是可以被訓練的
resnet50_fine_tune = keras.models.Sequential([
    keras.applications.ResNet50(include_top=False,#include_top:是否保留頂層的全連接網絡,這裏最後要定義自己的softmax選False
                                pooling='avg',#‘avg’代表全局平均池化,‘max’代表全局最大值池化
                                weights='imagenet',#None代表隨機初始化,即不加載預訓練權重;'imagenet’代表加載預訓練權重
                               ),
    keras.layers.Dense(num_classes, activation='softmax'),    
])
resnet50_fine_tune.layers[0].trainable=False #設置ResNet50這一層的參數不可訓練,因爲 weights='imagenet'
'''

#2.這裏ResNet50中最後幾層都是可以訓練,我們可以在模型架構裏面看到 Trainable params可訓練參數會大大增加
resnet50 = keras.applications.ResNet50(include_top=False, pooling='avg', weights='imagenet')
for layers in resnet50.layers[0:-5]: #這裏遍歷最後五層之前的layers並設置其權重相關參數不可遍歷
    layers.trainable = False

resnet50_fine_tune = keras.models.Sequential([
    resnet50,
    keras.layers.Dense(num_classes, activation='softmax'),    
])

'''
model = keras.models.Sequential([
    keras.layers.Conv2D(filters=32, kernel_size=3, padding="same", activation="relu",input_shape=(width, height, channels)),
    keras.layers.Conv2D(filters=32, kernel_size=3, padding="same", activation="relu"),
    keras.layers.MaxPool2D(pool_size=2),
    
    keras.layers.Conv2D(filters=64, kernel_size=3, padding="same", activation="relu"),
    keras.layers.Conv2D(filters=64, kernel_size=3, padding="same", activation="relu"),
    keras.layers.MaxPool2D(pool_size=2),
    
    keras.layers.Conv2D(filters=128, kernel_size=3, padding="same", activation="relu"),
    keras.layers.Conv2D(filters=128, kernel_size=3, padding="same", activation="relu"),
    keras.layers.MaxPool2D(pool_size=2),
    
    keras.layers.Flatten(),
    keras.layers.Dense(128,activation="relu"),
    keras.layers.Dense(num_classes, activation="softmax"),
])
'''

#損失函數 sparse_categorical_crossentropy 和 categorical_crossentropy 的選擇取決於前面設定的y值的取值類型
#如果y取值爲 2D的 one-hot編碼,則選擇 categorical_crossentropy
#如果y取值爲 1D的 整數標籤,則選擇 sparse_categorical_crossentropy
#前面的 tensorflow2------分類問題fashion_mnist 文章中有過相關描述
# metrics 表示選擇 accuracy作爲評價參數
resnet50_fine_tune.compile(loss="categorical_crossentropy", optimizer="adam",metrics=["accuracy"])

resnet50_fine_tune.summary()



Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
resnet50 (Model)             (None, 2048)              23587712  
_________________________________________________________________
dense (Dense)                (None, 10)                20490     
=================================================================
Total params: 23,608,202
Trainable params: 1,075,210
Non-trainable params: 22,532,992
import shutil

callback_dir = "./callback_10-monkey-species"

if os.path.exists(callback_dir):
    shutil.rmtree(callback_dir)
os.mkdir(callback_dir)

output_model_file=os.path.join(callback_dir,"10Monkey_model.h5")#在logdir中創建一個模型文件.h5

callbacks = [
    keras.callbacks.TensorBoard(callback_dir),
    keras.callbacks.ModelCheckpoint(output_model_file, save_best_only=True),
    keras.callbacks.EarlyStopping(patience=5,min_delta=1e-3),    
]


epochs = 10#使用fine_tune 不需要太多次迭代就能夠達到一個較好的效果
'''
#使用fit_generator是因爲使用的是 ImageDataGenerator 獲取數據集數據的
history = model.fit_generator(train_generator,#steps_per_epoch: 一個epoch包含的步數(每一步是一個batch的數據送入)
                              steps_per_epoch = train_num // batch_size,
                              epochs          = epochs,
                              validation_data = valid_generator,
                              validation_steps= valid_num // batch_size,
                              callbacks       = callbacks,
                             )
'''
history = resnet50_fine_tune.fit_generator(train_generator,#steps_per_epoch: 一個epoch包含的步數(每一步是一個batch的數據送入)
                              steps_per_epoch = train_num // batch_size,
                              epochs          = epochs,
                              validation_data = valid_generator,
                              validation_steps= valid_num // batch_size,
                              callbacks       = callbacks,
                             )
#運行打印看到val_accuracy的值並沒有逐漸變大而是一直保持不變,是因爲激活函數使用的是selu導致,可嘗試更換激活函數爲relu



Epoch 1/10
34/34 [==============================] - 17s 506ms/step - loss: 1.0719 - accuracy: 0.6614 - val_loss: 0.7943 - val_accuracy: 0.8828
Epoch 2/10
34/34 [==============================] - 16s 470ms/step - loss: 0.5916 - accuracy: 0.8030 - val_loss: 0.8710 - val_accuracy: 0.8555
Epoch 3/10
34/34 [==============================] - 17s 493ms/step - loss: 0.4014 - accuracy: 0.8771 - val_loss: 0.6169 - val_accuracy: 0.8867
。。。
Epoch 8/10
34/34 [==============================] - 16s 459ms/step - loss: 0.2234 - accuracy: 0.9231 - val_loss: 0.7226 - val_accuracy: 0.8984
Epoch 9/10
34/34 [==============================] - 16s 476ms/step - loss: 0.2005 - accuracy: 0.9465 - val_loss: 0.5425 - val_accuracy: 0.9102

 向模型中輸入一張圖片做預測來驗證模型

def preprocess_img(image):
    image = tf.image.decode_jpeg(image,channels=3)
    image = tf.image.resize(image,[width,height])
    image /= 255.0
    return image

def load_and_preprocess_image(path):
    image = tf.io.read_file(path)
    return preprocess_img(image)


#image_path_test = '/home/galaxy/DeepLearning/DATASETS/10-Monkey-Species/testImg/white_headed_capuchin.jpg'
image_path_test = '/home/galaxy/DeepLearning/DATASETS/10-Monkey-Species/testImg/japanese_macaque.jpg'

image = load_and_preprocess_image(image_path_test)
plt.imshow(image)

<matplotlib.image.AxesImage at 0x7fe7dc4da5f8>

from tensorflow.keras.preprocessing import image
import pprint

img = image.load_img(image_path_test, target_size=(height, width))
img = image.img_to_array(img)
print(img.shape)#這裏直接打印將img轉換爲數組後的數據維度 (128,128,3)
#因爲模型的輸入是要求四維的,所以我們需要將輸入圖片增加一個維度,使用 expand_dims接口
img = np.expand_dims(img, axis=0)
print(img.shape)


#predict表示預測輸出當前輸入圖像的 所有類型概率數組,即包含十個概率值的數組
pred = resnet50_fine_tune.predict(img)
pprint.pprint(pred)
print(np.argmax(pred,axis=1))#axis = 1是取行的最大值的索引,axis = 0是列的最大值的索引

#predict_classes 預測的是類別,打印出來的值就是類別號
pred_class = resnet50_fine_tune.predict_classes(img)
print(pred_class)

#建立對應的文件夾排序的標籤數組打印出預測的標籤
monkey_names = ['mantled_howler',
                'patas_monkey',
                'bald_uakari',
                'japanese_macaque',
                'pygmy_marmoset',
                'white_headed_capuchin',
                'silvery_marmoset',
                'common_squirrel_monkey',
                'black_headed_night_monkey',
                'nilgiri_langur']
label_name = [monkey_names[index] for index in pred_class]
print("This is a ",''.join(label_name))#list轉換爲string



(128, 128, 3)
(1, 128, 128, 3)
array([[2.86323209e-16, 1.02518805e-09, 6.43146951e-12, 9.99995470e-01,
        2.64310107e-09, 5.72158265e-10, 4.45466458e-06, 1.10910907e-11,
        9.24227888e-15, 1.14730796e-07]], dtype=float32)
[3]
[3]
This is a  japanese_macaque

參考博客:ImageDataGenerator生成器的flow,flow_from_directory用法

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章