[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,接下來我會把網絡多加幾層看看效果,然后通過這個測試一些神經網絡需要注意的問題~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容