MotoJapan's Tech-Memo

技術めも

【kaggle②】初心者がタイタニック号の生存予測モデル(Titanic: Machine Learning from Disaster)をやってみる(データ分析、整形、欠損データ補完)

これの続きです。

【kaggle①】初心者が Titanic: Machine Learning from Disasterをやってみる(Titanic概要 ~ データ確認) - MotoJapan's Tech-Memo


(ここからはpythonのpandasが便利すぎるというポジティブキャンペーンになります)
ちなみに「ここでわかってきたことは」を読めば記事の重要ポイントは大体把握できます。 

2.4.データ整形

前回の記事の続きですが、データを整形します。
男女(Sex)情報は文字列であるため、バイナリ(0/1)変換。

#男女を01で表現
df_train = df_train.replace("male",0).replace("female",1)

2.4.データ傾向確認

pandasのDataFrameで全体的なデータの傾向を可視化(便利!)

#各特徴量の大枠の分布を表示
df_train.describe()
#欠損データの集計を表示
df_train.isnull().sum()

f:id:motojapan:20170613223519p:plain
f:id:motojapan:20170613223627p:plain

ここでわかってきたことは

  • [Age]が一部欠損
  • [Cabin]は2/3以上欠損

今回は、欠損が多き過ぎるものは、学習させる特徴量としては一旦除外しておきます。  
あと、[PassengerId]は便宜上ついているだけの通し番号ですのでこれも除外します。

次で簡単に除外できます。(ありがとう、pandas様)

#学習に不要なデータを排除
df_train_dropna = df_train.dropna()
df_train_dropna = df_train_dropna.drop('Cabin', axis = 1)
df_train_dropna = df_train_dropna.drop('PassengerId', axis = 1)

2.5.データ相関分析

つぎにもう少し踏み込んで分析したいと思います。が、pandasでは既に簡単な分析ができる関数が準備されています(ありがとう!)

#一次相関のconfusion matrixを表示
df_train_dropna.corr()

f:id:motojapan:20170613225537p:plain

ここでわかってきたことは

  • [Sex][Age]は[Servived]に対して相関が高い
  • 逆に[Fare][Pclass]は相関が低い

乗船料金や旅客席クラスの高い人が優先的に救助されたというわけではなさそうですね。
年齢は一部欠損していますが、相関が高いことからここをいかにうまく補完できるかが鍵のようです。

2.6.欠損データ補完

[Age]は部分欠損を起こしてるため、補完処理を実施
2パターンを検証

  • 2.6.0 [Age]平均値で補完 #今回は割愛
  • 2.6.1 [Pclass]毎の[Age]平均値で補完 ([Age_1])
  • 2.6.2 [Name]の敬称毎の[Age]平均値で補完 ([Age_2])

2.6.0の[Age]平均値で補完は、初期検討ではいいですが、割と乱暴な手法なので今回は割愛させて頂きます。

2.6.1.[Pclass]毎の[Age]平均値で補完 [Age_1]

べたべたなやり方で、confusion matrixの[Age]と最も相関が高い[Pclass]で補完する方法です。

ap = pd.concat([df_train['Age'],df_train['Pclass']],axis=1)
ap = ap.dropna()
ap.plot(x='Age',y='Pclass',kind='scatter')
plt.xlabel('Age')
plt.ylabel('Pclass')
plt.show()

f:id:motojapan:20170617173754p:plain
ヒストグラム版はこんな感じ
f:id:motojapan:20170617174650p:plain

if Pclass == 1, Average Age == 38.2
if Pclass == 2, Average Age == 29.9
if Pclass == 3, Average Age == 25.1

ここでわかってきたことは

  • [Pclass]客室階級は高い程、平均年齢が高い傾向は見れる
  • 一方で、子供(例えば12歳以下)等の考慮ができない補完方法
    • 子供はどの[Pclass]にも存在するし、そもそも子供に妥当な年齢を与えられない

2.6.2 [Name]の敬称毎の[Age]平均値で補完 [Age_2]

結果を先に書くと、[Age_1]に比べて、精度は2%程向上しました。(記憶が正しければ)
※1のアプローチを参考にさせて頂きました。
これは、[Name]という一見扱いずらそうな文字列を特徴量にします。

手法

  • 名前から敬称[Honorific]を探す
  • 下記でラベリングする
    • mr->0, mrs->1, miss->2, master->3, ms.->4, dr.->5, else->6
  • [Honorific]毎の平均年齢を確認
  • 傾向が高いもので年齢補完する
    • elseとかは全体平均などで補う

ソースコード等は割愛しまする (GitHubにコードあり)
f:id:motojapan:20170617182137p:plain


ここでわかってきたことは

  • [Age_1]に比べて、敬称毎の年齢はばらつきが少なく、優秀そう(優秀でした)
  • ms.->4, dr.->5, else->6は数も少ないため、全体平均で代替でよさそう

以上、今回はここまでです。
次は特徴量生成と可視化。

【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