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

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

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

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

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

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

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

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


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


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

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

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

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

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



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/'にアクセス。

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


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 )について

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


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

$ docker images -aq | xargs docker rmi

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


AWSコマンド


Q14.s3にファイルのアップロード

# tar cf - ディレクトリ名 | gzip --best | aws s3 cp - s3://バケット/ファイル名

# ex
$ tar cf - model_tar | gzip —best | aws s3 cp - s3://sage/model.tar.gz

プログラマーの目の酷使・疲れを回復させるオススメの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は下の図。どうもこのサイトによるとメルカリでマイクロサービスとして運用されてるらしい  




このMLopsのk8sの部分だけ(下図)作成してみた。




・作成手順
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で中断。けど、せっかくなのでアウトプットしたログを記事にまとめておこうと思う。

構築に使用したコードとかファイルはgithubにあげてあリます。

目次
・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にアクセス」をクリックし、「起動」で開ける。





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

# 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を作成。

今回は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))

Amazon sagemakerで機械学習基盤(MLops)のベース環境構築手順・使い方まとめ

機械学習基盤(MLops)をkubernetesで構築してたけど、amazon sagemakerを使ったら、kubernetesよりかなり簡単に構築できた。

今回は、外部で学習したモデルでエンドポイント作成までやってみた。だいたい、MLopsをAmazon sagemakerで構築する前段階くらい。それでもかなりエラーでハマったので、その手順とかしっかりまとめてく。

目次
1.Amazon sageMakerについて

2.AWSコマンドの設定からECRにDockerイメージpush

3. ノートブックインスタンスでデプロイとエンドポイント削除まで

4.外部の訓練済みモデルのエンドポイントの作成

5.最後に & sagemakerいじってて気づいたことメモ


1.Amazon sageMakerについて

Amazon sageMakerは「開発、学習、デプロイ」の3つのフェーズに分かれてる。


それぞれのフェーズで、用途に特化した専用インスタンスを利用し、従来の開発環境より、MLopsをかなり簡単に構築できる。


開発フェーズ →Notebookインスタンス
最初にノートブックインスタンスはプライマリリソースと呼ばれ、ノートブックインスタンスを作成したアカウントに権限が与えられるっぽい

学習フェーズ → trainingインスタンス
外部学習モデルのデプロイにはいらないと思ってたら、必要だった。

デプロイフェーズ → Hostingインスタンス

これも実はトレーニングジョブでやる必要があって、デプロイフェーズが終わった後に、外部で学習したモデルのエンドポイントを作成できる。

まず、ノートブックインスタンスを作成、そのjupyter上でデプロイ、エンドポイントの削除までする。それで最終的に学習済みモデルのエンドポイントを作成。

文字だとややこしいので、順にやってくみた。



2.AWSコマンドの設定からECRにDockerイメージpush

まずはサイト通りにコンソールで定番のAWS CLI(AWSコマンド)を利用できるようにする。

$ aws configure


で「アクセスキー、シークレットアクセスキー、リージョン、json」を設定。

次はAWSのホーム画面からECRを選び、ECR画面上でレポジトリを作成。
ECRのリージョンはオハイオ(us-east-2)。
今回はGUIで「image」という名前のレポジトリを作成



1.dockerのインストール

まずdockerにログインコマンドを発行する。

そのためにdockerコマンド(docker)をインストール。公式サイト通りにdockerをインストール。

$ docker version

で色々出てくればOK

$ docker version
Client: Docker Engine - Community
 Version:           18.09.0
 API version:       1.39
 Go version:        go1.10.4
 Git commit:        4d60db4
 Built:             Wed Nov  7 00:47:43 2018
 OS/Arch:           darwin/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          18.09.0
  API version:      1.39 (minimum version 1.12)
  Go version:       go1.10.4
  Git commit:       4d60db4
  Built:            Wed Nov  7 00:55:00 2018
  OS/Arch:          linux/amd64
  Experimental:     false



2.dockerイメージ作成&ECRにpush

下記手順でECRにアクセス。dockerイメージを作成して、ECRへpush

$ docker build -t <イメージ名> . (今回はイメージ名=pred)
# コンテナを起動して、Exitしないことを確認
$ docker run --rm -itd pred


# ECRへログイン(権限が付与されてれば、クソ長いdocker loginコマンドが出てくるので、それをコピペして、コンソールで実行。)
$ aws ecr get-login --no-include-email --region us-east-2
$ docker login -u AWS -p= https://802052389487.dkr.ecr.us-east-2.amazonaws.com


# タグ付け & レポジトリへpush
$ docker tag pred <レポジトリURL>
$ docker push <レポジトリURL>


リージョン名はSageMaker と同じリージョン(ここではオハイオ)にしないとだめ。つまり、sagemakerとECRのリージョンは同じ必要がある。


ちなみにDockerfileの中身はこんな感じ。tensorflow用のdockerfileを参考にしてある。
デプロイ用コードはもちろん、学習済みモデルも入れてある。

FROM ubuntu:18.04
WORKDIR /app 
COPY *.py /app/ 
COPY grids.txt /app/ 
COPY inception.h5 /app/inception.h5  # 学習済みモデル
RUN apt-get update && apt-get install -y --no-install-recommends \
        build-essential \
        curl \
        libfreetype6-dev \
        libhdf5-serial-dev \
        libpng-dev \
        libzmq3-dev \
        pkg-config \
        python \
        python-dev \
        rsync \
        software-properties-common \
        unzip \
        && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

RUN curl -O https://bootstrap.pypa.io/get-pip.py && \
    python get-pip.py && \
    rm get-pip.py
RUN pip install --upgrade pip
RUN pip install -U numpy flask scikit-image 
ENTRYPOINT ["python", "app.py"] 
EXPOSE 8080


今回はあくまでベース基盤の構築方法なので、学習済みモデルと基本的なコードのみ。


3. ノートブックインスタンスでデプロイとエンドポイント削除まで

AWSのホーム画面からAmazon sagemakerへ移動し、左の「ノートブックインスタンス」を選択し、作成。

適当なs3バケットを指定すると専用のIAMロールが作成される。
juputerを開いて、外部で公開されてるコードでfit(), deploy()メソッドでデプロイとエンドポイントの削除まで。

ちなみにjupyterノートブック上でも、コンソール同様にpipでのinstallができた。

# 前準備
import os
import boto3
import sagemaker
from sagemaker.mxnet import MXNet
from mxnet import gluon
from sagemaker import get_execution_role
sagemaker_session = sagemaker.Session()
role = get_execution_role()
# pipでインストール
%%sh 
pip install mxnet

# データセット用意
from cifar10_utils import download_training_data
download_training_data()
#  'sagemaker-us-east-2-947675573067'というバケット作ったあと実行
inputs = sagemaker_session.upload_data(path='data', key_prefix='data/gluon-cifar10')
print('input spec (in this case, just an S3 path): {}’.format(inputs))
!cat ‘cifar10.py’  
m = MXNet("cifar10.py", 
          role=role, 
          train_instance_count=1, 
          train_instance_type="ml.p2.8xlarge",
          hyperparameters={'batch_size': 128, 
                           'epochs': 50, 
                           'learning_rate': 0.1, 
                           'momentum': 0.9})

# train
m.fit(inputs)

# デプロイ
predictor = m.deploy(initial_instance_count=1, instance_type=‘ml.m4.xlarge')

# エンドポイント削除
sagemaker.Session().delete_endpoint(predictor.endpoint)


ちなみにコードはこのサイトのやつを参考にした。
qiita.com

github.com


cifar10.pyのはこのコード



これでノートブックインスタンスから、エンドポイントの設定までできてる。

あとは外部で訓練したモデルのエンドポイントを作成するだけ。



4.外部の訓練済みモデルのエンドポイントの作成


左の「トレーニングジョブ」で作成したジョブを選択し、上の「アクション」から「モデルの作成」をクリック。


モデル名を入れ、「モデルアーティファクトと推論イメージの場所を指定」を選択。

外部トレーニングしたモデルやコードを含んだDockerイメージのURLを「推論コードイメージの場所」に入力。


ちなみにオプションの「アーティファクトの場所」は指定しない。

そのあと作成したモデルから「エンドポイントの作成」を選択。

「エンドポイント名」入力後(今回は’inception’とつけた)、「既存のエンドポイント設定の使用」から、さっき作ったやつを指定して、エンドポイントの作成。

下のようにエンドポイントが「InService」になってれば、エンドポイント完成。


構築したモデルの本番環境はAmazon sagemaker上でデプロイできる。

参考サイトではテスト環境のための、postmanというツールをダウンロードして、デプロイしてた。

Postmanは、認証、テスト、ドキュメント作成、バージョン管理とかテスト環境用のデプロイ向けらしい。


本番環境は、Amazon sagemakerとEC2でChaliceAWS SDK for Python (Boto3)を使ってデプロイできるらしい



5.最後に

これで、外部で訓練したモデルのエンドポイントを作成できた。MLopsのベース基盤構築にかなり近づいた感がすごい。

あとはflaskあたりでアプリケーション作って本格的にデプロイしたいけど、まだできてないのでそこらへんはまた次の機会にまとめたい。



sagemakerいじってて気づいたことメモ

・「Amazon Elastic Inference」とは
GPU をSageMaker のエンドポイントにアタッチ(使用)し、高速化する

・ARNはノートブックインスタンスからエンドポイントまで同じ
ARNについて

・ノートブックインスタンスはプライマリリソースで初めに必ず作成する
→作成したアカウントに管理権限があるっぽい。他はサブリソース。

・ECR とsagemakerとs3のリージョンは同じ必要あり

・EC2のアクセスキーの作り方

1.キーペア発行

2.移動
$ cd Downloads

3.EC2へコピー
$ cp sagemaker.pem.txt ~/.ssh/<任意のname(sagemaker_ops)>

4.アクセス許可付与
$chmod 400 ~/.ssh/sagemaker_ops

5.試しにroot@でログイン(リージョン=オハイオで、linuxインスタンス)
$ ssh -i ~/.ssh/sagemaker_ops root@<EC2のiPv4パブリックIP>
>>> Please login as the user "ec2-user" rather than the user “root”.

6.アクセス
$ ssh -i ~/.ssh/sagemaker_ops ec2-user@<EC2のiPv4パブリックIP>

参考サイト:Amazon SageMakerを使った学習済みモデルの流用〜ここどこサーチのデプロイまで

アソシエーション分析を使ったレコメンドアルゴリズム作成-機械学習・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のようにいろんな特徴量を含めれるようになってるので、特徴量はユーザーデータとアイテムデータ関係を使った。

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

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



分析例

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


端末はスマホが圧倒的に多かった



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

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



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

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


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


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


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

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

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



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

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





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


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


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


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

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


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

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


協調フィルダリング



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


(参考→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を使う複合アルゴリズムを使ってたのを参考にした

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))


レコメンド結果


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


レコメンドした商品内容は下図



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





最後に


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

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





気づいたこと、テクメモ



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

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角形の形。


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


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

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

として色を検出する。



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

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値のリスト

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

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






クラスタリングで色検出



まず、下の画像をopencvでrgbで読み込む。

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








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



算出した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値でも青と特定できる)。





実験



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

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


クラスタリング結果と検出した色名は以下の通り


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

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




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