20190727のGoに関する記事は6件です。

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の内容

Dockerfile
FROM 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/dlvgo 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.yml
version: '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

commandrealize を起動させています。

ports8888(WebServer用のポート)と 2345(デバッグ用のポート)を開放します。

security_opt, cap_add の2つはDocker上で delveを利用する為の設定です。

    security_opt:
      - seccomp:unconfined

Dockerが行うシステムコールを制限するオプションです。

(参考)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.yaml
settings:
  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... から設定を作成します。

createDebug1.png

この記事の通りにやれば delve での利用ポートはデフォルトの2345なので Go Remote のTemplateを使って作成すれば良いです。

createDebug2.png

Name はこの記事では app としていますが、わかり易ければ何でも構いません。

GoLand側からDockerコンテナに接続する

Run → Debug → から先程作成したデバッグ設定を開きます。

ConnectRemoteServer1.png

下記のように表示されていれば、接続が完了しています。

ConnectRemoteServer2.png

ちなみに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 app

GoLandでブレークポイントを設定してデバッグ

ブレークポイントを設定してアプリケーションのURLにアクセスしましょう。

ブレークポイントでアプリケーションが一時停止し変数の中身等が確認出来るかと思います。

ConnectRemoteServer3.png

あとがき

ここまでのコードはGitHubに公開してあります。

https://github.com/nekochans/portfolio-backend

Debuggerが使えると開発効率が上がりますので、少々時間をかけてでも設定を行うことをオススメします。

以上になります。最後まで読んで頂きありがとうございます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 )とのことなので、今後もキャッチアップを続けていきたいと思います。

おしまい。


  1. Go Modules 自体の説明は Go Modulesの概要とGo1.12に含まれるModulesに関する変更 - My External Storage がとてもわかりやすいので、そちらを参照ください。 

  2. go get のオリジナルデザインで重要だったことの1つに、Node の NPM や Java の Maven など中央集権型なコード管理ではなく、誰でも自分のコードを好きなサーバーで公開できて、誰もが好きなサーバーからコードをインポートできるという非中央集権型(分散型)なコード管理というものがあったとのこと。 

  3. Go パッケージの依存関係の分散化の重大な欠点として、公開されている Goパッケージ を見つけることの難しさや、そもそも Goパッケージ の提供が継続される保証がないということが挙げられていました。 

  4. 自分が読み違えて理解している場合もあるので、詳細が気になる方はオリジナルのブログを参照ください。 

  5. 公式ドキュメント では A module is a collection of related Go packages that are versioned together as a single unit. という定義がされています。 

  6. go help goproxy で使い方を参照ください。 

  7. 今回は Go Modules を利用しているプロダクトを Clone する形を取りましたが、単純に go get での比較でも良いと思います。 

  8. 詳細は Proposal: Secure the Public Go Module Ecosystem に記載されています。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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:latest

curlでアクセスすると、こんな感じになります。

$ 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使う
  • GitHubの「Project」を使って、タスクをIssueとして書く
  • コード書くのはmacOSでVScode、GitHub Desktopを併用
  • CIは、CircleCI使う。PRのMerge時にテストこけないようにする
  • 出来上がったコンテナは、DockerHubの自分のアカウントで公開する

この作業をやっていた2019/7/27は、Fuji Rockの中継をYoutubeでやっていたので、BGMとして楽しみました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自作ニューラルネットワークで手書き文字判別

この記事は前回Go言語でニューラルネットワークのスクラッチ実装の続きです。

前回、Go言語でニューラルネットワークを行列計算から作ってXORの非線形分類をやってみました。ただこれだと正直人間でも重みをイイ感じに設定すれば簡単にできてしまうのでどうにもニューラルネットワーク感がないなあと思いました。そこで、機械学習を使わないとコンピュータに処理させれなさそうで、かつすでに手法が存在して必ず成功するものはないかなと思ったところ、MNISTの手書き文字の分類をやればいいじゃないか!と思ったのでそれをやってみることにしました。

データの用意

機械学習と言ってもまずはデータがないと何もできません。今回は上にも書いたようにMNIST手書き文字のデータを使います。

データのダウンロード

THE MNIST DATABASE
of handwritten digits

このサイトからダウンロードできます。
キャプチャ.PNG

こんな感じの所があるのでポチポチダウンロードしてきます。上から学習用画像、学習用教師データ、テスト用画像、テスト用教師データです。

ダウンロードしてきたら解凍して適当なフォルダに入れてください。ここで、ダウンロードのページの下の方に行くとこのような記述があります。
データの概要.PNG

これは、先ほどダウンロードしてきたデータがどのような構造で保存されているかがかかれています。上はラベルファイル、つまり教師データです。これは最初の64bitには教師データではない情報が含まれていることがわかります。下はトレーニングデータです。これは最初の128bitは機械学習には必要のないデータが含まれています。

データの読み込み

ここで用意したデータを読み込みます。まずはトレーニングデータの画像の読み込みをします。画像のサイズは28×28なので最終的に[60000][28][28]のスライスを返すように実装します。

getmnist.go
func 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.go
func 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.go
func 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.go
package 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.go
func 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.go
func 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.go
func 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.go
func 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

epoc=10の誤差の推移
epoc=10.png

最後に

こうやってちょっと複雑なモデルを作ってると前回のように単純なものでは気づけなかった間違いに気づくことができました。ここまで来たら、CNNを実装してみたいので次はCNN作ります。この記事での間違いとか指摘などあったら教えてください。お願いします。(実はテスト期間真っただ中なのでそろそろ勉強しなくては)

github

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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を用いてごにょごにょしているから、完成したら投稿しようと思う。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

Visual Studio Code Remote

経緯

Goの開発環境つくるのってめんどい(気がする)
GOPATHはどうするんだっけとか考えていて、調べてたところ発見して触ってみたらめっちゃ便利だったので紹介します。

何が嬉しいか。

手元の開発環境と、Dockerコンテナ内部との違いなど、アレこれってどっちだっけと意識せずに開発が行えます。
便利〜〜

やっていく

前提として: 手元マシンにDockerがインストールされているとかの前提が必要です。

お品書き

  1. エクステンションのインストール
  2. リポジトリをクローン
  3. エクステンションの機能から開く
  4. 環境がすでに整ってる!!

1. エクステンションのインストール

以下のリンクからインストールできます。
もちろんVSCodeの拡張画面から「Visual Studio Code Remote」と検索してインストールしてもおっけい。
https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers

2. リポジトリをクローン

サンプルをシュシュっとクローン
git clone https://github.com/Microsoft/vscode-remote-try-go

3. リポジトリをエクステンションの機能から開く

画面左下のアイコンをクリックするとこんな感じの画面が出るので Openからクローンしてきたリポジトリを開く

image.png

4. 環境がすでに整ってる!!

ちょっと待つと、、

環境がすでに整った状態になって、
image.png

動かしてみる。9000番で立ち上がったようだな。
image.png

動いてる。すごい。
image.png

でもさ、ポートとか変えたいんだよね。って時ありますよね。
もちろん対応してます。
image.png

その他の細かい設定は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との差など

コンテナ上のエクステンションと、ローカルのエクステンションは別に管理される
image.png

image.png

まとめ

Visual Studio Code Remote を使うとGOPATHとかDockerfileとか何も考えなくても、エディタ側でよしなにしてくれるので、快適に環境構築ができます。
それではみなさん快適なVSCodeライフを〜。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む