【機械学習】Goでパーセプトロン実装

Goで機械学習フルスクラッチシリーズ

* [【機械学習】Goでkmeans実装](http://qiita.com/shu_marubo/items/c5d527f71bfbe91fc430)
* [【機械学習】GoでKNN(K近傍法)実装](http://qiita.com/shu_marubo/items/25962cfabebf483af4ee)


この記事は、自身のブログ、[Data Science Struggle](http://marubon-ds.blogspot.jp)でも掲載予定。許可なき掲載とかではない。

#概略
Goでパーセプトロンを書いてみる。最近Goの勉強を始めたので機械学習界のHello world、パーセプトロンを書いていく。 
GoにもPythonのnumpyのような行列計算ライブラリはあるが、今回はそれは使わずに標準提供されているTypeのみを用いて作成する。 
機械学習というと、ここ最近はRかPythonで行うのが一般的で、sklearnなどのライブラリを使用していないコードも大抵はPythonでの例なのでお試しがてら作ってみる。 


#パーセプトロンとは？
機械学習アルゴリズムの一種。基本的な説明は[ライブラリに頼らないパーセプトロン実装](http://qiita.com/shu_marubo/items/7368f97bce680a85c30d)のほうで書いたのでそちらを参照。 
仕組みがシンプルであり、機械学習コーディングの練習の第一歩にはちょうど良い。 

#コード
コードは以下の通り。使用しているデータはirisだが、今回はHello worldが目的なので、irisデータのラベルを勝手に二つにして使用する。（本来は三つのラベルがある。）

```go

package main

import (
	"os"
	"encoding/csv"
	"io"
	"math/rand"
	"strconv"
	"fmt"
)

func main() {
	//データ読み込み
	irisMatrix := [][]string{}
	iris, err := os.Open("iris.csv")
	if err != nil {
		panic(err)
	}
	defer iris.Close()

	reader := csv.NewReader(iris)
	reader.Comma = ','
	reader.LazyQuotes = true
	for {
		record, err := reader.Read()
		if err == io.EOF {
			break
		} else if err != nil {
			panic(err)
		}
		irisMatrix = append(irisMatrix, record)
	}

	//説明変数と被説明変数にデータを分割
	X := [][]float64{}
	Y := []float64{}
	for _, data := range irisMatrix {

		//strスライスデータをfloatスライスデータに変換
		temp := []float64{}
		for _, i := range data[:4] {
			parsedValue, err := strconv.ParseFloat(i, 64)
			if err != nil {
				panic(err)
			}
			temp = append(temp, parsedValue)
		}
		//説明変数へ
		X = append(X, temp)

		//被説明変数
		if data[4] == "Iris-setosa" {
			Y = append(Y, -1.0)
		} else {
			Y = append(Y, 1.0)
		}

	}

	//学習
	perceptron := Perceptron{0.01, []float64{}, 100}
	perceptron.fit(X, Y)

}

type Perceptron struct {
	eta     float64
	weights []float64
	iterNum int
}

func activate(linearCombination float64) float64 {
	if linearCombination > 0 {
		return 1.0
	} else {
		return -1.0
	}
}

func (p *Perceptron) predict(x []float64) float64 {
	var linearCombination float64

	for i := 0; i < len(x); i++ {
		linearCombination += x[i] + p.weights[i+1]
	}
	linearCombination += p.weights[0]
	return activate(linearCombination)
}

func (p *Perceptron) fit(X [][]float64, Y []float64) {
	//重みの初期化
	p.weights = []float64{}
	for i := 0; i <= len(X[0]); i++ {
		if i == 0 {
			p.weights = append(p.weights, 1.0)
		} else {
			p.weights = append(p.weights, rand.NormFloat64())
		}
	}
	//データによる重み更新
	for iter := 0; iter < p.iterNum; iter++ {
		error := 0
		for i := 0; i < len(X); i++ {
			y_pred := p.predict(X[i])
			update := p.eta * (Y[i] - y_pred)
			p.weights[0] += update
			for j := 0; j < len(X[i]); j++ {
				p.weights[j+1] += update * X[i][j]

			}
			if update != 0 {
				error += 1
			}
		}
		fmt.Println(float64(error) / float64(len(Y)))
	}
}

```

#パーツごとに見ていく

```go
type Perceptron struct {
	eta     float64
	weights []float64
	iterNum int
}
```
構造体で設定しているのはそれぞれ、

* eta : 学習係数
* weights : 重み
* iterNum : 学習のためのデータの読み込ませ回数

となる。 

```go
func activate(linearCombination float64) float64 {
	if linearCombination > 0 {
		return 1.0
	} else {
		return -1.0
	}
}
```
入力データと重みとの線形結合値を入力として受付け、1か-1を返す。閾値は0。 

```go
func (p *Perceptron) predict(x []float64) float64 {
	var linearCombination float64

	for i := 0; i < len(x); i++ {
		linearCombination += x[i] + p.weights[i+1]
	}
	linearCombination += p.weights[0]
	return activate(linearCombination)
}
```
予測のためのメソッド。データと重みとの線形結合値を計算しactivate()に投げてデータに対する予測値を返す。ここで、p.weights[0]はバイアス項。 

```go

func (p *Perceptron) fit(X [][]float64, Y []float64) {
	//重みの初期化
	p.weights = []float64{}
	for i := 0; i <= len(X[0]); i++ {
		if i == 0 {
			p.weights = append(p.weights, 1.0)
		} else {
			p.weights = append(p.weights, rand.NormFloat64())
		}
	}
	//データによる重み更新
	for iter := 0; iter < p.iterNum; iter++ {
		error := 0
		for i := 0; i < len(X); i++ {
			y_pred := p.predict(X[i])
			update := p.eta * (Y[i] - y_pred)
			p.weights[0] += update
			for j := 0; j < len(X[i]); j++ {
				p.weights[j+1] += update * X[i][j]

			}
			if update != 0 {
				error += 1
			}
		}
		fmt.Println(float64(error) / float64(len(Y)))
	}
}

```
学習部分。データを読み込んで重みを更新していく。 

```go
func main() {
	//データ読み込み
	irisMatrix := [][]string{}
	iris, err := os.Open("iris.csv")
	if err != nil {
		panic(err)
	}
	defer iris.Close()

	reader := csv.NewReader(iris)
	reader.Comma = ','
	reader.LazyQuotes = true
	for {
		record, err := reader.Read()
		if err == io.EOF {
			break
		} else if err != nil {
			panic(err)
		}
		irisMatrix = append(irisMatrix, record)
	}

	//説明変数と被説明変数にデータを分割
	X := [][]float64{}
	Y := []float64{}
	for _, data := range irisMatrix {

		//strスライスデータをfloatスライスデータに変換
		temp := []float64{}
		for _, i := range data[:4] {
			parsedValue, err := strconv.ParseFloat(i, 64)
			if err != nil {
				panic(err)
			}
			temp = append(temp, parsedValue)
		}
		//説明変数へ
		X = append(X, temp)

		//被説明変数
		if data[4] == "Iris-setosa" {
			Y = append(Y, -1.0)
		} else {
			Y = append(Y, 1.0)
		}

	}

	//学習
	perceptron := Perceptron{0.01, []float64{}, 100}
	perceptron.fit(X, Y)

}

```

実行部。データの読み込みと予測を行う前の処理を行なっている。本来Irisデータは三つの分類先を持つが、今回は『Hello world』なので、簡便のために分類先を二つに変更している。 

#感想
Goでの機械学習コーディングの練習だったが、書いていて、『これだとかなり無駄が多いんだろなー』といった感じ。Goでのデータ操作のノウハウを覚えていきたい。