本文主要介绍如何利用深度学习迁移方法进行中药分类的设计的过程
大量有效的中药图片是宝贵的资源,采用自己拍照的方式收集非常耗时,可以从借助搜索引擎从网络抓取中药材图片,方法如下
(1)安装Fatkun插件
在Chrome安装Fatkun插件,可以很方便的下载选定好的图片,并且可以对图片进行重命名保存,插件下载安装地址
https://chrome.google.com/webstore/detail/fatkun-batch-download-ima/nnjjahlikiabnchcpehcpkdeckfgnohf?hl=zh-CN
(2)搜索图片
打开搜索引擎https://images.google.com/?gws_rd=ssl(也可以使用百度)进行中药材图片的检索,例如搜索白术,然后下拉滚动网页刷新,大概获取200张左右图片后停止滚动
(3)下载图片
单击chrome右上角的Fatkun插件,
然后选择当前标签,即对当前页面的图片进行选定下载,
选择反选取消默认的选择,然后自己手动选择中药材图片,可以避免去除一些有噪声的不好的图片,全部选择好后,单击选项,选择按此重命名
然后再选择保存图片按钮,浏览器就会自动开始下载图片,全部下载完后,将会在下载文件夹里出现”白术-google搜索“文件夹,里面包含了下载的图片,浏览全部图片,把不能显示的损坏的图片删除,按照上面的方法再下载其他种类的中药材图片。最后枸杞、白术、茯苓各140张,然后按4:1比例分为训练集和验证集,即每个种类训练集112张,验证集28张。
如果没有google账号,请先申请一个google账号并登陆。因为我们将借助google免费在线 Jupyter 笔记本Colaboratory 的GPU进行自建数据集的训练,Colaboratory 是一个免费的 Jupyter 笔记本环境,不需要进行任何设置就可以使用,并且完全在云端运行。借助 Colaboratory,您可以编写和执行代码、保存和共享分析结果,以及利用强大的计算资源,所有这些都可通过浏览器免费使用。
(1)上传中药数据到google drive
打开google dirve云端硬盘,在mydrive下新建data文件夹,然后再新建medicine文件夹,再新建train和test文件夹(分别存放训练集和验证集),再新建model文件用于存放最后训练好的模型,tmp文件夹用于存放测试图片
最后的目录结构如下
然后将本地磁盘的枸杞、白术等文件夹拖到google drive的medicine文件下,注意train下面每个中药分类112张,test文件夹下每个种类28张。
(1)创建colab在线笔记
打开https://colab.research.google.com在编辑器,选择Google云端硬盘,然后选择下方的“新建python3笔记本”
(2)修改运行时为GPU,提高训练速度
选择上面的菜单代码执行程序-》更改运行时类型-》选择GPU后保存,然后再单击笔记本上方右边的连接,等待片刻,google将会为您分配一个强大的GPU云端计算引擎
(3)编写代码
[1]导入keras等相关包,然后按shift+return键运行并创建下一个单元格
from keras.applications.resnet50 import ResNet50
from keras.preprocessing import image
from keras.models import Model
from keras.layers import Dense, GlobalAveragePooling2D
[2]基于ResNet50构建迁移训练,顶层不需要参与训练,设置include_top=False
# 构建不带分类器的预训练模型
base_model = ResNet50(weights='imagenet', include_top=False)
[3]连接google 云端硬盘,以获得训练集和验证集图片数据
from google.colab import drive
drive.mount('/content/gdrive', force_remount=True)
root_dir = "/content/gdrive/My Drive/"
base_dir = root_dir + 'data/medicine/'
TRAIN_DIR = base_dir + 'train/'
VAL_DIR = base_dir + 'test/'
在shift+return执行当前代码后,下方会出现一个连接,单击连接授权可以访问google drive后,复制验证码,然后粘贴到Enter your authorization code:回车即可,如果出现Mounted at /content/gdrive 说明连接成功!
[4]准备好训练集,由于数据较少,所以需要使用keras的数据图像增强ImageDataGenerator方法
IMG_W,IMG_H =224,224 # 单张图片的大小
BATCH_SIZE = 16
# 2,准备训练集,keras有很多Generator可以直接处理图片的加载,增强等操作,封装的非常好
from keras.preprocessing.image import ImageDataGenerator
train_datagen = ImageDataGenerator( # 单张图片的处理方式,train时一般都会进行图片增强
rescale=1. / 255, # 图片像素值为0-255,此处都乘以1/255,调整到0-1之间
shear_range=0.2, # 斜切
zoom_range=0.2, # 放大缩小范围
horizontal_flip=True) # 水平翻转
train_generator = train_datagen.flow_from_directory(# 从文件夹中产生数据流
TRAIN_DIR, # 训练集图片的文件夹
target_size=(IMG_W, IMG_H), # 调整后每张图片的大小
batch_size=BATCH_SIZE,
class_mode='categorical') # 此处是多分类问题,故而mode是categorical
[5]按照相同的方法准备验证集,验证集的目的是帮助在训练期间不断的调整网络参数,以便提高精度,注意验证集不能使用图像增强方法
# 3 同样的方式准备测试集
val_datagen = ImageDataGenerator(rescale=1. / 255) # 只需要和trainset同样的scale即可,不需增强
val_generator = val_datagen.flow_from_directory(
VAL_DIR,
target_size=(IMG_W, IMG_H),
batch_size=BATCH_SIZE,
class_mode='categorical')
[6]查看keras生成分类的条目有哪些
#统计分类数
num_classes = len(train_generator.class_indices)
class_dictionary = train_generator.class_indices
print(num_classes)
print(class_dictionary)
返回结果是3 {'枸杞子': 0, '白术 ': 1, '茯苓': 2}
[7]创建并编译模型
from keras import optimizers
# 添加全局平均池化层
x = base_model.output
x = GlobalAveragePooling2D()(x)
# 添加一个全连接层
x = Dense(512, activation='relu')(x)
# 添加一个分类器,假设我们有3个类
predictions = Dense(3, activation='softmax')(x)
# 构建我们需要训练的完整模型
model = Model(inputs=base_model.input, outputs=predictions)
# 编译模型(一定要在锁层以后操作)
model.compile(loss='categorical_crossentropy',
optimizer=optimizers.SGD(lr=1e-4, momentum=0.9),
metrics=['accuracy'])
注意predictions = Dense(3, activation='softmax')(x),因为本文只创建了3个中药分类,如果是10个分类,那么需要修改为10
[8]训练模型
# 模型的训练
NUM_EPOCHS = 10
BATCH_SIZE = 16
num_train_images = 362
num_val_images = 84
history_1 = model.fit_generator(train_generator, # 数据流
steps_per_epoch=num_train_images // BATCH_SIZE,
epochs=NUM_EPOCHS,
shuffle = True,
verbose = 1,
validation_data=val_generator,
validation_steps=num_val_images // BATCH_SIZE)
然后会看到模型在训练期间精度和loss的变化
Epoch 1/10
/usr/local/lib/python3.6/dist-packages/PIL/Image.py:914: UserWarning: Palette images with Transparency expressed in bytes should be converted to RGBA images
'to RGBA images')
22/22 [==============================] - 12s 552ms/step - loss: 0.2469 - acc: 0.9643 - val_loss: 0.2140 - val_acc: 0.9265
Epoch 2/10
22/22 [==============================] - 12s 531ms/step - loss: 0.1807 - acc: 0.9813 - val_loss: 0.1931 - val_acc: 0.9706
Epoch 3/10
22/22 [==============================] - 12s 534ms/step - loss: 0.1827 - acc: 0.9744 - val_loss: 0.1512 - val_acc: 0.9559
Epoch 4/10
22/22 [==============================] - 12s 529ms/step - loss: 0.1506 - acc: 0.9829 - val_loss: 0.1515 - val_acc: 0.9706
Epoch 5/10
22/22 [==============================] - 12s 553ms/step - loss: 0.1269 - acc: 0.9886 - val_loss: 0.1375 - val_acc: 0.9750
Epoch 6/10
22/22 [==============================] - 12s 526ms/step - loss: 0.1260 - acc: 0.9825 - val_loss: 0.1171 - val_acc: 0.9853
Epoch 7/10
22/22 [==============================] - 12s 543ms/step - loss: 0.1058 - acc: 0.9886 - val_loss: 0.1263 - val_acc: 0.9559
Epoch 8/10
22/22 [==============================] - 12s 551ms/step - loss: 0.0951 - acc: 0.9825 - val_loss: 0.0899 - val_acc: 0.9853
Epoch 9/10
22/22 [==============================] - 11s 518ms/step - loss: 0.0837 - acc: 0.9886 - val_loss: 0.0936 - val_acc: 0.9853
Epoch 10/10
22/22 [==============================] - 12s 538ms/step - loss: 0.1098 - acc: 0.9773 - val_loss: 0.1112 - val_acc: 0.9706
[9]创建画图方法,为了更直观的观察精度和loss的变化
# 画图,将训练时的acc和loss都绘制到图上
import matplotlib.pyplot as plt
%matplotlib inline
def plot_training(history):
plt.figure(figsize=(8, 8))
plt.subplot(2, 1, 1)
train_acc = history.history['acc']
val_acc = history.history['val_acc']
#epochs = range(len(train_acc))
plt.plot(train_acc,label='Training Accuracy')
plt.plot(val_acc,label='Validation Accuracy')
plt.legend(loc='lower right')
plt.ylabel('Accuracy')
plt.xlabel('epoch')
plt.title('Training and Validation Accuracy')
plt.subplot(2, 1, 2)
train_loss = history.history['loss']
val_loss = history.history['val_loss']
#epochs = range(len(train_loss))
plt.plot(train_loss, label='Training Loss')
plt.plot(val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.ylabel('Cross Entropy')
plt.title('Training and Validation Loss')
plt.xlabel('epoch')
plt.show()
然后通过 plot_training(history_1) 调用即可打印输出图像,例如
[10]将训练的模型保存到google 云端硬盘,以便于下一步进行图片预测和构建web app使用
model.save(base_dir + 'model/' + 'keras_medicine_tl.h5')
[11]进行预测
选择一张不在训练集和验证集的枸杞图片上传到google/drive/data/medicine/tmp 下
#预测
import numpy as np
from keras.models import load_model
from keras.preprocessing import image
from keras.applications.imagenet_utils import preprocess_input, decode_predictions
#from keras.applications.inception_v3 import preprocess_input, decode_predictions
#加载模型预测
model = load_model(base_dir + 'model/' + 'keras_medicine_tl.h5')
# 1.读取图片并处理(方法一)
img_path = base_dir + 'tmp/' + 'gq.png'
img = image.load_img(img_path, target_size=(224, 224))
x = image.img_to_array(img)
x /= 255.
x = np.expand_dims(x, axis=0)
#x = preprocess_input(x) # 使用imagenet_utils的preprocess_input方法处理图片,会导致分类错误,改为 /255
preds = model.predict(x)
print(preds)
print("[INFO] classifying image...")
proba = model.predict(x)[0]
print(proba)
idx = np.argmax(proba)
print(idx)
print(train_generator.class_indices)
print(val_generator.class_indices)
inv_map = {v: k for k, v in train_generator.class_indices.items()}
label = inv_map[idx]
print(label)
运行后会得到如下反馈结果
[[0.68279696 0.2038744 0.11332864]]
[INFO] classifying image...
[0.68279696 0.2038744 0.11332864]
0
{'枸杞子': 0, '白术 ': 1, '茯苓': 2}
{'枸杞子': 0, '白术': 1, '茯苓': 2}
枸杞子
以上完整colab源码请参考https://github.com/puma007/cmapp/blob/master/MedicineTransferLearning.ipynb
(1)使用pycharm新建一个Flask应用cmapp,注意选择python解释器位置,当前python解释器必须已经安装Tensorflow和keras,因为本文keras使用的是Tensorflow作为后端引擎
(2)app.py 代码如下:
# coding=utf-8
import os, datetime, random
import numpy as np
from keras.models import load_model
from keras.preprocessing import image
from flask import Flask, request, render_template
from werkzeug.utils import secure_filename
# 定义一个flask web app
app = Flask(__name__)
app.config.update(
SECRET_KEY = os.urandom(24),
# 最大上传大小,当前5MB
MAX_CONTENT_LENGTH = 5 * 1024 * 1024
)
# 生成无重复随机数用于文件基本名
gen_rnd_filename = lambda :"%s%s" %(datetime.datetime.now().strftime('%Y%m%d%H%M%S'), str(random.randrange(1000, 10000)))
# 已经训练好的模型位置
MODEL_PATH = 'models/keras_medicine_tl.h5'
# 加载模型
model = load_model(MODEL_PATH)
# 编译并运行模型,可以让第一次预测更快,不然第一次加载会很慢
# 参考 https://github.com/keras-team/keras/issues/6124 解释
model._make_predict_function()
print('Model loaded. Start serving...')
# 也可以直接加载keras自带的模型 https://keras.io/applications/
#from keras.applications.resnet50 import ResNet50
#model = ResNet50(weights='imagenet')
#print('Model loaded. Start serving...')
def model_predict(img_path, model):
'''
预测
:param img_path: 图片
:param model: 模型
:return:result
'''
img = image.load_img(img_path, target_size=(224, 224)) #224,224
x = image.img_to_array(img)
x /= 255.
x = np.expand_dims(x, axis=0)
#x = preprocess_input(x)
proba = model.predict(x)[0]
idx = np.argmax(proba)
print(idx)
result = ''
label_medicine = {'枸杞子': 0, '白术 ': 1, '茯苓': 2}
for k, v in label_medicine.items():
if v == idx:
result = k
return result
@app.route('/', methods=['GET'])
def index():
return render_template('index.html')
@app.route('/predict', methods=['GET', 'POST'])
def upload():
if request.method == 'POST':
# 获取上传的文件
f = request.files['file']
# 保存上传文件到 ./uploads
basepath = os.path.dirname(__file__)
file_path = os.path.join(
basepath, 'uploads', secure_filename(gen_rnd_filename() + "." + f.filename.split('.')[-1]))
f.save(file_path)
# 预测
result = model_predict(file_path, model)
return result
return None
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8098, debug=True)
(3)从google drvie里model文件夹下载已经训练好的模型文件 keras_medicine_tl.h5
(4)在cmapp下新建models文件夹,然后将keras_medicine_tl.h5拷贝到此处,目录结构如下,其中uploads文件夹用于存放上传的预测图片
(5)另外还要构建页面文件index和相关样式文件main.css,以及页面脚本main.js
(6)运行app.py 文件
(7)在浏览器里输入http://127.0.0.1:5000/ 进行测试
Flask的web源码请参考https://github.com/puma007/cmapp/