MLP多層感知機


@author:wepon

@blog:http://blog.csdn.net/u012162613/article/details/43221829

 轉載:http://blog.csdn.net/u012162613/article/details/43221829

本文介紹多層感知機算法,特別是詳細解讀其代碼實現,基於python theano,代碼來自:Multilayer Perceptron,如果你想詳細了解多層感知機算法,可以參考:UFLDL教程,或者參考本文第一部分的算法簡介。

經詳細注釋的代碼:放在我的github地址上,可下載

一、多層感知機(MLP)原理簡介

多層感知機(MLP,Multilayer Perceptron)也叫人工神經網絡(ANN,Artificial Neural Network),除了輸入輸出層,它中間可以有多個隱層,最簡單的MLP只含一個隱層,即三層的結構,如下圖:

 

從上圖可以看到,多層感知機層與層之間是全連接的(全連接的意思就是:上一層的任何一個神經元與下一層的所有神經元都有連接)。多層感知機最底層是輸入層,中間是隱藏層,最后是輸出層。

 

輸入層沒什么好說,你輸入什么就是什么,比如輸入是一個n維向量,就有n個神經元。

隱藏層的神經元怎么得來?首先它與輸入層是全連接的,假設輸入層用向量X表示,則隱藏層的輸出就是

f(W1X+b1),W1是權重(也叫連接系數),b1是偏置,函數f 可以是常用的sigmoid函數或者tanh函數:

 

 

       
 
 

最后就是輸出層,輸出層與隱藏層是什么關系?其實隱藏層到輸出層可以看成是一個多類別的邏輯回歸,也即softmax回歸,所以輸出層的輸出就是softmax(W2X1+b2),X1表示隱藏層的輸出f(W1X+b1)。

 


MLP整個模型就是這樣子的,上面說的這個三層的MLP用公式總結起來就是,函數G是softmax

 

因此,MLP所有的參數就是各個層之間的連接權重以及偏置,包括W1、b1、W2、b2。對於一個具體的問題,怎么確定這些參數?求解最佳的參數是一個最優化問題,解決最優化問題,最簡單的就是梯度下降法了(SGD):首先隨機初始化所有參數,然后迭代地訓練,不斷地計算梯度和更新參數,直到滿足某個條件為止(比如誤差足夠小、迭代次數足夠多時)。這個過程涉及到代價函數、規則化(Regularization)、學習速率(learning rate)、梯度計算等,本文不詳細討論,讀者可以參考本文頂部給出的兩個鏈接。

 

了解了MLP的基本模型,下面進入代碼實現部分。

 

 

二、多層感知機(MLP)代碼詳細解讀(基於python+theano)

 
再次說明,代碼來自: Multilayer Perceptron,本文只是做一個詳細解讀,如有錯誤,請不吝指出。
 
這個代碼實現的是一個三層的感知機,但是理解了代碼之后,實現n層感知機都不是問題,所以只需理解好這個三層的MLP模型即可。概括地說,MLP的輸入層X其實就是我們的訓練數據,所以輸入層不用實現,剩下的就是“輸入層到隱含層”,“隱含層到輸出層”這兩部分。上面介紹原理時已經說到了,“輸入層到隱含層”就是一個全連接的層,在下面的代碼中我們把這一部分定義為HiddenLayer。“隱含層到輸出層”就是一個分類器softmax回歸(也有人叫邏輯回歸),在下面的代碼中我們把這一部分定義為LogisticRegression。
 
代碼詳解開始:
 

(1)導入必要的python模塊

主要是numpy、theano,以及python自帶的os、sys、time模塊,這些模塊的使用在下面的程序中會看到。

 

[python]  view plain  copy
 
  1. import os  
  2. import sys  
  3. import time  
  4.   
  5. import numpy  
  6.   
  7. import theano  
  8. import theano.tensor as T  



 

(2)定義MLP模型(HiddenLayer+LogisticRegression)

這一部分定義MLP的基本“構件”,即上文一直在提的HiddenLayer和LogisticRegression

  • HiddenLayer
隱含層我們需要定義連接系數W、偏置b,輸入、輸出,具體的代碼以及解讀如下:
 
[python]  view plain  copy
 
  1. class HiddenLayer(object):  
  2.     def __init__(self, rng, input, n_in, n_out, W=None, b=None,  
  3.                  activation=T.tanh):  
  4.         """ 
  5. 注釋: 
  6. 這是定義隱藏層的類,首先明確:隱藏層的輸入即input,輸出即隱藏層的神經元個數。輸入層與隱藏層是全連接的。 
  7. 假設輸入是n_in維的向量(也可以說時n_in個神經元),隱藏層有n_out個神經元,則因為是全連接, 
  8. 一共有n_in*n_out個權重,故W大小時(n_in,n_out),n_in行n_out列,每一列對應隱藏層的每一個神經元的連接權重。 
  9. b是偏置,隱藏層有n_out個神經元,故b時n_out維向量。 
  10. rng即隨機數生成器,numpy.random.RandomState,用於初始化W。 
  11. input訓練模型所用到的所有輸入,並不是MLP的輸入層,MLP的輸入層的神經元個數時n_in,而這里的參數input大小是(n_example,n_in),每一行一個樣本,即每一行作為MLP的輸入層。 
  12. activation:激活函數,這里定義為函數tanh 
  13.         """  
  14.           
  15.         self.input = input   #類HiddenLayer的input即所傳遞進來的input  
  16.   
  17. """ 
  18. 注釋: 
  19. 代碼要兼容GPU,則W、b必須使用 dtype=theano.config.floatX,並且定義為theano.shared 
  20. 另外,W的初始化有個規則:如果使用tanh函數,則在-sqrt(6./(n_in+n_hidden))到sqrt(6./(n_in+n_hidden))之間均勻 
  21. 抽取數值來初始化W,若時sigmoid函數,則以上再乘4倍。 
  22. """  
  23. #如果W未初始化,則根據上述方法初始化。  
  24. #加入這個判斷的原因是:有時候我們可以用訓練好的參數來初始化W,見我的上一篇文章。  
  25.         if W is None:  
  26.             W_values = numpy.asarray(  
  27.                 rng.uniform(  
  28.                     low=-numpy.sqrt(6. / (n_in + n_out)),  
  29.                     high=numpy.sqrt(6. / (n_in + n_out)),  
  30.                     size=(n_in, n_out)  
  31.                 ),  
  32.                 dtype=theano.config.floatX  
  33.             )  
  34.             if activation == theano.tensor.nnet.sigmoid:  
  35.                 W_values *= 4  
  36.             W = theano.shared(value=W_values, name='W', borrow=True)  
  37.   
  38.         if b is None:  
  39.             b_values = numpy.zeros((n_out,), dtype=theano.config.floatX)  
  40.             b = theano.shared(value=b_values, name='b', borrow=True)  
  41.   
  42. #用上面定義的W、b來初始化類HiddenLayer的W、b  
  43.         self.W = W  
  44.         self.b = b  
  45.   
  46. #隱含層的輸出  
  47.         lin_output = T.dot(input, self.W) + self.b  
  48.         self.output = (  
  49.             lin_output if activation is None  
  50.             else activation(lin_output)  
  51.         )  
  52.   
  53. #隱含層的參數  
  54.         self.params = [self.W, self.b]  


  • LogisticRegression

邏輯回歸(softmax回歸),代碼詳解如下。

(如果你想詳細了解softmax回歸,可以參考: DeepLearning tutorial(1)Softmax回歸原理簡介+代碼詳解

 
[python]  view plain  copy
 
  1. """ 
  2. 定義分類層,Softmax回歸 
  3. 在deeplearning tutorial中,直接將LogisticRegression視為Softmax, 
  4. 而我們所認識的二類別的邏輯回歸就是當n_out=2時的LogisticRegression 
  5. """  
  6. #參數說明:  
  7. #input,大小就是(n_example,n_in),其中n_example是一個batch的大小,  
  8. #因為我們訓練時用的是Minibatch SGD,因此input這樣定義  
  9. #n_in,即上一層(隱含層)的輸出  
  10. #n_out,輸出的類別數   
  11. class LogisticRegression(object):  
  12.     def __init__(self, input, n_in, n_out):  
  13.   
  14. #W大小是n_in行n_out列,b為n_out維向量。即:每個輸出對應W的一列以及b的一個元素。    
  15.         self.W = theano.shared(  
  16.             value=numpy.zeros(  
  17.                 (n_in, n_out),  
  18.                 dtype=theano.config.floatX  
  19.             ),  
  20.             name='W',  
  21.             borrow=True  
  22.         )  
  23.   
  24.         self.b = theano.shared(  
  25.             value=numpy.zeros(  
  26.                 (n_out,),  
  27.                 dtype=theano.config.floatX  
  28.             ),  
  29.             name='b',  
  30.             borrow=True  
  31.         )  
  32.   
  33. #input是(n_example,n_in),W是(n_in,n_out),點乘得到(n_example,n_out),加上偏置b,  
  34. #再作為T.nnet.softmax的輸入,得到p_y_given_x  
  35. #故p_y_given_x每一行代表每一個樣本被估計為各類別的概率      
  36. #PS:b是n_out維向量,與(n_example,n_out)矩陣相加,內部其實是先復制n_example個b,  
  37. #然后(n_example,n_out)矩陣的每一行都加b  
  38.         self.p_y_given_x = T.nnet.softmax(T.dot(input, self.W) + self.b)  
  39.   
  40. #argmax返回最大值下標,因為本例數據集是MNIST,下標剛好就是類別。axis=1表示按行操作。  
  41.         self.y_pred = T.argmax(self.p_y_given_x, axis=1)  
  42.   
  43. #params,LogisticRegression的參數       
  44.         self.params = [self.W, self.b]  


ok!這兩個基本“構件”做好了,現在我們可以將它們“組裝”在一起。

我們要三層的MLP,則只需要HiddenLayer+LogisticRegression,

如果要四層的MLP,則為HiddenLayer+HiddenLayer+LogisticRegression........以此類推。

下面是三層的MLP:

 

 

[python]  view plain  copy
 
  1. #3層的MLP  
  2. class MLP(object):  
  3.     def __init__(self, rng, input, n_in, n_hidden, n_out):  
  4.           
  5.         self.hiddenLayer = HiddenLayer(  
  6.             rng=rng,  
  7.             input=input,  
  8.             n_in=n_in,  
  9.             n_out=n_hidden,  
  10.             activation=T.tanh  
  11.         )  
  12.   
  13. #將隱含層hiddenLayer的輸出作為分類層logRegressionLayer的輸入,這樣就把它們連接了  
  14.         self.logRegressionLayer = LogisticRegression(  
  15.             input=self.hiddenLayer.output,  
  16.             n_in=n_hidden,  
  17.             n_out=n_out  
  18.         )  
  19.   
  20.   
  21. #以上已經定義好MLP的基本結構,下面是MLP模型的其他參數或者函數  
  22.   
  23. #規則化項:常見的L1、L2_sqr  
  24.         self.L1 = (  
  25.             abs(self.hiddenLayer.W).sum()  
  26.             + abs(self.logRegressionLayer.W).sum()  
  27.         )  
  28.   
  29.         self.L2_sqr = (  
  30.             (self.hiddenLayer.W ** 2).sum()  
  31.             + (self.logRegressionLayer.W ** 2).sum()  
  32.         )  
  33.   
  34.   
  35. #損失函數Nll(也叫代價函數)  
  36.         self.negative_log_likelihood = (  
  37.             self.logRegressionLayer.negative_log_likelihood  
  38.         )  
  39.   
  40. #誤差        
  41.         self.errors = self.logRegressionLayer.errors  
  42.   
  43. #MLP的參數  
  44.         self.params = self.hiddenLayer.params + self.logRegressionLayer.params  
  45.         # end-snippet-3  

 

MLP類里面除了隱含層和分類層,還定義了損失函數、規則化項,這是在求解優化算法時用到的。

 

 

(3)將MLP應用於MNIST(手寫數字識別)

上面定義好了一個三層的MLP,接下來使用它在MNIST數據集上分類,MNIST是一個手寫數字0~9的數據集。
 
首先定義加載數據  mnist.pkl.gz 的函數load_data():
 
[python]  view plain  copy
 
  1. """ 
  2. 加載MNIST數據集 
  3. """  
  4. def load_data(dataset):  
  5.     # dataset是數據集的路徑,程序首先檢測該路徑下有沒有MNIST數據集,沒有的話就下載MNIST數據集  
  6.     #這一部分就不解釋了,與softmax回歸算法無關。  
  7.     data_dir, data_file = os.path.split(dataset)  
  8.     if data_dir == "" and not os.path.isfile(dataset):  
  9.         # Check if dataset is in the data directory.  
  10.         new_path = os.path.join(  
  11.             os.path.split(__file__)[0],  
  12.             "..",  
  13.             "data",  
  14.             dataset  
  15.         )  
  16.         if os.path.isfile(new_path) or data_file == 'mnist.pkl.gz':  
  17.             dataset = new_path  
  18.   
  19.     if (not os.path.isfile(dataset)) and data_file == 'mnist.pkl.gz':  
  20.         import urllib  
  21.         origin = (  
  22.             'http://www.iro.umontreal.ca/~lisa/deep/data/mnist/mnist.pkl.gz'  
  23.         )  
  24.         print 'Downloading data from %s' % origin  
  25.         urllib.urlretrieve(origin, dataset)  
  26.   
  27.     print '... loading data'  
  28. #以上是檢測並下載數據集mnist.pkl.gz,不是本文重點。下面才是load_data的開始  
  29.       
  30. #從"mnist.pkl.gz"里加載train_set, valid_set, test_set,它們都是包括label的  
  31. #主要用到python里的gzip.open()函數,以及 cPickle.load()。  
  32. #‘rb’表示以二進制可讀的方式打開文件  
  33.     f = gzip.open(dataset, 'rb')  
  34.     train_set, valid_set, test_set = cPickle.load(f)  
  35.     f.close()  
  36.      
  37.   
  38. #將數據設置成shared variables,主要時為了GPU加速,只有shared variables才能存到GPU memory中  
  39. #GPU里數據類型只能是float。而data_y是類別,所以最后又轉換為int返回  
  40.     def shared_dataset(data_xy, borrow=True):  
  41.         data_x, data_y = data_xy  
  42.         shared_x = theano.shared(numpy.asarray(data_x,  
  43.                                                dtype=theano.config.floatX),  
  44.                                  borrow=borrow)  
  45.         shared_y = theano.shared(numpy.asarray(data_y,  
  46.                                                dtype=theano.config.floatX),  
  47.                                  borrow=borrow)  
  48.         return shared_x, T.cast(shared_y, 'int32')  
  49.   
  50.   
  51.     test_set_x, test_set_y = shared_dataset(test_set)  
  52.     valid_set_x, valid_set_y = shared_dataset(valid_set)  
  53.     train_set_x, train_set_y = shared_dataset(train_set)  
  54.   
  55.     rval = [(train_set_x, train_set_y), (valid_set_x, valid_set_y),  
  56.             (test_set_x, test_set_y)]  
  57.     return rval  


加載了數據,可以開始訓練這個模型了,以下就是主體函數test_mlp(),將MLP用在MNIST上:
 
[python]  view plain  copy
 
  1. #test_mlp是一個應用實例,用梯度下降來優化MLP,針對MNIST數據集  
  2. def test_mlp(learning_rate=0.01, L1_reg=0.00, L2_reg=0.0001, n_epochs=10,  
  3.              dataset='mnist.pkl.gz', batch_size=20, n_hidden=500):  
  4.     """ 
  5. 注釋: 
  6. learning_rate學習速率,梯度前的系數。 
  7. L1_reg、L2_reg:正則化項前的系數,權衡正則化項與Nll項的比重 
  8. 代價函數=Nll+L1_reg*L1或者L2_reg*L2_sqr 
  9. n_epochs:迭代的最大次數(即訓練步數),用於結束優化過程 
  10. dataset:訓練數據的路徑 
  11. n_hidden:隱藏層神經元個數 
  12. batch_size=20,即每訓練完20個樣本才計算梯度並更新參數 
  13.    """  
  14.   
  15. #加載數據集,並分為訓練集、驗證集、測試集。  
  16.     datasets = load_data(dataset)  
  17.     train_set_x, train_set_y = datasets[0]  
  18.     valid_set_x, valid_set_y = datasets[1]  
  19.     test_set_x, test_set_y = datasets[2]  
  20.   
  21.   
  22. #shape[0]獲得行數,一行代表一個樣本,故獲取的是樣本數,除以batch_size可以得到有多少個batch  
  23.     n_train_batches = train_set_x.get_value(borrow=True).shape[0] / batch_size  
  24.     n_valid_batches = valid_set_x.get_value(borrow=True).shape[0] / batch_size  
  25.     n_test_batches = test_set_x.get_value(borrow=True).shape[0] / batch_size  
  26.   
  27.     ######################  
  28.     # BUILD ACTUAL MODEL #  
  29.     ######################  
  30.     print '... building the model'  
  31.   
  32. #index表示batch的下標,標量  
  33. #x表示數據集  
  34. #y表示類別,一維向量  
  35.     index = T.lscalar()    
  36.     x = T.matrix('x')   
  37.     y = T.ivector('y')    
  38.                          
  39.   
  40.     rng = numpy.random.RandomState(1234)  
  41. #生成一個MLP,命名為classifier  
  42.     classifier = MLP(  
  43.         rng=rng,  
  44.         input=x,  
  45.         n_in=28 * 28,  
  46.         n_hidden=n_hidden,  
  47.         n_out=10  
  48.     )  
  49.   
  50. #代價函數,有規則化項  
  51. #用y來初始化,而其實還有一個隱含的參數x在classifier中  
  52.     cost = (  
  53.         classifier.negative_log_likelihood(y)  
  54.         + L1_reg * classifier.L1  
  55.         + L2_reg * classifier.L2_sqr  
  56.     )  
  57.   
  58.   
  59. #這里必須說明一下theano的function函數,givens是字典,其中的x、y是key,冒號后面是它們的value。  
  60. #在function被調用時,x、y將被具體地替換為它們的value,而value里的參數index就是inputs=[index]這里給出。  
  61. #下面舉個例子:  
  62. #比如test_model(1),首先根據index=1具體化x為test_set_x[1 * batch_size: (1 + 1) * batch_size],  
  63. #具體化y為test_set_y[1 * batch_size: (1 + 1) * batch_size]。然后函數計算outputs=classifier.errors(y),  
  64. #這里面有參數y和隱含的x,所以就將givens里面具體化的x、y傳遞進去。  
  65.     test_model = theano.function(  
  66.         inputs=[index],  
  67.         outputs=classifier.errors(y),  
  68.         givens={  
  69.             x: test_set_x[index * batch_size:(index + 1) * batch_size],  
  70.             y: test_set_y[index * batch_size:(index + 1) * batch_size]  
  71.         }  
  72.     )  
  73.   
  74.     validate_model = theano.function(  
  75.         inputs=[index],  
  76.         outputs=classifier.errors(y),  
  77.         givens={  
  78.             x: valid_set_x[index * batch_size:(index + 1) * batch_size],  
  79.             y: valid_set_y[index * batch_size:(index + 1) * batch_size]  
  80.         }  
  81.     )  
  82.   
  83. #cost函數對各個參數的偏導數值,即梯度,存於gparams  
  84.     gparams = [T.grad(cost, param) for param in classifier.params]  
  85.       
  86. #參數更新規則  
  87. #updates[(),(),()....],每個括號里面都是(param, param - learning_rate * gparam),即每個參數以及它的更新公式  
  88.     updates = [  
  89.         (param, param - learning_rate * gparam)  
  90.         for param, gparam in zip(classifier.params, gparams)  
  91.     ]  
  92.   
  93.     train_model = theano.function(  
  94.         inputs=[index],  
  95.         outputs=cost,  
  96.         updates=updates,  
  97.         givens={  
  98.             x: train_set_x[index * batch_size: (index + 1) * batch_size],  
  99.             y: train_set_y[index * batch_size: (index + 1) * batch_size]  
  100.         }  
  101.     )  
  102.   
  103.   
  104.     ###############  
  105.     # 開始訓練模型 #  
  106.     ###############  
  107.     print '... training'  
  108.       
  109.   
  110.   
  111.     patience = 10000    
  112.     patience_increase = 2    
  113. #提高的閾值,在驗證誤差減小到之前的0.995倍時,會更新best_validation_loss    
  114.     improvement_threshold = 0.995    
  115. #這樣設置validation_frequency可以保證每一次epoch都會在驗證集上測試。    
  116.     validation_frequency = min(n_train_batches, patience / 2)  
  117.     
  118.   
  119.     best_validation_loss = numpy.inf  
  120.     best_iter = 0  
  121.     test_score = 0.  
  122.     start_time = time.clock()  
  123.       
  124. #epoch即訓練步數,每個epoch都會遍歷所有訓練數據  
  125.     epoch = 0  
  126.     done_looping = False  
  127.   
  128.   
  129. #下面就是訓練過程了,while循環控制的時步數epoch,一個epoch會遍歷所有的batch,即所有的圖片。  
  130. #for循環是遍歷一個個batch,一次一個batch地訓練。for循環體里會用train_model(minibatch_index)去訓練模型,  
  131. #train_model里面的updatas會更新各個參數。  
  132. #for循環里面會累加訓練過的batch數iter,當iter是validation_frequency倍數時則會在驗證集上測試,  
  133. #如果驗證集的損失this_validation_loss小於之前最佳的損失best_validation_loss,  
  134. #則更新best_validation_loss和best_iter,同時在testset上測試。  
  135. #如果驗證集的損失this_validation_loss小於best_validation_loss*improvement_threshold時則更新patience。  
  136. #當達到最大步數n_epoch時,或者patience<iter時,結束訓練  
  137.     while (epoch < n_epochs) and (not done_looping):  
  138.         epoch = epoch + 1  
  139.         for minibatch_index in xrange(n_train_batches):#訓練時一個batch一個batch進行的  
  140.   
  141.             minibatch_avg_cost = train_model(minibatch_index)  
  142.             # 已訓練過的minibatch數,即迭代次數iter  
  143.             iter = (epoch - 1) * n_train_batches + minibatch_index  
  144. #訓練過的minibatch數是validation_frequency倍數,則進行交叉驗證  
  145.             if (iter + 1) % validation_frequency == 0:  
  146.                 # compute zero-one loss on validation set  
  147.                 validation_losses = [validate_model(i) for i  
  148.                                      in xrange(n_valid_batches)]  
  149.                 this_validation_loss = numpy.mean(validation_losses)  
  150.   
  151.                 print(  
  152.                     'epoch %i, minibatch %i/%i, validation error %f %%' %  
  153.                     (  
  154.                         epoch,  
  155.                         minibatch_index + 1,  
  156.                         n_train_batches,  
  157.                         this_validation_loss * 100.  
  158.                     )  
  159.                 )  
  160. #當前驗證誤差比之前的都小,則更新best_validation_loss,以及對應的best_iter,並且在tsetdata上進行test  
  161.                 if this_validation_loss < best_validation_loss:  
  162.                     if (  
  163.                         this_validation_loss < best_validation_loss *  
  164.                         improvement_threshold  
  165.                     ):  
  166.                         patience = max(patience, iter * patience_increase)  
  167.   
  168.                     best_validation_loss = this_validation_loss  
  169.                     best_iter = iter  
  170.   
  171.                     test_losses = [test_model(i) for i  
  172.                                    in xrange(n_test_batches)]  
  173.                     test_score = numpy.mean(test_losses)  
  174.   
  175.                     print(('     epoch %i, minibatch %i/%i, test error of '  
  176.                            'best model %f %%') %  
  177.                           (epoch, minibatch_index + 1, n_train_batches,  
  178.                            test_score * 100.))  
  179. #patience小於等於iter,則終止訓練  
  180.             if patience <= iter:  
  181.                 done_looping = True  
  182.                 break  
  183.   
  184.     end_time = time.clock()  
  185.     print(('Optimization complete. Best validation score of %f %% '  
  186.            'obtained at iteration %i, with test performance %f %%') %  
  187.           (best_validation_loss * 100., best_iter + 1, test_score * 100.))  
  188.     print >> sys.stderr, ('The code for file ' +  
  189.                           os.path.split(__file__)[1] +  
  190.                           ' ran for %.2fm' % ((end_time - start_time) / 60.))  


文章完,經詳細注釋的代碼: 放在我的github地址上,可下載


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM