Multi-layer Perceptron即多层感知器,也就是神经网络,要说它的Hello world,莫过于识别手写数字了。如果你已经了解它的原理并尝试过自己写一个后就可以试用下通用的类库,好将来用在生产环境。下面是使用SkLearn中的MLPClassifier识别手写数字,代码是在Python2.7上运行。
首先获取数据集,我是在http://www.iro.umontreal.ca/~lisa/deep/data/mnist/mnist.pkl.gz下载的,也可以直接搜索mnist.pkl.gz来获取。数据集大小可能有区别,但数组中的每一行都表示一个灰度图像。每个元素都表示像素所对应的灰度值。
先来初步查看下数据
from sklearn.neural_network import MLPClassifier
import numpy as np
import pickle
import gzip
import matplotlib.pyplot as plt
# 加载数据
with gzip.open(r"mnist.pkl.gz") as fp:
training_data, valid_data, test_data = pickle.load(fp)
X_training_data, y_training_data = training_data
X_valid_data, y_valid_data = valid_data
X_test_data, y_test_data = test_data
def show_data_struct():
print X_training_data.shape, y_training_data.shape
print X_valid_data.shape, y_valid_data.shape
print X_test_data.shape, y_test_data.shape
print X_training_data[0]
print y_training_data[0]
show_data_struct()
重点注意数据已经归一化,就是把数值映射到0到1之间,这对于mlp是很有必要的。
为什么要把数据分为训练集,测试集?
另外这里把数据分为了三类,分别为训练集,验证集,测试集。大多数时候是分为训练集和测试集。当然如果我们直接收集到的数据没有分开,训练模型时要自己分为训练集和测试集。
将原始数据分开,保证用于测试的数据是训练模型时从未遇到的数据可以使测试更客观。否则就像学习教课书的知识,又只考教课书的知识,就算不理解记下了就能得高分但遇到新问题就傻眼了。
好一点的做法就是用训练集当课本给他上课,先找出把课本知识掌握好的人,再参加由新题组成的月考即测试集,若是还是得分高,那就是真懂不是死记硬背了。
但这样选出来的模型实际是还是用训练集和测试集共同得到的,再进一步,用训练集和验证集反复训练和检测,得到最好的模型,再用测试集来一局定输赢即期末考试,这样选出来的就更好了。
这里就不搞这么复杂了,将training,validation合并,把数据分为训练集和测试集。
接下来先看下这些手写数字长什么样。
from sklearn.neural_network import MLPClassifier
import numpy as np
import pickle
import gzip
import matplotlib.pyplot as plt
# 加载数据
with gzip.open(r"mnist.pkl.gz") as fp:
training_data, valid_data, test_data = pickle.load(fp)
X_training_data, y_training_data = training_data
X_valid_data, y_valid_data = valid_data
X_test_data, y_test_data = test_data
# 合并训练集,验证集
X_training = np.vstack((X_training_data, X_valid_data))
y_training = np.append(y_training_data, y_valid_data)
def show_image():
plt.figure(1)
for i in range(10):
image = X_training[i]
pixels = image.reshape((28, 28))
plt.subplot(5,2,i+1)
plt.imshow(pixels, cmap='gray')
plt.title(y_training[i])
plt.axis('off')
plt.subplots_adjust(top=0.92, bottom=0.08, left=0.10, right=0.95, hspace=0.45,
wspace=0.85)
plt.show()
show_image()
Figure_1.png
这是测试集中的前10个数字,一眼就可以看出写的是什么。注意在上面的代码有一行是把图片对应的一行数据转为28 *28的二维数组,这个不转不行啊!把任何一个图片每行首尾相连,看一个1个像素高768个像素宽的图片,那可没办法知道写的是什么。那么问题来了,我们人看到的是一个二维的图片,而给模型输入的是个一维数组,丢失了原始图片的长宽比例信息,这样它还能识别出来吗?
呃呃呃,哦----,哦-----。当我说这些时你知道我想表示不知道,对不对?我们约定好一分钟内前30秒滴滴滴后30秒哒哒哒表示1,前20秒哒哒哒后40秒滴滴滴表示2,这样用一连串声音信号就能表示数字信息了。同样将图片从上到下从左到右逐个像素填到一个长度为768的向量,变成一个像素高度的图片,从左到右看过去就是一会儿白一会儿黑,就像刚才说的声音一样,它也能表示不同的数字。所以模型识别的并不是原始的图片而是转换后的一个向量。
现在就来看看效果
from sklearn.neural_network import MLPClassifier
import numpy as np
import pickle
import gzip
import matplotlib.pyplot as plt
# 加载数据
with gzip.open(r"mnist.pkl.gz") as fp:
training_data, valid_data, test_data = pickle.load(fp)
X_training_data, y_training_data = training_data
X_valid_data, y_valid_data = valid_data
X_test_data, y_test_data = test_data
# 合并训练集,验证集
X_training = np.vstack((X_training_data, X_valid_data))
y_training = np.append(y_training_data, y_valid_data)
def train():
mlp = MLPClassifier()
# 查看默认超参
print mlp
# 训练模型
mlp.fit(X_training, y_training)
print(mlp.score(X_test_data, y_test_data))
train()
我测试的分数是0.9807,还不低是不是?现在全用默认参数,如果再优化下,还能不能再提高些呢?下面用gridSearchCV来试下。
from sklearn.neural_network import MLPClassifier
from sklearn.model_selection import GridSearchCV
import numpy as np
import pickle
import gzip
import matplotlib.pyplot as plt
if __name__ == '__main__':
# 加载数据
with gzip.open(r"mnist.pkl.gz") as fp:
training_data, valid_data, test_data = pickle.load(fp)
X_training_data, y_training_data = training_data
X_valid_data, y_valid_data = valid_data
X_test_data, y_test_data = test_data
# 合并训练集,验证集
X_training = np.vstack((X_training_data, X_valid_data))
y_training = np.append(y_training_data, y_valid_data)
mlp_clf__tuned_parameters = {"hidden_layer_sizes": [(100,), (100, 30)],
"solver": ['adam', 'sgd', 'lbfgs'],
"max_iter": [20],
"verbose": [True]
}
mlp = MLPClassifier()
estimator = GridSearchCV(mlp, mlp_clf__tuned_parameters, n_jobs=6)
estimator.fit(X_training, y_training)
print estimator.get_params().keys()
print estimator.best_params_
这里的max_iter是想让它快些结束才设置为20默认是200,这样得到的最优参数自然不是真正的最优参数。这里虽然只调两个参数,排列组合得到的6种情况并行计算,还是很卡很慢。实际调参要先明白各参数的含义,预先猜测最好的值去测试,还要借助其它方法才能快速有效的调参。