【前半】雑魚のためのSageMaker入門（まずはSwaggerUIで遊ぶ）【企画倒れ】

# はじめに

<b>この記事ではSageMakerはいじりません．</b>SageMakerだけいじりたい人はクローンして次の記事から読んだらいいと思います．
<b>企画倒れしました．．．</b>

開発案件でSageMakerを味見する話があったのでやってみた記事（の前半）です．
※2022年11月25日現在の内容です．

『学習から行う』みたいな記事は見つけられたのですが，『推論だけやる』という記事は見つけられなかったので書いています．

よって，対象読者はこのあたりの条件を満たす人になるかと．
- 学習はローカル（というかAWS以外）でやる
- 推論のAPIを作りたい
- バックエンドのインフラはお任せにしたい（フロントエンドはさておき）
~~チュートリアル読めば書いてあったのかもしれない内容な気もしています．知りませんが．~~

誰かの技術ブログをサクッと再現できないたびに無力感を感じるので，タイトルに『雑魚のための』とつけました．

ソースコードは[Github](https://github.com/akh1r0ck/sagemaker_challenge)にあげてあります．
また前提条件としてDocker，AWSのアカウント登録が済んでいることとしています．

<details><summary> 間違ってもしないこと</summary>

- AWSでAPIのデバッグする
    - APIに与えるパラメータだとか，内部でどう処理されるのかとかをエンドポイント作成しながらデバッグしてたら長時間かかった
- AWSでDcokerfileのデバッグをする
    - エンドポイントを作成したらDockerfileが悪かったというパターンが超あった
</details>

<details><summary> 知ってたらよかったこと</summary>

この辺りの要素を知っている人だとヌルヌルだったはずです．
コアプログラムとしてPython，仮想環境としてDocker，APIに関してはSwagger，インフラとしてAWSという感じですね．

- Python
    - fastapi
- Docker
    - Dockerfile
- Swagger
    - Swagger UI
- AWS
    - Amazon SageMaker
    - Erastic Container Registry

</details>

# やったこと

1. Simpletransformersで文章分類器の作成
2. FastAPIでAPI作成
3. Amazon SageMakerでエンドポイント作成

3については規定の手続きになるので，1と2をどう組むかでアプリケーションを変えられますね．
ということで，1と2についてはこの記事で，3についてを次の記事で書いていきます．
（書いていたら長くなったので分けました．）
特にSimpletransformersは~~疲れたので~~なんとなく割愛しています．

## Simpletransformersで文章分類器の作成

[Simpletransformers](https://simpletransformers.ai/)+[Huggingface](https://huggingface.co/)の学習済みモデルを使って文章分類器を作ります．

Simpletransformersはtransformersをラッパーしたライブラリで，超簡単にtransformerが使えます．
Huggingfaceは自然言語系のライブラリや誰かが学習させたモデルが使えます．
~~雑魚なのでありものを使います．~~
文章分類は，ツイッター文章の感情分類をします．
Huggingfaceより[Cardiff大学の学習済みモデル]((https://huggingface.co/cardiffnlp/twitter-roberta-base-sentiment))を拝借します．
~~プログラムは書いてもらいました．~~

<details><summary>ソースコードはこちら</summary>

```python
from scipy.special import softmax
from simpletransformers.classification import ClassificationModel

def transformation(inputs):
    
    inputs = inputs["test_list"] if len(inputs["test_list"])>0 else [inputs["test_text"]] 

    model = ClassificationModel("roberta", "cardiffnlp/twitter-roberta-base-sentiment", use_cuda=False)
    _, raw_pred = model.predict(inputs)

    raw_pred = softmax(raw_pred, axis=1)
    outputs = [{"positive": round(raw[0], 3), "neutral": round(raw[1], 3), "negative": round(raw[2], 3)} for raw in raw_pred]
        
    return {"result": outputs}

inputs = {
    test_text: "",
    test_list: ["", ""]
    }

print(transformation(inputs))
```

</details>

`inputs`の中の`test_text`か`test_list`にstringを投げると推論してくれます．
※`test_list`が優先されます．
このコアプログラムはここでデバッグしておきましょう．
出力がdictなのはSageMakerへの仕込みです．

つまづきポイントはこの通り．
- `ClassificationModel()`に与えるHuggingFaceのパスが間違っていないか
- 関数への入力が間違っていないか

## FasAPIでAPI作成

### コーディング

[この記事](https://tech.fusic.co.jp/posts/2021-12-03-trying-sagemaker-serverless-inference/)を参考にして作成しました．
~~pydanicの部分で参考にした記事があったのですがダチョウ並の脳なので忘れました．~~

<details><summary>ソースコードです（Gitにあがっています）</summary>

```app.py
from fastapi import FastAPI
import uvicorn
from pydantic import BaseModel
from scipy.special import softmax
from simpletransformers.classification import ClassificationModel

app = FastAPI()

class Test(BaseModel):
    test_text: str
    test_list: list

@app.get("/ping")
def ping():
    return {"Hello": "World"}

@app.post("/invocations")
def transformation(inputs: Test):
    
    inputs = inputs.test_list if len(inputs.test_list)>0 else [inputs.test_text] 

    model = ClassificationModel("roberta", "cardiffnlp/twitter-roberta-base-sentiment", use_cuda=False)
    _, raw_pred = model.predict(inputs)

    raw_pred = softmax(raw_pred, axis=1)
    outputs = [{"positive": round(raw[0], 3), "neutral": round(raw[1], 3), "negative": round(raw[2], 3)} for raw in raw_pred]
        
    return {"result": outputs}


if __name__ == "__main__":
    uvicorn.run(
        "app:app",
        host="0.0.0.0",
        port=8080
    )
```

</details>

役割としては，通信がうまく行っているかping()を飛ばして確認したあとに，transformation()で処理を行うという感じで使いました（本当の使い方が違ったらすいません）．
さてさて，ここで大事なのはこれのデバッグってどこでやるの？という話ですよね．
実はFastAPIには自動ドキュメント作成機能があり，Swagger UIでブラウザを使ってAPIをテストすることができます（すごい）．

### Swagger UI

まずはuvicornを使ってAPIを起動してみます．
`pythonのファイル名:FastAPIのインスタンス名`で起動できます．
例）hoge.pyの中で`fuga = FastAPI()`としているならこんな感じです．
```bash
$ uvicorn hoge:fuga
```

app.pyの中でapp = FastAPI()としているのでこんな感じ↓で起動します．

```bash
$ uvicorn app:app
```

あるいはmainで呼び出すようにしているのでpythonコマンドでも起動できます．

```zsh
$ cd src
$ python app.py
```

どちらにせよこんなのが出れば正解です．

```zsh
INFO:     Started server process [30238]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
※pythonで起動するとこっち↓がでるかと思います
INFO:     Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit)
```

APIは`http://127.0.0.1:8000`をたたけばいいそうです．
Swagger UIには`http://127.0.0.1:8000/docs`でアクセスできます．
※pythonで起動した場合は`http://127.0.0.1:8000`→`http://0.0.0.0:8080`で読み替えてください．

こんな画面が出てきます．
~~ここから先の操作は雑魚らしく野生の勘で進めました．~~

![スクリーンショット 2022-11-26 12.35.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165289/13c62595-8e6c-796d-c132-39d54303c502.png)

まずはGETから試してみましょう．
1. GETのあたりをおもむろにポチる
1. 右側のTry it outをポチる
1. Executeをポチる


![ezgif.com-gif-maker.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165289/03a5a887-7507-3559-61fd-88dd30825132.gif)

Code 200が返ってくれば正解です．
失敗すると400とか500が返ってきます．

![スクリーンショット 2022-11-26 12.34.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165289/fa95cf28-10ca-040c-d290-2525b5807b23.png)


POSTも試してみましょう．今度はTry it outを押すとRequest bodyがガッと開きますガッと．

![ezgif.com-gif-maker (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165289/597e72d9-69f8-45d6-5e43-f0fa1d111d8a.gif)


せっかくなのでリクエストを編集してみます（画像小さい）．
Request bodyの中をこんな感じに変えてみます．
<details><summary>コピペはこちらから</summary>

```json
{
  "test_text": "",
  "test_list": [
    "string", "string"
  ]
}
```

</details>

![スクリーンショット 2022-11-26 12.33.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165289/f70def6e-e869-dc31-0245-5982f2a6cc6a.png)

そんなこんなでExecuteすると，今回は結果出力までしばらく時間がかかります．
:arrows_counterclockwise:	←こんなのがぐるぐるします．
ボトルネックはこの2つなので広い心で待ってあげましょう．
- HuggingFaceからモデルをダウンロードしてくる
- モデルを読み込んで推論する

こちらもうまくいくと200が返ってきて，失敗すると400とか500が返ってきます．
そして，terminal側にpythonのログが表示されます．

![スクリーンショット 2022-11-26 12.34.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165289/5d4bc78f-57b1-2287-2dd9-8c41f137e2cd.png)

おもむろに失敗させてみました．
terminal側ではこんな感じでhogeってなんぞやと返ってきています．

![スクリーンショット 2022-11-26 21.58.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165289/534e342f-5bd5-6975-0301-5c1203542ed1.png)

Swagger UI側ではこんな感じでServerがおかしいぞと返ってきています．

![スクリーンショット 2022-11-26 21.56.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165289/353c9b47-e17b-b3d0-ef2c-9bc570232f3a.png)



そんなこんなでSwagger UIを使ってAPIの動作確認をすることができました．
Swagger UIとterminalの表示を駆使してデバッグをすることができます．
ここでしっかりとデバッグをしておきましょう．

# おわりに

文章分類API自体はこれでオッケーです．
なにはともあれ無事にできてよかったです．
ありがとうHuggingFace，FastAPI，SwaggerUI．
終わりよければ全てよし．

いよいよ次はSageMakerでエンドポイントを作っていきます．

<details><summary>参考文献</summary>

順不同です．とりあえず読んだだろうというものを列挙してみました．
~~もっとあった気がするんですがマジで忘れましたすみません~~

- [Twitter-roBERTa-base for Sentiment Analysis](https://huggingface.co/cardiffnlp/twitter-roberta-base-sentiment)
- [Pythonでfastapiを使用しPOSTメソッドでデータを送信する](https://laboratory.kazuuu.net/using-fastapi-in-python-to-send-data-using-the-post-method/)
- [FastAPI入門（FastAPIを使ったWebAPIサーバ作り)](https://iij.github.io/bootcamp/server-app/fastapi/)
- [FastAPI リクエストボディ](https://fastapi.tiangolo.com/ja/tutorial/body/)

</details>