TL;DR
ScratchのPython拡張を使ってKerasを呼び出すことにより、Scratchを機械学習のクライアントとして使ってみました。
適当な文章なので詳細な内容はよくわからないかもしれませんが、画像を見れば何となく変なことをしているのがわかると思います。
背景
先日アプリコンテストに参加して、小学生がScratchを使って立派なアプリケーションを作成しているのを見て感化されました。そこで、小学生に負けないように、大人の威厳を見せつけてやろうというのが、この記事のモチベーションです(大人げない)。
環境構築
Scratchのインストール
以下のリンクから必要なものをダウンロードしてオフラインエディタでScratchが動作するようにします
Scratch 2.0 Offline Editor
Pythonのインストール
Python3をインストールします。( Windows | Mac )
ライブラリ群のインストール
pipを使って機械学習フレームワークのKerasをインストールします。
pipenvが使える場合はML-on-scratchをcloneしてpipenv install
すると環境構築が完了します。
$ pip install tensorflow
$ pip install keras
Scratch用のPython拡張を作成するためのライブラリをインストールします。
$ pip install blockext
ScratchのPython拡張を作成
BlockExtの使い方
まずは、簡単なコードを書いて拡張機能が正しく動作することを確認してみます。
blockextからrun
, reporter
, command
をインポートして、デコレータ記法で使います。
@command
はScratchの命令ブロックを作ることができます。
@reporter
はScratchのデータブロックを作ることができます。
run()
で最後の引数にポート番号を指定して実行するとサーバが立ち上がります。
from blockext import run, reporter, command
message = ""
@command("set message %s")
def set_message(m):
global message
message = m
@reporter("get message")
def get_message():
return message
if __name__ == "__main__":
run("Extension Test", "extension_test", 5678)
プログラムを実行するとブラウザなどからアクセスしてみて確認することができます。
$ python extension_test.py
Listening on 5678
Download Scratch 2.0 extensionをクリックしてs2e
ファイルを保存します。
このあともサーバが必要なので、ダウンロードが完了してもプログラムを終了してはいけません。
Scratchから読み込み
Scratchからs2e
ファイルを読み込んでPython拡張を有効化します。
Shiftを押しながらファイルを開くと実験的なHTTP拡張を読み込み
という項目が現れます。そこから保存したs2e
を選択して読み込みます。
すると、その他
に自作の関数が現れます。
set message
で適当なテキストを設定して、get message
で取得して出力するようにScratchのブロックを組んでみると、以下のように動作します。
あとは機械学習するだけ!
あとは内容を機械学習に変えるだけで、Scratchから機械学習ができるようになります。
XOR
機械学習の入門としてXORを解くプログラムを作ってみます。
$x_{1}$ | $x_{2}$ | $y$ |
---|---|---|
0 | 0 | 0 |
1 | 0 | 1 |
0 | 1 | 1 |
1 | 1 | 0 |
from blockext import run, reporter, command, predicate
from keras.models import Sequential
from keras.layers import Dense, Activation
import numpy as np
class Model:
def __init__(self):
self.x = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
self.y = np.array([0, 1, 1, 0])
self.is_busy = False
self.score = [0, 0]
def build(self):
self.model = Sequential()
self.model.add(Dense(2, input_dim=2))
self.model.add(Activation('sigmoid'))
self.model.add(Dense(1))
self.model.add(Activation('sigmoid'))
def train(self):
self.model.compile(loss='mse', optimizer='adam', metrics=['accuracy'])
self.model.fit(self.x, self.y, epochs=10000)
self.score = self.model.evaluate(self.x, self.y)
@command("start training")
def train():
model.is_busy = True
model.build()
model.train()
model.is_busy = False
@predicate("is busy")
def is_busy():
return model.is_busy
@reporter("get score")
def get_score():
return "%d" % (model.score[1] * 100)
if __name__ == "__main__":
model = Model()
run("XOR", "xor", 5678)
$ python xor.py
Using TensorFlow backend.
Listening on 5678
Scratch側の処理
先程と同じようにs2e
ファイルを生成してScratchから読み込みます。
こんな感じでブロックを並べてやると、旗をクリックするとXORの学習が始まって、完了すると猫が精度を教えてくれます。
任意の入力で試せるように
せっかくなので、任意の入力を受け取ってそれに対する予測値を返せるようにしてみます。
予測を返す関数predict()
と入力値をセットする関数set_x1()
, set_x2()
を追加しました。
from blockext import run, reporter, command, predicate
from keras.models import Sequential
from keras.layers import Dense, Activation
import numpy as np
class Model:
def __init__(self):
self.x = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
self.y = np.array([0, 1, 1, 0])
self.test = [0, 0]
self.is_busy = False
self.score = [0, 0]
def build(self):
self.model = Sequential()
self.model.add(Dense(2, input_dim=2))
self.model.add(Activation('sigmoid'))
self.model.add(Dense(1))
self.model.add(Activation('sigmoid'))
def train(self):
self.model.compile(loss='mse', optimizer='adam', metrics=['accuracy'])
self.model.fit(self.x, self.y, epochs=10000)
self.score = self.model.evaluate(self.x, self.y)
def predict(self):
if hasattr(self, 'model') and not self.is_busy:
x = np.array([self.test])
y = self.model.predict(x)
return y[0][0]
return "None"
@command("start training")
def train():
model.is_busy = True
model.build()
model.train()
model.is_busy = False
print(model.predict())
@command("set x1 %n")
def set_x1(n):
model.test[0] = n
@command("set x2 %n")
def set_x2(n):
model.test[1] = n
@predicate("is busy")
def is_busy():
return model.is_busy
@reporter("get score")
def get_score():
return "%d" % (model.score[1] * 100)
@reporter("predict")
def predict():
return model.predict()
if __name__ == "__main__":
model = Model()
run("XOR", "xor", 5678)
実行して、
$ python xor.py
Using TensorFlow backend.
Listening on 5678
Scratch側で以下のようにブロックを並べると、入力$x_{1}, x_{2}$に応じて$y$の値が出力できます。
これで、XORを解く問題をScratchで実装することができました。本当はトレーニングデータも自由に入力できるようにしてANDやORも試せるようにしたかったのですが、時間的な問題からここまでで力尽きました。
とりあえず、Scratchを機械学習のクライアントに使えるということは証明できたのではないでしょうか。