Amazon CodeWhisperer (AI Code Generator) で機械学習コードを自動生成してみる

# はじめに
最近、ChatGPT をはじめとしたGenerative AI（以下、生成系AI）が広く注目を集めており、様々なシーンで活用が検討されていると思います。クラウドベンダーの中では、マイクロソフトやGoogleが先行して生成系AIのサービスを推進していましたが、先日AWSも生成系AIのサービスを発表しました。

https://aws.amazon.com/jp/blogs/news/announcing-new-tools-for-building-with-generative-ai-on-aws/

発表の中に、Amazon CodeWhisperer の一般提供も含まれており、CodeWhisperer も生成系AIの位置づけで今後はサービス展開していくようですね。

CodeWhisperer を利用して機械学習の開発自体も便利にできないかなと思い、SageMaker Studio で扱う方法について考えてみました。

## 全体像
アーキテクチャは以下の通りです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2532317/71768cdf-326a-5cbe-9589-212c0b9c0234.png)

このアーキテクチャに至ったのは以下のような理由です。
- AWSで機械学習の開発環境といえば、SageMaker Studioがまず選択肢にあがります。
- しかし、現時点（2023年4月）では、CodeWhisperer とSageMaker は機能統合されておらず、直接利用することができません。
- では、Jupyterノートブックの拡張機能でいけるかと思いましたが、こちらもCodeWhisperer は対応していないようです。
- VSCode には対応しているということで、SageMaker Studioで code-serverを導入して、VSCode環境を用意してやればいいのではと思い、上記のアーキテクチャに至りました。

このアーキテクチャで環境を作って、CodeWhisperer によるコーディングをしていきたいと思います。

## ① SageMaker Studio でVS code-server をホストする

SageMaker にcode-server を導入するのって面倒なのでは？と思った方もいるかもしれませんが、AWS側がインストーラとなるスクリプトを用意してくれているので、非常に簡単に導入できます。

まず、SageMaker Studio のランチャーで、[System terminal]を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2532317/9871d38a-3f56-d73b-7eeb-7e33f89b7ed9.png)


terminal が起動したら次のコマンドを実行します。インストールにはインターネット接続が必要となります。
```sh
curl -LO https://github.com/aws-samples/amazon-sagemaker-codeserver/releases/download/v0.1.5/amazon-sagemaker-codeserver-0.1.5.tar.gz
tar -xvzf amazon-sagemaker-codeserver-0.1.5.tar.gz

cd amazon-sagemaker-codeserver/install-scripts/studio
 
chmod +x install-codeserver.sh
./install-codeserver.sh
```
※試してませんが、インストール資材を別端末で取得して、Studio上に配置しておけばオフラインインストールも可能そうです。ちなみに、`install-codeserver.sh`内でもインターネットからtarファイルを取得しているようなので、注意が必要です。

コマンドの実行が完了したらブラウザを再読み込みします。以下のように、code-serverのアイコンが表示されたら、インストール完了です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2532317/9768316d-507d-0284-45b7-8c361f312354.png)


## ② VS code-server でCodeWhisperer を有効化する
次にcode-server でCodeWhisperer を有効化します。
ランチャーに表示されたcode-serverのアイコンを押下すると、code-serverの画面が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2532317/f8312086-6fef-dc28-915b-808f8791291a.png)

code-server はSageMaker Studioのディレクトリと関連付けられており、Studioに配置している任意のディレクトリやファイルを表示することが可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2532317/1956c54d-bf7c-da88-0c53-7eec5b27e71d.png)

続いて、CodeWhisperer を利用するために、VSCode のエクステンションで AWS Toolkit をインストールします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2532317/628e6311-997b-665b-307c-5a26d8c089c8.png)

インストールしたら、AWS Toolkit を開き、[CodeWhisperer] > [Start]を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2532317/e80cd755-79ff-4f32-83b7-bd6c8a5cb92d.png)

画面の上部にドロップダウンが表示され、Amazon CodeWhisperer の使用を開始するよう求められます。CodeWhisperer は個人利用なら無料のため、個人利用となる [Use a person email to sign up and sign in with AWS Builder ID] を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2532317/cb14495e-f75d-77eb-c3e4-4d7bda69a98f.png)

AWS Builder ID のが必要となるため、持っていない場合、画面に従って作成します。持っている場合はサインインをします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2532317/16c271ca-ec3e-862d-c457-50c8c2f28a82.png)

AWS Builder ID との連携が完了したら、code-server にこのようなマークダウンが表示されるので、CodeWhisperer の利用が可能な状態になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2532317/c40b4826-9d4a-56a3-c34c-e136556c2eba.png)


## ③ CodeWhisperer でソースコードを自動生成する
では、CodeWhisperer で機械学習のソースコード（Python）を自動生成していきたいと思います。
お試しのため、簡単な前処理をしてSageMakerでモデルを作るだけのソースコードを生成します。

### 仮想環境の用意
まず、Pythonの実行環境を整えます。code-serverにPythonの仮想環境が用意されているのでアクティブにします。

```sh
conda activate codeserver_py39
```

ただ、この仮想環境は少々残念で、機械学習に必要なパッケージはほぼ入っていなかったので、自分でパッケージをインストールすることが必要でした。必要そうなやつをとりあえず入れてみました。
```sh
pip install pandas==1.2.0
pip install boto3==1.26.114
pip install scikit-learn==1.2.2
pip install sagemaker==2.123.0
```

### CodeWhisperer によるコーディング
では、CodeWhisperer によるコーディングをしていきます。以下のコード自動生成/補完機能が用意されており、これらを使いまわしていきます。
https://docs.aws.amazon.com/codewhisperer/latest/userguide/whisper-code-examples.html
- Single-line code completion
- Full function generation
- Block completion
- Docstring and Javadoc completion
- Line-by-line recommendations
- Single-line comment completion


今回は、「Single-line code completion」の機能を使い、実現したい処理のコメントを書き、提案されるコードを選択しながら一連のソースコードを作成しました。
間違ってそうだなと思いつつ、まずは提案されたコードがどんなものか見るために、まずはひととおり作ってみました。それが以下です。

```py:CodeWhisperer の操作のみで生成されたコード（コメント行は自分で定義）
# read data by pandas
import pandas as pd

# read data from csv file
data = pd.read_csv('data/bank-additional-full.csv')
print(data)

# drop columns
data = data.drop(['duration'], axis=1)
print(data)

# Convert categorical variables to sets of indicators
data = pd.get_dummies(data)
print(data)

# Split data into training and test
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(data.drop(['y'], axis=1), data['y'], test_size=0.2, random_state=42)

# Concat y_train and X_train
train_data = pd.concat([y_train, X_train], axis=1)

# Concat y_test and X_test
test_data = pd.concat([y_test, X_test], axis=1)

# to csv train_data
train_data.to_csv('data/train_data.csv', index=False)

# to csv test_data
test_data.to_csv('data/test_data.csv', index=False)

# Upload train_data and test_data to S3
# bucket: sagemaker-ap-northeast-1-＜私のアカウントID＞, prefix: codewhisperer
import boto3

s3 = boto3.client('s3')
s3.upload_file('data/train_data.csv', 'sagemaker-ap-northeast-1-＜私のアカウントID＞', 'codewhisperer/train_data.csv')

s3.upload_file('data/test_data.csv', 'sagemaker-ap-northeast-1-＜私のアカウントID＞', 'codewhisperer/test_data.csv')

# Create a SageMaker Estimator
from sagemaker.estimator import Estimator
from sagemaker.serializers import CSVSerializer
from sagemaker.inputs import TrainingInput

# Use Built-in XGBoost
from sagemaker.xgboost.estimator import XGBoost

# Create a SageMaker Estimator
from sagemaker.estimator import Estimator
from sagemaker.serializers import CSVSerializer
from sagemaker.inputs import TrainingInput

# Create a SageMaker Estimator
xgb_estimator = Estimator(
    image_uri='＜私のアカウントID＞.dkr.ecr.ap-northeast-1.amazonaws.com/sagemaker-xgboost-container:1.3-1',
    role='0000000000000000000000000:role/service-role/AmazonSageMaker-ExecutionRole-20201213T173230',
    train_instance_count=1,
    train_instance_type='ml.m4.xlarge',
    output_path='s3://sagemaker-ap-northeast-1-＜私のアカウントID＞/codewhisperer/xgboost-output',
    base_job_name='xgboost-base-job',
    sagemaker_session=sagemaker.Session()
    )

# Set hyperparameters
xgb_estimator.set_hyperparameters(
    max_depth=5,
    eta=0.1,
    gamma=0,
    min_child_weight=1,
    subsample=1,
    silent=0,
    objective='binary:logistic',
    nthread=4,
    scale_pos_weight=1,
    seed=27
    )

# fit the xgb_estimator
xgb_estimator.fit(
    TrainingInput(s3_data='s3://sagemaker-ap-northeast-1-＜私のアカウントID＞/codewhisperer/train_data.csv', content_type='text/csv'),
    logs=True
    )

```

ぱっと見でも動作しなさそうな部分（ロール名）があったので、軽く手直しします。
```py
xgb_estimator = Estimator(
    image_uri=image_uri='＜私のアカウントID＞.dkr.ecr.ap-northeast-1.amazonaws.com/sagemaker-xgboost-container:1.3-1',
    role='arn:aws:iam::＜私のアカウントID＞:role/service-role/AmazonSageMaker-ExecutionRole-20201213T173230',
    train_instance_count=1,
    train_instance_type='ml.m4.xlarge',
    output_path='s3://sagemaker-ap-northeast-1-＜私のアカウントID＞/codewhisperer/xgboost-output',
    base_job_name='xgboost-base-job',
    sagemaker_session=sagemaker.Session()
    )
```

### CodeWhisperer によるコードの実行
自動生成されたコードをを実行します。残念ながら、何個かエラーが出たので修正します。

#### エラー①
カテゴリ変数をワンホットエンコーディングした際に、目的変数カラムとなる`y`の名称が変わってしまったため、以下のエラーが発生しました。
```:エラー①
KeyError: "['y'] not found in axis"
```

うまく補完もしれくれなかったので、自分で修正しました。
```py
X_train, X_test, y_train, y_test = train_test_split(data.drop(['y_no', 'y_yes'], axis=1), data['y_yes'], test_size=0.2, random_state=42)
```

#### エラー②
Estimatorで指定していた、image_uriが間違っています。（自分のアカウントIDとなっていました）
```:エラー②
botocore.exceptions.ClientError: An error occurred (ValidationException) when calling the CreateTrainingJob operation: Cannot find repository: sagemaker-xgboost-container in registry ID: ＜私のアカウントID＞ Please check if your ECR repository exists and role arn:aws:iam::＜私のアカウントID＞:role/service-role/AmazonSageMaker-ExecutionRole-20201213T173230 has proper pull permissions for SageMaker: ecr:BatchCheckLayerAvailability, ecr:BatchGetImage, ecr:GetDownloadUrlForLayer
```
コード補完機能を使って、うまくイメージURIを取得できないか試行錯誤しましたが、だめでした。（私のコメント指示がいけていなかっただけかもしれませんが、、、）
以下のように自分で修正します。

```py
# Retrive the image URI
image_uri = sagemaker.image_uris.retrieve(region='ap-northeast-1', framework='xgboost', version='latest')

# Create a SageMaker Estimator
xgb_estimator = Estimator(
    image_uri=image_uri,
    role='arn:aws:iam::＜私のアカウントID＞:role/service-role/AmazonSageMaker-ExecutionRole-20201213T173230',
    train_instance_count=1,
    train_instance_type='ml.m4.xlarge',
    output_path='s3://sagemaker-ap-northeast-1-＜私のアカウントID＞/codewhisperer/xgboost-output',
    base_job_name='xgboost-base-job'
    )
```

#### エラー③
XGBoostの必須パラメータが足りていないとのことです。（気づきませんでした、、、）
```:エラー③
ClientError: Require input for parameter num_round, exit code: 1
```
エラーメッセージにある通り、`num_round`を追加します。
```py
# Set hyperparameters
xgb_estimator.set_hyperparameters(
    num_round=100,
    max_depth=5,
    eta=0.1,
    gamma=0,
    min_child_weight=1,
    subsample=1,
    silent=0,
    objective='binary:logistic',
    nthread=4,
    scale_pos_weight=1,
    seed=27
    )
```

#### エラー④
Estimatorをfitする際に、チャネル名で`train`が必須でした。

```:エラー④
ClientError: Channelname 'train' is required for training, exit code: 1
```

コメントを追加で書いて補完機能を使って、以下のようにうまいこと修正できました。

```py
# SageMaker InputPath
train_input = TrainingInput(s3_data='s3://sagemaker-ap-northeast-1-＜私のアカウントID＞/codewhisperer/train_data.csv', content_type='text/csv')
validation_input = TrainingInput(s3_data='s3://sagemaker-ap-northeast-1-＜私のアカウントID＞/codewhisperer/test_data.csv', content_type='text/csv')

# fit the xgb_estimator
xgb_estimator.fit({
    'train': train_input,
    'validation': validation_input
    },wait=True
)
```

### 最終的にできあがったコード（正常に動くコード）

上記のエラーを修正して、最終的に出来上がったコードは以下です。

```py
# read data by pandas
import pandas as pd

# read data from csv file
data = pd.read_csv('data/bank-additional-full.csv')
print(data)

# drop columns
data = data.drop(['duration'], axis=1)
print(data)

# Convert categorical variables into dummy variables
data = pd.get_dummies(data)
print(data)

# Split data into training and test
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(data.drop(['y_no', 'y_yes'], axis=1), data['y_yes'], test_size=0.2, random_state=42)

# Concat y_train and X_train
train_data = pd.concat([y_train, X_train], axis=1)

# Concat y_test and X_test
test_data = pd.concat([y_test, X_test], axis=1)

# to csv train_data
train_data.to_csv('data/train_data.csv', index=False, header=False)

# to csv test_data
test_data.to_csv('data/test_data.csv', index=False, header=False)

# Upload train_data and test_data to S3
# bucket: sagemaker-ap-northeast-1-＜私のアカウントID＞, prefix: codewhisperer
import boto3

s3 = boto3.client('s3')
s3.upload_file('data/train_data.csv', 'sagemaker-ap-northeast-1-＜私のアカウントID＞', 'codewhisperer/train_data.csv')

s3.upload_file('data/test_data.csv', 'sagemaker-ap-northeast-1-＜私のアカウントID＞', 'codewhisperer/test_data.csv')

# Create a SageMaker Estimator
from sagemaker.estimator import Estimator
from sagemaker.serializers import CSVSerializer
from sagemaker.inputs import TrainingInput
import sagemaker

# Retrive the image URI
image_uri = sagemaker.image_uris.retrieve(region='ap-northeast-1', framework='xgboost', version='latest')

# Create a SageMaker Estimator
xgb_estimator = Estimator(
    image_uri=image_uri,
    role='arn:aws:iam::＜私のアカウントID＞:role/service-role/AmazonSageMaker-ExecutionRole-20201213T173230',
    train_instance_count=1,
    train_instance_type='ml.m4.xlarge',
    output_path='s3://sagemaker-ap-northeast-1-＜私のアカウントID＞/codewhisperer/xgboost-output',
    base_job_name='xgboost-base-job'
    )

# Set hyperparameters
xgb_estimator.set_hyperparameters(
    num_round=100,
    max_depth=5,
    eta=0.1,
    gamma=0,
    min_child_weight=1,
    subsample=1,
    silent=0,
    objective='binary:logistic',
    nthread=4,
    scale_pos_weight=1,
    seed=27
    )

# SageMaker InputPath
train_input = TrainingInput(s3_data='s3://sagemaker-ap-northeast-1-＜私のアカウントID＞/codewhisperer/train_data.csv', content_type='text/csv')
validation_input = TrainingInput(s3_data='s3://sagemaker-ap-northeast-1-＜私のアカウントID＞/codewhisperer/test_data.csv', content_type='text/csv')

# fit the xgb_estimator
xgb_estimator.fit({
    'train': train_input,
    'validation': validation_input
    },wait=True
)

```

CodeWhisperer の操作に慣れていない面はあったものの、1時間程度で完成しました。

## まとめ
CodeWhisperer をSageMaker Studio で扱う方法についてまとめてみました。
思ったよりは手直しも少なく済み、機械学習開発においても便利なサービスだと思います。

ただ、今回くらいのコードであれば、SageMakerのローコード・ノーコード機能（DataWrangler、Autopilot、Canvasなど）を使った方がいいかなと思ったので、フレームワークなどコアなカスタムコードを自動で生成できると利用する価値がより高まると思いました。
また、セットアップの手間やコーディング自体も不完全な面もあるので、今後の改善に期待したいと思います。

世の中も動向やAWSの今回の生成系AIサービスの発表からも今後力をいれていくのは間違いないかなと思うので、アップデート情報を楽しみに待ちたいと思います。