日本語用Sentence-LUKEモデルを用いたレコメンドモデル作成【Flask編】

## 前回の振り返り
前回の記事では、日本語用Sentence-LUKEモデルを用いたレコメンドモデルをターミナル上で実行することに成功しました！
もし前回の記事を見ていない方は、先に以下の記事を見ることをおすすめします！

https://qiita.com/sowallows10/private/55556e16040e480d5b9f

<br>

## 今回の目標
今回では、前回作成したモデルを用いて、実際にWEBアプリに落とし込む所までを目標にしていきます！
最終的には、トップページから、今の気分でおすすめのビールをレコメンドする気分診断のページを作っていきます！　※但し、性格診断は省きます。
![スクリーンショット 2023-11-18 3.01.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3529811/45764065-f23c-a152-fc37-6f9434e8621b.png)
![スクリーンショット 2023-11-18 3.01.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3529811/5556e6dc-61c7-ea1b-ccc2-c66f466c0b51.png)



## ライブラリのインストール
前回と同様に、必要なライブラリをインストールしていきます！
今回は、**Flask**というフレームワークを用いるため、以下のコマンドをターミナルで実行してください

```:ターミナル
conda activate (仮想環境名)　   　#仮想環境の有効化
```

```:ターミナル
conda install flask 　   #Flaskのインストール
```

<br>

## 関数化
まずは、前回作成したmodel.pyとFlask部分となるapp.pyを繋ぐための関数化を行なっていきます！
model.pyを以下のように書き換えてください！

<details><summary>コード</summary>

```python:model.py

from transformers import MLukeTokenizer, LukeModel
import sentencepiece as spm
import torch
import scipy.spatial
import pandas as pd


class SentenceLukeJapanese:
    def __init__(self, model_name_or_path, device=None):
        self.tokenizer = MLukeTokenizer.from_pretrained(model_name_or_path)
        self.model = LukeModel.from_pretrained(model_name_or_path)
        self.model.eval()

        if device is None:
            device = "cuda" if torch.cuda.is_available() else "cpu"
        self.device = torch.device(device)
        self.model.to(device)

    def _mean_pooling(self, model_output, attention_mask):
        token_embeddings = model_output[
            0
        ]  # First element of model_output contains all token embeddings
        input_mask_expanded = (
            attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float()
        )
        return torch.sum(token_embeddings * input_mask_expanded, 1) / torch.clamp(
            input_mask_expanded.sum(1), min=1e-9
        )

    @torch.no_grad()
    def encode(self, sentences, batch_size=8):
        all_embeddings = []
        iterator = range(0, len(sentences), batch_size)
        for batch_idx in iterator:
            batch = sentences[batch_idx : batch_idx + batch_size]

            encoded_input = self.tokenizer.batch_encode_plus(
                batch, padding="longest", truncation=True, return_tensors="pt"
            ).to(self.device)
            model_output = self.model(**encoded_input)
            sentence_embeddings = self._mean_pooling(
                model_output, encoded_input["attention_mask"]
            ).to("cpu")

            all_embeddings.extend(sentence_embeddings)

        return torch.stack(all_embeddings)


# 既存モデルの読み込み
def recommend(query)：　　#関数化
        MODEL_NAME = "sonoisa/sentence-luke-japanese-base-lite"
        model = SentenceLukeJapanese(MODEL_NAME)


        sentences = []
        #CSVファイルパスの代入（自分のパスを入れてね！）
        csv_file_path = 'beersking_kansei.csv'

        # 読み込む列の名前を指定
        target_column_name = 'introduction_text'

        # CSVファイルをDataFrameとして読み込む
        data = pd.read_csv(csv_file_path)

        # 指定した列のデータをリストに追加
        sentences = data[target_column_name].tolist()


        #理想のクラフトビールのイメージを文章で受け取る
        sentences.append(query)

        # クラフトビールの説明文、受け取った文章をエンコード（ベクトル表現に変換）
        sentence_embeddings = model.encode(sentences, batch_size=8)

        # 類似度上位5つを出力
        closest_n = 1

        distances = scipy.spatial.distance.cdist(
            [sentence_embeddings[-1]], sentence_embeddings, metric="cosine"
        )[0]

        results = zip(range(len(distances)), distances)
        results = sorted(results, key=lambda x: x[1])

        # Print the recommendations from the second column of the CSV file
        print("\n\n======================\n\n")
        print("Query:", query)
        print("\nあなたにおすすめのクラフトビールは:")
        #for i, distance in results[1:closest_n+1]:  # Exclude the query itself
            #print(data.iloc[i]['name'])
        index= data[data['introduction_text']==sentences[results[1][0]].strip()].index[0]

        return data.iloc[index,1],data.iloc[index,5],data.iloc[index,2]
```

</details>

<br>

## app.pyの作成
次に、Flaskでアプリを作る際に重要な、app.pyを記述していきます！
VSCode上にapp.pyを作成し、以下のコードを記述してください！

<details><summary>コード</summary>

```python:app.py
import os
from flask import (
     Flask, 
     request, 
     render_template)
from model import recommend #model.pyからrecommend関数をインポート

app = Flask(__name__)

@app.route('/', methods=['GET', 'POST']) #トップページのルーティング
def top():
    return render_template('top.html')

@app.route('/kibun1', methods=['GET'])   #気分入力ページのルーティング
def kibun1():
    return render_template('kibun1.html')
    
@app.route('/kibun2', methods=['GET','POST'])   #出力ページのルーティング
def kibun2():
    if request.method == "GET":
        return render_template('kibun2.html')
    elif request.method == "POST":
        favs = request.form.getlist("fav")　#name属性がfavのcheckboxから複数の値を取得
        data_str = ",".join(favs)
        name,imageurl,url = recommend(data_str)
        return render_template('kibun2.html', name=name,imageurl=imageurl,url=url)　#左辺がHTML、右辺がPython側の変数

if __name__ == "__main__":
    app.run(debug=True)

```
</details>

<br>

Flaskの基本が知りたいという方は、こちらの動画を参考にすることをおすすめします！

https://www.youtube.com/watch?v=bzbrpkbjWe8

<br>

## HTMLの作成
次に、先ほど記述したルーティングに従って、HTMLを書いていきます！

<details><summary>top.html</summary>

```html:top.html
<!doctype html>
<html lang="ja">

<head>
    <title>アプリのタイトル名
    </title>
    <meta charset="utf-8"/>
</head>

<body>
    <h1>これはTOPページです</h1>
    <a href="/kibun1">気分でビールをレコメンド！</a>
</body>

</html>
```
</details>


<details><summary>kibun1.html</summary>

```html:kibun1.html
<!doctype html>
<html lang="ja">

<head>
    <title>気分レコメンド</title>
    <meta charset="utf-8" />
</head>

<body>
    <a href="/">TOPへ戻る</a>
    <h1>気分診断のページです！</h1>

    <div class="kibunform">
        <form action="/kibun2" method="POST">
            <label><input type="checkbox" name="fav" value="スッキリ">スッキリ</label>
            <label><input type="checkbox" name="fav" value="コク">コク</label>
            <label><input type="checkbox" name="fav" value="甘さ">甘さ</label>
            <label><input type="checkbox" name="fav" value="フルーティー">フルーティー</label>
            <label><input type="checkbox" name="fav" value="和風">和風</label>
            <label><input type="checkbox" name="fav" value="苦味">苦味</label>
            <label><input type="checkbox" name="fav" value="低アルコール">低アルコール</label>
            <label><input type="checkbox" name="fav" value="高アルコール">高アルコール</label> 
            <!--nameタグを必ず"fav"にしてください！app.pyの方でname属性がfavのやつ拾ってます！-->
            <input type="submit" value="今の気分を送信!">
        </form>
    </div>
    
</body>

</html>
```
</details>


<details><summary>kibun2.html</summary>

```html:kibun2.html
<!doctype html>
<html lang="ja">

<head>
    <title>気分レコメンド</title>
    <meta charset="utf-8" />
</head>

<body>
    <h1>あなたの気分に合うビールは、"{{name}}"です！</h1>　　<!--{{変数名}}でPython側の変数をHTMLで表示-->
    <img src= "{{ imageurl }}" style="margin: 0px; vertical-align: top; width: 60%;">
    <br>
    <a href="{{url}}">このビールの詳細を見る！</a>
    <br>
    <a href="/">TOPへ戻る</a>
</body>

</html>
```
</details>

<br>

## 最後に

３つのファイルが書けたらpython app.pyをターミナルで実行してWebアプリを立ち上げてみてください！
きっとお好みのビールが見つかると思います！
そう信じて、私は今日も一日ビールを楽しもうと思います☺️

![スクリーンショット 2023-11-18 4.20.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3529811/b0494a64-7f35-e924-f350-7199928727d4.png)