机器学习中,通常拿到的数据并不能直接使用,需要进行预处理,比如剔除部分特征、去除脏数据、数据归一化、独热编码等,也就是特征工程。我们不希望每次加载程序的时候都需要进行前面的预处理,因此可以把预处理之后的数据保存起来,这里可以用pickle模块。这有点类似电脑游戏中的进度保存。
下面以 notMNIST 数据集为例,介绍如何进行数据的预处理。
第一步,先把需要的模块导入。
import hashlib
import os
import pickle
from urllib.request import urlretrieve
import numpy as np
from PIL import Image
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelBinarizer
from sklearn.utils import resample
from tqdm import tqdm
from zipfile import ZipFile
print('All modules imported.')
hashlib
:用来提供常见的摘要算法,比如MD5、SHA1等。摘要算法又称哈希算法、散列算法。它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。摘要算法不是加密算法,只能用于防篡改,也可以在不存储明文口令的情况下验证用户口令。(参考 hashlib)
os
:包含普遍的操作系统功能,主要是对目录和文件进行访问,可以方便移植到不同的平台。
pickle
:序列化模块,用来把内存中的数据变成可存储和传输的数据。
urllib.request
:urllib
是用来处理网址的模块,其中urllib.request
是为打开url提供的可扩展类库。(参考 Python中的urllib.request模块)
PIL
:是python上的标准图像处理库,Python Imaging Library
。(参考 PIL)
tqdm
:用来显示进度条的,简单而且直观。(参考 python tqdm模块分析 、tqdm)
zipfile
:是用来做zip格式编码的压缩和解压缩的。(参考 zipfile)
将模型上传到云主机上计算的时候通常是先将数据集上传到网盘,再从网盘直接下载到云主机上,这样比采用本机上传要快很多。
数据格式采用“.zip”格式。
def download(url, file):
"""
从处下载数据
:参数 url: 文件的URL
:参数 file: 本地文件的路径
"""
if not os.path.isfile(file):
print('Downloading ' + file + '...')
urlretrieve(url, file)
print('Download Finished')
# 分别下载训练集和测试集.
download('https://s3.amazonaws.com/udacity-sdc/notMNIST_train.zip', 'notMNIST_train.zip')
download('https://s3.amazonaws.com/udacity-sdc/notMNIST_test.zip', 'notMNIST_test.zip')
# 校验MD5值,确保下载的数据是完整的
assert hashlib.md5(open('notMNIST_train.zip', 'rb').read()).hexdigest() == 'c8673b3f28f489e9cdf3a3d74e2ac8fa',\
'notMNIST_train.zip file is corrupted. Remove the file and try again.'
assert hashlib.md5(open('notMNIST_test.zip', 'rb').read()).hexdigest() == '5d3c7e653e63471c88df796156a9dfa9',\
'notMNIST_test.zip file is corrupted. Remove the file and try again.'
# 直到所有文件下载完,给出下列提示.
print('All files downloaded.')
自定义一个解压函数,从输入的zip文件中解压出features和labels,存储为numpy的数组格式。然后重采样出部分数据,方便后面快速测试模型。
def uncompress_features_labels(file):
"""
从zip文件中解压出features和labels
:参数 file: 待解压文件
"""
features = []
labels = []
with ZipFile(file) as zipf:
# 进度条
filenames_pbar = tqdm(zipf.namelist(), unit='files')
# 从所有文件中提取features和labels
for filename in filenames_pbar:
# 检查文件名是否为目录,不是则往下执行
if not filename.endswith('/'): #str.endswith()
#ZipFile.open()以二进制的形式访问文件
with zipf.open(filename) as image_file:
image = Image.open(image_file)
image.load()
# 将图像数据以一维矩阵形式存储
# 存储格式设定为 float32
feature = np.array(image, dtype=np.float32).flatten()
# 提取文件名的首位,存储为对应的label。
# os.path.split()分割为“路径+文件名”,文件名中无'/'
label = os.path.split(filename)[1][0]
features.append(feature)
labels.append(label)
return np.array(features), np.array(labels)
# 从zip文件中提取出train和test数据集中的features和labels
train_features, train_labels = uncompress_features_labels('notMNIST_train.zip')
test_features, test_labels = uncompress_features_labels('notMNIST_test.zip')
# 重新采样,数据大小限制为docker_size_limit
docker_size_limit = 150000
train_features, train_labels = resample(train_features, train_labels, n_samples=docker_size_limit)
# 为特征工程设置标记,防止跳过重要步骤
is_features_normal = False
is_labels_encod = False
# 直到所有features和labels被解压,打印以下内容
print('All features and labels uncompressed.')
这里只进行features的归一化和labels的二值化(独热编码)。
对训练集和测试集的features进行归一化处理,这里用最小-最大规范化(Min-Max scaling)。
Min-Max Scaling:
X′=a+(X−Xmin)(b−a)Xmax−Xmin
# 对于灰度图像数据的归一化
def normalize_grayscale(image_data):
"""
将输入的最小最大值缩放到[0.1, 0.9]
:参数 image_data: 待归一化的图像数据
:return: 归一化处理后的数据
"""
a = 0.1
b = 0.9
grayscale_min = 0
grayscale_max = 255
return a + ( ( (image_data - grayscale_min)*(b - a) )/( grayscale_max - grayscale_min ) )
# 如果标记为False,则归一化处理,并置标记为True
if not is_features_normal:
train_features = normalize_grayscale(train_features)
test_features = normalize_grayscale(test_features)
is_features_normal = True
用sklearn.preprocessing
中的LabelBinarizer()
对标签A~F
进行二值化处理,也即是独热编码(One-Hot Encoding
) 。
if not is_labels_encod:
# 应用独热编码,将labels转化成数字(0/1表示)
encoder = LabelBinarizer()
encoder.fit(train_labels)
train_labels = encoder.transform(train_labels)
test_labels = encoder.transform(test_labels)
# 转化为float32的格式,便于后面在TensorFlow可以进行乘法运算
train_labels = train_labels.astype(np.float32)
test_labels = test_labels.astype(np.float32)
is_labels_encod = True
首先检验特征工程是否完成,然后使用sklearn.model_selection
中的 train_test_split
将训练集随机划分为训练集和验证集。
assert is_features_normal, 'You skipped the step to normalize the features'
assert is_labels_encod, 'You skipped the step to One-Hot Encode the labels'
# 为训练集和验证集随机选取数据
train_features, valid_features, train_labels, valid_labels = train_test_split(
train_features,
train_labels,
test_size=0.05,
random_state=832289)
用pickle模块将处理好的数据存储成pickle格式,方便以后调用,即建立一个checkpoint。
# 保存数据方便调用
pickle_file = 'notMNIST.pickle'
if not os.path.isfile(pickle_file): #判断是否存在此文件,若无则存储
print('Saving data to pickle file...')
try:
with open('notMNIST.pickle', 'wb') as pfile:
pickle.dump(
{
'train_dataset': train_features,
'train_labels': train_labels,
'valid_dataset': valid_features,
'valid_labels': valid_labels,
'test_dataset': test_features,
'test_labels': test_labels,
},
pfile, pickle.HIGHEST_PROTOCOL)
except Exception as e:
print('Unable to save data to', pickle_file, ':', e)
raise
print('Data cached in pickle file.')
pickle.HIGHEST_PROTOCOL
:代表文件的协议版本,version 0~4,参考protocol version)
pickle.dump(obj, file[, protocol])
:序列化数据,将obj
数据流写入到file
中,file
必须是可写入模式。
下一次加载文件时不用重新处理数据,可以从保存的pickle文件中直接加载,具体方法如下。
%matplotlib inline #直接将绘图显示在当前页面,用于jupyter notebook
# 加载模块
import pickle
import math
import numpy as np
import tensorflow as tf
from tqdm import tqdm
import matplotlib.pyplot as plt
# 读取数据
pickle_file = 'notMNIST.pickle'
with open(pickle_file, 'rb') as f:
pickle_data = pickle.load(f) # 反序列化,与pickle.dump相反
train_features = pickle_data['train_dataset']
train_labels = pickle_data['train_labels']
valid_features = pickle_data['valid_dataset']
valid_labels = pickle_data['valid_labels']
test_features = pickle_data['test_dataset']
test_labels = pickle_data['test_labels']
del pickle_data # 释放内存
print('Data and modules loaded.')