#はじめに
最近流行りの機械学習について、大学院修了間際にテキストを入手していた。機械学習といえば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
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に含めるべきだったと思う)。
#動作結果
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
点と線が被っているように見えるが、なんとか分類はできているようだ。
##error
なぜか5個ほどのデータで2で収束する。データが悪いのかコードが悪いのかはわからない。
コードが間違っていました。修正したら1エポック目で1つ誤分類した後2エポック目以降は0だった。
#まとめ
とりあえずいい感じの線を引くところまではできた。今回は力尽きたので時間とやる気ができたらもうちょっとちゃんとやりたい。
fortranにはpythonのlistやC++のvectorみたいなコンテナがないのがつらすぎる
#bugfix
subroutine内の変数宣言で同時に初期化すると、そのsubroutineが2回以上呼び出されるとき2回目以降初期化されない(値が受け継がれてしまう)ようです。
septcolorさんの指摘で気づきました。ありがとうございました。
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
#参考文献