- 投稿日:2019-07-27T21:52:23+09:00
Docker上のGoLangをリモートデバッグする
概要
Docker上で動作するGoLangアプリケーションをデバッグする方法を解説します。
環境
- ホストOS(macOS Mojave バージョン 10.14.6)
- GoLand 2019.1.3
- Docker Desktop for Mac(Docker version 18.09.2)
- 利用するDockerイメージ(golang:1.12.7-alpine3.10)
この記事ではホストPC上で利用するツールとしてGoLandを利用しますが、Visual Studio Code 等でも同じことは実現出来ると思います。
Docker側の設定
Dockerfileの内容
DockerfileFROM golang:1.12.7-alpine3.10 as build ENV GO111MODULE on WORKDIR /go/app COPY . . RUN set -ex && \ apk update && \ apk add --no-cache git && \ go build -o portfolio-backend && \ go get -u github.com/oxequa/realize && \ go get -u github.com/go-delve/delve/cmd/dlv && \ go build -o /go/bin/dlv github.com/go-delve/delve/cmd/dlv FROM alpine:3.10 WORKDIR /app COPY --from=build /go/app/portfolio-backend . RUN set -x && \ addgroup go && \ adduser -D -G go go && \ chown -R go:go /app/portfolio-backend CMD ["./portfolio-backend"]
go get -u github.com/go-delve/delve/cmd/dlv
とgo build -o /go/bin/dlv github.com/go-delve/delve/cmd/dlv
の部分が重要になります。
delve
というGoLang用のDebuggerをインストールしています。開発時にホットリロードを有効にしたいので
go get -u github.com/oxequa/realize
でrealizeをインストールしています。docker-composeの設定
以下のように設定します。
docker-compose.ymlversion: '3.7' services: portfolio-backend: build: context: . dockerfile: Dockerfile target: build volumes: - ./:/go/app command: realize start --run ports: - 8888:8888 - 2345:2345 security_opt: - apparmor:unconfined cap_add: - SYS_PTRACE
command
でrealize
を起動させています。
ports
で8888
(WebServer用のポート)と2345
(デバッグ用のポート)を開放します。
security_opt
,cap_add
の2つはDocker上でdelve
を利用する為の設定です。security_opt: - seccomp:unconfinedDockerが行うシステムコールを制限するオプションです。
(参考)https://docs.docker.com/engine/security/seccomp/
cap_add: - SYS_PTRACEコンテナの capabilities の追加を行います。
(参考)https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities
(参考)Docker コンテナ上で Go のプロジェクトを delve でデバッグするには ptrace の許可が必要
realizeの設定
下記の通りです。
.realize.yamlsettings: legacy: force: false interval: 0s schema: - name: app path: . commands: install: status: true method: go build -o portfolio-backend run: status: true method: /go/bin/dlv args: - exec - ./portfolio-backend - --headless=true - --listen=:2345 - --api-version=2 - --accept-multiclient watcher: extensions: - go paths: - / ignore: paths: - .git - .realize - vendor重要なのは下記の部分です。
commands: install: status: true method: go build -o portfolio-backend run: status: true method: /go/bin/dlv args: - exec - ./portfolio-backend - --headless=true - --listen=:2345 - --api-version=2 - --accept-multiclientファイルの変更が行われる度にBuildを実行し
delve
を再読み込みします。argsは
delve
の実行時に渡す引数になります。GoLand側の設定
GoLandデバッグのデバッグ設定を作成する
Run → Debug → 0. Edit Configurations...
から設定を作成します。この記事の通りにやれば
delve
での利用ポートはデフォルトの2345なのでGo Remote
のTemplateを使って作成すれば良いです。
Name
はこの記事ではapp
としていますが、わかり易ければ何でも構いません。GoLand側からDockerコンテナに接続する
Run → Debug →
から先程作成したデバッグ設定を開きます。下記のように表示されていれば、接続が完了しています。
ちなみにDockerのログを見ると下記のようにアプリケーションの起動を確認出来るかと思います。
$ docker logs -f 0957f7698bbc [08:35:25][APP] : Watching 1 file/s 2 folder/s [08:35:25][APP] : Install started [08:35:26][APP] : Install completed in 0.585 s [08:35:26][APP] : Running.. [08:35:26][APP] : API server listening at: [::]:2345 [08:46:10][APP] : 2019/07/27 08:46:10 env: develop [08:46:10][APP] : 2019/07/27 08:46:10 Starting appGoLandでブレークポイントを設定してデバッグ
ブレークポイントを設定してアプリケーションのURLにアクセスしましょう。
ブレークポイントでアプリケーションが一時停止し変数の中身等が確認出来るかと思います。
あとがき
ここまでのコードはGitHubに公開してあります。
https://github.com/nekochans/portfolio-backend
Debuggerが使えると開発効率が上がりますので、少々時間をかけてでも設定を行うことをオススメします。
以上になります。最後まで読んで頂きありがとうございます。
- 投稿日:2019-07-27T18:28:27+09:00
Go Modules のダウンロードを高速化する Module Mirror と Module Index, Module Checksum Database について
はじめに
2018年末に Go の開発チームが 2019年 の Go Modules に関する計画を Go Modules in 2019 - The Go Blog で公開しました。
ブログの中では、Go 1.11 から提供された Go Modules1 の説明から始まり、go get で行えたことを Go Modules でも実現することの重要性2 や、コードの分散管理が許容されることの欠点3 に触れた後に、Go チームでの Go Modules に関する計画が書かれていました。4
今回は、当時 Go Modules に関する計画として紹介されていて、最近になり アルファテストの提供開始がアナウンスされた Module Mirror, Module Index, Module Checksum Database についてまとめていきたいと思います。
Module Mirror( https://proxy.golang.org )
Module Mirror は Google から提供される高速かつ高信頼性な Module5 のキャッシュプロキシーです。まだキャッシュされていない Module が Module Mirror にリクエストされた際には、公開されているオリジンサーバーから Module を取得してキャッシュしておいてくれる形です。
この Module Mirror を利用することで Module を高速にダウンロードできたり、GitHub などのオリジンサーバーがダウンしていた場合や URL が変更されてしまった場合でも、指定した Module のダウンロードが可能になります。
それでは Module Mirror の使い方について説明していきます。
Go Modules では Module Proxy(GOPROXY)という概念が導入6 されており、Module の取得をオリジンサーバーでなく任意のサーバーを指定することが可能になっているので、以下のように Module Proxy として Module Mirror を指定することで、ユーザーはその恩恵を受けることができます。GOPROXY=https://proxy.golang.orgでは、実際に Module をオリジンサーバーから取得する場合と Module Mirror から取得する場合の時間を比較してみたいと思います。7
# Go Modules を利用しているプロダクトを Clone してくる $ git clone https://github.com/spiffe/spire.git $ cd spire # Module Mirror を使わずに Module をダウンロードする $ time GO111MODULE=on go mod download ... real 2m29.631s user 2m32.240s sys 1m5.320s # Module Mirror を使って Module をダウンロードする $ GOPROXY=https://proxy.golang.org GO111MODULE=on go mod download ... real 0m36.504s user 0m21.410s sys 0m16.240s以下の結果となりました。Mirror Module を利用することで Module のダウンロードが高速化されることがわかります。とても早いですね!
あり/なし 所要時間 Mirror Module なし 約2分半 Mirror Module あり 約30秒 Module Checksum Database( https://sum.golang.org )
Module を認証するために利用可能な Checksum が格納されているデータベースとなります。8 様々なサーバーで公開されている Module の go.sum が提供されるイメージです。
Module Checksum Database から取得した Checksum と、ダウンロードした Module の Checksum を比較することでコードの信頼性を確認することが可能になります。
少し例をあげると、https://sum.golang.org/latest にリクエストを投げることで、Module Checksum Database のツリーサイズや最新のログのハッシュなどを取得することが出来たり、https://sum.golang.org/lookup/github.com/kubernetes/kubernetes@v1.15.0 のように
https://sum.golang.org/lookup/<Module>@<Version>
と指定することで、対象 Module の情報を取得することが出来ます。これらを利用して Go コマンドの内部でダウンロードした Module の信頼性確認が行われているのだと思います。Module Checksum Database の使い方ですが、
GOPROXY=https://proxy.golang.org
で Module Mirror を利用する場合には、自動的にこの Module Checksum Database が使われる仕様のようです。明示的に設定したい場合にはGOSUMDB=sum.golang.org
を宣言すれば良いです。なお、Go 1.12 以前では
gosumcheck
を使って、手動で Module Checksum Database を使ってのチェックが可能なようなので、興味ある方はお試しください。go get golang.org/x/mod/gosumcheck gosumcheck /path/to/go.sum蛇足ですが、元々は
the Go notary
という名前だったらしいですが、CNCF Projects の Notary との混同を避けるために現在の命名となったようです。Module Index( https://index.golang.org )
新しい Module のバージョンのフィードを提供するもので https://index.golang.org/index からフィードを確認できます。フィードは年代順にソートされており、各 Module は以下の Key を持った JSON として構成されています。
Key 概要 Path Module 名 Version Module のバージョン Timestamp Module Mirror( proxy.golang.org )で最初にキャッシュされた時刻 また、以下のパラメータがサポートされています。
パラメータ 概要 例 since 指定した Timestamp 以降のフィードを取得できる https://index.golang.org/index?since=2019-07-01T17:00:00%2b09:00 (JST で 2019-07-01T17:00:00+09:00 を指定している) limit 指定したサイズのフィードを取得できる https://index.golang.org/index?limit=10 これを利用することで新しい Module などを発見することが可能になります。godoc.org は今後こちらを参照するようになる?のようなことも記載されておりました。
懸念事項
Module Mirror の個人的な懸念点としては、気をつけて利用しないと Private な Module が Module Mirror にキャッシュされてしまうのでは?ということがあります。なので Private な Module を利用しているプロダクトでの Module Mirror 有効化に少し不安があります。
GOPRIVATE, GONOPROXY, GONOSUMDB 辺りを使えば良いのかな?ぐらいの理解なので、今後調べきれたら追記したいと考えています。
さいごに
今回は Module Mirror, Module Index, Module Checksum Database について紹介しました。現時点では Stable ではないので、不安定性やバグなどがある可能性もありますが、利用する価値はあるのかなと思います。開発されているプロダクトの要件に応じて採用するかどうかをジャッジ頂ければと思います。
Go 1.13 以降の Go コマンドでは、Module Mirror と Module Checksum Database の利用がデフォルトで有効となる( https://github.com/golang/go/commit/f8a5ba2a3880f4782d8250fd26dde2baa3990afa )とのことなので、今後もキャッチアップを続けていきたいと思います。
おしまい。
Go Modules 自体の説明は Go Modulesの概要とGo1.12に含まれるModulesに関する変更 - My External Storage がとてもわかりやすいので、そちらを参照ください。 ↩
go get
のオリジナルデザインで重要だったことの1つに、Node の NPM や Java の Maven など中央集権型なコード管理ではなく、誰でも自分のコードを好きなサーバーで公開できて、誰もが好きなサーバーからコードをインポートできるという非中央集権型(分散型)なコード管理というものがあったとのこと。 ↩Go パッケージの依存関係の分散化の重大な欠点として、公開されている Goパッケージ を見つけることの難しさや、そもそも Goパッケージ の提供が継続される保証がないということが挙げられていました。 ↩
自分が読み違えて理解している場合もあるので、詳細が気になる方はオリジナルのブログを参照ください。 ↩
公式ドキュメント では
A module is a collection of related Go packages that are versioned together as a single unit.
という定義がされています。 ↩
go help goproxy
で使い方を参照ください。 ↩今回は Go Modules を利用しているプロダクトを Clone する形を取りましたが、単純に go get での比較でも良いと思います。 ↩
詳細は Proposal: Secure the Public Go Module Ecosystem に記載されています。 ↩
- 投稿日:2019-07-27T16:45:38+09:00
REST風にコマンドを呼び出せる(例えばhttp://localhost/bin/date な)command-as-a-serviceのコンテナを作ってみた
概要
このエントリでは、REST風にOS上のコマンドを呼び出せるcommand-as-a-serviceのコンテナを作ってみたときに作業した内容について紹介します。
対象読者
以下の方を対象とします
- ネタが嫌いではない方
- 厳格なREST主義以外でもネタとして受け付けられる方
- お仕事でそのまま使ってやろう、と思う前にいろいろ自分で確認できる心の余裕がある方動機
「http://localhost/bin/grep」とかでコマンド呼び出せたら、意味もなく面白いのでは、と思ったので。
関連
限られた1つのコマンドだけを実行したいときには、「Goで外部コマンドを呼び出すのをHTTPリクエスト越しに実行してみる(Ginを使って)」も参照ください。Ginが対応しているものがいろいろ使えるので、カタく使うならおすすめです。
TL;DR; 一言で言ってこういうものです
様子だけ知りたい方
コンテナを実行しておいて、
$ docker run -p 8080:8080 hrkt/command-as-a-service:latestcurlでアクセスすると、こんな感じになります。
$ curl "http://localhost:8080/bin/date" Sat Jul 27 07:08:47 UTC 2019標準入力にはPOSTのbodyを渡して、コマンドのオプションはクエリパラメーターで与えます。
$ curl "http://localhost:8080/usr/bin/sort?-r&-n" --data-binary @testdata/test.txt hello goodbye(補足)上記の例でのデータの内容
$ cat testdata/test.txt goodbye helloご自身でも動かしてみたい方
Dockerhubで公開しておりますので、上記のコマンドの通り起動してみて、(デフォルトではWhitelistで制限された)コマンドを試してみてください。
何をやっているか
URLのハンドル
Go-langのnet/httpを使い、自分でハンドラを書いています。
以下を行いました。
- リクエストのディレクトリをOSのコマンドへのパスに読み替えるために利用したい。一般的な用途に対して使いやすい、パスを固定的に関数にバインドしてくれるルータを使うと帰って大変になるので、一つのハンドラで足りるように書く
- OSのコマンドへのオプションとして利用するため、クエリパラメータを解析。goのモジュールのお便利関数を使うと解析結果がmapになってしまうので、そこを実装
- コマンドの実行は、別エントリで書いたものを踏襲。ただし、コマンド実行時のエラーハンドルを少しできるようにし、httpのリターンコードとして表現できるようにする
コードは↓な感じです。
func MyServer() http.Handler { return &myHandler{} } type myHandler struct { } func (f *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { log.Println("called:" + r.URL.Path) buffer, err := ioutil.ReadAll(r.Body) if err != nil { log.Printf(err.Error()) } unescaped, _ := url.QueryUnescape(r.URL.RawQuery) params := strings.Split(unescaped, "&") log.Println(params) code, res := executeIt(r.URL.Path, string(buffer), params) if code == 400 { http.Error(w, res, http.StatusBadRequest) return } w.Write([]byte(res)) }OSコマンドの実行
OSのコマンド実行を全部できるようにしてしまうと、流石にイマイチではないかと考え、実行可能なコマンドは、設定ファイルでホワイトリストとして与えるようにしました。
(が、細かいことはいいんだよ、という目的も考え、設定ファイルで「DangerousMode」を有効化すると、何でもかんでも実行できるようにしてあります)
func executeIt(path string, requestBody string, params []string) (int, string) { if !appConfig.DangerousMode { _, ok := whitelistMap[path] if !ok { return 400, string("ERROR: command " + path + " not in the whitelist") } } var cmd *exec.Cmd if len(params) > 0 && params[0] == "" { cmd = exec.Command(path) } else { cmd = exec.Command(path, params[:]...) } cmd.Stdin = strings.NewReader(requestBody) var stdout bytes.Buffer cmd.Stdout = &stdout err := cmd.Run() if err != nil { log.Printf(err.Error()) return 400, string("ERROR: command execution failed. reason: " + err.Error()) } log.Println(stdout.String()) return 0, string(stdout.String()) }まとめ
このエントリでは、REST風にコマンドを呼び出せるcommand-as-a-serviceのコンテナを作ってみるにあたり、あれこれ試してみた結果について紹介しました。
参照
- GitHub: https://github.com/hrkt/command-as-a-service
- DockerHub: https://cloud.docker.com/repository/registry-1.docker.io/hrkt/command-as-a-service
(参考)開発スタイル
以下でやってます。
- GitHub使う
- GitHubの「Project」を使って、タスクをIssueとして書く
- コード書くのはmacOSでVScode、GitHub Desktopを併用
- CIは、CircleCI使う。PRのMerge時にテストこけないようにする
- 出来上がったコンテナは、DockerHubの自分のアカウントで公開する
この作業をやっていた2019/7/27は、Fuji Rockの中継をYoutubeでやっていたので、BGMとして楽しみました。
- 投稿日:2019-07-27T12:11:54+09:00
自作ニューラルネットワークで手書き文字判別
この記事は前回Go言語でニューラルネットワークのスクラッチ実装の続きです。
前回、Go言語でニューラルネットワークを行列計算から作ってXORの非線形分類をやってみました。ただこれだと正直人間でも重みをイイ感じに設定すれば簡単にできてしまうのでどうにもニューラルネットワーク感がないなあと思いました。そこで、機械学習を使わないとコンピュータに処理させれなさそうで、かつすでに手法が存在して必ず成功するものはないかなと思ったところ、MNISTの手書き文字の分類をやればいいじゃないか!と思ったのでそれをやってみることにしました。
データの用意
機械学習と言ってもまずはデータがないと何もできません。今回は上にも書いたようにMNIST手書き文字のデータを使います。
データのダウンロード
THE MNIST DATABASE
of handwritten digits
このサイトからダウンロードできます。
こんな感じの所があるのでポチポチダウンロードしてきます。上から学習用画像、学習用教師データ、テスト用画像、テスト用教師データです。
ダウンロードしてきたら解凍して適当なフォルダに入れてください。ここで、ダウンロードのページの下の方に行くとこのような記述があります。
これは、先ほどダウンロードしてきたデータがどのような構造で保存されているかがかかれています。上はラベルファイル、つまり教師データです。これは最初の64bitには教師データではない情報が含まれていることがわかります。下はトレーニングデータです。これは最初の128bitは機械学習には必要のないデータが含まれています。
データの読み込み
ここで用意したデータを読み込みます。まずはトレーニングデータの画像の読み込みをします。画像のサイズは28×28なので最終的に[60000][28][28]のスライスを返すように実装します。
getmnist.gofunc GetTrainImg() [][][]int{ // ファイルをOpenする f, err := os.Open("C:\\hoge\\hoge\\train-images-idx3-ubyte") // 読み取り時の例外処理 if err != nil{ fmt.Println("error") } // 関数が終了した際に確実に閉じるようにする defer f.Close() // バイト型スライスの作成 buf := make([]byte, 60000 * 28 * 28 + 16) // nはバイト数を示す n, err := f.Read(buf) // バイト数が0になることは、読み取り終了を示す if n == 0{ os.Exit(1) } if err != nil{ os.Exit(1) } imgs := make([][][]int,60000) counter := 16 for i:=0;i<60000;i++{ imgs[i] = make([][]int,28) for j:=0;j<28;j++{ imgs[i][j] = make([]int,28) for k:=0;k<28;k++{ imgs[i][j][k] = int(buf[counter]) counter ++ } } } return imgs }次にトレーニングデータの教師データを読み込みます。
getmnist.gofunc GetTrainLabel() []int{ // ファイルをOpenする f, err := os.Open("C:\\hoge\\hoge\\train-labels-idx1-ubyte") // 読み取り時の例外処理 if err != nil{ fmt.Println("error") } // 関数が終了した際に確実に閉じるようにする defer f.Close() // バイト型スライスの作成 buf := make([]byte, 60008) // nはバイト数を示す n, err := f.Read(buf) // バイト数が0になることは、読み取り終了を示す if n == 0{ os.Exit(1) } if err != nil{ os.Exit(1) } //データを格納するスライス label := make([]int,60000) // バイト型を数字に変換してスライスに入れる for i:=8;i<len(buf);i++{ label[i-8] = int(buf[i]) } return label }テストデータに関しては読み込み用のバッファを10008に、データを格納するスライスを10000にしてそれ用の関数を作ります。
getmnsit.go//テストデータの画像を読み込む func GetTestImg() [][][]int{ //上の感じで実装してください } //テストデータの教師データを読み込む func GetTestLabel() []int{ //上の感じで実装してください }データの整形
画像データは二次元です。ですが機械学習にぶち込むときは一次元にしなければなりません。ですので、28×28の画像を784のベクトルに変換します。
dataclean.go//データ数*28*28のスライスをデータ数*784にする func ReduceDimention(data [][][]int) [][]float64{ ans := matrix.MakeMatrix(len(data),28*28) for i:=0;i<len(data);i++{ for j:=0;j<28;j++{ for k:=0;k<28;k++{ ans[i][j*28+k] = float64(data[i][j][k]) / 255.0 } } } return ans }次に、教師データは0~9までの数字が入っていますが、これもニューラルネットワークの関係上onehotencodeingというものを施してあげて10この要素の中で一つだけ1が入っているベクトルにします。
dataclean.gofunc Onehotencoding(data []int) [][]float64{ ans := matrix.MakeMatrix(len(data),10) for i:=0;i<len(data);i++{ ans[i][data[i]] = 1.0 } return ans }今回は確率的勾配法というのを用います。なにぶん訓練データが6万もあるのですべてでいっぺんに学習していると時間がかかってしまいます。ですのでいくつかの塊にランダムに分割してそれを学習するということをすることをします。インデックスを並び替えるのはFisher-Yatesアルゴリズムなるものを使いました。
dataclean.go//0からn-1までの数字をランダムに並べる //Fisher-Yatesアルゴリズム(たぶん) func Shuffle(n int) []int{ rand.Seed(time.Now().UnixNano()) ls := make([]int,n) for i:=0;i<n;i++{ ls[i] = i } for i := len(ls)-1;i>=0;i--{ j := rand.Intn(i + 1) ls[i], ls[j] = ls[j], ls[i] } return ls } //バッチサイズで分割 nはデータ数を割り切れる数にしてほしい func SplitData(n int,data [][]float64,label [][]float64) ([][][]float64 ,[][][]float64){ ans := make([][][]float64,len(data)/n) anslabel := make([][][]float64,len(data)/n) for i:=0;i<len(ans);i++{ ans[i] = make([][]float64,n) anslabel[i] = make([][]float64,n) for j:=0;j<len(ans[0]);j++{ ans[i][j] = make([]float64,len(data[0])) anslabel[i][j] = make([]float64,len(label[0])) } } indexes := Shuffle(len(data)) count := 0 for i:=0;i<len(ans);i++{ for j:=0;j<len(ans[0]);j++{ for k:=0;k<len(data[0]);k++{ ans[i][j][k] = data[indexes[count]][k] //fmt.Print(data[indexes[count]][k]) if i == 0 && j == 0 { //fmt.Print(data[indexes[count]][k], " ") } } for k:=0;k<len(anslabel[0][0]);k++{ anslabel[i][j][k] = label[indexes[count]][k] } count ++ } } return ans,anslabel }行列計算の実装
行列計算の実装は...しません!前回の記事で使ったものを流用します。ただ、プログラムがすごく長くなってしまったので別のファイルに移しました。
matrix.gopackage matrix import ( "os" "math" "math/rand" "time" ) //大きい値を出力 func Max(a float64,b float64) float64{ if a > b{return a} else{return b} } //行列を生成 func MakeMatrix(i int,j int) [][]float64{ } //行列を合計 func Add(a [][]float64,b [][]float64) [][]float64{ } //アダマール積 func AdaMul(a [][]float64,b [][]float64) [][]float64{ } //行列の積 func Multi(a [][]float64,b [][]float64) [][]float64{ } //行列に定数を加算 func ConstAdd(n float64,mat [][]float64) [][]float64{ } //行列を定数倍 func ConstMult(n float64,matrix [][]float64) [][]float64{ } //行列の転置 func Trans(a [][]float64) [][]float64{ } //行列にベクトルを足し算 /* [[1,2,3], [[1+1,1+2,1+3], [4,5,6], + [[1,1,4]] = [4+1,5+1,6+3], [7,8,9]] [7+1,8+1,9+3]] */ func AddVector(mat [][]float64,vec [][]float64) [][]float64{ } //要素1、大きさ1行n列のベクトルとの内積つまり、列方向の合計 func VecMul(mat [][]float64) [][]float64{ } //シグモイド関数 func Sigmoid(x [][]float64) [][]float64{ } //シグモイド関数の微分(出力された値を引数に入れる) func DiffSigmoid(x [][]float64) [][]float64{ } //ソフトマックス関数 func Softmax(x [][]float64) [][]float64{ } //二乗誤差 func Square(y [][]float64,t [][]float64) float64 { } //乱数を発生 func random(min, max float64,seed int) float64 { } //i*jの中身0~1の乱数の行列を生成 func MakeWight(i int,j int) [][]float64{ }これだけの関数をmatrix.goに移します。
ニューラルネットワークを実装
ここからニューラルネットワークを実装していきたいと思います。まず、今回と前回との違いは、入力層の数が違うことです。前回は入力層は2でしたが、今回は28×28=784層の入力層を使います。さらに、データ数が6万もあるので少し複雑にしてよいかなとおもって、中間層を二層にしてそれぞれ64層のネットワークを構成しました。
順伝搬の変更
neuralnetwork.go//変数を四層分用意 var w1_1,w1_0,w2_1,w2_0,w3_1,w3_0,layer_z1,layer_a1,layer_z2,layer_a2,layer_z3,layer_a3,dw1,db1,dw2,db2,dw3,db3 [][]float64 var costList []float64 //重みの初期化 func initValue(){ //データの次元数 dim := 28*28 //中間層1のノードの数 midNode := 64 //中間層2のノード数 midNode2:= 64 //出力層のノード数 finNode := 10 //入力層から中間層1への重み w1_1 = matrix.MakeWight(dim,midNode) //入力層から中間層1へのかけ合わせないやつ w1_0 = matrix.MakeWight(1,midNode) //中間層1から中間層2への重み w2_1 = matrix.MakeWight(midNode,midNode2) //中間層1から中間層2へのかけ合わせないやつ w2_0 = matrix.MakeWight(1,midNode2) //中間層から出力層への重み w3_1 = matrix.MakeWight(midNode2,finNode) //中間層から出力層へのかけ合わせないやつ w3_0 = matrix.MakeWight(1,finNode) } //順伝搬 func forward(data [][]float64) [][]float64{ //入力から中間層1 layer_z1 = matrix.AddVector(matrix.Multi(data,w1_1),w1_0) layer_a1 = matrix.Sigmoid(layer_z1) //中間層1から中間層2 layer_z2 = matrix.AddVector(matrix.Multi(layer_a1,w2_1), w2_0) layer_a2 = matrix.Sigmoid(layer_z2) //中間層2から出力層 layer_z3 = matrix.AddVector(matrix.Multi(layer_a2,w3_1), w3_0) layer_a3 = matrix.Softmax(layer_z3) return layer_a3 }逆伝搬
前回、出力層ー中間層間の微分が謎な関数が大量につながっていたんですけど、それやっぱ間違ってたっぽくて、交差エントロピー誤差の微分にしました。交差エントロピー誤差とソフトマックス関数の微分、引き算だけでできるの本当に世の中うまくできてるなと思いました。
neuralnetwork.gofunc back(x [][]float64,y [][]float64){ //中間層2-出力層の重みでの微分を求める output_delta := matrix.Add(layer_a3,matrix.ConstMult(-1,y)) dw3 = matrix.Multi(matrix.Trans(layer_a2),output_delta) db3 = matrix.VecMul(output_delta) //中間層1-中間層2の重みで微分を求める mid2_delta := matrix.AdaMul(matrix.Multi(output_delta,matrix.Trans(w3_1)),matrix.DiffSigmoid(layer_a2)) dw2 = matrix.Multi(matrix.Trans(layer_a1),mid2_delta) db2 = matrix.VecMul(mid2_delta) //入力層-中間層の重みでの微分を求める mid_delta := matrix.AdaMul(matrix.Multi(mid2_delta,matrix.Trans(w2_1)),matrix.DiffSigmoid(layer_a1)) dw1 = matrix.Multi(matrix.Trans(x),mid_delta) db1 = matrix.VecMul(mid_delta) }変数が増えたので重みの更新もたくさん書いてあげます。
neuralnetworl.gofunc update(alpha float64){ w1_1 = matrix.Add(w1_1,matrix.ConstMult(-alpha,dw1)) w1_0 = matrix.Add(w1_0,matrix.ConstMult(-alpha,db1)) w2_1 = matrix.Add(w2_1,matrix.ConstMult(-alpha,dw2)) w2_0 = matrix.Add(w2_0,matrix.ConstMult(-alpha,db2)) w3_1 = matrix.Add(w3_1,matrix.ConstMult(-alpha,dw3)) w3_0 = matrix.Add(w3_0,matrix.ConstMult(-alpha,db3)) }精度測定用の関数
出力された予測の何パーセントが正しい出力かを計算する関数を実装します。出力される値はそれぞれの数字の確率なので一番大きな確率の数字と教師データと比較します。
neuralnetwork.gofunc accuracy(data [][]float64,label []int) float64{ acc := 0 for i:=0;i<len(data);i++{ max := 0.0 num := 0 for j:=0;j<10;j++{ if data[i][j] > max{ max = data[i][j] num = j } } if label[i] == num{ acc ++ } } return float64(acc)/float64(len(label)) }main関数の実装
neuralnetwork.gofunc main(){ //変数の初期化 initValue() //トレーニングデータの画像を読み込み data := dataclean.ReduceDimention(getmnist.GetTrainImg()) //トレーニングデータの教師データを読み込み label := dataclean.Onehotencoding(getmnist.GetTrainLabel()) //エポック epoc := 10 //確率的勾配法 for i:=0;i<epoc;i++{ //データを100個ずつに分割 t,l := dataclean.SplitData(100,data,label) count := 0 costsum := 0.0 for j:=0;j<len(t);j++{ x := t[j] y := l[j] //学習 train(x,y,0.1,1) count ++ a := cost(layer_a3,y) costsum += a if a == math.NaN(){ i = epoc break } //コストを記録 if count % 10 == 0{ costList = append(costList,costsum) //fmt.Println(costList[len(costList)-1]) costsum = 0.0 count = 0 } } } //テストデータでを予測 output := forward(dataclean.ReduceDimention(getmnist.GetTestImg())) //テストデータの教師データを読み込み la := getmnist.GetTestLabel() //精度を出力 fmt.Println(accuracy(output,la)) //以下、コスト関数の推移をテキストファイルに記録する処理 file, _ := os.Create(`C:\\hoge\\grade.txt`) defer file.Close() for i:=0;i<len(costList);i++{ output := []byte(strconv.FormatFloat(costList[i], 'f', 15, 64) + " ") file.Write(([]byte)(output)) } }結果
最も精度が良かったepoc数は10程度でした。それ以上いくと過学習を起こしました。
epoc accuracy 8 0.9389 10 0.9459 15 0.9173 20 0.9293 最後に
こうやってちょっと複雑なモデルを作ってると前回のように単純なものでは気づけなかった間違いに気づくことができました。ここまで来たら、CNNを実装してみたいので次はCNN作ります。この記事での間違いとか指摘などあったら教えてください。お願いします。(実はテスト期間真っただ中なのでそろそろ勉強しなくては)
- 投稿日:2019-07-27T11:40:33+09:00
Goで並行処理練習(Goroutine,Channel)
Golangによる並行処理
最近Golangを使って過去に書いたプログラムを書き直して勉強している。CライクでありCのめんどくさい記述が省けるとこに魅力を感じている。Golangは並行処理の記述が書きやすいらしい。では、手を出してみよう。ちなみに私は先輩にコードが汚い、センスがないと言われているので、見にくいかもです。これでも頑張ってます。
定積分の台形法による近似
今回Goroutineの練習に用いた題材は「定積分を台形法を用いて近似する」というもの。
アルゴリズムについては、ググってもらえばたくさん出てくる。いたってシンプルかつ簡単というのがGoroutine入門に向いているのではないか?的なノリで採択。そして今回用いた数式はこちら。
\int_{0}^{1}\frac{4}{1+x^2}dx = \pi計算の方針として、積分区間を任意の幅で分割し台形の計算を並行処理する。
日本語が下手くそすぎる故、コードをみてもらった方が早いです。package main import ( "fmt" "sync" ) const ( p_num = 15 //区切りはば low = 0.0 //積分区間 high = 1.0 ) func main() { ch := make(chan float32) var dx float32 var x, sum float32 var wg sync.WaitGroup point := make([]float32, p_num) dx = (high - low) / p_num x = low for i := 0; i < p_num; i++ { //区切った時のXの値を記憶 point[i] = x x += dx } for i := 0; i < p_num; i++ { wg.Add(1) go calculate(ch, i, point[i], dx, &wg) //go func(i int){ // wg.Add(1) // fmt.Println(i) // x := point[i] // gx := 4 / (1.0 + x*x) // hx := 4 / (1.0 + (x+dx)*(x+dx)) // ch <- (gx + hx) * dx / 2 // wg.Done() //}(i) } go func(){ wg.Wait() close(ch) }() for v := range ch { sum += v } fmt.Println(sum) } func calculate(ch chan float32, i int, x, dx float32, wg *sync.WaitGroup) { gx := 4 / (1.0 + x*x) hx := 4 / (1.0 + (x+dx)*(x+dx)) ch <- (gx + hx) * dx / 2 wg.Done() }今回はmain関数の外に別でcalculate関数を用意しているが、コメントアウト部分のように、mainの中に並行処理部分を記述しても良い。しかし、注意する点として、変数iをgoroutineに参照させるために
go func(i int){ // do something }(i)と書く必要がある。
仮に書かなかったら、どうなるかをわかりやすい例で書くとpackage main import ( "fmt" "time" ) func main() { for i := 0; i < 10; i++ { go func(){ fmt.Println(i) }() } time.Sleep(time.Second*5) //goroutineが始まる前にmainが終了するのを防ぐため //WaitGroupeを用いるのがオススメ } /********************** 10 8 10 10 10 10 10 10 10 10 **********************/うまく変数iの値が参照されない。iを参照するように変更すると
package main import ( "fmt" "time" ) func main() { for i := 0; i < 10; i++ { go func(i int){ fmt.Println(i) }(i) } time.Sleep(time.Second*5) //goroutineが始まる前にmainが終了するのを防ぐため //WaitGroupeを用いるのがオススメ } /********************** 1 0 7 6 8 9 3 5 4 2 **********************/以上
はまったポイント
今回の練習ではまったポイントと言えば、WaitGroupeを用いて同期を取っているのだが、
全てのGoroutineがdeadlockしたよってpanicメッセージが飛んでくることがあった。これは、Wait()
もGoroutine化してあげることで解決。go func(){ wg.Wait() close(ch) }()全てのGoroutineが完了したらデータ受け渡し用のchannelをcloseしてあげることで、
for v := range ch { sum += v }を用いてchannelの中の値をエラーなくrangeで参照することができる。
closeせずにchannelから値を参照しようとすると、エラーが発生する。これは少し手間取った。
以上おわりに
次は、チャットアプリをTCPを用いてごにょごにょしているから、完成したら投稿しようと思う。
- 投稿日:2019-07-27T02:11:33+09:00
Visual Studio Code Remote を使って快適にGoの環境構築
Visual Studio Code Remote とは
Mozyです。
みなさんVSCode使ってますか〜。
自分は、最近VimからVSCodeに本格的に乗り換えてエディタのカスタマイズにハマってます。
そこで、おおっと思うVSCodeのエクステンションを発見しました。
これを使うといきなりDockerコンテナ内で開発が行えます。便利〜〜Developing inside a Container
https://code.visualstudio.com/docs/remote/containers経緯
Goの開発環境つくるのってめんどい(気がする)
GOPATHはどうするんだっけとか考えていて、調べてたところ発見して触ってみたらめっちゃ便利だったので紹介します。何が嬉しいか。
手元の開発環境と、Dockerコンテナ内部との違いなど、アレこれってどっちだっけと意識せずに開発が行えます。
便利〜〜やっていく
前提として: 手元マシンにDockerがインストールされているとかの前提が必要です。
お品書き
- エクステンションのインストール
- リポジトリをクローン
- エクステンションの機能から開く
- 環境がすでに整ってる!!
1. エクステンションのインストール
以下のリンクからインストールできます。
もちろんVSCodeの拡張画面から「Visual Studio Code Remote」と検索してインストールしてもおっけい。
https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers2. リポジトリをクローン
サンプルをシュシュっとクローン
git clone https://github.com/Microsoft/vscode-remote-try-go
3. リポジトリをエクステンションの機能から開く
画面左下のアイコンをクリックするとこんな感じの画面が出るので Openからクローンしてきたリポジトリを開く
4. 環境がすでに整ってる!!
ちょっと待つと、、
でもさ、ポートとか変えたいんだよね。って時ありますよね。
もちろん対応してます。
その他の細かい設定はjsonファイルをいじれば良いようです。
.devcontainer/devcontainer.json{ "name": "Go", "dockerFile": "Dockerfile", "appPort": 9000, "extensions": [ "ms-vscode.go" ], "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ], "settings": { "go.gopath": "/go", "go.inferGopath": true, "go.useLanguageServer": true } }通常のVSCodeとの差など
コンテナ上のエクステンションと、ローカルのエクステンションは別に管理される
まとめ
Visual Studio Code Remote を使うとGOPATHとかDockerfileとか何も考えなくても、エディタ側でよしなにしてくれるので、快適に環境構築ができます。
それではみなさん快適なVSCodeライフを〜。