MotoJapan's Tech-Memo

技術めも

【行動認識 #1】機械学習/深層学習で人間行動認識 ~事始め~

人間行動認識 ( Human Activity Recognition ) の領域の話。
去年の3月頃にやっていたが途中になっていたこともあったので、追試も兼ねて再開。
(tensorflowの勉強もしたいし)

人間行動認識 ( Human Activity Recognition ) とは

私の認識では、カメラやセンサ等、様々な機器から得られた情報を基に、人間の行動を学習/推定する認識技術テーマ。

f:id:motojapan:20170801234935p:plain



既存で様々な研究が進んでいて、
データであれば、

  • RGB画像、デプスなどの光学系
  • 加速度、角速度、気圧などのセンサ系

などがある。

行動であれば、

  • Standing, Sittingなどの姿勢系
  • Walking, Runningなどの遷移系
  • その他、より特徴的な動き

など多岐にわたる。

詳しくは後々まとめる。

やりたいこと

  • 行動認識のデータセットをいじり倒したい
  • tensorflowを勉強したい
  • 機械学習やら深層学習の知見を深めたい

データセット

個人的なモチベとしては、スマホでとれるセンサ系データを扱いたい(去年は扱っていた)

まず触ってみるのにいろいろ探したが、一番簡単に扱えそうなUCIから提供されるデータセットを利用。
UCI Machine Learning Repository: Human Activity Recognition Using Smartphones Data Set

一応、日本のデータセットも探した。
特に、大きいところだと、Human Activity Sensing Consortium(HASC)というコミュニティもあるが、簡単にアクセスできるデータが少なかったので今回は諦める。

ちなみに、UCIから提供される上記のデータセットは、2015年のUpdate版もある。
UCI Machine Learning Repository: Smartphone-Based Recognition of Human Activities and Postural Transitions Data Set

An updated version of this dataset can be found at [Web Link]. It includes labels of postural transitions between activities and also the full raw inertial signals instead of the ones pre-processed into windows.


「Human Activity Recognition Using Smartphones Data Set」『UCI Machine Learning Repository』より。
URL: https://archive.ics.uci.edu/ml/datasets/human+activity+recognition+using+smartphones

データについて

重要なことを書き残し

被験者 ボランティア 30人
年齢 19-48歳
行動種類 WALKING, WALKING_UPSTAIRS, WALKING_DOWNSTAIRS, SITTING, STANDING, LAYING
データ収集デバイス smartphone (Samsung Galaxy S II)
バイス上のセンサ 3軸加速度、3軸角速度
サンプリングレート 50 [Hz]
ラベリング手法 同時に撮影した映像からラベルを手動生成
データセット 学習データ:テストデータ=7:3 (被験者単位で分割)

データセットについては次の記載がある。

The sensor signals (accelerometer and gyroscope) were pre-processed by applying noise filters and then sampled in fixed-width sliding windows of 2.56 sec and 50% overlap (128 readings/window). The sensor acceleration signal, which has gravitational and body motion components, was separated using a Butterworth low-pass filter into body acceleration and gravity. The gravitational force is assumed to have only low frequency components, therefore a filter with 0.3 Hz cutoff frequency was used. From each window, a vector of features was obtained by calculating variables from the time and frequency domain.


「Human Activity Recognition Using Smartphones Data Set」『UCI Machine Learning Repository』より。
URL: https://archive.ics.uci.edu/ml/datasets/human+activity+recognition+using+smartphones

つまりこういうこと(だよね?)

  1. 加速度/角速度には、ノイズフィルタを適用
  2. 各データ(各軸)は128samplesのウィンド、オーバーラップは50%
  3. 加速度には、バターワスフィルタで重力成分を分離
  4. 特徴量ベクトルを時間/周波数領域から取得

今日は以上、次回は実際にデータを眺めたりしたい。

【Linebot #3】家の温度をLineで通知するアプリ (BME280使用)

家の温度をサーバーに通知して、自分のスマホに通知したい。
でも開発は最小にしたいので、Linebotとして開発した。
サーバサイドのみの開発で済むしね。
忘れそうなことや注意点を書き残し。
主に、サーバーサイドとクライアントサイドのpython実装とかとか。

できたもの

自動通知された時
日中部屋は地獄のように熱い orz (外の方が涼しい)
f:id:motojapan:20170722121708p:plain

システム図

  • 家で稼働しているraspberry piから温度センサ値を取得
  • herokuに定期通知
  • MessagingAPIを用いて、自作Lineアプリに温度を定期通知、もしくは、要求する。

f:id:motojapan:20170722120819p:plain

温度取得に使ったもの

1. 温度センサ
使用した温度センサはBME280。
温度だけでなく、気圧、湿度も取得可能。
これ↓↓↓
BME280使用 温湿度・気圧センサモジュールキット: センサ一般 秋月電子通商 電子部品 ネット通販

2. Raspberry Pi 3 Model B
https://www.amazon.co.jp/Raspberry-Pi-TSI-PI018-Clear-Pi3-Model/dp/B01D1FR29M

やったこと(忘れやすいことのメモ)

1. raspberry piにraspbianをインストール(割愛)
2. raspberry piにログイン

Raspbianの初期パスワード

username password
pi raspberry
4. 初期設定とか
$sudo apt-get update
$sudo apt-get upgrade
5. I2Cを有効化、ツールインストー
$sudo raspi-config

“5 Interfacing Options” -> “P5 I2C”でEnableを選択
カーネルモジュールがロード確認 (無事、i2c_devがある)

$lsmod

f:id:motojapan:20170722123534p:plain

I2C関連ツールをインストー

$sudo apt-get install i2c-tools
$sudo apt-get install python-smbus
6. BME280を繋いでI2C接続確認

実際の配線とかは※1を参考にしているので割愛

こんな感じ
f:id:motojapan:20170722124641p:plain

接続確認は以下

$ i2cdetect -y 1

f:id:motojapan:20170722123444p:plain

7. raspberry piから温度取得(割愛)

※1がわかりやすいのでそちらを参照しました。

8. 温度情報をHerokuに送信する

raspbianはpython2.7系なので、次のコードでクライアントサイドを実装
urllibを使います。
今回はPOSTになります。

#the case of python 2.7
import urllib 
def post_message(t, p, h):
    print('post')
    data = {}
    data["temp"] = t
    data["pres"] = p
    data["hum"]  = h
    server_addr = 'https://xxxxxxxxx.herokuapp.com/info'
    try:
        data = urllib.urlencode(data).encode("utf-8")
        res = urllib.urlopen(server_addr, data=data)
        res = res.read().decode("utf-8")
        print(res)
    except:
        print('error')

3系だったらこんな感じのはず

#the case of python 3.xx
import urllib.request, urllib.parse
def post_message(t, p, h):
    print('post')
    data = {}
    data["temp"] = t
    data["pres"] = p
    data["hum"]  = h
    server_addr = 'https://xxxxxxxxx.herokuapp.com/info'
    try:
        data = urllib.parse.urlencode(data).encode("utf-8")
        with urllib.request.urlopen(server_addr, data=data) as res:
           res = res.read().decode("utf-8")
           print(res)
    except:
        print('error')
9. 温度情報をHeroku側で受信する

サーバーサイド。こっちはflaskです。
linebot側はこの辺の知識を活用です。
【Linebot #2】herokuでブックマークアプリを作ってみた - MotoJapan's Tech-Memo
今回はPOSTを受ける。

#the case of python 3.xx
@app.route("/info", methods=['POST'])
def info():
    param_t = float(request.form['temp'])
    param_p = float(request.form['pres'])
    param_h = float(request.form['hum'])
    content = ' temp : {0:3.1f} deg \n hum  : {1:2.1f} % \n pres  : {2:4.1f} hPa '.format(param_t, param_h, param_p)
    user_id = 'yyyyyyyyyyyyy'
    line_bot_api.push_message(user_id, TextSendMessage(text=content))
    return 'POST OK!'

注意
GETも同様に書けばよいですが、param_t, param_p, param_hをグローバル変数にいれておいて、GETするのは危険です。
Herokuは定期的に寝てしまうので、寝ると消えるようです。
ちゃんとDBに登録したものを返すようにしましょう。

HTMLを返すとブラウザからはこんな感じで見えます。
f:id:motojapan:20170722131251p:plain

10. raspberry piスクリプトを起動し続ける

普段はteratermから接続していますが、セッションを切るとプロセスもきれる。
なので、次の2つ方法で、永続化をします。

1. rc.localに書き込む
これは起動時に実行されるスクリプトを指定できます。

$sudo emacs /etc/rc.local

重要なのは、「exit 0」の前に書くこと。

sudo python [target].py & #<-ここを追記
exit 0


2. 実行時にnohupを使う
これはセッションを切ってもプロセスを残す方法です。

$nohup sudo python [target].py &


以上、のんびりやっても半日くらいもあればできました!

参考資料

※1: BME280をRaspberryPiから動かす話(すごくわかりやすい)
Raspberry Pi + BME280モジュールで自動で温度・湿度・気圧を測定してグラフ化する - karaage. [からあげ]

【Linebot #2】herokuでブックマークアプリを作ってみた

普段気になるURLをブックマークしているが、また見返すことなく、あっという間に埋もれてしまう。
なので、LINEアプリとしてメモも書けるブックマークアプリを作ってみた。
後々は、特定のアドレスに定期送信したり、Slackにnotifyしたり、傾向解析したりする予定。

イメージ図

シーケンス図

  • 構成要素は、LineApp, Line server Heroku sever, Postgres server。
  • 実装作業は、Heroku severのみで、Postgres serverは、HerokuにDeployしたコードからSQLでいじる程度。

f:id:motojapan:20170719234712p:plain

登録 (Store)

  • URLとメモ(optional)を登録すると、タイトルを自動生成して送り返してくる

f:id:motojapan:20170719235752p:plain

履歴確認 (Restore)

  • 過去の登録内容(登録時刻、タイトル、メモ、URL)が表示される

f:id:motojapan:20170720000621p:plain

結構簡単にできる。

【Linebot #1】windows8.1(local)とherokuでpostgreSQL環境構築 (psycopg2)

Linebotを最近書いているのですが、そろそろオウム返しbotだけだとつまらないので、データベースを組み込みたいなと。
もともとsqlite3を使っていたのですが、Herokuはsqliteが簡単に使えないことをdeploy時に気づき、やむなくpostgres対応を考えることに。。


そこまでの手順とハマったことの覚書です。

手順は次です。

1. ローカルのwindows環境でpostgresをインストール・動作確認
2. heroku環境でpostgresをAddon追加・動作確認

1. ローカルのwindows環境でpostgresをインストール・動作確認

まず動作確認用のローカル環境を準備

1.1. windowsにpostgresをダウンロード・インストー

v 9.6.3-2を下記よりダウンロード

(あっという間に9系が主流みたいですね、pgadminも入っているし)

http://www.postgresql.org/

★インストール最終段階で「Database Cluster Initialization Failed」がでて失敗

対策:PostgreSQLのインストーディレクトリにフルコントロールを与える

これで無事インストールできました。(めでたしめでたし)

1.2. pythonから実行するためのモジュール(psycopg2)をインストー

次でインストー

>pip install psycopg2

動作確認

>python
Python 3.4.3 |Anaconda 2.3.0 (64-bit)| (default, Mar  6 2015, 12:06:10) [MSC v.1
600 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import psycopg2
>>>

エラーなしでOK!
たった手順2つで簡単に環境が揃いました。
あとは、psycopg2のAPIで接続やテーブル作成してみて動いていることを確認。

2. heroku環境でpostgresをAddon追加・動作確認

実はherokuでのpostgres環境構築は、windows環境よりはるかに楽でした。
(というかwindows側いらなかったと反省、インストールも早いし)

やることは、「herokuアプリケーションに heroku-postgresql Addonを追加」する。

herokuはpostgresをaddonとして対応(無料)しているのでこれを使う。
しかもheroku-postgresqlはherokuと独立したインスタンスを提供されます。
なので、ローカルで動作確認したい場合は、上記1.1で飛ばして、ローカルからpsycopg2で接続すればおk!

2.1. heroku-postgresql Addonの確認

確認方法1 (GUI)
https://dashboard.heroku.com/apps/application_id/resources
※application_idは自分のlinebotをdeployしているアプリケーションID
ここにheroku-postgresql Addonがあるか確認

確認方法2 (CUI)

>heroku addons --app <application_id>

無ければ次へ。

2.2. heroku-postgresql Addonの追加

>heroku addons:add heroku-postgresql --app <application_id>

下図のように、https://dashboard.heroku.com/apps/application_id/resourcesに追加されています。

f:id:motojapan:20170709165719p:plain

2.3. heroku-postgresql Addonの設定取得

>heroku config --app <application_id>
DATABASE_URL:              postgres://aaaa:bbbb@cccc:dddd/eeee
LINE_CHANNEL_ACCESS_TOKEN: xxxxxxxxxxxxxxxxxxxxxxxxxxx
LINE_CHANNEL_SECRET:       yyyyyyyyyyyyyyyyyyyyyyyyyyy

今回重要なのは、[DATABASE_URL]説明は次の通りです。

aaaa user_name
bbbb password
cccc server_name (私の環境はawsの模様)
dddd port
eeee database_name

psycopg2での接続は次のような対応になります。

import psycopg2
user   = aaaa
pwd    = bbbb
server = cccc
port   = dddd
db     = eeee        
con = psycopg2.connect("host=" + server + " port=" + port + " dbname=" + db + " user=" + user + " password=" + pwd)

なので、ローカル環境でもdeploy先のherokuでも上記で繋げばOKです。

【Messaging API #2】reply_messageで「request body has 1 error」が起きる (python)

herokuにdeployしていた下のコード。
ちゃんと動いていたはずのreply_messageがいつからかErrorを吐いて終了していたので覚書。

変な文字列でも入れたのかと切り分けを進めたが、結果的には、contentsの文字列が長すぎた模様。

line_bot_api.reply_message(
    event.reply_token,
    TextSendMessage(text=contents)
)

大体、マルチバイト含んで2000文字近く送ろうとした時に落ちてる。

OK NG
len(contents) 1709 2008
len(contents.encode(utf-8)) 2401 3092

headerとかもろもろのオーバーヘッドがあるので、具体的によくわかないけど目安がてら。


対策は下記2つ。

1. contentを分けて、reply_messageを細かく送信

予想はしていましたが、結果としてもダメでした。
event.reply_tokenは、①30秒以内に、②1度のみ送信できるルールでした。

2. contentを分けて、push_messageを細かく送信

これは成功しました。

(例)

for content in contents.split('\n')
    try:
        line_bot_api.push_message(user_id, TextSendMessage(text=content))
    except LineBotApiError as e:
        print(e)

めでたしめでたし。

【Messaging API #1】 クライアントのユーザID(user_id)とアカウント名(display_name)の取得方法 (python)

久しぶりにMessaging APIを使ってLinebotを書いてますが、メッセージを送信してきたクライアントのユーザIDとアカウント名の取得方法を忘れそうなので、書き残し。
クライアント側から「おはようございます」とpostすると、Webhookされたサーバーサイドのeventの中にこんな情報が入ってきます。
eventはWebhookParserでparseした中にiteratableで入っています。

event {
    "message": {"id": "xxx", "text": "おはようございます", "type": "text"}, 
    "replyToken": "yyy", 
    "source": {"type": "user", "userId": "zzz"}, 
    "timestamp": 14994xxxxxxxx, 
    "type": "message"
}

ここで得られる"userId": "zzz"が、ユーザIDに相当し、ここからget_profileでアカウント名を得ます。

if isinstance(event.source, SourceUser):
    profile = line_bot_api.get_profile(event.source.user_id)
    user_id        = event.source.user_id # ユーザID (zzz)
    user_disp_name = profile.display_name # アカウント名


終了です。

これで得られたユーザIDでDB登録をしたりできますね。

【kaggle⑤】初心者がタイタニック号の生存予測モデル(Titanic: Machine Learning from Disaster)をやってみる(機械学習、DeepLearning)

これの続きで、今回で一通り終わります。
【kaggle④】初心者がタイタニック号の生存予測モデル(Titanic: Machine Learning from Disaster)をやってみる(学習データ相関確認、分割、正規化) - MotoJapan's Tech-Memo

3. 機械学習

今回は下記6パターンをそれぞれ比較しました。
・Random Forest
・K Nearest Neighbor
・SGDClassifier(パーセプトロン)
Support Vector Machine
・Gradient Boosting Decision Tree
Deep Learning(by Chainer)

精度面では結論から言うといまのところ、Support Vector Machineが最も優秀でした。
Random Forest / Support Vector Machine / Deep Learning (by Chainer) がタイでベストスコア。(K Nearest Neighborが6%程悪い)
(17/07/15更新)


上記の内、古典的アルゴリズムは、sklearnに含まれています。
Deep LearningはChainerでシンプルなFFNNを構成しています。
実装はすべてgitにあるので参考にしてください。
sklearnに含まれているものは、どれも同じような呼び方で3行程度書けば学習もでき、評価もsklearn.metricsを使うと簡単です。(素晴らしいですね)

学習方法

sklearnに含まれるアルゴリズムでは、次の5手順で基本的には終わり

K近傍法の場合は下記

#1.アルゴリズムのインポート
from sklearn.neighbors import KNeighborsClassifier
#2.パラメータを指定してアルゴリズムインスタンス生成
knc = KNeighborsClassifier(n_neighbors=3)
#3.学習データで学習
knc.fit(x_train_std, y_train) 
#4.評価データで推定
y_true, y_pred = y_eval, knc.predict(x_eval_std)

ここでのパラメータはハイパーパラメータと呼ばれており、値によって精度が大きく変わる。
理詰めでハイパーパラメータのあたりをつけるのも良いが、組み合わせ爆発を起こすため、自力で求めず、グリッドサーチといった手法を使う方がベター

pythonには、sklearn.grid_search があるのでこれを使ってインスタンスを生成してベストなパラメータを求める。

今回は、Support Vector Machine / Gradient Boosting Decision Tree で利用。

精度評価

精度評価にはprecision, recall, f1 scoreが用いられるが、忘れやすいので、ここに書き残しておく。

説明 数式
precision 適合率 (正確性:システムが正しいと判定したものの内、本当に正しく判断できたのの割合) tp/(tp+fp)
recall 再現率 (網羅性:正しく判断されるべきものの内、本当に正しく判断できたのの割合) tp/(tp+fn)
f1 score F値 (2×適合率×再現率)/(適合率+再現率)

pythonでは上記の情報を1行でかける

from sklearn.metrics import classification_report
print(classification_report(y_true, y_pred, target_names=["not Survived", "Survived"]))

f:id:motojapan:20170630005035p:plain
これを用いて各精度比較を進める。

各精度比較

古典的アルゴリズム

F値精度を比較しました。
f:id:motojapan:20170630013252p:plain

  • SVMは精度が軒並み高く優秀
  • 一方、ワーストはSGD
    • 特にSurvivedの認識精度が悪い(データ数の問題か、表現力の問題か、、)

DeepLearning

FFNN (DeepLearning by Chainer)については、4層モデルを構築した。
1層当たりのunit数を調整しながら検証したが、8程度が過学習しないちょうどよいモデルであった。
ただし、データ量も少なくあまり良い成果は出なかった。(SVMには劣る結果)

unit数=8の場合

学習曲線(損失)
f:id:motojapan:20170630021025p:plain
学習曲線(精度)
f:id:motojapan:20170630021035p:plain

データ正規化比較

アルゴリズムを100回実行した場合の平均値は下記の通りとなりました。
これはF値ではなく、kaggleで扱われる単純な認識精度となります。

f:id:motojapan:20170630015326p:plain

  • 全体傾向は改善方向
  • KNN、SGDは大幅な精度改善が見える 5~10ポイント以上の改善
  • SVMでも、0.14ポイント精度改善
  • 手法によって精度が悪くなるパターンもあるがほぼステイ

正規化による精度改善がうかがえます。


以上、精度 78.974% (順位1777位タイ / 7082 teams) の話でした。

時間ができたら、もっとしっかり特徴量設計したいなぁ。

精度向上のための特徴量ToDo

  • 年齢ではなく年齢層を用いる
    • 1桁精度の年齢は実は不要な可能性がある
  • データ数を間引く
    • 全データを必ず使わなければならないというルールはなく、むしろ無理やりフィッティングすることで汎化性能が下がる可能性が高い
    • 信頼性のあるデータのみを使うほうがよいかも