[TensorFlow Probability] 一般化線形モデル(GLM)で Google Analytics コンバージョン予測

Miki Katsuragi
google-cloud-jp
Published in
12 min readAug 30, 2019

TensorFlow Probability(TFP)は TensorFlow に基づいて作成された確率的推論と統計的分析のための Python ライブラリです。TFP を使用すると、最新のハードウェア(TPU、GPU)上で確率モデルとディープ ラーニングを組み合わせることができます。ここでは、TensorFlow Probability の高位 API tfp.glm を使って Web サイト訪問者の購買予測をするモデルを作ってみます。ソースコード全体はこちらを参照ください。

Google Merchandice Store

Google Analytics の Web サイト訪問データは、BigQuery の Public データセット “bigquery-public-data.google_analytics_sample” でどなたでも入手可能です。このデータは Google Merchandice Store という実在の EC サイトの Google Analytics のデータです。今回は以下のような SQL で買ったか買わないか(totals.transaction に数値がある場合は 1)を “label” というカラム名にしてセッションデータを抽出し Data Frame として読み込みます。

import tensorflow as tf
import tensorflow_probability as tfp
import pandas as pd
import numpy as np
# 以下はBigQuery に接続する場合に実行
from google.cloud import bigquery
from google.colab import auth
auth.authenticate_user()
#@title プロジェクト変数の設定 { run: "auto", display-mode: "form" }
project_id = 'test' #@param {type:"string"}
#dataset_name = "cycle" #@param {type:"string"}
#eval_name = model_name + "_eval"
client = bigquery.Client(project=project_id)
#GA360 session データ取得
query = """
SELECT
IF(totals.transactions IS NULL, 0, 1) AS label,
IFNULL(device.operatingSystem, "") AS os,
device.isMobile AS is_mobile,
IFNULL(geoNetwork.country, "") AS country,
IFNULL(totals.pageviews, 0) AS pageviews
FROM
`bigquery-public-data.google_analytics_sample.ga_sessions_*`
WHERE _TABLE_SUFFIX BETWEEN '20160801' AND '20170631'
LIMIT 100000;
"""
df = client.query(query).to_dataframe()
df.head()

なお、上記はcolaboratoryでの実行を想定していますが、マネージド型の JupyterLab ノートブック インスタンス AI Platform Notebookでもこのコードを実行可能です。 AI Platform Notebookを使う場合は以下のようにデータを取得します。

%load_ext google.cloud.bigquery
%%bigquery df
SELECT
IF(totals.transactions IS NULL, 0, 1) AS label,
IFNULL(device.operatingSystem, "") AS os,
device.isMobile AS is_mobile,
IFNULL(geoNetwork.country, "") AS country,
IFNULL(totals.pageviews, 0) AS pageviews
FROM
`bigquery-public-data.google_analytics_sample.ga_sessions_*`
WHERE _TABLE_SUFFIX BETWEEN '20160801' AND '20170631'
LIMIT 100000;

今回はわかりやすくするため pageview から label(購買有無) をコンバージョンとして予測するシンプルなロジスティック回帰モデルを作ります。学習データとテストデータに分割し、全て1の切片項を追加しましょう。

from sklearn.model_selection import train_test_split
Y = df.label
X=df.pageviews
X_train, X_test, y_train, y_test = train_test_split(X, Y,train_size=0.8)
#切片項追加
from statsmodels import api as sm
X_train = sm.add_constant(X_train)

データが準備できたので tfp.glm で学習してみます。ロジスティック回帰を行うためモデルにはベルヌーイ分布を指定しています。学習自体は一瞬で終わり、coeffs をprintすると、shape=(2,)というように切片と傾きが学習できていることが確認できます。

features = X_train.values
labels = np.array(y_train,dtype='float64')
# Specify model.
model = tfp.glm.Bernoulli()
# Fit model given data.
coeffs, linear_response, is_converged, num_iter = tfp.glm.fit(
model_matrix=features,
response=labels,
model=model)
print(coeffs)WARNING: Logging before flag parsing goes to stderr.
W0827 03:37:19.371737 140339571730304 deprecation.py:323] From /usr/local/lib/python3.6/dist-packages/tensorflow_probability/python/glm/fisher_scoring.py:352: add_dispatch_support.<locals>.wrapper (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.where in 2.0, which has the same broadcast rule as np.where
Tensor("fit/while/Exit_2:0", shape=(2,), dtype=float64)

今回はロジスティック回帰分析で訪問者の購買を予測したいので、生成された線形予測子(linear_response)を確率に変換するシグモイド関数を自前で定義します。

def sigmoid(x):
return 1 / (1 + np.exp(-x))
sigmoid(sess.run(linear_response))
array([0.00394942, 0.00394942, 0.00394942, ..., 0.00441452, 0.00441452,
0.00394942])

予測結果をみてみると全体的に 0.3〜0.4% 前後と値が小さすぎるように見られます。買う人がそもそも少なすぎるのかもしれません。そこでnp.unique関数を使ってデータのバランスを参照してみます。

X = df[["os", "is_mobile", "country", "pageviews"]]
y = df["label"]
np.unique(y, return_counts = True)
(array([0, 1]), array([15835, 165]))

16000の学習データで買う人はわずか165人となっていました。不均衡データなので極端に買わないと予測するモデルになっていたようです。これに対処するためover samplingしてみましょう。ここではimbalanced-learnライブラリを使ってランダムにover sampling してみます。

from imblearn.over_sampling import RandomOverSampler
ros = RandomOverSampler(ratio = {0:16000, 1:16000})
X_resample, y_resample = ros.fit_sample(X_train, y_train)
X_resample.shape
(32000, 2)

買った人、買わない人いずれも16000件となるようにover samplingしました。このデータを使って再度学習します。

features = X_resample.astype(np.float64)
labels = np.array(y_resample,dtype='float64')
# Specify model.
model = tfp.glm.Bernoulli()
# Fit model given data.
coeffs, linear_response, is_converged, num_iter = tfp.glm.fit(
model_matrix=features,
response=labels,
model=model)
sess.run(linear_response)
Tensor("fit_1/while/Exit_2:0", shape=(2,), dtype=float64)
array([-3.40057161, -3.40057161, -3.04961137, ..., 1.16191145,
16.95512205, 3.6186331 ])

linear_responseを参照してみると先程より大きな値になっていることが確認できます。学習データの confusion matrix をみてみましょう。

from sklearn.metrics import confusion_matrix
y_pred=sigmoid(sess.run(linear_response))
labels_pred=np.where(y_pred>0.5,1,0)
confusion_matrix(y_resample, labels_pred)
array([[14689, 1311],
[ 1067, 14933]])

実測と予測のバランスは悪くなさそうなので、このモデルを使ってテストデータで検証してみます。

b=sess.run(coeffs)
#テストデータで検証
y_pred_test = sigmoid(b[0]+b[1]*X_test)
labels_pred_test=np.where(y_pred_test>0.5,1,0)
confusion_matrix(y_test, labels_pred_test)
array([[2932, 240],
[ 1, 27]])

この結果 Recall=0.96(買った人28人中27人適合) となりました。

BigQueryMLとの比較

比較のため BigQuery の SQL で ML モデルを作成できる BigQuery ML(BQML) でも同様のモデルを作ってみます。上記 over sampling 済みのデータを任意のデータセットにテーブルとして取り込めば、あとは以下のSQL でロジスティック回帰モデルが作れます。

#standardSQL, oversamplingしたデータを取り込み済み
CREATE OR REPLACE MODEL `bqml_codelab.sample_model`
OPTIONS(model_type='logistic_reg') AS
SELECT
label AS label,
pageview
FROM `bqml_codelab.ga360_oversample_train`

生成されたモデルは、しきい値0.5060で再現率0.9323という結果になりました。

AutoML Tables

AutoML Tablesでも試してみました。その結果Recall 93.6%となりました。精度も91.9%とBigQueryML(92.5%)と近く、全体的に指標にはそれほど大きな差はないように見られます。

まとめ

一般化線形モデルの tfp.glm.fit 関数は慣れれば直感的に使えるので工夫すれば活用の幅が広がりそうだと思いました。ただ、予測時に線形予測子を自分で sigmoid 関数に適用する手間などが必要だったので、ロジスティック回帰のような単純なモデルなら他のフレームワークですませたほうが手っ取り早そうな気もします。上記のBigQuery ML の場合数行の SQL だけで同様のモデルを作れるので、tfp も今後のアップデートでもう少しお手軽に使えるようになると良いですね。

--

--