【Snowpark】Frosty Friday ライブチャレンジに出演してきた【SnowVillage】

# はじめに
先日、SnowVillage の有志で定期開催している Frosty Friday Live Challenge にお邪魔してきました。このライブ配信では、[Frosty Friday](https://frostyfriday.org) で公開されている問題を、ライブコーディング・解説する配信を公開しています！

私の出演回は [Vol.8](https://www.youtube.com/watch?v=yn7ujhda0vs) で、[Snowpark と機械学習を組み合わせた処理に関する問題](https://frostyfriday.org/blog/2022/10/14/week-18-linear-regression/)でした。回答のソースコードは [GitHub](https://github.com/THiyama/TH-Frosty-Friday/blob/main/Frosty-Friday-Week18/notebook_app.ipynb) にアップしています。こちらのソースコードは、Snowflake Notebooks 上で動作する実装となっている点にご注意ください。

本記事では、その内容をかい摘んで紹介したいと思います。あわせて、ぜひ動画もご視聴いただけますと幸いです！🎉

# 解説

## 問題設定　ー インフレが急上昇しています！🤯
この問題は、Snowflake Marketplace から得られた実際の経済データを用いて将来のインフレを予測することです。具体的には、Snowflake 内で線形回帰モデルを訓練し、任意の将来の年の個人消費支出（PCE）を予測します。

<details><summary>線形回帰とは？</summary>
線形回帰とは、一つ以上の独立変数と従属変数の間の線形関係をモデリングする統計的な手法のことです。独立変数は今回でいう `YEAR` を、従属変数とは独立変数に伴って変化する値、すなわち `PCE` を指します。

この手法は、データから直線的な傾向を見つけ出し、将来の値を予測するためによく用いられます。
</details>


## データの準備
実は、こちらの問題が作成された当時の Knoema 社によるデータセットは、現在非公開になってしまっています。そこで今回は、Cybersyn 社がほぼ同等のデータセットを公開してくださっていたため、そちらを使用することにします。

データセット名は、「Cybersyn Financial & Economic Essentials」です。Snowflake の Marketplace で無料で取得できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540224/816a16dd-1735-dd96-a9ab-4fcd00fe867f.png)

続いて、今回使用するデータの確認を行います。データセットの中でも、一部のデータ（PCE）のみを使用するため、適切にフィルタリングして取得します。
```sql
select 
    year(date) as Year, VALUE as PCE
from 
    financial__economic_essentials.cybersyn.FINANCIAL_FRED_TIMESERIES
where true
    and variable_name = 'Personal Consumption Expenditures: Chain-type Price Index, Seasonally adjusted, Monthly, Index 2017=100'
    and date >= '1972-01-01'
    and month(date) = 1
order by date
;
```

上記の SQL の実行結果は、下表のようになります。元のデータには月ごとの PCE 値が含まれているため、毎年1月のデータのみを抽出するようにしています。
| YEAR | PCE     |
|------|---------|
| 1972 | 21.015  |
| 1973 | 21.695  |
| 1974 | 23.523  |
| ...  | ...     |
| 2021 | 106.145 |
| 2022 | 112.829 |

これらのデータをプロットすると、下図のようになります。サクッと線形回帰モデルを使って翌年以降の PCE を予測できそうですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540224/66130dd3-2398-4c34-3325-e60b6e836301.png)


## 解法１
こちらは、Pandas DataFrame と Scikit-learn を使って線形回帰を行う方法です。先ほど SQL で取得した結果表のうち、ラベル x を YEAR、予測値 y を PCE として LinearRegression 関数により学習を実行します。 
```python
from sklearn.linear_model import LinearRegression

pd_df_pce_year = sql_get_data.to_pandas()

x = pd_df_pce_year["YEAR"].to_numpy().reshape(-1,1)
y = pd_df_pce_year["PCE"].to_numpy()

model = LinearRegression().fit(x, y)
```

続いて、上記で学習したモデルを使用して、数年分の PCE 値の予測結果を出力してみます。
```python
# test model for 2022
predictYear = 2022
pce_pred = model.predict([[predictYear]])
# print the last 5 years
st.dataframe(pd_df_pce_year.tail(), hide_index=True)
# run the prediction for 2022
print ('Prediction for '+str(predictYear)+': '+ str(round(pce_pred[0],2)))
```

その結果、下表のように、徐々に上昇していく様子が確認できます。
| YEAR | PCE     |
|------|---------|
| 2020 | 104.458 |
| 2021 | 106.145 |
| 2022 | 112.829 |
| 2023 | 119.011 |
| 2024 | 121.966 |


・・・しかし、これではせっかくの Snowflake、Snowpark for Python の特徴を活かしきれていませんね？ 

## 解法２
そこで続いては、お待ちかね（？）Snowpark DataFrame と Snowpark ML を使用した Snowflake ネイティブな線形回帰モデルの実装です。

<details><summary>Snowpark DataFrame とは？</summary>
Snowpark DataFrame とは、Python でよく使う DataFrame 形式でデータ処理を記述することで、Snowflake 上のデータを、Snowflake に置いたまま処理することができる機能となります。裏側では、DataFrame 処理が SQL に変換され実行されています。
</details>

<details><summary>Snowpark ML とは？</summary>
Snowpark ML とは、有名な機械学習ライブラリ（scikit-learn や XGBoost など）が、Snowflake ネイティブな形式で実装されているライブラリです。学習や予測、データ変換が、Snowflake のコンピューティングリソース上で並列分散処理されるように工夫されています。
</details>




それでは、ソースコードを確認してみます。基本的にやっていることは変わりありませんね。
```python
from snowflake.ml.modeling.linear_model.linear_regression import LinearRegression

snow_df_pce_year = sql_get_data.to_df()

regr = LinearRegression(
    input_cols = "YEAR",
    label_cols = "PCE",
)

_ = regr.fit(snow_df_pce_year)
```

主な違いとしては、下記のようなものでしょうか。
- ライブラリインポートを、Snowpark ML ライブラリ用に変更
- `to_pandas` ではなく `to_df` の使用により、Snowpark DataFrame に変換
- LinearRegression の学習時には、Snowpark DataFrame のカラムを事前に指定してから入力

これらは特に難しいものではありませんが、Snowpark DataFrame ＆ Snowpark ML を使う際には必要となる修正点のため意識しておきましょう。

続いては、予測についても確認します。
```python
def predict_pce(years: list)->list:
    input = session.create_dataframe(years, schema = ["YEAR"])
    pce_pred = regr.predict(input)

    return pce_pred.to_pandas()
    
# test model for 2022
predictYear = 2022
predicted_pce = predict_pce([predictYear])

# print the last 5 years
st.dataframe (snow_df_pce_year.to_pandas().tail(), hide_index=True)
# run the prediction for 2022
print ('Prediction for '+str(predictYear)+': '+ str(round(predicted_pce.iloc[0,1],2)))
```
予測の際に `predict` メソッドを使う点は変わりありませんが、この際 Snowpark DataFrame を入力する必要があるため、Python のリストを `session.create_dataframe` により Snowpark DataFrame に変換しています。これも、定型文のため、意識しておけば難しいものではありません。


上記のコードの結果は下表のように、解法１と全く同じ結果が得られました。
| YEAR | PCE     |
|------|---------|
| 2020 | 104.458 |
| 2021 | 106.145 |
| 2022 | 112.829 |
| 2023 | 119.011 |
| 2024 | 121.966 |

## 可視化
最後に、元の学習用データと予測データを重ねたグラフを作成し、適切に回帰モデルが構築されていることを確認します。
```python
import plotly.express as px

predictYears = list(range(1972, 2031))
predicted_pce = predict_pce(predictYears)

fig = px.line(snow_df_pce_year, x="YEAR", y="PCE")
fig.add_traces(px.scatter(predicted_pce, x="YEAR", y="OUTPUT_PCE").data)
st.plotly_chart(fig)

if st.button(":snowflake:"):
    st.snow()
```

上記の結果は、下図のようになり適切に予測されていることが確認できます。実線が学習用データで、点線が予測値です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540224/047d8fc1-90aa-3d71-35fc-7dad91c3f117.png)

Snowflake Notebooks を使うことで、カンタンにグラフの描画が行えることも確認できましたね。

## Wrap up
今回、Snowpark MLライブラリを使って線形回帰を行ってみました。この方法のメリットは何でしょうか？

それは、「Snowflakeからデータが外に出ないこと」です。これにより、**モデルの学習時に比較的小さいローカルリソースを使うのではなく、Snowflakeのスケーラブルで従量制のコンピューティングリソースを活用できます**。

また、今後、NotebooksやSiSは、ウェアハウス分離機能によりフロントはXSのマネージドウェアハウスを、クエリ実行は任意のサイズのウェアハウスをそれぞれ使い分けることができるようになります。

このとき、大容量データをフロント用のマネージドウェアハウス上でPandas・Scikit-learnを使って学習させるのではなく、Snowpark DataFrame・Snowpark MLを使って学習させることで、コスト効率良く、フレキシブルにSnowflakeのコンピューティングを活用することができます。

なお、今回の線形回帰の学習部分は対象外（予測は対象！）ですが、Snowpark MLの前処理やモデリング関数の一部は、Snowflakeのウェアハウス上で分散実行されるように設計されています。こうしたSnowflakeに最適になるように構成されていることも、Snowpark MLライブラリの素晴らしいポイントだと考えております。詳しい対応リストはこちらの公式ドキュメントを参照してみてください。https://docs.snowflake.com/developer-guide/snowpark-ml/modeling#snowpark-ml-modeling-classes

# おわりに
初挑戦だった Frosty Friday ですが、準備の間も非常に勉強になることが多く、挑戦しがいがありました。また、それを YouTube に配信されることを想定して解説することで、更に自身の知識・解説力を磨くことにも繋がりました。

Frosty Friday は、[本家の公式サイト](https://frostyfriday.org/)上で気軽に挑戦できる上、日本の [Snowflake コミュニティ](https://usergroups.snowflake.com/snowvillage/)でワイワイと取り組むことも、立候補制で挑戦できます。本記事を通して興味を持った方は、ぜひ挑戦してみてください！