Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
サイバーエージェントにおける
MLOpsに関する取り組み

PyData.Tokyo Meetup #23
MLOps ~ AIを社会に届ける技術
芝田 将
2
Masashi Shibata
● 2017年新卒入社・AI Lab所属

● CyberAgent Developer Experts

● Optunaコミッター

● Kubeflow/Katibレビュアー

● go-prompt,...
3
今日の内容
機械学習を本番投入していくうえでの取り組み・Pythonまわりの実装テクニック
1. Dynalystの事例紹介
Cythonを使った高速化やメモリ管理
2. AirTrackの事例紹介
ハイパーパラメータ最適化の転移学習
3....
4
1 Dynalystの事例紹介
Cythonによる高速化やメモリ管理
5
Dynalystにおける機械学習
6
機械学習の重要性
スマートフォン向けのリターゲティング※1広告配
信プラットフォームを開発。
1. 表示する広告はオークションで決まる
2. SSPが、オークションを開始
3. DSPが、購買確率を予測し入札
(どの広告をいくらで表示したい...
7
Field-aware Factorization Machines
https://www.csie.ntu.edu.tw/~cjlin/papers/ffm.pdf
DynalystではCVR予測に利用している手法
● KDD CUPや...
8
LIBFFMへの
機能追加
LIBFFMには追加でパッチをあてて利用
● 遅れコンバージョン問題に対処するた
め、因果推論の手法を機械学習の損
失関数に組み込む。
● DynalystにおいてA/Bテストを実施し
た結果、売上が約30%増加...
9
高速化の重要性
2019年時点の公開情報でDynalystのトラ
フィックは、数十万リクエスト/秒。
→ 高いスループットが求められる。
大規模広告配信プロダクトの今後と課題 by 黒崎 優太 (Feb. 2019) より参照
広告の表示が...
10
Cythonによる
推論サーバーの高速化
11
推論サーバー (gRPC) 

学習パイプライン

推論サーバー

12
Cythonとは
PythonとC/C++の静的型システムを融合したプ
ログラミング言語。
● Cythonのソースコードを入力に、効率的な
C拡張モジュールを生成 ※1
● Pythonのスーパーセットな言語。静的型情
報を与えるほど高...
13
Cythonによる
高速化
必要なステップは主に次の5つ
1. Cythonファイルの用意 (拡張子は .pyx)
2. cdefキーワードによる静的な型宣言
3. コードアノテーションの確認 ※1
4. setuptoolsスクリプトを...
14
GILの解放
GIL (Global Interpreter Lock)
● GILを持つ(OSレベル)スレッドだけが
Pythonバイトコードを実行できる ※1
● マルチスレッドを使ってもプロセッサコアの
レベルでは並列に処理されない...
15
Cython Compiler
Directives
安全性を犠牲にしたさらなる高速化
● cdivision: ZeroDivisionError例外
● boundscheck: IndexError例外
● wraparound: ...
16
推論処理の
最適化結果
レイテンシーとスループットの向上。
● FFM推論時間 約10% (90%減少)
● 推論サーバー レイテンシー 約60%
● 推論サーバー スループット 1.35倍※1
※1 単純計算すると、仮にサーバー50台用...
17
LIBFFMバインディングの実装
参照カウントとNumPy C-API
18
C++ライブラリを
ラップする
1. cdef extern from による宣言。
2. PyMem_Malloc※1でC++構造体初期化。
3. C++コードの呼び出し。
4. 確保したメモリ領域を、PyMem_Freeで解
放。
#...
19
C++ (LIBFFM)
Cython
Pythonと連動したメモリ管理
重み配列のメモリ領域確保
ptr = malloc(n*m*k*sizeof(float))
FFMの学習
model = ffm.train()
C++関数呼び出...
20
参照カウント
Pythonのメモリ管理機構との連動。
● CPythonのメモリ管理は参照カウント※1
● Numpy配列(model._weights)が破棄され
ると同時に、C++配列のメモリ領域を解放し
たい。
● 右で参照カウント...
21
多次元配列のメモリレイアウト
>>> x = np.arange(12).reshape((3, 4), order='C')
>>> x
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, ...
22
NumPy C-API
コピーなしで安全にC++配列をラップする。
● LIBFFM側でmallocされたメモリ領域は
libc.stdlib.free()で解放
● PyArray_SimpleNewFromData:
C連続な配列のポ...
23
【余談】型付きメモリビュー
Cythonの中で扱うだけなら、型付きメモリビュー(Typed Memoryview)の利用を推奨。
# model_ptr.W は、C連続なshape=(n, m, k) の多次元配列
cdef ffm_pr...
24
● 機械学習モデルの精度が売上に直結
○ 因果推論の手法を使った遅れコンバージョン問題への対処
○ データコピーなしで安全に配列のメモリー領域を管理
● 大量のトラフィック、厳しいレイテンシー要件 (100ms以内)
○ Cythonを使...
25
2 AirTrackの事例紹介
OptunaとMLflowを使ったハイパー
パラメータ最適化の転移学習
26
AirTrackの学習パイプライン
27
AirTrack
位置情報を活用した来店計測や広告配信を行う
● オフラインの行動にもとづくターゲティングを
実現
● GPSから取得した位置情報を(個人に紐付
かない形で)利用
● ユーザやエリアの属性推定、店舗への来訪
予測に機械学習...
28
学習
パイプライン
学習パイプラインは、定期的に実行。新し
いデータでモデルを再学習。
● AWS Step Functionsで構築
● 各種メトリクスの収集やモデルの管
理にはMLflowを利用
最適なハイパーパラメーターもOptun...
29
MLflow
MLライフサイクルを管理するプラットフォー
ム。AirTrackでの用途は2つ。
● パラメーターやメトリクス 、アーティファ
クトを収集する。
● 学習済みモデルをバージョニング・管
理する。
# Experimentに紐...
30
Optunaの基礎知識と
Warm Starting CMA-ES
31
Optuna
PFN社が開発・公開しているハイパーパ
ラメータ最適化ライブラリ
● Define-by-Runスタイルによる柔
軟な探索空間の定義
● 豊富なアルゴリズムのサポート
● プラガブルなストレージバックエンド
● シンプルな分...
32
相関関係を
考慮する手法
探索空間が変化しない場合、ハイパーパ
ラメータ間の相関関係を考慮するアルゴリ
ズムが利用可能※1。

● 多変量TPE

● CMA Evolution Strategy

● ガウス過程ベースのベイズ最適化

...
33
CMA-ES
ブラックボックス最適化において最も有望な手法の
1つ※1。
● 多変量正規分布から解を生成し、その解の評
価値を利用して、より良い解を生成するような
分布に更新を行う手法
● PythonライブラリをGitHubで公開
○ ...
34
Warm Starting
CMA-ES
似たようなHPOタスクの試行結果を事前情報と
して活用することで効率的に探索。

● AI Lab 野村将寛が中心となり提案。

● AAAI 2021にて採択

● Optuna v2.6.0か...
35
前回の試行結果の活用
最新のデータの取得
学習パイプライン Optunaの実行
試行結果を保存
MLflow Artifact
最新のデータの取得
学習パイプライン Optunaの実行
試行結果を保存
MLflow Artifact
Ai...
36
Optuna + MLflowを使った
ハイパーパラメータ最適化転移学習
37
Optunaと
MLflowの連携
Optunaの試行結果(SQLite3)は、MLflowの
アーティファクトとして保存

1. 事前情報として利用する試行結果の
取り出し(後述)

2. デフォルトパラメータの評価

3. メトリクス...
38
前回の試行結果
の取得
先週のHPO試行結果を取り出す。
1. Model Registryから本番で利用して
いるモデルの情報を取得
2. Model InfoからRun IDを取得
3. RunのArtifactsからSQLite3フ...
39
MLflow UIから結果を確認
「デフォルトより良いハイパーパラメータが見つかっているかどうか」、
「デフォルトと比べてどのくらい評価指標(AUC)が改善したか」等を確認。
40
XGBoostデフォルト値からの改善量
単変量TPE (初回実行時) Warm Starting CMA-ES
AUC
(詳細は非公開
)
AUC
(詳細は非公開
)
Trial数 (評価回数) Trial数 (評価回数)
enqueue...
41
AirTrackでの取り組みまとめ
機械学習モデルの精度が売上に直結
● 継続的ハイパーパラメータ最適化の実施
● Warm Starting CMA-ESにより先週の試行結果を事前情報
として活用することで探索の効率を大幅に改善
● M...
42
3 AI Messanger Voicebotの事例紹介
軽量スレッドとWebSocket
43
AI Messanger
Voicebot
AI電話自動応対サービスを開発

● 東京都多摩市:コロナワクチン接種のAI予
約専用ダイアル

● 福井県との実証実験:

道路規制情報に関する電話応対→ 電話
による問い合わせ総数例年比22...
44
システム構成
twilioを経由してWebSocketで音声データをやりとり。
WebSocket
IP電話
45
機械学習システムの開発の難しさ
“In an ML project, the team usually includes data scientists or ML researchers,
who focus on explorator...
46
WSGIと軽量スレッド
47
Web Server Gateway Interface (PEP 3333)

● WSGIアプリケーションの実体は、callableなオブ
ジェクト (関数など)

● WebSocketのような双方向リアルタイム通信の実
現には工夫...
48
軽量スレッド (グリーンスレッド)
各WebSocketのコネクションにOSネイティブスレッド(threading.Thread)を1つ割り当て
たくない。
● OSのコンテキストスイッチが重たい
○ スレッドの状態(レジスタの内容)をメ...
49
Gevent-websocketの仕組み
50
Gevent
import threading
import time
thread1 = threading.Thread(target=time.sleep, args=(5,))
thread2 = threading.Thread...
51
Gevent
from gevent import monkey
monkey.patch_all()
import threading
import time
thread1 = threading.Thread(target=time...
52
from gevent import monkey
monkey.patch_all()
import threading
import time
thread1 = threading.Thread(target=time.sleep,...
53
WebSocket
Gevent-websocketの仕組み

● Gunicornワーカープロセス生成後に
Monkey patchを呼び出し

● WSGIアプリケーションは、gevent.

Greenlet(軽量スレッド)で呼び出...
54
【余談】ASGIについて
Asynchronous Server Gateway Interface
https://asgi.readthedocs.io/en/latest/specs/main.html
● Djangoコア開発者の...
まとめ
56
まとめ
機械学習システムの開発はさまざまな難しさがある。
今回の発表では社内サポートを通して取り組んできた内容を紹介。
● Cythonによる高速化
● C++ライブラリのラップとメモリ管理
● OptunaとMLflowを使ったハイパー...
WE’RE HIRING
57
ご応募お待ちしております
https://cyberagent.ai/careers/
Upcoming SlideShare
Loading in …5
×
Upcoming SlideShare
What to Upload to SlideShare
Next
Download to read offline and view in fullscreen.

18

Share

Download to read offline

サイバーエージェントにおけるMLOpsに関する取り組み at PyDataTokyo 23

Download to read offline

PyData.Tokyo Meetup #23 MLOps〜AIを社会に届ける技術での発表資料
https://pydatatokyo.connpass.com/event/210654/

Related Books

Free with a 30 day trial from Scribd

See all

サイバーエージェントにおけるMLOpsに関する取り組み at PyDataTokyo 23

  1. 1. サイバーエージェントにおける MLOpsに関する取り組み
 PyData.Tokyo Meetup #23 MLOps ~ AIを社会に届ける技術 芝田 将
  2. 2. 2 Masashi Shibata ● 2017年新卒入社・AI Lab所属
 ● CyberAgent Developer Experts
 ● Optunaコミッター
 ● Kubeflow/Katibレビュアー
 ● go-prompt, kube-prompt作者
 ● 共訳書 エキスパートPythonプログラミング 改訂2版
 @c-bata @c_bata_
  3. 3. 3 今日の内容 機械学習を本番投入していくうえでの取り組み・Pythonまわりの実装テクニック 1. Dynalystの事例紹介 Cythonを使った高速化やメモリ管理 2. AirTrackの事例紹介 ハイパーパラメータ最適化の転移学習 3. AI Messanger Voicebotの事例紹介 軽量スレッドとWebSocketサーバーの話
  4. 4. 4 1 Dynalystの事例紹介 Cythonによる高速化やメモリ管理
  5. 5. 5 Dynalystにおける機械学習
  6. 6. 6 機械学習の重要性 スマートフォン向けのリターゲティング※1広告配 信プラットフォームを開発。 1. 表示する広告はオークションで決まる 2. SSPが、オークションを開始 3. DSPが、購買確率を予測し入札 (どの広告をいくらで表示したいか) 4. 落札者を決定し、その広告を表示 ※1 一度反応のあった人(例: 過去にアプリを入れたことがある) に 再度興味を持ってもらえるようにアプローチする広告配信戦略。 機械学習によるユーザー購買確率 (CTR, CVR)の予測精度が売上に直結
  7. 7. 7 Field-aware Factorization Machines https://www.csie.ntu.edu.tw/~cjlin/papers/ffm.pdf DynalystではCVR予測に利用している手法 ● KDD CUPやKaggleのCTR, CVRの予測コ ンペで好成績を収める※1 ● 実装はLIBFFMを使用 (https://github.com/ycjuan/libffm) ● LIBFFMは、C++のライブラリでコマンドライ ンインターフェイスのみを提供 ※1 提案者のYuchin Juanさんらは、CTR予測のKaggleコンペでもFFMを使って2度優勝
  8. 8. 8 LIBFFMへの 機能追加 LIBFFMには追加でパッチをあてて利用 ● 遅れコンバージョン問題に対処するた め、因果推論の手法を機械学習の損 失関数に組み込む。 ● DynalystにおいてA/Bテストを実施し た結果、売上が約30%増加。 https://dl.acm.org/doi/10.1145/3366423.3380032 Pythonバインディングも自前で実 装する必要がある
  9. 9. 9 高速化の重要性 2019年時点の公開情報でDynalystのトラ フィックは、数十万リクエスト/秒。 → 高いスループットが求められる。 大規模広告配信プロダクトの今後と課題 by 黒崎 優太 (Feb. 2019) より参照 広告の表示が遅れないように、RTBでは大 体100ms以内にレスポンスを返さなければ ならない。 → レスポンスタイムも重要。 機械学習によるCTRやCVRの予測 (推 論処理) も高速化が不可欠。
  10. 10. 10 Cythonによる 推論サーバーの高速化
  11. 11. 11 推論サーバー (gRPC) 
 学習パイプライン
 推論サーバー

  12. 12. 12 Cythonとは PythonとC/C++の静的型システムを融合したプ ログラミング言語。 ● Cythonのソースコードを入力に、効率的な C拡張モジュールを生成 ※1 ● Pythonのスーパーセットな言語。静的型情 報を与えるほど高速なコードを生成 ● C/C++とPythonとのインターフェイスとして も利用可能 In [1]: %load_ext cython In [2]: def py_fibonacci(n): ...: a, b = 0.0, 1.0 ...: for i in range(n): ...: a, b = a + b, a ...: return a In [2]: %%cython ...: def cy_fibonacci(int n): ...: cdef int i ...: cdef double a = 0.0, b = 1.0 ...: for i in range(n): ...: a, b = a + b, a ...: return a In [4]: %timeit py_fibonacci(10) 582 ns ± 3.72 ns per loop (...) In [5]: %timeit cy_fibonacci(10) 43.4 ns ± 0.14 ns per loop (...) ※1 手書きのCコードより高速になることも珍しくない。また移植性も高く、多く のPythonバージョンやCコンパイラをサポートする。
  13. 13. 13 Cythonによる 高速化 必要なステップは主に次の5つ 1. Cythonファイルの用意 (拡張子は .pyx) 2. cdefキーワードによる静的な型宣言 3. コードアノテーションの確認 ※1 4. setuptoolsスクリプトを用意 5. C拡張モジュールのコンパイル from setuptools import setup, Extension from Cython.Build import cythonize setup( ..., ext_modules=cythonize([ Extension("foo", sources=["foo.pyx"]), ]), ) ※1 cythonコマンド(cython -a foo.pyx)や、cythonize(..., annotate=True)オプ ション、IPythonマジックコマンド(%%cython -a)で生成可能。 # cython: language_level=3 def predict(float[:, :, :] weights, float[:, :] x): cdef float result ... return result # Cレベル関数宣言 cdef float sigmoid(float x): return 1.0 / (1.0 + e ** (-x)) $ python setup.py build_ext --inplace
  14. 14. 14 GILの解放 GIL (Global Interpreter Lock) ● GILを持つ(OSレベル)スレッドだけが Pythonバイトコードを実行できる ※1 ● マルチスレッドを使ってもプロセッサコアの レベルでは並列に処理されない ● Python/C APIを利用せず、Pythonの データ構造にも触れない箇所では、GILを 明示的に解放できる ※2 def fibonacci(kwargs): cdef double a cdef int n n = kwargs.get('n') with nogil: a = fibonacci_nogil(n) return a cdef double fibonacci_nogil(int n) nogil: ... Python/C APIを呼ぶ行は黄 色く表示される。 純粋なCの関数 ※1 各スレッドは5ms実行するとロックを受け渡す ※2 Cのレベルでは、PY_BEGIN_ALLOW_THREADSマクロ / Py_END_ALLOW_THREADSマクロが呼ばれる。
  15. 15. 15 Cython Compiler Directives 安全性を犠牲にしたさらなる高速化 ● cdivision: ZeroDivisionError例外 ● boundscheck: IndexError例外 ● wraparound: Negative Indexing その他よく使うもの ● profile ● linetrace Python/C APIが一切使われ ない (ZeroDivisionError例 外を投げたりしない)
  16. 16. 16 推論処理の 最適化結果 レイテンシーとスループットの向上。 ● FFM推論時間 約10% (90%減少) ● 推論サーバー レイテンシー 約60% ● 推論サーバー スループット 1.35倍※1 ※1 単純計算すると、仮にサーバー50台用意して捌いていた場合、38台で十分 (12台分のコストも浮く)。
  17. 17. 17 LIBFFMバインディングの実装 参照カウントとNumPy C-API
  18. 18. 18 C++ライブラリを ラップする 1. cdef extern from による宣言。 2. PyMem_Malloc※1でC++構造体初期化。 3. C++コードの呼び出し。 4. 確保したメモリ領域を、PyMem_Freeで解 放。 # cython: language_level=3 from cpython.mem cimport PyMem_Malloc, PyMem_Free cdef extern from "ffm.h" namespace "ffm" nogil: struct ffm_problem: ffm_data* data ffm_model *ffm_train_with_validation(...) cdef ffm_problem* make_ffm_prob(...): cdef ffm_problem* prob prob = <ffm_problem *> PyMem_Malloc(sizeof(ffm_problem)) if prob is NULL: raise MemoryError("Insufficient memory for prob") prob.data = ... return prob def train(...): cdef ffm_problem* tr_ptr = make_ffm_prob(...) try: tr_ptr = make_ffm_prob(tr[0], tr[1]) model_ptr = ffm_train_with_validation(tr_ptr, ...) finally: free_ffm_prob(tr_ptr) return weights, best_iteration ※1 from libc.stdlib cimport malloc も利用できますが、PyMem_Mallocは CPythonのヒープからメモリ領域を確保するため、システムコールの発行回数を抑え ることができる。特に小さな領域はこちらから確保するほうが効率的
  19. 19. 19 C++ (LIBFFM) Cython Pythonと連動したメモリ管理 重み配列のメモリ領域確保 ptr = malloc(n*m*k*sizeof(float)) FFMの学習 model = ffm.train() C++関数呼び出し ffm_train_with_validation() Python 重み配列のメモリ領域解放 free(ptr) オブジェクトの破棄 del model 重み配列をNumPyでラップ (NumPy C-APIを利用) Pythonオブジェクト生成 model = ffm.train()
  20. 20. 20 参照カウント Pythonのメモリ管理機構との連動。 ● CPythonのメモリ管理は参照カウント※1 ● Numpy配列(model._weights)が破棄され ると同時に、C++配列のメモリ領域を解放し たい。 ● 右で参照カウントが2と表示されるのは、 sys.getrefcount()の呼び出し時に参照が 追加で発生するため。 import ffm import sys def main(): train_data = ffm.Dataset(...) valid_data = ffm.Dataset(...) # ‘model._weights’の実体は、libffmが確保したC++配列 # Pythonのメモリ管理と連動して適切に deallocateしたい model = ffm.train(train_data, valid_data) print(sys.getrefcount(model._weights)) # -> 2 del model # -> ‘model.weights’ is deallocated. print("Done") # -> Done ※1 循環参照による問題を解決するためだけに、Mark&Sweepライクな独自のGCも持っています。“ライク”とつけた理由は @atsuoishimoto氏の解説記事を参照 (URL: https://atsuoishimoto.hatenablog.com/entry/20110220/1298179766)。
  21. 21. 21 多次元配列のメモリレイアウト >>> x = np.arange(12).reshape((3, 4), order='C') >>> x array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]]) >>> x.flatten() array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) >>> x = np.arange(12).reshape((3, 4), order='F') >>> x array([[ 0, 3, 6, 9], [ 1, 4, 7, 10], [ 2, 5, 8, 11]]) >>> x.flatten() array([ 0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11]) ● Numpy配列作成関数(np.ones, np.empty等)のデフォルトレイアウトはすべてC連続。 ● Fortran連続なNumPy配列は、np.ones(..., order=’F’) のようにorder引数を指定。 C連続 (C contiguous) バッファがメモリの連続領域にまとまっていて、 最後の次元がメモリ内で連続 。 Fortran連続 (Fortran contiguous) バッファがメモリの連続領域にまとまっていて、 最初の次元がメモリ内で連続 。
  22. 22. 22 NumPy C-API コピーなしで安全にC++配列をラップする。 ● LIBFFM側でmallocされたメモリ領域は libc.stdlib.free()で解放 ● PyArray_SimpleNewFromData: C連続な配列のポインターとshape、型情 報を渡して、NumPy配列でラップ ● PyArray_SetBaseObject: NumPy配列の実体(C++配列のポインタ) を所有するオブジェクトを指定※1 ※1 cnp.set_array_base()が内部で呼び出す。この関数はbaseオブジェクトの参照カ ウントをついでにインクリメントしてくれる。これを忘れるとNumPy配列が生存している のに実体は解放されてしまうバグにつながるので注意。 cimport numpy as cnp from libc.stdlib cimport free cdef class _weights_finalizer: cdef void *_data def __dealloc__(self): if self._data is not NULL: free(self._data) cdef object _train(...): cdef: cnp.ndarray arr _weights_finalizer f = _weights_finalizer() model_ptr = ffm_train_with_validation(...) shape = (model_ptr.n, model_ptr.m, model_ptr.k) # FFMの重みベクトル model_ptr.W をNumpy配列でラップ arr = cnp.PyArray_SimpleNewFromData( 3, shape, cnp.NPY_FLOAT32, model_ptr.W) f._data = <void*> model_ptr.W cnp.set_array_base(arr, f) free(model_ptr) return arr, best_iteration
  23. 23. 23 【余談】型付きメモリビュー Cythonの中で扱うだけなら、型付きメモリビュー(Typed Memoryview)の利用を推奨。 # model_ptr.W は、C連続なshape=(n, m, k) の多次元配列 cdef ffm_problem* model_ptr model_ptr = train_with_validation(...) # 最後の次元のストライドを 1に(最後の次元が連続 )、それ以外をコロン 1つに設定する ※1 cdef ffm_float[:, :, ::1] mv = <ffm_float[:model_ptr.n, :model_ptr.m, :model_ptr.k]> model_ptr.W print("配列のsize:", mv.size) print("配列のshape:", mv.shape) print("weights[0, 0, 0] の値:", mv[0, 0, 0]) ※1 Fortran連続(最初の次元が連続)な配列の型付きメモリービューは、 cdef ffm_float[::1, :, :] mv のように宣言する。他のレイアウトにも対応できるが、C連続 or Fortran連 続にしておくとCythonはストライドアクセスを計算に入れないより効率的なコードを生成できる。 ● memoryview風のインターフェイスを提供。Cレベルでバッファを直接操作するより簡単。 ● Pythonのオーバーヘッドがかからない(Python/C APIを呼び出さない)。GILも解除可能。
  24. 24. 24 ● 機械学習モデルの精度が売上に直結 ○ 因果推論の手法を使った遅れコンバージョン問題への対処 ○ データコピーなしで安全に配列のメモリー領域を管理 ● 大量のトラフィック、厳しいレイテンシー要件 (100ms以内) ○ Cythonを使った推論処理の高速化 ○ スループット1.35倍、レイテンシー60% Dynalystでの取り組みまとめ
  25. 25. 25 2 AirTrackの事例紹介 OptunaとMLflowを使ったハイパー パラメータ最適化の転移学習
  26. 26. 26 AirTrackの学習パイプライン
  27. 27. 27 AirTrack 位置情報を活用した来店計測や広告配信を行う ● オフラインの行動にもとづくターゲティングを 実現 ● GPSから取得した位置情報を(個人に紐付 かない形で)利用 ● ユーザやエリアの属性推定、店舗への来訪 予測に機械学習を利用 機械学習モデルの性能がそのまま プロダクトの武器になる
  28. 28. 28 学習 パイプライン 学習パイプラインは、定期的に実行。新し いデータでモデルを再学習。 ● AWS Step Functionsで構築 ● 各種メトリクスの収集やモデルの管 理にはMLflowを利用 最適なハイパーパラメーターもOptuna で毎回探索する。
  29. 29. 29 MLflow MLライフサイクルを管理するプラットフォー ム。AirTrackでの用途は2つ。 ● パラメーターやメトリクス 、アーティファ クトを収集する。 ● 学習済みモデルをバージョニング・管 理する。 # Experimentに紐付け mlflow.set_experiment("train_foo_model") # MLflow Runを新しく生成 & 紐付け with mlflow.start_run(run_name="...") as run: # MLflow Model Registryにモデルを登録 model = train(...) mv = mlflow.register_model(model_uri, model_name) MlflowClient().transition_model_version_stage( name=model_name, version=mv.version, stage="Production" ) # パラメーターを保存 (Key-Value形式) mlflow.log_param("auc", auc) # メトリクスを保存 (Key-Value形式) mlflow.log_metric("logloss", log_loss) # アーティファクトを保存 # (RDBではなくS3とかで管理したいファイル等 ) mlflow.log_artifacts(dir_name) MLflowの用語 1. Run: 1回の実行単位 2. Experiment: Runをグルーピング
  30. 30. 30 Optunaの基礎知識と Warm Starting CMA-ES
  31. 31. 31 Optuna PFN社が開発・公開しているハイパーパ ラメータ最適化ライブラリ ● Define-by-Runスタイルによる柔 軟な探索空間の定義 ● 豊富なアルゴリズムのサポート ● プラガブルなストレージバックエンド ● シンプルな分散最適化機構 ● リッチなWeb Dashboard https://github.com/optuna/optuna import optuna def objective(trial): regressor_name = trial.suggest_categorical( 'classifier', ['SVR', 'RandomForest'] ) if regressor_name == 'SVR': svr_c = trial.suggest_float('svr_c', 1e-10, 1e10, log=True) regressor_obj = sklearn.svm.SVR(C=svr_c) else: rf_max_depth = trial.suggest_int('rf_max_depth', 2, 32) regressor_obj = RandomForestRegressor(max_depth=rf_max_depth) X_train, X_val, y_train, y_val = ... regressor_obj.fit(X_train, y_train) y_pred = regressor_obj.predict(X_val) return sklearn.metrics.mean_squared_error(y_val, y_pred) study = optuna.create_study() study.optimize(objective, n_trials=100)
  32. 32. 32 相関関係を 考慮する手法 探索空間が変化しない場合、ハイパーパ ラメータ間の相関関係を考慮するアルゴリ ズムが利用可能※1。
 ● 多変量TPE
 ● CMA Evolution Strategy
 ● ガウス過程ベースのベイズ最適化
 ※1 デフォルトのアルゴリズムである単変量TPEは、ハイパーパラメータ間の相関関係を考慮しない。 ※2 画像は http://proceedings.mlr.press/v80/falkner18a/falkner18a-supp.pdf より参照 最適解の位置が図左のように右上と左下にあるケースを想定 ※2。 相関関係を考慮しない手法では真ん中のように左上や右下も多く探索 def objective(trial): x = trial.suggest_float('x', -10, 10) y = trial.suggest_float('y', -10, 10) v1 = (x-5)**2 + (y-5)**2 v2 = (x+5)**2 + (y+5)**2 return min(v1, v2)
  33. 33. 33 CMA-ES ブラックボックス最適化において最も有望な手法の 1つ※1。 ● 多変量正規分布から解を生成し、その解の評 価値を利用して、より良い解を生成するような 分布に更新を行う手法 ● PythonライブラリをGitHubで公開 ○ https://github.com/CyberAgent/cmaes ○ Optunaからも利用が可能
 Optuna公式ブログに投稿した解説記事。 https://medium.com/optuna/introduction-to-cma-es-sampler-ee68194c8f88 
 ※1 N. Hansen, The CMA Evolution Strategy: A Tutorial. arXiv:1604.00772, 2016.

  34. 34. 34 Warm Starting CMA-ES 似たようなHPOタスクの試行結果を事前情報と して活用することで効率的に探索。
 ● AI Lab 野村将寛が中心となり提案。
 ● AAAI 2021にて採択
 ● Optuna v2.6.0から利用可能
 # 事前情報として利用する試行結果を SQLite3のファイルから取り出す source_study = optuna.load_study( storage="sqlite:///source-db.sqlite3", study_name="..." ) source_trials = source_study.trials # ターゲットタスクのハイパーパラメータ最適化を実行 study = optuna.create_study( sampler=CmaEsSampler(source_trials=source_trials), storage="sqlite:///db.sqlite3", study_name="..." ) study.optimize(objective, n_trials=20) https://www.cyberagent.co.jp/news/detail/id=25561 https://github.com/optuna/optuna/releases/tag/v2.6.0
 利用例: 毎週新しいデータでHPOを実行 する際に、先週のHPO試行結果を事前 情報として活用し効率化。
  35. 35. 35 前回の試行結果の活用 最新のデータの取得 学習パイプライン Optunaの実行 試行結果を保存 MLflow Artifact 最新のデータの取得 学習パイプライン Optunaの実行 試行結果を保存 MLflow Artifact AirTrackの来訪予測モデル(XGBoost)で、 オフラインでの性能検証を行い、Optunaのデフォルト 最適化手法と比べて約2倍の高速化 を実現 1週間後
  36. 36. 36 Optuna + MLflowを使った ハイパーパラメータ最適化転移学習
  37. 37. 37 Optunaと MLflowの連携 Optunaの試行結果(SQLite3)は、MLflowの アーティファクトとして保存
 1. 事前情報として利用する試行結果の 取り出し(後述)
 2. デフォルトパラメータの評価
 3. メトリクスの収集
 4. 試行結果のアップロード
 mlflow.set_experiment("train_foo_model") with mlflow.start_run(run_name="...") as run: # 事前情報として利用する試行結果を取り出す(後述) source_trials = ... sampler = CmaEsSampler(source_trials=source_trials) # 最初にXGBoostのデフォルトパラメーターを挿入 # (デフォルトよりも悪くならないことを保証する ) study.enqueue_trial({"alpha": 0.0, ...}) study.optimize(optuna_objective, n_trials=20) # 最適化にまつわるメトリクスの保存 mlflow.log_params(study.best_params) mlflow.log_metric("default_trial_auc", study.trials[0].value) mlflow.log_metric("best_trial_auc", study.best_value) # 探索空間の変化に対応するためのタグ mlflow.set_tag("optuna_objective_ver", optuna_objective_ver) # Optunaの最適化結果(db.sqlite3)をアーティファクトに保存 mlflow.log_artifacts(dir_name)
  38. 38. 38 前回の試行結果 の取得 先週のHPO試行結果を取り出す。 1. Model Registryから本番で利用して いるモデルの情報を取得 2. Model InfoからRun IDを取得 3. RunのArtifactsからSQLite3ファイ ルを取得 4. Optunaの探索空間が変化していな いことは、タグで識別 def load_optuna_source_storage(): client = MlflowClient() try: model_infos = client.get_latest_versions( model_name, stages=["Production"]) except mlflow_exceptions.RestException as e: if e.error_code == "RESOURCE_DOES_NOT_EXIST": # 初回実行時は、ここに到達する。 return None raise if len(model_infos) == 0: return None run_id = model_infos[0].run_id run = client.get_run(run_id) if run.data.tags.get("optuna_obj_ver") != optuna_obj_ver: return None filenames = [a.path for a client.list_artifacts(run_id)] if optuna_storage_filename not in filenames: return None client.download_artifacts(run_id, path=..., dst_path=...) return RDBStorage(f"sqlite:///path/to/optuna.db")
  39. 39. 39 MLflow UIから結果を確認 「デフォルトより良いハイパーパラメータが見つかっているかどうか」、 「デフォルトと比べてどのくらい評価指標(AUC)が改善したか」等を確認。
  40. 40. 40 XGBoostデフォルト値からの改善量 単変量TPE (初回実行時) Warm Starting CMA-ES AUC (詳細は非公開 ) AUC (詳細は非公開 ) Trial数 (評価回数) Trial数 (評価回数) enqueue_trialで挿入したXGboostのデ フォルトハイパーパラメータ Warm Starting CMA-ESが有望領域を中心に探索 序盤からデフォルトハイパーパラメータよりも良い解を見つける。
  41. 41. 41 AirTrackでの取り組みまとめ 機械学習モデルの精度が売上に直結 ● 継続的ハイパーパラメータ最適化の実施 ● Warm Starting CMA-ESにより先週の試行結果を事前情報 として活用することで探索の効率を大幅に改善 ● MLflowとOptunaを使ってシステム導入
  42. 42. 42 3 AI Messanger Voicebotの事例紹介 軽量スレッドとWebSocket
  43. 43. 43 AI Messanger Voicebot AI電話自動応対サービスを開発
 ● 東京都多摩市:コロナワクチン接種のAI予 約専用ダイアル
 ● 福井県との実証実験:
 道路規制情報に関する電話応対→ 電話 による問い合わせ総数例年比228%へ増 加見込み
 機械学習の精度がユーザー体験に直結
 https://www.cyberagent.co.jp/news/detail/id=25849
  44. 44. 44 システム構成 twilioを経由してWebSocketで音声データをやりとり。 WebSocket IP電話
  45. 45. 45 機械学習システムの開発の難しさ “In an ML project, the team usually includes data scientists or ML researchers, who focus on exploratory data analysis, model development, and experimentation. These members might not be experienced software engineers who can build production-class services.” ー MLOps: Continuous delivery and automation... (cloud.google.com)
 開発初期の頃、Flaskで実装したWebSocketサーバーが
 特定の条件下で固まって動かなくなると相談を受ける。
 結構ややこしく、わかりやすい資料もないためハマる人が多そうな問題。

  46. 46. 46 WSGIと軽量スレッド
  47. 47. 47 Web Server Gateway Interface (PEP 3333)
 ● WSGIアプリケーションの実体は、callableなオブ ジェクト (関数など)
 ● WebSocketのような双方向リアルタイム通信の実 現には工夫が必要 ※1
 ● WSGIアプリケーションを呼び出したスレッドは、通 信が終わるまで処理を離せない
 ● Websocketの接続数分のスレッドが必要
 WSGIの制限 ※1 AI Messanger Voicebotの開発初期に使われていた Flask-sockets (Kenneth Reitz作)では、事前に生成した WebsocketオブジェクトをWSGI environmentから渡してFlaskから使えるようにしている。 
 def application(env, start_response): start_response('200 OK', [ ('Content-type', 'text/plain; charset=utf-8') ]) return [b'Hello World']
  48. 48. 48 軽量スレッド (グリーンスレッド) 各WebSocketのコネクションにOSネイティブスレッド(threading.Thread)を1つ割り当て たくない。 ● OSのコンテキストスイッチが重たい ○ スレッドの状態(レジスタの内容)をメモリにダンプして、別のスレッドの状態をメ モリから読み出し処理を再開する ● スレッドのスタックサイズが大きい ○ (システムによりますが) 2MBなど ユーザーランドで動作するスレッドのようなもの (軽量スレッド) を利用したい。 → Flask-socketsはGevent-websocketを内部で利用
  49. 49. 49 Gevent-websocketの仕組み
  50. 50. 50 Gevent import threading import time thread1 = threading.Thread(target=time.sleep, args=(5,)) thread2 = threading.Thread(target=time.sleep, args=(5,)) thread1.start() thread2.start() thread1.join() thread2.join() 実行するとスレッドが 2つ生成され 並行に処理される。
  51. 51. 51 Gevent from gevent import monkey monkey.patch_all() import threading import time thread1 = threading.Thread(target=time.sleep, args=(5,)) thread2 = threading.Thread(target=time.sleep, args=(5,)) thread1.start() thread2.start() thread1.join() thread2.join() Geventを使うと、1スレッドしかないにもか かわらず並行に処理されている。
  52. 52. 52 from gevent import monkey monkey.patch_all() import threading import time thread1 = threading.Thread(target=time.sleep, args=(5,)) # -> gevent.Greenlet(gevent.sleep, 5) ... Gevent 標準ライブラリのブロックする操作を置き換え threading.Thread → gevent.Greenlet (軽量スレッド) time.sleep → gevent.sleep
  53. 53. 53 WebSocket Gevent-websocketの仕組み
 ● Gunicornワーカープロセス生成後に Monkey patchを呼び出し
 ● WSGIアプリケーションは、gevent.
 Greenlet(軽量スレッド)で呼び出し
 ● Monkey patchが対応していないものを使 うとブロック
 from gevent.pool import Pool from gevent import hub, monkey, socket, pywsgi class GeventWorker(AsyncWorker): def init_process(self): # プロセス生成後に Monkeypatchをあてる monkey.patch_all() ... def run(self): servers = [] for s in self.sockets: # Greenlet(軽量スレッド)プールを用意 pool = Pool(self.worker_connections) environ = base_environ(self.cfg) environ.update({"wsgi.multithread": True}) server = self.server_class( s, application=self.wsgi, ... ) server.start() servers.append(server) gunicorn/workers/ggevent.py#L37-L38 このプロダクトでは、Text-to-Speech APIのSDK が内部でgRPC 双方向ストリーミングを利用して いたことが原因でした。
  54. 54. 54 【余談】ASGIについて Asynchronous Server Gateway Interface https://asgi.readthedocs.io/en/latest/specs/main.html ● Djangoコア開発者のAndrew Godwinを中心に仕様を策定 ○ WebSocketもサポートできるようなインターフェイス ○ ASGI互換ライブラリ: Uvicorn, Starlette (FastAPI)など ● とはいえ、まだまだこれから... ○ DB-API(PEP 249)の非同期インターフェイスの不在 ○ SQLAlchemyの非同期対応もPostgreSQL(asyncpg)のみ ● WebSocketが必要なときなどは適宜使っていきましょう
  55. 55. まとめ
  56. 56. 56 まとめ 機械学習システムの開発はさまざまな難しさがある。 今回の発表では社内サポートを通して取り組んできた内容を紹介。 ● Cythonによる高速化 ● C++ライブラリのラップとメモリ管理 ● OptunaとMLflowを使ったハイパーパラメータ最適化の転移学習 ● WSGIとGevent-websocketの仕組み
  57. 57. WE’RE HIRING 57 ご応募お待ちしております https://cyberagent.ai/careers/
  • moritata

    Jul. 28, 2021
  • KoheiSenkawa

    Jun. 15, 2021
  • IsseiMori1

    May. 31, 2021
  • KanTorii

    May. 28, 2021
  • KenTakehara

    May. 28, 2021
  • HaradaTomoki

    May. 28, 2021
  • MasaruOokawa

    May. 27, 2021
  • MakotoTakizawa

    May. 27, 2021
  • NobuakiSakuragi

    May. 27, 2021
  • YasunobuIgarashi

    May. 27, 2021
  • YukiIsoo

    May. 27, 2021
  • YuriUshigome

    May. 27, 2021
  • HiroyukiNishizaki1

    May. 27, 2021
  • takatsugunokubi

    May. 27, 2021
  • drillbits

    May. 27, 2021
  • ssuser359f79

    May. 27, 2021
  • ybjack

    May. 26, 2021
  • tsubame9590206

    May. 26, 2021

PyData.Tokyo Meetup #23 MLOps〜AIを社会に届ける技術での発表資料 https://pydatatokyo.connpass.com/event/210654/

Views

Total views

10,682

On Slideshare

0

From embeds

0

Number of embeds

3,354

Actions

Downloads

55

Shares

0

Comments

0

Likes

18

×