利用python的theano庫刷kaggle mnist排行榜

出自集智百科
跳轉到: 導覽搜尋

目錄

背景

theano 是一個python語言的庫,實現了一些機器學習的方法,最大的特點是可以就像普通的python程序一樣透明的使用GPU

theano的主页:http://deeplearning.net/software/theano/index.html

theano 同時也支持符號計算,並且和numpy相容,numpy是一個python的矩陣計算的庫,可以讓python具備matlab的計算能力,雖然沒有matlab方便

numpy的主页:http://www.numpy.org/

MNIST是一個手寫數字識別的公開數據集,我以為地球人都知道

front_page.png

mnist主页:http://yann.lecun.com/exdb/mnist/

其他大部分資源位於deeplearning嚮導的主頁:

deeplearning.net向导: http://deeplearning.net/tutorial/

kaggle是一個供大家公開測試各種機器學習演算法的平台,包括ICML和KDD cup一類的比賽都在上面進行,其中的入門測試集就是MNIST:

kaggle的MNIST主页:http://www.kaggle.com/c/digit-recognizer

目前發表的最好結果是卷積神經網路方法的0.23%錯誤率[1],kaggle上被認可的最好結果是0.5%。看這個架勢,mnist已經基本被大家解決了。不過本著實踐出真知和學習threano用法的目的,我覺得用python的theano庫對kaggle mnist刷個榜玩玩也不錯。

數據轉換與代碼修改

theano的代碼位於:

https://github.com/lisa-lab/DeepLearningTutorials

我修改後的代碼位於:

https://github.com/chaosconst/DeepLearningTutorials

輸入數據修改

原來是從cPickle導入:

    #############
    # LOAD DATA #
    #############
 
    # Download the MNIST dataset if it is not present
    data_dir, data_file = os.path.split(dataset)
    if (not os.path.isfile(dataset)) and data_file == 'mnist.pkl.gz':
        import urllib
        origin = 'http://www.iro.umontreal.ca/~lisa/deep/data/mnist/mnist.pkl.gz'
        print 'Downloading data from %s' % origin
        urllib.urlretrieve(origin, dataset)
 
    print '... loading data'
 
    # Load the dataset
    f = gzip.open(dataset, 'rb')
    train_set, valid_set, test_set = cPickle.load(f)
    f.close()

更改為讀取train.csv和test.csv,先初始化四個list。

    print '... loading data'
    train_set=list();
    valid_set=list();
    test_set=list();
    predict_set=list();

valid_set是用來在SGD迭代過程中,用來驗證效果但不參與訓練的數據集。每次只有確定在valid_set上更有效,才繼續進行目標函數的優化,這樣可以防止過擬合。參見early-stopping[2]

設定數據集的大小,如果是調試模式則減小數據集。

    train_set_size = 36000;
    valid_set_size = 5000;
    test_set_size = 1000;
    predict_set_size = 28000;
 
    debug = "false";
    if debug == "true":
      train_set_size = 3600;
      valid_set_size = 500;
      test_set_size = 100;
      predict_set_size = 2800;

MNIST共有7w條記錄,其中6w是訓練集,1w是測試集。theano的樣常式序就是這麼做的,但kaggle把7w的數據分成了兩部分,train.csv一共42000行,test.csv一共28000行。實際可用來訓練的數據只有42000行(由此估計最後的效果也會有相應的折扣)。theano把6w的訓練集分為了5w的test_set和1w的valid_set,我在這裡把42000行數據分為36000的train_set、5000行的valid_set和1000行的test_set(訓練時用不到)。

另外我建了一個predict_set,用來保存準備提交給kaggle的數據。然後我進行了變數初始化並從文件讀取數值,讀取的時候把kaggle的int轉化成了theano需要的float。

    train_set.append(numpy.ndarray(shape=(train_set_size,28*28), dtype=theano.config.floatX));
    train_set.append(numpy.ndarray(shape=(train_set_size), dtype=int));
    valid_set.append(numpy.ndarray(shape=(valid_set_size,28*28), dtype=theano.config.floatX));
    valid_set.append(numpy.ndarray(shape=(valid_set_size), dtype=int));
    test_set.append(numpy.ndarray(shape=(test_set_size,28*28), dtype=theano.config.floatX));
    test_set.append(numpy.ndarray(shape=(test_set_size), dtype=int));
    predict_set.append(numpy.ndarray(shape=(predict_set_size,28*28), dtype=theano.config.floatX));
    predict_set.append(numpy.ndarray(shape=(predict_set_size), dtype=int));
 
    #load data from kaggle test set
    with open('train.csv', 'rb') as csvfile:
      datareader = csv.reader(csvfile, delimiter=',')
      index=0;
      for row in datareader:
        if index<train_set_size : 
          train_set[1][index] = string.atoi(row[0]);
          for pixel_index in xrange(1,28*28+1) : 
            train_set[0][index][pixel_index-1] = string.atof(row[pixel_index])/255;
        elif index < train_set_size + valid_set_size :
          valid_set[1][index-train_set_size] = string.atoi(row[0]);
          for pixel_index in xrange(1,28*28+1) : 
            valid_set[0][index-train_set_size][pixel_index-1] = string.atof(row[pixel_index])/255;
        else :
          test_set[1][index-train_set_size-valid_set_size] = string.atoi(row[0]);
          for pixel_index in xrange(1,28*28+1) : 
            test_set[0][index-train_set_size-valid_set_size][pixel_index-1] = string.atof(row[pixel_index])/255;
        index+=1;
        if index == train_set_size + valid_set_size + test_set_size : 
          break; 
 
    print '... loading predict dataset'
    #load data from kaggle test set
    with open('test.csv', 'rb') as csvfile:
      datareader = csv.reader(csvfile, delimiter=',')
      index=0;
      for row in datareader:
        for pixel_index in xrange(0,28*28) : 
          predict_set[0][index][pixel_index] = string.atof(row[pixel_index])/255;
        index+=1;
        if index == predict_set_size: 
          break;
 
    train_set = tuple(train_set);
    valid_set = tuple(valid_set);
    test_set = tuple(test_set);
    predict_set = tuple(predict_set);

輸出數據修改

theano的convnet是由兩個卷積層,一個hidden layer和一個logistic regression構成的,如圖[3]mylenet.png

我們需要的是最後一層的輸出,theano的樣常式序在最後一層lr給了我們一個符號變數y_pred,定義如下:

        # initialize with 0 the weights W as a matrix of shape (n_in, n_out)
        self.W = theano.shared(value=numpy.zeros((n_in, n_out),
                                                 dtype=theano.config.floatX),
                                name='W', borrow=True)
        # initialize the baises b as a vector of n_out 0s
        self.b = theano.shared(value=numpy.zeros((n_out,),
                                                 dtype=theano.config.floatX),
                               name='b', borrow=True)
 
        # compute vector of class-membership probabilities in symbolic form
        self.p_y_given_x = T.nnet.softmax(T.dot(input, self.W) + self.b)
 
        # compute prediction as class whose probability is maximal in
        # symbolic form
        self.y_pred = T.argmax(self.p_y_given_x, axis=1)

手冊上說可以使用eval()對其進行實例化[4]

        predict_results = layer3.y_pred.eval({input:predict_set_x});

但是我這樣做不行,只好用了很不理想的方案,原諒我

    predict_model = theano.function([index], layer3.predict(),
             givens={
                x: predict_set_x[index * batch_size: (index + 1) * batch_size]})

其中predict函數為:

    def predict(self):
        return T.mul(self.y_pred,1);

我對技術不敬畏,對不起各位了。

這樣我們就得到可以操作的數組,寫入輸出文件:

                    predict_res_array = [predict_model(i) for i in xrange(n_predict_batches)]
                    print predict_res_array;
                    f = open("predict_res","w+");
                    for y_pred_item_array in predict_res_array:
                      for y_pred_item in y_pred_item_array:
                        f.write(str(y_pred_item)+'\n');
                    f.close();

平移數據

以上可以差不多達到1.0%的誤差,和理論值0.5%還有差距,我覺得可能是因為數據不夠多,所以我對輸入輸出數據進行了平移預處理。 輸入數據平移:

#!/bin/bash
awk -F , '{
 
for (shiftx=-1;shiftx<=1;shiftx++) {
  for (shifty=-1;shifty<=1;shifty++) {
 
    printf $1","; 
 
    for (y=0;y<28;y++) {
      for (x=1;x<=28;x++) {
        x_shift = x + shiftx;
        y_shift = y + shifty;
        if ((x_shift<1) || (x_shift>28) || (y_shift<0) || (y_shift>=28)) {
          printf "0,";
        } else {
        i=y_shift*28+x_shift+1;
        printf $i",";
      }
    }
  }
 
  printf"\n"
 
}}
//g' | sed 's/,$//g'

輸出的時候讓平移後的9個位置進行投票,boost了一把

#!/bin/bash
awk '{
  dist[$0]++; 
  if (NR%9==0) {
    best=1;
    for (x in dist) {
      if (dist[x]>dist[best]) best=x
    } 
    printf best"\n"
    delete dist;
  }
}'

ok,萬事俱備,刷榜吧!

運行結果

kaggle傳送門

valid_set_error=0.90 test_set_error=0.68

Kaggle mnist rank1.png

Kaggle mnist rank2.png

刷到前10,我感覺可以了,再往上刷10名就要被懷疑作弊了。

不明覺厲

simple cell到complex cell是怎麼實現的?

  1. 拿著某一斜率的filter去掃一遍全局的圖像
  2. 把圖像分割成nxn份,做pooling(可以是max pooling)

兩個二維向量卷積的意思就是掃一遍,類似於你在暗處拿著一個手電筒把一篇文章看一遍。掃的每一幀的具體操作就是相乘(找相似的特徵,僅僅是相乘就可以了)。卷積不是目的,掃一遍算相似度才是。

當做polling的時候,時空信息就消失了,本來是28x28維的空間,如果4x4方塊做pooling,就只剩下7x7的位置信息了。取而代之的,是feature域的信息。典型的「時空樣本變換」,不過這個是98年就做出來的,實在是很贊。

學習方案是構造一個損失函數,然後用SGD求解,因為有很多層,所以損失函數的梯度計算超級複雜,參數也很多很多,不過theano有一個庫,可以自動計算梯度。先進行符號計算,然後Sample一些輸入數據算梯度。

嗯,大概就是這個樣子吧。

改進

  • 肖達說:「Hinton組的cuda-convnet GPU卷積庫確實快,實現同樣結構的卷積神經網做MNIST手寫體分類,比theano的GPU卷積快5倍多。另一個發現,用sgd優化時,max kernel norm constraint比weight decay好用」。
  • cuda-convnet, https://code.google.com/p/cuda-convnet/
  • weight decay不知道theano用了沒有

參考

  1. http://yann.lecun.com/exdb/mnist/, mnist homepage
  2. http://deeplearning.net/tutorial/gettingstarted.html#early-stopping
  3. http://deeplearning.net/tutorial/lenet.html
  4. http://deeplearning.net/software/theano/tutorial/adding.html#adding-two-scalars

個人工具
名字空間
動作
導覽
工具箱