tensorflow2------貓狗數據集的分類以及模型部署運行在jetson nano上

1. 貓狗圖片數據集下載

首先我們需要做一些準備工作,即準備需要訓練模型的貓狗圖片數據集。

這裏我們貓狗數據集圖片來源於kaggle中Dogs vs. Cats數據集,下載後我們提取了其中的貓的圖片和狗的圖片各1500張,訓練集中帽鉤圖片各1000張,驗證集中貓狗圖片各500張。並新建了一個test文件夾,用於存放我們需要預測的一些貓狗圖片資源。文件夾目錄下: test  train  validation

2. 遷移學習resnet50並進行模型訓練

這裏我們遷移學習使用resnet50的模型,resnet模型非常強大,也是經常使用到的模型,很多新發的paper都會有借鑑到此模型,resnet模型最大的特點就是使用了 跳躍連接 這一特性。

首先我們需要準備定義文件夾的路徑名以及數據集標籤數組classes_name,這裏有必要強調這個數據集標籤數組,因爲模型輸出的概率分佈是根據這個classes_name的順序來的。

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 pprint
import shutil
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__)

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/dogs_and_cats/train/'
valid_dir = '/home/galaxy/DeepLearning/DATASETS/dogs_and_cats/validation/'
test_dir  = '/home/galaxy/DeepLearning/DATASETS/dogs_and_cats/test/'

#確認各路徑是否正確
print(os.path.exists(train_dir))
print(os.path.exists(valid_dir))
print(os.path.exists(test_dir))

#打印各目錄下的文件、文件夾
print(os.listdir(train_dir))
print(os.listdir(valid_dir))
print(os.listdir(test_dir))

#建立分類的標籤 dog && cat
classes_name = ['cat', 'dog']


True
True
True
['dog', 'cat']
['dog', 'cat']
['100.jpg', '101.jpg', '66.jpg', '22.jpg', '44.jpg', '99.jpg', '11.jpg', '102.jpg', '33.jpg', '55.jpg']

ImageDataGenerator中對數據集做數據增強的相關操作,然後使用flow_from_directory讀取數據集目錄生成訓練集和驗證集的datagenerator,

#建立train文件的datagenerator

#resnet50模型要求的寬高均爲224
height      = 224
width       = 224
channels    = 3
batch_size  = 24
num_classes = 2

#train datasets
train_datagen = keras.preprocessing.image.ImageDataGenerator(
    #rescale           = 1./255, #放縮因子, 除以255是因爲圖片中每個像素點值範圍都在0~255之間
    preprocessing_function = keras.applications.resnet50.preprocess_input,
    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_name,
)
print(train_generator.class_indices)


#valid datasets
valid_datagen = keras.preprocessing.image.ImageDataGenerator(
    #rescale           = 1./255, #放縮因子, 除以255是因爲圖片中每個像素點值範圍都在0~255之間
    preprocessing_function = keras.applications.resnet50.preprocess_input,
)
valid_generator = valid_datagen.flow_from_directory(
    valid_dir,
    target_size = (height,width), #目錄下的圖片會被resize的大小
    batch_size  = batch_size,
    seed        = 7,#隨機種子,用於洗牌和轉換,隨便給個數即可
    shuffle     = False,#False->則按字母數字順序對數據進行排序 True->打亂數據
    class_mode  = "categorical", # 該參數決定了返回的標籤數組的形式
    classes     = classes_name,
)
print(valid_generator.class_indices)

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


Found 2000 images belonging to 2 classes.
{'cat': 0, 'dog': 1}
Found 1000 images belonging to 2 classes.
{'cat': 0, 'dog': 1}
2000 1000

建立模型並進行訓練,使用callbacks保存keras模型(後面會將該模型轉換爲 tflite模型)

resnet50_fine_tune = keras.models.Sequential([
    keras.applications.ResNet50(include_top = False,pooling = 'avg',weights = 'imagenet'),
    keras.layers.Dense(num_classes, activation = 'softmax')
])
resnet50_fine_tune.layers[0].trainable = False


resnet50_fine_tune.compile(loss='categorical_crossentropy',optimizer='adam', metrics=['accuracy'])
resnet50_fine_tune.summary()


callback_dir = "./cat_and_dog"
if os.path.exists(callback_dir):
    shutil.rmtree(callback_dir)
os.mkdir(callback_dir)
output_model_file=os.path.join(callback_dir,"cat_and_dog_model.h5")#在logdir中創建一個模型文件.h5
 
callbacks = [
    keras.callbacks.TensorBoard(callback_dir),
    keras.callbacks.ModelCheckpoint(output_model_file, save_best_only=True, save_weights_only=False),
    keras.callbacks.EarlyStopping(patience=5,min_delta=1e-3),    
]
epochs = 10#使用fine_tune 不需要太多次迭代就能夠達到一個較好的效果
#使用fit_generator是因爲使用的是 ImageDataGenerator 獲取數據集數據的
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,
                             )


Model: "sequential_2"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
resnet50 (Model)             (None, 2048)              23587712  
_________________________________________________________________
dense_2 (Dense)              (None, 2)                 4098      
=================================================================
Total params: 23,591,810
Trainable params: 4,098
Non-trainable params: 23,587,712
_________________________________________________________________
Epoch 1/10
83/83 [==============================] - 42s 500ms/step - loss: 0.2986 - accuracy: 0.8740 - val_loss: 0.0415 - val_accuracy: 0.9858
......
Epoch 10/10
83/83 [==============================] - 41s 497ms/step - loss: 0.0825 - accuracy: 0.9666 - val_loss: 0.0483 - val_accuracy: 0.9837

從epoch的打印可以看到,在第一次epoch時就已經達到了一個很好的準確度,這也是遷移學習的一個優點,我們不需要重頭開始訓練模型

3. 輸入一張圖片驗證模型

from tensorflow.keras.preprocessing import image

image_path_test = '/home/galaxy/DeepLearning/DATASETS/dogs_and_cats/test/100.jpg'

#顯示要預測的圖片
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_show = load_and_preprocess_image(image_path_test)
plt.imshow(image_show)

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


#predict表示預測輸出當前輸入圖像的 所有類型概率數組,即包含十個概率值的數組
pred = resnet50_fine_tune.predict(img)
pprint.pprint(pred)
print(np.argmax(pred,axis=1))#axis參數:對於二維向量而言,0代表對行進行最大值選取,此時對每一列進行操作;1代表對列進行最大值選取,此時對每一行進行操作
 
#predict_classes 預測的是類別,打印出來的值就是類別號
pred_class = resnet50_fine_tune.predict_classes(img)
print(pred_class)
 
#建立對應的文件夾排序的標籤數組打印出預測的標籤
label_name = [classes_name[index] for index in pred_class]
print("This is a ",''.join(label_name))#list轉換爲string


(224, 224, 3)
(1, 224, 224, 3)
array([[9.9968374e-01, 3.1634010e-04]], dtype=float32)
[0]
[0]
This is a  cat

這裏我們本地驗證向該模型中輸入一張貓的圖片來驗證模型是否正常,可以看到識別出上面貓的概率爲99.9968%,單從這張圖片上來看還是比較準確的,當然我們也可以在創建一個test數據集來評估該模型的準確率,這裏我就不創建test數據集來測試評估了

4. 保存模型爲 tflite

這裏我沒有使用量化模型,將cat_and_dog_tflite模型我們scp到jetson nano上

#保存model到文件夾,然後可以通過 saved_model_cli 查看模型
tf.saved_model.save(resnet50_fine_tune,'./cat_and_dog/keras_saved_graph')
!saved_model_cli show --dir ./cat_and_dog/keras_saved_graph --all

#通過keras模型保存爲 tflite 模型
loaded_keras_model = keras.models.load_model('./cat_and_dog/cat_and_dog_model.h5')
loaded_keras_model(np.ones((1,228,228,3)))

run_model = tf.function(lambda x : loaded_keras_model(x))
keras_concrete_function = run_model.get_concrete_function(
    tf.TensorSpec(shape=(1,224,224,3), dtype=np.float32,)#設置具體函數的入參必須維度爲(1,224,224,3)
)
#keras_concrete_function(tf.constant(np.ones((1,228,228,3), dtype=np.float32)))

concrete_function_to_tflite_converter = tf.lite.TFLiteConverter.from_concrete_functions([keras_concrete_function])
concrete_function_tflite=concrete_function_to_tflite_converter.convert()

if not os.path.exists('./cat_and_dog/tflite_model'):
    os.mkdir('./cat_and_dog/tflite_model')
with open('./cat_and_dog/tflite_model/cat_and_dog_tflite', 'wb') as f:
    f.write(concrete_function_tflite)

5. 加載模型並向模型輸入一張圖片進行測試驗證

我們新建一個python文件,用來加載模型並輸入一張圖片進行測試看看

import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
#下面這一句是爲了可以在notebook中畫圖
%matplotlib inline
#設置輸入圖片的相關信息
from tensorflow.keras.preprocessing import image


#加載tflite模型並進行預測

#設置模型需要的寬高
height       = 224
width        = 224
channels     = 3
classes_name = ['cat', 'dog']#標籤數組應該與訓練時一致,所以我們在下載任意模型時都會帶一個labels.txt

#interpreter = tf.lite.Interpreter(model_content=concrete_function_tflite)
interpreter = tf.lite.Interpreter(model_path='./cat_and_dog/tflite_model/cat_and_dog_tflite')
interpreter.allocate_tensors()

input_details  = interpreter.get_input_details()
output_details = interpreter.get_output_details()

print(input_details)
print(output_details)

input_shape = input_details[0]['shape']
# input_data  = tf.constant(tf.ones(input_shape,dtype=np.float32))

#設置輸入圖片的相關信息
from tensorflow.keras.preprocessing import image
image_path_test = '/home/galaxy/DeepLearning/DATASETS/dogs_and_cats/test/100.jpg'
#顯示要預測的圖片
def preprocess_img(image):
    image = tf.image.decode_jpeg(image,channels)
    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_show = load_and_preprocess_image(image_path_test)
plt.imshow(image_show)
#加載圖片至內存中並resize和增加維度
img = image.load_img(image_path_test, target_size=(height, width))
img = image.img_to_array(img)
print(img.shape)#這裏直接打印將img轉換爲數組後的數據維度 (128,128,3)
img = np.expand_dims(img, axis=0)#因爲模型的輸入是要求四維的,所以我們需要將輸入圖片增加一個維度,使用 expand_dims接口
print(img.shape)


interpreter.set_tensor(input_details[0]['index'],img)
interpreter.invoke()

output_results = interpreter.get_tensor(output_details[0]['index'])
print(output_results)
print(np.argmax(output_results,axis = 1))
label_name = [classes_name[index] for index in np.argmax(output_results,axis = 1)]
print(label_name)


[{'name': 'x', 'index': 186, 'shape': array([  1, 224, 224,   3], dtype=int32), 'dtype': <class 'numpy.float32'>, 'quantization': (0.0, 0)}]
[{'name': 'Identity', 'index': 0, 'shape': array([1, 2], dtype=int32), 'dtype': <class 'numpy.float32'>, 'quantization': (0.0, 0)}]
(224, 224, 3)
(1, 224, 224, 3)
[[0.9734476  0.02655234]]
[0]
['cat']

6. 部署tflite模型到jetson nano

我們首先需要在jetson nano上搭建安裝tflite環境,這裏就不直接介紹了,在tensorflow官網中有相關安裝指導。

我們將第五步中生成的cat_and_dog_tflite模型scp到jetson nano上,將第六步中的python代碼文件scp到jetson nano上的tflite模型相同目錄中,然後只需要修改python代碼部分路徑和測試圖片文件路徑,執行 python3 cat_and_dog_tflite.py

我們可以看到最終預測輸入的圖片類型爲一隻貓,概率爲99.324%

總結: 這篇博客主要使用的是在jetson nano上使用python來加載運行模型,下一篇文章我們使用C++來加載運行tflite模型來分類圖片。使用C++環境的話,我們需要使用到tensorflow源碼中的lite部分,我們參考label_image.cc 將該檢測貓狗的代碼集成到tensorflow源碼中,調用tf lite靜態庫,編譯生成bin文件scp到jetson nano上運行測試,具體的步驟可以參考我前面的博客

jetson nano上部署運行 tensorflow lite模型

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