ゼロから作る DeepLearning(Go) Part.1 ~ Go での代用 ~
はじめに
こんにちは。みです。
これは、「ゼロから作る DeepLerning ①」を Python ではなく、GO を使用してまとめてみた内容です。
参考:ゼロから作る DeepLerning ①
良ければご覧ください。
目次
- Python 入門(GO での代用) ← 今回はこちら ★
- パーセプトロン
- ニューラルネットワーク
- ニューラルネットワークの学習
- 誤差逆伝播法
- 学習に関するテクニック
- 畳み込みニューラルネットワーク
- ディープラーニング
1.Python 入門(GO での代用)
環境構築に関しては、こちらを参考にしてください。
Tutorial: Get started with Go
1.1 算術計算
1.1.1 加算
func add() {
a, b := 1, 2
fmt.Printf("加算:%d + %d = ", a, b)
fmt.Println(a + b)
}
結果
$ go run main.go
加算:1 + 2 = 3
1.1.2 減算
func sub() {
a, b := 1, 2
fmt.Printf("減算:%d - %d = ", a, b)
fmt.Println(a - b)
}
結果
$ go run main.go
減算:1 - 2 = -1
1.1.3 乗算
func mul() {
a, b := 4, 5
fmt.Printf("乗算:%d * %d = ", a, b)
fmt.Println(a * b)
}
結果
$ go run main.go
乗算:4 * 5 = 20
1.1.4 除算
func div() {
a, b := 7, 5
fmt.Printf("除算:%d / %d = ", a, b)
fmt.Println(float64(a) / float64(b))
}
結果
$ go run main.go
除算:7 / 5 = 1.4
1.1.5 累乗
import (
"fmt"
"math"
)
func pow() {
a, b := 3, 2
fmt.Printf("累乗:%d ^ %d = ", a, b)
fmt.Println(math.Pow(float64(a), float64(b)))
}
結果
$ go run main.go
累乗:3 ^ 2 = 9
1.2 データ型
データ型のチェックは、reflect の「TypeOf」を使用する。
参考:reflect
1.2.1 整数
import (
"fmt"
"reflect"
)
func PrintType() {
fmt.Printf("値:%d,型: %s\n", 1, reflect.TypeOf(1))
}
結果
$ go run main.go
値:1,型: int
1.2.2 不動小数点
import (
"fmt"
"reflect"
)
func PrintType() {
fmt.Printf("値:%f,型: %s\n", 1.000, reflect.TypeOf(1.000))
}
結果
$ go run main.go
値:1.000000,型: float64
1.2.3 文字列
import (
"fmt"
"reflect"
)
func PrintType() {
fmt.Printf("値:%s,型: %s\n", "hello", reflect.TypeOf("hello"))
}
結果
$ go run main.go
値:hello,型: string
1.3 変数
変数宣言はいろいろなやり方が存在しているので、公式チュートリアルなどを参考にしてください。
公式チュートリアル
Python と違い注意しないといけない点は、Python は「動的型付け言語」に対し、Go は「静的型付け言語」である点です。
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 では以下のように、明示的な型変換が必要になります。
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))
}
結果
$ go run main.go
100
314
int
float64
float64
1.4 リスト
Go では、配列とスライスの二つの型があるので注意。
1.4.1 配列
定義時に、長さを指定すると配列が作成される。
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))
}
結果
$ go run main.go
配列:[1 2 3 4 5]
長さ:5
型:[5]int
1.4.2 スライス
長さを未指定、もしくは、make()で作成時はスライスができる。
python での配列は、こちら
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))
}
結果
$ go run main.go
スライス:[1 2 3 4 5]
長さ:5
型:[]int
1.4.3 スライシング
基本的には Python と同じ。
slice[-1]
などで、末尾からの取得ができないので注意。
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])
}
結果
$ go run main.go
要素にアクセス(先頭):1
要素にアクセス(2番目~4番目):[2 3 4]
要素にアクセス(末尾):5
1.5 ディクショナリ
Go の辞書型は Map 型を使用する。
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)
}
結果
$ go run main.go
マップ:map[height:180]
要素にアクセス:180
マップ:map[height:180 weight:70]
マップ:map[height:180]
1.6 ブーリアン
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)
}
結果
$ go run main.go
型:bool
NOT:false
AND:false
OR:true
1.7 if 文
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")
}
}
結果
$ go run main.go
hungry:true
I'm hungry
hungry:false
I'm not hungry
I'm sleepy
1.8 for 文
スライスの要素を使用してループする形として、for-range
を使用する。
func PrintFor() {
slice := []int{1, 2, 3, 4, 5}
for i, v := range slice {
fmt.Printf("Index:%d,Val:%d\n", i, v)
}
}
結果
$ go run main.go
Index:0,Val:1
Index:1,Val:2
Index:2,Val:3
Index:3,Val:4
Index:4,Val:5
1.9 関数
func function(引数)(返り値){
引数に対する処理
return 返り値
}
1.10 スクリプトファイル
Python はインタプリタでも実行出来ますが、Go ではスクリプトのファイルが必須のため、ここはスキップします。
1.11 クラス
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)
}
func main(){
man := NewMan("David")
man.Hello()
man.GoodBye()
}
結果
$ go run main.go
Initialized!
Hello David!
Good-bye David!
1.12 Numpy の代用
今回は、gonum/mat を代用として使用します。
参考:gonum/mat
1.12.1 gonum/mat インストール
詳細
ターミナルで以下を実行してインストールをする。
$ go get -u gonum.org/v1/gonum/mat
1.12.2 gonum の配列の作成
mat.NewDense(行数, 列数, data)
という形で作成する。
func MakeArray() {
x := mat.NewDense(1, 3, []float64{1.0, 2.0, 3.0})
fmt.Println(x)
fmt.Printf("型:%s\n", reflect.TypeOf(x))
}
実際に実行してみるとわかると思いますが、求めてる形と違う形で出力されます。
$ go run main.go
&{{1 3 [1 2 3] 3} 1 3}
型:*mat.Dense
配列部分[1,2,3]だけ出力してほしいですが、実際出力するとよくわからない形で出力されいるのがわかります。
NewDense の実際のコードを見るとわかりますが、[]float64 のデータや行数、列数、ストライドを保持しているのです。
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
Dense 型は、Matrix の Intafece を実装しているので、mat.Formatted
を使用し、配列と同様の出力になるようにする。
※デバッグ時などに使用する可能性があるため、出力用の関数を作成しておく。
最終的に先ほどまでのソースは以下のようになる。
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)
}
結果
$ go run main.go
[1 2 3]
型:*mat.Dense
1.12.3 gonum の算術計算
mat.Dense.Add()
を使用します。
二つの行列を受け取ってその和を自分自身に代入するという実装になっているので、直感的にわかりにくい。
※要素数が異なる場合は、エラーになるので注意。
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)
}
そのため、以下のように、加算した結果を返すような関数を作成しておく。
func Add(a, b mat.Matrix) mat.Matrix {
var result mat.Dense
result.Add(a, b)
return &result
}
合わせてほかの四則演算も作成しておきます。
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
}
各実行結果
``` 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]
1.12.4 gonum の N 次元配列(形状取得・型取得)
gonum で Python のnp.array()
のような任意の次元配列を作成できるものはなさそうでした。
gonum で作成できる最大 N 次元配列は 2 次元配列が限界のようです。
不随した gonum での代わりを記載します。
-
shape
(形状の取得):Dims
-
dtype
(データ型の取得):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)
}
結果
$ go run main.go
2次元配列
⎡1 2⎤
⎢3 4⎥
⎣5 6⎦
形状取得
Rows: 3, Columns: 2
$ go run main.go
2次元配列
⎡1 2⎤
⎢3 4⎥
⎣5 6⎦
形状取得
Rows: 3, Columns: 2
1.12.5 ブロードキャスト
gonum で代用となる関数は存在しませんでした。
すごく欲しいとなった時に、独自で作成しようかと思います。
1.12.6 要素へのアクセス
Python や、1 次元配列のように、X[0]では取得ができません。
Gonum の mat.Dense 型は内部的に一次元スライスを使用しているため、RawMatrix().Data
を使うことで、一次元スライスにアクセスできます。
ただ、Python のようなx[np.array(0,2,4)]
のような柔軟なアクセスに関しては、簡単にはできないため、ここではスキップとします。
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)
}
結果
$ 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]
1.13 Matplotlib の代用
今回は、gonum/plot を代用として使用します。
参考:gonum/plot
1.13.1 gonum/plot インストール
詳細
ターミナルで以下を実行してインストールをする。
$ go get gonum.org/v1/plot
1.13.2 単純なグラフの描画
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
}
}
1.13.3 pyplot の機能
MatplotLib の pyplot の機能で作成されたものを作成してみます。
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
}
}
1.13.4 画像の表示
gonum/plot では、MatplotLib のimshow
のような画像表示するメソッドは、ありませんでした。
ブラウザ表示するなどして、表示させるのが手軽でよさそうでした。
1.14 最後に
- numpy がとても高機能なライブラリであることを再認識しました。
- どうしても、ライブラリで保管できない箇所を自作することになるので、記述量は多くなりました。
- 今後速度面などで、どのような違いが出てくるかが楽しみです。