Edited at

fortranでパーセプトロン

More than 1 year has passed since last update.


はじめに

最近流行りの機械学習について、大学院修了間際にテキストを入手していた。機械学習といえば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


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

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


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



参考文献