読者です 読者をやめる 読者になる 読者になる

uyamazakのブログ

仕事中の問題と解決メモ。PythonとGoogle Cloudがメイン。bizoceanで新規事業の企画と開発担当。 BigQueryを使ったビッグデータ収集・解析・リアルタイム処理プロジェクト進行中 https://github.com/uyamazak/oceanus

Googleのslack競合「Hangouts Chat」に期待

弊社ではG Suiteを使っているので、Googleがslackっぽいの出してくれればと思ってましたが、やってくれるようです。


www.itmedia.co.jp


slackは今も使ってますが、たくさんメッセージを使うのには有料プランが必要なので、月額がかかってしまいます。G Suiteに含めてくれれば助かります。

G Suiteで会社のユーザー管理をしているので、Google側でまとまれば助かります。


あとはSlack並にbotが簡単に作れるようになっていれば文句ありません。

kubectlでimageの変更をコマンドラインで行う

Google Container Engineでのデプロイは、今までは

kubectl edit deploy {name}

でエディタを開き、手でimageの値を書き換えていたけど、面倒になったので自動化を考えた。

マスターのバージョンは1.4.8。

editではなくpatchというコマンドがあった。

kubectl patch - Kubernetes


不要な部分を削除して下記の様な形で成功した。
nameを入れないとエラーになるのでハマった。

既存のnameにしないと、別名で複数起動してしまう。

kc patch deployment redis-pd -p '{"spec": { "template": { "spec": {"containers": [{"name":"appname", "image": "asia.gcr.io/project-id/appname:tag"}]}}}}'

これを更新用シェルスクリプトに含めて、アプリ名、タグ名を入れればpushとデプロイまでコマンド一発でできるようになった。

Google Container Engineでtype=LoadBalancerでexposeすると、毎月2000円~程度かかるので注意

GKEを使っていて、利用料金を見ていたら、作った覚えのないロードバランサーの金額が入っていた。

Compute Engine Network Load Balancing: Forwarding Rule Minimum Service Charge in Japan: 672 時間 [Currency conversion: USD to JPY using rate 115.1]

他のプロジェクトではHTTPS Load Balancerを使っていて、それで料金が掛かっているのは把握していたけど、該当のプロジェクトでは思い当たる節がなかった。

該当のプロジェクトでは、GKEで開発用途のプロキシでnginxを動かしているのみ。


下記のURL(project-nameはそれぞれ)で確認すると、自動的にTCPロードバランサーが作成され動いていた。

https://console.cloud.google.com/networking/loadbalancing/list?project=project-name

最初なんだこれ、いらねと思って消したらアクセスできなくなり焦った。


どうやら、

kubectl expose deployment deployment-namne type=Loadbalancer

でserviceを作成をすると自動的に設定されるらしい。

これには「負荷分散とプロトコル転送」の料金がかかり、

Google Compute Engine Pricing  |  Compute Engine Documentation  |  Google Cloud Platform


アメリカ、EU、台湾は

最初の 5 つの転送ルール $0.025 1 時間あたり

なのに対し

アジア(日本)だけ

最初の 5 つの転送ルール $0.038 1 時間あたり

と1.5倍の料金が請求されるので注意。

日本以外だと月18ドル(0.025 * 24 * 30)、日本だと月27.36ドル。

性能、機能ともに非常に高く、本番用では申し分のない低価格だけど、ほとんどアクセスのない開発用途にはちょっと高いと思う。


TCPロードバランサーとHTTP(S)ロードバランサーの値段は変わらないようなので、HTTPの方を使って、ロードバランサーSSL証明書を載せた方がいろいろ便利な気がしてきた。


Docker + Pythonで設定ファイルを環境変数によって切り替える

oceanusではPythonで書いたアプリケーションをDockerで動かしています。

Dockerを使ったアプリケーションでは、コンテナごとに変わる可能性がある変数は、DockerfileでENVを使って、環境変数にセットして、プログラム側でそれを読み込んで使えます。

しかし、Pythonで書かれた設定ファイルで、dict型だったり入れ子になったリストやタプル、CPUのコア数な動的に変更されるような変数は、環境変数での管理は難しくなります。


そこで、どの設定ファイル読み込むか、ENVで切り替えられるように作る必要があります。

もう少し詳しく言うと設定ファイルを、取得した環境変数を使って動的に読み込んで、さらに内容を展開する必要があります。

Djangoのsettings.pyを起動時に指定できるのも同じでしょうか(未確認)。

共通に読み込む設定ファイルを作る

当たり前ですが設定ファイルは複数のアプリケーション、モジュールから呼び出されます。

もし、設定ファイルを変更する度にモジュール名の変更が必要になってしまったり、動的に切り替える処理をそれぞれに書いてしまうとDRYではありません。


そこで、一つ不変な設定ファイルを作り、アプリケーションは常にそれを読み込めばいい形にします。

例として、下記のように、settings.pyを読み込む不変なファイルとし、読み込む選択肢は例として、my_settngs.py、other_settngs.pyとします。

- app
  - app.py
  - common
   - settings.py
   - my_settngs.py
   - other_settngs.py

app.pyから読み込む場合は下記のようにモジュールとして必要な変数(DATABASE_NAME)を読み込みます

from common.settings import DATABASE_NAME

もちろん、DATABASE_NAMEのような設定はdefault_settings.py、other_settings.pyに宣言し、settings.pyには無し、もしくはデフォルト値を書いておきます。

例として下記のような設定です。

my_settngs.py

DATABASE_NAME = "my-oceanus"

other_settngs.py

DATABASE_NAME = "other-oceanus"

settings.pyから動的に別ファイルを読み込む

まず読み込むモジュール名を環境変数からstr型として取得するようにします。

モジュール名なので、ディレクトリは.で区切り、ファイル名の.pyは不要です。

importが慣れない人は、まずコマンドラインなどで正しく読めるかを試したほうがいいかもしれません。

環境変数名をOCEANUS_SETTINGSとし、これにモジュールパス(例:common.default_settings)を入れます。

Dockerfileでは下記のように書いておき

ENV OCEANUS_SETTINGS common.default_settings

runする時に、-eをつかって

docker run -e OCEANUS_SETTINGS=common.other_settings

pythonで同名の変数に代入。

デフォルトが必要な場合はgetの第2引数でセットしておきます。

次にその文字列を使って動的にimportします。

動的にimportを行うには、__import__()でも可能ですが、importlib.import_moduleが推奨されています。

31.5. importlib — import の実装 — Python 3.6.1 ドキュメント

from os import environ

OCEANUS_SETTINGS = environ.get("OCEANUS_SETTINGS", "common.default_settings")

import_module(OCEANUS_SETTINGS)

これだけはまだdefault_settingsを読み込んだだけで、中身の展開はできていないので

from common.settings import DATABASE_NAME

という形では使えません。

読み込んだ設定ファイルの中身を展開する

そして、その読み込んだファイルにある変数などをすべて展開します。

ここはどうやるかしばらくハマりましたが、下記のページを参考にglobals()を使って、現在のモジュール(common.settings)のグローバル変数に追加することで、うまくいきました。

31.5. importlib — import の実装 — Python 3.6.1 ドキュメント

最終的には下記のような形になります。
settings.py

from os import environ
from importlib import import_module

OCEANUS_SETTINGS = environ.get("OCEANUS_SETTINGS", "common.default_settings")

globals().update(import_module(OCEANUS_SETTINGS).__dict__)

これで、環境変数OCEANUS_SETTINGSを変更して、

from common.settings import DATABASE_NAME

のように読み込めば、common.default_settingsや、common.other_settingsの設定ファイルを読みこむことができます。

この動的読み込み&展開は設定ファイルに限らずいろんな所で応用がききそうです。

実践力を身につける Pythonの教科書

実践力を身につける Pythonの教科書

Google Cloud Datalabに任意のパッケージをインストールする

自社では基本的にBigQueryにデータをぶちこんでいるので、データ解析や、機械学習にはJupyterのGoogleカスタマイズ版であるDatalabを使っています。

cloud.google.com

基本的な使い方はJupyter(IPython)と一緒です。

BigQueryをはじめ、Storage等、Google環境に必要なライブラリは一通り入っていますが、やはり色々やろうとすると、pip installやら、apt-getやらが必要になってきます。


そこで2つ、カスタマイズ方法を紹介します。

使うパッケージは一回だけと思われる場合

コマンドでインストールします。再起動時などには消えるので、それでもかまわない場合。


まずdocker psで、datalabのコンテナIDを取得しておき、下記のコマンドを行う

docker exec {コンテナID} pip install {欲しいパッケージ名}

もしくは、bashなどで入ってからインストールする

docker exec -it {コンテナID} bash

bashが立ち上がるのでpipとかapt-getする。

apt-getの場合は、updateしないと駄目な場合が多いので、bashで入ったほうが楽かも。

永続的に使いたい場合

自分でDockerfileを使って、拡張します。

好きな名前でディレクトリを作ってDockerfile作成

mkdir mydatalab
cd mydatalab
vim Dockerfile

私の場合は下記のような感じ。

FROM gcr.io/cloud-datalab/datalab:local

# install from pypi
RUN pip install --upgrade pip && pip install -q \
bigquery-python \
cython \
oauth2client \
sendgrid \
gensim \
scipy \
networkx \
suds

RUN apt-get update && apt-get install -y \
    gcc

githubからもがんばれば出来る。


ビルド

sudo docker build -t mydatalab:latest

起動

sudo docker run -d -p "8080:8080" \
    -v "/path/to/content:/content" \
    mydatalab:latest

ビルドとか起動とかはシェルスクリプト化しておくと便利

ただCloud Datalabは、Google社内と同じくPython2.7なのでPython3にしかないものを使うときはいろいろと大変そう。まだ困ったことはない。

Dockerエキスパート養成読本[活用の基礎と実践ノウハウ満載!] (Software Design plus)

Dockerエキスパート養成読本[活用の基礎と実践ノウハウ満載!] (Software Design plus)

大規模分散型リレーショナルデータベースGoogle Cloud Spanner来た

以前からGoogleのセミナーなどでは話題に上がっていた、分散RDBがついに公開されました。

これまでトランザクションが必要なDBは分散するのが難しく、注文が集中するECや、ゲームなどでは、RDBボトルネックになっていましたが、もしCloud Spannerが期待通りのものであれば、これまで高負荷DBサーバーの苦労は簡単に解決できそう。

Cloud Spanner | Automatic Sharding with Transactional Consistency at Scale  |  Google Cloud Platform


itpro.nikkeibp.co.jp


jp.techcrunch.com

Word2Vecを使って行動履歴から関連商品やおすすめ商品を出す

bizoceanで集めたデータを使って何かできないかを考えていたら、少し前に話題になったWord2Vecを使って書式のダウンロード履歴を文章として入力すれば、近いベクトルの書式、つまり関連性の高い書式が出せるのでは?と思い試してみた。

おすすめ、レコメンドにも使えそう。

Word2Vecについては下記がわかりやすい。
deepage.net

ただの単語の羅列をn次元のベクトル化してくれるのは、データの用意も簡単な上、いろいろ加工もしやすくて便利。


データのもととなるbizoceanは、請求書、履歴書、契約書などの書式のダウンロードサイト。
www.bizocean.jp

ほとんどが無料だけど、2016年から有料販売もできるようになった。

他のECサイトなどでは、ダウンロードではなく、購入になるだろう。



まず、文章のように、ユーザーごとのDL履歴を1行ずつ、スペース区切りで書き出す。

環境はoceanusを使って、BigQueyにぶち込んだアクセスログを、Google Cloud Datalab(Jupyter)で。

cloud.google.com


SQLは下記のような感じ。

SELECT doc_ids FROM
(
  SELECT
    sid,
    GROUP_CONCAT_UNQUOTED(doc_id, " ") as doc_ids,
    count(doc_id) as count
  FROM
  (
  SELECT
    sid,
    doc_id
  FROM
   ( SELECT 
      sid,
      dt,
      REGEXP_EXTRACT(url, r'/doc/download/complete/([0-9]+)/') as doc_id,
    FROM TABLE_DATE_RANGE(
      {TABLE_PREFIX},
      TIMESTAMP("{FROM}"), 
      TIMESTAMP("{TO}")
    )
    WHERE
      evt = "pageview" AND 
      url CONTAINS "/doc/download/complete/"
      ORDER BY dt ASC
    )
  GROUP BY
    sid,
    doc_id
  )
  GROUP BY
  sid
)
WHERE count > 1


これの実行結果をファイルに書き出す。to_fileで一発。

import datalab.bigquery as bq
query = bq.Query("""上記のSQL""")

query.to_file("donwload_relative_.csv")

csvは下記のようなファイルになる。1つだけでは意味が無いので、2DL以上した人のみを対象にした。3ヶ月程度で20万行弱、7MBのデータになった。

doc_ids
100454 103502 103002
104552 102249 102230 104526
108875 517686 516829 517682 108177 517680 517677 517679
520271 518137 104615 100956 516761 519367 519366 102746 519880 103574
520693 108875 521525 519565 521536 516128 109213 521490 521528 521445 521537 100550 521522 521677 520694 109042 517770 521696 521790 521208 521904 519006 521807 521686 109209 521275 101698 102384 520599 103661 103189 107848 521252 521768 518169
104443 109407 107643 521478 103113 519969 103428 103104 103177 515111 104487
104710 516744 516750 104712 104714
102728 109481 108971 105489 104446
103438 516191
516761 102763 516791
103101 106929 106932 100549 103141 102068 100556
500108 104449 519125 517878 519706 102724 500110
519467 517568 103601 518222 103114 517998 109407 519365 519737 515198 518201 103183 521047 100570 520320 515257 519250 102880
516741 520351
520351 104713
521206 521258 521270 521213 521217 521259 521481 521246
521256 521234 521534 521496 521538 521230 521267 521536 521229 521552 521411 521258 521246 521247 521461 521212 521532 521265 521537 521266 521522 521495 521255 521225 521447 521549 521525 521528 521213 521487 521444 521254 521541 521453 521553 521523 521242 521249 521251 521257 521542 521494 521530 521478
519250 520268 102901 518138
500418 104393 518306
518205 106898 101682 516772 103714 104761 100971
104570 102349
521537 521213 521536 521229 521252 521246 521443 521542 521218 521322 521517 521532 521247 521250 521214 521534 521237
104475 109362
516797 515042 516780 516486 516768
516744 104710 107846 519511 515633 520353 515644 104715
516268 515041 103127 104478 515040 109390 103126
521227 521252 521521 521487 521213 521524 521105 521537 521646 521231 521530 519982
519509 520358
104714 104712
518362 518354
104714 104710 104711

Word2Vecの実装には、gensimを使う。別途pipでインストール。今回はDatalabなので、Dockerfileを使ってインストールしておいた。
詳しいカスタマイズ方法は↓
uyamazak.hatenablog.com



gensim: models.word2vec – Deep learning with word2vec

from gensim.models import word2vec
#data = word2vec.Text8Corpus('donwload_relative_20170213-01.csv')
data = word2vec.LineSentence("donwload_relative_" + filename + ".csv")

ググって出て来る記事だとText8Corpusが使われてるけど、行ごとに処理してくれる?LineSentenceを使う。

モデルの作成。これが重い。

model = word2vec.Word2Vec(data, size=200, window=5, min_count=20, workers=8)

実行マシンは社内のサーバーマシンで
Intel(R) Xeon(R) CPU E3-1230 v5 @ 3.40GHz、メモリ32GBを兼ね備えているけど、20分ぐらいかかる。

モデル作成が終わったらmost_similarを使って、一番近いものを出す。
これは爆速、一瞬で帰ってくる。

200次元とはいえ、単純な行列の計算になるから早いのだと思ってる。

targetに書式IDを入れる。

out=model.most_similar(positive=[target], topn=15)

IDだけだとよくわからないので、書式名が入ってるテーブルから名前を取ってpandasでくっつけた。

一番人気の「請求書003 シンプルな請求書」を入れてみた
請求書003 シンプルな請求書|テンプレートのダウンロードは【書式の王様】

102693 請求書004 0.841390490532
517568 エクセル請求書(窓付封筒、長3対応) 0.830850958824
517868 請求書01 0.827962636948
519366 スタイリッシュな請求書(イエロー) 0.82549983263
102417 請求書006 0.825079739094
520271 自動計算付きの請求書(エクセル) 0.824660122395
102418 請求書012(サービス業用) 0.815610051155
104445 請求書008(サービス業用) 0.815160274506
102748 請求書05 0.814028024673
102419 請求書007 0.808267712593
519880 請求書&送付状 0.795116245747
104615 請求書001 0.793960869312
517422 請求書(窓付封筒、長3対応) 0.793055713177
518137 請求書010 0.787589609623
520984 シンプル請求書_スマホ入力用 0.785955011845


スケジュール表
スケジュール表|テンプレートのダウンロードは【書式の王様】

520286 スケジュール表:ガントチャート式【改】 0.960333704948
521092 工程管理表 スケジュール管理表 2016年3月~ 0.960129082203
521176 工程管理表 スケジュール管理表 2016年9月~ 0.946121633053
516756 Excelで作るA41枚の月間スケジュール管理 0.942580997944
104454 建築業用作業工程表(年間) 0.912206470966
500428 工程管理_04 0.912127077579
521111 2016年度カレンダー 一覧(2016年4月~) 0.886768221855
521186 課題管理表_2016年 0.879617512226
516786 Wordで作るA41枚の月間スケジュール管理 0.879371285439
517912 年間スケジュール2014 0.877403259277
500422 工程管理_02 0.875747382641
521100 2016年度カレンダー 0.862578749657
500426 工程管理_03_製造業 0.861161231995
517999 2014年 年間カレンダー 0.856253266335
104453 建築業用作業工程表(月間) 0.849661827087


ニワトリとカラフルな花飾りの年賀状
ニワトリとカラフルな花飾りの年賀状|テンプレートのダウンロードは【書式の王様】
当たり前だけど、年賀状でもポップなものを入れれば、ポップなものが返ってくる。

521915 富士山と門松の年賀状2017 酉・鳥 0.953142642975
521445 ニワトリと花の年賀状 0.952863097191
521416 ニワトリと富士山の年賀状 0.946253061295
521446 ひよこの年賀状 0.944137215614
521530 楽しい音楽隊 0.94397175312
521225 鶏の家族がかわいらしい年賀状 0.937339186668
521523 虹を渡って行こう! 0.934369027615
521533 お散歩 0.926785349846
521251 木にとまる鶏の年賀状 0.925209403038
521497 ニワトリとヒヨコの年賀状 0.92281216383
521542 (word)酉年をお花で彩る年賀状 0.917050004005
521913 (word)コマ回し名人(酉年) 0.913929462433
521520 ハートの仲良し一家 0.912433743477
521517 水引飾り・酉:jpg 0.910482287407
521524 パンダと体操 0.910432219505

作成したモデルはメモリ上にあるけど、ファイルに書き出しておくこともできる

model.save("donwload_relative.model")
model.load("donwload_relative.model")

モデル作成さえバッチなどで処理してしまえば、2万件近くあっても、瞬時にレスポンスできるのでAPI化も簡単にできそう。


ユーザーの購入履歴、行動履歴に基づいたレコメンドなどを考えていて、難しいものは避けたい方は、こんな感じで気軽にWord2Vecを使うのもありだと思う。


おまけ

networkxを使って、近いものをネットワーク表示してみた。読めない。
f:id:uyamazak:20170214163219p:plain