背景简介
本文实现的是Kaggle竞赛平台上Getting Started级别的Digit Reconizer(即MNIST)。使用Tensorflow搭建了5层卷积神经网络(Convolutional Neural Network,简称CNN),最终取得测试成绩是0.98742。
正文
- 导入相关的包
import pandas as pd import tensorflow as tf import numpy as np from sklearn.preprocessing import OneHotEncoder
本文使用了numpy,pandas,scikit-learn,以及tensorflow包。其中numpy主要用于处理数据格式;pandas主要用于读写文件;使用scikit-learn包进行One-Hot格式处理;Tensorflow负责CNN的搭建及运行。
缺陷: 未进行数据可视化和数据清理工作。
- 读取数据,并查看内容
# 读取训练集 train = pd.read_csv("/{你的数据文件存放位置}/digit_recognizer/train.csv") # 读取测试集 test = pd.read_csv("/{你的数据文件存放位置}/digit_recognizer/test.csv") # shape:(42000,785) train_df = train.copy() test_df = test.copy()
首先使用pandas的read_csv()方法读取训练集和测试集,然后通过describe()和info()方法查看训练集的格式为(42000,785),其中第一列为训练集标签(即Label),剩余784列是手写图像的数据(因为图片大小是28*28)。
使用了copy()方法的初衷是防止未来操作修改了原始数据。后来经查阅相关资料发现Python中存在引用(=)、浅拷贝(copy())、深拷贝的差别(deepcopy())。正确的应该是使用deepcopy()对原始数据进行深拷贝,这样Python会重新创建一个对象,对新对象的任何修改都不会影响到原始对象。
缺陷: copy()方法使用错误,应该使用deepcopy()方法。
- 格式化数据
# 根据需求格式化训练集 # x_train.shape -> (42000, 784) x_train = train_df.drop(['label'], axis=1).values.reshape([-1, 784]).astype(np.float32) # y_train.shape -> (42000, 1) # y_train[0] -> [1.] y_train = train_df['label'].values.reshape([-1, 1]).astype(np.float32) # one-hot using scikit-learn # onehot_encoded.shape -> (42000, 10) # onehot_encoded[0] -> [0. 1. 0. 0. 0. 0. 0. 0. 0. 0.] onehot_encoded = OneHotEncoder(sparse=False).fit_transform(y_train)
格式化数据时应该注意以下几点:
- 格式化要和占位符的格式匹配,否则喂数据的时候会出错。
- 数据类型需要是float32或者float64,实验中报错提示的信息,具体为什么暂时没研究。
- 训练集标签(Label),即此处的y_train,需要做One-Hot处理。此处采用scikit-learn包装的方法。
缺陷:
- 刚入门,对数据格式没有足够的重视,实验中发现它是最重要的环节之一!
- 实际上代码的正确顺序应该是先写占位符,再根据占位符的shape进行训练集数据的格式化工作。
- One-Hot算法有多种实现方式,其一是自定义实现;其二是scikit-learn包;其三Tensorflow包也提供算法支持。未来要有能力对One-Hot算法信手拈来。
- 对数据格式认识不足,其实还有更简便的格式化方式,例如,x_train=train_df.iloc[:, 1:].values一步到位。
- 定义占位符,喂数据时使用
# 占位符 x_ = tf.placeholder(dtype=tf.float32, shape=[None, 784]) y_ = tf.placeholder(dtype=tf.float32, shape=[None, 10])
要注意的是,占位符的shape要和喂神经网络的数据格式匹配。
经验: (若你和Milo一样曾经研究方向是分布式)可以把神经网络当成一个分布式系统,作业被分成许多个基本任务shuffle到各个节点上。其实,基本任务就是确定(f(x) = wx + b)的参数,此处参数不是向量,而节点就是一个个神经元。输入和输出有多少个节点,就有多少个任务,也就确定了向量的维度。
- 定义神经网络的基本单元
""" 定义变量 """ def weight(shape): return tf.Variable(tf.truncated_normal(shape, stddev=0.1)) def bias(shape): return tf.Variable(tf.constant(0.1, shape=shape)) """ 定义层 """ def conf2d(x, w): return tf.nn.conv2d(x, w, strides=[1, 1, 1, 1], padding='SAME') def max_pool(x): return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
此处Milo定义了权重、偏置、卷积层以及Max Pooling层,其中偏置项取常量0.1;卷基层的步伐是1,填充方式为SAME;Max Pooling层核大小是2*2,步伐为2,填充方式同样是SAME。
这里有必要解释一下卷积层输出大小的计算:
- 教科书上,以及视频教程手推卷积层大小公式为:(f(x) = frac{Input - Filter + 2 * Padding}{Stride} + 1)
- Tensorflow中,SAME填充方式的卷积层输出大小计算公式为:(f(x) = lceil frac{W}{Stride} rceil),跟Filter和Padding没半毛钱关系.
- Tensorflow中,VALID填充方式的卷积层输出大小计算公式为:(f(x) = lceil frac{W - Filter + 1}{Stride} rceil)
缺陷:
- 起初写代码的时候完全没考虑重构。在没定义神经网络的基本单元的时候,整段代码乱成一锅粥,十分不利于代码调试,同时也阻碍对神经网络模型的理解。所以,
强烈建议进行重构工作!
- 计算输出输出格式不熟练。根据卷积层和Max Pooling层的参数,需要提前规划神经网络模型中每一层的大小,正确计算每一层的输出,否则将会出现意想不到的惊(yi)喜(chang)。
- 定义神经网络模型
# SAME:输出大小等于输入大小除以步长向上取整,s是步长大小; # VALID:输出大小等于输入大小减去滤波器大小加上1,最后再除以步长(f为滤波器的大小,s是步长大小)。 # 第一层 28*28*1 -> 14*14*32 x_images = tf.reshape(x_, [-1, 28, 28, 1]) w_conv1 = weight([5, 5, 1, 32]) b_conv1 = bias([32]) h_1 = tf.nn.sigmoid(conf2d(x_images, w_conv1) + b_conv1) p_1 = max_pool(h_1) # 第二层 14*14*32 -> 7*7*64 w_conv2 = weight([5, 5, 32, 64]) b_conv2 = bias([64]) h_2 = tf.nn.sigmoid(conf2d(p_1, w_conv2) + b_conv2) p_2 = max_pool(h_2) # 第三层 7*7*64 -> 4*4*128 w_conv3 = weight([5, 5, 64, 128]) b_conv3 = bias([128]) h_3 = tf.nn.sigmoid(conf2d(p_2, w_conv3) + b_conv3) p_3 = max_pool(h_3) # 第四层 4*4*128 -> 1*1024 w_fc1 = weight([4 * 4 * 128, 1024]) b_fc1 = bias([1024]) h_fc1 = tf.nn.sigmoid(tf.matmul(tf.reshape(p_3, [-1, 4 * 4 * 128]), w_fc1) + b_fc1) # 第五层 1*1024 -> 1*10 w_fc2 = weight([1024, 10]) b_fc2 = bias([10]) y_conv = tf.matmul(h_fc1, w_fc2) + b_fc2 # tf.nn.softmax_cross_entropy_with_logits做了softmax和交叉熵 prediction = tf.nn.softmax(tf.matmul(h_fc1, w_fc2) + b_fc2)
经验:
- 卷积核的深度需要是2的N次幂。(他人经验借鉴)
- tf.reshape()的维度参数中,-1表示不定义张量的维度。第一层中张量的维度将被reshape为42000维。
- 每一层中做的操作包括:图片信息和权重w做卷积操作 —> 卷积结果加上偏置项做激活操作 —> 经过激活函数的结果进行Pooling操作。
- 预测时,只关心softmax结果,所以图片数据直接喂prediction即可。
缺陷:
- 没有重构成独立的模块。最初抽象成独立模块的时候,Milo对输入数据的格式定义不熟悉,出现了各种异常,所以决定拆开,优先确保程序成功运行。
- 对最后全连接的定义不清晰。究竟什么时候开始全连接?
- 定义结果需要使用的数据
# cross_entropy = -tf.reduce_sum(y_ * tf.log(y_conv)) loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y_conv)) train_step = tf.train.GradientDescentOptimizer(learning_rate=0.1).minimize(loss) correct_prediction = tf.equal(tf.argmax(y_conv, axis=1), tf.argmax(y_, axis=1)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, dtype=tf.float32))
- 训练过程是要是损失函数取最小值,此处是对softmax结果做交叉熵,然后进行均值降维。
- 精度计算是对比训练集标签和实际标签。
- 题外话
# batch # no batch cause: CUDNN_STATUS_INTERNAL_ERROR # The value of a feed cannot be a tf.Tensor object. # ("shuffle_batch:0", shape=(100, 42000, 784), dtype=float32) # ("Placeholder:0", shape=(?, 784), dtype=float32). # images, labels = tf.train.shuffle_batch([x_train, onehot_encoded], # batch_size=100, # num_threads=6, # capacity=int(50000 * 0.4 + 3 * 100), # min_after_dequeue=1000 # )
经验:
- 在训练神经网络模型的时候出现OOM的情况,想使用tf.train.shuffle_batch分批处理,结果出现异常:”The value of a feed cannot be a tf.Tensor object.”。
- 神经网络模型的输入和输出一定刚要和喂的数据维度相匹配!
- 运行训练,并保存结果
config = tf.ConfigProto() config.gpu_options.per_process_gpu_memory_fraction = 0.7 # maximun alloc gpu50% of MEM config.gpu_options.allow_growth = True # allocate dynamically with tf.Session(config=config) as sess: # for i in range(100): # if i % 10 == 0: # train_accuracy = accuracy.eval(session=sess, feed_dict={x_: x_train, y_: onehot_encoded}) # print("train_accuracy %g" % train_accuracy) # train_step.run(session=sess, feed_dict={x_: x_train, y_: y_train}) sess.run(tf.global_variables_initializer()) # 迭代20次,每次都把全部数据分成42000/128个批次,分批喂入tf for epoch in range(20): print('epoch', epoch + 1) for batch in range(int(len(x_train) / 128)): batch_x = x_train[batch * 128:(batch + 1) * 128] batch_y = onehot_encoded[batch * 128:(batch + 1) * 128] sess.run(train_step, feed_dict={x_: batch_x, y_: batch_y}) batch_x = x_train[int(len(x_train) / 128) * 128:] batch_y = onehot_encoded[int(len(x_train) / 128) * 128:] sess.run(train_step, feed_dict={x_: batch_x, y_: batch_y}) print("accuracy: %f" % sess.run(accuracy, feed_dict={x_: batch_x, y_: batch_y})) tf.train.Saver().save(sess, "/{你的数据文件存放位置}/mnist_model.ckpt") tf.train.Saver().restore(sess, "//{你的数据文件存放位置}/mnist_model.ckpt") x_test = test_df.values.reshape([-1, 784]).astype(np.float32) # 直接跑图的最后一步: softmax计算能力 result = sess.run(prediction, feed_dict={x_: x_test}) label_test = np.argmax(result, axis=1) # pd.DataFrame(label_test).to_csv("//{你的数据文件存放位置}/test_result.csv") # 保存结果 submission = pd.DataFrame({ "ImageId": np.arange(1, 28000 + 1), "Label": label_test }) submission.to_csv("/home/milo/Github/machinelearning/digit_recognizer/test_result.csv", index=False)
经验:
- 某个人没安装CUDA,还瞎吉儿Debug得很欢快,以为程序写错了!
- 一次性将42000张图片喂到5层神经网络结构中会出现OOM的情况,所以措施一是设置GPU内存使用阈值0.7;措施二是使用小批量处理。
- 小批量处理需要注意一个细节:最后一批需要在循环外单独处理。
- 注意输出精确度的print写法,是使用sess.run()运行accuracy方程。
- 使用tf.train.Saver()可以保存模型,参数是tf.session(),需要指定模型文件的存储位置。
- 上传Kaggle时需要注意,Kaggle要求CSV文件中要带有标签,所以需要自定义。注意细节:”ImageId”: np.arange(1, 28000 + 1),Id从1开始,直接写死就行了。
缺陷:
- 代码需要重构。
- 流程需要梳理清楚。训练跑的是优化器取损失函数最小是的方程;打印精确度跑的是对比预测值的方程;测试是直接跑神经网络模型最后的softmax方程。
实验环境
- 硬件:
- I5-9400F
- 16G DDR4
- 七彩虹RTX 2060 6G Ultra OC
- 软件:
- Anaconda(Python3.7.3) 1.7.2
- Tensorflow 1.13.1
- Pycharm 2019.1
- cudatoolkit 10.0.130 (运用显卡的计算能力,很重要!!!)
- cudnn 7.3.1
实验结果
待补充
总结
机器学习项目清单(摘录自《机器学习实战 基于Scikit-Learn和Tensorflow》)
- 架构问题,关注蓝图
- 获取数据
- 研究数据以获取灵感
- 准备数据以更好地将低层模型暴露给机器学习
- 研究各种不同的模型,并列出最好的模型
- 微调模型,并将其组合为最好的解决方案
- 提出解决方案
- 启动、监视、维护系统
- 以上清单可以随时调整
本文内容转自冰部落,仅供学习交流,版权归原作者所有,如涉及侵权,请联系删除。 声明: 本平台/个人所提供的关于股票的信息、分析和讨论,仅供投资者进行研究和参考之用。 我们不对任何股票进行明确的买入或卖出推荐。 投资者在做出投资决策时,应自行进行充分的研究和分析,并谨慎评估自己的风险承受能力和投资目标。 投资有风险,入市需谨慎。请投资者根据自身的判断和风险承受能力,自主决策,理性投资。