特徴量エンジンKaskada利用イメージ: 機械学習モデルのトレーニング

# はじめに

先日、以下の記事を発表しました。

https://qiita.com/yoshiyuki_kono/items/7dc7bdad5e02ff986f5d

新しいテクノロジーに触れる時はいつでも楽しいものです。

まだまだ勉強中ですが、公式ドキュメントの学習過程の記録として、以下の記事をまとめてみました。

本稿情報のソースとして、下記ドキュメントを参照ください。

https://github.com/kaskada-ai/kaskada/blob/main/examples/ML_example.ipynb

## このサンプルについて

この例では、ゲームプレイヤーの行動に関するイベントを扱います。
ユーザーがアップグレードの料金を支払うかどうかを予測する機械学習モデルを構築するためのトレーニングデータをKaskadaを使って準備します。

## Kaskada 環境のセットアップ
```
!pip install kaskada
```
```python
from kaskada.api.session import LocalBuilder
session = LocalBuilder().build()
%load_ext fenlmagic
```
### サンプルデータセットの構築

この例では、ゲームプレイヤーの行動に関するイベントを扱います。

イベントには、ユーザーが勝った、負けた、そして物の購入の３種類があります。

イベントは 2 つの CSV ファイルに保存されているものとします（ここではテストデータの作成をJupyterノートブック上で行います）。

`game_play.csv`には、プレイヤーが行ったゲームの勝敗に関するイベント情報が含まれています。

```python
%%writefile game_play.csv
event_at,entity_id,duration,won
2022-01-01 02:30:00+00:00,Alice,10,true
2022-01-01 02:35:00+00:00,Bob,3,false
2022-01-01 03:46:00+00:00,Bob,8,false
2022-01-01 03:58:00+00:00,Bob,23,true
2022-01-01 04:25:00+00:00,Bob,8,true
2022-01-01 05:05:00+00:00,Alice,53,true
2022-01-01 05:36:00+00:00,Alice,2,false
2022-01-01 07:22:00+00:00,Bob,7,false
2022-01-01 08:35:00+00:00,Alice,5,false
2022-01-01 10:01:00+00:00,Alice,43,true
```
`purchase.csv`は、プレイヤーが購買を行った時に記録されるイベント情報が含まれています。
```python
%%writefile purchase.csv
event_at,entity_id
2022-01-01 01:02:00+00:00,Alice
2022-01-01 01:35:00+00:00,Alice
2022-01-01 03:51:00+00:00,Bob
```

## Kaskadaテーブルの作成とデータ登録

上記で書き出したcsvファイルを Kaskada にロードします。 テーブルが作成されると、そのテーブルは Kaskada 環境に保持されます。

```python
import kaskada.table as ktable

# Create table objects in Kaskada.
ktable.create_table(
  table_name = "GamePlay",
  entity_key_column_name = "entity_id",
  time_column_name = "event_at",
  grouping_id = "User",
)
```
```python
ktable.create_table(
  table_name = "Purchase",
  entity_key_column_name = "entity_id",
  time_column_name = "event_at",
  grouping_id = "User",
)
```

```python
# Load the data into the Purchase table
ktable.load(table_name="GamePlay", file="game_play.csv")
```

```python
# Load the data into the Purchase table
ktable.load(table_name="Purchase", file="purchase.csv")
```

### 登録データの確認

```python
# Get the table after loading data
ktable.get_table("GamePlay")
```
```python
%%fenl
# Query the table to see that data has been loaded
GamePlay
```

## Step 1: 特徴量定義

ユーザーがアップグレードの料金を支払うかどうかを予測したいと考えています。初めのステップは、イベントから特徴量を計算することです。 最初の特徴量として、ユーザーがゲームで負けたときに費やした時間を利用します。負けが多いユーザーはおそらくアップグレードにお金を払う可能性が高くなります。

```python
%%fenl

let GameDefeat = GamePlay | when(not(GamePlay.won))

let features = {
    loss_duration: sum(GameDefeat.duration) }

in features
```

結果は、この特徴量が時間の経過とともにどのように変化したかを表す「ステップ関数」を示すタイムラインデータです。 元のイベントが発生した時間に関係なく、いつでもこのステップ関数の値を「観察」できます。

### ステップ関数
ステップ関数（Step function）とは、関数への入力値が0未満の場合には常に出力値が0、入力値が0以上の場合には常に出力値が1となるような関数です。AIの分野では、ステップ関数は、ニューラルネットワーク（その基礎となっているパーセプトロン）の活性化関数として知られていますが、ここでは、純粋に上記の意味で使われています。

注目すべき点は、これらの結果がユーザーごとに自動的にグループ化されることです。 Kaskada のテーブルは各行に関連付けられた「エンティティ」を指定しているため、ユーザーごとに明示的にグループ化する必要はありません。

## Step 2: 予想時点の定義 


2 番目のステップは、予測が行われた時点での特徴量を観察することです。 ゲーム設計者が、ユーザーが 2 回連続でゲームに負けるたびにアップグレードを提供したいと考えているとします。 ユーザーが 2 回連続で負けたときの特徴量を観察することで、この予測時間に関連付けられた一連の例を構築できます。

```python
%%fenl

let GameDefeat = GamePlay | when(not(GamePlay.won))

let features = {
    loss_duration: sum(GameDefeat.duration) }

let is_prediction_time = not(GamePlay.won) and count(GameDefeat, window=since(GamePlay.won)) == 2

let example = features | when(is_prediction_time)
    
in example
```


このクエリは一連の例を提供します。各例には、予測を行いたい特定の時点で計算された入力特徴量が含まれています。

## Step 3: サンプルデータのシフト　


3 番目のステップは、予測している結果が観察される時点に各サンプルを移動することです。 ユーザーにアップグレードのオファーを確認し、受け入れるかどうかを決定し、支払いを行うまでの時間を与えたいと考えています。オファーを行ってから 1 時間後にユーザーが受け入れたかどうかを確認してみましょう。

```javascript
%%fenl

let GameDefeat = GamePlay | when(not(GamePlay.won))

let features = {
    loss_duration: sum(GameDefeat.duration) }

let is_prediction_time = not(GamePlay.won) and (count(GameDefeat, window=since(GamePlay.won)) == 2)

let example = features | when(is_prediction_time) | shift_by(seconds(60*10))

in example
```

トレーニング例は、予測したいラベルが観察できる時点に移動しました。 時間列の値が前のステップより 1 時間遅れていることに注意してください。

## Step 4: 予測ラベルのサンプル 


最後のステップは、予測が行われた後に購入が行われたかどうかを確認することです。 これが目標値となり、現在特徴量が含まれているレコードに追加されます。

```javascript
%%fenl --var training

let GameDefeat = GamePlay | when(not(GamePlay.won))

let features = {
    loss_duration: sum(GameDefeat.duration),
    purchase_count: count(Purchase) }

let is_prediction_time = not(GamePlay.won) and (count(GameDefeat, window=since(GamePlay.won)) == 2)

let example = features | when(is_prediction_time)
    | shift_to(time_of($input) | add_time(seconds(60*10)))

let target = count(Purchase) > (example.purchase_count | else(0))
    
in extend(example, {target}) | when(is_valid($input.loss_duration))
```

## モデルのトレーニング


正しい時点での特徴とラベルを観察したので、例からモデルをトレーニングできます。

```python
from sklearn.linear_model import LogisticRegression
from sklearn import preprocessing

X = training.dataframe[['loss_duration']]
y = training.dataframe['target']

scaler = preprocessing.StandardScaler().fit(X)
X_scaled = scaler.transform(X)


model = LogisticRegression(max_iter=1000)
model.fit(X_scaled, y)
```

ここで用いているデータセットでは、当然ながら、優れたモデルの生成は期待されません。
また、バリデーションデータセットやモデルの評価についても、ここでは触れていません。