[kaggle系列 四] 通过mnist来研究神经网络的一些细节(1)

题目

https://www.kaggle.com/c/digit-recognizer

前言

前面玩泰坦尼克号花费了一些时间,想要把分数刷的高一些,但是没有成功,感觉再搞下去意义不大,毕竟只是拿来熟悉kaggle和一些机器学习算法的,目的已经达到了,没必要纠缠下去。所以就开新坑啦~
其实我重点是想要搞神经网络深度学习的,mnist是一个比较简单的数据集,是Yann LeCun大神搞出来的,收集了6,7万个手写数字的图片,对于神经网络来说还是比较容易的,很多教程里都会用mnist来进行入门。
最近也有看CS231n的公开课,前面有讲到一些对于训练比较有用的东西,我的想法是用mnist来把这些东西实践一下,当然,有可能这个数据集的复杂度比较低,用到网络的话也比较浅,可能有些问题触及不到,这个等发现了再说吧,不行就换imageNet之类的试一试,mnist比较小,训练也快,先把能用它实践的先试一试吧~

简析

这个问题是识别手写数字,给出的是一个2828的图片的灰度值,也就是一个2828的矩阵,每个位置的值是0-255的整数,数据给的时候把矩阵展开来了,也就是把2828的矩阵拉成了一行,即7841。一开始,我打算只用个一层的神经网络写一写,当然了,一层的话还是叫线性分类器更准确一点吧,总之,我们的输入是一个784*1的图片,输出是0~9的类别。
图片的话,用卷积神经网络的准确度会更高,不过我们现在只是为了探讨神经网络中会遇到的一些问题,所以先怎么简单怎么来~
首先是处理数据和训练流程的一些代码,这部分不打算多说,不难写。由于测试集是没有label的,所以我先在训练集里拿了十分之一的数据作为测试集,先用这个测试代码和参数,等一切就绪以后再用全部的数据进行训练。我这里使用了神经网络的训练框架:tensorflow,这个在现在是比较火的啦~ 还是有必要掌握的。

处理数据的代码

总之,先贴一下处理数据之类的代码:

import csv
import os
import numpy as np
import tensorflow as tf
from model_simple import SimpleModel

def readData(fileName):
    result = []
    with open(fileName,'rb') as f:
        rows = csv.reader(f)
        isFirst = True
        count = 0
        for row in rows:
            if isFirst:
                isFirst = False
                continue
            result.append(row)
            count += 1
    return result

def writeData(fileName, data):
    csvFile = open(fileName, 'w')
    writer = csv.writer(csvFile)
    n = len(data)
    for i in range(n):
        writer.writerow(data[i])
    csvFile.close()

def convertData(dataList):
    res = np.array(dataList).astype(float)
    return res

def labelToMat(ylabel):
    n = len(ylabel)
    res = np.zeros((n,10))
    for i in range(n):
        p = int(ylabel[i])
        res[i][p] = 1
    return res

def run():
    dataRoot = '../../kaggledata/mnist/'
    trainData = readData(dataRoot + 'train.csv')
    trainData = convertData(trainData)
    x_input = np.delete(trainData,0,axis=1)
    y_label = labelToMat(trainData.T[0])
    x_input /= 255

    model = SimpleModel()
    n = len(y_label) - int(len(y_label)/10)
    model.build_model()
    print n
    model.train(x_input[:n],y_label[:n])
    #model.init_model('simple.model.ckpt-0')
    predict = model.test(x_input)
    print 'predict len:' + str(len(predict))
    train_acc = 0.
    test_acc = 0.
    for i in range(len(predict)):
        if predict[i] ==  trainData.T[0][i]:
            if i <= n:
                train_acc += 1
            else:
                test_acc += 1
    print train_acc, test_acc
    print 'train_acc is: %.6f, test_acc is %.6f'%(train_acc / n,test_acc/(len(predict) - n) )

def train():
    dataRoot = '../../kaggledata/mnist/'
    trainData = readData(dataRoot + 'train.csv')
    trainData = convertData(trainData)
    x_input = np.delete(trainData,0,axis=1)
    y_label = labelToMat(trainData.T[0])
    x_input /= 255

    model = SimpleModel()
    model.build_model()
    model.train(x_input,y_label)

def test():
    dataRoot = '../../kaggledata/mnist/'
    testData = readData(dataRoot + 'test.csv')
    x_input = convertData(testData)
    x_input /= 255

    model = SimpleModel()
    model.init_model('simple.model.ckpt-0')
    predict = model.test(x_input)
    result = []
    result.append(['ImageId', 'Label'])
    for i in range(len(predict)):
        result.append([i + 1, predict[i] ])
    writeData(dataRoot + 'result.csv', result)

if __name__ == '__main__':
    run()
    # train()
    # test()

模型与代码

在上面代码中,可以看到我写了个模型的类,这个模型主要有三个函数:build_model , train , test。也就是建立模型,训练模型和测试,我们一个一个来说这几个东西。
首先是build一个模型,网络结构非常简单,就是一个线性模型(x*w +b),然后输出套了个softmax :

def build_model(self):
        print 'build_model'
        # x对应训练数据或者测试数据,None表示不确定的数量
        # 因为我们训练的时候不是一个数据一个数据去训练的,而是选一组数据作为一个batch
        # 每次用一个batch去训练,这个batch其实也是个超参数,需要调的
        self.x = tf.placeholder(tf.float32,[None, 784])
        # W 和 b 就是我们需要训练的参数
        self.W = tf.Variable(tf.random_normal([784,10], stddev=1),name='weights')
        self.b = tf.Variable(tf.zeros([10]),name='biases')
        # 输出后面用个softmax以用来分类
        self.y = tf.nn.softmax( tf.matmul(self.x,self.W) + self.b)
        # 实际的结果(label)
        self.label = tf.placeholder(tf.float32,[None,10])
        # 使用交叉熵作为损失函数
        self.cross_entropy = -tf.reduce_sum(self.label*tf.log(self.y))
        # 使用梯度下降进行训练,learning_rate(学习率)是一个超参数,我用的0.01
        opt = tf.train.GradientDescentOptimizer(learning_rate=self.learning_rate)
        self.train_step = opt.minimize(self.cross_entropy)
        
        # 启动模型和保存模型的一些代码
        config = tf.ConfigProto(allow_soft_placement=True, log_device_placement=False)
        self.sess = tf.Session(config=config)
        init = tf.global_variables_initializer()
        self.sess.run(init)
        self.saver = tf.train.Saver(tf.global_variables())

build模型的话,需要注意的问题有两个,一个是参数初始化的问题,如果我把W初始化为0了,会怎么样呢?就像这样:

    self.W = tf.Variable(tf.zeros([784,10]),name='weights')

答案是你的模型可能没办法训练下去,你会发现在某个时刻,你的loss有概率会变为nan:

为什么会这样呢?我们可以看一下交叉熵函数:

有个ln,我们看一下y = ln x的图像,就会发现,当我们的y非常小,甚至为0的时候,就会导致loss变为nan。

为了减少这种情况的发生,我们可以使用高斯分布来对参数进行初始化,简单来说,就是让参数的初始值稍微大一些,防止计算结果为0导致梯度计算出问题。我在这里用了个正态分布来初始化参数,但是还是有几率出现loss变成nan的情况。只有一层的网络都有这个问题,对于层数更多的网络更需要注意,如果参数初始化出了问题,训练就有可能无法进行下去,这个问题后面在继续说一说,层数变多会出现另外的问题。

另一个问题就是学习率,这是个需要调整的超参数,学习率太大会导致后面学不下去,太小会导致学习速度非常慢而且很难达到最优点。

接下来是训练和测试模型的代码。首先,我们要明确训练的时候,我们数据不是一次训练把所有的数据都用上,而是挑选一部分作为一个batch进行训练的,这个batch的大小也是一个超参数,需要人手工调整的。然后训练会经历几轮,我们称为epoch,为了保证训练比较充分,一般会多训个几轮。怎样选取batch也是个问题,最好选batch是让数据的分布是随机的,这样有助于减轻神经网络学习的时候发生过拟合。不过在个训练中训练轮数也不多,而且数据给的时候已经是随机的了,所以影响不大,不过我还是写了个随机选取batch的函数。

下面就直接贴完整代码吧,训练和测试的代码还是比较简单的:

import os
import numpy as np
import tensorflow as tf
import random

class SimpleModel(object):
    def __init__(self):
        self.learning_rate = 0.01
        self.batch_size = 200

    def build_model(self):
        print 'build_model'
        self.x = tf.placeholder(tf.float32,[None, 784])
        self.W = tf.Variable(tf.random_normal([784,10], stddev=1),name='weights')
        self.b = tf.Variable(tf.zeros([10]),name='biases')
        self.y = tf.nn.softmax( tf.matmul(self.x,self.W) + self.b)
        self.label = tf.placeholder(tf.float32,[None,10])
        self.cross_entropy = -tf.reduce_sum(self.label*tf.log(self.y))
        opt = tf.train.GradientDescentOptimizer(learning_rate=self.learning_rate)
        self.train_step = opt.minimize(self.cross_entropy)

        config = tf.ConfigProto(allow_soft_placement=True, log_device_placement=False)
        self.sess = tf.Session(config=config)
        init = tf.global_variables_initializer()
        self.sess.run(init)
        self.saver = tf.train.Saver(tf.global_variables())

    def randomBatch(self,size, epoch):
        self.data_tags = []
        for i in range(epoch):
            for j in range(size):
                self.data_tags.append(j)
        random.shuffle(self.data_tags)
        self.data_pos = 0

    def getNextBatch(self, x_inputs, y_labels):
        batch_x = []
        batch_y = []
        m = len(self.data_tags)
        for i in range(self.batch_size):
            p = self.data_tags[self.data_pos]
            self.data_pos = (self.data_pos + 1)%m
            batch_x.append(x_inputs[p])
            batch_y.append(y_labels[p])
        return np.array(batch_x),np.array(batch_y)

    def train(self,x_inputs, y_labels):
        pos = 0
        count = 0
        epoch = 5
        total = int(len(x_inputs)/self.batch_size)
        self.randomBatch(len(x_inputs),epoch)
        for i in range(epoch*total):
            x_batch,y_batch = self.getNextBatch(x_inputs,y_labels)
            loss,_ = self.sess.run([self.cross_entropy,self.train_step],feed_dict={self.x:x_batch,self.label:y_batch})

            count += 1
            if count % 50 == 0:
                print 'step %d: ,loss:%.6f' % (count, loss)

        self.saver.save(self.sess, './train_models/simple.model.ckpt',global_step=0)

        print 'train over'

    def init_model(self,modelName):
        self.build_model()
        self.saver.restore(self.sess, os.path.join('./train_models/',modelName) )

    def test(self, x):
        predict = self.sess.run(self.y, feed_dict={self.x:x})
        res = np.argmax(predict, axis=1)
        return res

结论

这个模型最终的准确率有0.89871,一点都不高,在kaggle上也是垫底,不过我们至少有了一个baseline,接下来我会把网络多加几层看看效果,然后通过这个测试一些神经网络需要注意的问题~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容