chrome拡張機能
今回のとりあえずやってみるはchromeの拡張機能。 普段からadblock系やVue.js Devtoolsをブラウザに導入して使っているけど、その作り方に関する知識はまったくのゼロ
今回はchrome公式ドキュメントをGetting Started Tutorialをやってみて、chrome拡張機能のお作法を学んでみる。 (お作法全体を振り返りません、自分がポイントだと思うところを書きだすだけなので、チュートリアルとして利用しないで。。)
最終的にはオリジナル拡張機能を作る予定なので、記事をシリーズ化して今後まとめていこうかなと。
persistentオプション
バックグラウンドスクリプトに対して設定できるオプション persistentは持続的なという意味
persistent: trueにすると、バックグラウンドスクリプトが常に"アクティブ"でメモリ上にロードされている
一方、persistent: falseにすると、バックグラウンドスクリプトがイベント発火時にメモリにロードされるイメージ
persistent: falseにすることが推奨されていて、つまりイベント駆動のバックグラウンドスクリプトにしなさいという話らしい
(参照: Migrate to Event Driven Background Scripts)
chrome.storage.sync.setで設定した値を確認する
chrome.storage.sync.set({color: '#3aa757'}, function() { console.log("The color is green."); });
上記のコードでバックグランドの変更色を設定している。 この情報は普通のdevtoolでは確認できないようで、consoleから確認するしか術がないようだ。
chrome://extensions/ → バックグランドページ(どの拡張機能のリンクをたどってもよい) → consoleタブ
上記でたどり着いたconsoleから、
chrome.storage.sync.get(null, function (data) { console.info(data) });
を実行すると、設定した値が確認できる
ナレッジを収集するslack botの作成~GASを使って~
ナレッジを収集したい
slack上のナレッジを収集するslack botを作成しよう。アイデアの着想を得たのは、ある企業の人をお話から。その企業はslack上の会話によって蓄積されるナレッジを体系的な収集するslack botを作成したらしい。
私も個人開発でslackを使っている。単純にチャンネルごとにメモを分けるだけでも、タスク管理の効果があるし、スター機能を使えば重要な情報を管理できる。しかし、スター付けるほどではない情報やメソッド(これらをナレッジと呼ぼう)も多数あり、これらを効率的に管理する枠組みが必要であると感じていた。よし、私もナレッジを管理できるslack botを作成しよう。
どうやら、その企業のbotは言語処理モデルも組み込んで非常に高度なことをやっているようだが、今回は簡単に明示的にコマンドを打つことによってナレッジを保存するシステムを作成しようと考えている。
機能
- ナレッジのジャンルを登録できる機能
- ジャンルごとにナレッジを登録できる機能
- spread sheetに追加したナレッジをジャンルごとにslack上で呼び出せる機能
slack botの作り方
slack botの作成には、slack側の設定と実際アプリが機能する環境を用意する必要がある。ここではslack側の設定をまとめる。 タスクフローは以下の通り。
- slack apiにアクセス(https://api.slack.com/apps)
- Create New Appからアプリを作成
- アプリの名前とワークスペースを指定
- OAuth設定
- 左側メニューからFeatures→OAuth&Permissions
- 「app_mentions:read、chat:write」の2つのスコープを与える
- Event Subscriptionsの設定
- slack上のイベントをアプリに通知するための設定です
- webアプリのurlが必要なので後で設定します
GAS(Google App Script)が便利
実際にアプリが動作する環境としてGAS(Google App Script)を利用しよう。googleアカウントさえあれば、すぐGASの実行環境を作成できる。また、ボタン1つでwebアプリとして公開することも可能だ。
GASプロジェクトの立ち上げ、SlackAppライブラリの導入
私が参考にしたサイトを載せる。(分かりやすい) qiita.com
Bot User OAuth Access Tokenの登録
先ほどのslack apiのアプリ設定画面を開きます。 Features→OAuth&PermissionsにあるBot User OAuth Access Tokenをコピーしてください。
そして、そのコードをGASのプロジェクトに登録します。
コードピックアップ
コードの中でポイントとなるところだけをpick upします。
const params = JSON.parse(e.postData.getDataAsString()); ========================================================= return ContentService.createTextOutput(params.challenge);
params.challengeをreturnしてあげることで認証が完了します。
function writeLog(text){ let spreadSheet1 = SpreadsheetApp.openByUrl('ログ用spread sheetの共有url'); let sheet1 = spreadSheet1.getSheets()[0]; // 値の書き込み sheet1.appendRow([new Date(), text]); }
デバックするための関数。 GASはGCPとの連携をしないとデバックが困難。 適当なspread sheetを用意して、そのシートにログを出力しながらデバックするのがよし。
参考にさせてもらいました。
Google Apps Scriptでログをスプレッドシートに書き出す方法
コード
let spreadSheete; let sheet; function doPost(e) { try{ const token = PropertiesService.getScriptProperties().getProperty('KNWL_SLACK_ACCESS_TOKEN'); const slackApp = SlackApp.create(token); const params = JSON.parse(e.postData.getDataAsString()); const commands = params.event.text.split(' '); // 返送メッセージ let message; /* コマンド解析 */ const action = commands[1]; const genre = commands[2]; let content; if(commands.length == 3){ content = null; }else if(commands.length == 4){ content = commands[3]; }else{ const options = { channelId: "#general", userName: "knwl", message: "書式に違反しています" } slackApp.postMessage(options.channelId, options.message, {username: options.userName}); // validation return ContentService.createTextOutput(params.challenge); } /* ファイルを開く */ spreadSheet = SpreadsheetApp.openByUrl('ナレッジを書き込むspread sheetの共有url'); sheet = spreadSheet.getSheets()[0]; /* アクションごとに関数を呼び出す */ switch(action){ case 'setGenre': message = setGenre(genre); // ジャンルをセットする break; case 'set': message = setInfo(genre, content); // あるジャンルにナレッジをセットする break; case 'get': if(genre == "genres"){ message = getGenres(); // ジャンル一覧を取得する }else{ message = getGenreInfo(genre); // あるジャンルのナレッジをすべて取得する } break; default: break; } /* メッセージの送信 */ let options = { channelId: "#general", userName: "knwl", message: message } // send message slackApp.postMessage(options.channelId, options.message, {username: options.userName}); /* validation */ return ContentService.createTextOutput(params.challenge); }catch(err){ writeLog(err); } } function doGet(e){ doPost(e); } function setGenre(newGenre){ // 最終列を取得 const lastCol = sheet.getLastColumn(); // ジャンルが1つも存在しないとき if(lastCol == 0){ sheet.getRange(1, 1).setValue(newGenre); sheet.getRange(2, 1).setValue(2); return '正常にジャンルを登録しました。 ' }; // すべてのジャンル名を取得 const genres = sheet.getRange(1,1,1,lastCol).getValues()[0]; if(genres.indexOf(newGenre) == -1){ sheet.getRange(1, lastCol+1).setValue(newGenre); sheet.getRange(2, lastCol+1).setValue(2); return "正常にジャンルを登録しました。" }else{ return "そのジャンルはすでに登録されています。" } } function setInfo(genre, info){ // 最終列を取得 const lastCol = sheet.getLastColumn(); if(lastCol == 0){ return 'ジャンルが1つも存在しません '}; // すべてのジャンル名を取得 const genres = sheet.getRange(1,1,1,lastCol).getValues()[0]; if(genres.indexOf(genre) == -1){ return "ジャンルが存在しません" }else{ const genreCol = genres.indexOf(genre) + 1; const genreRow = parseInt(sheet.getRange(2,genreCol).getValues()[0]); // infoの書き込み sheet.getRange(genreRow+1, genreCol).setValue(info); // genreの最終行の記録 sheet.getRange(2,genreCol).setValue(genreRow+1); return "正常にナレッジを登録しました。" } } function getGenres(){ // 最終列を取得 const lastCol = sheet.getLastColumn(); if(lastCol == 0){ return 'ジャンルが1つも存在しません '}; // すべてのジャンル名を取得 const genres = sheet.getRange(1,1,1,lastCol).getValues()[0]; message = genres.join(' '); return message; } function getGenreInfo(genre){ // 最終列を取得 const lastCol = sheet.getLastColumn(); if(lastCol == 0){ return 'ジャンルが1つも存在しません '}; // すべてのジャンル名を取得 const genres = sheet.getRange(1,1,1,lastCol).getValues()[0]; if(genres.indexOf(genre) == -1){ return "ジャンルが存在しません" }else{ const genreCol = genres.indexOf(genre) + 1; const genreRow = parseInt(sheet.getRange(2,genreCol).getValues()[0]); // genreのinfoを取得 const allInfo = sheet.getRange(3, genreCol, genreRow - 2, 1).getValues(); return allInfo.join(' '); } } function writeLog(text){ let spreadSheet1 = SpreadsheetApp.openByUrl('ログ用spread sheetの共有url'); let sheet1 = spreadSheet1.getSheets()[0]; // 値の書き込み sheet1.appendRow([new Date(), text]); }
Events Subscriptionsの設定
さっき保留していたEvents Subscriptionsの設定。webアプリケーションのurlをslack apiの設定画面に登録する。
- webアプリケーションの公開
- GAS上のメニュー→webアプケーションとして導入から更新
- urlをコピー
※ プログラムを編集する度にProject VersionをNewにして更新
※ 公開範囲を「Anyone, even anonymous」に
- Events Subscriptionsの設定
完成!! 各コマンドの機能を説明
- @アプリ名 setGenre ジャンル1
- ジャンル1をspread sheetにジャンルとして登録する
- @アプリ名 set ジャンル1 ナレッジ1
- ナレッジ1をジャンル1に登録
- @アプリ名 get genres
- 登録されている全ジャンルを表示
- @アプリ名 get ジャンル1
- ジャンル1のすべてのナレッジを取得し、slack上に表示する
まとめ
はじめてslack botを作ったが、GASが便利すぎる。自分でサーバーも用意しなくていいし、deployして外に公開するのもボタン1つ。この簡易性は代えがたい。ただ、GAS単体ではデバックがしにくかったり、オンラインの開発環境なのでgit等での管理ができなかったり(やり方はあるかも・・・)不便なまだまだは多い。それでも、簡単なslack botを作るぐらいなら非常に有用性があることは間違いなさそう。
参考サイト
- Google Apps Script で Slack Botを作ってみた。(お勉強編),
https://qiita.com/Quikky/items/9de56c049304885a4f4f - 初心者がGASでSlack Botをつくってみた,
https://tech.camph.net/slack-bot-with-gas/ - Google Apps Scriptでログをスプレッドシートに書き出す方法 , http://www.dtm-hirasan.com/gas-log/
異なる背景に対する学習精度の変化
狙い
CNNを利用したネットワークでは認識したい物体の背景が学習に影響を与えると考えられている。
今回の記事ではその現象を実験的に示した。
データ
丸と四角をクラスとする2値分類問題とする。
グループ1(背景差なし)
グループ2(背景差あり)
各グループにおいて、クラス違い、背景違いで等分されるようにTrain,Testに分割した。
モデル
# モデルの定義 model = Sequential() model.add(Conv2D(32,3,input_shape=(256,256,3),kernel_initializer='glorot_uniform')) model.add(Activation('sigmoid')) model.add(Conv2D(32,3,kernel_initializer='glorot_uniform')) model.add(Activation('relu')) model.add(MaxPool2D(pool_size=(2,2))) model.add(Conv2D(64,3,kernel_initializer='glorot_uniform')) model.add(Activation('relu')) model.add(MaxPool2D(pool_size=(2,2))) model.add(Conv2D(128,3,kernel_initializer='glorot_uniform')) model.add(Activation('relu')) model.add(MaxPool2D(pool_size=(2,2))) model.add(Conv2D(64,3,kernel_initializer='glorot_uniform')) model.add(Activation('relu')) model.add(MaxPool2D(pool_size=(2,2))) model.add(Conv2D(64,3,kernel_initializer='glorot_uniform')) model.add(Activation('relu')) model.add(MaxPool2D(pool_size=(2,2))) model.add(Conv2D(64,3,kernel_initializer='glorot_uniform')) model.add(Activation('relu')) model.add(MaxPool2D(pool_size=(2,2))) model.add(Flatten()) model.add(Dense(1024)) model.add(Activation('relu')) model.add(Dropout(1.0)) model.add(Dense(2, activation='softmax')) rms = RMSprop(lr=0.0005, rho=0.9, epsilon=None, decay=0.0) run_options = tf.RunOptions(report_tensor_allocations_upon_oom = True) model.compile(optimizer=rms, loss='binary_crossentropy', metrics=["accuracy"],options=run_options)
結果
グループごとのvalidation error
val_error | 1回目 | 2回目 | 3回目 |
---|---|---|---|
G1 (背景差なし) | 81.25% | 81.25% | 87.50% |
G2 (背景差あり) | 62.50% | 56.25% | 68,75% |
簡単な分類問題を解いてみる
データ
以下のようなデータをpythonで出力する。
図1 散布図
エリアごとに色分けされたデータが散らばっていることがわかる。
各点の色は座標(x1,x2)によって定まるようである。
グラフのデータを機械学習で利用しやすい形に持ち込む。
図2 行列データ
図2において、1行目はx1 =0.031295,x2=0.284174の座標に赤点を存在することをします。
モデル
図3モデル
プログラム
今のプログラムの目的は、学習の経過を示すグラフの出力と新データに対して正しくラベル(色)が予測されているかを確認することである。
import keras from keras.layers import Input, Dense#,Dropout from keras.models import Model import numpy as np import matplotlib.pyplot as plt from keras.optimizers import RMSprop #データを作成 # generate data x1_r = np.random.rand(100)*0.5 x2_r = np.random.rand(100)*0.5 #label 1(red) l_r = np.zeros_like(x1_r) l_r = l_r + 0 x1_b = np.random.rand(100)*0.5 + 0.5 x2_b = np.random.rand(100)*0.5 #label 2(blue) l_b = np.zeros_like(x1_b) l_b = l_b + 1 x1_g = np.random.rand(100)*0.5 x2_g = np.random.rand(100)*0.5 + 0.5 #label 3(green) l_g = np.zeros_like(x1_g) l_g = l_g + 2 x1_y = np.random.rand(100)*0.5 + 0.5 x2_y = np.random.rand(100)*0.5 + 0.5 #label 4(yellow) l_y = np.zeros_like(x1_y) l_y = l_y + 3 #array merge A_r = np.c_[x1_r,x2_r,l_r] A_b = np.c_[x1_b,x2_b,l_b] A_g = np.c_[x1_g,x2_g,l_g] A_y = np.c_[x1_y,x2_y,l_y] A = np.r_[A_r,A_b,A_g,A_y] #shuffle np.random.shuffle(A) #split トレイン用データとテスト用データを分離 data,label = np.hsplit(A,[2]) #data_train :(300,2) date_test :(100,2) data_train,data_test = np.vsplit(data,[300]) #label_train :(300,1) label_test :(100,1) label_train,label_test = np.vsplit(label,[300]) label_train = keras.utils.np_utils.to_categorical(label_train.astype('int32'),4) label_test = keras.utils.np_utils.to_categorical(label_test.astype('int32'),4) #ニューラルネットワークのモデルを定義 #入力層 2入力 inputs = Input(shape=(2,)) #隠れ層 3出力で活性化関数ははrelu nw = Dense(3, activation='relu')(inputs) #出力層 4出力 ソフトマックス関数を挿入(0~1の値に収める) predictions = Dense(4, activation='softmax')(nw) #モデルをまとめる model = Model(inputs=inputs, outputs=predictions) #モデルをコンパイル 最適化手法:RMSprop 目的関数:categorical_crossentropy model.compile(optimizer=RMSprop(), loss='categorical_crossentropy', metrics=['accuracy']) #学習の実行 epochsは学習回数 historyには学習の経過が記録される。 history = model.fit(data_train, label_train, batch_size=10, epochs=150, verbose=1,validation_data=(data_test, label_test)) #学習の経過をグラフ化 plt.plot(history.history['acc']) plt.plot(history.history['val_acc']) plt.title('model accuracy') plt.ylabel('accuracy') plt.xlabel('epoch') plt.legend(['train', 'test'], loc='upper left') plt.show() #新データに対する予測 xa = np.array([[0.8,0.7]]) print("新データ(0.8,0.7)に対する予想") print(model.predict(xa))
出力
図4 認識精度の推移
epoch(学習回数)が増えるごとに認識精度が向上していることが見て取れる。
新データ(0.8,0.7)に対する予想 [[ 0.00191311 0.06421792 0.00333812 0.93053085]]
学習済みのニューラルネットワークは新データ(0.8,0.7)に対して4番目の要素(黄色に対応)である確率が
0.93053085でもっとも高いと予想している。
図1を見てみるとデータ(0.8,0.7)には黄色のラベルが付されそうである。
人間の判断とニューラルネットワークの判断が一致している。
このニューラルネットワークは正確な分類器として機能しているようである。
まとめ
非常に簡単なデータにたいして、ニューラルネットワークをトレーニングさせ、高精度の分類器を実現できた。
今回はepoch数を150に設定したが、50に設定しトレーニングすると、認識精度は150のときのそれに及ばなかった。つまり、epoch数を増やすことで認識精度が向上する様子を観察できた。
さらなる認識精度の向上には、さまさまな手法が存在するが、またの機会に記事にしたい。
■
KerasとTensorFlowで機械学習始めます。(1)
Kerasとtensorflowなのか
機械学習のライブラリといえば、Chainer,TensorFlow,Theanoなどがあがると思います。
機械学習を始めたての私にとっては当然どれがいいのかわからなくて、とりあえず国産のライブラリであるChainerから始めました。国産であるので、日本語のリファレンス等も豊富で、さらに記法もわかりやすい! といいところづくめだったんですが,GPU対応がどうしてもできませんでした。WindowsマシンにGPU対応環境をつくることが私の能力ではできなかった・・・。
そこで次にTensorFlowに手を出しました。Googleを出してるライブラリで世界的なシェアが大きく、学ぶには大きな意味がありそうでした。さらにTensorFlowでよかったのがWindows マシンでGPU環境を作ることが簡単にできたことです。オンラインのリファレンス読んで、もろもろのソフトのversionに気を付ければ結構簡単にGPUを利用できるようになりました!
しかし!私はこのTensorFlowを断念しました! なんか書き方が難しい! マジ無理!
計算グラフを常に考えた記法は私にとって少々難解でした。早くプログラム動かしたいのに、なんか覚えるメソッド多すぎ! 私はTensorFlowとお別れしました。
ただ、TensorFlowでGPU環境を作れたので、このアドバンテージは捨てたくない。
そこで、救世主Kerasが現れました。
KerasはTensorFlowなどのライブラリをバックエンド(計算等は元のライブラリに任せる)として機械学習のプログラムを簡単に書ける優れものでした。
TensorFlowで作ったGPU環境を利用しつつ簡単な記述でニューラルネットワークを記述できるんです!私自身まだ始めたてなのですが、これはいけるぞ! という感じがありますね。
このような経緯で私はKerasとTensorFlowでコーディングをしていくことを決意しました。
これから
このブログではとにかく、いろいろな問題に対して機械学習を用いて、解決策を見つけていこうと思います。とにかく、実装をこころがけて邁進します。
完全に私の忘備録になりますので、細かな説明などなくだらだら書いてしまうとおもいますが、もし興味を持ってコメントだったり、アドバイスをしてくれたら大変うれしいです。