自己动手做一个识别手写数字的web应用01

2018-04-17 13:46:41 浏览数 (1)

最近在深入地学习keras,发现网上各种教程都是教你怎么训练模型的,很少有问题提到如何把训练好的模型部署为后端服务,为web及app提供服务。

于是,我决定把学习的过程完整的记录下来,帮大家更快地把深度学习的模型应用到实际场景中。

用到的技术:

keras tensorflow flask

这个教程分为4篇。

第一篇

介绍开发环境--训练模型--保存至本地;

第二篇

介绍导入训练好的模型--识别任意的手写数字图片;

第三篇

介绍用Flask整合keras训练好的模型,并开发后端服务;

第四篇

介绍前端web单页应用的开发。

为了方便入门,下面采用docker的方式进行实验。

01

采用docker部署开发环境

首先安装好docker,我是mac,window用户请查阅官方安装教程,运行docker,终端输入:

docker pull floydhub/dl-docker:cpu

在本地电脑新建一个目录,我这边是kerasStudy,路径是

/Users/shadow/Documents/02-coding/kerasStudy

大家可以改成自己本机对应的路径。

终端运行:

docker run -it -p 6006:6006 -p 8888:8888 -v /Users/shadow/Documents/02-coding/kerasStudy:/root/kerasStudy floydhub/dl-docker:cpu bash

-p 6006:6006,表示将Docker主机的6006端口与容器的6006接口绑定;

-v 参数中,冒号":"前面的目录是宿主机目录,后面的目录是容器内目录。

记得,还需要在docker中配置宿主机的与镜像共享的目录地址

将新建一个容器,并在容器中开启一个交互模式的终端,结果如下:

02

启动jupyter notebook

终端输入:

mkdir $HOME/.keras/

cd $HOME/.keras/

vim keras.json

键盘按 i ,按回车及方向键控制光标,把floydhub/dl-docker:cpu镜像默认的使用Theano作为后端,改为如下:

代码语言:javascript复制
{
    "image_data_format": "channels_last",
    "epsilon": 1e-07,
    "floatx": "float32",
    "backend": "tensorflow"
}

按下esc键;输入:wq,保存修改结果。

终端输入:

代码语言:javascript复制
jupyter notebook

上图显示jupyter notebook已经运行成功,打开浏览器,在地址栏中输入:

localhost:8888

即可访问jupyter

03

Hello Jupyter Notebook

上文提到的jupyter notebook到底是什么东西?

Jupyter Notebook 是一款集编程写作于一体的效率工具,优点:

  • 分享便捷
  • 远程运行
  • 交互式展现

在浏览器可以访问Jupyter Notebook,也就是说,我可以部署成web应用的形式,用户可以分享,通过域名访问,并且可以利用web的任何交互方式。

继续我们的教程,在浏览器打开Jupyter Notebook后,找到我们与本地共享的项目目录kerasStudy,点击进入,然后点击jupyter右上角的new,选择python2,如下图所示:

新建一个notebook。

先来做个小实验:

输入:

import numpy as np

np.random.seed(1337)

np.random.rand(5)

然后在菜单中,选择Cell--Run Cells,运行代码

如下图所示,输出了一些结果。

第一行代码:

import numpy as np

引入 numpy ,一个用python实现的科学计算包。提供了许多高级的数值编程工具,如:矩阵数据类型、矢量处理,以及精密的运算库。专为进行严格的数字处理而产生。numpy的教程可以参看官网http://www.numpy.org/

np.random.seed()

使得随机数据可预测。相当于给随机数赋了个id,下次调用随机数的时候,只要再次取这个id,再调用随机数,即可产生相同的随机数

可以做下这个练习:

3.1

np.random.seed(0)

np.random.rand(5)

#控制台输出结果

array([ 0.5488135 , 0.71518937, 0.60276338, 0.54488318, 0.4236548 ])

3.2

np.random.seed(1676)

np.random.rand(5)

#控制台输出结果

array([ 0.39983389, 0.29426895, 0.89541728, 0.71807369, 0.3531823 ])

3.3

np.random.seed(1676)

np.random.rand(5)

#控制台输出结果

array([ 0.39983389, 0.29426895, 0.89541728, 0.71807369, 0.3531823 ])

04

Keras训练模型

这里结合了keras的官方案例,训练一个多层感知器。

4.1

重新建一个notebook,

输入:

代码语言:javascript复制
from __future__ import print_function

'''

 Python 3.x引入了一些与Python 2不兼容的关键字和特性,在Python 2中,
 可以通过内置的__future__模块导入这些新内容。
 如果你希望在Python 2环境下写的代码也可以在Python 3.x中运行,那么建议使用__future__模块。
 import print_function 这里使用3.x的 print方法
 在Python 3中必须用括号将需要输出的对象括起来。
 在Python 2中使用额外的括号也是可以的。
 但反过来在Python 3中想以Python2的形式不带括号调用print函数时,
 会触发SyntaxError。
 
 '''
import numpy as np

4.2

代码语言:javascript复制
from keras.datasets import mnist
# 导入mnist数据库, mnist是常用的手写数字库
from keras.models import Sequential
#导入序贯模型,Sequential是多个网络层的线性堆叠,也就是“一条路走到黑”。
from keras.layers.core import Dense, Dropout, Activation
#导入全连接层Dense,激活层Activation 以及 Dropout层
from keras.optimizers import SGD, Adam, RMSprop
#导入优化器 SGD, Adam, RMSprop
from keras.utils import np_utils
#导入numpy工具,主要是用to_categorical来转换类别向量

4.3

代码语言:javascript复制
#变量初始化
batch_size = 128 
#设置batch的大小
nb_classes = 10
#设置类别的个数
nb_epoch = 20
#设置迭代的次数

4.4

代码语言:javascript复制
#准备数据 
(X_train, y_train), (X_test, y_test) = mnist.load_data()
print(X_test.shape,X_test[0])
#keras中的mnist数据集已经被划分成了60,000个训练集,10,000个测试集的形式,按以上格式调用即可
X_train = X_train.reshape(60000, 784)
#X_train原本是一个60000*28*28的三维向量,将其转换为60000*784的二维向量

X_test = X_test.reshape(10000, 784)

print(X_test.shape)

#X_test原本是一个10000*28*28的三维向量,将其转换为10000*784的二维向量

X_train = X_train.astype('float32')
X_test = X_test.astype('float32')

#将X_train, X_test的数据格式转为float32存储

X_train /= 255
X_test /= 255

#归一化

print(X_train.shape[0], 'train samples')
print(X_test.shape[0], 'test samples')

4.5

代码语言:javascript复制
# 转换类标号 convert class vectors to binary class matrices

Y_train = np_utils.to_categorical(y_train, nb_classes)
Y_test = np_utils.to_categorical(y_test, nb_classes)

4.6

代码语言:javascript复制
#建立模型 使用Sequential()
'''
模型需要知道输入数据的shape,
因此,Sequential的第一层需要接受一个关于输入数据shape的参数,
后面的各个层则可以自动推导出中间数据的shape,
因此不需要为每个层都指定这个参数
''' 
model = Sequential()

model.add(Dense(512, input_shape=(784,)))
model.add(Activation('relu'))
model.add(Dropout(0.2))

# 输入层有784个神经元
# 第一个隐层有512个神经元,激活函数为ReLu,Dropout比例为0.2

model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.2))

# 第二个隐层有512个神经元,激活函数为ReLu,Dropout比例为0.2

model.add(Dense(10))
model.add(Activation('softmax'))

# 输出层有10个神经元,激活函数为SoftMax,得到分类结果

# 输出模型的整体信息
# 总共参数数量为784*512 512   512*512 512   512*10 10 = 669706

model.summary()

4.7

代码语言:javascript复制
#打印模型
model.summary()

4.8

代码语言:javascript复制
#配置模型的学习过程
'''
compile接收三个参数:
1.优化器optimizer:参数可指定为已预定义的优化器名,如rmsprop、adagrad,
或一个Optimizer类对象,如此处的RMSprop()
2.损失函数loss:参数为模型试图最小化的目标函数,可为预定义的损失函数,
如categorical_crossentropy、mse,也可以为一个损失函数
3.指标列表:对于分类问题,一般将该列表设置为metrics=['accuracy']
'''

model.compile(loss='categorical_crossentropy',
              optimizer=RMSprop(),
              metrics=['accuracy'])

4.9

代码语言:javascript复制
#训练模型
'''
batch_size:指定梯度下降时每个batch包含的样本数
nb_epoch:训练的轮数,nb指number of
verbose:日志显示,0为不在标准输出流输出日志信息,1为输出进度条记录,2为epoch输出一行记录
validation_data:指定验证集
fit函数返回一个History的对象,其History.history属性记录了损失函数和其他指标的数值随epoch变化的情况,
如果有验证集的话,也包含了验证集的这些指标变化情况
'''

history = model.fit(X_train, Y_train,
                    batch_size=batch_size, nb_epoch=nb_epoch,
                    verbose=2, validation_data=(X_test, Y_test))

这个时候,可以Cell--Run All 一下,可以看到训练过程,如下图:

4.10

代码语言:javascript复制
#模型评估
score = model.evaluate(X_test, Y_test, verbose=0)
print('Test score:', score[0])
print('Test accuracy:', score[1])

4.11

代码语言:javascript复制
#保存神经网络的结构与训练好的参数
json_string = model.to_json()  
open('my_model_architecture.json','w').write(json_string)    
model.save_weights('my_model_weights.h5')

系列文章第一篇至此结束。

关于第四部分Keras训练模型的源代码文件,可以在后台留言,索取。

0 人点赞