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

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

pythonで強化学習のモデルベースの手法とその学習法をまとめてみた【機械学習】

書籍「pythonで学ぶ強化学習」を読んだ。強化学習のアウトプットの機会がないので、とりあえず学んだ内容を備忘録も兼ねてまとめてみた。

主に強化学習は「モデルベース」と「モデルフリー」に分類でき、今回はモデルベースの手法のまとめ。

目次
1.モデルベース強化学習のメイン「変数・関数・環境」
2.モデルベースとモデルフリー
3.強化学習の学習法「policyベース」と「valueベース」
4.まとめ


1.モデルベース強化学習のメイン「変数・関数・環境」

モデルベースの強化学習で主に定義するのは、行動、状態、遷移関数、報酬関数、環境。

例:
環境:12マスのゲーム、緑にたどり着ければ報酬ゲット
f:id:trafalbad:20190425080644p:plain

行動(a):右、左、上、下

状態(s):行列のセルの位置

遷移関数(T(s, a)):行動と状態を引数にとり、次に移動するセル(遷移先:s')とそこへ遷移する確率を返す関数

報酬関数(R(s', s)):状態と遷移先(s')を引数に、赤のセルなら-1と、緑のセルなら+1を返す関数


変数、関数、環境の概略コード

# 状態
class State():
    # 略
     return self.row == other.row and self.column == other.column

# 行動
class Action(Enum):
    UP = 1
    DOWN = -1
    LEFT = 2
    RIGHT = -2

# 環境
class Environment():
    def __init__(self, grid, move_prob=0.8):

   # 遷移関数
   def transit_func(self, state, action):
        # 略
        return transition_probs

   # 報酬関数
   def reward_func(self, state):
        # 略
        return reward, done

   return next_state, reward, done


***ちなみに***
強化学習は「遷移先の状態は直前の状態と、そこでの行動のみに依存する。報酬は直前の状態と遷移先に依存する」という決まり(「マルコフ決定過程(MDP)」)がある。
つまり、強化学習は「行動、状態、遷移関数、報酬関数、環境を与えることで報酬の総和(≒評価値)が決まる」という決まりになってる。


強化学習マルコフ決定過程の図
f:id:trafalbad:20190425080625p:plain



2.モデルベースとモデルフリー

強化学習はモデルベースとモデルフリーに分類できる。

モデルベース




モデルベース=「どんな行動をとれば報酬が得られるか(遷移関数と報酬関数)がはっきりわかってる環境」
のこと

下図の環境は
・緑のセル→プラスの報酬
・オレンジのセル→マイナスの報酬

と「どんな行動をとれば報酬がもらえるのか」はっきりしているからモデルベースの一例。

f:id:trafalbad:20190425080644p:plain


モデルフリー



モデルフリー=「どんな行動をとれば報酬に繋がるかわからない(遷移関数と報酬関数がわからない)環境」のこと。


スポーツなら全般(例えばサッカーとかバドミントンとか)が当てはまり、有名なアルファ碁は対戦相手によって報酬が決まらないからモデルフリー。

モデルフリーは遷移関数と報酬関数を定義せず、エージェント(環境におけるプレイヤー)を定義する。




モデルベース、モデルフリーにおける報酬の総和(価値)の計算



モデルベース、モデルフリーの環境はどちらも
・環境における制限時間(エピソード)終了時の報酬の総和(以下「価値」)

を計算するときは、

・過去の計算値を使う
・(行動)確率をかけて期待値で表す

という2つテク(Bellman Equation)を使って計算する。

強化学習はこの「価値」を最大化することが目的なので、重要な部分。

def V(s, gamma=0.99):
    V = R(s) + gamma * max_V_on_next_state(s)
    return V


def R(s):
    if s == "happy_end":
        return 1
    elif s == "bad_end":
        return -1
    else:
        return 0

def max_V_on_next_state(s):
    # 略
    return max(values)

def transit_func(s, a):
    # 略
        return {
            next_state(s, a): MOVE_PROB,
            next_state(s, opposite): 1 - MOVE_PROB}




3.強化学習の学習法「policyベース」と「valueベース」

強化学習の学習目的で一番の焦点は

・Bellman Equationで表した「価値」をどうやって最大化するか

この目的に合わせて、主に「policyベース」と「valueベース」2つの学習方法がある。(2つ両方使う、二刀流的な学習方法もある)




policyベース
=> どのように行動するか(戦略)を基準に学習する

イメージ

policyベース→参謀長みたいな策略家で現実的な思考で学習する感じ


valueベース
=> 「特定の行動をとる=評価になる」ということだけ学習する。(評価=行動選択)

イメージ
valueベース→いいならいい、ダメならダメと勝手に決めて学んでくので、楽観的な思考で学習する感じ




# valueベースとpolicyベースで共通のPlannerを定義
class Planner():

    def __init__(self, env):
        self.env = env
        self.log = []

    def initialize(self):
        self.env.reset()
        self.log = []

    def plan(self, gamma=0.9, threshold=0.0001):
        raise Exception("Planner have to implements plan method.")

    def transitions_at(self, state, action):
        transition_probs = self.env.transit_func(state, action)
        for next_state in transition_probs:
            prob = transition_probs[next_state]
            reward, _ = self.env.reward_func(next_state)
            yield prob, next_state, reward

    def dict_to_grid(self, state_reward_dict):
        grid = []
        for i in range(self.env.row_length):
            row = [0] * self.env.column_length
            grid.append(row)
        for s in state_reward_dict:
            grid[s.row][s.column] = state_reward_dict[s]

        return grid

# Plannerを継承した「valueベース」
class ValuteIterationPlanner(Planner):
    # 略
    return V_grid


# Plannerを継承した「policyベース」
class PolicyIterationPlanner(Planner):
    # 略

 # 戦略の価値計算用関数
 def plan(self, gamma=0.9, threshold=0.0001):
       # 略
        return V_grid



ちなみさっきのセルのモデルベース環境でvalueベースで訓練した結果。上手く緑にたどり着けてる。

f:id:trafalbad:20190425080711p:plain

環境が単純だからpolicyベースでもほとんど変わらない結果だった。



4.まとめ

モデルベースの強化学習の学習・評価までの流れは

①環境の定義に加えて、行動、状態、遷移関数、報酬関数の定義



②「エピソード終了時の報酬の総和(価値)」の定義

強化学習では学習目的がこの「価値」の最大化であり、この「価値」は、
・過去の計算値を使う
・(行動)確率をかけて期待値で表す

というテク「Bellman Equation」を使って計算される。



③「policyベース」か「valueベース」の手法で学習する
policyベース、valueベースまたは、その両方を使った手法を使って学習・評価する。




今の主流はvalueベースとpolicyベースでお互いの弱点を補いながら併用する手法がメインぽい。強化学習の実用されてるのは全てディープラーニングだけど、基礎的な手法自体はここで書いたやり方と同じ。

今回は「モデルベース」のメインの手法をまとめた。

「モデルフリー」の手法をまとめた次記事はこちら
trafalbad.hatenadiary.jp

CNNの訓練済みモデルで特徴抽出して、faissによる類似画像検索してみた【機械学習】

メルカリの画像分類で、end-to-endで学習した学習済みモデルを使って、特徴抽出してから、faissで類似画像検索する手法が使われてた。

詳しくは語られてないけど、下の図の流れ。
f:id:trafalbad:20190306192449j:plain

手順でいうとこんな感じ

→データセット画像(indexing dataset)とクエリ画像(query image)を用意
→訓練済みモデルを使い特徴抽出(feature extraction)
→抽出した特徴量をPCAで次元削減(PCA)
→faissでクエリ画像と似てる画像をデータセット画像から類似画像を検索(faiss)

この手法を前からやってみたかったので、今回は上の図の手法通りに、訓練済みモデルを使い、faissで類似画像検索をしてみた。

目次
・今回の手法の概要
・faissで類似画像検索の行程
1.データセット画像とクエリ画像の用意
2.訓練済みモデルで特徴抽出
3.PCAで次元削減について
4.faissで類似画像検索

今回の手法の概要
以前書いた画像分類記事のときに、inception-resnet-v2でend-to-end学習した時の、学習済みモデルを使った。

inception-resnet-v2を使ったモデルで、200ラベルの精度は50%くらいだった。ただ4種のスニーカー

・vans
・new-balance
adidas
nike

は形が同じで、ロゴが違うだけなので、各々の画像の分類精度はよくなかった。

そこでメルカリの類似画像検索手法で、さらに精度よく分類できるか試してみたのが今回の記事。


特徴抽出は一般的に訓練済みモデルを使い、より汎用的な特徴を取り出すことが目的。CNNは出力層の全結合層前の、より深い部分の畳み込み層から取り出した方が、特徴量という意味ではより汎用性が高い。



一方、faissクラスタリングとか、類似画像検索ができるライブラリらしい。今回は類似画像検索に使ってみた。全体の順序としては、下図のメルカリの画像通りの手順でやってみる。

f:id:trafalbad:20190306192449j:plain

手順

1.データセットの画像とクエリ画像を用意する

2.訓練済みのモデルで畳み込み層('conv_7b')から特徴抽出した出力と、入力画像から新しいモデルを作り、予測

3.次元削減(PCA)

4.faissでクエリ画像と似てる画像をデータセットから検索

faissで類似画像検索の行程


1.データセット画像とクエリ画像の用意

end-to-endで学習した学習済みモデルでは、スニーカー4種類「vans, new-balance, adidas, nike」の精度は10〜50%までと低い。今回は類似画像検索でこれらを上手く分類できるか試す。

まず4種類のデータセット画像を、Googleから適当に集めた。その後、画像を左右反転して水増し、合計928枚用意した。

# image dataset
# ex:new balance
path='/Users/desktop/imgs/new_balance'
new_b=[cv2.resize(cv2.imread(path+'/'+i),dsize=(150, 150)) for i in os.listdir(path)]
new_b=[preprocess_input(x) for x in new_b]
new_b=np.reshape(new_b, (len(new_b),150,150,3))
# 画像枚数水増し
flip=[cv2.flip(i, 1) for i in new_b]
new_bs = np.r_[new_b, flip]

# 他省略
# 4つを結合
ext_img = np.r_[new_bs, nikes, adidass, vanss]
print('total shape {}'.format(ext_img.shape))
>>>
(266, 150, 150, 3)
(244, 150, 150, 3)
(218, 150, 150, 3)
(200, 150, 150, 3)
total shape (928, 150, 150, 3)

# ラベル(0~3)も作成
ext_label = np.r_[new_bl, nikel, adidasl, vansl]
print('total lenth {}'.format(len(ext_label)))
>>>
total lenth 928

クエリ画像は以下の6枚。この画像と同じラベルの画像と似た画像を、データセット画像から検索できるか試す

# クエリラベル
(6, 150, 150, 3)
query image1 name    nike.jpeg
query image2 name   adidas2.jpeg
query image3 name   vans2.jpeg
query image4 name   vans1.jpeg
query image5 name   nikes.jpeg
query image6 name   adidas.jpeg
query_label:[1, 2, 3, 3, 1, 2]

・クエリ画像
f:id:trafalbad:20190306221251p:plain




2.訓練済みモデルで特徴抽出

特徴抽出は前述の通り深い層からより汎用的な特徴を抽出する方法。

以下の手順で特徴抽出して出力と入力画像から、新しいモデルを作成して、そのモデルで予測するまでをやってみる。

sess = tf.Session(config=tf.ConfigProto(allow_soft_placement=True, log_device_placement=False))
K.set_session(sess)
# Inception-resnet-v2呼び出す
train_model= InceptionResNetV2(Input((150, 150, 3)))

# 訓練済みモデル読み込み
train_model.load_weights('/Users/downloads/saved_model.h5')

# 畳み込み層の呼び出し
feature = train_model.get_layer('conv_7b')
print(type(feature))  # <class 'keras.layers.convolutional.Conv2D'>
print(feature.name, feature.output_shape) # conv_7b (None, 3, 3, 1536)

# Globalpooling層の追加
output = GlobalMaxPooling2D()(feature.output)
# 新しいモデル作成
model = Model(inputs=train_model.input, outputs=output)

print(model.output_shape) # (None, 1536)
model.summary()
"""
global_max_pooling2d_1 (GlobalM (None, 1536)           conv_7b[0][0]                    
================================
Total params: 54,332,128
Trainable params: 54,274,656
Non-trainable params: 57,472
"""
# predict()でデータセット画像の特徴抽出
features = model.predict(ext_img)
print(features.shape) # (1564, 1536)

# クエリ画像の特徴抽出
query = model.predict(query_imgs)
print('query shape:{}'.format(query.shape)) # query shape:(6, 1536)

これでデータセット画像とクエリ画像の特徴抽出ができた


3.PCAで次元削減について

PCAを行うのは計算量を減らすため。クエリ画像が数万枚〜数百万以上のときとか、特徴抽出した特徴量を使い、新しいモデルで再訓練するときに行う。

メルカリは多分、特徴抽出してから新しいモデルで再訓練してるから、そのときに次元削減してるかもしれない。

今回はPCAはしないけど、
・クエリ画像が数万枚以上あるとき

・特徴抽出して、新しいモデルで再訓練する

際のコードを試したので、書いとく

・クエリ画像が数万枚以上あるときのPCA実行

"""
クエリ画像が数千枚以上ある場合はPCAで圧縮して、高速化(今回はしない)
"""
from sklearn.decomposition import PCA
pca = decomp.PCA(n_components = 700)
pca.fit(features)

# 主成分分析の結果に基づき、次元削減する。
ex_features = pca.transform(features)
print('extracted feature shape {}'.format(ex_features.shape))

E = pca.explained_variance_ratio_        # 寄与率
cumsum_explained = np.cumsum(E)[::-1][0]  # 累積寄与率

print('累積寄与率{}'.format(cumsum_explained))
# extracted feature shape (928, 700)
# 累積寄与率0.9828264117240906

・特徴抽出後、PCAして(簡易なモデルを使い)再訓練する

# PCA
pca = decomp.PCA(n_components = 700)
pca.fit(features)
ex_features = pca.transform(features)

# ラベル
ext_label = np.r_[new_bl, nikel, adidasl, vansl]
n_labels = len(np.unique(ext_label))  
test_y=np.eye(n_labels)[ext_label]
test_y=np.reshape(test_y, (len(test_y), 4))

# train
ext_model=models.Sequential()
ext_model.add(Dense(2048, input_dim=400, activation='relu'))
ext_model.add(Dense(1024, activation='relu'))
ext_model.add(Dense(512, activation='relu'))
ext_model.add(Dense(256, activation='relu'))
ext_model.add(Dense(4, activation='softmax'))
ext_model.compile(optimizer=SGD(lr=0.01, momentum=0.9, decay=0.001, nesterov=True),
                    loss='categorical_crossentropy', metrics=['accuracy'])
history=ext_model.fit(ex_features, test_y, epochs=20, callbacks=callback)
"""
loss: 1.2685 → loss: 0.0013
acc: 0.4073 → acc: 1.0000
"""


4.faissで類似画像検索

あとはクエリ画像と似てる画像をデータセット画像から検索する。
jupyter上で試すので、まずfaissのインストール

$ conda install -c pytorch faiss-cpu

クエリ画像とデータセット画像と同じ次元数にする(dim=1536)。
あとは類似画像検索を実行。

import faiss
import collections

dim = 1536
index = faiss.IndexFlatL2(dim)   
index.add(features)

# Search
candidate=10
for q in query:
    D, I = index.search(np.expand_dims(q, axis=0), candidate)
    for i in I:
        print(ext_label[i])
        c = collections.Counter(ext_label[i])
        print(c)

パーフォマンス結果は以下の感じ
クエリ画像、そのラベル、そのスニーカータイプ

f:id:trafalbad:20190306221251p:plain
query_label:[1, 2, 3, 3, 1, 2]

query images namenike.jpeg, adidas2.jpeg, vans2.jpeg, vans1.jpeg, nikes.jpeg, adidas.jpeg

・各クエリ画像の検索結果

# candidate(検索数)=1のとき
[1]
Counter({1: 1})
[2]
Counter({2: 1})
[3]
Counter({3: 1})
[3]
Counter({3: 1})
[1]
Counter({1: 1})
[0]
Counter({0: 1})

# candidate=10のとき(左から類似してる順)

[1 1 2 0 2 2 1 1 0 2]
Counter({1: 4, 2: 4, 0: 2})
[2 2 2 0 2 2 2 2 2 2]
Counter({2: 9, 0: 1})
[3 3 3 3 3 2 3 3 3 3]
Counter({3: 9, 2: 1})
[3 1 3 3 3 1 3 3 1 3]
Counter({3: 7, 1: 3})
[1 2 0 3 0 1 1 1 1 0]
Counter({1: 5, 0: 3, 2: 1, 3: 1})
[0 1 3 3 3 1 2 2 0 0]
Counter({0: 3, 3: 3, 1: 2, 2: 2})

スニーカーがドアップのやつはちゃんと検索できてる。最後のスニーカーが2枚写ってるadidasのは、

・データセットに含まれてない

・データセットが足りないか
のどちらかで、多分前者。

結果的に、前回の精度が10%〜50%くらいの画像を、ほぼ間違いなく検索できてる。



画像分類はend-to-endで学習したら終わりだと思ってた。けど「特徴抽出、ファインチューニング、転移学習」などをすることで、より精度が向上しそう。end-to-endで学習することしか画像分類手法としては知らなかったので、今回はいいアウトプットになった。


追記:GlobalAveragePooling2Dでの精度再検証
GlobalAveragePooling2Dは平均プーリング演算で、maxPoolingよりも、メモリ消費量が少なくて、識別率も同じか、少し上回る精度らしい。

特徴抽出をGlobalAveragePooling2Dに置き換えて、faissでの精度を再検証してみた。

・特徴抽出部分でGlobalAveragePooling2Dに変える

output = GlobalAveragePooling2D()(feature.output)
model = Model(inputs=train_model.input, outputs=output)

print(model.output_shape)
model.summary()

"""
# Total paramsはmaxPoolingと変化なし

conv_7b (Conv2D)                (None, 3, 3, 1536)   3194880     block8_10[0][0]                  
_____________________
global_average_pooling2d_1 (Glo (None, 1536)         0           conv_7b[0][0]                    
============
Total params: 54,332,128
Trainable params: 54,274,656
Non-trainable params: 57,472
"""


・faissでcandidate=10で再検証

[1 0 1 3 2 1 1 0 2 2]
Counter({1: 4, 2: 3, 0: 2, 3: 1})
[2 2 2 2 2 2 2 1 2 0]
Counter({2: 8, 1: 1, 0: 1})
[3 3 3 3 3 3 3 3 3 3]
Counter({3: 10})
[3 3 1 3 3 1 3 3 3 0]
Counter({3: 7, 1: 2, 0: 1})
[2 1 1 0 1 0 2 0 0 0]
Counter({0: 5, 1: 3, 2: 2})
[2 0 3 0 3 1 3 2 3 2]
Counter({3: 4, 2: 3, 0: 2, 1: 1})

query_label:[1, 2, 3, 3, 1, 2]
f:id:trafalbad:20190306221251p:plain


maxPoolingで間違えてた、「最後の画像=Adidasの2枚写ってる画像」が正確に判断できてる。

代わりに、最後から二番目のnikeの画像が間違えてる、多分ロゴマークが鮮明でないからだと思う。でも上位3つ出す使い方すれば、当ってる事になるし、GlobalAveragePooling2Dの方がパフォーマンス的にベターみたい。

InceptionResNetV2も本来、使ってるのはMaxPoolingではなくGlobalAveragePooling2Dだし、本来の精度出すにはGlobalAveragePooling2D使った方がいい。



参考サイト
Facebook Researchのfaissで類似検索
Deep Features と Faiss

kubenetesでDjangoのアプリをデプロイする手順と作業ログ(on GKE)

pythonフレームワークでflaskが人気みたいだけど、せっかくDjango勉強したし、フレームワーク的に仕組みも同じなので、どうせならDjangoで作ったアプリをGKE上でデプロイしようと思い、やってみた。

前回、ローカルで画像の予測ラベルを表示するDjangoのアプリを作ったので、それを使用。

これに、redisとceleryのworker処理を加えた。ローカルで動かすとredis 用、worker用、Django用に3つのターミナルを使って処理を行う。

f:id:trafalbad:20190216004311p:plain

ローカルではちゃんとworker処理が行われて予測ラベルが表示される。worker部分は本題でないので詳細はコードを参照。

やったことはdjango_redisなるライブラリを使って、だいたい
このサイト


celeryチュートリアル
を参考にして作った。


けど、worker処理ありのDjangoをGKE上でデプロイしようとすると、redis用のpodを作るあたりでうまくいかない。

なのでworker処理なしの、前作った普通の画像ラベル予測用のDjangoアプリをGKE上でデプロイすることにした。


GCPでのDjangoデプロイの全体像
f:id:trafalbad:20190330134444j:plain


そのデプロイ手順と作業ログを書いていく。

目次
1.DjangoをGKE上でデプロイする

2.workerジョブのDjangoをGKE上でデプロイしようとした時の作業ログ


f:id:trafalbad:20190215203319j:plain

1.DjangoをGKE上でデプロイする

worker処理のない普通のDjangoアプリをデプロイ

worker処理を行うときは、フロントエンドに直結するファイルのコードに、worker処理用のdelayメソッドを使う。
今回、フロントエンドに直結するのはmain.pyというファイル。worker処理ありとなしのコードは以下の通り。

・main.py(worker処理なし)のdelayメソッド部分

from image_pred.tasks import add_numbers   # workerから関数の呼び出し

def pred(img_path):
    x=np.asarray(Image.open(img_path))
    x=np.resize(x,(150,150,3))
    x = np.expand_dims(x, axis=0)
    image = preprocess_input(x)
    image_dump=base64.b64encode(image.dumps()).decode()
    id = add_numbers(image_dump)
    return id

・main.py(worker処理あり)のdelayメソッド部分

from image_pred.tasks import add_numbers # workerから関数の呼び出し
def pred(img_path):
    x=np.asarray(Image.open(img_path))
    x=np.resize(x,(150,150,3))
    x = np.expand_dims(x, axis=0)
    image = preprocess_input(x)
    image_dump=base64.b64encode(image.dumps()).decode()
    id = add_numbers.delay(image_dump)
    time.sleep(70) # 70秒待つ
    ids=id.result
    return ids


delayメソッドを使うか、使わないかだけで、他のworker用のceleryファイルはそのままにしてある。



GKE上でデプロイするためにローカル用のDjangoの変更点

GKE上でデプロイするにはGoogleのチュートリアルを参考にして、足りないところをDjangoアプリのサンプルコードを使って改良した。

これをもとに自分のDjangoのコードで変更したところを列挙してく。

Djangoのproject名:image_pred(チュートリアルの場合、mysite)
アプリ名:myapp(チュートリアルではpolls)

・DockefileのCMDコマンドを変更

CMD gunicorn -b :$PORT image_pred.wsgi



・polls.yamlの内容を変更

・metadata.name
・metadata.labels.app
・spec.metadata.app

の三ヶ所をpollsからmyappに変更



・必要ファイルの必要箇所を追記・変更

・polls.admin.py

・mysite.urls.py

・mysite.settings.py

の内容を追記変更



・myapp/migrations/0001_initial.pyを追加 & 内容変更

githubのpolls/migrations/0001_initial.pyと同じように、0001_initial.pyのmygrations.AddField()内の以下の部分を変更

"to='polls.Question')"
# 以下に変更(pollsを自分のアプリ名に変更する)。

"to='myapp.Question')"


このファイルがないと

$python manage.py makemigrations

実行時にうまくいかない。


他は足りないファイルを追加したりした。


GKE上でデプロイする手順

あとはGKE上でデプロイするための手順を書いてく。
リージョン:us-central1 、ゾーン:us-central1-c、インスタンス:myapp-bccプロジェクト名:sturdy-willow-167902、connectionName の値:sturdy-willow-167902:us-central1:myapp-bcc

1.ローカルにDjango アプリを作成 (at local)

2.ローカル環境の設定

# SQLプロキシをインストールする at local(macos64ビット)
$ curl -o cloud_sql_proxy https://dl.google.com/cloudsql/cloud_sql_proxy.darwin.amd64 && chmod +x cloud_sql_proxy

# Cloud SQL(PostgreSQL) インスタンスを作成する at GKE
"""
SQL→インスタンス作成→PostgreSQL選択→インスタンスID myapp-bcc パスワードk8s リージョン us-central1 設定オプションから[マシンタイプとストレージの設定]でcpu(コア数) 2とメモリ→[作成]
"""

# connectionNameの確認
"""
「SQL」をクリック、インスタンスの一覧が表示されるので、使用するインスタンスをクリック、概要の下にある「インスタンス接続名」があるので確認(sturdy-willow-167902:us-central1:myapp-bcc)
"""

# 新しい Cloud SQL ユーザーとデータベースの作成 at GKE
・DB(gcloud sql databases create [DATABASE_NAME] ―instance=[INSTANCE_NAME])
$ gcloud sql databases create redis --instance=myapp-bcc

・DBユーザーを作成する(by GCU)
"""
GCPメイン画面で、[SQL]を選択、データベースを作成したいインスタンスをクリック、ユーザー」タブをクリック、「ユーザーアカウントを作成」をクリック、「ユーザー名」(bc)と「パスワード」(k8s)を入力して「作成」
"""

3.サービス アカウント(ユーザー & パスワード)を作成する at GKE

「IAMと管理」の中から「サービスアカウント」を選択、上部の「サービスアカウントを作成」をクリック


1.サービスアカウント名(bc)
2.役割に「Cloud SQL クライアント」を設定して「続行」をクリック
3.「キーを作成」をクリックし、キーのタイプにて「JSON」を選択して「作成」をクリックと認証ファイルがダウンロードされる
4.キーが表示されていることを確認して、「完了」をクリック
5. サービスアカウントの一覧に戻るので、作成したアカウントが存在することを確認する
6.JSONキーをmanage.pyと同じパスに移動させる

# Cloud SQL インスタンスを初期化する at local
# プロキシの開始コマンド
$ ./cloud_sql_proxy -instances=インスタンス接続名=tcp:5432 -credential_file=key.json

# 例
$ ./cloud_sql_proxy -instances=sturdy-willow-167902:us-central1:myapp-bcc=tcp:5432 -credential_file=sturdy-willow-167902-beaf39d3bdbd.json


4.データベースを構成する at local

# ローカルでDBにアクセスするため環境変数を設定(DBユーザー名作成時の「ユーザー名」(bc)と「パスワード」(k8s)を使う)
$ export DATABASE_USER=bc && export DATABASE_PASSWORD=k8s

5.GKE を構成する at local

# マニフェストファイルpolls.yaml で、以下のところを変更
your-project-id =>実際のプロジェクトID(プロジェクトID:sturdy-willow-167902)

your-cloudsql-connection-string => connectionName の値: sturdy-willow-167902:myapp-bcc


6.ローカルコンピュータでアプリを実行する

# 孤立した Python 環境を作成し、依存関係をインストール(at local)
$ brew update && brew install mysql   # mysqlをインストール
$ virtualenv venv && source venv/bin/activate && pip install -r requirements.txt


# モデルを設定するために Django の移行を実行 at GKE
"""
=>Cloudsql APIを有効にする
APIとサービス -> ダッシュボード -> APIを有効にする -> 「cloud sql api」 で検索 -> 有効にするをクリック

=> ローカルのDjangoファイルのmysite.settings.pyのDATABASEのNAMEをredisに変更 at local
"""

# celery用のデータベースマイグレーション 
$ python manage.py migrate django_celery_results

# マイグレーションファイルを作成 & 実行(データベースの同期)
$ python manage.py makemigrations && python manage.py migrate 

# 試しにローカル ウェブサーバーを起動
$ python manage.py runserver  # 「http://127.0.0.1:8000/myapp/」 にアクセス & Ctrl+Cキーで停止

参考
Cloudsql APIを有効にする
django_celery_results用のマイグレーション



7.Django 管理コンソールを使用する at local

# スーパーユーザー(管理画面を利用できる管理者ユーザー)の作成
$ python manage.py createsuperuser

# 実行
$ python manage.py runserver
# 「http://127.0.0.1:8000/admin/」にアクセス

# UsernameとPasswordを入力し、管理サイトにログイン

8.アプリを GKE にデプロイする at GKE

# Cloud Storage バケットを作成 & 公開
$ gsutil mb gs://polls-images && gsutil defacl set public-read gs://polls-images

# => gcloudコマンドインストールし、ローカルで実行 (gsutil rsync -R static/ gs://<your-gcs-bucket>/static)&  staticフォルダにあるmyappに移動
$ cd myapp
$ gsutil rsync -R static/ gs://polls-images/static

# mysite/settings.py で、STATIC_URL の値をこの URL「http://storage.googleapis.com/<your-gcs-bucket>/static/」に設定
=http://storage.googleapis.com/polls-images/static/

# GKE クラスタを作成 at GKE
$ gcloud container clusters create myapp --scopes "https://www.googleapis.com/auth/userinfo.email","cloud-platform" --num-nodes 4 --zone "us-central1-c"


# GKEクラスタ操作権限get(kubectl が正しいクラスタとやり取りするように構成されていることを確認)
$ gcloud container clusters get-credentials myapp --zone "us-central1-c"


# シークレットの作成=> JSONキーアップロード & アクセス用にシークレットを作成
$ kubectl create secret generic cloudsql-oauth-credentials --from-file=credentials.json=sturdy-willow-167902-beaf39d3bdbd.json

# データベース アクセスに必要なシークレット作成
$ kubectl create secret generic cloudsql --from-literal=username=bc --from-literal=password=k8s

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


# Cloud SQL プロキシのパブリック Docker イメージを取得 at GKE
$ docker pull b.gcr.io/cloudsql-docker/gce-proxy:1.05

# From here command run at local

# Docker イメージを作成(each Dockerfileを移動)
$ docker build -t gcr.io/sturdy-willow-167902/myapp .

# 認証ヘルパーとしてgcloud を使用するようにdockerを構成。GCRにイメージを pushできる
$ gcloud auth configure-docker

# docker push
$ docker push gcr.io/sturdy-willow-167902/myapp

# GKE上でpod.yamlをアップロード at GKE
$ kubectl create -f myapp.yaml


# 動作確認 at GKE
$ kubectl get pods or $ kubectl describe pod <your-pod-id>
$ kubectl get services myapp

# 'http://[EXTERNAL-IP]/myapp/'にアクセス。

ちゃんとローカルと同じくデプロイできた。

f:id:trafalbad:20190216133734p:plain


2.worker処理ありのDjangoをGKE上でデプロイしようとした時の作業ログ

worker処理ありのDjangoアプリ(ローカルでworker(celery)、redis、Django用にターミナルを3つ使って行うやつ)をGKE上でデプロイしようと思ったけど、GKE上でredisへのアクセスがうまくいかないため、「GKE上でredisを使うためにやった作業」のログ。

ちなみにcelery用ファイルはimage_predフォルダ内に入れてあるので、celeryのworker起動用のファイルは以下の通り。

celery.yamlcelery_docker/Dockefile、run.sh、celery.py


実験1.Dockerfileでapt-getでredis-serverインストール & CMDで起動


エラー1:なぜか、レスポンス返ってこない。

エラー2:Dockefile一つにCMDコマンドでredis-server、celery、Django3つ一気に起動するのはNG。
多分、ローカルでターミナル3つ必要だったから、Dockerでも一気に3つ起動は無理だからだと思う。



実験2.redisインスタンスを使用する(k8sクラスタはipエイリアスとそうじゃないの両方試した)


Googleチュートリアル通りにやった前の記事を参考に、redisインスタンスを設定し、「redis://10.0.0.3:6379」にアクセス。

エラー1:IPの範囲設定を前の記事通りすると、サーバにアクセスできない

エラー2:IP範囲設定せずに、configmap使ったらアクセスできたものの、結果が返ってこない。おそらくredisインスタンスにアクセスできてない



実験3.redisクラスターの使用


以下の手順でredisクラスターを作成

# githubからclone
$ git clone https://github.com/sanderploegsma/redis-cluster

# GCPにファイルアップロードし、起動
$ kubectl apply -f redis-cluster.yaml

# 下記コマンド実行してredisクラスターを作成
$ git clone https://github.com/antirez/redis.git && cd redis && make && sudo make install
$ apt-get update && apt-get install ruby vim wget redis-tools && gem install redis && wget http://download.redis.io/redis-stable/src/redis-trib.rb && chmod 755 redis-trib.rb

# podが6つできるので、実行
$ kubectl exec -it redis-cluster-0 -- redis-cli --cluster create --cluster-replicas 1 $(kubectl get pods -l app=redis-cluster -o jsonpath='{range.items[*]}{.status.podIP}:6379 ')

# 本来、マニフェストファイル内では、cluster IPがredisのIPになる

エラー:なぜか今まで発生してないpandasのエラーがでてきて、解決策講じたものの通用しないので中断


この記事だと、一番現実的なのはredisクラスターかなと思った。k8s用のredisクラスターは事例がgithubにたくさんある。

まあ仕組み理解しないとこの問題は解決しないなと感じた。

今回は普通のDjangoアプリケーションをGKE上でデプロイするまでの内容 & 手順と、ちょっとした作業ログのまとめを備忘録もかねて書いた。



個人的メモ

Djangowsgi.pyファイルに関係する「サーバソケット」について

ソケットは一般にクライアントとサーバーの対話で使用される。
クライアントとサーバーとの間のデータ交換は、 クライアントがソケットを経由してサーバーに接続しているときに行われる。



・gunicornとその-bオプション(-b BIND, --bind=BIND )について

Gunicornとは、Python製のWSGIサーバ(WSGI(Web Server Gateway Interface)でサポートされているWebサーバとWebアプリケーションをつなぐサーバ)。

pipでインストールして、

$ gunicorn myproject.wsgi

とすれば、走る。

− bオプションはバインド(結合)するサーバソケットを指定する。

サーバソケット(BIND)は「$(HOST)」、「$(HOST):$(PORT)」 or 「unix:$(PATH)」で指定。IPは有効なものを指定。


・dockerイメージ全削除コマンド

$ docker images -aq | xargs docker rmi


・プロキシサーバについて

Webブラウザ)の代わりに、ホームページにアクセスしてくれるコンピュータ(サーバ)のこと。

f:id:trafalbad:20190325114444p:plain

ここでは、kubenetesとローカルマシンを繋いでくれる中継地点の役割を果たしてる

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)