| 基于《神经网络和深度学习》这本绝好的教材提供的相关资料和代码,我们自己动手编写“随机取样的梯度下降神经网络”。为了更好地说明问题,我们先从简单的开始:  
 1、sigmod函数,基本上就是基于定义的; 
  
 
     
   
   
    
    #########helper函数######## 
     
    #计算sigmoid,这个函数来自定义 
     
    def 
    sigmoid( 
    z): 
     
    return 
    1.0/( 
    1.0+np.exp(-z)) 
     
    #计算sigmoid的导数,这个函数可以被证明 
     
    def 
    sigmoid_prime( 
    z): 
     
    return sigmoid(z)*( 
    1 - sigmoid(z)) 
    
       
     
 2、构造函数 
  
  
   
   ###########Main函数######## 
    
   #使用例子 net = GoNetwork([2, 3, 1]) 
    
   class 
   GoNetwork( 
   object): 
    
   def 
   __init__( 
   self, 
   sizes): 
   #构造函数 
    
   self.num_layers = 
   len(sizes) 
   #层数 
    
   self.sizes = sizes 
   #每层size 
    
   #随机生成子节点 
    
   self.biases= [np.random.randn(y, 
   1) 
   for y 
   in sizes[ 
   1:]] 
    
   # net.weights[1] 是一个存储着连接第二层和第三层神经元权重的 Numpy 矩阵。 
    
   self.weights = [np.random.randn(y, x) 
    
   for x, y 
   in 
   zip(sizes[:- 
   1], sizes[ 
   1:])] 
    
  
  这个地方有以下几个地方,一个是在Python中类和类的构造函数是这样定义的;二个是Python如何体现出其强大的数据处理能力的。 
   
  这里,如果 
   
  sizes = [2,  3,  1] 
   
  则sizes [1:] = [3,1] 
  
     
   
   
   
   numpy.random.randn(d0, d1, ..., dn) 这个函数的作用就是从标准正态分布中返回一个或多个样本值,比如  
  
    bbb = [np.random.randn( 
   3, 
   2)] 
    
  表示的是生成3X2的随机序列,可以这样来使用,就是加上偏置了 
   
   
     
   
   2.5 * np.random.randn(2, 4) + 3 
         返回:  
    array([[ 4.128****53,  1.764****44 ,  2.732****92,  2.90839231],
            [ 
     0.174****86,   
     4.92026887,  1.574****66, -0.4305991 ]]) 
      
          
  
    aaa =[ np.random.randn(y, 
   1) 
   for y 
   in sizes[ 
   1:]] 
    
  这是一种Python的连写方法,这里就是对[3,1]分别生成随机序列。这个随机是用来干什么的?就是随机的权值。 
   
  描述 zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表 
   
  这里 
   
  zip(sizes[:-1], sizes[1:]) 
   
  表示的是将第1、2层之间,2、3层之间的全连接生成随机权值。 
   
    
   
  3、前向网络,主要用于测试当前网络 
   
   
    
    def 
    feedforward( 
    self, 
    a): 
     
    for b,w 
    in 
    zip( 
    self.biases, 
    self.weights): 
    
     a = sigmoid(np.dot(w,a)+b) 
     
    return a 
     
  非常直接的按照定义,进行上一层到下一层的前向 
  计算,注意这里得到的a也是x行1列的一个矩阵 
   
    
   
  4、评价函数,基本上也是按照定义进行设定的 
   
   
    
    def 
    evaluate( 
    self, 
    test_data): 
    
     test_results = [(np.argmax( 
    self.feedforward(x)), y) 
    #这里需要注意feedforward的参数x,实际上它是一个in/out参数。 
     
    for (x, y) 
    in test_data] 
     
    return 
    sum( 
    int(x == y) 
    for (x, y) 
    in test_results) 
    #做出了正确的预测 
     
   
   这个地方调用了feedforward(x),并且和y进行比较,得到准确比对有哪些。应该说代码非常精简。 
    
     
    
    
    5、代价函数 
     
     
      
      #cost代价函数 
       
      def 
      cost_derivative( 
      self, 
      output_activations, 
      y): 
       
      return (output_activations-y) 
       
      
     
    以上几项都是非常好理解的,基本上你看到的立刻就能够理解,需要补充的知识并不是很多。结合上一课的相关知识,我们这里提出的所谓随机,就是提取很小的一块数据,而后进行计算梯度下降参数,更新网络的权重和偏置 
     
     
      
      def 
      update_mini_batch( 
      self, 
      mini_batch, 
      eta): 
      
       nabla_b = [np.zeros(b.shape) 
      for b 
      in 
      self.biases] 
      #生成b和w形状的以0填充的矩阵 
      
       nabla_w = [np.zeros(w.shape) 
      for w 
      in 
      self.weights] 
       
      for x, y 
      in mini_batch: 
      
       delta_nabla_b, delta_nabla_w = 
      self.backprop(x, y) 
      #理解反向传播就是一种快速计算梯度的方法 
      
       nabla_b = [nb+dnb 
      for nb, dnb 
      in 
      zip(nabla_b, delta_nabla_b)] 
      
       nabla_w = [nw+dnw 
      for nw, dnw 
      in 
      zip(nabla_w, delta_nabla_w)] 
       
      self.weights = [w-(eta/ 
      len(mini_batch))*nw 
       
      for w, nw 
      in 
      zip( 
      self.weights, nabla_w)] 
       
      self.biases = [b-(eta/ 
      len(mini_batch))*nb 
       
      for b, nb 
      in 
      zip( 
      self.biases, nabla_b)] 
       
    其中 
     
    
         nabla_b = [np.zeros(b.shape) for b in self.biases] 
     
         nabla_w = [np.zeros(w.shape) for w in self.weights] 
     
      生成b和w形状的以0填充的矩阵,这里就是用来填充原始数据的。 
     
      在这个小循环里面,我们可以以“黑箱”的形式来理解backprop函数,就是一种用来计算最快下降梯度的方法。 
      
     
        for x, y in mini_batch: 
      
                   delta_nabla_b, delta_nabla_w = self.backprop(x, y) 
      
                   nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)] 
      
                   nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)] 
      
      在这里,我们便历所有的mini_batch,注意在上面这行代码中, 
     
      而后,引入eta,以这个梯度作为 
     delta_nabla_b, 
      delta_nabla_w 
      的初始值都为空. 
     
      这样,我们按照定义进行了一次小数据的更新。其能够完成,是因为backprop为我们成功计算了代价函数的两个梯度。 
      
     
     6、后向传播函数,其目的是进行梯度下降计算,是最为复杂的部分 
      
      
       
       #反向传播就是一种快速计算代价函数梯度的方法,也就是计算delta的一种方法 
        
       def 
       backprop( 
       self, 
       x, 
       y): 
        
       #都以空矩阵来进行初始化 
       
        nabla_b = [np.zeros(b.shape) 
       for b 
       in 
       self.biases] 
       
        nabla_w = [np.zeros(w.shape) 
       for w 
       in 
       self.weights] 
        
       # feedforward 
       
        activation = x 
       
        activations = [x] 
       # list to store all the activations, layer by layer 
       
        zs = [] 
       # list to store all the z vectors, layer by layer 
        
       for b, w 
       in 
       zip( 
       self.biases, 
       self.weights): 
       
        z = np.dot(w, activation)+b 
       #前向传播 
       
        zs.append(z) 
       
        activation = sigmoid(z) 
       
        activations.append(activation) 
        
       # backward pass 
       
        delta = 
       self.cost_derivative(activations[- 
       1], y) * \ 
       
        sigmoid_prime(zs[- 
       1]) 
       
        nabla_b[- 
       1] = delta 
       
        nabla_w[- 
       1] = np.dot(delta, activations[- 
       2].transpose()) 
       
          
        
       for l 
       in 
       range( 
       2, 
       self.num_layers): 
       
        z = zs[-l] 
       
        sp = sigmoid_prime(z) 
       
        delta = np.dot( 
       self.weights[-l+ 
       1].transpose(), delta) * sp 
       
        nabla_b[-l] = delta 
       
        nabla_w[-l] = np.dot(delta, activations[-l- 
       1].transpose()) 
        
       return (nabla_b, nabla_w) 
       
        
      
     其中内容比较复杂,一条一条进行解释  
      
      
      
        nabla_b = [np.zeros(b.shape) 
       for b 
       in 
       self.biases] 
       
        nabla_w = [np.zeros(w.shape) 
       for w 
       in 
       self.weights] 
        
     生成空矩阵 
      
      
       
       # feedforward 
       
        activation = x 
       
        activations = [x] 
       # list to store all the activations, layer by layer 
       
        zs = [] 
       # list to store all the z vectors, layer by layer 
        
       for b, w 
       in 
       zip( 
       self.biases, 
       self.weights): 
       
        z = np.dot(w, activation)+b 
       #前向传播 
       
        zs.append(z) 
       
        activation = sigmoid(z) 
       
        activations.append(activation) 
       
        
      
     前向计算,保存所有b、w和 z。后面的几行代码,主要都是和4个公式严格对应的 
      
     
       delta = 
      self.cost_derivative(activations[- 
      1], y) * sigmoid_prime(zs[- 
      1]) 
      
        
      
     对应BP1 
      
       
      
      
      
        nabla_b[- 
       1] = delta 
       
        nabla_w[- 
       1] = np.dot(delta, activations[- 
       2].transpose()) 
        
       
      
     分别对应BP3和BP4,就是最后来计算具体的梯度值 
      
       
      
     
       delta = np.dot( 
      self.weights[-l+ 
      1].transpose(), delta) * sp 
       
       
      
     对应BP2,反向计算。 
      
       
      
     7、 
     随机梯度下降算法,到了这里也就是将上面的合起来 
      
      
       
       #随机梯度下降算法 
        
       def 
       SGD( 
       self, 
       training_data, 
       epochs, 
       mini_batch_size, 
       eta, 
       test_data= 
       None): 
       
        training_data = 
       list(training_data) 
       
        n = 
       len(training_data) 
        
       if test_data: 
       
        test_data = 
       list(test_data) 
       
        n_test = 
       len(test_data) 
        
       #⾸先随机地将训练数据打乱 
        
       for j 
       in 
       range(epochs): 
       
        random.shuffle(training_data) 
        
       #再将它分成多个适当⼤⼩的⼩批量数据 
       
        mini_batches = [ 
       
        training_data[k:k+mini_batch_size] 
        
       for k 
       in 
       range( 
       0, n, mini_batch_size)] 
        
       for mini_batch 
       in mini_batches: 
       #最主要的一行代码 
        
       self.update_mini_batch(mini_batch, eta) 
        
       if test_data: 
        
       print( 
       "Epoch  
       {} 
        :  
       {} 
        /  
       {} 
       ".format(j, 
       self.evaluate(test_data),n_test)) 
        
       else: 
        
       print( 
       "Epoch  
       {} 
        complete".format(j)) 
        
     主要优化的地方,就是将原较大的数据集分成多个部分,而后遍历所有的部分,进行梯度下降运算,并且打印比较的结果。应该说再次体现了Python强大的集成编码能力。 
      
       
     
 
 
 
 |