MotoJapan's Tech-Memo

技術めも

【kaggle①】初心者がタイタニック号の生存予測モデル(Titanic: Machine Learning from Disaster)をやってみる(Titanic概要 ~ データ確認)

昨今取り沙汰されているkaggleに初挑戦してみました~

今回は、初心者向けのTitanicの生存率推定の精度を上げようと挑戦してみた過程を書き残します。
このタスクを通して、日頃は得意なデータや解析手法、機械学習手法しか扱っていなかった自分を戒め、いろいろやってみた経過を書いておきます。

結果

結果からすると、17/06/11 時点で
・精度は、78.974%
・順位は、1777位タイ / 7082 teams
みたいです。
精度は80% over / 順位は3桁目指したいですね。
しかも1人正しく判定できるか否かで全然順位が変わるので、この当たりはかなりひしめき合ってますね。

f:id:motojapan:20170611155133p:plain

Kaggleとは

Kaggleとは、世界最大レベルの機械学習コンペティションプラットフォームで、最近ではgoogleに買収されたことが話題となりました。

Kaggleは企業や研究者がデータを投稿し、世界中の統計家やデータ分析家がその最適モデルを競い合う、予測モデリング及び分析手法関連プラットフォーム及びその運営会社である。


「Kaggle」『フリー百科事典 ウィキペディア日本語版』より。
"最終更新 2016年9月1日 (木) 03:17" UTC
URL: https://ja.wikipedia.org/wiki/Kaggle

Titanic : Machine Learning from Disasterとは

In this challenge, we ask you to complete the analysis of what sorts of people were likely to survive. In particular, we ask you to apply the tools of machine learning to predict which passengers survived the tragedy.

『Titanic: Machine Learning from Disaster | Kaggle』より。
URL: https://www.kaggle.com/c/titanic

大雑把にいうと、タイタニック号の乗客の情報から傾向を見つけて、彼ら生存率を推定するタスクです。
具体的に、データセットは、train.csv(train用) / test.csv(submit用) の2つが存在し、
test.csvは、Survivedのみ情報が欠落しているため、これを推定して、KaggleサーバーにSubmitするというのがゴールになります。
データセットの詳細は次に。


0.環境とソースコード

私の環境は次の通り。
Python 3.4.3 |Anaconda 2.3.0 (64-bit)| (default, Mar 6 2015, 12:06:10) [MSC v.1600 64 bit (AMD64)] on win32
(・お好みでseaborn)
・IPython notebookで作業

ソースコードGitHub
github.com

1.モジュールロード

よく使うのものは早めにコールしておきます。

#load module
import csv
import pandas as pd
%matplotlib inline
import matplotlib.pylab as plt
import seaborn as sns # it's up to you
import numpy as np
sns.set_style("whitegrid")

2.データセットについて

まずデータセットの中身を確認
これで大雑把に把握

SRC_TRAIN = '../data/train.csv'
df_train = pd.read_csv(SRC_TRAIN)
df_train.head(8)

f:id:motojapan:20170611160809p:plain

それぞれの詳細なデータの説明は次の通り。
f:id:motojapan:20170611160900p:plain


へぇ~って感じですね。
やるべきことはこのデータを解析して、足りないデータを補ったり、特徴量を選んだり、新たに作ったり、
機械学習手法を選んだり、ハイパーパラメータを設定したり、、、、って感じで精度の高い判別器をつくります。


お腹がすいて集中力が切れたので本日はここまで。


次は、具体的なデータ解析を書き残します。

【機械学習】超雑メモ2 (サポートベクターマシン(SVM)、決定木(Decision Tree))

続き
motojapan.hateblo.jp

サポートベクターマシン(SVM)

  • マージン最大化により得られる超平面で分類/回帰
  • カーネルトリックを使った非線形分離が可能
    • カーネル」=「2サンプル間の類似性を表現する関数」
    • [python] 例えば、kernel=rbf
    • ハイパーパラメータ C/γを調整
      • C = 誤差分類ペナルティ : マージン幅を制御可能
        • 大きい程、誤差を許さない
      • γ = 1 / σ^2 (分散の逆数)
        • 小さい程、トレーニングサンプルの影響が大きくなり、決定境界が滑らかになる
  • ハイパーパラメータはグリッドサーチで最適解を求める
    • [python] from sklearn.grid_search import GridSearchCV が便利

決定木(Decision Tree)

  • 情報利得最大化となる特徴量でデータを分割
    • 情報利得:分割された集合のばらつきの減少
  • 葉が純粋(すべて同じクラス)になるまで分割
  • ただし、決定木の深さ=過学習と検索速度
  • 分割条件は3つ
    • エントロピー (entropy)
      • 各クラスの相互依存度を最大化するという条件(それぞれのノードの各クラスのばらつきを最小化)
      • IH(t) = - \sum_{i=1}^{C}(p(i|t)log2(p(i|t)))
      • p(i|t) : 特定ノードtにおいてクラスiに属するサンプルの割合

f:id:motojapan:20170607003010p:plain

    • ジニ不純度 (Gini impurity)
      • 不純度を最小化(誤分類確率の最小化)するという条件
      • IG(t) =  \sum_{i=1}^{C}(p(i|t)(1-p(i|t)) = 1 - \sum_{i=1}^{C}p(i|t)^2
      • 二値分類でこれが最大化するのは、p(i|t)=0.5 : IG(t) = 0.5

f:id:motojapan:20170607005322p:plain

    • 分類誤差 (classification error)
      • IE(t) =  1 - \max[p(i|t)]
  • Random Forest
    • Decision Treeのアンサンブル学習
    • トレーニングデータセットとして、ブーストラップ標本を用いる
    • 汎化性能向上と過学習回避、Decision Tree noiseに強い
    • Decision Treeの数だけ計算コスト大
    • 各分割の特徴量数 {\sqrt{m}} :
    •  m:トレーニングデータセットの特徴量個数

【機械学習】超雑メモ1 (パープトロン~ロジスティックス回帰)

思い出しのためにもメモ

 

パーセプトロン(Rosenblatt)

 

ADALINE

  • Rosenblattパープトロンの改良版
  • 活性化関数が線形関数
  • 量子化器がステップ関数
  • 活性化関数が微分可能となり、勾配降下法で誤差更新が可能

 

ロジスティックス回帰

  • パープトロンの学習則の見直し(クラスの線形分離が完全にできない限り、永久に収束しない)
  • 活性化関数が非線形関数(シグモイド関数)
  • ここで範囲が0-1に収まり、確率観点で捉えることができる
  • コスト関数は対数尤度最大化と置き換え、重み更新する

 

 

【Python】【Webアプリ】flaskで外部公開できないとき

flaskを使っていてウェブアプリを作成時に気づいたことの書き留め

何故か外部公開できない。。。

from flask import Flask, render_template, request
app = Flask(__name__)

....

if __name__ == "__main__":
    app.run(port=9999)


host設定が必要でした!

from flask import Flask, render_template, request
app = Flask(__name__)

....

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=9999)

これでも繋げない場合はファイアウォールとかかな

【Android】ファイル 書き出し(保存)、追記、エラー時(Java.illegalArgumentException : File contains a path separator) 対策

android アプリにおけるjava実装で忘れやすいことをメモメモ

ファイル 書き出し(保存)、追記、エラー時(Java.illegalArgumentException : xxx contains a path separator) 対策について、実装例を上げていきます。

まず、ファイル 書き出し(保存)、追記について下記。

// 新規ファイルとしてファイル保存
// MODE_PRIVATE : 実装しているアプリのみからアクセス可能
public void writeTextToFile(String fileName, String text) {
    FileOutputStream fos = null;
    try {
        fos = openFileOutput(fileName, Context.MODE_PRIVATE);
        fos.write(text.getBytes());
    } catch (IOException e) {
        e.printStackTrace();
    }
}

// 既存ファイルへ追記保存
// MODE_APPEND :追記モード
public void appendTextToFile(String fileName, String text) {
    FileOutputStream fos = null;
    try {
        fos = openFileOutput(fileName, Context.MODE_PRIVATE|Context.MODE_APPEND);
        fos .write(text.getBytes());
    } catch (IOException e) {
        e.printStackTrace();
    }
}


これで実行時エラー(Java.illegalArgumentException : File xxx contains a path separator)が起きる場合がある
ローカル領域へ直保存する場合は問題ないが、ディレクトリ構造を持つ場合、このエラーが発生する

このエラーはFileOutputStreamインスタンスを次に修正すれば対応可能

// 新規ファイルとしてファイル保存
public void writeTextToFile(String fileName, String text) {
    FileOutputStream fos = null;
    try {
        //fos = openFileOutput(fileName, Context.MODE_PRIVATE);
        fos = new FileOutputStream(new File(fileName));
        fos.write(text.getBytes());
    } catch (IOException e) {
        e.printStackTrace();
    }
}

// 既存ファイルへ追記保存
public void appendTextToFile(String fileName, String text) {
    FileOutputStream fos = null;
    try {
        //fos = openFileOutput(fileName, Context.MODE_PRIVATE|Context.MODE_APPEND);
        fos = new FileOutputStream(new File(fileName), true);
        fos .write(text.getBytes());
    } catch (IOException e) {
        e.printStackTrace();
    }
}

追記モードの設定は、FileOutputStreamの第2引数=trueで設定可能
ちなみに、追記モードはtextに改行コードを入れないと素直に文字が追加される


AndroidManifest.xmlパーミッション設定は忘れずに。

<manifest ...>
    ...
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    ...
</manifest>

【 opencv 基礎知識 3】 異常検知アプリ作成!③ -フレーム間差分の二値化、二値変化率の認識、画像保存、検知枠の設定-

このお題目は2年前に完遂していたにも関わらず、纏めないまま放置されていたので思い出しも兼ねて書きます!

以前はC++で書いていましたが、pythonで書くと短期作業で実装できるので切り替えました。
環境は次の通り。


これの続き
motojapan.hateblo.jp


今回のスクリプトでできることは、画像のような感じで、
認識エリアを設定して、その範囲に動きがあればカメラのシャッターを切るといったものです。

例えば、コルクに認識エリアを設定して、(Escキーで決定)
f:id:motojapan:20170507013230p:plain
持っていこうとすると犯行現場が撮影される!って感じです。(逮捕!!)
f:id:motojapan:20170507013235p:plain

おさらい(カメラ起動/終了とグレースケール画像取得)

pythonで書くとこんな感じです。

def get_gray_frame(cap, size=DEFAULT_SIZE, flip=FLIP):
	res, frame = cap.read()
	if size is not None and len(size) == 2:
		frame = cv2.resize(frame, size)
	if flip is True:
		frame = frame[:,::-1]
	gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
	return gray

def start_preview(device_id = DEVICE_ID):
	#init camera device
	cap = cv2.VideoCapture(device_id)
	while True:
		start = time.time()
		# get frame
		#frame = get_frame(cap)
		frame = get_gray_frame(cap)

		# display frame
		cv2.imshow('camera preview', frame)
		if cv2.waitKey(1) == 27: # wait 1msec / finish by ESC key
			break

		elapsed_time = time.time() - start
		sys.stdout.write('elapsed_time {:3.3f} [s] \r'.format(1 / elapsed_time))
		sys.stdout.flush()

	# destroy window
	cv2.destroyAllWindows()
	#release camera device
	cap.release()

大事なところは、
1. cv2.VideoCaptureでカメラ起動、cap.release()でカメラ終了
2. cv2.VideoCaptureは引数にDEVICE_IDを設定
  これはPCにWebカメラが2つ以上あった時に、DEVICE_ID=0, DEVICE_ID=1, , , のように設定します。
3. cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)でグレースケール化

ちなみに、グレースケール化したframeの中はこのような感じ

array([[139, 139, 139, ..., 165, 165, 165],
       [139, 139, 138, ..., 164, 164, 164],
       [138, 137, 136, ..., 164, 164, 164],
       ...,
       [ 63,  63,  63, ..., 103, 102, 100],
       [ 64,  63,  63, ..., 103, 101, 100],
       [ 63,  63,  63, ..., 102, 100,  99]], dtype=uint8)

フレーム間差分の二値化

これはcv2が便利な関数(cv2.absdiff/cv2.threshold)を準備しているので是非使います。

def detector(cap, detect_rect):
	detect = False
	prev_frame = None
	crt_frame  = None
	while True:
		start = time.time()
		# get frame
		#frame = get_frame(cap)
		crt_frame = get_gray_frame(cap)

		if prev_frame is not None:
			# diff frame
			diff_frame = cv2.absdiff(crt_frame, prev_frame)
			# binary frame
			diff_b_frame = cv2.threshold(diff_frame, 50, 255, cv2.THRESH_BINARY)[1]
			cv2.imshow('processing preview', diff_b_frame)
			detect, ratio = check_detect(diff_b_frame, detect_rect)
		if detect:
			# destroy window
			cv2.destroyAllWindows()
			return crt_frame

		# display frame
		cv2.imshow('camera preview', crt_frame)
		if cv2.waitKey(250) == 27: # wait 250 msec / finish by ESC key
			break

		prev_frame = crt_frame
		elapsed_time = time.time() - start
		sys.stdout.write('elapsed_time {:3.3f} [s] \r'.format(1 / elapsed_time))
		sys.stdout.flush()

	# destroy window
	cv2.destroyAllWindows()
	return None

特に

diff_frame = cv2.absdiff(crt_frame, prev_frame)

2枚のグレースケール輝度差を出力

diff_b_frame = cv2.threshold(diff_frame, 50, 255, cv2.THRESH_BINARY)[1]

グレースケール画像を2値化 (0 or 255)

(慣れていれば別ですが)これを使わず自力で書くといろいろと考慮することが発生するので、使えるものはどんどん使う。
(arrayの引き算をするにしていも dtype=uint8であるし、0-255に納めなきゃいけないし、、)

二値変化率の認識

ここまでくればあとは何をトリガーにframeを保存するかだけです。
今回は二値の変化率が一定の閾値を超えたかどうかで判断しています。
(例えば0.2)

def check_detect(b_frame, detect_rect):
	detect_rect.modify()
	window = b_frame[detect_rect.y : detect_rect.y + detect_rect.h, detect_rect.x : detect_rect.x + detect_rect.w]
	#check change ratio of binary values
	ratio = np.mean(window) / 255
	if ratio > 0.2:
		return True, ratio
	return False, ratio

ポイントはこれ

ratio = np.mean(window) / 255

numpy.meanによる平均値を使いますが、二値化は0 or 255なので、255で割って0 ~ 1の範囲に収めます。
windowは検知エリアのフレーム情報です。

画像保存

検知エリアをつける場合はcv2.rectangleなどで検知エリアを書き込みます。
※ただし、今回はすべてグレースケールへ落とし込んでいるので、(255, 255, 255) -> (255, 255, 0) としても枠は白です。

# save image
if initial_img is not None:
	save_image('./save', '0_initial_image_with_rect.png', initial_img)
if detect_img is not None:
	save_image('./save', '1_detect_image.png', detect_img)
	cv2.rectangle(detect_img, (detect_rect.x, detect_rect.y), (detect_rect.x + detect_rect.w, detect_rect.y + detect_rect.h), (255, 255, 255), 2)
	save_image('./save', '2_detect_image_with_rect.png', detect_img)

検知枠の設定

最初に設定するものを最後に説明することになりましたが、cv2.setMouseCallbackを使ってマウス操作で検知枠を設定します。

class Rect:
	def __init__(self, x, y, w, h):
		self.x = x
		self.y = y
		self.w = w
		self.h = h

	def modify(self):
		if self.w < 0:
			self.w *= -1
			self.x -= self.w
		if self.h < 0:
			self.h *= -1
			self.y -= self.h

class Meta:
	def __init__(self, window_name, img, rect):
		self.img = img
		self.img_bk =np.copy(img)
		self.rect = rect
		self.window_name = window_name

def mouse_event(event, x, y, flags, param):
	if event == cv2.EVENT_LBUTTONDOWN:		
		param.img = np.copy(param.img_bk)
		param.rect.x = x
		param.rect.y = y
	if event == cv2.EVENT_MOUSEMOVE and flags == cv2.EVENT_FLAG_LBUTTON:
		param.img = np.copy(param.img_bk)
		param.rect.w = x - param.rect.x
		param.rect.h = y - param.rect.y
		cv2.rectangle(param.img, (param.rect.x, param.rect.y), (param.rect.x + param.rect.w, param.rect.y + param.rect.h), (255, 255, 255), 2)
		cv2.imshow(param.window_name, param.img)
	if event == cv2.EVENT_LBUTTONUP:
		param.img = np.copy(param.img_bk)
		param.rect.w = x - param.rect.x
		param.rect.h = y - param.rect.y
		cv2.rectangle(param.img, (param.rect.x, param.rect.y), (param.rect.x + param.rect.w, param.rect.y + param.rect.h), (255, 255, 255), 2)
		cv2.imshow(param.window_name, param.img)

def configure_detect_rectangle(cap):
	crt_frame = get_gray_frame(cap)
	window_name = 'configure detect rectangle'
	detect_rect = Rect(0, 0, 0, 0)
	meta = Meta(window_name, crt_frame, detect_rect)
	cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)
	cv2.setMouseCallback(window_name, mouse_event, meta)
	cv2.imshow(window_name, crt_frame)
	while True:
		if cv2.waitKey(1) == 27: # wait 1msec / finish by ESC key
			break
	cv2.destroyAllWindows()
	return detect_rect, meta.img

大事なところだけ抜粋すると、この辺です。

param.img = np.copy(param.img_bk)

cv2.rectangleは第一引数のframeに直接書き込みます。
なので検知枠はドラックをしていると常に書き込まれ続けて、大量の四角形が画像に書かれてしまいます。
pythonの場合、primitive以外の型は、(=)オペレータで渡すと、shallowコピーになるので、np.copyでリフレッシュが必要。
(もっといいやり方あったら是非教えてください)

おわりに

ふぅ、終わましたね。

私の場合、昔ロードバイクのタイヤだけ盗まれるという珍事件に巻き込まれたこともあり、
2年前はロードバイクに検知枠をつけて異常があった時にメールが届くようにしていました。(写真付き)

使い方は様々でよいかと思います。(悪用はダメ)
最近だったらSlack経由で通知するとか。

ソースコードgithubにおいてあるのでこちらからどうぞ。
github.com

【AWS】http サーバーを立てて、Webページを公開する

前回に引き続き、今回はhttpサーバーを立てて、適当なWebページを公開

motojapan.hateblo.jp

apache httpd サーバーインストール

$ sudo yum update
$ sudo yum install httpd
$ sudo service httpd start

index.html作成、配置

$ sudo emacs /var/www/html/index.html
<html>
<head>
<title>AWS http service!</title>
</head>
<body>
<h1>Hello, EC2</h1>
Hello EC2!
</body>
</html>

セキュリティグループ設定

前回同様、セキュリティグループ設定を行う
今回はポート番号:80

確認

クライアントPCのブラウザから次を確認

http://<パブリック IP>

するとこんな感じの画面で確認できました
f:id:motojapan:20170505181545p:plain

OK