数据集简介
数据集中树叶的样子有四种,分别为锈病,健康,黑腐,黑星四个种类,而我们要做的工作。就是让机器使用神经网络学习给定的树叶数据集,实现对不同样态的特征提取。然后我们再利用训练好的神经网络,投喂新的树叶图片,让机器自己划分其样态的种类,实现机器识别叶子的智能操作。
将数据集打包分类后,变成traindata和testdata两个数据集。以及对应的train_label和test_label.
代码部分
当时这个项目是在笔记本上完成的,环境是tensorflow2.1.0,话不多说直接上代码
import os
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
from PIL import Image
from tensorflow.keras.preprocessing.image import ImageDataGenerator
# 设置相关路径
train_txt = './second/train_label.txt'
x_train_savepath = './second/model_x_train.npy'
y_train_savepath = './second/model_y_train.npy'
test_txt = './second/test_label.txt'
x_test_savepath = './second/model_x_test.npy'
y_test_savepath = './second/model_y_test.npy'
checkpoint_save_path = "./checkpoint/model_data.ckpt"
# 为数据增广的方法设置参数 图中分别设置了随机旋转,水平/垂直的平移变换,和缩放变换等等
image_gen_train = ImageDataGenerator(
rotation_range=90,
width_shift_range=.15,
height_shift_range=.15,
zoom_range=0.5
)
# 设置函数:从label中找到文件路径,读取文件到返回参数x中,并设置返回参数y_作为相应标签
def generateds(txt):
f = open(txt, 'r') # 以只读形式打开txt文件
contents = f.readlines() # 读取文件中所有行
f.close() # 关闭txt文件
x, y_ = [], [] # 建立空列表
for content in contents: # 逐行取出
print(content)
value = content.split(",") # 以,分开,图片路径为value[0] , 标签为value[1] , 存入列表
img_path = value[0] # 拼出图片路径和文件名
print(img_path)
img = Image.open(img_path) # 读入图片
img = np.array(img.convert('L')) # 由于笔记本内存有限,图片全部转化为单通道灰度图像
img = img / 255. # 数据归一化 (实现预处理)
x.append(img) # 归一化后的数据,贴到列表x
y_.append(value[1]) # 标签贴到列表y_
print('loading : ' + content) # 打印状态提示
x = np.array(x) # 变为np.array格式
y_ = np.array(y_) # 变为np.array格式
y_ = y_.astype(np.int64) # 变为64位整型
return x, y_ # 返回输入特征x,返回标签y_
# 这里是判断是否已存在保存好的图片文件,这种操作可以使得第二次加载模型时不用再单独读取每张图片,而仅仅只是读取之前保存好的npy格式的东东,大大提升读取速度
if os.path.exists(x_train_savepath) and os.path.exists(y_train_savepath) and os.path.exists(
x_test_savepath) and os.path.exists(y_test_savepath):
print('-------------Load Datasets-----------------')
x_train_save = np.load(x_train_savepath)
y_train = np.load(y_train_savepath)
x_test_save = np.load(x_test_savepath)
y_test = np.load(y_test_savepath)
# 将数据集图像最后增加一维
x_train = np.reshape(x_train_save, (len(x_train_save), 256, 256, 1))
x_test = np.reshape(x_test_save, (len(x_test_save), 256, 256, 1))
else:
print('-------------Generate Datasets-----------------')
x_train, y_train = generateds(train_txt)
x_test, y_test = generateds(test_txt)
x_train = np.reshape(x_train, (len(x_train), 256, 256, 1))
x_test = np.reshape(x_test, (len(x_test), 256, 256, 1))
print('-------------Save Datasets-----------------')
# 将数据集图像最后增加一维
x_train_save = np.reshape(x_train, (len(x_train), 256, 256, 1))
x_test_save = np.reshape(x_test, (len(x_test), 256, 256, 1))
np.save(x_train_savepath, x_train_save)
np.save(y_train_savepath, y_train)
np.save(x_test_savepath, x_test_save)
np.save(y_test_savepath, y_test)
# 网络结构
model = tf.keras.models.Sequential([
tf.keras.layers.Conv2D(filters=36, kernel_size=(3, 3), padding='same'),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.Activation('relu'),
tf.keras.layers.Conv2D(filters=12, kernel_size=(3, 3), padding='same'),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.Activation('relu'),
tf.keras.layers.MaxPool2D(pool_size=(2, 2), strides=2, padding='same'),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Dense(4, activation='softmax')
])
# 设置优化器
model.compile(optimizer=tf.keras.optimizers.Adam(lr=0.01),
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
metrics=['sparse_categorical_accuracy'])
# 学习率自动调整
reduce_lr = tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss', patience=10, mode='auto')
if os.path.exists(checkpoint_save_path + '.index'):
print('------------------ restore model successfully------------------')
model.load_weights(checkpoint_save_path)
cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_save_path,
save_weights_only=True,
save_best_only=True)
history = model.fit(image_gen_train.flow(x_train, y_train, batch_size=24), epochs=200, validation_data=(x_test, y_test), validation_freq=1, callbacks=[reduce_lr, cp_callback])
# 数据可视化
acc = history.history['sparse_categorical_accuracy']
val_acc = history.history['val_sparse_categorical_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']
plt.subplot(1, 2, 1)
plt.plot(acc, label='Training Accuracy')
plt.plot(val_acc, label='Validation Accuracy')
plt.title('Training and Validation Accuracy')
plt.legend()
plt.subplot(1, 2, 2)
plt.plot(loss, label='Training Loss')
plt.plot(val_loss, label='Validation Loss')
plt.title('Training and Validation Loss')
plt.legend()
plt.show()
第一次改进:由于数据集样本比较少,需要使用数据增广,但上述代码的数据增广特别简陋,是强行每次用image_gen_train.flow方法操作的,会导致每次喂入神经网络的训练集不同,可能会导致模型不收敛。于是之后在此基础上,单独增加了一个py文件,进行可视化的图像增广,专门设置了输出增广后图像的代码,直接生成增广后的图像数据。
from tensorflow.keras.preprocessing.image import ImageDataGenerator, array_to_img, img_to_array, load_img
import numpy as np
beishu = 10 # 增广倍数
in_path = "" # 输入类别子文件夹的上一级文件夹
out_path = # 输出类别子文件夹的上一级文件夹
# 具体增广操作
datagen = ImageDataGenerator(
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,
fill_mode='nearest')
gener = datagen.flow_from_directory(in_path, batch_size=108, shuffle=False, save_to_dir=out_path, save_prefix='', save_format='jpg')
for i in range(beishou):
gener.next()
这样增广后的图像可以直接可见,下图是增广后的部分图像
然鹅鉴于博主技术水平有限,这样的数据增广方式仍然存在许多局限性,譬如在增广时,只能将增广后的不同种类图像输出到同一个文件夹,这就意味着,生成label文件时,不能用直接引用当前图片的父文件夹名称作为标签。不过好在这个方法数据增广后的图片文件在图片名字前自带标签,算是一定程度上缓解了这个问题。
**新的发现:**直接这样用,不需要label文件!不需要之前的generate函数!只需要train和test两个部分的标签文件夹包含路径,具体操作是:先创建一个ImageDataGenerator的实例并定义好伸缩变化那些参数后,直接分别创建ImageDataGenerator.flow_from_directory的两个实例,就可以直接在model.fit中用,真是太吊拉!代码长度如下:
import os
import numpy as np
import tensorflow as tf
from PIL import Image
from tensorflow.keras.preprocessing.image import ImageDataGenerator
checkpoint_save_path = #检查点的路径
train_path = #类别文件夹父路径
test_path = #同理
model = tf.keras.models.Sequential([
tf.keras.layers.Conv2D(filters=64, kernel_size=(5, 5), padding='same'),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.Activation('relu'),
tf.keras.layers.MaxPool2D(pool_size=(2, 2), strides=2, padding='same'),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.Conv2D(filters=64, kernel_size=(5, 5), padding='same'),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.Activation('relu'),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.MaxPool2D(pool_size=(2, 2), strides=2, padding='same'),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Dense(4, activation='softmax')
])
model.compile(optimizer='adam',
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
metrics=['sparse_categorical_accuracy'])
# reduce_lr = tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss', patience=10, mode='auto')
if os.path.exists(checkpoint_save_path + '.index'):
print('--------------- save model successfully------------------')
model.load_weights(checkpoint_save_path)
cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_save_path,
save_weights_only=True,
save_best_only=True)
train_datagen = ImageDataGenerator(
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True)
# this is the augmentation configuration we will use for testing:
# only rescaling
test_datagen = ImageDataGenerator(rescale=1. / 255)
train_generator = train_datagen.flow_from_directory(
train_path,
target_size=(256, 256),
batch_size=24,
class_mode='binary')
validation_generator = test_datagen.flow_from_directory(
test_path,
target_size=(256, 256),
batch_size=24,
class_mode='binary')
model.fit(train_generator, steps_per_epoch=24, epochs=500, validation_data=validation_generator,
validation_steps=1, callbacks=[cp_callback])
下图是train_path的示例
先扬后抑:然鹅新的方法简单是简单,也存在和最开始方法相同的问题。就是看不见变化后的图像,而且每次模型的训练集和测试集都不一样。这个方法虽然让新手入门网络训练简化了很多步骤,但是有的时候如果只会用这些简化的方法就会有很多限制。之前的第一次的方法虽然代码长度比这个新的发现长了不少,但是里面的generate函数我还可以在其他神经网络结构里面复用,也是很香的。
参考资料: