ゼロから作る DeepLearning(Go) Part.1 ~ Go での代用 ~

# ゼロから作る DeepLearning(Go) Part.1 ~ Go での代用 ~

## はじめに

こんにちは。みです。

これは、「ゼロから作る DeepLerning ①」を Python ではなく、GO を使用してまとめてみた内容です。  
参考：[ゼロから作る DeepLerning ①](https://www.oreilly.co.jp/books/9784873117584/)

良ければご覧ください。

## 目次

1. **Python 入門（GO での代用）**　 ← 今回はこちら ★
2. パーセプトロン
3. ニューラルネットワーク
4. ニューラルネットワークの学習
5. 誤差逆伝播法
6. 学習に関するテクニック
7. 畳み込みニューラルネットワーク
8. ディープラーニング

## 1.Python 入門（GO での代用）

環境構築に関しては、こちらを参考にしてください。  
[Tutorial: Get started with Go](https://go.dev/doc/tutorial/getting-started)

### 1.1 算術計算

#### 1.1.1 加算

```go:math.go
func add() {
	a, b := 1, 2
	fmt.Printf("加算：%d + %d = ", a, b)
	fmt.Println(a + b)
}
```

<details><summary>結果</summary>

```shell-session
$ go run main.go
　加算：1 + 2 = 3
```

</details>

#### 1.1.2 減算

```go:math.go
func sub() {
	a, b := 1, 2
	fmt.Printf("減算：%d - %d = ", a, b)
	fmt.Println(a - b)
}
```

<details><summary>結果</summary>

```shell-session
$ go run main.go
　減算：1 - 2 = -1
```

</details>

#### 1.1.3 乗算

```go:math.go
func mul() {
	a, b := 4, 5
	fmt.Printf("乗算：%d * %d = ", a, b)
	fmt.Println(a * b)
}
```

<details><summary>結果</summary>

```shell-session
$ go run main.go
　乗算：4 * 5 = 20
```

</details>

#### 1.1.4 除算

```go:math.go
func div() {
	a, b := 7, 5
	fmt.Printf("除算：%d / %d = ", a, b)
	fmt.Println(float64(a) / float64(b))
}
```

<details><summary>結果</summary>

```shell-session
$ go run main.go
　除算：7 / 5 = 1.4
```

</details>

#### 1.1.5 累乗

```go:math.go
import (
	"fmt"
	"math"
)

func pow() {
	a, b := 3, 2
	fmt.Printf("累乗：%d ^ %d = ", a, b)
	fmt.Println(math.Pow(float64(a), float64(b)))
}
```

<details><summary>結果</summary>

```shell-session
$ go run main.go
　累乗：3 ^ 2 = 9
```

</details>

### 1.2 データ型

データ型のチェックは、reflect の「TypeOf」を使用する。  
参考：[reflect](https://pkg.go.dev/reflect)

#### 1.2.1 整数

```go:type.go
import (
	"fmt"
	"reflect"
)

func  PrintType() {
	fmt.Printf("値:%d,型: %s\n", 1, reflect.TypeOf(1))
}
```

<details><summary>結果</summary>

```shell-session
$ go run main.go
　値:1,型: int
```

</details>

#### 1.2.2 不動小数点

```go:type.go
import (
	"fmt"
	"reflect"
)

func  PrintType() {
	fmt.Printf("値:%f,型: %s\n", 1.000, reflect.TypeOf(1.000))
}
```

<details><summary>結果</summary>

```shell-session
$ go run main.go
　値:1.000000,型: float64
```

</details>

#### 1.2.3 文字列

```go:type.go
import (
	"fmt"
	"reflect"
)

func  PrintType() {
	fmt.Printf("値:%s,型: %s\n", "hello", reflect.TypeOf("hello"))
}
```

<details><summary>結果</summary>

```shell-session
$ go run main.go
　値:hello,型: string
```

</details>

### 1.3 変数

変数宣言はいろいろなやり方が存在しているので、公式チュートリアルなどを参考にしてください。  
[公式チュートリアル](https://go.dev/doc/tutorial/create-module#:~:text=Declare%20a%20message,v.%20Welcome!%22%2C%20name)

Python と違い注意しないといけない点は、Python は「**動的型付け言語**」に対し、Go は「**静的型付け言語**」である点です。

Python では以下のようになりますが、

```python:python
>>> x = 100
>>> print(x)
100
>>> y = 3.14
>>> x * y
314.0
>>> type(x)
<class 'int'>
>>> type(y)
<class 'float64'>
>>> type(x * y)
<class 'float64'>
```

Go では以下のように、明示的な型変換が必要になります。

```Go:Go
import (
	"fmt"
	"reflect"
)

func PrintVariable() {
	x := 100
	fmt.Println(x)

	y := 3.14
	fmt.Println(x*y)
	// invalid operation: x * y (mismatched types int and float64)

	// x * y の演算では xがint、yがfloat64なので型が一致しません。
	// そのため、型変換を行う必要があります。
	fmt.Println(float64(x) * y)

	fmt.Println(reflect.TypeOf(x))
	fmt.Println(reflect.TypeOf(y))
	fmt.Println(reflect.TypeOf(float64(x) * y))
}
```

<details><summary>結果</summary>

```shell-session
$ go run main.go
　100
  314
  int
  float64
  float64
```

</details>

### 1.4 リスト

Go では、配列とスライスの二つの型があるので注意。

#### 1.4.1 配列

定義時に、長さを指定すると配列が作成される。

```go:list.go
func List() {
	list := [5]int{1, 2, 3, 4, 5}
	fmt.Printf("配列：%v\n", list)
	fmt.Printf("長さ:%d\n", len(list))
	fmt.Printf("型：%s\n", reflect.TypeOf(list))
}
```

<details><summary>結果</summary>

```shell-session
$ go run main.go
　配列：[1 2 3 4 5]
  長さ:5
  型：[5]int
```

</details>

#### 1.4.2 スライス

長さを未指定、もしくは、make()で作成時はスライスができる。  
python での配列は、こちら

```go:list.go
func Slice() {
	slice := []int{1, 2, 3, 4, 5}
	fmt.Printf("スライス：%v\n", slice)
	fmt.Printf("長さ:%d\n", len(slice))
	fmt.Printf("型：%s\n", reflect.TypeOf(slice))
}
```

<details><summary>結果</summary>

```shell-session
$ go run main.go
　スライス：[1 2 3 4 5]
  長さ:5
  型:[]int
```

</details>

#### 1.4.3 スライシング

基本的には Python と同じ。  
`slice[-1]`などで、末尾からの取得ができないので注意。

```go:list.go
func Slice() {
	slice := []int{1, 2, 3, 4, 5}
	fmt.Printf("要素にアクセス（先頭）:%d\n", slice[0])
	fmt.Printf("要素にアクセス（2番目～4番目）:%v\n", slice[1:4])
	fmt.Printf("要素にアクセス（末尾）:%d\n", slice[len(slice)-1])
}
```

<details><summary>結果</summary>

```shell-session
$ go run main.go
　要素にアクセス（先頭）:1
  要素にアクセス（2番目～4番目）:[2 3 4]
  要素にアクセス（末尾）:5
```

</details>

#### 1.5 ディクショナリ

Go の辞書型は Map 型を使用する。

```go:map.go
func PrintMap() {
	me := map[string]int{"height": 180}
	fmt.Printf("マップ：%v\n", me)
	fmt.Printf("要素にアクセス:%d\n", me["height"])

	// 要素追加
	me["weight"] = 70
	fmt.Printf("マップ：%v\n", me)

	// 要素削除
	delete(me, "weight")
	fmt.Printf("マップ：%v\n", me)
}
```

<details><summary>結果</summary>

```shell-session
$ go run main.go
  マップ：map[height:180]
  要素にアクセス:180
  マップ：map[height:180 weight:70]
  マップ：map[height:180]
```

</details>

#### 1.6 ブーリアン

```go:boolean.go
import (
	"fmt"
	"reflect"
)

func PrintBool() {
	hungry := true
	sllepy := false
	fmt.Printf("型:%s\n", reflect.TypeOf(hungry))
	fmt.Printf("NOT:%t\n", !hungry)
	fmt.Printf("AND:%t\n", hungry && sllepy)
	fmt.Printf("OR:%t\n", hungry || sllepy)
}
```

<details><summary>結果</summary>

```shell-session
$ go run main.go
  型:bool
  NOT:false
  AND:false
  OR:true
```

</details>

#### 1.7 if 文

```go:if.go
func PrintIf() {
	hungry := true
	fmt.Printf("hungry:%t\n", hungry)
	if hungry {
		fmt.Println("I'm hungry")
	}

	hungry = false
	fmt.Printf("hungry:%t\n", hungry)
	if hungry {
		fmt.Println("I'm hungry")
	} else {
		fmt.Println("I'm not hungry")
		fmt.Println("I'm sleepy")
	}
}
```

<details><summary>結果</summary>

```shell-session
$ go run main.go
  hungry:true
  I'm hungry
  hungry:false
  I'm not hungry
  I'm sleepy
```

</details>

#### 1.8 for 文

スライスの要素を使用してループする形として、`for-range`を使用する。

```go:for.go
func PrintFor() {
	slice := []int{1, 2, 3, 4, 5}

	for i, v := range slice {
		fmt.Printf("Index:%d,Val:%d\n", i, v)
	}
}
```

<details><summary>結果</summary>

```shell-session
$ go run main.go
  Index:0,Val:1
  Index:1,Val:2
  Index:2,Val:3
  Index:3,Val:4
  Index:4,Val:5
```

</details>

#### 1.9 関数

```go:function.go
func function(引数)(返り値){
	引数に対する処理
	return 返り値
}
```

#### 1.10 スクリプトファイル

Python はインタプリタでも実行出来ますが、Go ではスクリプトのファイルが必須のため、ここはスキップします。

### 1.11 クラス

Go には、クラスが存在していないため、構造体に、メソッドを定義して使用する。

```go:man.go
type man struct {
	name string
}

func NewMan(name string) *man {
	fmt.Println("Initialized!")
	return &man{
		name: name,
	}
}

func (m *man) Hello() {
	fmt.Printf("Hello %s!\n", m.name)
}

func (m *man) GoodBye() {
	fmt.Printf("Good-bye %s!\n", m.name)
}
```

```go:main.go
func main(){
	man := NewMan("David")
	man.Hello()
	man.GoodBye()
}
```

<details><summary>結果</summary>

```shell-session
$ go run main.go
　Initialized!
  Hello David!
  Good-bye David!
```

</details>

### 1.12 Numpy の代用

今回は、gonum/mat を代用として使用します。  
参考：[gonum/mat](https://pkg.go.dev/gonum.org/v1/gonum/mat)

#### 1.12.1 gonum/mat インストール

<details><summary>詳細</summary>

ターミナルで以下を実行してインストールをする。

```shell-session
$ go get -u gonum.org/v1/gonum/mat
```

</details>

#### 1.12.2 gonum の配列の作成

`mat.NewDense(行数, 列数, data)`という形で作成する。

```go:array.go
func  MakeArray() {
	x := mat.NewDense(1, 3, []float64{1.0, 2.0, 3.0})
	fmt.Println(x)
	fmt.Printf("型:%s\n", reflect.TypeOf(x))
}
```

実際に実行してみるとわかると思いますが、求めてる形と違う形で出力されます。

```shell-session
$ go run main.go
  &{{1 3 [1 2 3] 3} 1 3}
  型:*mat.Dense
```

配列部分[1,2,3]だけ出力してほしいですが、実際出力するとよくわからない形で出力されいるのがわかります。

NewDense の実際のコードを見るとわかりますが、[]float64 のデータや行数、列数、ストライドを保持しているのです。

```go:dense.go
func NewDense(r, c int, data []float64) *Dense {
	if r <= 0 || c <= 0 {
		if r == 0 || c == 0 {
			panic(ErrZeroLength)
		}
		panic(ErrNegativeDimension)
	}
	if data != nil && r*c != len(data) {
		panic(ErrShape)
	}
	if data == nil {
		data = make([]float64, r*c)
	}
	return &Dense{
		mat: blas64.General{
			Rows:   r,
			Cols:   c,
			Stride: c,
			Data:   data,
		},
		capRows: r,
		capCols: c,
	}
}
```

参考：[dense.go](https://github.com/gonum/gonum/blob/master/mat/dense.go#L49C1-L72C2)

Dense 型は、Matrix の Intafece を実装しているので、`mat.Formatted`を使用し、配列と同様の出力になるようにする。  
※デバッグ時などに使用する可能性があるため、出力用の関数を作成しておく。

最終的に先ほどまでのソースは以下のようになる。

```go:array.go
func MakeArray() {
	x := mat.NewDense(1, 3, []float64{1.0, 2.0, 3.0})
	outPut(x)
	fmt.Printf("型:%s\n", reflect.TypeOf(x))

}

func outPut(m mat.Matrix, options ...mat.FormatOption) {
	// デフォルトオプション
	defaultOptions := []mat.FormatOption{
		mat.Prefix(""),
		mat.Squeeze(),
	}

	// 渡されたオプションがある場合、追加する
	formatOptions := append(defaultOptions, options...)

	f := mat.Formatted(m, formatOptions...)
	fmt.Printf("%v\n", f)
}
```

<details><summary>結果</summary>

```shell-session
$ go run main.go
  [1  2  3]
  型:*mat.Dense
```

</details>

#### 1.12.3 gonum の算術計算

`mat.Dense.Add()`を使用します。

二つの行列を受け取ってその和を自分自身に代入するという実装になっているので、直感的にわかりにくい。
※要素数が異なる場合は、エラーになるので注意。

```go:main.go
func main(){
	x := []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
    A := mat.NewDense(3, 4, x)
	B := mat.NewDense(3, 4, nil)
	B.Add(A, A)
    outPut(B)
}
```

そのため、以下のように、加算した結果を返すような関数を作成しておく。

```go:calc.go
func Add(a, b mat.Matrix) mat.Matrix {
	var result mat.Dense
	result.Add(a, b)
	return &result
}
```

合わせてほかの四則演算も作成しておきます。

```go:calc.go
func Sub(a, b mat.Matrix) mat.Matrix {
	var result mat.Dense
	result.Sub(a, b)
	return &result
}

func MulElem(a, b mat.Matrix) mat.Matrix {
	var result mat.Dense
	result.MulElem(a, b)
	return &result
}

func DivElem(a, b mat.Matrix) mat.Matrix {
	var result mat.Dense
	result.DivElem(a, b)
	return &result
}
```

<details><summary>各実行結果</summary>

``` go:main.go
func main(){
	x := mat.NewDense(1, 3, []float64{1.0, 2.0, 3.0})
	y := mat.NewDense(1, 3, []float64{2.0, 4.0, 6.0})
	fmt.Println("足し算")
	result := Add(x, y)
	outPut(result)

    fmt.Println("引き算")
    result = Sub(x, y)
    outPut(result)

    fmt.Println("積")
    result = MulElem(x, y)
    outPut(result)

    fmt.Println("割り算")
    result = DivElem(x, y)
    outPut(result)

}

````

```shell-session
$ go run main.go
  足し算
  [3  6  9]
  引き算
  [-1  -2  -3]
  積
  [2  8  18]
  割り算
  [0.5  0.5  0.5]
````

</details>

#### 1.12.4 gonum の N 次元配列(形状取得・型取得)

gonum で Python の`np.array()`のような任意の次元配列を作成できるものはなさそうでした。  
gonum で作成できる最大 N 次元配列は 2 次元配列が限界のようです。  
不随した gonum での代わりを記載します。

- `shape`(形状の取得):`Dims`
- `dtype`(データ型の取得):Go は静的型言語のため基本不要。

```go:main.go
func main(){
	x := mat.NewDense(3, 2, []float64{1.0, 2.0, 3.0, 4.0, 5.0, 6.0})
	fmt.Println("2次元配列")
	outPut(x)

	fmt.Println("形状取得")
	r, c := x.Dims()
	fmt.Printf("Rows: %d, Columns: %d\n", r, c)
}
```
<details><summary>結果</summary>

```shell-session
$ go run main.go
  2次元配列
  ⎡1  2⎤
  ⎢3  4⎥
  ⎣5  6⎦
  形状取得
  Rows: 3, Columns: 2
```

</details>


#### 1.12.5 ブロードキャスト

gonum で代用となる関数は存在しませんでした。  
すごく欲しいとなった時に、独自で作成しようかと思います。

#### 1.12.6 要素へのアクセス

Python や、1 次元配列のように、X[0]では取得ができません。  
Gonum の mat.Dense 型は内部的に一次元スライスを使用しているため、`RawMatrix().Data`を使うことで、一次元スライスにアクセスできます。  
ただ、Python のような`x[np.array(0,2,4)]`のような柔軟なアクセスに関しては、簡単にはできないため、ここではスキップとします。

```go:main.go
func Get() {
	x := mat.NewDense(3, 2, []float64{1.0, 2.0, 3.0, 4.0, 5.0, 6.0})
	outPut(x)

	fmt.Println("行のアクセス0行目")
	outPut(x.RowView(0))
	fmt.Println("列のアクセス0列目")
	outPut(x.ColView(0))
	fmt.Printf("値のアクセス(0,0):%v\n", x.At(0, 0))


	fmt.Println("1次元配列への変換")
	flattened := x.RawMatrix().Data
	fmt.Println("一次元スライス:", flattened)
}

```

<details><summary>結果</summary>

```shell-session
$ go run main.go
  ⎡1  2⎤
  ⎢3  4⎥
  ⎣5  6⎦
  行のアクセス0行目
  ⎡1⎤
  ⎣2⎦
  列のアクセス0列目
  ⎡1⎤
  ⎢3⎥
  ⎣5⎦
  値のアクセス(0,0):1
  1次元配列への変換
  一次元スライス: [1 2 3 4 5 6]
```

</details>

### 1.13 Matplotlib の代用

今回は、gonum/plot を代用として使用します。  
参考：[gonum/plot](https://pkg.go.dev/gonum.org/v1/plot)

#### 1.13.1 gonum/plot インストール

<details><summary>詳細</summary>

ターミナルで以下を実行してインストールをする。

```shell-session
$ go get gonum.org/v1/plot
```

</details>

#### 1.13.2 単純なグラフの描画

```go:plot.go
func PrintSin() {
	// プロットを作成
	p := plot.New()

	// タイトルとラベルを設定
	p.Title.Text = "Sin"
	p.X.Label.Text = "X"
	p.Y.Label.Text = "Y"
	p.X.Min = 0
	p.X.Max = 6
	p.Y.Min = -1
	p.Y.Max = 1

	// Sin関数のデータを生成
	p.Add(plotter.NewFunction(math.Sin))

	if err := p.Save(4*vg.Inch, 4*vg.Inch, "assets/sin.png"); err != nil {
		fmt.Println("Error saving plotStruct:", err)
		return
	}
}
```

<details><summary>結果</summary>

![sin.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3891234/0bee8dfa-09ca-702e-cd7d-3c933db8355f.png)

</details>

#### 1.13.3 pyplot の機能

MatplotLib の pyplot の機能で作成されたものを作成してみます。

```go:plot.go
func PrintPyplot() {
	// プロットを作成
	p := plot.New()

	// タイトルとラベルを設定
	p.Title.Text = "Sin&Cos"
	p.X.Label.Text = "X"
	p.Y.Label.Text = "Y"
	p.X.Min = 0
	p.X.Max = 6
	p.Y.Min = -1
	p.Y.Max = 1

	// Sin関数のデータを生成
	sin := plotter.NewFunction(math.Sin)
	p.Add(sin)

	// Cos関数のデータを生成
	cos := plotter.NewFunction(math.Cos)
	// 破線の設定
	cos.LineStyle.Dashes = []vg.Length{vg.Points(5), vg.Points(5)}
	p.Add(cos)

	// 判例を追加
	p.Legend.Add("sin", sin)
	p.Legend.Add("cos", cos)
	p.Legend.Top = true

	if err := p.Save(4*vg.Inch, 4*vg.Inch, "assets/sincos.png"); err != nil {
		fmt.Println("Error saving plotStruct:", err)
		return
	}
}

```

<details><summary>結果</summary>

![sincos.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3891234/798f2e0c-1228-7ba9-498c-ba15398966c3.png)

</details>

#### 1.13.4 画像の表示

gonum/plot では、MatplotLib の`imshow`のような画像表示するメソッドは、ありませんでした。  
ブラウザ表示するなどして、表示させるのが手軽でよさそうでした。

### 1.14 最後に

- numpy がとても高機能なライブラリであることを再認識しました。
- どうしても、ライブラリで保管できない箇所を自作することになるので、記述量は多くなりました。
- 今後速度面などで、どのような違いが出てくるかが楽しみです。

## 次回

### Part.2：**パーセプトロン**