日曜技術者のメモ

趣味でやった事のメモ書きです。

多層パーセプトロンをPythonで実装してみた-1-

A Neural Network Playgroundのデモが素晴らしかったので
Pythonを使ってそれっぽく動く物を作ってみた。

開発環境

  • Visual studio 2015 community
    • PTVS
  • Python3.5.2
    • numpy 1.11.1
    • scipy 0.18.0
    • scikit-learn 0.18
    • matplotlib 1.5.3

多層パーセプトロン

ニューラルネットワークはまったくの初心者だったので色々調べると
多層パーセプトロンを使えばできそうなのでそれについて調べた。

順方向の計算は簡単

f:id:ginnyu-tei:20161008103403g:plain:w400

逆誤差伝播法はかなり難しい・・・
出力層はなんとか自分で計算できたが隠れ層は無理。
↓も正解か自信がない(手書き文字認識は問題なく出来たから大丈夫だとは思う)

+出力層
f:id:ginnyu-tei:20161008111856g:plain:w400

+隠れ層
f:id:ginnyu-tei:20161008110211g:plain:w400

学習は1データ(x,yの値と期待値y)毎に更新するオンライン学習
重み更新は学習率固定

ニューロンレイヤークラス

まず基本となるレイヤークラスを作成して
それを継承して出力層クラスと隠れ層クラスを作成した。
playgroundのデモは値の範囲が-1~+1までなので活性化関数はtanh(x)を使った。

※11/1複数の出力層に対応する様修正

# -*- coding: utf-8 -*-

import numpy as np

class neuronLayer(object):
  """レイヤー基本クラス"""
  def __init__(self,input_num,neuron_num,output_num,e,bias=None):
    self._input_num = input_num
    self._neuron_num = neuron_num
    self._ouput_num = output_num
    self._e = e

    if bias is None:
      self._isBias = False
    else:
      self._isBias = True
      #biasを追加する拡張
      self._input_num += 1
      self._bias = np.ones([self._neuron_num,1]).reshape(self._neuron_num,1)

    #初期化
    self._x = np.zeros(self._input_num*self._neuron_num).reshape(self._neuron_num,self._input_num)
    self._s = np.zeros(self._neuron_num)
    self._y = np.zeros(self._ouput_num*self._neuron_num).reshape(self._neuron_num,self._ouput_num)
    self._delWdelE = np.zeros(self._input_num*self._neuron_num).reshape(self._neuron_num,self._input_num)
    self._delSdelE = np.zeros(self._ouput_num*self._neuron_num).reshape(self._neuron_num,self._ouput_num)

    #重みランダム初期化
    #self._weight = np.random.rand(self._neuron_num,self._input_num)*2-1.0
    #http://deeplearning.net/tutorial/mlp.html
    self._weight = np.asarray(
                np.random.uniform(
                    low=-np.sqrt(6. / (self._input_num + self._ouput_num)),
                    high=np.sqrt(6. / (self._input_num + self._ouput_num)),
                    size=(self._neuron_num, self._input_num)
                )
            )

  def activationFunction(self,x):
    return self.tanh(x)

  def dActivationFunction(self,x):
    return self.dtanh(x)

  def sigmoid(self,x):
    return(1.0/(1.0+np.exp(-x)))

  def dsigmoid(self,x):
    return self.sigmoid(x)*(1-self.sigmoid(x))

  def tanh(self,x):
    return np.tanh(x)

  def dtanh(self,x):
    return 1.0-self.tanh(x)*self.tanh(x)
  
  def ReLu(self,x):
    return x * (x > 0)

  def dReLu(self,x):
    return 1.0 * (x > 0)

  def foward(self,x):
    #biasが有効のときはbiasを追加
    if(self._isBias == True):
      self._x = np.hstack((x,self._bias))
    else:
      self._x = x

    # s = Σ(x*w)
    if(self._neuron_num == 1):
      self._s = (self._x * self._weight).sum()
    else:
      self._s = (self._x * self._weight).sum(axis=1)+np.zeros(self._ouput_num).reshape(self._ouput_num,1)
    self._y = self.activationFunction(self._s)

  def updateWeight(self):
    #print(self._weight)
    self._weight -= self._e * self._delWdelE

  def getY(self):
    return self._y
  
  def getWeight(self):
    if(self._isBias == True):
      #前段はbiasの重みが不要なので削除して返す
      return np.delete(self._weight,self._input_num-1,1)
    else:
      return self._weight

  def getDelSdelE(self):
    return self._delSdelE


class inputLayer(object):
  def __init__(self,input_num,neuron_num,output_num):
    self._input_num = input_num
    self._neuron_num = neuron_num
    self._ouput_num = output_num

    self._x = np.zeros(self._ouput_num).reshape(self._ouput_num,1)
    self._y = np.zeros(self._input_num*self._ouput_num).reshape(self._input_num,self._ouput_num)

  def foward(self,x):
      self._y = x + self._x

  def getY(self):
    return self._y


class outputLayer(neuronLayer):
  def backprop(self,y):
    if(self._neuron_num==1):
      self._delSdelE = (self._y -y) * self.dActivationFunction(self._s)
    else:
      self._delSdelE = (self._y -y) * self.dActivationFunction(self._s)
      self._delSdelE = self._delSdelE.T

    self._delWdelE = self._delSdelE * self._x



class hiddenLayer(neuronLayer):
  def backdrop(self,delSdelE,wn):
    if(self._ouput_num==1):
      self._delSdelE = (delSdelE * wn).sum() * self.dActivationFunction(self._s)
      self._delSdelE = self._delSdelE.reshape(self._neuron_num,1)
    else:
      self._delSdelE = (delSdelE * wn).sum(axis=0).reshape(1,self._neuron_num)
      self._delSdelE *= self.dActivationFunction(self._s[0,:])
      self._delSdelE = self._delSdelE.T

    self._delWdelE = self._delSdelE * self._x

実際に分類したりMNISTで手書き文字認識したりは次の記事に書く。