Python
js
データ分析
アプリ開発
Jupyter

Jupyter上でマジックコマンドを利用して、簡単なアプリを作る

これなに?

Fringe81で機械学習エンジニアをやっている長谷川です。 業務では主に広告配信のサーバ側の開発(Scala)および、その配信上で実行する機械学習アルゴリズムの開発(Python)をやっています。

この記事では、Jupyterのマジックコマンドを利用して複数の言語を組み合わせることで、簡単なアプリケーション作成方法について書きます。

作るアプリの実行例

twitterのデータを手動でラベルづけするアプリです。Jupyter上で実行してます。

demo.gif

Jupyterとは?

さまざまなコードのインタラクティブな実行などがおこなえる、ブラウザ上で実行するエディタです。この記事もすべてJupyter上で書きました。 読み方は「ジュピター」派と「ジュパイター」派がいるようです。(個人的にはジュピター、ちなみにJupyterの名前の由来はJulia+python+Rだそうです。)

pythonのイメージが強いですが、kernelさえ入れ替えれば大抵の言語は実行可能です。

マジックコマンドとは?

さらに、juypterにはマジックコマンドと呼ばれる機能があり、セルの先頭に%%xxxのようなアノテーションを付けると、シェルやsqlなど、さまざまな処理がJupyter上で実行可能になります。

以下全て、Jupyterのcellで実行します

%%javascript

%%javascript
alert('こんにちはJS');

Jupyter上で実行するとダイアログができます。

%%HTML

%%html
<script>
alert("こんにちは HTML");
</script>

同じくダイアログが出ます。

JSのコードの中で、pythonの処理を実行するには?

今回の作成するアプリのイメージとしては、入出力の部分をhtml及びjsで記述し、裏側での処理をpythonで行う必要があります。そのため、jsのコードの中で、pythonの処理を実行するという、少しひねくれた処理を行う必要があります。

そのような処理をおこなうためには、IPython.notebook.kernel を使用します。これにより、jsの中から、notebookのカーネル上で実行したい処理を送信することができます

# これはpythonで実行
labels=[]

jsでコードを記述します

%%javascript
// これはjsで実行
var kernel = IPython.notebook.kernel;
// kernel上で実行したい処理をstringで記述
var label=1
kernel.execute("labels.append(" + label + ")");

結果を出力します。

print(labels)

# 出力結果    [1]

無事、js上からpythonでの処理が実行できました

アプリの例: データセットのラベルづけ

機械学習をする上で、ある意味アルゴリズム以上に辛いのは、データセットの作成です。教師あり学習の場合、当然教師ラベルを付与する必要があるのですが、そんな都合よくラベルが付いているデータがあることは少なく、仮についていたとしても人力での補正が必要な場合は多々あります。

そこで、人力でのラベル付けを行うためのアプリをJupyter上で完結する形で実装してみます。

例として、twitter上で「ワンピース」ついて検索して、某海賊漫画っぽい内容の場合は「1」、それ以外の場合は「0」でラベル付けするためのアプリを作成します。

なお、この例はPython Real World Data Science の内容を参考にしました。

バージョン

python -V
Python 3.7.0

jupyter notebook --version 
5.6.0

twitter==1.18.0

Tokenのロード

まず、別ファイルに記述してあるTokenを読み込みます。

機密情報をJupyerにべた書きすると、予期せぬところで流出するリスクが極めて高いので、別ファイルに書き出しておいて読み込んだほうが良いです。

こんなかんじに予め別ファイルに記述しておきます。

{
"consumer_key":"xxx",
"consumer_secret":"xxx",
"token":"xxx",
"token_secret":"xxx"
}

twtterの接続するためのモジュールをpipで入れます。

# twitterのAPIを叩くためにpipでいれる
!pip install twitter

Tokenを読み込みます。

import twitter
import json 

# 機密情報をうっかり流出させないように、別ファイルに保存してあるものを取得
with open('./twitter.json','r') as file: 
    token=json.load(file)
authorization = twitter.OAuth(**token)

t=twitter.Twitter(auth=authorization)

APIを叩く

データを取得するためのメソッドです。tweetが存在するもののみを返します。

tweets=[] # tweetを格納していくlist

def  twitter_gen(search_word,count=100):
    search_results=t.search.tweets(q=search_word,count=count)['statuses']
    index=0

    while True:
        search_result=search_results[index]
        if 'text' in search_result: 
            tweet=search_result['text']
            tweets.append(tweet)           
            yield tweet

        index+=1 

tweet_op=twitter_gen('ワンピース')

generatorで取得しているので、nextで読み込みます

print(next(tweet_op))
『お前がおれと同じ夢を持ってたからだ』 

by 赤足のゼフ

https://t.co/nXLEZN5lCx #ワンピース #画像 #名言

ゼフが誰か忘れましたが、無事取得できました。

アプリケーションの動きをJSで実装

アプリケーション上で実行するために、マジックコマンドを利用して、jsでscriptを記述します。

そして、そのscriptからJupyterのkernelに処理を送ることで、バックグラウンドでpythonを実行し、データを読み込みます。

# 正解ラベルを格納するlist
labels=[]

まず、ユーザーから判定されたラベルを追加していく処理です。

pythonのlistに人力で入力したラベルを格納していきます

%%javascript

function set_label(label){
    var kernel = IPython.notebook.kernel;
    kernel.execute("labels.append(" + label + ")");
    load_next_tweet();
}

次のtweetを読み込むための処理です。実行時にcallbackを登録すると、kernel側で非同期で行われた処理の結果を、登録してある関数(今回はhandle_output)に渡せます

%%javascript
function load_next_tweet(){
   var code_input = "next(tweet_op)";
   var kernel = IPython.notebook.kernel;
   var callbacks = { 'iopub' : {'output' : handle_output}};
   kernel.execute(code_input, callbacks, {silent:false});
}

tweetをhtml上で出力するための処理です

%%javascript

function handle_output(out){
   var res = out.content.data["text/plain"];
   $("div#tweet_text").html(res);
}

HTMLを記述

必要な処理はすべて定義したので、後は描画するだけです

%%html
<div name="tweetbox">
    某海賊漫画っぽいtweetならば「1」、そうでないならば「0」を入力してください<br>
Tweet: 
    <div id="tweet_text" value="text"></div><br>
<input type=text id="capture"></input><br>
</div>



<script>
load_next_tweet();
$("input#capture").keypress(function(e) {
if(e.which == 48) {
    set_label(0);
    $("input#capture").val("");
}else if (e.which==49){
    set_label(1);
    $("input#capture").val("");
  }
});

function set_label(label){
    var kernel = IPython.notebook.kernel;
    kernel.execute("labels.append(" + label + ")");
    load_next_tweet();
}

function load_next_tweet(){
   var code_input = "next(tweet_op)";
   var kernel = IPython.notebook.kernel;
   var callbacks = { 'iopub' : {'output' : handle_output}};
   kernel.execute(code_input, callbacks, {silent:false});
}

function handle_output(out){
   var res = out.content.data["text/plain"];
   $("div#tweet_text").html(res);
}
</script>

スクリーンショット 2018-12-04 21.57.05.png

無事、ラベル付けアプリができました👌
あとはラベル付けした内容を保存するだけです

保存

with open('./dataset.csv','w') as file:
    file.write('tweet,label\n')
    for x,t in zip(tweets,labels):
        file.write(f'{x},{t}\n')

念の為、保存されているか確認します

! cat ./dataset.csv 

こんなかんじに保存されてます。

    tweet,label
    ハイキューと僕のヒーローアカデミア、ワンピース買いに行きてぇ…!!,1

    まさかのワンピースかぶりっ!,0
    ワールドトリガー、僕のヒーローアカデミア、ワンピースの最新刊ゲット,1
    ツリーのワンピース可愛い~ #DDON https://t.co/Rb5VEOBicQ,1
    【 限定品 】 気になったら♡、欲しくなったらRT,0
    ...

ちなみにhtmlをstringで定義して、IPython.displayにあるHTMLで読み込んでも同じように出力されます。

html='''
上と同じ内容
'''
from IPython.display import HTML

HTML(html)

demo.gif

感想とか

こんなかんじにアプリができました、jupyterを使用すると、フロント側とバックグランド側の処理を一つの環境の中で行えるので、なにか作りたいアプリのたたき台やモックを作成するのにもjupyerは便利なんじゃないかなと思います👍
ただし、jsのコードのデバッグはムズいので、ガッツリ開発するのには向いてないです👎
マジックコマンドを使うとこんなかんじで様々な処理が一つのnotebookで完結してできるので、いろんな処理をつなぎ合わせて実験するのはとても楽しいです👌