fortranでパーセプトロン

#はじめに
最近流行りの機械学習について、大学院修了間際に[テキスト](https://www.amazon.co.jp/Python%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0-%E9%81%94%E4%BA%BA%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%A8%E3%83%B3%E3%83%86%E3%82%A3%E3%82%B9%E3%83%88%E3%81%AB%E3%82%88%E3%82%8B%E7%90%86%E8%AB%96%E3%81%A8%E5%AE%9F%E8%B7%B5-impress-top-gear/dp/4844380605)を入手していた。機械学習といえばpythonだが、テキストのコードを丸ごとコピペしてもおもしろくないし、~~誰もやってなさそうなので、~~fortranで実装してみることにした。

#パーセプトロン
詳しくは他のサイトに譲るとして、簡単な説明を記す。

パーセプトロンは機械学習の最も基本的なもので、1サンプルを表すベクトル $\vec{x_i} = \\{ x^1_i, x^2_i, \cdots ,x^n_i\\}$とその正誤$y_i$からなるデータセットを何周か読み込み、サンプルを2つに分類するものだ。

パーセプトロン自体は内部状態$\vec{w}$を持ち、入力されたサンプルベクトルに対して内積$\vec{w}\cdot\vec{x}$を計算する。その値を活性化関数$\phi(z)$に通し、与えられた正解$y$と比較、差分$y-\phi(z)$に応じて$\vec{w}$を修正する。

#ソースコード
下記githubにあげてある。
https://github.com/Bluepost59/fortran_perceptron

```fortran:mod_prcptr.f90
module mod_prcptr
  implicit none
  !----------------------------------------------------------------------
  ! header of perceptron
  !----------------------------------------------------------------------
  type perceptron
     integer :: dimsion ! dimension of sample data
     double precision,allocatable :: w(:)
     double precision :: eta
     double precision :: epoch
   contains
     procedure :: learn => perceptron_learn
  end type perceptron

  interface perceptron
     module procedure init_perceptron
  end interface perceptron

contains
  !======================================================================
  ! implementations of perceptron
  !----------------------------------------------------------------------
  ! constructer
  !----------------------------------------------------------------------
  type(perceptron) function init_perceptron(nnn,myeta)
    integer :: nnn
    double precision :: myeta

    allocate(init_perceptron%w(nnn))
    init_perceptron%w = 0d0

    init_perceptron%dimsion = nnn
    init_perceptron%eta = myeta

    init_perceptron%epoch = 0
  end function init_perceptron
  !----------------------------------------------------------------------
  ! learning
  !----------------------------------------------------------------------
  subroutine perceptron_learn(self,mydata,y,answered_error)
    class(perceptron) :: self
    double precision :: mydata(:) ! 1 sample (not dataset)
    double precision :: y         ! true answer
    logical,optional :: answered_error              ! error

    double precision :: z = 0d0
    double precision :: output
    integer :: i

    answered_error = .false.

    if(size(mydata) /= self%dimsion) return
    z=0d0
    do i=1, size(mydata)
       z = z + mydata(i) *self%w(i)
    end do

    output = phi(z)

    if(output /= y) answered_error = .true.

    do i=1, self%dimsion
       self%w(i) = self%w(i) + self%eta *(y-output) *mydata(i)
    end do

  end subroutine perceptron_learn
  !
  !======================================================================
  ! other functions
  !----------------------------------------------------------------------
  ! activation function
  !----------------------------------------------------------------------
  double precision function phi(x)
    double precision :: x

    if(x<=0) then
       phi = -1
    else
       phi = 1
    end if
  end function phi


end module mod_prcptr
```
走り書きなので、仕様は自分でもかなりめちゃくちゃだと思う。```perceptron%learn```でサンプルデータを一つ読み込む（データセットではない）。

テストコードではtest.f90でデータファイルを1周読み込む、という動作を何エポックも繰り返すようにしてある。（これもperceptronに含めるべきだったと思う）。

#動作結果
```mydata.txt
5.0 -7.0 -1
6.0 -8.0 -1
-1.3 6.3 1
-5.3 0.2 1
9.3 -2.2 -1
```
![out.png](https://qiita-image-store.s3.amazonaws.com/0/107045/31b7b4eb-8c8d-40be-c370-80347a1a25e3.png)

点と線が被っているように見えるが、なんとか分類はできているようだ。

##error
~~なぜか5個ほどのデータで2で収束する。データが悪いのかコードが悪いのかはわからない。~~
コードが間違っていました。修正したら1エポック目で1つ誤分類した後2エポック目以降は0だった。

#まとめ
とりあえずいい感じの線を引くところまではできた。今回は力尽きたので時間とやる気ができたらもうちょっとちゃんとやりたい。

~~fortranにはpythonのlistやC++のvectorみたいなコンテナがないのがつらすぎる~~

#bugfix
subroutine内の変数宣言で同時に初期化すると、そのsubroutineが2回以上呼び出されるとき2回目以降初期化されない（値が受け継がれてしまう）ようです。
septcolorさんの指摘で気づきました。ありがとうございました。

```smp.f90
program main
  integer i

  ! 10回ずつ呼び出してみる
  do i=1,10
     call smp_ng()
  end do

  do i=1,10
     call smp_ok()
  end do
  
contains
  !=====================================
  ! nが初期化されず1,2,3,...と増えてしまう！
  subroutine smp_ng()
  !-----------------------
    integer :: n=0
  !-----------------------
    n=n+1
    write(6,*) n
  end subroutine smp_ng
  !=====================================
  ! ちゃんと毎回nが初期化され、常に1を返す
  subroutine smp_ok()
  !-----------------------
    integer :: n
    n=0
  !-----------------------

    n=n+1
    write(6,*)n
  end subroutine smp_ok
end program main

```


#参考文献
* [Python機械学習プログラミング 達人データサイエンティストによる理論と実践 (impress top gear)](https://www.amazon.co.jp/Python%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0-%E9%81%94%E4%BA%BA%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%A8%E3%83%B3%E3%83%86%E3%82%A3%E3%82%B9%E3%83%88%E3%81%AB%E3%82%88%E3%82%8B%E7%90%86%E8%AB%96%E3%81%A8%E5%AE%9F%E8%B7%B5-impress-top-gear/dp/4844380605)