首先我们需要做一些准备工作,即准备需要训练模型的猫狗图片数据集。
这里我们猫狗数据集图片来源于kaggle中Dogs vs. Cats数据集,下载后我们提取了其中的猫的图片和狗的图片各1500张,训练集中帽钩图片各1000张,验证集中猫狗图片各500张。并新建了一个test文件夹,用于存放我们需要预测的一些猫狗图片资源。文件夹目录下: test train validation
这里我们迁移学习使用resnet50的模型,resnet模型非常强大,也是经常使用到的模型,很多新发的paper都会有借鉴到此模型,resnet模型最大的特点就是使用了 跳跃连接 这一特性。详细可见我前面的博文:卷积神经网络.经典神经网络模型之ResNet
代码中也已经实现了迁移学习使用MobileNet_V2模型生成猫狗分类模型,mobilenet模型的优点主要在于能够提升模型运行速度但是会出现一定程序的精度损失,其生成的模型大小也比Resnet50模型要小很多(后面我们量化tflite模型可以进一步减小模型的大小),比较适合于移动设备端的使用。
首先我们需要准备定义文件夹的路径名以及数据集标签数组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,
preprocessing_function = keras.applications.mobilenet_v2.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,
preprocessing_function = keras.applications.mobilenet_v2.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.applications.MobileNetV2(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时就已经达到了一个很好的准确度,这也是迁移学习的一个优点,我们不需要重头开始训练模型
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数据集来测试评估了
这里可以量化模型,可以进一步减小模型的大小但是却对于模型的精度影响不大!将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_to_tflite_converter.optimizations = [tf.lite.Optimize.OPTIMIZE_FOR_SIZE]#模型量化操作
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:
with open('./cat_and_dog/tflite_model/quantized_cat_and_dog_tflite', 'wb') as f:
f.write(concrete_function_tflite)
我们新建一个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 = tf.lite.Interpreter(model_path='./cat_and_dog/tflite_model/quantized_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': , 'quantization': (0.0, 0)}]
[{'name': 'Identity', 'index': 0, 'shape': array([1, 2], dtype=int32), 'dtype': , 'quantization': (0.0, 0)}]
(224, 224, 3)
(1, 224, 224, 3)
[[0.9734476 0.02655234]]
[0]
['cat']
我们首先需要在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模型