データサイエンスのためのTeradata入門: In-DB分析機能による回帰分析

# この記事について

Teradataが独自拡張したSQL関数を用いて回帰分析の学習・予測を行う方法を紹介します。
これは、機械学習の学習から推論までを全てデータベース上で実行する、珍しい機能です。
分散データベースの処理に最適化された関数を用いるため、高い効率性を実現します。


:::note
実行コードは、[日本テラデータGitHub](https://github.com/Teradata/teradata-japan/blob/main/tutorial/4.1_InDB-training-house-price.ipynb)にて公開中しています。
このコードは無料のTeradata環境 [ClearScape Analytics Experience](https://clearscape.teradata.com) にて実行可能です。
:::



## 要点

- TeradataのIn-DB分析関数により、線型回帰モデルを学習・予測
- 機械学習モデルの学習・デプロイ・推論の全てをデータベース内で完結


# Teradata In-DB分析関数

この記事では線型回帰モデルを扱いますが、他にも主要な分析モデルをサポートしています。
たとえば、ロジスティック回帰、ランダムフォレスト、ARIMA、XGBoost、サポートベクトルマシンなどが利用可能です。


# 事前準備

必要なライブラリをインストールします。

```shell:ライブラリのインストール
pip install pandas "sqlalchemy<2" teradataml
```

:::note warn
`sqlalchemy` バージョン2との互換性に不具合が出ているので、改善まではバージョン1を指定します
:::

## Teradataへの接続

実際の環境に合わせて接続情報を指定します。
わからなければデータベース管理者に聞いてみましょう。
この例はお試し環境 [ClearScape Experience](https://clearscape.teradata.com/) での設定です。

```python:接続情報
from getpass import getpass
from urllib.parse import quote_plus

host = "host.docker.internal"
user = "demo_user"
database = "demo_user"
password = getpass("Password > ")
dbs_port = 1025
encryptdata = "true"

# sqlalchemy用の接続文字列
connstr = (
  f"teradatasql://{user}:{quote_plus(password)}@{host}/?"
  f"&database={database}"
  f"&dbs_port={dbs_port}"
  f"&encryptdata={encryptdata}"
)
```

```python:セッション開始
from sqlalchemy import create_engine
from teradataml import create_context, DataFrame
engine = create_engine(connstr)
context = create_context(tdsqlengine=engine, temp_database_name=user)
```

# 住宅価格の予測 (LightGBM)

[カリフォルニア住宅価格データ](https://scikit-learn.org/stable/modules/generated/sklearn.datasets.fetch_california_housing.html)を用いて、価格予測を行います。

## データの準備

```python:データのロード
from sklearn.datasets import fetch_california_housing

data = fetch_california_housing(as_frame=True)
df = data["data"]
df.insert(0, "target", data["target"])

# teradata へデータをロード
from teradataml import copy_to_sql, DataFrame
copy_to_sql(df, "housing", if_exists="replace", index=True)

# ロード結果を確認
DataFrame("housing")
#target MedInc HouseAge AveRooms          AveBedrms          Population AveOccup           Latitude  Longitude index_label
#1.208  3.75   10.0     3.45              0.8                50.0       2.5                38.01     -121.29   16385
#1.55   2.9821 32.0     6.576923076923077 1.1153846153846154 138.0      2.6538461538461537 38.02     -121.27   16387
#1.125  1.15   52.0     4.88              1.04               62.0       2.48               38.05     -121.3    16
#....
```

- `copy_to_sql` を用いてローカル環境のデータフレームをteradataへロードしています
- `index=True` を指定することで、もともとの行番号を識別列として追加しています (データの順序は入れ替わってしまうので、後で突合するときに必要)


```python:学習・テストデータの分割
df = DataFrame("housing")
df_tr = df[df.index_label.mod(5) > 0]
df_te = df[df.index_label.mod(5) == 0]

copy_to_sql(df_tr, "housing_tr", if_exists="replace")
copy_to_sql(df_te, "housing_te", if_exists="replace")
```

- `index_label` を5で割った余りが0ならテストデータ、それ以外なら学習データとして用います
- 他にもう少し混み入ったアルゴリズムでの分割も可能です ([TrainTestSplit](https://docs.teradata.com/r/Enterprise/Teradata-Package-for-Python-Function-Reference-17.20/teradataml-Analytic-Database-17.20.xx-Analytic-Functions/MODEL-EVALUATION-functions/TrainTestSplit) 関数)
- ここでも `copy_to_sql` 関数を使っていますか、ここでは `teradataml.DataFrame` をテーブルとして保存するために使っています


## データの標準化

```python:データの標準化（学習）
from teradataml import ScaleFit, ScaleTransform

# 説明変数
cols = ["MedInc", "HouseAge", "AveRooms", "AveBedrms", "Population", "AveOccup", "Latitude", "Longitude"]

# 事前に説明変数を標準化する
scale = ScaleFit(data=x_tr, target_columns=cols, scale_method="USTD")
scale.output   # 標準化にかかる情報はデータフレームとして保持される

#TD_STATTYPE_SCLFIT MedInc            HouseAge          ...  Latitude          Longitude
#sum                63944.67140000035 473791.0          ...  588353.2699999986 -1974336.8599999906
#null               0.0               0.0               ...  0.0               0.0
#avg                3.872618180717075 28.6937378875969  ...  35.63185985949604 -119.56981952519322
```

- `ScaleFit` 関数でデータを標準化するための情報を学習します
- 情報は、1つのテーブルとして保持されます

```python:データの標準化（変換）
# 学習データ・テストデータに標準化を行う
x_tr_scaled = ScaleTransform(
  data=x_tr, object=scale.output, accumulate=["target", "index_label"]).result
x_te_scaled = ScaleTransform(
  data=x_te, object=scale.output, accumulate=["target", "index_label"]).result
```

- 標準化を実行するには、`ScaleTransform`関数を用います

## 線型回帰モデルの学習

```python:線型回帰モデルの学習
from teradataml import GLM

glm = GLM(data=x_tr_scaled,
          input_columns=cols,
          response_column="target",
          family="GAUSSIAN", 
          lambda1=0.01,
          alpha=0.3,
          batch_size=4500)
# 学習結果はテーブルとして保持される
glm.result.to_pandas().sort_index()

#attribute  predictor   estimate  value
#0          (Intercept)	2.040508  None
#1          MedInc      0.775201  None
#2          HouseAge    0.100127  None
#...
```

- `GLM`関数で一般化線形モデルを学習します。`family="GAUSSIAN"`とすれば線型回帰になります
- 学習結果はやはり1つのテーブルとして保持されます

```python:線型回帰モデルによる予測
# 学習データへの予測
from teradataml import TDGLMPredict, RegressionEvaluator

# 予測を実行
pred_tr = TDGLMPredict(
  newdata=x_tr_scaled,      # 予測対象のデータを指定
  object=glm.result,        # 学習済モデルを保持するオブジェクトを指定
  id_column="index_label",  # id列を指定
  accumulate="target"       # 予測結果とともに抽出したい変数を指定
).result
pred_tr

#index_label prediction         target
#16386       1.753725879086769  0.828
#16388       1.0432197226949214 1.125
#16389       1.800250953823959  1.068
#....
```

- `id_column` には識別列を指定
- `accumulate` には結果と一緒に出力したい列名を与えます


```python:回帰予測の結果を評価
# 予測結果を評価
res_tr = RegressionEvaluator(
  data=pred_tr,                    # 予測結果を保持するテーブルを指定
  observation_column="target",     # 真の値
  prediction_column="prediction",  # 予測値
  metrics=["MSE", "MAE", "R2"]     # 計算する指標
)
res_tr.result

#MAE                MSE                R2
#0.5725529079984077 0.6137348063921505 0.5384568788835442
```

# 結び

以上、In-DB分析関数を用いて、線型回帰モデルを学習し、それを用いた予測を行う手順を紹介しました。他にもランダムフォレストやXGBoostなど、主要な学習アルゴリズムが利用可能なので、ぜひお試しください。
疑問点・懸念点などありましたらぜひコメントください。[GitHub](https://github.com/Teradata/teradata-japan/issues)の方にIssueを立てていただいても結構です。