じぶんメモ

プログラミングのメモ、日常のメモとか。

typescriptのビルド設定

はじめに

babel7がリリースされましたね! それに伴いtypescriptもサポートされたのでbabel単体でtsのビルドができるようになりました。
しかしbabelはtypescriptから型情報を取り除くだけなので型チェックまではサポートしておらず、
型チェックに関してはtsc経由で行う必要あります。
今回はwebpack + babel + tsc + tslintでtypescriptのビルド+lint+型チェックを行えるようにしたいと思います。

※ソースはこちらです https://github.com/tmzkysk/hello_typescript

必要なmoduleをインストール

まずは適当に作業ディレクトリを作り、 npm(yarn) initでプロジェクトを作成してください。 ※yarnを入れていない人は随時npmで置き換えてください。

$ mkdir hello_typescript && cd hello_typescript
$ yarn init

package.jsonが作成されたら必要なmoduleをインストールしていきます。

$ yarn add typescript webpack webpack-cli webpack-dev-server
$ yarn add -D @babel/core @babel/preset-env babel-loader ts-loader tslint tslint-loader prettier tslint-config-prettier tslint-config-standard tslint-plugin-prettier

ひとつずつ解説すると、

  • webpack:言わずもがな。ファイルバンドリングツール。
  • webpack-cli: webpackをcli上で使用できるようにする。
  • webpack-dev-server:開発時にtsの変更をwatchして自動的にビルドする
  • @babel/core:ES6 -> ES5を行うトランスパイラ
  • @babel/preset-env:サポートされている環境に基づいて必要なBabelプラグインを自動で決定するライブラリ
  • babel-loader:webpack上でbabelを使用するために必要
  • ts-loader:webpack上でtypescriptのビルドをするのに必要
  • tslint:typescriptの構文チェッカー(型チェックはやらないよ)
  • prettier:コードフォーマッター
  • tslint-config-prettier:TSLintの設定のうちPrettierと衝突するものを無効化してくれるパッケージ
  • tslint-plugin-prettier:TSLintのチェックをかけるときに一緒にPrettierをながしてくれる
  • tslint-config-standard:こちらの内容にそってTSLintのチェックをするときに必要
  • tslint-loader:webpackでTSLintを使用するのに必要

結構多い。。。 TSLintとprettierの違いはこちらの記事が参考になります。 https://qiita.com/soarflat/items/06377f3b96964964a65d

typescript側の設定

tsconfig.jsonの作成

typescriptのビルドやチェックのルールを設定するファイルです。 以下のコマンドでディレクトリに作成されます。

$ yarn tsc --init

tsconfig.jsonの設定

configに設定できる項目はたくさんありますが、とりあえずこれらを設定しておけば良いと思います。 型チェックや不要な変数のチェックなどはtsconfig側の設定になります。

{
  "compilerOptions": {
    /* Basic Options */
    "target": "ESNEXT",                    /* tsを書くときにどのversionのESが対象か: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
    "module": "ESNext",                    /* どのversionのESを生成するか: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
    "jsx": "preserve",                     /* reactを書くときに必要, 'react-native', or 'react'. */
    // "outFile": "./",                    /* 出力するファイル(ファイル出力はwebpackで行うので不要) */
    // "outDir": "./",                     /* 出力するディレクトリ(ファイル出力はwebpackで行うので不要) */
    // "rootDir": "./",                    /* ビルド時のルートディレクトリ(ビルド対象のパス設定はwebpackで行うので不要) */
    // "noEmit": "true",                   /* ビルド時に型ファイルを生成しない(これがあるとts-loaderでエラーが出るので外しています */

    /* Strict Type-Checking Options */
    "strict": true,                        /* すべての strict タイプ・オプション(noImplicitAny, strictNullChecks, noImplicitThis(thisの型チェック), alwaysStrict(strictモードのjs出力)) を 有効化する */
    "noImplicitAny": true,                 /* any型の使用不可 */
    "strictNullChecks": true,              /* nullable型以外でnullを許容しない */

    /* Additional Checks */
    "noUnusedLocals": true,                /* 未使用の変数を許容しない */
    "noUnusedParameters": true,            /* 未使用の変数を許容しない */
    "noImplicitReturns": true,             /* メソッド内で返り値の型があっているかをチェック */

    /* Module Resolution Options */
    "moduleResolution": "node",            /* http://js.studio-kingdom.com/typescript/handbook/module_resolution 参照 */
    "esModuleInterop": true                /* ESModuleと同じ動作をする. */
  }
}

babelの設定

まずは.babelrcを作成します。

$ touch .babelrc

中身はこのような感じです。 (react開発をする場合は@babel/preset-reactが必要です)

{
  "presets": [
    "@babel/preset-env"
  ]
}

tslintの設定

まずは設定ファイルを作ります。

$ touch tslint.json

中身はこのような感じ。 (reactの文法もチェックする場合はtslint-reactが必要です)

{
  "rulesDirectory": [
    "tslint-plugin-prettier"
  ],
  "extends": [
    "tslint-config-standard",
    "tslint-config-prettier"
  ],
  "rules": {
    "prettier": [
      true,
      {
        "singleQuote": true, // 文字列はシングルクオートで囲む
        "semi": false         // 文末のセミコロン(;)は省く。prettierのデフォルトはtrue
      }
    ]
  }
}

webpackの設定

まずは以下のコマンドでファイルを作成します。

$ touch webpack.config.js
module.exports = {
  mode: process.env.NODE_ENV || "development",
  entry: "./src/index.ts",
  output: {
    filename: "bundle.js",
    path: __dirname + "/dist"
  },
  resolve: {
    extensions: [".ts", ".tsx", ".js", ".json"]
  },
  module: {
    rules: [
      {
        test: /\.ts?$/,
        use: [
          // 下から順に処理される
          { loader: "babel-loader" },
          { loader: "ts-loader" },
          {
            loader: 'tslint-loader',
            options: {
              typeCheck: true,
              // tslint時に自動的に修正しない
              fix: false,
              // warningをエラーにすることでその後のビルドを止める
              emitErrors: true
            },
          },
        ],
        exclude: /node_modules/
      }
    ]
  },
};

今回はsrc/index.tsをビルドし、dist/bundle.jsとして出力する前提で記載しています。 複数ファイルの場合は配列やhashでの設定も可能です。 注意する点としてはloaderは下から上の順に実行されるので、

1.tslint-loaderでlintチェック 2.ts-loaderでtypescript -> ES6へ変換 3.babel-loaderでES6 -> ES5へ変換

というようになっています。

ビルド用のtypescriptファイル準備

試しにビルドする用のtypescriptを用意しましょう。 今回はsrc/index.tsを用意し、dist/bundle.jsとして出力したいので準備をします。

$ mkdir src dist
$ touch src/index.ts

中身は適当なtypescriptにしましょう。

const hoge: string = 'test'
console.log(hoge)

package.jsonへscript設定

あとはこれらを実行するのですが、package.jsonのscript項目に記載しておけば、 npm(yarn) run [タスク名] で実行可能です。

{
  "name": "hello_typescript",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "typescript": "^3.1.1",
    "webpack": "^4.20.2",
    "webpack-dev-server": "^3.1.9"
  },
  "scripts": {
    "build": "webpack",
    "watch": "webpack-dev-server",
    "lint" : "yarn run tsc && yarn run tslint",
    "tslint": "tslint --project tsconfig.json"
    "tslint-fix": "tslint --fix --project tsconfig.json"
  },
  "devDependencies": {
    "@babel/core": "^7.1.2",
    "@babel/preset-env": "^7.1.0",
    "babel-loader": "^8.0.4",
    "prettier": "^1.14.3",
    "ts-loader": "^5.2.1",
    "tslint": "^5.11.0",
    "tslint-config-prettier": "^1.15.0",
    "tslint-config-standard": "^8.0.1",
    "tslint-loader": "^3.6.0",
    "tslint-plugin-prettier": "^2.0.0",
    "webpack-cli": "^3.1.2"
  }
}

実行

ようやくビルドできるようになりました。 実行してみましょう。

普通にビルド

$ yarn run build
yarn run v1.5.1
$ webpack
Hash: 8732ceeed4c190294712
Version: webpack 4.20.2
Time: 7067ms
Built at: 2018-10-05 22:46:29
    Asset      Size  Chunks             Chunk Names
bundle.js  3.84 KiB    main  [emitted]  main
Entrypoint main = bundle.js
[./src/index.ts] 52 bytes {main} [built]
✨  Done in 8.76s.

できました! dist/bundle.jsが出来上がっていると思います。

lintは動くか

では、src/index.tsを以下のように書き換えてビルドするとどうなるか・・

const hoge: string="test";
console.log(hoge)
$ yarn run build
yarn run v1.5.1
$ webpack
Hash: 8f442c0fec1e406e6b3d
Version: webpack 4.20.2
Time: 3450ms
Built at: 2018-10-05 22:52:04
    Asset      Size  Chunks             Chunk Names
bundle.js  3.85 KiB    main  [emitted]  main
Entrypoint main = bundle.js
[./src/index.ts] 52 bytes {main} [built] [1 error]

ERROR in ./src/index.ts
Module Error (from ./node_modules/tslint-loader/index.js):
[1, 19]: Replace `="test";` with `·=·'test'`

error An unexpected error occurred: "Command failed.
Exit code: 2
Command: sh
Arguments: -c webpack
Directory: /Users/tmzkysk/hello_typescript
Output:
".

ちゃんとエラーになりました。 こういうときはyarn run tslint-fixとすれば直ります。

$ yarn run tslint-fix
yarn run v1.5.1
$ tslint --fix --project tsconfig.json
Fixed 1 error(s) in /Users/tmzkysk/hello_typescript/src/index.ts

中身を見るとしっかり直ってます。 なお、webpack.config.jsのtslint-loaderのoptionのfixをtrueにすればビルド時に勝手に直してくれます。

$ cat src/index.ts
const hoge: string = 'test'
console.log(hoge)

型チェックとかはどうか

次にtypescriptのコンパイラ側のチェックがちゃんと動くか確認します。 index.tsを以下のようにしてビルドしてみます。

const hoge: number ="test"
console.log(hoge)
$ yarn run build
yarn run v1.5.1
$ webpack
Hash: 8732ceeed4c190294712
Version: webpack 4.20.2
Time: 3322ms
Built at: 2018-10-05 22:58:50
    Asset      Size  Chunks             Chunk Names
bundle.js  3.84 KiB    main  [emitted]  main
Entrypoint main = bundle.js
[./src/index.ts] 52 bytes {main} [built] [1 error]

ERROR in /Users/tmzkysk/hello_typescript/src/index.ts
./src/index.ts
[tsl] ERROR in /Users/hello_typescript/src/index.ts(1,7)
      TS2322: Type '"test"' is not assignable to type 'number'.
error An unexpected error occurred: "Command failed.
Exit code: 2
Command: sh
Arguments: -c webpack
Directory: /Users/tmzkysk/hello_typescript

number型にstring型を代入しているためしっかりエラーになりました。

その他

package.jsonに設定した以下の内容について解説します。

  "scripts": {
    "build": "webpack",
    "watch": "webpack-dev-server",
    "lint" : "yarn run tsc && yarn run tslint",
    "tslint": "tslint --project tsconfig.json"
    "tslint-fix": "tslint --fix --project tsconfig.json"
  },
  • build: webpack.config.jsの設定に沿ってビルドします。
  • watch: webpack.config.jsの設定に沿ってビルドします。ファイルの変更を監視するのでtsファイル変更時に再度ビルドする必要がありません。
  • lint: typescript側のビルド時のチェックとTSLintのチェックをかけます。
  • tslint: TSLintのチェックのみかけます。
  • tslint-fix: TSLintのエラーを修正してくれます。

webpack-dev-serverについて

基本的に開発時はファイル変更と同時にビルドが走って欲しいので、
webpack-dev-serverを使用することになります。
webpack-dev-serverはwebpack-cliと違い、
何も設定していなければ localhost:8080 にビルド後のassetを吐き出すので、
railsでwebpackでビルドしたassetを参照する場合はlocalhost:8080から読み込むようにしてください。

おわりに

@babel/preset-typescritpがtsconfigの内容を踏襲してくれれば楽でしたが、
現状型チェックは提供されていないのでts-loaderをかませるようにしています。
今後のbabelに期待!

golangテストはじめ

はじめに

golangでのテストはとてもシンプルで、rubyrspecのように新しくDSLを覚える必要もありません。
テストについての記事は沢山あるのですが、自分の中で特にこれは最初に覚えておいた方がいいなと思うことをピックアップしました。

基本的なtestの書き方

  • 例えばcalc.go のテストならば同じディレクトリ内にcalc_test.goという名前で作成する。
  • テストファイル内ではtestingパッケージをインポートする。
  • テストファイル内では、TestXXXという名前でテストメソッドを作成する。
  • DSLは特に無いので普通にテストコードを書く。
  • パラメータと期待値の組み合わせの配列を用意して、ループで検証していく形が推奨されている(Table Driven Test)
package calc

func Add(a,b int) int {
    return a + b
}
package calc

import (
    "testing"
)

func TestAdd(t *testing.T) {
    patterns := []struct {
        a        int
        b        int
        expected int
    }{
        {1, 2, 3},
        {10, -2, 8},
        {-10, -2, -12},
    }

    for idx, pattern := range patterns {
        actual := Add(pattern.a, pattern.b)
        if pattern.expected != actual {
            t.Errorf("pattern %d: want %d, actual %d", idx, pattern.expected, actual)
        }
    }
}

testの実行方法

  • カレントディレクトリ以下すべてを再帰的にテストgo test -v ./...
  • 特定のパッケージをテストgo test -v ./hogehoge(パッケージディレクトリを相対パスで指定する)
  • 特定のメソッドのみテストするgo test -run TestAdd ./...

※ -v オプションを付けると実行結果に詳細が付きますので、基本的にはつけておいたほうが良いです。

テストの実行前後に処理を入れるには

TestMainメソッドを定義します。
code := m.Run()を実行するとテストメソッドが走るので、その前後にDBの初期化処理等を入れることが出来ます。

package calc

import (
    "fmt"
    "os"
    "testing"
)

func TestMain(m *testing.M) {
    fmt.Println("before test")
    code := m.Run()
    fmt.Println("after test")
    os.Exit(code)
}

func TestAdd(t *testing.T) {
    // 以下省略
}

これを実行すると、以下のようになります。
テストの前後にfmt.Printlnが入っているのがわかります。

$ go test -v ./...
before test
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
PASS
after test
ok

テストでモックを使うには

インターフェースを使ったモック

インターフェースを使っているオブジェクトの場合、実際のコードとテストコードでインタフェースに定義するオブジェクトを変えることでテスト時の振る舞いを変えることが出来ます。 ここではsomefuncパッケージのClientオブジェクトのRunメソッド内で呼び出されるcallメソッドの振る舞いを、モックを使って切り替える方法を紹介します。

package somefunc

type Caller interface {
    call(val int) int
}

type Client struct {
    FuncCaller Caller
}

type ExampleCaller struct{}

func (c *Client) Run(val int) int {
    return c.FuncCaller.call(val)
}

func (f *ExampleCaller) call(val int) int {
    return val
}

上記のコードを実行するには以下のように呼び出します。

c := somefunc.Client{&somefunc.ExampleCaller{}}
c.Run(1)

ここで、テスト時にExampleCallerのモックを作って、callメソッドの振る舞いを変えるにはテストコードを以下のようにします。

package somefunc

import (
    "testing"
)

func TestRun(t *testing.T) {

    patterns := []struct {
        val      int
        expected int
    }{
        {2, 2},
        {8, 8},
        {-10, -10},
    }

    for idx, pattern := range patterns {
        // Clientのnewの際に、モックオブジェクトを引数にする
        c := Client{&mockCaller{}}
        actual := c.Run(pattern.val)
        if pattern.expected != actual {
            t.Errorf("pattern %d: want %d, actual %d", idx, pattern.expected, actual)
        }
    }
}

// callメソッドのレシーバをmockCallerとして宣言する。
type mockCaller struct{}

// 通常のコードではcallメソッドは引数の値をそのまま返却するが、
// モックでは、引数 + 10した値を返却するようにする。
func (s *mockCaller) call(val int) int {
    return val + 10
}

変数の再代入で行う方法

ここではsomeprocessパッケージのRun関数のテストを行っていますが、Run内でcallという関数を呼び出しています。
このcall関数の挙動をテストの時だけ切り替えるには、call関数を変数に入れ、テスト内で変数にモックを再代入すればOKです。

package someprocess

func Run(val int) int {
    return call(val)
}

var call = func(val int) int {
    return val
}
package someprocess

import (
    "testing"
)

func TestRun(t *testing.T) {
    call = func(val int) int {
        return val + 10
    }

    patterns := []struct {
        val      int
        expected int
    }{
        {2, 12},
        {8, 18},
        {-10, 0},
    }

    for idx, pattern := range patterns {
        actual := Run(pattern.val)
        if pattern.expected != actual {
            t.Errorf("pattern %d: want %d, actual %d", idx, pattern.expected, actual)
        }
    }
}

anyenvを使って~env系をひとまとめにする

ruby, pythonを使用しているとrbenv, pyenv等を使ってバージョン管理をするが、
これらのバージョン管理システムは、言語ごとに導入、PATHを通す必要がある。
anyenvを使用すれば、これらの言語ごとのバージョン管理システムを一元管理できる。

1.以前使用していた~env系を削除

念の為。以前使用していた~env系とanyenvが競合しないように削除しておく。
brewでインストールしている場合は以下のコマンドでOK.

$ brew uninstall rbenv

他のツールが依存していると削除できないこともあるので、
その時はenvに依存しているツールを先に削除することでenvも削除することができる。
ディレクトリは残るのでこれも削除しておく。

$ rm -rf ~/.rbenv

2.anyenvのインストール

githubからcloneしてくる

$ git clone https://github.com/riywo/anyenv ~/.anyenv

PATHを通す。(bash_profileに記載)

if [ -d ${HOME}/.anyenv ] ; then
    export PATH="$HOME/.anyenv/bin:$PATH"
    eval "$(anyenv init -)"
fi

おそらくanyenv導入前は、各env毎に↑のコードを書いていたと思う。

3.各種envをインストール

以下のコマンドで各envを導入する。

$ source ~/.bash_profile
$ anyenv install rbenv
$ anyenv install pyenv

あとは以前と同じように各言語のversionをインストールすればOK。

$ rbenv install 2.4.0

gitで歴史を改ざんするには

直前の履歴を改ざんする

1. コミットをなかったことに

HEADの一つ前に戻る。

$ git reset HEAD~1
$ git push -f origin

2. コミットを修正

対象ファイルを修正し、commit --amendする。

$ git commit --amend
$ git push -f origin

特定の歴史を改ざんする

rebase -iを使い、改ざんしたいコミットまで戻り、修正する。

$ git rebase -i <改ざんしたいコミットのID>
pick c832e Add comment
pick f983e Initial commit

戻りたいコミットを指定し、editにし保存する。

edit c832e Add comment
pick f983e Initial commit

改ざんしたいファイルを修正し、commit --amendする。 その後、git rebase --continueで元に戻す。

$ git rebase --continue

途中、コンフリクトが発生することがあるので、そのときは解消して、
rebase --continueする。
Successfully rebased and updated refs/heads/ブランチ名 と出れば完了。

途中で訳わからなくなってもとに戻したいと思ったら↓のコマンドでOK。

$ git rebase --abort

n-gramによる文章の類似率を求める

N-gramとは、テキストで隣り合ったN文字のことを示す。
以下では2つ文章を指定された文字数で分割し、2つの文章間で分割した文字がどれだけマッチするかの頻度から、2つの文章の類似率を求める。

def ngram(str, num):
    res  = []
    slen = len(str) - num + 1
    for i in range(slen):
        extract_str = str[i:i+num]
        res.append(extract_str)
    return res

# 2つの文章の類似率を調べる
def diff_ngram(str1, str2, num):
    str1_ngram   = ngram(str1, num)
    str2_ngram   = ngram(str2, num)
    match_result = []
    count        = 0
    for str1_unit in str1_ngram:
        for str2_unit in str2_ngram:
            if str1_unit == str2_unit:
                count += 1
                match_result.append(str1_unit)
    return count / len(str1_ngram), match_result

# 2つの文章がどれだけ似ているかテストする
# 文字の分割を2文字ごと3文字ごとにして調べる。
a = '今日、渋谷で美味しいトンカツを食べた。'
b = '渋谷で食べた今日のトンカツは美味しかった。'

result2, word_list2 = diff_ngram(a, b, 2)
result3, word_list3 = diff_ngram(a, b, 3)

print('2-gram:', result2, word_list2)
print('3-gram:', result3, word_list3)

# 2-gram: 0.6111111111111112 ['今日', '渋谷', '谷で', '美味', '味し', 'トン', 'ンカ', 'カツ', '食べ', 'べた', 'た。']
# 3-gram: 0.29411764705882354 ['渋谷で', '美味し', 'トンカ', 'ンカツ', '食べた']

これだと単純な接続詞とかでもヒットしてしまうのであまり精度は高く無い気がする。 もっと本格的に文章解析するならば、RNNやLSTMに手をつかけるのがいいのかな。。。

TensorflowとKerasを用いてmnistのCNNを構築してみる

コードは以下の通り。

Tensorflow・Kerasを使ってmnistの訓練を行う

from keras.models               import Sequential
from keras.datasets             import mnist
from keras.layers.convolutional import Conv2D
from keras.layers.pooling       import MaxPooling2D
from keras.layers.core          import Dense, Dropout, Activation, Flatten
from keras.optimizers           import Adam
from keras.utils                import np_utils

from keras import backend as K

# ------------------------------
# データ準備
# ------------------------------
(X_train, y_train), (X_test, y_test) = mnist.load_data()

# データをfloat型にして正規化する
X_train = X_train.astype('float32') / 255.0
X_test  = X_test.astype('float') / 255.0

img_rows = 28
img_cols = 28

# image_data_formatによって畳み込みに使用する入力データのサイズが違う
if K.image_data_format() == 'channels_first':
    X_train     = X_train.reshape(-1, 1, img_rows, img_cols)
    X_test      = X_test.reshape(-1, 1, img_rows, img_cols)
    input_shape = (1, img_rows, img_cols)
else:
    X_train     = X_train.reshape(-1, img_rows, img_cols, 1)
    X_test      = X_test.reshape(-1, img_rows, img_cols, 1)
    input_shape = (img_rows, img_cols, 1)

# ラベルはone-hot encodingを施す
y_train = y_train.astype('int32')
y_test  = y_test.astype('int32')
y_train = np_utils.to_categorical(y_train, 10)
y_test  = np_utils.to_categorical(y_test, 10)

# ------------------------------
# モデルの定義
# ------------------------------
# 場合分けでインプットデータのテンソルの形を変える
# mnistデータは28×28ピクセルで60000個のデータで、28×28ピクセルで1チャネルのデータに変える
# reshapeの-1は、28×28ピクセルで60000個のデータを28×28ピクセルで1チャネルによしなに変えてくれる
model = Sequential()

# 畳み込み層1
# フィルタは5×5ピクセルで28個→出力データは32チャネル
# 入力データは28×28ピクセルの1チャンネル
# input_shapeを指定するのは1層目だけ
model.add(Conv2D(32, (5, 5), input_shape=input_shape))
model.add(Activation('relu'))
# プーリング層1
model.add(MaxPooling2D(pool_size=(2, 2)))

# 畳み込み層2
# フィルタは5×5ピクセルで64個→出力データは64チャネル
model.add(Conv2D(64, (5, 5)))
model.add(Activation('relu'))
# プーリング層2
model.add(MaxPooling2D(pool_size=(2, 2)))

# Flattenして全結合する
model.add(Flatten())
model.add(Dense(128))
model.add(Activation('relu'))

# 過学習予防のためドロップアウトして最後に出力(出力データ数はone-hotなので10個)
model.add(Dropout(0.5))
model.add(Dense(10, activation='softmax'))

# ------------------------------
# 学習の開始
# ------------------------------
epochs = 20
batch_size = 100

adam = Adam(lr=1e-4)
model.compile(optimizer=adam, loss='categorical_crossentropy', metrics=["accuracy"])
history = model.fit(X_train, y_train, batch_size=batch_size, epochs=epochs, verbose=1, validation_split=0.1)

loss, accuracy = model.evaluate(X_test, y_test, verbose=1)
print('loss=', loss)
print('accuracy=', accuracy)

以下、注意点。

  • mnistは28×28の1チャネルだが、RGBの画像とかだと3チャネルになる。
  • Conv2Dの引数は出力サイズ(フィルタのデータ数), フィルタの縦横サイズ、入力データのサイズ
  • KerasのConv2Dでは、1層目のみ入力データのサイズの指定が必要
  • KerasのバックエンジンによってConv2Dに渡す入力データのサイズの指定形式が異なる。

3つめの入力形式に関しては~/.keras/keras.jsonに記載されているimage_data_formatをみて判断する。

コードでは、1層目でmnistのデータを(データの個数, 28ピクセル, 28ピクセル , 1チャネル)のテンソルに変換し、
(5ピクセル, 5ピクセル, 1チャネル)のデータ32個のフィルタにかけていて、その結果をプーリングして次の層へ渡す。
最終的な出力の前に全結合してドロップアウトした結果をソフトマックス関数にかけてone-hotのラベル形式で出力している。

TensorflowとKerasを使ってmnistの訓練と評価をしてみる

Tensorflow・Kerasとは(ザックリ)

  • Tensorflowはpythonで使える機械学習のためのライブラリ
  • Kerasは更にラッパーライブラリで、Tensorflowだと数百行かかるコードが数十行で済んだりする。

インストール方法

とりあえずpipで入れてみる。

$ pip install -U tensorflow
$ pip install -U keras

Tensorflow・Kerasを使ってmnistの訓練を行う

from keras.datasets    import mnist
from keras.models      import Sequential
from keras.layers.core import Dense, Dropout, Activation
from keras.optimizers  import Adam
from keras.utils       import np_utils

# mnistデータの読み込み
(X_train, y_train), (X_test, y_test) = mnist.load_data()

# データをfloat型にして正規化する
# データ数×縦ピクセル数×横ピクセル数のデータをデータ数×784の方にする
X_train = X_train.reshape(60000, 784).astype('float32') / 255
X_test  = X_test.reshape(10000, 784).astype('float') / 255

# ラベルをone_hot形式に変換
y_train = np_utils.to_categorical(y_train, 10)
y_test  = np_utils.to_categorical(y_test, 10)

# モデルの構造を定義
model = Sequential()
# 1層目
model.add(Dense(512, input_shape=(784,)))
model.add(Activation('relu'))

# 2層目
model.add(Dense(512))
model.add(Activation('relu'))

# 出力
model.add(Dense(10))
model.add(Dropout(0.2))
model.add(Activation('softmax'))

# モデルの構築
model.compile(
    loss='categorical_crossentropy',
    optimizer=Adam(),
    metrics=['accuracy'])

# データで訓練
hist = model.fit(X_train, y_train)

# テストデータで評価する
loss, accuracy = model.evaluate(X_test, y_test, verbose=1)
print('loss=', loss)
print('accuracy=', accuracy)

特に注目したいのがmodelを用いて直感的に学習の手順を記述することができる。
コードでは3層のニューラルネットワークを構築している。
Denseは全結合を行うモジュールで、1層目のみ入力データのサイズを指定する必要がある。

モデルを定義し、コンパイルし、訓練をし、テストデータで精度を評価するという流れ。