我们之前用到的用于训练网络的数据集大部份为常用的经典数据集,可以 通过 TensorFlow 几行代码即可完成数据集的下载、加载以及预处理工作,这无疑大大的提升了算 法的研究效率,对于刚入门的新手来说比较友好。然而在实际应用中,针对于不同的应用场景,算法的数据集也各不相同。因此我们就需要自定义数据集,来完场网络的训练。下面我们就要对水泥裂缝图片进行数据集的制作:
def load_liefeng(root, mode='train'):
# 创建数字编码表
name_label = {}
# 遍历根目录下的子文件夹,并排序,保证映射关系固定
for name in sorted(os.listdir(os.path.join(root))):
# 跳过非文件夹
if not os.path.isdir(os.path.join(root, name)):
continue
# 给每个类别编码一个数字
name_label[name] = len(name_label.keys())
...
第二步、创建路径-标签表格
编码表确定后,我们需要根据实际数据祈祷存储方式获得每个样本的存储路径以及他 的标签数字,分别表示为 images 和 labels 2 个 List 对象。其中 images List 存储了每个样本 的路径字符串,labels 存储了样本的类别数字,两者长度一致。 我们将 images 和 labels 信息存储在 csv 格式的文件中,其中 csv 文件格式是一种以逗 号符号分隔数据的纯文本文件格式,可以使用记事本或者 MS Excel 软件打开。通过将所有 样本信息存储在一个 csv 文件中有诸多好处,比如可以直接进行数据集的划分,可以随机 采样 batch 等等。csv 文件中可以保存数据集所有样本的信息,也可以根据 train-val-test 分 别创建 3 个 csv 文件。最终产生的 csv 文件内容如下图所示,每一行的第一个元素保存 了当前样本的存储路径,第二个元素保存了样本的类别数字。
csv 文件创建实现如下,遍历 liefeng根目录下的所有图片,保存图片的路径,并根 据编码表获得其编码数字,保存到 csv 文件中:
def load_csv(root, filename, name_label):
# 从 csv 文件返回 images,labels 列表
# root:数据集根目录,filename:csv 文件名, name_label:类别名编码表
if not os.path.exists(os.path.join(root, filename)):
# 如果 csv 文件不存在,则创建
images = []
for name in name_label.keys(): # 遍历所有子目录,获得所有的图片
images += glob.glob(os.path.join(root, name, '*.jpg'))
print(len(images), images)
random.shuffle(images) # 随机打散顺序
# 创建 csv 文件,并存储图片路径及其 label 信息
with open(os.path.join(root, filename), mode='w', newline='') as f:
writer = csv.writer(f)
for img in images:
name = img.split(os.sep)[-2]
label = name_label[name]
writer.writerow([img, label])
print('written into csv file:', filename)
...
创建完 csv 文件后,下一次只需要从 csv 文件中读取样本路径和标签信息即可:
def load_csv(root, filename, name2label):
…
# 此时已经有 csv 文件,直接读取
images, labels = [], []
with open(os.path.join(root, filename)) as f:
reader = csv.reader(f)
for row in reader:
img, label = row
label = int(label)
images.append(img)
labels.append(label)
# 返回图片路径 list 和标签 list
return images,labels
第三步、数据集划分
数据集的划分需要根据实际情况来调整划分比率,当数据集样本数较多时,可以选择 80%-10%-10%的比例分配给训练集、验证集和测试集;当样本数量较少时,如这里的裂缝数据集图片总数仅 1000 张左右,如果验证集和测试集比例只有 10%,则图片数量约为 100 张,因此验证准确率和测试准确率可能波动较大。对于小型的数据集,尽管样本数量 较小,但是验证集和测试集比例需要适当增加,以保证获得准确的测试结果。这里我们将 验证集和测试集比例均设置为 20%,即有约 200 张图片用作验证和测试。 实现如下,调用 load_csv 函数加载 images 和 labels 列表,根据当前模式 mode 加载对 应部分的图片和标签。具体地,如果模式为 train,则分别取 images 和 labels 的前 60%数据 作为训练集;如果模式为 val,则分别取 images 和 labels 的 60%到 80%区域数据作为验证 集;如果模式为 test,则分别取 images 和 labels 的后 20%作为测试集。
def load_pokemon(root, mode='train'):
…
# 读取 Label 信息
# [file1,file2,], [3,1]
images, labels = load_csv(root, 'images.csv', name2label)
# 数据集划分
if mode == 'train': # 60%
images = images[:int(0.6 * len(images))]
labels = labels[:int(0.6 * len(labels))]
elif mode == 'val': # 20% = 60%->80%
images = images[int(0.6 * len(images)):int(0.8 * len(images))]
labels = labels[int(0.6 * len(labels)):int(0.8 * len(labels))]
else: # 20% = 80%->100%
images = images[int(0.8 * len(images)):]
labels = labels[int(0.8 * len(labels)):]
return images, labels, name_label
整体代码如下:
import os, glob
import random, csv
import tensorflow as tf
def load_csv(root, filename, name2label):
if not os.path.exists(os.path.join(root, filename)):
images = []
for name in name2label.keys():
images += glob.glob(os.path.join(root, name, '*.png'))
images += glob.glob(os.path.join(root, name, '*.jpg'))
images += glob.glob(os.path.join(root, name, '*.jpeg'))
print(len(images), images)
random.shuffle(images)
with open(os.path.join(root, filename), mode='w', newline='') as f:
writer = csv.writer(f)
for img in images:
name = img.split(os.sep)[-2]
label = name2label[name]
writer.writerow([img, label])
print('written into csv file:', filename)
images, labels = [], []
with open(os.path.join(root, filename)) as f:
reader = csv.reader(f)
for row in reader:
img, label = row
label = int(label)
images.append(img)
labels.append(label)
return images, labels
def load_pokemon(root, mode='train'):
name2label = {}
for name in sorted(os.listdir(os.path.join(root))):
if not os.path.isdir(os.path.join(root, name)):
continue
name2label[name] = len(name2label.keys())
images, labels = load_csv(root, 'images.csv', name2label)
if mode == 'train':
images = images[:int(0.6 * len(images))]
labels = labels[:int(0.6 * len(labels))]
elif mode == 'val':
images = images[int(0.6 * len(images)):int(0.8 * len(images))]
labels = labels[int(0.6 * len(labels)):int(0.8 * len(labels))]
else:
images = images[int(0.8 * len(images)):]
labels = labels[int(0.8 * len(labels)):]
return images, labels, name2label
img_mean = tf.constant([0.485, 0.456, 0.406])
img_std = tf.constant([0.229, 0.224, 0.225])
def normalize(x, mean=img_mean, std=img_std):
x = (x - mean)/std
return x
def denormalize(x, mean=img_mean, std=img_std):
x = x * std + mean
return x
def preprocess(x,y):
x = tf.io.read_file(x)
x = tf.image.decode_jpeg(x, channels=3)
x = tf.image.resize(x, [244, 244])
x = tf.image.random_flip_up_down(x)
x= tf.image.random_flip_left_right(x)
x = tf.image.random_crop(x, [224, 224, 3])
x = tf.cast(x, dtype=tf.float32) / 255.
x = normalize(x)
y = tf.convert_to_tensor(y)
return x, y
def main():
images, labels, table = load_pokemon('E:\data\pokemon\\', 'train')#这儿的路径就是你自己电脑当中存储图片的实际路径
print('images:', len(images), images)
print('labels:', len(labels), labels)
print('table:', table)
db = tf.data.Dataset.from_tensor_slices((images, labels))
db = db.shuffle(1000).map(preprocess).batch(32)
if __name__ == '__main__':
main()
import matplotlib
from matplotlib import pyplot as plt
matplotlib.rcParams['font.size'] = 18
matplotlib.rcParams['figure.titlesize'] = 18
matplotlib.rcParams['figure.figsize'] = [9, 7]
matplotlib.rcParams['font.family'] = ['KaiTi']
matplotlib.rcParams['axes.unicode_minus']=False
import os
import tensorflow as tf
import numpy as np
from tensorflow import keras
from tensorflow.keras import layers,optimizers,losses
from tensorflow.keras.callbacks import EarlyStopping #注意调用位置
from pokemon import load_pokemon,normalize #Pokemon为自定义模块
tf.random.set_seed(1234) #种子数设置为1234个
np.random.seed(1234)
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' #日志中只显示警告和错误
assert tf.__version__.startswith('2.') #判断版本是否为2.开头的,若不是,assert返回false
def preprocess(x,y):
# x: 图片的路径,y:图片的数字编码
x = tf.io.read_file(x)
x = tf.image.decode_jpeg(x, channels=3) # RGBA
x = tf.image.resize(x, [244, 244])
x = tf.image.random_flip_left_right(x) #图片增强之左右镜像
x = tf.image.random_flip_up_down(x) #图像增强之上写翻转
x = tf.image.random_crop(x, [224,224,3])#图像增强之随机裁剪
# x: [0,255]=> -1~1
x = tf.cast(x, dtype=tf.float32) / 255.
x = normalize(x)
y = tf.convert_to_tensor(y)
y = tf.one_hot(y, depth=5)
return x, y
batchsz = 32
# 创建训练集Datset对象
images, labels, table = load_pokemon('E:\data\pokemon\\',mode='train')
db_train = tf.data.Dataset.from_tensor_slices((images, labels))
db_train = db_train.shuffle(1000).map(preprocess).batch(batchsz)
# 创建验证集Datset对象
images2, labels2, table = load_pokemon('E:\data\pokemon\\',mode='val')
db_val = tf.data.Dataset.from_tensor_slices((images2, labels2))
db_val = db_val.map(preprocess).batch(batchsz)
# 创建测试集Datset对象
images3, labels3, table = load_pokemon('E:\data\pokemon\\',mode='test')
db_test = tf.data.Dataset.from_tensor_slices((images3, labels3))
db_test = db_test.map(preprocess).batch(batchsz)
# 用到了迁移学习的知识点,直接对现有的Desnet121网络进行迁移训练加载DenseNet网络模型,并去掉最后一层全连接层,最后一个池化层设置为max pooling
net = keras.applications.DenseNet121(include_top=False, pooling='max')
net.trainable = True
newnet = keras.Sequential([
net, # 去掉最后一层的DenseNet121
layers.Dense(1024, activation='relu'), # 追加全连接层
layers.BatchNormalization(), # 追加BN层
layers.Dropout(rate=0.5), # 追加Dropout层,防止过拟合
layers.Dense(5) # 根据宝可梦数据的任务,设置最后一层输出节点数为5
])
newnet.build(input_shape=(4,224,224,3))
newnet.summary()
newnet.compile(optimizer=tf.optimizers.Adam(1e-3),
loss=tf.losses.categorical_crossentropy,
metrics=['accuracy'])
history = newnet.fit(db_train, validation_data=db_val, validation_freq=1,
epochs=10)
history = history.history
print(history.keys())
print(history['val_accuracy'])
print(history['accuracy'])
test_acc = newnet.evaluate(db_test)
plt.figure()
returns = history['val_accuracy']
plt.plot(np.arange(len(returns)), returns, label='val_accuracy')
plt.plot(np.arange(len(returns)), returns, 's')
returns = history['accuracy']
plt.plot(np.arange(len(returns)), returns, label='train_accuracy')
plt.plot(np.arange(len(returns)), returns, 's')
plt.plot([len(returns)-1],[test_acc[-1]], 'D', label='test_accuracy')
plt.legend()
plt.xlabel('Epoch')
plt.ylabel('accuracy')
plt.savefig('scratch.svg')
上面的训练结果不是裂缝图片的训练结果,是另一种数据集的训练结果,处于保密,就不详细介绍该数据集。裂缝图片的准确率最后能达到98%,大家可以看见上面的图,主要是图片数据集做的好,图片清晰,特征明显。但往往有些时候数据集并不好制作,数据集做不好,训练结果也就有待提高。由上训练结果可见一斑。好了,本文就此结束。不知不觉,两个小时已经过去了。最后谢谢大家的阅读,也希望为本文为大家提功一点参考作用。喜欢的点个赞,收藏一下,不然划着划着就找不到了