我教宝宝学AI (五)挖坑中成长
一、不断挖坑
学习的目标在哪里,如何看到技术未来的发展方向并选择?随着对新技术的深入理解,会根据心里的标准去评判每一个已经做出的选择,并警醒下一个新的选择。在教的过程中,女儿说我是在不断地“挖坑”,我要说形容的很贴切。暑假时间已经过去差不多一个月,在教学内容选择上我形成了一些思路:
1)开源是未来的方向,也是极好的资源,学会使用开源社区大众化的工具如git是必须的;
2)由于女儿的数学知识未达到一定程度,无法掌握深度学习的所有数学原理,但是要求她去理解深度学习的一些基本算法和整体的思路。通过keras做一个识别手写数字的简单卷积神经网络模型(LeNet)加深认识AI,backend采用tensorflow;
3)苹果操作系统是认证的UNIX,与开源技术自然结合,抛弃用Visual Studio学习的想法,而是改为学习在Xcode上用swift语言编程。学习苹果新推出的Core ML,以苹果app的模式在iPad Pro上开发一个识别手写数字的程序。另外ARKit也是苹果在iOS11上新推出的好东西,只可惜这次没有足够的时间去琢磨。但是我想只要教会了她学习方法,在有空的时候完全可以自己去琢磨。
编程语言从本质上说都差不多,Borland公司在1995年推出Delphi 1.0,曾经轰动全世界,因为是第一套真正面向对象的所见即所得快速开发工具。把Object Pascal和苹果的swift比较后,会发现两个语言很像:变量申明方式、类的引用等,但是由于苹果具有比Borland无法比拟的优势,就是iOS和MacOS操作系统是自家的产品,因此在swift上独特增加一些特性,使得完成的程序可以非常贴切地和苹果操作系统融合在一起,可以说swift就是为苹果操作系统专门设计的开发工具。在教女儿的同时,我也感觉自己在同步成长,在深入了解swift语言后,心里开始纠结以后是不是客户端也要完全脱离windows搬到MacOS上呢?
在这里要特别宣传下Stanford的CS193P课程:Developing Apps for iOS。白胡子教授Paul Hegarty的教学深入浅出,教会学生的每一个小技巧,课程内容非常棒。总共17课,和女儿一起学习到了第9课。Paul教授的经典手势:
二、卷积神经网络模型(CNN)
卷积神经网络模型的英文名称叫Convolutional Neural Network。
1)什么是卷积操作?
下面的动画图片形象的说明了卷积操作过程。如下图原始的绿色图像size为5*5,卷积的kernel是黄色的3*3矩阵[(1,0,1),(0,1,0),(1,0,1)],通过每次移动黄色kernel一格,也就是step为1,每次使用绿色相应像素值和矩阵相应位数相乘之和能获得一个数值,按顺序放在右边粉色图像中,则形成粉色图像就是经过卷积操作后的结果,也叫feature map。
通过filter取值的不同,经过卷积操作可以提取原图片的指定特征,形成新的feature map。
2)什么是pooling操作?
如果输入图片size为28*28,经过kernel size为3*3、step为1的卷积操作后的feature map size为26*26。为了进一步压缩图片向量数量,一个很自然的想法就是对不同位置的特征进行聚合统计,比如可以计算图像每个2*2区域上的最大值来代表这个区域的特征。这个操作就是pooling操作,如对26*26的图片取值pool size 2*2,则可以缩小图片为13*13。
3)LeNet卷积神经网络模型
LeCun教授在上世纪90年代发明LeNet卷积神经网络模型,该模型被成功应用于银行的支票识别系统。
下图为LeNet模型,由两个convolution层、两个pooling层和两个full connection层组成。
三、用Keras实现手写数字识别(LeNet实现)
源码可以用git获取:https://github.com/jiayulinfs/mnist-keras。
# 以下代码可以直接在python 3.6.1种执行
from __future__ import print_function
import keras
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras import backend as K
batch_size = 128
num_classes = 10
epochs = 12
# 输入图片格式 28*28
img_rows, img_cols = 28, 28
# 加载mnist图片数据
(x_train, y_train), (x_test, y_test) = mnist.load_data()
if K.image_data_format() == 'channels_first':
x_train = x_train.reshape(x_train.shape[0], 1, img_rows, img_cols)
x_test = x_test.reshape(x_test.shape[0], 1, img_rows, img_cols)
input_shape = (1, img_rows, img_cols)
else:
x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 1)
x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 1)
input_shape = (img_rows, img_cols, 1)
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255
print('x_train shape:', x_train.shape)
print(x_train.shape[0], 'train samples')
print(x_test.shape[0], 'test samples')
# 划分训练数据和测试数据
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)
model = Sequential()
# 增加第一个convolution层,32个filter,kernel size 3*3
model.add(Conv2D(32, kernel_size=(3, 3),
activation='relu',
input_shape=input_shape))
# 增加pooling层
model.add(MaxPooling2D(pool_size=(2, 2)))
# 增加第一个convolution层,64个filter,kernel size 3*3
model.add(Conv2D(64, (3, 3), activation='relu'))
# 增加pooling层
model.add(MaxPooling2D(pool_size=(2, 2)))
# 增加full connection层
model.add(Flatten())
model.add(Dense(500, activation='relu'))
# 增加full connection层,结果是10个数字分类
model.add(Dense(num_classes, activation='softmax'))
model.compile(loss=keras.losses.categorical_crossentropy,
optimizer=keras.optimizers.Adadelta(),
metrics=['accuracy'])
model.fit(x_train, y_train,
batch_size=batch_size,
epochs=epochs,
verbose=1,
validation_data=(x_test, y_test))
score = model.evaluate(x_test, y_test, verbose=0)
print('Test loss:', score[0])
print('Test accuracy:', score[1])
model.save('mnist-keras.h5')
训练过程:
通过保存的模型文件mnist-keras.h5,测试图片:
import numpy as np
from keras.datasets import mnist
import pandas as pd
from keras.optimizers import Adam
from PIL import Image
from keras import backend as K
from keras.models import load_model
img_rows, img_cols = 28, 28
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train = x_train.reshape(x_train.shape[0], -1) / 255.
x_test = x_test.reshape(x_test.shape[0], -1) / 255.
if K.image_data_format() == 'channels_first':
x_train = x_train.reshape(x_train.shape[0], 1, 28, 28)
x_test = x_test.reshape(x_test.shape[0], 1, img_rows, img_cols)
input_shape = (1, img_rows, img_cols)
else:
x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 1)
x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 1)
input_shape = (img_rows, img_cols, 1)
print(K.image_data_format())
model=load_model('mnist-keras.h5')
fname = '8.png'
image = Image.open(fname).convert("L")
image = image.resize((28,28),Image.ANTIALIAS)
arr = np.asarray(image)
print(arr.shape)
arr=1-arr/255
arr=np.reshape(arr,(1,28,28,1))
print(arr.shape)
out1 = model.predict_classes(arr)
print("print after load:", out1)
四、总述
2012年的ImageNet图像分类竞赛中采用改进LeNet后的AlexNet模型(以第一作者Alex命名),获得比赛第一名,并且最终成绩把第二名远远甩开。后续年份Google和Microsoft的连续改进提出的CNN模型也分别获得第一名,并且现在计算机的图片分类识别能力已经超过了人类的眼睛。
在这里讲一个AlexNet的微小改进,通过增加dropout层,就是人为的随机暂时丢弃部分神经元,这样可以通过增加整体的训练次数,又防止过拟合。仔细思考下这个小改动,是不是这个想法就是人类学习方式的一种仿生?我觉得从小学习就有这种经验,遇到一个不懂的不要着急,先放在一边,然后第二次看的时候,说不定就自动理解啦。
人类本身就是一个神奇,虽然还不知道大脑是怎么工作的,但是只要模仿它,就会有很多神奇的发现。不管它是什么,它的道理就存在那儿,人工智能也会这样去书写神奇的未来。