アプリとサービスのすすめ

アプリやIT系のサービスを中心に書いていきます。たまに副業やビジネス関係の情報なども気ままにつづります

2017,18年で便利だったpython, sql,linuxとかのコマンド・コードまとめ

2017〜2018年で頻繁に使ったコードのまとめで、個人的備忘録。
これからも追記はしてきます。

pandas

Q1.pandasで特定の列だけ演算をする場合

下のようなDataFrame(df)で、特定の列('c')を-1する場合。

# df
   a, b, c, d, e
0  1  2  3  2  2
1  2  2  3  2  1
2  3  3  3  4  4
3  2  4  5  3  1


A.apply()を使う

new_df['c'] = df['c'].apply(lambda x:x-1)


Q2.pandasのdataframeをリストの中に辞書を挿入した形式にする方法

>>> import pandas as pd
>>> df = pd.DataFrame({"A":[1,2,3], "B":[4,5,6], "C":[7,8,9]})
>>> df
   A  B  C
0  1  4  7
1  2  5  8
2  3  6  9

>>> tmp = df.to_dict(orient="index")
>>> [tmp[i] for i in df.index]
[{'B': 4, 'A': 1, 'C': 7}, {'B': 5, 'A': 2, 'C': 8}, {'B': 6, 'A': 3, 'C': 9}]   



# 例 factrization machines
[ {'user_id': '23', 'movie_id': '12', 'occupation':'11', 'zip_code': '11'}, 
 {'user_id': '34', 'movie_id': '22', 'occupation':'22', 'zip_code': '33'}, 
 {'user_id': '445', 'movie_id': '33', 'occupation':'44', 'zip_code': '22'}, ]


Q3.列の内容が同じDataFrame同士を連結する方法

A.pandas.merge()を使う

pandas.DataFrameを結合するmerge, join





Q4.pandasでDataFrameの特定の列の値の重複数を入れた新たな列を作成する方法

# df
    x    y
# 0  AA  100
# 1  AA  200  
# 2  AA  200  
# 3  BB  100
# 4  BB  200
# 5  CC  300


そこからdfの列xの該当する値に重複数を入れた新たな重複数の列(z)を含んだ行列(dff)を作る。

# dff
    x     y       z
0  AA  100  3
1  AA  200  3
2  AA  200  3
3  BB  100  2
4  BB  200  2 
5  CC  300  1

A.groupby().transform('count')

重複数のカウントが簡単にできる

import pandas as pd
df = pd.DataFrame({
    'x':['AA','AA','AA','BB','BB','CC'],
    'y':[100,200,200,100,200,300]})

df['z'] = df.groupby('x').transform('count')
print(df)
     x    y      z
0  AA  100  3
1  AA  200  3
2  AA  200  3
3  BB  100  2
4  BB  200  2
5  CC  300  1


Q5.行数の合わない2つのlistをpandasのDataFrameで表にする方法

listA = ['A', 'B', 'C', 'D', 'E', 'F']
listB = ['a', 'b', 'c', 'd']

pd.DataFrame([listA, listB], columns=list(‘ABCDEF'))





Q6.pandasの欠損値以外を1に変える方法

pandasのリスト(図)を作成し、欠損値以外は文字が表示されている。
欠損値を0に変えたあと、文字を全て1に変える処理をしたい場合。

A.notnull()で欠損値以外を検索し、.fillna()で欠損値を指定した値で埋め込む

下記の例は欠損値以外を1に置き換えた後、欠損値を0に置き換えてる

import pandas as pd
from numpy import nan

df = pd.DataFrame([['a', nan], [nan, 'b']])
df[df.notnull()] = 1
df.fillna(0, inplace=True)

Q7.pandasの特定の列を最後に移動させる方法

pandasで表を作ったとき、特定の列の値を最後に移動させる方法。

例:下の図だとt列を最後に移動させる


A.除外してあとで付け加える

参考=>pandasのデータフレームの列を入れ替える

col = df.columns.tolist() # 列名のリスト
col.remove('t') # 't'を削除 ※列名は重複していないものとする
col.append('t') # 末尾に`t`を追加
df = df.ix[:,col]
#df = df[col] でもよい

Gitコマンド


Q8.githubにcommitする方法

# 既存レポジトリ対象のcommand 
git clone URL 
cd jupyter_note(cloneしたフォルダ) 
mkdir detecting_trend_words 
cd detecting_trend_words #ファイル移動 
cp change_detection.ipynb detecting_trend_words/change_detection.ipynb 
git add . 
git commit -m "コメント” 
git push origin master
# 特定のファイルの削除
git reset 
git rm 'ファイル名' 
git commit -m "コメント” 
git push origin master

SQL


Q9.Bigquery(SQL)で5年間分の検索単語数を1日おきに抽出する方法

google bigquery で検索ワードを以下の条件でSQLで出す。


・指定期間は5年間
・1時間ごとの単語数をcountする
・1列で1時間分の「単語数、その時、ワード」の3行分のマトリックスを作成

LSTM用には時系列データとしてshape=(365×5×24,)(例:batch,time_window,dim=365×5,3,21)
の形で抽出するのが目的。


ちなみに、5年分の単語抽出のSQLは以下の通り。

# original_keywords:単語
# time:検索ワードが入力された時間

SELECT original_keywords, time AS t
from TABLE_DATE_RANGE(search_logs.search_log_, TIMESTAMP ('2012-6-10'), TIMESTAMP ('2017-6-10') )
WHERE original_keywords IS NOT NULL


A.集計関数を利用すれば可能


timeがTIMESTAMP型ならこんな感じ。

#standardSQL

SELECT
  original_keywords,
  format_time("%Y%m%d%H", time) AS hours,
  COUNT(*) AS cnt   # ワード、時間、ワード数
fROM  search_logs.search_log_*
WHERE original_keywords IS NOT NULL
AND _TABLE_SUFFIX BETWEEN '20120610' AND20170610' # 5年間でワードがある場合の条件指定
GROUP BY original_keywords, format_timestamp("%Y%m%d%H", time) # ワードと時間で一まとめにする


BIG Queryで使うSQLの例

WHERE句の条件指定では「WHERE al._TABLE_SUFFIX BETWEEN」
を使う。

#standardSQL
select al.id, ial.id, al.id2
al.id3
FROM `reco.log_*` al
JOIN `action.log_*` ial ON al.id=ial.id
WHERE al._TABLE_SUFFIX BETWEEN '20180610' AND '20180611'
group by al.id, ial.id, al.id2, al.id3

Tips


Q10.if __name__ == ‘__main__':の意味


python コード.py」などと直接実行された場合、if __name__ == '__main__':以下が実行される。




Q11.for文でリスト内の要素を辞書に格納する方法

下の用のfor文を使ってdicに自動で格納したい場合。

cat=[['b', 'a1'],
 ['b', 'a2'],
 ['c', 'b1'],
 ['c', 'b2'],
 ['c', 'b3'],
 ['d', 'c1'],
 ['d', 'c2'],
 ['d', 'c3']]

dic={'b': (['a1', 'a2']), 'c': ['b1','b2','b3'], ‘d':['c1','c2','c3']}




A.setdefaultを使う

cat = [['b', 'a1'],
       ['b', 'a2'],
       ['c', 'b1'],
       ['c', 'b2'],
       ['c', 'b3'],
       ['d', 'c1'],
       ['d', 'c2'],
       ['d', 'c3']]

dic = {}
for k,v in cat:
    dic.setdefault(k, []).append(v)

print(dic)  # => {'b': ['a1', 'a2'], 'c': ['b1', 'b2', 'b3'], 'd': ['c1', 'c2', 'c3']}


Linux


Q12.末尾の名前が0~Nまでのディレクトリを作成する

$ for i in $(seq 0 199); do mkdir "label_$i"; done


$(seq 0 199)で0~199までの数字を返し、末尾がその数字のディレクトを作成してる



Q13.ファイルの拡張子の最後を.jpgに変換する

$ for i in $(ls); do mv $i "$i.jpg"; done

例:dog.jpeg.jmg →dog.jpg

Q14.別フォルダ(fAとfB)同士の同名ファイルを削除する

# fB内で実行
for i in *.gz; do rm "/*/*/downloads/*/fA/$i"; done

tensorboard





Tensorboardの表示方法

qiita.com

$ tensorboard --logdir=[ファイルがあるdir名]
ex:
$ tensorboard --gdir=/Users/〜/Downloads
# 「http://localhost:6006」にaccess

プログラマーの目の酷使・疲れを回復させるオススメの11の方法【視力ケア】

プログラミングで仕事するようになってから、PCや勉強などで目をより使うことになった。

過去の悪き呪いもあり、目が悪くなるのはやばいと思いかなりケアに徹してきた。その結果、個人的に目のケアで普段やってることをまとめておこうと思う。目を悪くしないと意識するだけでもかなり違う。

目次
1.目が悪くなる条件、知識
・環境作り
2.証明とかで日光で明るい環境での作業や読書
3.パソコン画面のズームアップ
4.ブルーライトカットのPCメガネを使う
・作業中のケア
5.まばたきを多めにする
6.直感で目が痛いと感じたら即休む

・アフターケア
7.目をほぐすアイマッサージャーを使う
8.休む日、目を使わない日を作る
9.視力回復効果を持つブルーベリーをとる
10.睡眠をタップリとる、遠慮なく昼寝する
11.マッサージで教えてもらった目のツボを押して筋肉をほぐす
12.+α 風の自己予防法

f:id:trafalbad:20170123070523j:plain

1.目が悪くなる条件、知識

これは目医者に聞いたり、直感で感じたこと。


度数の強い(遠くがよく見える)メガネ・コンタクトで、至近距離のパソコン・物体を直視し続けると、一気に近視が進む。
→通常より弱いか、丁度よいくらいの度数のメガネ・コンタクトがベスト

基準は30分やったら10分休める
→ 目の筋肉は使いすぎると硬直するので、遠くの緑を見るとか、マッサージをする

頑張って見ようとすると眼精疲労が進む
→適正度数なら頑張ってみようとしないので、近視は進まない。物を見ていて変に力んだり、違和感を覚えたら適正度数ではないってこと

目の疲れは直感を信じる
→ 脳信号からのサインを感じ取る。ワンピースの見聞色的な、何となくの延長上の感覚


パソコンをつける前の環境作り


2.証明とかで日光で明るい環境での作業や読書

パソコンをいじるなら、部屋を明るくする環境は必須。漫画とかの読書にも言える。

例えば、曇りの日だと部屋はかなり暗く、画面を見るのに目の負担がでかいから、証明はつけたほうがいい。

パソコンは日の当たるところ・直射日光は避けた方がいいだろう。直射日光は画面の光を乱反射させて、よけいパソコン画面を見辛くしてしまう。

パソコンを置くなら、窓辺近くの日陰とかがベストコンディション。




漫画とかの読書の類は日光を大いに活用した方が個人的にベスト。日光を明かり代わりにして読むとだいぶ目の負担が楽になる。




3.パソコン画面のズームアップ

パソコンのほとんどは文字媒体。特に仕事では文章だらけだから、そうした時は画面を必ずズームアップしよう。

逆にアニメや動画など特別はっきり見えなくていいものは、度が弱いメガネで見るとかなり楽。





4.ブルーライトカットのPCメガネを使う

ブルーライトはPCやスマホから発せられる目に害のある光。PCメガネはそれをカットすることで有名。

f:id:trafalbad:20170123070607j:plain

ネット上では賛否両論だが、実際に使っている経験から言わせてもらうと、かなり効果はある。

買う前はPC作業15分で目が痛くなってきたが、PCメガネでは1時間は持つ。定期的に休憩を入れれば、プログラマーでも休憩を入れれば、一日中PC・スマホの相手ができるほどの優れもの。

値段はフレーム込みで5000円〜1万円程度。実際にメガネに行って、度とサイズが合うものを購入した方がいい。PCメガネ有る無しでは目の普段が驚くほど減る。




パソコン操作中の視力ケア



5.まばたきを1秒に一回する

人間は普通は2秒に一回はまばたきをする。しかしパソコン画面とかを見てるときは、0.5秒〜1秒単位でまばたきを意識的にしてみよう。

主な理由は、目の渇きを抑えることと、パソコンを凝視することを抑える効果がある。

まばたきをしないと、パソコン画面をじっとにらみつけていることが多くなってしまい、結果的に目が疲れやすくなる。

まばたきを意識的にすることで目の負担は大分軽くなる。




6.直感で目が痛いと感じたら即休む

パソコンやスマホを30分いじったら10分休憩するのがベストらしい。
メガネ屋の定員曰く、休憩時間には
・目を閉じる

・遠くを見る

・昼寝をする
とかあるけど、個人的に一番肌感覚であってるものをやるのが一番いい。

目が痛くなったり、違和感を感じたら脳からの危険信号だと思って早めに休憩することをオススメする。
自分は、直感を信じて対処して、特に昼寝を重視してる。


アフターケア



7.目をほぐすアイマッサージャーを使う

アイマッサージャーはamazon で買った。自宅でできるし、ホットマスクと同じ効果がある。しかも充電できるから何回でも使えてコスパが良すぎる。


使ってみたら、1週間もかからず。目のシボシボが取れた。これはもう体験してとしか言うしかないが、効果はかなりあって今では手放せないアイテムになってる。



ホットマスクは面倒なので、充電で済むマシーン系の方が自分は好きなのでこれを選んだ。




8.休む日、目を使わない日を作る

普段はスマホだテレビだ漫画だと、視力を使いまくってしまう。特に陰キャで外に出ないならなおさらだろう。

だから普段から目を酷使しないように意識するのは当たり前として、それでもさらに目を酷使しない日を作ることが大切。

そもそも家にこもってると目を使ってしまうから、温泉でも出かけるとか、外出する日は、そういうきっかけには持ってこいだろう。




9.視力回復効果を持つブルーベリーをとる

視力回復効果を持つ食品はいろいろあるが、誰でも聞いたことあるのは、ブルーベリーなはず。

ブルーベリーに含まれるアントシアニンという成分が、目の網膜のロドブシンの再合成を活発化させる。つまりブルーベリーには目の疲れを回復させる、体の機能を促進させる効果がある。

ジャムやヨーグルトでとると、パンと一緒に食べられるし、ヨーグルトだとデザートにもオススメ。

もっと手軽にサプリメントとして買えるようになっている。自分は普段DHCのブルーベリーエキスのサプリメントを一日2錠飲んでる。

飲まない前と比べると目の疲れや疲労はたしかに取れた。今ならドラッグストアやamazon でも買えるので、自分は60日分はストックしておいて、朝飲むようにしてる。

f:id:trafalbad:20170123070719j:plain:w250
AmazonDHC ブルーベリーエキス




10.睡眠をタップリとる、遠慮なく昼寝する

人間は体の性質上、睡眠が一番健康上効果がある。惰眠とか規則正しい生活しつつも適度に昼寝してると血行も肌のツヤも良くなる。

おまけに、活動・免疫能力、回復能力も上がるので、眼精疲労とかの目の疲れには欠かせない。

最近というか前から自分は、効率重視のため眠くなったら寝るようにしてる。そうすると能率がかなり上がる。

あとは外に出て考えたりとか、目を使わずに仕事をする方法を考えたりと、「目を使う時間&使わない時間」のバランス考えるようになった。



11.マッサージで教えてもらった目のツボを押して筋肉をほぐす

マッサージ師に教えてもらった目のツボがある。言葉では表しにくいので図で示してみた。

目の上の骨盤

だいたい眉毛のあたり。眼球穴が空いてる周りの骨の部分で、眉毛の上あたり

こめかみ

だいたい図の位置。骨のくぼみがあるからそこがツボになってる。

頭の後ろ側(背骨の延長線上にある)


だいたい図のあたり。水平線上に耳があるところかつ、背骨の延長線上にある。


ブログだととても伝わりにくいので、詳しくはマッサージ師に教えてもらうのが一番。

眼精疲労は筋肉が凝り固まって生じるから、マッサージでほぐしてやるとかなり効果がある。

自分でやっても、マッサージ師に揉んでもらう分の何割かは自分でできるのだから、知っといて損はない技術。




12.+α 風の自己予防法

医者に聞いたセルフ式風邪予防のメモ。



・日頃のからうがい手洗いをする。
・あとよく寝る。


・風邪はビタミンcを含む食品とること。
・極力、人混みは必要ないときは避ける。

・風邪の時期はマスクをする。

・熱を引いたときは
→水を多く飲むとか、暑いなら服脱いで熱を逃すとかなどが対策っぽい。





今回はパソコン作業やスマホをいじるのが多くなってきたので、目のケア方法を改めてまとめてみた。
どれも実践すると効果的なものばかりなのでオススメ。

GKE上のkubernetesで機械学習運用環境(MLops)を作成手順・コマンド・知識メモ

機械学習運用環境(MLops)の一部を話題のkubernetes(k8s)で作成してみた。GCPのGKE上でk8sを利用できるので、今回はGKE上で途中まで構築。

今回参考にするMLopsは下の図。どうもこのサイトによるとメルカリでマイクロサービスとして運用されてるらしい  
f:id:trafalbad:20181207154107p:plain




このMLopsのk8sの部分だけ(下図)作成してみた。
f:id:trafalbad:20181207154123p:plain




・作成手順
step1. persistentVolumeを使ってAWSのS3とマウントしたPodをLoad balancerを使って、負荷分散を行う。http://[EXTERNAL-IP]にアクセスして、flaskで’hello, world’を表示させる。


step2.redisインスタンスと結合させたflaskアプリケーション用Podをgoogleチュートリアルに従い、作成


step3.上の図のPersistentVolumeと繋がってるtensorflowの部分の「worker用Pod」を作成。今回は簡易版でworker用のpodをceleryで作成



途中からAmazon sageMakerの方がMLopsを簡単に作れるという噂で、k8sでの構築はstep3で中断。けど、せっかくなのでアウトプットしたログを記事にまとめておこうと思う。


目次
・MLopsの構築

kubernetes関係知識まとめ

・コマンド関連まとめ



MLopsの構築

step1. flask用Podを作成して、で’hello world'を表示

まずflaskアプリケーションで’hello world’を表示させてみる。flaskはDjangoより便利ってことで使った。


・用意したファイル
app.yaml 、main.py(flaskアプリ本体) 、requirements.txt(インストール用のライブラリ)、Dockerfile、flask_pod.yaml(Pod作成用マニフェストファイル)、sample_lb.yaml(load balancer用ファイル)


プロジェクト ID:sturdy-willow-167902


1. Dockerイメージpush & 確認

まずGCPのホーム画面からcloud shell(コンソール)を開いて(右上のとこにある)、kubernetes Engineを選択。 

cloud shell(コンソール)の起動方法は、GCPメイン画面から[プロジェクト]をクリック。

上段の右から6番目のアイコン「cloud shellにアクセス」をクリックし、「起動」で開ける。
f:id:trafalbad:20181207154314p:plain





以下コマンドはコンソール上で実行。

# Dockerイメージ作成して、Container Repositoriesにpush
$ export PROJECT_ID="$(gcloud config get-value project -q)”
$ docker build -t gcr.io/${PROJECT_ID}/visit-counter:v1 .
$ gcloud docker -- push gcr.io/${PROJECT_ID}/visit-counter:v1

#コンソール右上の [ウェブでプレビュー]で ’hello world’ が表示されるか確認
$ docker run --rm -p 8080:8080 gcr.io/${PROJECT_ID}/visit-counter:v1

→ Container Repositoriesに「visit-counter」ができる



2.Podの作成とserviceの定義


ここは単純にkubektlコマンドでPodの作成とサービスの定義(負荷分散)だけ。

そのためにGUIでもCUIでもいいので、kubernetes Engineのクラスタを作成しておく。クラスタを作成したら、コンソールで以下コマンド実行。

# クラスタ操作権限取得
$ gcloud container clusters get-credentials <クラスタ名> --zone=asia-northeast1-a

# 一応、VMインスタンス確認
$ gcloud compute instances list


***注意、マニフェストファイル(flask_pod.yaml)ではイメージ名のプロジェクトIDを展開する参考サイト

asia.gcr.io/$PROJECT_ID/wdpress/hello-world
# $PROJECT_ID は展開する必要があ流ので、上のイメージ名を下のように修正
asia.gcr.io/sturdy-willow-167902/wdpress-123456/hello-world

# Podの作成
$ kubectl apply -f flask_pod.yaml (pod確認  $ kubectl get pod)
# Serviceの定義
$ kubectl apply -f sample_lb.yaml
# EXTERNAL-IP(IPアドレス)の確認
$ kubectl get service

→ http://[EXTERNAL-IP] にブラウザからアクセスして、’hello world’が表示される。



step2.redisインスタンスと結合したflask用Pod作成

redisインスタンスと結合したflaskアプリケーションのPodを作成。
作成するMLopsではこの部分

これはGoogleのチュートリアルがあるが、素でやってもすんなり行かないので、ちょっと修正した。あとはチュートリアル通りに実行。 


チュートリアルで修正した点
インスタンスクラスタのリージョンを同じにする => region=us-central1

・RESERVED_IP_RANGE の部分を、実際のインスタンスの予約済み IP 範囲に置き換える(参考サイト

マニフェストファイル(YAMファイル)のイメージ名のプロジェクトIDを展開



この3点だけ修正。あとはチュートリアル通りに実行。

プロジェクトID:sturdy-willow-167902



1.redisインスタンス作成

# インスタンス作成
$ gcloud redis instances create myinstance --size=2 --region=us-central1

# インスタンス情報確認
$ gcloud redis instances describe myinstance --region=us-central1

>> createTime: '2019-01-06T13:19:26.236748318Z'
currentLocationId: us-central1-c
host: 10.0.0.3
locationId: us-central1-c
memorySizeGb: 2
name: projects/sturdy-willow-167902/locations/us-central1/instances/myinstance
port: 6379
redisVersion: REDIS_3_2
reservedIpRange: 10.0.0.0/29
state: READY
tier: BASIC

# redisインスタンスを削除するとき
$ gcloud redis instances delete myinstance ―region=us-central1




2.CUIクラスタ作成

#  redisインスタンスのリージョンに設定
$ gcloud config set core/project sturdy-willow-167902 && gcloud config set compute/zone us-central1-c
# クラスタ作成
$ gcloud container clusters create visitcount-cluster --num-nodes=3 --enable-ip-alias
# クラスタの権限get
$ gcloud container clusters get-credentials visitcount-cluster --zone us-central1-c --project sturdy-willow-167902





3.RESERVED_IP_RANGE の部分を、実際のインスタンスの予約済み IP 範囲に置き換え

$ git clone https://github.com/bowei/k8s-custom-iptables.git && cd k8s-custom-iptables/

$ TARGETS="10.0.0.0/29 192.168.0.0/16" ./install.sh && cd ..

4.Dockerイメージ作成とpush

$ export PROJECT_ID="$(gcloud config get-value project -q)"
$ docker build -t gcr.io/${PROJECT_ID}/visit-counter:v1 .
$ gcloud docker -- push gcr.io/${PROJECT_ID}/visit-counter:v1

5. configmapの作成

$ kubectl create configmap redishost --from-literal=REDISHOST=10.0.0.3
# configmap確認
$ kubectl get configmaps redishost -o yaml


6.pod作成, service定義、アクセス

$ kubectl apply -f visit-counter.yaml
$ kubectl get service visit-counter

# アクセスして動作確認
$ curl http://[EXTERNAL-IP]



Step3.celeryでworker用のPodを作成

ここでは下図のworkerのPodを作成。
f:id:trafalbad:20181207154249p:plain

今回はPVと接続せず、簡単にworker用のcelery のpodを作成してみた。
用意したfileは「Dockerfile, run.sh, celery_conf.py, celery_worker.yaml」。


そのためには別途dockerイメージを作成する必要がある。

$export PROJECT_ID="$(gcloud config get-value project -q)"
$ docker build -t gcr.io/${PROJECT_ID}/visit-counter:worker .
$gcloud docker -- push gcr.io/${PROJECT_ID}/visit-counter:worker

各ファイルのコードは以下の通り。
 
・Dockerfile

FROM library/celery
ADD celery_conf.py /app/celery_conf.py
ADD run.sh /usr/local/bin/run.sh

ENV C_FORCE_ROOT 1
CMD ["/bin/bash", "/usr/local/bin/run.sh"]      # run.shのコマンド実行
CMD exec /bin/bash -c "trap : TERM INT; sleep infinity & wait"  # 永続的に起動される


・run.sh

#!/bin/bash
# celery worker起動コマンド
/usr/local/bin/celery -A celery_conf worker -l info


celery_conf.py

from celery import Celery
from flask import Flask

def make_celery(app):
    celery = Celery(app.import_name, backend=app.config['CELERY_RESULT_BACKEND'],
                    broker=app.config['CELERY_BROKER_URL'])
    celery.conf.update(app.config)
    return celery

flask_app = Flask(__name__)
flask_app.config.update(CELERY_BROKER_URL='redis://10.0.0.3:6379', CELERY_RESULT_BACKEND='redis://10.0.0.3:6379')
celery = make_celery(flask_app)

@celery.task()
def add(a, b):
    return a + b


celery_worker.yaml
参考サイトによるとspec.template.metadata.labelsをvisit-counterにすることでflask側のPodと接続させるらいし

apiVersion: v1
kind: ReplicationController
metadata:
  labels:
    component: celery
  name: celery-controllers
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: visit-counter
        component: visit-counter
    spec:
      containers:
      - image: "gcr.io/sturdy-willow-167902/visit-counter:worker"
        name: celery 
        env:
        - name: REDISHOST
          valueFrom:
            configMapKeyRef:
              name: redishost
              key: REDISHOST
        ports:
        - containerPort: 5672
        resources:
          limits:
            cpu: 100m


最終的に作ったPodが

$ kubectl apply -f visit-counter.yaml && kubectl get pod 

で確認したとき、「Running」になっているので、worker用podの完成。


あとは、flask側のpodで

$ from celery_conf import add
$ r=add.delay(1,3)
$ r.result

こんな感じでworker側に投げた値を受け取れるようにする必要がある。
これはserviceを定義するとか、マニフェストファイルを定義するとか、いろいろありそうなので、またの機会にした。

というのも、amazon sagemakerというのが便利との噂を聞いて、k8sからamazon sagemakerに切り替えてMLopsを構築したから。
なので、k8sでの作業は中断し、amazon sagemakerでmlopsを構築した。


ちなみに、Amazon sageMakerでのMLops構築手順の関係記事は別記事にまとめた。

trafalbad.hatenadiary.jp




kubernetes関係知識まとめ

PersistentVolumeについて

k8sのpersistentvolume (以下PV)はPodとは独立した外部ストレージ。

通常のk8sのPVはクラスタの外部の別の場所に作成するが、GCP上ではPVは代替ストレージの「GCEPesistentDisk」がすでに存在してる。PVCで用途に応じて容量・タイプなどを適宜追加・拡張する。




・PVのアクセスモード

  • > ReadWriteOnce: 1つのノードからR/Wでマウントできる
  • > ReadOnlyMany: 複数のノードからReadOnlyでマウントできる
  • > ReadWriteMany: 複数のノードからR/Wでマウントできる


・PVCの作成時、PVのストレージタイプの「標準」か「SSD」をStorageClassで指定

SSD」はマニフェストファイルではstorageClassName: ssdで指定。デフォルトは「標準」



app.yamlPython ランタイムについて

=>アプリケーションのコードと依存関係をインストールして実行するソフトウェアスタック。
標準ランタイムは、app.yaml で以下のように宣言(Docker でビルド)
runtime: python
env: flex


=>アプリケーションの app.yaml で使用するバージョン(Python2 or 3)をruntime_config に指定

runtime: python
env: flex
runtime_config:
    python_version: 3

=>ライブラリのインストール
同じディレクトリ内の requirements.txt ファイルを検索し、pipでrequirements.txt 内のライブラリを全てインストールする。




・PV<=> PVC <=> Pod間の結合関係
PVとPVCとPod間のどこが結合してるか?の関係について、


PV:metadata.name: nfs001, storageClassNameフィールドのslow

PVC:metadata.name: nfs-claim1, storageClassNameフィールドのslow

Pod:metadata.name: my-nginx-pod, persistentVolumeClaim.claimNameフィールドのnfs-claim1

つまりPVとPod、PVCとPodの結合関係は
PVとPod: slow(PV) <=> slow(Pod)
PVCとPod:nfs-claim1(PVC)<=> nfs-claim1(Pod)


DockerfileのENVの環境変数について
ENV命令は、環境変数と値のセット。値はDockerfile から派生する全てのコマンド環境で利用でき、 上書き、変数扱いも可能。

ex: 環境変数に値をセットする場合、

ENV MES Good-bye 

は MES=Good-bye
を意味する。 $MESで環境変数をあらわせる。




・gunicorn
例えば myapp.py というファイル内に app という名前でFlaskアプリがある場合、以下のように gunicorn で起動。

$ gunicorn myapp:app

デフォルトのPORTとHOSTは127.0.0.1と8000



gunicornのportについて
This will start one process running one thread listening on 127.0.0.1:8000.

I wish to run gunicorn on port 80.
→ gunicorn -b 0.0.0.0:80 backend:app




・flask

if __name__ == ‘__main__':
   app.run(debug=False, host='0.0.0.0', port=80)

host='0.0.0.0' の指定が大事。これがなければ、外部からアクセスすることができない。参考サイト



・os.environ.get
環境変数 key が存在すればその値を返し、存在しなければ default を返す。
key、default、および返り値は文字列。
もし環境変数がなくともKeyErrorがraiseされず、第二引数(キーワード引数)のdefaultの値が返る。(指定しなければNoneが返る)



・PORT、HOST
REDISHOST→ネットワークに接続されたコンピュータのこと(redisインスタンス自体)
REDISPORT→redisインスタンスに接続するドア(番号)




・configMap
# マニフェストファイル内での部分

env:
     - name: REDISHOST
        valueFrom:
          configMapKeyRef:
            name: redishost
            key: REDISHOST

keyのREDISHOSTが環境変数

# configmap作成コマンド
$ kubectl create configmap <map-name> <data-source>

# 例
$ kubectl create configmap redishost ―from-literal=REDISHOST=10.0.0.3

redishostがmap-nameで、それ以下がdata-source

# configmap削除
$ kubectl delete configmap <configmap名>

上のコマンドで環境変数のREDISHOSTが10.0.0.3に変更された。

REDISHOST=redisインスタンス自体を環境変数に代入。Dockerfileのデフォルトのクラスタ環境変数をredisインスタンスのPORT(自体)で上書き。




コマンド関連まとめ



(ターミナル上での)k8sクラスタ作成コマンド

$ gcloud container clusters create <クラスタ名> --machine-type=n1-standard-1 ―num-nodes=3

ex: gcloud container clusters create mlops --machine-type=n1-standard-1 --num-nodes=3



クラスタ環境設定の権限取得(cloud shell上で作成したクラスタを操作できる)

$ gcloud container clusters get-credentials <名前> ―zone=<ゾーン>

ex:$ gcloud container clusters get-credentials mlopss --zone=asia-northeast1-a



kubectlコマンドでクラスタ内のノードとpod関係の操作

$ kubectl get nodes (ノードの確認)
$ kubectl get pod  (Podの確認)
# Serviceの情報を確認(クラスタ内で通信可能なIPアドレス)
$ kubectl get services sample-clusterip

# Pod内へのログイン
$ kubectl exec -it sample-pod /bin/bash
→sample-podはmetadata.nameフィールド名



・dockerコマンド関係

$ docker rmi <イメージID>  -f (dockerイメージの削除) 
$ docker images (dockerイメージ確認)
$ docker ps(コンテナが正常に起動しているか確認)
$ docker images -aq | xargs docker rmi(dockerイメージ全部削除)

# コンテナ停止、削除
$ docker stop [コンテナ名]  (全コンテナ停止 docker stop $(docker ps -q))
$ docker rm [コンテ名] (全コンテナ削除 docker rm $(docker ps -q -a))

アソシエーション分析を使ったレコメンドアルゴリズム作成-機械学習・python

レコメンドは普通、評価値(レーティング)を使った手法がメインだが、今回は都合でレーティングがない環境下で、レコメンドアルゴリズムを作らなきゃならなかった。
そこで、アソシエーション分析を使ったレコメンドアルゴリズムを作ったので、その過程をまとめてく



目次
・アソシエーション分析のレコメンドについて
・特徴量エンジニアリング
アプリオリアルゴリズムでメタラベル作成
・協調フィルダリング
・ランクネット
・レコメンド結果
・最後に
・気づいたこと、テクメモ



アソシエーション分析のレコメンドについて



主にレコメンドは評価値(レーティグ)を使う/使わないの2手法で2つに大別できる。

①レーディングを使うアルゴリズム→ユーザーの購入意図を推測するレコメンド

②レーティングを使わないアルゴリズムトランザクション履歴などの過去の購買データから、商品間の一般的な購買ルール(依存関係)を見つけ、そこからレコメンドを作成する


今回はレーティングがないので、②のタイプのレコメンドモデルを作成。
このレコメンドの対象商品は主に下のタイプ。

・人気の高いアイテム

・トレンドアイテム

・ニューリリースアイテム

・類似アイテム

・頻出アイテム

この手の手法は個人の趣向を考えない(パーソナライズしない)タイプのレコメンドに該当する。
今回のレコメンドは「類似アイテム」をレコメンドすることが目的。

対象ユーザーは、購入済みユーザーだけに類似アイテムをレコメンドし、新規ユーザーは対象外。


使うアルゴリズムは、協調フィルダリングとランクネットを使った複合アルゴリズム



具体的なやり方は4つのパラメータ(モデル、価格帯、性別、評価値)を使って、類似アイテムを取り出す。
まず協調フィルダリングで3パラメータ「モデル、価格帯、性別」を使い100万単位のアイテム候補から百~千単位に絞ったあと、ランクネットで評価値パラメータを使い、高評価順に並び替え、上位N個表示する。




手法youtubeAmazonを参考にした。

youtubeは似たような複合アルゴリズムを使っていたから。
amazonは、データ分析計の本を購入したら、「同シリーズ、同カテゴリ、ほぼ同価格帯、高評価」の商品をレコメンドしてきて、感激したので。
その流れで複合アルゴリズムで、amazonみたいな4パラメータで類似商品をレコメンドするアルゴリズムを作ることにした。


コードはgithubに上げてあります



特徴量エンジニアリング


選んだ特徴量

特徴量は一回の取引に付属するユーザーの行動・アイテムのデータを使用。
(SQLで取り出すときは商品系、会員情報、取引系のテーブルのレコードをgroup by)。


ユーザーが必ずしない行動を含んだテーブルのレコードは「LEFT OUTER JOIN」か「FULL JOIN」して、後はpandasとかで欠損値を0にして使った。

最近のレコメンドはfactorizition machinesのようにいろんな特徴量を含めれるようになってるので、特徴量はユーザーデータとアイテムデータ関係を使った。

ユーザーデータ → ユーザーの行動データ、ログイン端末(スマホとか)、ユーザーの個人情報、ログインした場所・時間とか

アイテムデータ→アイテムデータ関係で使えそうなものは全部。



分析例

タイムデータはヒートマップで分析するとログイン時間は朝方が多いっぽい

f:id:trafalbad:20181120150300p:plain


端末はスマホが圧倒的に多かった
f:id:trafalbad:20181120150315p:plain



地理データは、今回はほぼ日本ユーザーしかいないので必要なかった。(youtubeみたいな世界規模なら必要だけど)

地理データを調べてみたら、ログインした場所 (県)は「東京、大阪」の大都市が最も多かったので、そういう場合は、
SQLのCASE文で多い箇所を1、他0
みたいに、「多い場所を特別扱いする」ように加工すると使いやすいかと思う。



あと特徴量選択でやったこと

①レコメンドは「UXのデザイナーみたいに仮説立てるのが大事」みたいな記事読んで、UX関係の人に広告で重要なデータ、立てた仮説、ユーザーのニーズとかを聴いた


YouTubeの論文とかkaggleで使われてる情報を参考に特徴量選択


③特徴量選びは、下の順序でやった
1.会員、取引、商品関係のテーブル表眺めて、使えそうなものを選ぶ
2.xgboostと重回帰分析にかけて、説明変数と目的変数の関係の仮説を立てる。
3.仮説と分析結果から総括して選択


④広告に重要なデータの傾向
→ userごとによる違いが顕著なデータ(セッション回数に関係するものとか)

→Userのニーズ判断に重要な過去行動、ネガポジフィードバックの情報

→UXで立てた & 改善した仮説で重要な「クリック率、表示速度」とか



youtubeレコメンドの特徴量分析
予測対象:視聴時間
特徴量:動画の視聴時間の予測に関係する特徴量は全部使ってる

動画情報=>動画関係は必須
地理情報=> 世界的ユーザーがいるので
性別 => 男女で好みがあるから
探した情報=> 見たい動画に探す行動は大きく反映するから
年 => 年齢層で見るもの違うから





アプリオリアルゴリズムでメタラベル作成


今回はlabelに使用するレーティングのデータがないので、labelはバスケット分析の一つ「アソシエーション分析(by アプリオリアルゴリズム)」からルールを見つけ作った。


目的は自社サイト独自の購買ルールを探し、そこで見つけたルールに沿ったベストなlabelを作成するため。


ラベルの作成
「同じカテゴリの商品を買った人=似ている」
と仮定して、会員IDとカテゴリIDからトランザクション履歴を作り、アプリオリアルゴリズムで分析。

どうやら
「似た用途のカテゴリを買った人は似た用途の商品を購入する」
ルールがあるみたいなので、これを使った。

f:id:trafalbad:20181120150345p:plain


そこで、カテゴリIDは1000~9000まであるので、似た用途のカテゴリIDを91この新たなカテゴリに分類。0~91の新たなメタデータを作ってラベルにした。

ラベルは協調フィルダリングでtop5を予測する形で使う。


協調フィルダリング



普通レコメンドではfactrizavtion machines(FM)みたいなアルゴリズムを使うことが多い。しかし、FMは計算量が多いし、最近ではyoutubeのようなRNN系のneural network(NN)を使ったケースが人気らしい

f:id:trafalbad:20181120150155p:plain


(参考→youtubeの論文

今回はレーティングのデータがないからyoutubeを真似したNNをFMの代わりに使った。
特徴量はユーザーとアイテム関係のデータともに商品を推測するのに有益な構成になった。


youtubeの論文だと層が厚い方が精度がよくなるらしいので、層は2048層

def c_filtering(INPUT_DIM, weight_path):
    classes=91
    model = Sequential()
    model.add(Dense(2048, input_dim=INPUT_DIM, activation='relu'))
    model.add(Dense(1024, activation='relu'))
    model.add(Dense(512, activation='relu'))
    model.add(Dense(256, activation='relu'))
    model.add(Dense(classes, activation='softmax'))
    model.load_weights(weight_path)
    model.compile(optimizer='Adam',
                    loss='categorical_crossentropy', 
                    metrics=['accuracy'])
    return model


NNの結果

正解率は85.7%
類似したラベルのtop5の確率値出すので、間違ってもほとんど問題ないと思う



類似アイテムを取り出す順序
①まず予測したlabel(メタデータ)上位5つと同じやつに絞る

②高評価以外のモデル、価格帯、性別の3つのパラメータの抽出条件の関数作り、抽出

③最終的に絞ったアイテム数は876142件 => 185件。

④あとはランキング学習で評価パラメータを使い、高評価順に並び替える




抽出条件の関数

def kbn_type(target_df, dataset):
    kbn_colmuns=['model', 'line','series','kigata']
    kbn_type=[]
    for kbn in kbn_colmuns:
        for value in target_df[kbn]:
            if value==1:
                kbn_type.append(kbn)
    kbn_type=','.join(kbn_type)
    return dataset[dataset[kbn_type]==1]


def tanka(target_df, dataset):
    price=int(target_df['tanka'])
    yen=2000
    tankset=dataset[(dataset['tanka']>=(price-yen)) & (dataset['tanka']<=(price+yen))]
    return pd.DataFrame(tankset)


def sex(target_df, dataset):
    sex_colmuns=['man', 'women']
    sex_type=[]
    for sex in sex_colmuns:
        for value in target_df[sex]:
            if value==1:
                sex_type.append(sex)
    sex_type=','.join(sex_type)
    return dataset[dataset[sex_type]==1]




ランクネット


レーティングがないので、ランクネットの評価値は、「高評価の指標に関係しそうな特徴量」に重み付けして代用。もちろん使うときは、正規化した。


Ranknetを使う発想はyoutubeがNNの後にRankNetを使う複合アルゴリズムを使ってたのを参考にした
f:id:trafalbad:20181120150411p:plain

def Ranknet(INPUT_DIM):
    model = Sequential()
    model.add(Dense(INPUT_DIM, input_dim=INPUT_DIM, activation='relu'))
    model.add(Dropout(0.1))
    model.add(Dense(64, activation='relu'))
    model.add(Dropout(0.1))
    model.add(Dense(32, activation='relu'))
    model.add(Dense(1))
    model.compile(optimizer='Adam', loss='binary_crossentropy', metrics=['accuracy'])
    return model

Ranknetの関係でわかったこと、やったこと


YouTubeは見た感じ、次のタイプの特徴量を使ってるよう
→印象的な・視聴したビデオid
→ユーザーの母国語
→ビデオの言語
→最後に見てからの時間
→(印象的な)動画に関係する過去のユーザー情報


・CTR(クリック率)は「ユーザーとの相性や貢献度が高い/クリックしたい衝動の指標」であって、評価とはほぼ関係なく、ラベルに使わなかった


・特徴量選択は協調フィルダリングと同じ分析手法を使った。評価に関係ありそうなユーザーの行動データ、アイテムのデータを選択





評価値の作成と訓練・評価

まず特徴量の内訳


rating →着レポとかいう商品の評価らしい(全部の商品についてない)

popularity →人気度の係数

minyuka →日本未入荷の商品タグ

teikei_cd→特集ページからの流入

ps →新規か常連ユーザーか

add_favarit →お気に入りに登録

view_detail →商品詳細を見た

good →セラーへの評価

safety →安心プラス


評価値の作成は計算式を作った。重回帰分析の寄与度のスコアから、重み付けの値を決定。(普通に足し算するより、レコード数で割った方が精度がよかった)


・計算式
rank= (rating×0.01+ popularity×2+ minyuka×3 + teikei_cd×3 + ps×3 + add_favarit×4 + view_detail×3 + good×3) /8


・評価指標はNDCG
結果は100%(計算式を使ってるから当たり前)
あとはふつうにranknetでtrain/test

def ndcg(y_true, y_score, k=100):
    y_true = y_true.ravel()
    y_score = y_score.ravel()
    y_true_sorted = sorted(y_true, reverse=True)
    ideal_dcg = 0
    for i in range(k):
        ideal_dcg += (2 ** y_true_sorted[i] - 1.) / np.log2(i + 2)
    dcg = 0
    argsort_indices = np.argsort(y_score)[::-1]
    for i in range(k):
        dcg += (2 ** y_true[argsort_indices[i]] - 1.) / np.log2(i + 2)
    ndcg = dcg / ideal_dcg
    return ndcg

print(ndcg(y_test, pred))


レコメンド結果


ターゲットユーザーの商品内容は
ブランド→シャネル
カテゴリ→コインケース・小銭入れ


レコメンドした商品内容は下図
f:id:trafalbad:20181120150526p:plain



ちゃんとアソシエーション分析で見つけたルール通り、同じメタカテゴリに含まれてるカテゴリ商品がレコメンドされてる。形式的にはうまくいったと思う





最後に


今回は初めてレコメンドアルゴリズムを作成した。レーティングがない状況下で、チートなレベルだったけど、なんとか形になるものができた。

レコメンドの設計方法、特にアルゴリズムの組み合わせ、類似度抽出の方法とかは、アルゴリズム作成者の思案次第で無限に組み合わせがある。
老舗の業者見てるともっと凝ったレコメンドはかなりあるので、他サイトを見てみるのは、レコメンド作成でかなり参考になった。





気づいたこと、テクメモ



・今回は商品の個体を識別するデータがなかった

iPhone 10のような商品の個体を識別するデータがない、ので同一商品をレコメンドする可能性があり困った


・ID系の膨大なカテゴリ変数の加工テク

→word2vecで類似度変換して量的変数にするとか


→一番頻度の高い順にでランク付け、頻度が少ないやつは0として扱う


→別々の変数同士を組み合わせて新たな変数を作る(ex:カウント数順で上位112目
brand_id=256, model_id=0, cate_id=3105 => cnt=112)

クラスタリング(k-means)で画像から色の検出(機械学習、opencv)

今回はクラスタリング手法で、画像の重要な色を検出するタスクをやった。ニューラルネットワークならより正確な検出が可能だけど、データセット作成もろもろコストがでかい。なので、昔からあるクラスタリング手法で手軽に、かつ精度よく色を検出してみた。そのロジックと工程を書いてく。

目次
・rgb値から色を特定する方法
・用意したもの&ライブラリ
クラスタリングで色検出
・色同士のノルム計算で色の特定
・実験
・追記-なぜ思いついたからのルーツ


rgb値から色を特定する方法



色はrgb値の3つの値で表される。赤なら255:0:0だし、紫なら128:0:128。各3つの値のとる範囲は0〜255。rgb値はよく数学であるxyz空間と同じで、rgb空間上の点で表せる。

rgb空間は図で表すと下のような、6角形の形。

f:id:trafalbad:20180926120222p:plain


数学では三平方の定理とかでよく出てくるけど、「ベクトル間の距離(ノルム)が近いもの同士は似てる」という性質がある。


色でも同じ性質が使えて、rgb値のノルムが近いほど、互いの色が似ていることを表す。
この性質を使って、

・rgb値のノルムが近いもの同士=似た色

として色を検出する。



ただし、rgb空間は六角形なので、xyz空間のように3次元に変換してやる必要がある(座標変換)。rgb空間の3次元空間はlab空間というらしい。

f:id:trafalbad:20180926120244j:plain

lab値の各値のとりうる範囲は

L: [0, 100]
a: [-127, 127]
b: [-127, 127]

これでノルムを計算する。




色を特定する手順はこんな感じ

①色のlab値のリストの作成

②rgbで画像の読み込み

③rgb値のままクラスタリング

④skimage.color.rgb2lab()でrgbをlabに変換

⑤リストのlab値と、読み込んだ画像のlabのノルムを計算。リスト内の色と類似した色のtop4を特定



lab値の変換について
ライブラリのopencvでrgbをlabに変換すると、値は0〜255の範囲のまま。それだと困るので、ここではskimageというライブラリを使う。skimageならlabの値の範囲内で正確に変換してくれる。

参考サイト





用意したもの&ライブラリ


ライブラリ
opencv=> rgb値での画像読み込み用

・skimage => rgb値をlab値に変換用

・k-means => クラスタリング



用意したもの
・色のlab値のリスト

f:id:trafalbad:20180926120616j:plain

=> lab値の取得は下記のサイトを使った
https://syncer.jp/color-converter

・試す画像複数枚 =>上手く検出できるか試す画像






クラスタリングで色検出



まず、下の画像をopencvでrgbで読み込む。
f:id:trafalbad:20180926120426p:plain

def load_image(image_file):
    # cv2 load images as BGR
    image_bgr=[cv2.imread(image_file+'/'+i) for i in os.listdir(image_file)]
    image_rgb = [cv2.cvtColor(i, cv2.COLOR_BGR2RGB) for i in image_bgr]
    image_rgb = [cv2.resize(i, (150, 150)) for i in image_rgb]
    return image_rgb


img = load_image("/Users/d/clust_img")
img=np.reshape(img, (len(img),150,150,3))
hstack=np.hstack(img)
plt.imshow(hstack)
plt.show()

そのままこの画像をクラスタリング、して色の抽出。最後は、5色のrgb値を算出してる。
メインの関数はコード参照


img = load_image("/Users/tatsuyahagiwara/d/clust_img")
img=np.reshape(img, (len(img),150,150,3))
b,w,h,c=img.shape
N=7
sample_img=[i[int(w/10):int(N*w/10), int(h/10):int(N*h/10)] for i in img]
sample_img=[i.reshape(-1,3) for i in sample_img]
color=[]
for i in sample_img:
    clt = KMeans(n_clusters = 5)
    clt.fit(i)
    hist = centroid_histogram(clt)
    bar = plot_colors(hist, clt.cluster_centers_)
    plt.figure()
    plt.axis("off")
    plt.imshow(bar)
    plt.show()

k-meansはお手頃だし、割と正確に検出できたので使った。クラスタリングした結果の一部はこんな感じ。

f:id:trafalbad:20180926120449p:plain
f:id:trafalbad:20180926120503p:plain
f:id:trafalbad:20180926120512p:plain






色同士のノルム計算で色の特定



算出したrgb値をskimage.color.rgb2lab()でlab値に変換。
あとは関数でリストの色と読み込んだ色のlab値のノルムを計算して、一番近い(似ている)色を出した。

for i in sample_img:
    clt = KMeans(n_clusters = 5)
    clt.fit(i)
    hist = centroid_histogram(clt)
    bar = plot_colors(hist, clt.cluster_centers_)
    plt.figure()
    plt.axis("off")
    plt.imshow(bar)
    plt.show()
    bar_lab=rgb2lab(bar) # LAB値に変換
    c1, c2, c3, c4, c5=get_lab_from_list(bar_lab[0].tolist())
    dic1, dic2, dic3, dic4, dic5=lab_distance_dic(color_name, lab_list, c1, c2, c3, c4, c5)
    t1,t2,t3,t4,t5=get_topN_color(dic1,dic2,dic3,dic4,dic5)
    color.append([t1,t2,t3,t4,t5])

main_color=[]
for i in color:
    for n in i:
        main_color.append(n[0][0])
        
N=sorted(list(collections.Counter(main_color).values()),reverse=True)
for key, value in collections.Counter(main_color).items():
    if value in N[:4]:
        print('top_color:{}'.format(key))

色のtop4算出結果はこんな感じ。割と画像のメインの色を特定できてる感じなので、十分使えると思う。

ブラック系
ピンク系
ブルー系
グレー系



精度を上げるためにしたこと


メインのカラーが全部で13 。なので、色ごとに一つのlab値(青なら青一色のlab値)しか用意してない場合、単一のlab値でしか色を判断できない。
しかし、色一つとっても、その色に近い色はたくさんある。(例えば青でも、青っぽい色はたくさんある)
http://irononamae.web.fc2.com/colorlist/wa.html




なので、精度を上げる方法としては、

①増やしたい代表色(列の一番目)を決める

②代表色に似ている色とそのlab値を、列の2番目以降に入れる。




こうすれば、代表色をいろんなlab値で表せるので、精度は上がる。今回は青、イエロー、赤を増やしてみた結果、精度が上がった。

(ex:青っぽい色を全部「青」と区分すれば、「青っぽい色=全て青」となるので、いろんなlab値でも青と特定できる)。





実験



一応実験として、下の画像群でも色の特定をしてみた

f:id:trafalbad:20180926120741p:plain

やることは上の作業を別の画像でやるだけ


クラスタリング結果と検出した色名は以下の通り
f:id:trafalbad:20180926120754p:plain


ブラウン系
ピンク系
ブラック系
グレー系
イエロー系

赤とブラウンの区別が難しいので、主観でlab値を選んだので、検出結果はよくわからない。
総括では、割といい感じにできて、実用できるレベルだと思う




ガチのところは、ニューラルネットワークを使ってかなり精度高く色検出やってる(メルカリとか)。ただ、管理、データセット作成、計算もろもろコストがでかすぎるのが難点。その分、k-meansみたいな昔からある手法ても、工程さえしっかりやれば、簡単かつ高い精度でできるケースのが多い。アルゴリズムは手段にすぎないので使えさえすれば、k-meansでも十分精度の良いものができた。

追記-なぜ思いついたからのルーツ

・まず調査しまくる

・書籍、ネットの情報、自分の経験それらを総動員して考えまくるとアイデアがわく。like アインシュタイン、アイデアが思いつく法則

リクルートの事例参考になんで抽出できるか考えてたら、ML分野の中でクラスリングならできそうだということで、調べ、考えまくってたら、ベクトルノルムの方法思いついた

AWSのGPU環境下でkerasを使った百万単位(ビックデータ)の画像分類の訓練、テスト、予測までの過程まとめ

今回はkerasを使って、AWSGPU環境下で5百万枚の画像を訓練してみた。ラベル数は200ラベル。おそらくビックデータと呼ばれる規模だと思う。エラーとか、障壁が多々あったので、備忘録もかねて工程を一通りまとめてく

目次
・EC2にGPU適用&jupyter環境構築
・tfrecordから画像の読み込み(input image)
・訓練(training)
・学習率
GPU環境下での訓練
・モデルの保存
・テスト・予測
・最後に




EC2にGPU適用&jupyter環境構築



EC2インスタンスGPUの適用

Deep learning ubuntu(linux)で検索、インスタンス作成、作成時に「EPS-optimize( EPS最適化)」を選択

②あとはチュートリアル通りに最後までやれば、GPU環境を構築できる。
(https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/install-nvidia-driver.html )





**補足**

チュートリアルの下のコマンドはlinuxパッケージのみ必要らしい。

sudo yum erase nvidia cuda


GPUの型番を確認コマンド ([ ]の中がGPUの型番に該当)

sudo update-pciids 
lspci | grep -i nvidia 




jupyter notebook 設定について

① 以下のサイトの$ipython以降を実行し、パスワードとか設定https://qiita.com/Salinger/items/c7b87d7000e48be6ebe2

② EC2インスタンスでjupyter用のIPアドレスの設定

EC2のインスタンス画面→対象のインスタンスのセキュリティグループを選択→インバウンドから編集→カスタムTCPルールのポート範囲/送信元を設定(8888/カスタム) →「http://[IPv4パブリックIP]:8888/」にアクセス(ex:http://18.217.126.3:8888/)

③必要なパッケージpipでインストール

④次のコマンドでjupyterの起動

$jupyter notebook & 

⑤jupyter上でGPU適用の確認 (「nvidia-smi」コマンドはGPU動作中のみ有効)

from tensorflow.python.client import device_lib
device_lib.list_local_devices()

上のコマンド打って、「name: "/device:GPU:0"〜」みたいなメッセージが出ればOK







tfrecordから画像の読み込み(input image)



だいたい、cifar-10のチュートリアル通り。けど、バッチを作るメソッドの

tf.train.shuffle_batch()

は画像枚数が大きすぎると、メモリを食い過ぎてショートする。tfrecordを複数にしても、ラベルが上手くシャッフルできないせいか、損失関数が上手く減少しない。ここら辺のロジックは、なかなかセオリー通りにいかないので、以下のコードで読み込ませた。

def distorted_input(data_files, batch_size, train=True, num_readers = 60):
    num_class=200
    if train:
        filename_queue = tf.train.string_input_producer(data_files, shuffle=True, capacity=16)
    else:
        filename_queue = tf.train.string_input_producer(data_files, shuffle=False, capacity=1)
    num_preprocess_threads = 60
    examples_per_shard = 1024
    min_queue_examples = examples_per_shard * 16
    if train:
        examples_queue = tf.RandomShuffleQueue(capacity=min_queue_examples + 3 * batch_size,
                min_after_dequeue=min_queue_examples, dtypes=[tf.string])
    else:
        examples_queue = tf.FIFOQueue(capacity=examples_per_shard + 3 * batch_size, 
                                      dtypes=[tf.string])

    # Create queue
    if num_readers > 1:
        enqueue_ops = []
        for _ in range(num_readers):
            reader = tf.TFRecordReader()
            _, value = reader.read(filename_queue)
            enqueue_ops.append(examples_queue.enqueue([value]))
        tf.train.queue_runner.add_queue_runner(
            tf.train.queue_runner.QueueRunner(examples_queue, enqueue_ops))
        example_serialized = examples_queue.dequeue()
    else:
        reader = tf.TFRecordReader()
        _, example_serialized = reader.read(filename_queue)
        
    images_and_labels = []
    for thread_id in range(num_preprocess_threads):
        image, label_index = parse_example_proto(example_serialized)
        images_and_labels.append([image, label_index])
    
    images, label_index_batch = tf.train.batch_join(images_and_labels,
             batch_size=batch_size, capacity=2 * num_preprocess_threads * batch_size)
    
    height = 150
    width = 150
    images = tf.reshape(images, shape=[batch_size, height, width, 3])
    
    return tf.subtract(tf.div(images,127.5), 1.0), tf.one_hot(tf.reshape(label_index_batch, [batch_size]), num_class)


def parse_example_proto(serialized_example):
    height = 150
    width = 150
    features = tf.parse_single_example(serialized_example,
                        features={"label": tf.FixedLenFeature([], tf.int64),
                                  "image": tf.FixedLenFeature([], tf.string)})
    label = tf.cast(features["label"], tf.int32)
    imgin = tf.reshape(tf.decode_raw(features["image"], tf.uint8), tf.stack([height, width, 3]))
    image = tf.cast(imgin, tf.float32)
    distorted_image = tf.image.random_flip_left_right(image)
    distorted_image = tf.image.random_brightness(distorted_image, max_delta=63)
    distorted_image = tf.image.random_contrast(distorted_image, lower=0.2, upper=1.8)
    distorted_image.set_shape([height, width, 3])
    return distorted_image, label

num_readers と num_preprocess_threadsの数を増やしてやれば、大容量でも上手く読み込めた。枚数が億単位になるとできるか不明。

batch数は32×GPU数(今回は32×8=256)が一番安定して、損失関数が減少した。






tfrecordファイルの作成について

tfrecordはprotocol buffer 形式で画像とラベルがペアで入ってる。けど、tfrecordにこだわらず、pickleファイルとか他のファイルでも問題ない。
tfrecordはたまたま使いやすいから、個人的に使っているだけ。枚数が増えてメルカリみたいに転移学習を用いる環境下ではむしろtfrecordは向いてないので、適材適所でファイルは使うべき。

ちなみに1ラベルごとに1つのtfrecordを作成したので、合計200ファイル。ラベルごとの作成過程は、
urlを呼び出してcsvに保存→s3からディレクトリに保存し→tfrecord作成

まで一連の過程を1ラベルごとにした。


label_0.csv → label_0(ディレクトリ)→train_label_0.tfrecords

こんな感じでやると以下の利点があって、一番効率がよく作成できた。



・問題が起こってもすぐ作り直せる

・for文で連鎖的に呼び出せる

path='/home/ubuntu/train_tf'
filenames = [os.path.join(path, 'record_%d.tfrecords' % i) for i in range(0, 200)]


・ミスなく作れる


ちなみに全tfrecordの中身の画像枚数は下のコードでカウント

# pathの読み込み
path=[os.path.join('/home/ubuntu/train_tf/record_%d.tfrecords' % i) for i in range(0, 200)]

# データ数カウント
cnts=0
for i, p in enumerate(path):
    cnt = len(list(tf.python_io.tf_record_iterator(p)))
    print("NO:{}, データ件数:{}".format(i, cnt))
    cnts+=cnt
print("合計データ件数:{}".format(cnts))





訓練(training)



モデルはinception_ resnet_v2を使用。
f:id:trafalbad:20180925213911j:plain




訓練のメイン部分

sess = tf.Session(config=tf.ConfigProto(allow_soft_placement=True, log_device_placement=False))
K.set_session(sess)

# lr = tf.train.exponential_decay(0.1, global_step, decay_steps, learning_rate_decay_factor, staircase=True)

def step_decay(epoch):
    initial_lrate = 0.01
    decay_rate = 0.5
    decay_steps = 8.0
    lrate = initial_lrate * math.pow(decay_rate,  
           math.floor((1+epoch)/decay_steps))
    return lrate


NUMN_GPUS = 8
batch_size=32*NUMN_GPUS

train_image, train_labels=distorted_input(filenames, batch_size, train=True)

with tf.device('/cpu:0'):
    train_model= InceptionResNetV2(train_image)
pmodel= multi_gpu_model(train_model, gpus=NUMN_GPUS)
pmodel.compile(optimizer=SGD(lr=0.01, momentum=0.9, decay=0.001, nesterov=True),
                    loss='categorical_crossentropy', 
                    metrics=['accuracy'], target_tensors=[train_labels])

# callback
history = History()
callback=[]
callback.append(history)
callback.append(LearningRateScheduler(step_decay))
  

tf.train.start_queue_runners(sess=sess)
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess, coord)
try:
    pmodel.fit(steps_per_epoch=int(5101031/batch_size), epochs=3, callbacks=callback)
finally:
    train_model.save('/home/ubuntu/check_dir/inception_model_ep8.h5')
coord.request_stop()
coord.join(threads)

K.clear_session()


損失関数はこんな感じで順調に減少。3エポックくらいから減少しなくなった。

f:id:trafalbad:20180925213933p:plain





学習率
cifar-10とかで使ってる学習率の調整用メソッド
tf.train.exponential_decay()はkerasの場合、次で代用できる。

(tf.train.exponential_decayの引数「global_step」は kerasではepoch)

def step_decay(epoch):
     learning_rate = 0.01
    decay_rate = 0.5
    decay_steps = 8.0
    lrate = learning_rate  * math.pow(decay_rate,
           math.floor((1+epoch)/decay_steps))
    return lrate

これを

callback.append(LearningRateScheduler(step_decay))

でcallbackに入れれば、epochごとに最適な学習率に調節してくれる。パラメータは自分で調整した。

resnet系のoptimizerのベストプラクティスに関しての詳細はここら辺のサイトに詳しくのってる。→参考サイト

SGD+Momentumとnesterov=Trueがベストプラクティスらしい。パラメータはチュートリアルとか、論文読みあって総括して決めた






GPU環境下での訓練
kerasの場合、複数のGPUを使用するなら、modelをmulti_gpu_model()の引数に代入する必要がある。インスタンスタイプのp2_8xlargeはGPUを8こ搭載してるので、gpus=8にしてる。

またモデルの読み込みはCPU上でやらないと、OOMエラーが出る

with tf.device('/cpu:0'):
    train_model= InceptionResNetV2(train_image)

またOOMエラー対策かわからないけど、GPU使うときはtf.Session内で、次のおまじないが必要。

sess = tf.Session(config=tf.ConfigProto(allow_soft_placement=True,
log_device_placement=False))




モデルの保存
訓練はmulti_gpu_model()メソッドで作成したモデルでやってるけど、
保存はmulti_gpu_model()メソッドに入れたモデル(ここではtrain_model)を保存しないとダメ。このときモデルの保存にcallbackの
ModelCheckpointはmulti_gpu_model()メソッドのモデルを保存してしまうので使えない。

またモデルの保存は訓練終了時に指定。

coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess, coord)
try:
    pmodel.fit(steps_per_epoch=int(5101031/batch_size), epochs=3, callbacks=callback)
finally:
    train_model.save('/home/ubuntu/check_dir/inception_model_ep8.h5')
coord.request_stop()
coord.join(threads)








テスト・予測



テスト、予測のメイン部分のコード

sess = tf.Session(config=tf.ConfigProto(allow_soft_placement=True, log_device_placement=False))
K.set_session(sess)

NUM_GPUS=8
batch_size=125*NUM_GPUS

test_image, test_labels= distorted_input(filenames, batch_size)
with tf.device('/cpu:0'):
    test_model = InceptionResNetV2(test_image)
test_model.load_weights('/home/ubuntu/check_dir/inception_model_ep8.h5')
test_model= multi_gpu_model(test_model, gpus=NUM_GPUS)
test_model.compile(optimizer=SGD(lr=0.01, momentum=0.9, decay=0.001, nesterov=True),
                    loss='categorical_crossentropy', 
                    metrics=['accuracy'], target_tensors=[test_labels])


coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess, coord)

_, acc = test_model.evaluate(x=None, verbose=1, steps=100)
print('\nTest accuracy: {0}'.format(acc))

y_pred = test_model.predict(x=None,verbose=1,steps=100)
LABEL=[]
for i in range(100):
    LABEL.append(sess.run(test_labels))
top_k=sess.run(tf.nn.top_k(y_pred,k=3,sorted=False))
coord.request_stop()
coord.join(threads)

test_model.evaluate(x=None, verbose=1, steps=100)のstepsとバッチサイズは、読み込むテスト画像とぴったり合うようする必要がある。
今回はテスト画像枚数が10万枚なので、steps=100 、batch_size=125*8
にして、1000バッチを100回繰り返して、10万枚の画像を読み込んだ。

また評価用にラベルもtfrecordから取り出した。

LABEL=[]
for i in range(100):
    LABEL.append(sess.run(test_labels))

あとは混合行列と、f値で評価

# Accuracy , F-score.etc
print('acuracy:{}'.format(accuracy_score(label_batch,f_pred)))
label_string = ['{}'.format(i) for i in range(200)]
print(classification_report(label_batch, f_pred,target_names=label_string))





最後に


今回は、

・環境が整ってない
・5百万単位の大規模データでの訓練

の2つが要因でエラーが多々発生した。
反省点としては


・停滞したら、立ち止まって調査、考える時間にあてる
・思考時間とPC作業の割合は7:3くらいが自分にはベストプラクティス

これ以上枚数が増えると、一部の界隈(メルカリ)みたいに

転移学習+次元削減(PCAとか)+faiss

を使うような、効率性を追求した環境構築の必要あると思った。




EC2、S3関係のコマンドメモ


# sshのバージョン
$ ssh -V
OpenSSH_7.9p1, LibreSSL 2.7.3

# ssh鍵をジェネレート
$ ssh-keygen -t rsa
$ mkdir ~/.ssh
$ mv id_rsa.pub ~/.ssh
$ mv id_rsa ~/.ssh
$ cd ~/.ssh # 移動しておく
# .ssh配下にauthorized_keysがない場合
$ mv id_rsa.pub authorized_keys
$ chmod 600 authorized_keys && chmod 600 id_rsa

・キーの作成
# ec2インスタンスへのログインキーの作成・保存

cp path/to/フィル名.txt ~/.ssh/任意の名前

chmod 400  ~/.ssh/名前


sshでログイン

$ ssh -i ~/.ssh/任意の名前 root@ 

・scpコマンドで、フォルダorファイルコピー
# EC2からローカルにファイルのコピー

scp -i ~/.ssh/aws_develop_sec /Users/Downloads/test.tfrecords ubuntu@13.231.234.143:/home/ubuntu/test.tfrecords 

# ローカルからEC2へファルダのコピー

scp -r -i ~/.ssh/aws_develop_sec /Users/Downloads/prediction_img centos@13.231.108.156:/home/centos/prediction_img

# EC2からローカルへフォルダのコピー

scp -r -i ~/.ssh/aws_develop_sec centos@13.231.234.17:/home/centos/test /Users/Downloads/tfrecords/test 


# EC2からs3へフォルダのアップロード

aws s3 cp a s3://cnn-image --recursive --acl public-read --cache-control “max-age=604800"

# S3にディレクトリごとupload・download

$ aws s3 cp <ディレクトリ名> s3://<バケット名>/<バケット名>/ --recursive --acl public-read --cache-control "max-age=604800"


# S3からローカルにダウンロード
$ aws s3 cp s3://バケット名/ . --recursive


sshのログインがキレてもpythonを動かし続けられるコマンド(バックグラウンド→ https://qiita.com/alancodvo/items/15dc36d243e842448d33

$ nohup python Inception_keras_gpu_train.py & 

xgboostの回帰モデルで精度検証から重要な特徴量選択までやってみた全行程まとめ(機械学習)

今回はkaggleでよくある特徴量エンジニアリングのテクを使って、精度向上から重要な特徴選択までをやった。普通は精度高ければ終わり的な感じだけど、今回は精度検証からさらに掘り下げて、特徴量の選択までやったので、その過程を書いてく。

目次
・プロジェクト紹介
・デーセット
・特徴量作成
・モデル構築
・正解率
・特徴量の探索(分析)
・最後に(反省点)



プロジェクト紹介


一回しか購入しないユーザー(u1)と2回以上購入しているユーザー(u2)を分ける重要な特徴量を見つけるプロジェクト。目的は一回しか買わないユーザーに有効な施策をうつこと。

よくある特徴量エンジニアリングで、回帰モデルを使って精度を競うkaggleとかのコンペと似ている。けど、今回は精度検証後に重要な特徴量選択までしたので、特徴量選択の分析過程も含まれてる。全体の工程としては、特徴量作成が7、8割を占めた感じ。

f:id:trafalbad:20180921103119j:plain

特徴量エンジニアリングが、精度向上に重要な過程だと再認識。





データセット


データセットはデータベースに保存してあるテーブルのレコード(特徴量)から選択して作成。kaggleみたいにあらかじめ綺麗なのが用意されてないし、データベースとbigqueryに別々に保存してある。そんな中、無数にある特徴量から適切なのをSQLで取り出して、ベストな特徴量を作成。

最終的な特徴量は、2回目の検証結果から決めた。アルゴリズムでの検証は全体で3回。

まず1回目は商品に結びつきそうな特徴量を選んで検証。1回目はtouroku_dateという特徴量が一番重要らしく、次の特徴量を選ぶ指針になった。

2回目は施策から「u1が1回で購入をやめてしまう」ことに関する仮説を立てて、この仮説に直結する特徴を選んで使うことにした。
仮説の設定はAmazonとか、リピート率が鬼のように高いサイトレビューとかを参考にした。


一回で購入をやめてしまう or 継続して購入する理由の仮説

=> 評価の悪いユーザーから購入してる
=> レコメンド関係の施策で欲しい商品を購入してる
=> 欲しい商品が見つからない(時間をかけて探していない)



3回目は特徴量を2個追加して、若干の修正後に再検証。






***仮説を立てることで作業を効率化
無数にある特徴量の中から、適切なものを選択する過程で、施策に応じて仮説を立てる作業はかなりの効率化につながった。はじめに仮説を立てるのは、データサイエンティスト的な手法らしい。

さらに、選んだ特徴量から、u1とu2間の違いをあらかじめ調べる作業をする事で、さらに効率化。違いを見るのには「標準偏差・平均・中央値」とかの分析手法を使った。

特徴量は何を選べばいいかわからない状況下で、

・仮説の設定

・違いを見る作業

この二つをすることで、かなり効率化できた。

特徴量に必要な特徴量を選択するまでの、全行程はこんな感じ

1、まず分類に使えそうな特徴量を作成・検証

2、試作にあった仮説を立てる(重要)

3、仮説に直結する特徴量を選んで、標準偏差とかで違いをみる(重要)

4、特徴量を決めて、作成

2、3の作業が一番精度向上につながった。

最終的なデータセットは、
特徴量10こ
u1は250853件、u2は1169898件のデータを使った。




特徴量作成


特徴量の作成過程は上で書いた通りなので、ここではデータベースのレコードをSQLで取り出し、それを使いやすい形に変えて特徴量を作成する特徴量エンジニアリングのテクニックについて触れたい。

特徴量エンジニアリングでは主に質的変数(赤、青、白とかみたいな数字じゃない形)をダミー変数に、量的変数(体重とか身長みたいな数値)を使いやすく変形させるテクがある。

特にダミー変数は離散化とか、いろいろあるので、ここら辺の資料を参考にした。

機械学習レシピ#3

データの前処理だけで競馬は強くなる

特徴量の形を使いやすい形に変えるのも、めっちゃ大事。
今回は可視化した結果の分布が、「0が多く、0以上が少ない」場合、SQLでこんな感じに取り出すのが有効だった。

SUM(case when レコード= 値 then 1 else 0 end) 


逆に分布がバラバラな特徴量は、AVG() で平均をとるか、そのまま使うのが有効だった。

特徴量は検証前に、標準化しないと分類精度がすごい下がるので、データ作成後は必ず標準化した。






モデル構築


モデルは、kaggleで有名なランダムファレストの改良版のxgboostを使った。
特にGBDTの改良手法Dropouts meet Multiple Addtive Regression Trees(DART)とパラメーターチューニングのhyperoptの組み合わせが、xgboostとドンピシャで、かなりいい分析精度になった。

xgboostのパラメータは山ほどあるので、チューニングはベストプラクティスを載せてるサイトがを参考にした。

Parameter_Tuning_XGBoost_with_Example

パラメータチューニングはhyperoptとgridseachを使い分けて、手早くチューニング。

精度検証で、交差検証はもはや当たり前で、さらに複数のアルゴリズムでの検証や、複数の精度指標での試行錯誤も王道らしい。

def objective(params):

    skf = cross_validation.StratifiedKFold(
        train_y, # Samples to split in K folds
        n_folds=5, # Number of folds. Must be at least 2.
        shuffle=True, # Whether to shuffle each stratification of the data before splitting into batches.
        random_state=30 # pseudo-random number generator state used for shuffling
    )

    boost_rounds = []
    score = []

    for train, test in skf:
        _train_x, _test_x, _train_y, _test_y = \
            train_x[train], train_x[test], train_y[train], train_y[test]

        train_xd = xgb.DMatrix(_train_x, label=_train_y)
        test_xd = xgb.DMatrix(_test_x, label=_test_y)
        watchlist = [(train_xd, 'train'),(test_xd, 'eval')]

        model = xgb.train(
            params,
            train_xd,
            num_boost_round=100,
            evals=watchlist,
            early_stopping_rounds=30
        )

        boost_rounds.append(model.best_iteration)
        score.append(model.best_score)

    print('average of best iteration:', np.average(boost_rounds))
    return {'loss': np.average(score), 'status': STATUS_OK}

def optimize(trials):
    space = {'booster':'dart',
         'learning_rate':0.1,
         'n_estimators':1000,
         'sample_type':'uniform',
         'normalize_type': 'tree',
         'objective':'binary:logistic',
         'min_child_weight':1,
         'max_depth':9,
         'gamma':0.0,
         'subsample':0.6,
         'colsample_bytree':0.9,
         'reg_alpha':1e-05,
         'nthread':4,
         'scale_pos_weight':1,
         'seed':27,}
    
    # minimize the objective over the space
    best_params = fmin(
        fn=objective,
        space=space,
        algo=tpe.suggest,
        trials=trials,
        max_evals=10
    )

    return best_params

# parameter tuning
trials = Trials()
best_params = optimize(trials)
print(best_params)

# 損失関数(loss)の計算
print(objective(best_params))






正解率


正解率は


AUCで89.33%



MCCで0.548(-1~1の範囲をとり、1で100%の精度)
f:id:trafalbad:20180921110446p:plain



特徴量の重要度
f:id:trafalbad:20180921103149p:plain


評価指標はAUCがよく使われるけど、不均衡データとかより正確な精度判定にはMCCの方がいいっぽい。

from sklearn.metrics import matthews_corrcoef
thresholds = np.linspace(0.01, 0.99, 50)
mcc = np.array([matthews_corrcoef(test_y, pred_y>thr) for thr in thresholds])
plt.plot(thresholds, mcc)
best_threshold = thresholds[mcc.argmax()]
print(mcc.max())

この他にも混合行列とかF値を使ってもいいけど、今回はMCCがF値と同じくらい精密なので、MCCで代用。




特徴量の探索(分析)



特徴量の重要度、そのツリー、xgboostの結果から、u1とu2に有効な特徴量選択。検証からさらに特徴量を探すまで深掘りするケースはサイトでは見つからなかったので、このフェーズが一番大変だった。
とりあえず、一般的な統計手法とか、マーケット分析事例で使われてるっぽい分析手法を漁った。

あとは知識、ひらめき、そしてひたすら根気の作業。
結果、ヒストグラムクラスタリング、次元削減、重回帰分析とかが使えるっぽくて、一番合理的な「重回帰分析」を使った。

重回帰分析は、ある一つの特徴量と、他の複数の特徴量の相関関係を計算してくれる

結果的に、

u1=>海外商品系の特徴量

u2=>満足度が高い特徴量

との相関が高く、これらが重要な特徴量と判断。

結論とその理由とかのロジック的な部分は、他の特徴量との相関関係の数値、仮説の消去法で導いた感じ。ちゃんと根拠つきで




最後に(反省点)



kaggleとかの特徴量エンジニアリングを使った分析は、あらかじめ綺麗な特徴量が用意されていて、分析精度が良ければ終わり、みたいなケースが多い。
けど今回はさらに掘り下げて、重要な特徴量の選択までやった。前例がないとほぼ手探りなので、かなり大変だった。

とりあえず、反省点として

・試行錯誤より頭を使って、作業量を減らす(think more, do less)

・行き詰まったり、停滞したら、一回止まって問題の分解とか本質の問題探しをする。絶対にただタスクをこなすだけのループにはまらないこと
(今回はマッキンゼーの問題解決法の書籍が本質の問題探しにかなり使えた
イシューからはじめよ―知的生産の「シンプルな本質」
世界一やさしい問題解決の授業―自分で考え、行動する力が身につく


・チームで動く以上、進歩確認は随時やる


・データ量が多ければ、少ないデータ量(1万件くらい)で試す


・やることの意思疎通にすれ違いがないようにして、やり直しは極力やらない


・段取り8割


・わからないことは、詳しい人に積極的に、遠慮なく聞くこと

あえてxgboostのDARTみたいなハイリスク・ハイリターンのスキルを使うことで、かなりのスキルが身についたなと実感した。




kaggleのテクニック系の参考資料
top2%の私が教えるKaggleの極意

Kaggleで使われた特徴量エンジニアリングとアルゴリズムまとめ