zoukankan      html  css  js  c++  java
  • DeepLearning tutorial(3)MLP多层感知机原理简介+代码详解

    本文介绍多层感知机算法,特别是详细解读其代码实现,基于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 plaincopy
     
    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 plaincopy
     
    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 plaincopy
     
    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 plaincopy
     
    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 plaincopy
     
    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 plaincopy
     
    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地址上,可下载
    如果有任何错误,或者有说不清楚的地方,欢迎评论留言。
  • 相关阅读:
    linux部署docker镜像
    SpringBoot 定时任务篇
    POST形式 soapUI调用WebService的restful接口,传入json参数,并且返回json
    Java操作FTP工具类(实例详解)
    MyBatis逆向工程:根据table生成Model、Mapper、Mapper.xml
    Netty完成网络通信(二)
    NIO完成网络通信(一)
    MySQL5.6数据库8小时内无请求自动断开连接
    Eclipse集成Tomcat插件(特别简单)
    程序从sqlserver2008搬家到MySQL5.6
  • 原文地址:https://www.cnblogs.com/anyview/p/5014676.html
Copyright © 2011-2022 走看看