にほんごのれんしゅう

日本語として伝えるための訓練を兼ねたテクログ

Google Cloud FunctionをPythonで使う

世間ではAWS Lambdaばかり着目されますが、GoogleもCloud Functionと呼ばれるLambdaに相当する機能を提供しています

LambdaがPython,JS,Javaなどをサポートしているのに比べて、Cloud FunctionはJSのみのサポートとなっていています

Python3(PyPy3)をGoogle Clund Functionにデプロイして、実質的にPythonで使えるようにして、いくつかの応用例を示したいと思います

目次

  • A. nodejsでしか動かないはずのCloud FunctionでPythonを使う  
  • B. gcloud-toolのインストール
  • C. コード書いてデプロイする
  • D. リクエストを送ってみる
  • 調査: Cloud FunctionでScraperは使えるか
  • 例: リクエスト送った人のGlobal IPを返すだけの例
  • 例: (ユーザ行動などのIoT情報を取得する)ビーコンのデータを受け取りCloud Strageに格納する
  • 例: Amazon Dash Buttonより便利な、クラウド操作ボタンをスマホに作る
  • E: まとめ

A. nodejsでしか動かないはずのCloud FunctionでPythonを使う 

1. 環境依存がないPyPy3を利用する

色々試した結果、いろんなLinuxの環境で動くように調整されたコンパイル済みで環境依存の少ないpypy3を利用することで、Google Cloud FunctionでPython3を利用できることがわかりました(どうしてもPython3の文法を使いたい主義)

PyPy

$ bzip2 -d pypy3-v5.9.0-linux64.tar.bz2
$ tar xvf pypy3-v5.9.0-linux64.tar
$ mv pypy3-v5.9.0-linux64 {YOUR_GOOGLE_CLOUD_FUNCTION_DIR}

pipの機能を有効化します

$ ./pypy3-v5.9.0-linux64/bin/pypy3 -m ensurepip

2. 動作が期待できるライブラリ

OSがDebianでversionがよくわかっていません、そのため、手元のLinuxなどでコンパイルが必要なライブラリをコンパイルして送っても、動作しないことがあります。

どうしても動作させたいライブラリがある場合はCloud FunctionのLinuxのlibcやインストールされているshared objectを分析調査するスクリプトを別途記述して、確認する必要があります

    1. numpy
    1. requests
    1. BeautifulSoup4

など、PurePythonで記述されたものと、PyPyで正式にサポートされているnumpyなどは動作します

3. PyPy3にライブラリをインストール

pipはサポートされているので、このように任意の(限定はされていますが)インストールすることができます

$ ./pypy3-v5.9.0-linux64/bin/pypy3 -m pip install flask

B. gcloud-toolのインストール

任意のLinuxで動作する方法を示します

何度かこのツールを使っていますが、aptやyumレポジトリを利用するより、直接バイナリをダウンロードして来た方が安定性が良い気がします

Googleからダウンロードすることができます

$ wget https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-170.0.1-linux-x86_64.tar.gz
$ tar zxvf google-cloud-sdk-170.0.1-linux-x86_64.tar.gz?hl=ja
$ ./google-cloud-sdk/install.sh
$ ./google-cloud-sdk/bin/gcloud init
(各種、認証が求められるので、通しましょう)

.bashrcにこの記述を追加すると、相対パスを入力しなくても使えます

PATH=$HOME/google-cloud-sdk/bin:$PATH

Cloud Functionはオプション扱いらしく、こうすることで正しくインストールすることができます

$ gcloud components update beta && gcloud components install

Cloud Functionのコードやバイナリを置くbacketを作ります

$ gsutil mb gs://{YOUR_STAGING_BUCKET_NAME}

C. コード書いてデプロイする

ディレクトリの中で作業すると、そのディレクトリの中身全てがGoogle Cloud Functionのコンテナにデプロイされますので、あまり大きなファイルはおけないようです

エントリーポイント(Cloud Functionが呼びされた時に最初に実行される関数)はindex.jsという風になっています。

spawnというプロセス間通信を使うと、このJavaScriptのファイルと一緒にデプロイされPyPy3が実行されて、結果を得ることができます

const spawnSync = require('child_process').spawnSync;
exports.pycall = function pycall(req, res) {
  result = spawnSync('./pypy3-v5.9.0-linux64/bin/pypy3', ['./inspect.py'], {
    stdio: 'pipe',
  });

  if (result.stdout){
    res.status(200).send(result.stdout);
  }else if (result.stderr){
    res.status(200).send(result.stderr);
  }
};

デプロイはこのように行います

$ gcloud beta functions deploy ${YOUR_CLOUD_FUNCTION_NAME} --stage-bucket ${YOUR_STAGING_BUCKET} --trigger-http

D. リクエストを送ってみる

コードをデプロイしたタイミングでapiのURLが標準出力に表示されるので、そのURLを参照すると、Cloud Functionが実行されます

$ curl https://${YOUR_PROJECT}.cloudfunctions.net/pycall

curljsonをポストする例

$ curl -X POST -H "Content-Type:application/json"  -d '{"message":"hello world!"}' https://${YOUR_PROJECT}.cloudfunctions.net/pycall

調査: Cloud FunctionでScraperは使えるか

AWS LambdaではFunctionを実行するたびに、IPなどが変わることがあるので、スクレイパーとしても利用することが期待できるのですが、Google Cloud Functionではどうでしょうか

1000回、Cloud Functionを呼び出して、その時のGlobal IPを調べて、どのような分布になっているか調べました
(Global IPを調べるサイトのAPIの制限で、累積値が1000になっていませんが、IPのレンジはAWSより広くなく、固まっている印象があります。また、やはりコンテナはなんども再利用されているようです)

107.178.232.249 8
107.178.232.247 8
107.178.232.181 7
107.178.236.24 7
107.178.238.51 6
107.178.236.4 6 
107.178.236.8 6 
107.178.232.167 6
107.178.237.16 5
107.178.232.180 4

IPという視点で見ると、効率的に使うことは現時点ではあまり期待できなそうです

例: リクエスト送った人のGlobal IPを返すだけの例

やってて思ったのですが、自分のマシンにscpで外部からデータを持ってこようという時に、いちいちiPhoneに記されたIPアドレス帳を参照していたのですが、コマンドを叩いてverboseを利用するより個人的には、jqなどのコマンドで確認できる方が望ましいと考えています

そのため、リクエスト送信元のheaderをjsonに変換してそのままインデントをつけて返します

index.js

const spawnSync = require('child_process').spawnSync;
exports.reflection = function reflection(req, res) {
  result = spawnSync('./pypy3-v5.9.0-linux64/bin/pypy3', ['./reflection.py'], {
    stdio: 'pipe',
    input: JSON.stringify(req.headers)
  });
  if (result.stdout){
    res.status(200).send(result.stdout);
  }else if (result.stderr){
    res.status(200).send(result.stderr);
  }
};

reflection.py

import json
print(json.dumps(json.loads(input()), indent=2))

デプロイしてクエリを投げてみます

$ sh deploy.sh 
$ curl  https://us-central1-wild-yukikaze.cloudfunctions.net/reflection2

出力結果はjsonフォーマットで、最初から結構見やすい!

$ curl  https://us-central1-wild-yukikaze.cloudfunctions.net/reflection2
{
  "host": "us-central1-wild-yukikaze.cloudfunctions.net",
  "user-agent": "curl/7.55.1",
  "accept": "*/*",
  "function-execution-id": "03jbvskqvfyu",
  "x-appengine-api-ticket": "a140cc827b21f195",
  "x-appengine-city": "arakawa",
  "x-appengine-citylatlong": "35.736080,139.783369",
  "x-appengine-country": "JP",
  "x-appengine-https": "on",
  "x-appengine-region": "13",
  "x-appengine-user-ip": "118.241.189.54",
  "x-cloud-trace-context": "8ab2a49b8cd1c80b068daaafda2c85a1/10677056975691001014;o=1",
  "x-forwarded-for": "118.241.189.54",
  "accept-encoding": "gzip"
}

(ユーザ行動などのIoT情報を取得する)ビーコンのデータを受け取りCloud Strageに格納する

アドテクというか、ユーザのサイト内での回遊情報を調べるのに一般的に、ページのどこまでを視認したか、スクロールしたか、PCなのかスマホなのか、画面のサイズは、ブラウザは、オーガニック検索なのか、直帰率はどうなのか、マウスオーバー情報はどうなのか、といった視点がJavaScriptで取得可能であることは、広く知られたことだと思います

これらの複雑なJavaScriptを受け取り、Cloud Strage(AWS S3のようなもの)に書き込むことができれば、サーバレスで行動ログを測定 -> 保存までできます。  

さらに、DataFlowともプロセスをつなぐことができますので、実質的に、集計項目の設計、JSの実装(これは外部)、デプロイ、測定、分析、施策がEnd2Endでできやすくなって、素早いイテレーションを回せそうで、すごくいいです

こんな感じのEnd2Endで観測、集計、分析までできたらうれしい!

ブラウザ側のjavascriptは割愛します

index.js

header, post, getなどの全てのパラメータをpythonに渡します

const spawnSync = require('child_process').spawnSync;
exports.pycall_gcs = function pycall_gcs(req, res) {
  result = spawnSync('./pypy3-v5.9.0-linux64/bin/pypy3', ['./cloudstrage-push.py'], {
    stdio: 'pipe',
    input: JSON.stringify({'headers':req.headers, 'body':req.body, 'query':req.query})
  });
  if (result.stdout){
    res.status(200).send(result.stdout);
  }else if (result.stderr){
    res.status(200).send(result.stderr);
  }
};

cloudstrage-push.py
googleのCloud Strageに書き込む権限を与えたcredentialファイルと共にdeployして、ユーザやアクセス度(ここは適切だろう粒度で設計する必要があります)でuuidやhashで、blob(Cloud Strageにおけるファイル単位)を作り、書き込んで行くことができます

import os
import zipfile
import json
import uuid
import sys
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = './credentials.json'
raw = input()
try:
  from google.cloud import storage
  from google.cloud.storage import Blob
  client = storage.Client()
  bucket = client.get_bucket('wired-ant')
  print(bucket, file=sys.stderr)
  uuid = '%s.shard'%uuid.uuid4()
  blob = bucket.get_blob(uuid)
  if blob is None:
    print(blob, file=sys.stderr)
    blob = Blob(uuid, bucket)
    source = ''
  else:
    source = blob.download_as_string().decode()
  blob.upload_from_string( source + raw + '\n', content_type='text/plain')
except Exception as ex:
  print(ex)

試しに自分の無料のGCP枠で自分のgithub.ioに入れてやって見ましたけど、期待した通り動作していることを確認しました。   ただ、上書きが一度ロードしてからでないとできないので、何かうまくchunkingする方法を考えている次第です(しないという手もあります)。  

自分の行動ログが正しく書き込まれていることが確認できました(ぼっちっぽい)

例: Amazon Dash Buttonより便利な、クラウド操作ボタンをスマホに作る

Cloud Functionでも、AWS Lambdaでもいいのですが、ハマりどころが微妙にあまりないという制約があります

AWS Lambdaなどを繋ぎに、各種サービスをつないでいっていって価値のあるサービスを創出することが、一つの課題なのですが、単独で使おうとすると、リソース的な制約が大きく、機械学習も難しい(少なくとも現時点では)ので、ツール系になりがちです。

例えば、私は毎月クラウドの料金に苦しめられるのですが、計算が重いと言われる機械学習とはいえ、深層学習をのぞいて、朝起きて出社して、GCPAWSにログインして、インスタンスを起動して、何かやって、帰り際にシャットダウンして〜とするので、120秒は溶けますし、というか、めんどくさいはプライスレスです

試しに、GCPで私が契約しているリージョンのインスタンスを出社して作業を開始する前に、一括起動し、IPアドレスを確認し、帰り際に一括シャットダウンします

例えば、iPhoneのホームボタンにはURLショートカットを載せることができて、urlショートカットにはurlパラメータを載せることができます。つまり、iPhoneの画面上で動作する、Amazon Dash Button的なものを作ることができるのです!のです!

実際作ったCloud Functionへのショートカット、タップするだけでGCPがコントロールできて便利

index.js

htmlのコンテンツを生成して返す感じです

const spawnSync = require('child_process').spawnSync;
exports.pycall_instance_controls = function pycall_instance_controls(req, res) {
  result = spawnSync('./pypy3-v5.9.0-linux64/bin/pypy3', ['./instance-control.py'], {
    stdio: 'pipe',
    input: JSON.stringify(req.query)
  });
  if (result.stdout){
    //res.status(200).send(result.stdout);
    res.status(200).send(`<!doctype html>` + result.stdout + `</html>`);
  }else if (result.stderr){
    res.status(200).send(result.stderr);
  }
};

instance-control.py

GCPの承認情報をlocalに通してしまって、pypyにgcloud関連をインストールすると、このような闇魔術が使えます

from oauth2client.client import GoogleCredentials
credentials = GoogleCredentials.get_application_default()

from googleapiclient import discovery
compute = discovery.build('compute', 'v1', credentials=credentials)

imgs = ['"https://ja.gravatar.com/userimage/9847738/e0cfe4c445d28598ffc3d0a4fd235fa5.jpg?size=200"', \
  '"https://ja.gravatar.com/userimage/9847738/647f7900b8912b0669a1da2edc352b5e.jpg?size=200"',
  '"https://ja.gravatar.com/userimage/9847738/44b7b8d6b72f2dce6609be9e059c3920.jpg?size=200"']
html = '''
<head>
<title>GCP Control</title>
<link rel="icon" href={img}/>
<link rel="apple-touch-icon" href={img}/>
</head>
<body>
<p>
{body}
</p>
</body>
'''

project = 'wild-yukikaze'
zone = 'asia-northeast1-c'
def stop_all():
  instances = compute.instances().list(project=project, zone=zone).execute()
  for instance in instances['items']:
    name = instance.get('name')
    compute.instances().stop( project=project, zone=zone, instance=name).execute()
  print(html.format(body='finished all tear down', img=imgs[0]))

def start_all():
  instances = compute.instances().list(project=project, zone=zone).execute()
  for instance in instances['items']:
    name = instance.get('name')
    compute.instances().start( project=project, zone=zone, instance=name).execute()
  print(html.format(body='finished finished all start up', img=imgs[1]))

アイコンは大事  

オタクの皆様には語るまでもないですが、印象とユーザビリティを大きく支配するものなので、かわいいが好きなのでかわいい高解像度のアイコンを設定できるかどうかは、わりと死活問題です

htmlのメタタグにこのようなデータを入れると、高解像度のICONが作れます

<link rel="apple-touch-icon" href={img}/>

また、ICON画像は外部のサイトを参照させることが可能で、gravatar.comさまの公開URLを利用すると便利です

コード

E. まとめ

Cloud Function面白いですね

ディスクに書き込む作業ができないので、temporaryな処理を書き出す際には、Google Cloud Strageなどの外部のサービスと連携する必要がありますが、pipで各種モジュールをインストールできますので、なんでもできます  

しかし、もっとも頭を悩ませたのがリソースの制約で、圧縮して特定の容量を超えると、zipファイルでもデプロイできません。この制約は機械学習を使いたい時、モデルのデータサイズが大きい勾配ブースティングと深層学習はきついので、SVMまであたりでの運用となりそうです  

将来、Google Cloud FunctionやAWS Lambdaのリソースの緩和で大きなデータが扱えるようになってくると、ほとんどのサービスにおいてサーバレスが実現するかもしれません  

機械学習ではじめるDocker

目次とお断り

この資料をまとめるに当たって、実際に開発したり運用したりという経験のスニペットから、できるだけ編集して、自分なりに体系化したものです  

様々な角度のデータが乱雑なっててわかりにくいかもしれませんが、ご了承いただけると幸いです  

  • "1. Dockerとは"
  • "2. Dockerを用いるメリット"
  • "3. docker.ioのインストール"
  • "4. dockerでコンテナの起動"
  • "5. 基本的な操作"
  • "6. Dockerコンテナにsshdなどの必須ソフトウェアをインストールする"
  • "7. dockerコンテナのexportとimport"
  • "8. 機械学習ように調整したコンテナの利用"
  • "9. 実際に使用している例"
  • "10. Docker Hub連係"
  • "11. Docker Compose"
  • "12. Dockerのコンテナとホストマシン間でファイルの共有をする"
  • "13. 便利なチートシート"
  • "14. まとめ"

1. Dockerとは

  • 仮想化コンテナで、VMWareVirtualBoxのようなsystemdやシステムのサービス管理サービスが稼働するような重いものではない
  • chrootみたいなものだが、immutable architectureを前提とするので、docker commitするまで状態は保存されない
  • vmware, virtualboxみたいな完全なOSを乗っけるイメージよりアプリレベルの簡単な仮想化を想定(指定したバイナリしか動かないのでsystemd通常動作させない)

2. Dockerを用いるメリット

開発と分析するいう側面  

  • 依存の多い分析ライブラリと開発環境を簡単にどこでも好きな場所で再現できる
  • XGBoost, LightGBM, LibSVM, RedisやAerospikeのような微妙な環境じゃないと動かなかったりするものを封じ込めて、安定運用版のスナップショットとしていつでも利用できる

運用という側面

  • Dockerで構築したアプリをそのままデプロイ用に加工して、DevOpsに渡すことで、運用に回してもらう
  • DevOpsは開発レイヤーが強いので、なんなら機械学習とエンジニアリング専門の私でも、運用ルールを策定できる

3. docker.ioのインストール

手法1. dockerのrepositoryからのインストール

$ sudo apt install docker.io
$ sudo usermod -aG docker $USER
LOGOUTしてLOGINか、再起動

手法2. docker公式が提供する公式のインストールスクリプトからインストール

$ curl -fsSL get.docker.com -o get-docker.sh
$ sh get-docker.sh
$ sudo usermod -aG docker $USER
LOGOUTして、LOGINか再起動

4. dockerでコンテナの起動

最小サイズのubuntuをインストール

$ docker pull ubuntu
$ docker run -it ubuntu bash

5. 基本的な操作

dockerにはなぜか名前がつかないことがあって、docker tagで名前をつける

$ docker tag ${ID} ${NAME}

run(内容が保存されない)

$ docker run -it ${NAME} /bin/bash

すでに起動済みのDockerにCONTAINER IDでログイン  

$ docker exec -it ${CONTAINER_ID} /bin/bash

恒久化(Commit)  

作業した内容は保存されないないので、commitすることで、状態が差分として保存される

$ docker commit ${CONTAINER_ID} ${NAME}/${TAG}

Dockerでportをホストのportとバインドして起動

docker内でportを起動して、ホストマシンにつなげてサービスを公開するときなど、利用できる

$ docker run -it -p ${PORT}:${PORT} bash

6. Dockerコンテナにsshdなどの必須ソフトウェアをインストールする

DockerコンテナにSSHでアクセスする

sshdなどはdockerの性質上、systemdなどで管理なので、dockerにbashなどでログインして、手動やdocker-composeなどで起動する必要があります

sshdのインストール(Ubuntu, Debian)

$ apt install openssh-server

sshdのインストール(ArchLinux)

$ sudo pacman -S openssh

sshdサーバの起動

$ /usr/bin/sshd

sshへのアクセス
IPアドレスなどをnet-toolsのifconfigなどで確認して、ホストマシンからclientのマシンに接続する

$ ssh ${USER}@172.17.0.2

.bashrcのロード
通常の環境変数のローディングと異なるので、.bashrcから読み取る  

$ source .bashrc

これをしないとlocaleがja_JP.UTF-8にならずにtmuxなどが使えないっぽい

7. dockerコンテナのexportとimport

諸々をインストールして、安定して運用できるようになったらば、tar形式にexportできます

export
tarファイルで出力することができます

$ docker export ${PID} > alice-bob.tar

import

$ docker import ./hogehoge.tar

インポートした状態では、最初は

REPOSITORY:<none> TAG:<none>

となるので、tagコマンドで適切に名前をつける

$ docker tag ${NAME}/${TAG} ${NEWTAGNAME}

8. 機械学習ように調整したコンテナの利用

ArchLinux(Official)

$ docker pull archlinux/base

Debian(Official)

$ docker pull debian

私のDocker Hubのレポジトリ

機械学習専用のコンテナを作りました!

sshd, python3, pip3, mecab, neologd, lightgbm, xgboost, neovim, tmux, git, JVM, Kotlin, leveldb, rocksdbなどをインストールしてすぐ使えるようにしたイメージです。docker hubで公開しているので、docker pullでダウンロードしてすぐ利用できます  

すぐに機械学習できますね!!

$ docker pull nardtree/oppai:latest
$ docker run -it nardtree/oppai bash
(sshd serverモードではこうして、表示されたipaddressにssh oppao@${IP_ADDR}とします)
$ docker run -p 1022:22 -it nardtree/oppai server.py

server.pyでは、ifconfigとsshdが非同期で実行されて、このDockerのsshdにアクセスできるようになります

**ここからコンテナ内部です**
# sudo su oppai #passwordはoppaiです
$ cd ~
$ source .bashrc

$ python3
Python 3.6.3 (default, Oct 24 2017, 14:48:20) 
[GCC 7.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 

$ sudo pip3 install mecab-python3
Requirement already satisfied: mecab-python3 in /usr/lib/python3.6/site-packages

$ echo "艦これ" | mecab
艦これ  名詞,固有名詞,一般,*,*,*,艦これ,カンコレ,カンコレ
EOS

$ which lightgbm
/usr/local/bin/lightgbm

$ which xgboost
/usr/bin/xgboost

$ which nvim
/usr/bin/nvim

$ which javac
/usr/bin/javac

$ which kotlin
/usr/bin/kotlin

9. 実際に使用している例

開発、分析環境として利用する例

本番に投入するサービスとして利用する例

分析機にはUbuntu, Debian or ArchLinuxを使うことが多く、本番環境ではRHELCentOSを使う運用が多い構成なのですが、バイナリや微妙なディストリビューション間の差を人間が吸収するのはやめて、Dockerの出力を利用してどこでも再現してもらえば、問題ないよね、好きな構成でアプリとして機能すればDevOpsでよろしく組み合わせるよ、というスパースな設計ができます

このように一つの機能を持ったアプリとして動作させるには、docker composeやkubernetesを用いてDockerの断片をつなぎ合わせて、巨大なシステムを編纂します。   

ローカルでserverの機能をもつアプリをDockerでラップした一つのアプリの粒度として動作させるには、このようにします。

$ docker run -p ${PORT1}:${PORT2} -it ${APPLICATION_NAME}

10. Docker Hub連係

DockerというgithubのようなコンテナのイメージのSNSみたいなのがあります
nardtree/archのようなtag名をつけるとスラッシュの先のユーザ名のレポジトリにpushできて再現性が確保で来ます

$ docker tag ${IMAGE_ID} ${USER_NAME}/${SOME_NAME}
$ docker push ${USER_NAME}/${SOME_NAME}

11. Docker Compose

Dockerの一連の複雑なオペレーションを自動化した状態で、取り扱うのが、Docker Composeです

$ git clone https://github.com/GINK03/docker-compose-templates
$ cd docker-compose-templates/minimal-machine-learning-oppai
$ docker-compose up

これをやると、一連のめんどくさいセットアップコマンド各種をすっ飛ばして、port 1022でlocalhostsshdできるArchLinuxが仕上がります

$ ssh -p 1022:22 oppai@localhost
# password:oppai

12. Dockerのコンテナとホストマシン間でファイルの共有をする

Dockerコンテナでは状態を持たないイミュータブルアーキテクチャになっており、あまり状態を持つことを期待されて居ません   しかし、RDBやKVSなどを利用する際は、当然ながら、ファイルに書き出したりして状態を保存しなければいけません

DockerfileではVOLUMEオプションをつけることで、マウントするディレクトリを指定し、作ることができます

FROM nardtree/oppai                              
EXPOSE 22                                        
EXPOSE 1022                                      
EXPOSE 4567                                      
VOLUME ["/data"]                                 
CMD ["server.py"]  

Docker-compose.ymlでホストの$HOME/dataとコンテナの/dataを結び、片方で書き込まれたらもう片方にも反映されるようになります

version: '3'                                     
services:                                        
  ml-app:                                        
    build: .                                     
    ports:                                       
     - "4567:4567"                               
     - "1022:22"                                 
    volumes:                                     
      - ~/data:/data  

13. 便利なチートシート

noneの一括削除

$ docker images | awk '/<none/{print $3}' | xargs docker rmi

肥大化するvarを別のポイントに移動
ホストマシンのデフォルトのdockerのコンテナの保存を別の場所にする
SSDでパフォーマンスが大きく変わるサービスである、KVSなどを利用する際は、こっちの方がいいよ
  方法1. /etc/default/dockerを編集します

DOCKER_OPTS="-g $HOME/sda/docker/data"

方法2. /var/lib/dockerをssdやnvmeからリンクする

$ sudo mv /var/lib/docker ${nvme_dir}
$ sudo ln -s ${nvme_dir} /var/lib/docker

docker containerのIPや詳細を調べる

$ docker inspect ${PID}

docker composeで一括アプリの実行

$ docker-compose run ml-app

14. まとめ

私の所属しているいくつかの組織では、インフラエンジニアが十分に確保できなく、慣れないインフラオペレーションにイライラしていたのですが、時代はDevOpsということでソフトウェアエンジニアや私のような機械学習エンジニアがインフラの管理をしなくてはいけないことが、まま発生するようになりました

最近はもう諦めて、Dockerや最近のDocker-Compose、より上位のKubernetesなどを勉強し始めて、依存や環境を丸めて機能のみを取り出すDockerの粒度が一つのアプリの粒度として優れていると、思うようになり、運用と開発の面で導入するようになりました。実際便利であり、オンプレでサーバを持たなくていいので楽です

3つのレコメンド系アルゴリズム

(誤字脱字が目立ったので、修正しました。。)

3つのレコメンド系アルゴリズム

  1. 協調フィルタリング
  2. fasttextでの購買時系列を考慮したアイテムベースのproduct2vec(skipgram)
  3. アイテムベースのtfidfなどの類似度計算を利用したレコメンド

1. 協調フィルタリング

協調フィルタリング自体は簡潔なアルゴリズムで、実装しようと思えば、簡単にできる類のものであるように思えるのですが、製品と製品の類似度を計算するのに、その製品を購入したユーザをベクトル列としてみなすと割と簡単に計算できます[5]。世の中のコンテンツはユーザの関連度の計算の方が多い気がしますが、今回はアイテムにひもづくユーザをベクトルにします 

例えば、今回はbookmeter.comさまのユーザの読んだ本情報を用いて、一人のユーザを一つのユニークな特徴量としてみなすことで、本同士の関連度が計算可能になります

Albertさんなどのブログなどを参考し、今回の問題に当てはめると、このようなことであると言えそうです。

今回は要素間距離の計算方法に、cosine similarityを利用していますが、ジャッカードという距離の取り方や、色々あるので、実際に業務で使う時にはあるべきレコメンドリストを作って定量評価すると良いでしょう。  

図1. 今回用いた協調フィルタリング

今回用いさせていただいた、bookmeter.comさんから作成したデータセットこちらです。27GByte程度あるので、覚悟してダウンロードしてください(何度もスクレイピングするのは気がひけるので、初期調査はこのデータセットを公開しますので、有効活用していただけると幸いです。用途はアカデミックな用途に限定されると思います)  

また、必要なユーザと読んだ本とその時のタイムスタンプの情報のみをまとめたものは、こちらからダウンロードできます。

このアルゴリズムはユーザの行動が蓄積されていなくても、一つ商品を選べば、それに関連してレコメンドできるという特徴があり、この用途場合、強力に動作します。 

学習

Google Cloud Strageから、mapped.jsonpを当プロジェクトにダウンロードしたという前提です

$ wget https://storage.googleapis.com/nardtree/bookmeter-scraping-20171127/mapped.jsonp
$ python3 reduce.py --fold1
$ cd collaborative-filtering-itembase 
$ python3 make_matrix.py --step1
$ python3 make_matrix.py --step2 # <- とても重いので、計算に数日みてください

結果.  

いくつか結果をピックアップします。チェリーピックにならないように、ランダムサンプルしたうち知っているものを選んで見ました。   それなりに正しく動作していることがわかり一安心です。

氷菓(1)(角川コミックス・エース) との類似度   当然、その本地身とシリーズが出て欲しいのですが、例えば、コミック版氷菓を読むユーザは、「我妻さんは俺のヨメ」シリーズも読むことがわかりました。 氷菓が好きな人は、このシリーズもきっとおすすめです

氷菓(1)(角川コミックス・エース) 1.0
氷菓(3)(角川コミックス・エース) 0.6324555320336759
氷菓(2)(角川コミックス・エース) 0.565685424949238
氷菓(4)(角川コミックス・エース) 0.5477225575051661
氷菓(5)(角川コミックス・エース) 0.4743416490252569
我妻さんは俺のヨメ(8) 0.4472135954999579
キン肉マン60(ジャンプコミックスDIG… 0.4472135954999579
弱虫ペダル 51(少年チャンピオン・コミッ… 0.4472135954999579
魔法遣いに大切なこと~夏のソラ~(1)(角… 0.4472135954999579
人生はまだ長いので(FEELCOMICS… 0.4472135954999579
キン肉マン59(ジャンプコミックスDIG… 0.4472135954999579
お前はまだグンマを知らない 1巻(バンチコ… 0.4472135954999579
我妻さんは俺のヨメ(5) 0.4472135954999579
トリニティセブン 7人の魔書使い(1)(ド… 0.4472135954999579
このお姉さんはフィクションです!?:4… 0.4472135954999579
我妻さんは俺のヨメ(6)(週刊少年マガジン… 0.4472135954999579
ナナマルサンバツ(7)(角川コミックス・… 0.4472135954999579
夏の前日(4) 0.4472135954999579
我妻さんは俺のヨメ(9)(週刊少年マガジン… 0.4472135954999579
お前はまだグンマを知らない 2巻(バンチコ… 0.4472135954999579
塩田先生と雨井ちゃん2 0.4472135954999579

ガガガ文庫 羽月莉音の帝国10 との類似度
完全に趣味ですが、一時期、どハマりしていた「羽月莉音の帝国」シリーズです。お金と欲と権力に振り回される少年少女とディープステートたちは熱かった

この系統の書籍が好きでしたが、当時は定量的に解析するすべを知らず、今このように類似度を計算すると感慨深いものがあります

ガガガ文庫 羽月莉音の帝国10(イラスト完全… 1.0
ガガガ文庫 羽月莉音の帝国9(イラスト完全版) 1.0
群れない生き方(SB文庫) 0.7071067811865475
ハチワンダイバー100万円争奪・地下将棋!… 0.7071067811865475
ガガガ文庫 羽月莉音の帝国3(イラスト完全版) 0.7071067811865475
大人のADHD ――もっとも身近な発達障害… 0.7071067811865475
ハチワンダイバー闇将棋集団・鬼将会を追え編… 0.7071067811865475
ガガガ文庫 羽月莉音の帝国5(イラスト完全版) 0.7071067811865475
奥の細道(21世紀によむ日本の古典15) 0.7071067811865475
ハチワンダイバー恋の“賭け将棋”にダイブ!… 0.7071067811865475
ハチワンダイバー激アツ修行!24時間将棋編… 0.7071067811865475
世界の端っことあんずジャム(2)(デザート… 0.7071067811865475
No.1理論―ビジネスで、スポーツで、受験… 0.7071067811865475
腸が嫌がる食べ物、喜ぶ食べ物 40歳を過ぎた… 0.7071067811865475
UXの時代―IoTとシェアリングは産業を… 0.7071067811865475
図解・速算の技術 一瞬で正確に計算するための… 0.7071067811865475
マンガでわかる有機化学 結合と反応のふしぎか… 0.7071067811865475
頭の体操 第1集~パズル・クイズで脳ミソを鍛… 0.7071067811865475
ガガガ文庫 羽月莉音の帝国7(イラスト完全版) 0.7071067811865475
コンなパニック(1)(なかよしコミックス) 0.7071067811865475
ガガガ文庫 羽月莉音の帝国8(イラスト完全版) 0.7071067811865475
ガガガ文庫 羽月莉音の帝国4(イラスト完全版) 0.5
シンギュラリティ・ビジネス AI時代に勝ち残… 0.5

ハーモニー〔新版〕(ハヤカワ文庫JA) に類似する本
私が唯物論者になるきっかけで、ビッグデータ機械学習による意思決定のその先に置けそうな社会として、「意識」が消失するという仮説は大変魅力的な小説です
伊藤計劃三部作の中で一番好きです。

ハーモニー〔新版〕(ハヤカワ文庫JA) 1.0
虐殺器官〔新版〕(ハヤカワ文庫JA) 0.4568027835606774
屍者の帝国(河出文庫) 0.2727051065113576
虐殺器官(ハヤカワ文庫JA) 0.14109861338756574
TheIndifferenceEngin… 0.12909944487358058
アンドロイドは電気羊の夢を見るか?(ハヤカ… 0.11756124576907943
PSYCHO-PASSGENESIS1… 0.11603391514496726
PSYCHO-PASSGENESIS2… 0.11489125293076057
know(ハヤカワ文庫JA) 0.11199737837836385
一九八四年[新訳版](ハヤカワepi文庫) 0.10744565033846916
PSYCHO-PASSASYLUM1(… 0.10264528445948555
PSYCHO-PASSGENESIS3… 0.09331569498236164
PSYCHO-PASSASYLUM2(… 0.09203579866168446
ニューロマンサー(ハヤカワ文庫SF) 0.0900003231847342
伊藤計劃記録II(ハヤカワ文庫JA) 0.08882967062031503
いなくなれ、群青(新潮文庫nex) 0.08679025878292934
[映]アムリタ(メディアワークス文庫の… 0.08626212458659283
華氏451度〔新訳版〕(ハヤカワ文庫SF) 0.08464673190724618
その白さえ嘘だとしても(新潮文庫nex) 0.08191004034460432

PSYCHO-PASSも好きですが、小説でてたんですか!良いですね。買います!←こんな感じのユースケースを想定しています

データセットのダウンロード

  1. アイテムベースの協調フィルタリング結果
  2. ユーザベースの協調フィルタリング結果

2. fasttextでのアイテムベースのproduct2vec(skipgram)

一部でproduct2vecと呼ばれる技術のようですが、同名でRNNを用いた方法も提案されており、購買鼓動を一連の時系列として文章のように捉えることで、似た購買行動をするユーザの購買製品が似たようなベクトルになるという方法を取っているようです

このベクトルとベクトルのコサイン距離か、ユークリッド距離を取れば、購買行動が似た商品ということができそうです

リクルートではこのアリゴリズムは動作しているよう[4]なのですが、効果のほどはどうなんですかね?知りたいです

期待される結果

  • 流行があり、時代と時期によって読まれやすい本などが存在している場合、同じ時代に同じ流れで、読まれやすい本のレコメンド
  • 本のコンテンツの類似度ではなく、同じような本を読む人が同じ時代にどういった方も、また読んでいたか、という解釈
  • 時系列的な影響を考慮した協調フィルタリングのようなものとして働くと期待できる

学習アルゴリズム

  • fasttext
  • skipgram
  • 512次元
  • n-chargramは無効化

前処理

bookmeterさんからスクレイピンしたデータからユーザ名とIDで読んだ本を時系列順に紐づけます

$ python3 parse_user_book.py --map1
$ python3 parse_user_book.py --fold1

bookmeterさんのデータをpythonで扱うデータ型に変換します

$ python3 reduce.py --fold1
$ python3 reduce.py --label1 > recoomender-fasttext/dump.jsonp

fasttext(skipgramを今回計算するソフト)で処理できる形式に変換します

$ cd recoomender-fasttext
$ python3 mkdataset.py

SkipGramでベクトル化と、本ごとのcosime similarityの計算

$ sh run.sh
$ python3 ranking.py --to_vec
$ mkdir sims
$ python3 ranking.py --sim

定性的な結果

  1. 近年、本は大量に出版されて、その時に応じて売れ行きなどが変化するため、その時代に同じような本を買う傾向がある人が同じような買うというプロセスで似た傾向の本を買うと仮定ができそうである  
  2. 本は、趣味嗜好の内容が似ている系列で似る傾向があり、コンテンツの内容では評価されない

以上の視点を持ちながら、私が知っている書籍では理解しやすいので、いくつかピックアップした

聖☆おにいさん(11)(モーニングKC) と同じような購買の行動で登場する本

聖☆おにいさん(11)(モーニングKC) 聖☆おにいさん(11)(モーニングKC) 1.0 
聖☆おにいさん(11)(モーニングKC) 富士山さんは思春期(1)(アクションコミッ… 0.8972133709829763
聖☆おにいさん(11)(モーニングKC) 血界戦線4―拳客のエデン―(ジャンプコ… 0.8880415759167462
聖☆おにいさん(11)(モーニングKC) 闇の守り人2(Nemuki+コミックス) 0.874690584963135
聖☆おにいさん(11)(モーニングKC) 高台家の人々1(マーガレットコミックス) 0.8739713568492584
聖☆おにいさん(11)(モーニングKC) よりぬき青春鉄道(MFコミックスジーンシ… 0.8654563200372407
聖☆おにいさん(11)(モーニングKC) MUJIN―無尽―2巻(コミック(Y… 0.864354621889988
聖☆おにいさん(11)(モーニングKC) 日日べんとう8(オフィスユーコミックス) 0.8642825499104115
聖☆おにいさん(11)(モーニングKC) Baby,ココロのママに!(1)(ポラリ… 0.8633512045595658
聖☆おにいさん(11)(モーニングKC) 僕とおじいちゃんと魔法の塔(1)(怪CO… 0.8599349055581674

ボールルームへようこそ(5) と同じような購買の行動で登場する本

ボールルームへようこそ(5)(講談社コミッ… ボールルームへようこそ(5)(講談社コミッ… 0.9999999999999999
ボールルームへようこそ(5)(講談社コミッ… 乙女座・スピカ・真珠星―タカハシマコ短編集… 0.9209695753657776
ボールルームへようこそ(5)(講談社コミッ… 微熱×発熱(少コミフラワーコミックス) 0.9200136951145198
ボールルームへようこそ(5)(講談社コミッ… 銀魂-ぎんたま-52(ジャンプコミックス) 0.9192139559710213
ボールルームへようこそ(5)(講談社コミッ… ボールルームへようこそ(3)(講談社コミッ… 0.9185994617714857
ボールルームへようこそ(5)(講談社コミッ… リメイク5(マッグガーデンコミックスE… 0.9178462840921753
ボールルームへようこそ(5)(講談社コミッ… カラダ探し2(ジャンプコミックス) 0.9133800737256051
ボールルームへようこそ(5)(講談社コミッ… エンジェル・ハート1STシーズン4(ゼノ… 0.9131301530550904
ボールルームへようこそ(5)(講談社コミッ… モテないし・・・そうだ、執事を召喚しよう。… 0.912900126682059
ボールルームへようこそ(5)(講談社コミッ… IPPO1(ヤングジャンプコミックス) 0.9127181876031607

バーナード嬢曰く。(REXコミックス) と同じような購買の行動で登場する本

バーナード嬢曰く。(REXコミックス) バーナード嬢曰く。(REXコミックス) 0.9999999999999999
バーナード嬢曰く。(REXコミックス) 宝石の国(7)(アフタヌーンKC) 0.8471396900305308
バーナード嬢曰く。(REXコミックス) 宝石の国(5)(アフタヌーンKC) 0.8382072129764407
バーナード嬢曰く。(REXコミックス) 里山奇談 0.816403634050381
バーナード嬢曰く。(REXコミックス) ファイブスター物語(12)(ニュータイプ… 0.8158735339937038
バーナード嬢曰く。(REXコミックス) 汐の声(山岸凉子スペシャルセレクション2) 0.813237955152229
バーナード嬢曰く。(REXコミックス) ゴールデンカムイ5(ヤングジャンプコミッ… 0.8124194507560973
バーナード嬢曰く。(REXコミックス) サンドマン(1)(DCCOMICSV… 0.8063562786141018
バーナード嬢曰く。(REXコミックス) ナチュン(5)(アフタヌーンKC) 0.8062792077739434
バーナード嬢曰く。(REXコミックス) めぞん一刻15(ビッグコミックス) 0.8050153883069142

34万冊にも及ぶ購買行動関連スコアを計算したので、きっとあなたの好きな本を広げるにも役に立つはずです。参考にしていただければ幸いです

Google Cloud Strageにtar.gzで固めたファイルを置いてあるので、どの本が何の本と関連度がどれくらい持ちたい方は参考にしてみてください

アイテムベースのtfidfの類似度を利用したレコメンド

今更、tfidfなのかという気持ちもあるのですが、ユーザの購買行動ログが蓄積されていない時には有効な手段です
skipgramにせよRNNのEncoder-Decoderモデルを利用するにせよ、ログがある程度あることを前提とするので、コンテンツを示す情報が何かしら適切に表現されていれば、類似度の計算が可能です。
例えば、本の概要情報をBag Of Wordsとして文字列のベクトルとしてみなすことで、レコメンドが可能になります。

前処理

本の概要情報をtfidfでベクトル化します

$ python3 tfidf.py --make

類似度を計算

$ python3 tfidf.py --similarity

具体例

その女アレックス (文春文庫)

{ 
  "その女アレックス (文春文庫)": 1.0,
  "死のドレスを花婿に (文春文庫)": 0.217211675720195,
  "蟻の菜園 ―アントガーデンー (宝島社文庫 『このミス』大賞シリーズ)": 0.17019688046138778,
  "ゴーストマン 時限紙幣": 0.15007411620369276, 
  "使命と魂のリミット (角川文庫)": 0.11353386562607512,
  "ネトゲの嫁は女の子じゃないと思った? (電撃文庫)": 0.10053605201421331,
  "絶叫": 0.09928094382921815, 
  "限界点": 0.09839970550260285,
  "探偵が早すぎる (上) (講談社タイガ)": 0.09710240347327244,
  "何様ですか? (宝島社文庫 『このミス』大賞シリーズ)": 0.0967122631023104
}

ビブリア古書堂の事件手帖 (6) ~栞子さんと巡るさだめ~ (メディアワークス文庫)

{
  "ビブリア古書堂の事件手帖 (6) ~栞子さんと巡るさだめ~ (メディアワークス文庫)": 1.0,
  "ビブリア古書堂の事件手帖4 ~栞子さんと二つの顔~ (メディアワークス文庫)": 0.14235848517270094,
  "ビブリア古書堂の事件手帖 (5) ~栞子さんと繋がりの時~ (メディアワークス文庫)": 0.12690320576737468,
  "ビブリア古書堂の事件手帖 2 栞子さんと謎めく日常 (メディアワークス文庫)": 0.10860850572897893,
  "ビブリア古書堂の事件手帖3 ~栞子さんと消えない絆~ (メディアワークス文庫)": 0.09155840600578283,
  "0能者ミナト〈3〉 (メディアワークス文庫)": 0.08957995681421002,
  "ビブリア古書堂の事件手帖7 ~栞子さんと果てない舞台~ (メディアワークス文庫)": 0.07945929682993862,
  "きみと暮らせば (徳間文庫)": 0.07897713721113855,
  "ビブリア古書堂の事件手帖―栞子さんと奇妙な客人たち (メディアワークス文庫)": 0.07818237184568279,
  "天使たちの課外活動2 - ライジャの靴下 (C・NOVELSファンタジア)": 0.07661947755379202
}

オプションデータ

月ごとの本棚に登録された本の累計(その月での)数

読んでる人が多い本ランキング

読んでる人が多い本(2017年11月時点)

永遠の0(講談社文庫) 6805
ビブリア古書堂の事件手帖―栞子さんと奇妙な客… 6551
舟を編む 6500
イニシエーション・ラブ(文春文庫) 6158
火花 5988
阪急電車(幻冬舎文庫) 5979
夜は短し歩けよ乙女(角川文庫) 5964
君の膵臓をたべたい 5504
コンビニ人間 5134
ビブリア古書堂の事件手帖2栞子さんと謎め… 5075
レインツリーの国(新潮文庫) 4994
その女アレックス(文春文庫) 4792
ぼくは明日、昨日のきみとデートする(宝島社… 4631
氷菓(角川文庫) 4606
ビブリア古書堂の事件手帖3~栞子さんと消え… 4590

6ヶ月以上、連続で本を読んでいる人の読んでる本ランキング

1 舟を編む 5419 
1 永遠の0(講談社文庫) 5296
1 ビブリア古書堂の事件手帖―栞子さんと奇妙な客… 5277
1 火花 5081 
1 イニシエーション・ラブ(文春文庫) 4784
1 阪急電車(幻冬舎文庫) 4570
1 夜は短し歩けよ乙女(角川文庫) 4427
1 君の膵臓をたべたい 4422
1 コンビニ人間 4225
1 その女アレックス(文春文庫) 4186

6ヶ月間、連続で本を読むことができなかった人の読んでる本ランキング

0 夜は短し歩けよ乙女(角川文庫) 1537
0 永遠の0(講談社文庫) 1509
0 阪急電車(幻冬舎文庫) 1409
0 イニシエーション・ラブ(文春文庫) 1374
0 ビブリア古書堂の事件手帖―栞子さんと奇妙な客… 1274
0 ぼくは明日、昨日のきみとデートする(宝島社… 1143
0 レインツリーの国(新潮文庫) 1120
0 君の膵臓をたべたい 1082
0 舟を編む 1081
0 西の魔女が死んだ(新潮文庫) 1004

コード

githubにて管理しております。

参考

[1] Instacart Product2Vec & Clustering Using word2vec
[2] MRNet-Product2Vec: A Multi-task Recurrent Neural Network for Product Embeddings
[3] Deep Learning at AWS: Embedding & Attention Models
[4] リクルートのデータで世界へ挑む 組織を率いるサイエンティストの仕事観 [5] 推薦システムのアルゴリズム

勾配ブースティングを利用した、KPIに効く特徴量のレコメンド

勾配ブースティングを利用した、KPIに効く特徴量のレコメンド

近況:おばあちゃんが亡くなった関係で、しばらく更新できませんでした。人間の一生とは改めて有限で、限られた時間で何をどうやるかを意識しないといけないなと思いました

意思決定をサポートする機械学習の必要性

機械学習は、マスメディアの中で語られるまるで人間の置き換えかのように表現されることが多いのですが、利用できる実態としては以下の側面が私が携わる案件で多いです。

  1. CTR予想など、KPIを自動で場合によっては高速に計算することで、人間が最適な意志決定しやすくする
  2. 膨大な経験に基づく必要がある一部の職人の仕事で、何がKPIなのかなどの特徴量重要度を把握し、定量的な値で意志決定することで、属人性をなくす

まだ私自身、キャリアとして、そんなに長くない機械学習エンジニアですが、主要な幾つか本質的な要素に還元すると、このようなものであることが多いです

いろんなシステムを作ってきましたが、意外とヒアリングしていると、システムに半自動でいいから意思決定を任せたい(もしくは施策が定量的にどの程度有効なのか知りたい)などが多く見受けられました

IBMのコグニティブコンピューティングの資料を引用しますと、コグニティブコンピューティングのDecision(レコメンド), Discovery(意思決定のサポート)の領域とも無理くり解釈できそうではあります

ワトソンの料理の新しいレシピのレコメンドからの発想

もともと、このKPIを最大化する特徴量群をレコメンドするという考えは、シェフ・ワトソン[1]の全く新しい食材の組み合わせで美味しいと言われているレシピをレコメンドするという機能を幾つかの状況証拠から仮説を立てて、導いたものです
IBMが長らく研究していたテーマとして、意味情報を演算可能にするというお題があったはずですが、これはちょっと別で、機械学習の最小化のアルゴリズムでいくつかこの特徴量とあの特徴量が同時に存在して、ある量以下(または以上)ならば、という膨大なルールセットを獲得し、その獲得した条件式が成立していれば、美味しいはずであるという発想に基づいているかと思います

図1. 何品か、最初の条件となる、食品を選んで、他の何品かのレシピを自動でレコメンドします

今回はテキストの作りに着目し、テキストにどのような単語が含まれていたら、KPIの向上が望めるかのシステムを2chまとめサイトに対象にやってみたいと考えています

【実験】jin115.comのコメント数をどんな単語で記述したら数が増えるか予想する

jin115.comのデータセットをzip圧縮で17GByteもダウンロードしてしまったので、何か有効活用できないかと考えていたのですが、難しく、なんともKPIという軸で定量化できないので、 ある記事に対してつく、コメント数を増やすには、どんな単語選択が適切かという軸に切り替えて、分析してみました。

コードはgithubのレポジトリで管理しています

jin115.comでは、2chまとめが流行った時に大量のアクセスを受けていたという背景があり、一気にその時に成長したのですが、今はユーザ数はサチっているように見えます。
各月のユーザのコメント数を累積していくと、このようなグラフを書くことができました(2006年からやっているんですね。。すごい)

前処理

データセットダウンロード

Google Cloud Strageに大容量のスクレイピング済みのデータセットをzip圧縮したものがありので、追試等が必要な方は参照してください。

$ wget https://storage.googleapis.com/nardtree/jin115-20171125/jin115-20171125.zip

htmlコンテンツから必要なarticle, commentの数, 日時, タイトルのパース

ダウンロード済みのhtmlコンテンツから予想の元となる説明変数(article, title, date)と、目的変数であるcomment数をパースします  

この捜査自体が極めて重いので、Google Data Strageからもダウンロードできます  

$ python3 parser.py --map1

パースしたデータを分かち書きします

分かち書きして、形態素解析して、BoWとしてベクトル表現にできる状態にします

$ python3 wakati.py --verb

LightGBM, XGBoostで機械学習可能なデータセット(train, test)を作成します

まず最初に、予想するとしたKPIであるコメント数を予想するもモデルを構築することを目的とします  

そのために、トレインとテストのデータセットを分割して、作成します

$ python3 make_sparse.py --terms # uniq単語をカウント
$ python3 make_sparse.py --sparse # train, testデータセットを作成します

LightGBMで学習して、モデルを作成します
一般的に、言語のBoWは単語数に応じたベクトル時原まで拡大されますので、numpyなどで密行列で表現することには向いていません
そのため、デフォルトでスパースマトリックスを扱え、CUI経由で学習できるLightGBMのバイナリを直接実行してモデルを作成します

$ lightgbm config=train.conf

(LightGBMのインストールは、Githubからcloneして、cmake, make & sudo make installでシステムパスに追加しておくと便利です)

特徴量重要度を確認する
Gradient Boosting Machine系の特徴量重要度は、必ずしも、事象を説明する特徴量が支配的に記述されるわけではないのですが、どんな特徴量が効いているのか参考適度になります。
また、今回、どんな単語をアーティクルやタイトルに記述されていれば、KPIが向上するのかの選択候補を与えるために、これらの特徴量重要度が高い候補の単語から抽出します

特定の記事から、KPIを増加させるために、なんの単語群を選択すればいいのか

ここまでとても長かったのですが、ここからが本番で、クリエイティブを作る人は、書きかけの記事の単語選択や、表現に迷うことがあります。

一般的に単語や表現は組み合わせで初めて、価値を発揮することが多く、経験則的にはこのようなクリエイティブはKPIが異なるとされています

[もちもち した 美肌 ! 今なら 、 キャンペーン で 送料 無料 !] 
[もちもち した 食感 ! 北海道産 、 まんじゅう ! 美味しい !]

liblinearや単純な重回帰では、「もちもち」は単独の特徴量として表現されて、「もちもち」+ 「美肌」 と「もちもち」+「まんじゅう」の組み合わせることで、別の特徴量だと考慮すべき点は見ることができません

Gradient Boosting MachineやDeep Learningなどは、組み合わせ表現の重要度を獲得可能なので、「美容」のクリエイティブを作成している時に、「美肌」という制約が入った状態で、最適などの単語を使うべきかをレコメンド可能になります

KPIを予想するモデルを移動する

$ mv LightGBM_model.txt best-term-combination-search/
$ cd best-term-combination-search/

元となるテキストをベクトル化する
デフォルトでは、学習データセットに用いなかったjin115.comの最新の記事を参照するようにしていますが、適宜必要なデータセットに変えてください

$ python3 make_init.py

モデルで判別するために獲得された単語群を作成する この単語があるから、KPIが変わった、という判別の閾値に用いられている、単語の一覧を得て、その単語群を組み合わせることで、よくなるのか、悪くなるか計算することができます

$ python3 check_importance.py

単語群を利用して、ランダムの組み合わせ表現100,000通りを作成する
レコメンドの候補となる単語群の組み合わせ10万通りを計算して、データセットを作成します

$ python3 calc_combinations.py  > dump.dat

前処理で学習したモデルを利用して、KPI増加量が多い単語の組み合わせ表現を、昇順でランキングします

$ python3 upper_calc.py | less

(この例では、コメントするユーザのハッシュ値も予想の重要な特徴量になってしまっているので、名詞のみのモデルでランキングしています) 具体例
やはり、2chねらーとTwitterユーザは仲が悪のか、Twitterネタは炎上しやすい感じでした

三つの単語をレコメンドし、右側の数字のぶんだけ、KPIであるコメント投稿数が上がると期待されると解釈します

articleはコンテンツ中, h1は記事のタイトル中に含まれているべき単語です

article@頂点 article@SCEJ article@バリュー 354.4107576417173
article@マジモン article@東方 article@板 353.46575634371357
article@pr h1@ツイッター民 article@543 348.5449500051823
h1@バカッター article@老害 article@5W 345.9868295939084
article@商業 article@肯定 h1@ツイッター民 337.3307174323563
h1@ファイナルファンタジー15 article@安倍晋三 article@一位 330.16779109936283
article@2月27日 article@年収 h1@ツイッター民 315.97116550315286
article@ネトウヨ article@ただ h1@爆死 313.42247907534056
article@570 article@2013年 h1@ツイッター民 308.77429368376124
article@暗殺 h1@バカッター h1@赤字 304.52876971810906
article@ロゴ article@熊本県 h1@バカッター 302.28776850940653
h1@ソニー article@ガム h1@ツイッター民 294.8744340111921

では、KPIが下がってしまうようなコンテンツの組み合わせにするには、どんな単語にすればいいのでしょうか

article@テキスト article@デュラララ!! article@ドラマ -189.53177862790812
article@tenki h1@黒子のバスケ article@動向 -190.51400300682553
article@気象庁 article@165 h1@プロジェクト -190.59643895049328
h1@配信決定 article@30代 article@コンピュータ -190.85115503750387
article@3月20日 article@グランツーリスモSPORT article@舞台化 -191.62621800299712
article@怪獣 article@太鼓の達人 article@MP -191.65833548081196
article@子会社 article@DMM h1@2012 -192.6149488251317
article@逮捕 article@簡単 h1@2012 -193.43026190229068
article@カオス h1@配信決定 article@机 -194.11086374900992 
article@売却 article@ギズモード・ジャパン article@新テニスの王子様 -194.42483360420647
article@ロケットニュース24 article@故郷 article@2005年 -194.87825375720115
h1@配信決定 article@音声 article@オンラインサービス -195.4315454292838
article@情報番組 h1@無料配信 article@よ -196.20890731778456
article@狂気 h1@2012 article@人間関係 -196.5040631720508
article@PS4 article@年寄り h1@2012 -197.5578885851079
article@アイドルマスター ミリオンライブ! h1@C article@Sony Computer Entertainment -198.7143092931767

一部の作品や層を狙った文章にすると、KPIが下がるとかそういうことだと思います

想定する使い方

今回は、記事全文の長文に対して重要な単語のレコメンドをやってみたのですが、それより、短い文章(例えばキャッチコピーや記事のタイトルなど)に部分的に適応するのが筋が良さそうに見えました

jin115.comさんに限らず、クリエイティブの作り方や、組み合わせのレシピなどで美味しくなるなどは、一定の傾向が存在し、それらを獲得することで、何かを最大化するレコメンドシステムは作れることが示せたかと思います

ディープラーニングではなく勾配ブースティングを用いたのは、スパースマトリックの扱いやすさからなので、適応例によっては、ディープラーニングでも同等の仕組みは作れると考えています

膨大にある意思決定のサポートの一側面のみの利用法なので、全てではないし、他のアプローチもありますが、この方法だと考えることが少なく、簡単に適応できます(簡単にできることはとても重要だと思います)

参考文献