教你 30 分钟写完 MNIST 作业
laekov 2018.05.20
Overview
题目要求
随便用什么框架去 Kaggle 上做手写数字识别的数据集.
你可以选择自己写一个框架. (谁爱写谁写)
THU PACMAN 实验室最近在筹划写一个框架, 有兴趣的同学可以联系我.
最好多实现几个模型, 调调参, 写报告需要.
需要做的事
- 找一个框架
抄一遍 Tutorial- 写一个处理数据 (csv格式) 的模块
- 写一个模型
- 写训练/验证/测试的东西
CNTK 框架
微软的框架. Python 文档见https://cntk.ai/pythondocs/index.html
安装. 见官网. pip
, conda
, install_cntk.exe
什么的都行.
略.
使用 CNTK 框架.
import os
import cntk as C
import numpy as np
轻松愉快.
处理 CSV
数据格式
第一行不用管
1234567890,0,0,...,255,0
一行一张图, 第一个是数字, 后面是28x28
个0-255
的灰度值.
可以用 pandas
之类的又麻烦又难用的库.
不如自己写个小函数.
def csv_reader(filename, batch_size = 1, is_test = False):
with open(filename, 'r') as f:
f.readline()
batch = { 'image': [], 'label': [] }
while True:
raw_line = f.readline().split(',')
if len(raw_line) < 10:
break
label = np.zeros(10, dtype = float)
if not is_test:
label[int(raw_line[0])] = 1
raw_line = raw_line[1:]
batch['image'].append(np.array([ float(x) for x in raw_line ], dtype = float).reshape((1, 28, 28)))
batch['label'].append(label)
if len(batch['image']) == batch_size:
yield batch
batch = { 'image': [], 'label': [] }
if len(batch['image']) > 0:
yield batch
模型
计算部分
LeNet
一层CNN一层ReLU 一口咸菜一口粥
两遍, 然后全连接两层 (中间有一个隐层).
线性结构, CNTK自带Sequential化简.
def create_model(features):
with C.layers.default_options(init=C.glorot_uniform(), activation=C.relu):
h = features
h = C.layers.Convolution2D(filter_shape=(5,5),
num_filters=8,
strides=(2,2),
pad=True, name='first_conv')(h)
h = C.layers.Convolution2D(filter_shape=(5,5),
num_filters=16,
strides=(2,2),
pad=True, name='second_conv')(h)
r = C.layers.Dense(10, activation=None, name='classify')(h)
return r
Loss部分
不管是啥抄 Tutorial 就好.
一个简单的交叉熵. 输入是模型的输出 (十维向量) 和标签 (One-hot 的十维向量)
def create_criterion_function(model, labels):
loss = C.cross_entropy_with_softmax(model, labels)
errs = C.classification_error(model, labels)
return loss, errs
拼起来部分
def main(is_test = False):
x = C.input_variable((1, 28, 28), name = 'image')
y = C.input_variable((10, ), name = 'label')
z = create_model(x)
model = z(x / 255)
loss, label_error = create_criterion_function(model, y)
训练验证测试
训练
训练一个epoch的过程是: 把一个batch的数据正向跑一遍, 反向算梯度, 更新权重.
使用自带的 learner
和 trainer
即可.
learning_rate = 0.2
lr_schedule = C.learning_parameter_schedule(learning_rate)
learner = C.sgd(z.parameters, lr_schedule)
trainer = C.Trainer(z, (loss, label_error), [learner])
训练一个epoch的过程就是把数据读进来喂给trainer
的无脑过程.
def train_epoch(loss, learner, trainer):
i = 0
for batched_data in csv_reader('train.csv', batch_size = 64):
trainer.train_minibatch(batched_data)
training_loss = trainer.previous_minibatch_loss_average
i += 1
if i % 100 == 0:
print('Trained %d batches, loss = %e' % (i, training_loss))
验证
把一部分数据拿来算一遍精度, 实时监测训练进度, 防止过拟合.
算精度相当于把训练结果拿出来. 使用 model.eval
函数.
善用np.array
的argmax
函数.
def validate(model):
i = 0
tot = 0.
corr = 0.
for batched_data in csv_reader('train.csv', batch_size = 64):
data = { 'image': batched_data['image'] }
ans = model.eval(data)
for posbs, answer in zip(ans, batched_data['label']):
x = np.array(posbs).argmax()
corr += 1. if x == answer.argmax() else 0.
tot += 1.
i += 1
if i % 100 == 0:
print('Tested %d batches' % (i))
print('Accuracy = %.7f' % (corr / tot))
return corr / tot
测试
跑测试集, 把答案写进result.csv
. 和 Validate 大同小异.
def test(model):
with open('result.csv', 'w') as f:
f.write('ImageId,Label\n')
base_id = 1
i = 0
for batched_data in csv_reader('test.csv', batch_size = 256, is_test = True):
data = { 'image': batched_data['image'] }
ans = model.eval(data)
for j, a in enumerate(ans):
f.write('%d,%d\n' % (j + base_id, np.array(a).argmax()))
base_id += len(ans)
i += 1
if i % 10 == 0:
print('Inferred %d images' % base_id)
总过程
重复以下过程: 训练, 验证. 找到一个不错的模型就存下来.
直到你觉得OK了就把你觉得OK的模型Load进来跑测试.
trainer
有 save_checkpoint
和 load_from_checkpoint
函数. 参数就是文件名. 很方便.
def main(is_test = False):
x = C.input_variable((1, 28, 28), name = 'image')
y = C.input_variable((10, ), name = 'label')
z = create_model(x)
model = z(x / 255)
loss, label_error = create_criterion_function(model, y)
if not is_test:
max_epoch = 10
learning_rate = 0.2
lr_schedule = C.learning_parameter_schedule(learning_rate)
learner = C.sgd(z.parameters, lr_schedule)
trainer = C.Trainer(z, (loss, label_error), [learner])
if os.path.isfile('lenet.pm'):
trainer.restore_from_checkpoint('lenet.pm')
best_acc = validate(model)
for i in range(max_epoch):
print('Running epoch %d' % i)
train_epoch(loss, learner, trainer)
acc = validate(model)
trainer.save_checkpoint('lenet.pm')
if acc > best_acc:
trainer.save_checkpoint('lenet_best.pm')
else:
if os.path.isfile('lenet.pm'):
trainer.restore_from_checkpoint('lenet.pm')
test(model)
思考题和讨论
验证集的划分
我没划分直接在训练集上跑的. 肯定不对. 划分也不难.
模型复杂化
只用修改 create_model
函数就行了.
调参
程序里写的常数都能调. 比如 lr
. 模型里还有一些默认的参数, 详见文档.
性能优化
CNTK自带优先在GPU上跑.
GPU加速有多快? 也就几十倍.?
hint
这回提供的代码我良心地跑过了且确实能跑能用.