作者:欧新宇(Xinyu OU)
本文档所展示的测试结果,均运行于:Intel Core i7-7700K CPU 4.2GHz, nVidia GeForce GTX 1080 Ti 本教案所涉及的数据集仅用于教学和交流使用,请勿用作商用。
最后更新:2021年3月6日
数据准备是深度学习训练、测试等工作的前提,它是保证深度学习有效的必要工作。特别是对于自建数据集
来说,数据清洗,数据分类和数据列表的生成都是必备可少的工作。数据准备是一项工程性
非常强的工作,每一个数据集都需要针对性地撰写代码。
为了简化工作,本课程涉及的所有数据集都做好了前期的清洗和分类工作,只需要按照分类任务的特点生成数据列表即可。下面以两个不同的数据集为例,进行数据集分割,并生成样本列表。具体包括:手势识别数据集、十二生肖分类数据集。所有的数据集都按照工程习惯生成四个数据集,具体包括:
训练集
和验证集
混合获得,用于在模型训练完毕后的二次训练【特别提示】:
训练集
和验证集
;并进行模型训练,在完成超参数选择后,再将训练集
和验证集
合并在一起后,进行统一训练。没有标注
的,并且测试集也不应该被用来参与模型参数的选择,只能在模型训练好,进行一次性的结果输出或性能评估。蝴蝶分类数据集包含7个类,619张图片。所有数据都放在Data
文件夹,并按照7个类别分别存入子文件夹;但该数据集没有官方的数据分割建议,因此需要手动进行分割。数据列表生成时将按照7:1:2的比例进行分割。
##################################################################################
# 数据集预处理
# 作者: Xinyu Ou (http://ouxinyu.cn)
# 数据集名称:蝴蝶数据集Butterflies
# 数据集简介: 数据集样本存储在Data文件夹,样本总数619,包含7个类.
# 本程序功能:
# 1. 将数据集按照7:1:2的比例划分为训练集、验证集、测试集
# 2. 代码将生成4个文件:训练集列表train.txt, 验证集列表val.txt, 测试集列表test.txt, 数据集信息dataset_info.json
# 3. 代码输出信息:图像列表已生成, 其中训练集样本423个, 验证集样本67个, 测试集样本129个, 共计619个。
# 最后更新:2021年3月20日
###################################################################################
import os
import json
import codecs
# 初始化参数
num_trainval = 0
num_train = 0
num_val = 0
num_test = 0
class_dim = 0
dataset_info = {
'dataset_name': '',
'num_trainval': -1,
'num_train': -1,
'num_val': -1,
'num_test': -1,
'class_dim': -1,
'label_dict': {}
}
# 本地运行时,需要修改数据集的名称和绝对路径,注意和文件夹名称一致
dataset_name = 'Butterfly'
dataset_path = 'D:\\Workspace\\ExpDatasets\\'
dataset_root_path = os.path.join(dataset_path, dataset_name)
excluded_folder = ['.DS_Store'] # 被排除的文件夹
# 定义生成文件的路径
data_path = os.path.join(dataset_root_path, 'data')
trainval_list = os.path.join(dataset_root_path, 'trainval.txt')
train_list = os.path.join(dataset_root_path, 'train.txt')
val_list = os.path.join(dataset_root_path, 'val.txt')
test_list = os.path.join(dataset_root_path, 'test.txt')
dataset_info_list = os.path.join(dataset_root_path, 'dataset_info.json')
# 检测数据集列表是否存在,如果存在则先删除。其中测试集列表是一次写入,因此可以通过'w'参数进行覆盖写入,而不用进行手动删除。
if os.path.exists(trainval_list):
os.remove(trainval_list)
if os.path.exists(train_list):
os.remove(train_list)
if os.path.exists(val_list):
os.remove(val_list)
if os.path.exists(test_list):
os.remove(test_list)
# 按照比例进行数据分割
class_name_list = os.listdir(data_path)
with codecs.open(trainval_list, 'a', 'utf-8') as f_trainval:
with codecs.open(train_list, 'a', 'utf-8') as f_train:
with codecs.open(val_list, 'a', 'utf-8') as f_val:
with codecs.open(test_list, 'a', 'utf-8') as f_test:
for class_name in class_name_list:
if class_name not in excluded_folder:
dataset_info['label_dict'][str(class_dim)] = class_name
images = os.listdir(os.path.join(data_path, class_name))
count = 0
for image in images:
if count % 10 == 0: # 抽取大约10%的样本作为验证数据
f_val.write("{0}\t{1}\n".format(os.path.join(class_name, image), class_dim))
f_trainval.write("{0}\t{1}\n".format(os.path.join(class_name, image), class_dim))
num_val += 1
num_trainval += 1
elif count % 10 == 1 or count % 10 == 2: # 抽取大约20%的样本作为测试数据
f_test.write("{0}\t{1}\n".format(os.path.join(class_name, image), class_dim))
num_test += 1
else:
f_train.write("{0}\t{1}\n".format(os.path.join(class_name, image), class_dim))
f_trainval.write("{0}\t{1}\n".format(os.path.join(class_name, image), class_dim))
num_train += 1
num_trainval += 1
count += 1
class_dim += 1
# 将数据集信息保存到json文件中供训练时使用
dataset_info['dataset_name'] = dataset_name
dataset_info['num_trainval'] = num_trainval
dataset_info['num_train'] = num_train
dataset_info['num_val'] = num_val
dataset_info['num_test'] = num_test
dataset_info['class_dim'] = class_dim
# 输出数据集信息json和统计情况
with codecs.open(dataset_info_list, 'w', encoding='utf-8') as f_dataset_info:
json.dump(dataset_info, f_dataset_info, ensure_ascii=False, indent=4, separators=(',', ':')) # 格式化字典格式的参数列表
print("图像列表已生成, 其中训练验证集样本{},训练集样本{}个, 验证集样本{}个, 测试集样本{}个, 共计{}个。".format(num_trainval, num_train, num_val, num_test, num_train+num_val+num_test))
图像列表已生成, 其中训练验证集样本490,训练集样本423个, 验证集样本67个, 测试集样本129个, 共计619个。
十二生肖分类数据集包含12个类,8508张图片。数据集已经事先实现了训练(train)、验证(valid)和测试(test)的分割。因此,在进行数据列表生成的时候,不需要手动进行分割,但要注意确保三种类型数据标签和类别的统一。在范例代码中,我们使用训练集生成标签列表,并将标签列表应用到验证集和测试集列表的生成过程中。
##################################################################################
# 数据集预处理
# 作者: Xinyu Ou (http://ouxinyu.cn)
# 数据集名称:十二生肖数据集Zodiac
# 数据集简介: 数据集包含12个类别,其中训练集样本7198个, 验证集样本650个, 测试集样本660个, 共计8508个。
# 本程序功能:
# 1. 将数据集按照7:1:2的比例划分为训练集、验证集、测试集
# 2. 代码将生成4个列表文件:训练集列表train.txt, 验证集列表val.txt, 测试集列表test.txt, 训练验证集trainval.txt
# 3. 数据集基本信息:数据集的基本信息使用json格式进行输出,包括数据库名称、数据样本的数量、类别数以及类别标签。
###################################################################################
import os
import cv2
import json
import codecs
# 初始化参数
num_trainval = 0
num_train = 0
num_val = 0
num_test = 0
class_dim = 0
dataset_info = {
'dataset_name': '',
'num_trainval': -1,
'num_train': -1,
'num_val': -1,
'num_test': -1,
'class_dim': -1,
'label_dict': {}
}
# 本地运行时,需要修改数据集的名称和绝对路径,注意和文件夹名称一致
dataset_name = 'Zodiac'
dataset_path = 'D:\\Workspace\\ExpDatasets\\'
dataset_root_path = os.path.join(dataset_path, dataset_name)
class_prefix = ['train', 'valid', 'test']
# 定义生成文件的路径
# data_path = os.path.join(dataset_root_path,=)
trainval_list = os.path.join(dataset_root_path, 'trainval.txt')
train_list = os.path.join(dataset_root_path, 'train.txt')
val_list = os.path.join(dataset_root_path, 'val.txt')
test_list = os.path.join(dataset_root_path, 'test.txt')
dataset_info_list = os.path.join(dataset_root_path, 'dataset_info.json')
# 读取数据清洗获得的坏样本列表
bad_list = os.path.join(dataset_root_path, 'bad.txt')
with codecs.open(bad_list, 'r', 'utf-8') as f_bad:
bad_file = f_bad.read().splitlines()
# 检测数据集列表是否存在,如果存在则先删除。其中测试集列表是一次写入,因此可以通过'w'参数进行覆盖写入,而不用进行手动删除。
if os.path.exists(trainval_list):
os.remove(trainval_list)
if os.path.exists(train_list):
os.remove(train_list)
if os.path.exists(val_list):
os.remove(val_list)
if os.path.exists(test_list):
os.remove(test_list)
class_name_list = os.listdir(os.path.join(dataset_root_path, 'train'))
with codecs.open(trainval_list, 'a', 'utf-8') as f_trainval:
with codecs.open(train_list, 'a', 'utf-8') as f_train:
with codecs.open(val_list, 'a', 'utf-8') as f_val:
with codecs.open(test_list, 'a', 'utf-8') as f_test:
for prefix in class_prefix:
class_name_dir = os.listdir(os.path.join(dataset_root_path, prefix))
for i in range(len(class_name_list)):
class_name = class_name_list[i]
dataset_info['label_dict'][i] = class_name_list[i]
images = os.listdir(os.path.join(dataset_root_path, prefix, class_name))
for image in images:
if os.path.join(prefix, class_name, image) not in bad_file: # 判断文件是否是坏样本
if prefix == 'train':
f_train.write("{}\t{}\n".format(os.path.join(prefix, class_name, image), str(i)))
f_trainval.write("{}\t{}\n".format(os.path.join(prefix, class_name, image), str(i)))
num_train += 1
num_trainval += 1
elif prefix == 'valid':
f_val.write("{}\t{}\n".format(os.path.join(prefix, class_name, image), str(i)))
f_trainval.write("{}\t{}\n".format(os.path.join(prefix, class_name, image), str(i)))
num_val += 1
num_trainval += 1
elif prefix == 'test':
f_test.write("{}\t{}\n".format(os.path.join(prefix, class_name, image), str(i)))
num_test += 1
# 将数据集信息保存到json文件中供训练时使用
dataset_info['dataset_name'] = dataset_name
dataset_info['num_trainval'] = num_trainval
dataset_info['num_train'] = num_train
dataset_info['num_val'] = num_val
dataset_info['num_test'] = num_test
dataset_info['class_dim'] = len(class_name_list)
# 输出数据集信息json和统计情况
with codecs.open(dataset_info_list, 'w', encoding='utf-8') as f_dataset_info:
json.dump(dataset_info, f_dataset_info, ensure_ascii=False, indent=4, separators=(',', ':')) # 格式化字典格式的参数列表
print("图像列表已生成, 其中训练验证集样本{},训练集样本{}个, 验证集样本{}个, 测试集样本{}个, 共计{}个。".format(num_trainval, num_train, num_val, num_test, num_train+num_val+num_test))
图像列表已生成, 其中训练验证集样本7840,训练集样本7190个, 验证集样本650个, 测试集样本660个, 共计8500个。
数据清洗一般有两种方法:
下面的代码,将做两种设置:
##################################################################################
# 数据清洗
# 作者: Xinyu Ou (http://ouxinyu.cn)
# 数据集名称:十二生肖数据集Zodiac
# 本程序功能:
# 1. 删除MacOS自动生成的文件'.DS_Store'
# 2. 对图像坏样本,进行索引,并保存到bad.txt中
###################################################################################
import os
import cv2
import codecs
# 本地运行时,需要修改数据集的名称和绝对路径,注意和文件夹名称一致
dataset_name = 'Zodiac'
dataset_path = 'D:\\Workspace\\ExpDatasets\\'
dataset_root_path = os.path.join(dataset_path, dataset_name)
excluded = ['.DS_Store'] # 被排除的文件
class_prefix = ['train', 'valid', 'test']
num_bad = 0
num_good = 0
num_folder = 0
# 检测数据集列表是否存在,如果存在则先删除。其中测试集列表是一次写入,因此可以通过'w'参数进行覆盖写入,而不用进行手动删除。
bad_list = os.path.join(dataset_root_path, 'bad.txt')
if os.path.exists(bad_list):
os.remove(bad_list)
# 执行数据清洗
with codecs.open(bad_list, 'a', 'utf-8') as f_bad:
for prefix in class_prefix:
class_name_list = os.listdir(os.path.join(dataset_root_path, prefix))
for class_name in class_name_list:
if class_name in excluded:
os.remove(os.path.join(dataset_root_path, prefix, class_name))
else:
images = os.listdir(os.path.join(dataset_root_path, prefix, class_name))
for image in images:
if image in excluded:
os.remove(os.path.join(dataset_root_path, prefix, class_name, image))
else:
img_path = os.path.join(dataset_root_path, prefix, class_name, image)
try:
img = cv2.imread(img_path, 1)
x = img.shape
num_good += 1
pass
except:
bad_file = os.path.join(prefix, class_name, image)
f_bad.write("{}\n".format(bad_file))
num_bad += 1
num_folder += 1
print('\r 当前清洗进度:{}/{}'.format(num_folder, 3*len(class_name_list)), end='')
print('数据集清洗完成, 损坏文件{}个, 正常文件{}.'.format(num_bad, num_good))