认真学习,佛系更博。
本章将介绍如何实现数据的载入功能,该模块和其他模块较为独立,也最容易实现;因此本章内容只做简单介绍,不做过多说明;
我们拿mnist作为示例,目标是将mnist数据转化为神经网络模型可以处理的数据。首先获取mnist原始图片数据,并保存在本地,获取mnist图片的方法见我另一篇博客:深度学习系列:从mnist数据集中提取mnist图片
拿到图片数据后我们将其放入一个文件夹中,比如我放在项目的dataset目录内,下面有10个子目录,分别对应0-9十种类型的图片,接下来将实现数据处理的功能;
先建立一个子模块我取名为enet,然后在enet下新建子模块,取名为data,然后在data下新建python文件,取名为image_controller.py,此时data下有两个文件:__init__.py和image_controller.py,不要删除__init__.py文件,后面封装库需要用到;
编辑image_controller.py文件,新建一个类:ImageHandler,其初始化需要4个参数:data_dir、gray、use_scale、flatten,其含义见代码注释:
def __init__(self, data_dir, gray=False, use_scale=False, flatten=False):
"""
:param data_dir: 根目录
:param gray: 是否以灰度化格式读入图片
:param use_scale: 是否/255.
:param flatten: 数据结果是否需要拉伸为1维
"""
self.data_dir = data_dir
self.gray = gray
self.use_scale = use_scale
self.flatten = flatten
self.class_dict = dict()
class_dict用于获取每个目录对应的类别,比如有两个子目录,目录名分别为“猫”和“狗”,则class_dict为{0: "猫", 1: "狗"}
然后我们需要定义一个函数获取数据,命名为get_data,获取数据的主要步骤很简单,也很流程化,首先获取子目录,然后对子目录的每一个图片,使用opencv读取图像数据,根据参数确定数据的样式,最后返回。其代码为:
sub_dir_list = [sub_dir for sub_dir in os.listdir(self.data_dir) if
os.path.isdir(os.path.join(self.data_dir, sub_dir))]
sub_dir_list.sort()
train_data = []
train_label = []
# 遍历文件夹
for dir_index, sub_dir in enumerate(sub_dir_list):
for sub_file in os.listdir(os.path.join(self.data_dir, sub_dir)):
if self.gray:
image = cv2.imread(os.path.join(self.data_dir, sub_dir, sub_file), cv2.IMREAD_GRAYSCALE)
else:
image = cv2.imread(os.path.join(self.data_dir, sub_dir, sub_file))
image = np.array(image)
# 灰度模式读取为2维数据,需要添加1维通道信息
if self.gray:
image = np.expand_dims(image, axis=-1)
# 如果使用flatten,则应该拉成向量
if self.flatten:
image = image.flatten()
if self.use_scale:
image = image / 255.
# 插入到结果集中
train_data.append(image)
train_label.append(dir_index)
# train_label = np.eye(len(sub_dir_list))[train_label]
train_label = convert_one_hot(train_label, len(sub_dir_list))
return train_data, train_label
convert_one_hot函数将标签转化为one_hot向量,其代码为:
def convert_one_hot(input_signal, size):
"""
将输入数据转化称one_hot数据
:param input_signal: 输入数据
:param size: 样本类别
:return:
"""
return np.eye(size)[np.asarray(input_signal)]
这里需要了解一下np.eye的用法;
另外还需要获取class_dict的功能,实现为get_class_dict,代码如下:
def get_class_dict(self):
"""
获取类别字典
:return:
"""
self.class_dict.clear()
# 读取数据,去掉缓存文件
sub_dir_list = [sub_dir for sub_dir in os.listdir(self.data_dir) if os.path.isdir(os.path.join(self.data_dir,
sub_dir))]
sub_dir_list.sort()
for index, sub_dir in enumerate(sub_dir_list):
self.class_dict[index] = sub_dir
return self.class_dict
过程很简单,需要注意的是获取子目录的时候要排除非目录文件,因为我们添加了cache功能用于加速数据的读取,可能会生成cache文件,完整的image_handler.py代码如下:
import os
import cv2
import numpy as np
import pickle
from enet.utils.util import train_test_split, convert_one_hot
class ImageHandler(object):
"""
数据集载入控制类,传入参数为目录,并且目录满足以下特征:
1. 目录下所有文件都是两级结构,比如 class1/file1.png
2. 所有文件都为图片类型,可被计算机读取
3. 同一类图片需要放在同一个目录下
在生成数据的同时会解析标签字典,其过程为:
1. 将1级目录排序
2. 按照顺序生成标签,从0开始
3. 记录标签到目录名的字典返回class_dict
"""
def __init__(self, data_dir, gray=False, use_scale=False, flatten=False):
"""
:param data_dir: 根目录
:param gray: 是否以灰度化格式读入图片
:param use_scale: 是否/255.
:param flatten: 数据结果是否需要拉伸为1维
"""
self.data_dir = data_dir
self.gray = gray
self.use_scale = use_scale
self.flatten = flatten
self.class_dict = dict()
def get_data(self, ratio=0.3, read_cache=True):
"""
读取数据
:param ratio: 数据拆分比例
:param read_cache: 是否使用cache读入,若使用,则直接使用缓存数据,上述参数可能无效
:return:
"""
# 这里引入pickle加速读取
if read_cache:
if os.path.exists(os.path.join(self.data_dir, "data_cache.pkl")):
with open(os.path.join(self.data_dir, "data_cache.pkl"), "rb") as reader:
return pickle.load(reader)
train_data, train_label = self.load_data()
result = train_test_split(train_data, train_label, ratio)
# 保存到cache文件中
with open(os.path.join(self.data_dir, "data_cache.pkl"), "wb") as writer:
pickle.dump(result, writer)
return result
def load_data(self):
"""加载图片数据, 返回数据和标签"""
sub_dir_list = [sub_dir for sub_dir in os.listdir(self.data_dir) if
os.path.isdir(os.path.join(self.data_dir, sub_dir))]
sub_dir_list.sort()
train_data = []
train_label = []
# 遍历文件夹
for dir_index, sub_dir in enumerate(sub_dir_list):
for sub_file in os.listdir(os.path.join(self.data_dir, sub_dir)):
if self.gray:
image = cv2.imread(os.path.join(self.data_dir, sub_dir, sub_file), cv2.IMREAD_GRAYSCALE)
else:
image = cv2.imread(os.path.join(self.data_dir, sub_dir, sub_file))
image = np.array(image)
# 灰度模式读取为2维数据,需要添加1维通道信息
if self.gray:
image = np.expand_dims(image, axis=-1)
# 如果使用flatten,则应该拉成向量
if self.flatten:
image = image.flatten()
if self.use_scale:
image = image / 255.
# 插入到结果集中
train_data.append(image)
train_label.append(dir_index)
# train_label = np.eye(len(sub_dir_list))[train_label]
train_label = convert_one_hot(train_label, len(sub_dir_list))
return train_data, train_label
def get_class_dict(self):
"""
获取类别字典
:return:
"""
self.class_dict.clear()
# 读取数据,去掉缓存文件
sub_dir_list = [sub_dir for sub_dir in os.listdir(self.data_dir) if os.path.isdir(os.path.join(self.data_dir,
sub_dir))]
sub_dir_list.sort()
for index, sub_dir in enumerate(sub_dir_list):
self.class_dict[index] = sub_dir
return self.class_dict
如注释所写,该模块对数据目录结构有要求,必须为2级目录,每类图片放在相同的子目录内;
最后修改__init__.py内容:
from enet.data.image_controller import ImageHandler
修改后就可以在外部使用from enet.data import ImageHandler调用该类的所有功能;
到这里,该模块内容已经实现完毕,不过功能较单一,只能实现对图片类数据读取。可以尝试写其他类型数据的读取功能,也可以尝试调用该模块看看返回数据的格式、内容等;
整个项目代码见github:https://github.com/darkwhale/neural_network ;
下一章将详细介绍网络层的实现及原理;