祝 Azure OpenAI Service GA! ③Embedding & 自然言語検索を Jupyter Notebook からお試し

1/16 に GA (General Availability: 一般提供) になった Azure OpenAI Service は、Azure 上で OpenAI が稼働するサービスです。Cognitive Services の一員として & Web API として利用できる、Studio と呼ばれる GUI が用意されており、テストや調整などが可能です。

今回は、Azure OpenAI Studio **から操作できない** Embedding 機能 (ご自身のデータを投入して、自然言語でセマンティック検索が可能な機能) を試してみます。まずは、Jupyter Notebook から Python ライブラリーを使って利用してみます。

> 3/10 現在、GA したとはいえ、Azure OpenAI Service を利用するには [Web から利用申請](https://customervoice.microsoft.com/Pages/ResponsePage.aspx?id=v4j5cvGGr0GRqy180BHbR7en2Ais5pxKtso_Pz4b1_xUOFA5Qk1UWDRBMjg0WFhPMkIzTzhKQ1dWNyQlQCN0PWcu&culture=ja-jp&country=jp)が必要です。
Azure OpenAI Service の申し込み方法は [祝 Azure OpenAI Service GA! ①](https://qiita.com/annie/items/ce7214c6d683b9442382) の記事をご覧ください。

> Azure OpenAI Service 自体は GA ですが、3/10~ 利用可能になった ChatGPT, 3/22~ 利用可能になった GTP-4 は現在プレビュー中です。

# 準備

- Azure OpenAI Service の作成
    - [GPT-3 & ChatGPT を Azure OpenAI Studio からお試し](https://qiita.com/annie/items/ce7214c6d683b9442382#azure-portal-%E3%81%8B%E3%82%89-azure-openai-service-%E3%81%AE%E4%BD%9C%E6%88%90) と同様の手順で、Azure OpenAI Service 利用可能になった Azure サブスクリプションで、Azure OpenAI Studio サービスを作成します。この時、場所は **South Central US (米国中南部)** を選択してください。(3/23 現在、今回必要なモデルが East US には用意されていません)
    - キー1 と エンドポイント が利用するのに必要になりますので、ローカルに保存しておきます。(右端のコピーボタンをクリックすると、内容がコピーできます。)

![スクリーンショット 2023-03-10 102704.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/bfe9e6b3-15ba-c4b1-558b-d9cea07c18ec.png)

- Embededding して検索したいデータ
    - id, text, ... といった形式のデータを CSV で準備しておきます。今回は "bing検索" で Tweet 検索したデータを利用しました (100件)
    - **[4/11 ハンズオン開催期間限定]**
        - **[利用したサンプルデータ](https://embeddedings2023sto.blob.core.windows.net/embedding-data/tweets_2023-03-23.csv?sp=r&st=2023-04-11T07:00:00Z&se=2023-04-14T15:00:00Z&spr=https&sv=2021-12-02&sr=b&sig=zP7x2P89EasUA9gKLoTmjNNC1xKnqMyw9vicTnCx4gU%3D)** をダウンロード可能です (4/15(金)0:00 まで)。

- 開発環境
    - 今回はローカル環境ではなく、[Azure Machine Learning](https://azure.microsoft.com/ja-jp/products/machine-learning) の Azure Machine Learning Studio にある Jupyter Notebook を使いました。[クイックスタート: Azure Machine Learning の利用を開始するために必要なワークスペース リソースを作成する](https://learn.microsoft.com/ja-jp/azure/machine-learning/quickstart-create-resources) の手順で、ワークスペースとコンピューティングインスタンス(VM)を作成します。
    - [クイック スタート: スタジオで Jupyter ノートブックを実行する](https://learn.microsoft.com/ja-jp/azure/machine-learning/quickstart-run-notebooks) を参照して、新しいフォルダーと　Jupyter ノートブックを作成し、検索したい CSV データを同じフォルダーにアップロードしておきます。カーネルは Azure Machine Learning Studio で用意されている環境である **Python3.8 - AzureML** を使います。

![スクリーンショット 2023-03-23 111204.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/3deaced1-8403-e93d-157b-c57e114a82d5.png)


# 手順

## 1. Azure OpenML Studio から Text Search Curie モデルのデプロイ

[Azure OpenAI Studio](https://oai.azure.com) を開き、Azure OpenAI Service を作成した (or 利用するためのロールを持っている) アカウントでサインインします。

今回は Text Search Curie (text-search-curie-doc-001, text-search-curie-query-001) モデルを利用するので、モデルをデプロイしてから試用を行います。[GPT-3 & ChatGPT を Azure OpenAI Studio からお試し](https://qiita.com/annie/items/ce7214c6d683b9442382#gpt-3-%E3%83%A2%E3%83%87%E3%83%AB-%E3%81%8A%E3%82%88%E3%81%B3-chatgpt-%E3%83%A2%E3%83%87%E3%83%AB%E3%81%AE%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4) と同様の手順で、text-search-curie-doc-001 と text-search-curie-query-001、2つのモデルを作成します。

> 現在、ひとつのモデルにつき、ひとつしかデプロイできません。

![スクリーンショット 2023-03-23 101450.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/ac9f2bee-d3b1-5bad-b15d-24199446790c.png)

## 2. Azure Machine Learning Studio の Jupyter Notebook を使って、Python ライブラリーから Embededing 機能の実装とセマンティック検索をお試し

### ライブラリーのインポート

今回利用する openai のライブラリー他、作業に必要なライブラリーをインポートします。

```TweetSearch202303.py
import openai
import re
import requests
import sys
from num2words import num2words
import os
import pandas as pd
import numpy as np
from openai.embeddings_utils import get_embedding, cosine_similarity
from transformers import GPT2TokenizerFast
```

### Azure OpenAI Service の情報セット＆接続確認

本来なら環境変数として設定すべきですが、お試しなので直接、ローカルに保存しておいた Azure OpenAI Service のキー と エンドポイントをコード内で設定します。

```TweetSearch202303.py
API_KEY = "YOUR_API_KEY" 
RESOURCE_ENDPOINT = "https://YOUR_OPENAI_SERVICE.openai.azure.com/" 

openai.api_type = "azure"
openai.api_key = API_KEY
openai.api_base = RESOURCE_ENDPOINT
openai.api_version = "2022-12-01"

url = openai.api_base + "/openai/deployments?api-version=2022-12-01"
r = requests.get(url, headers={"api-key": API_KEY})
print(r.text)
```

```print(r)``` で、以下のような Azure OpenAI Service のデプロイ情報などが取得できます (ので接続が確認できます。)
![スクリーンショット 2023-03-23 111301.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/b629419d-0938-c504-0f6d-db2d1ee7ad0b.png)


### データの読み込みと前処理

用意した CSV ファイルを読み込みます。

```TweetSearch202303.py
df = pd.read_csv("tweets_2023-03-23.csv")
df
```

今回は、tweetId, userId, text, lang という列を持つ (Tweet を取得した) CSV データであることが確認できます。

![スクリーンショット 2023-03-23 111354.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/c9c7a066-95d2-bff0-9cac-d58bf0ed5494.png)

ここで、データから改行 (\n) や不要な文字などを削除する整形を行います。

```TweetSearch202303.py
# s is input text
def normalize_text(s, sep_token = " \n "):
    s = re.sub(r'\s+',  ' ', s).strip()
    s = re.sub(r". ,","",s)
    # remove all instances of multiple spaces
    s = s.replace("..",".")
    s = s.replace(". .",".")
    s = s.replace("\n", "")
    s = s.strip()
    
    return s

df['text'] = df["text"].apply(lambda x : normalize_text(x))
```

### Tokenizer によるトークン化とトークン数の算出

Tokenizer を使ってトークン化と、トークン数の計算を行い、text のトークン(数)を n_token 列に追加します。

```TweetSearch202303.py
tokenizer = GPT2TokenizerFast.from_pretrained("gpt2")
df['n_tokens'] = df["text"].apply(lambda x: len(tokenizer.encode(x)))
df = df[df.n_tokens<2000]
len(df)
```

> *n_tokens<2000* としているのは、一度に利用できるトークンの上限が 2000 だからです。

トークンが2000 以下のデータは 59件、調べてみるとデータに n_token 列が追加されたのが分かります。

![スクリーンショット 2023-03-23 111512.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/2e34c1dc-f23d-a1f9-1b97-8dedfd34d18a.png)


### text-curie-search-doc を使った埋め込み (Embedding)

整形したデータを curie-search モデルに埋め込みます。これには **text-curie-search-doc** モデルを利用します。

```TweetSearch202303.py
df['curie_search'] = df["text"].apply(lambda x : get_embedding(x, engine = 'text-search-curie-doc-001'))
```

埋め込んだ検索データのベクトルが curie_search 列として追加されました。

![スクリーンショット 2023-03-23 111602.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/5c576328-b403-f7d0-6737-bee675390066.png)


### text-curie-search-query を使ったセマンティック検索

それでは、実際にこの Embedded Search の検索を行ってみます。**text-curie-search-query** モデルを利用して、search_docs という関数を作っておきます。コサイン類似度(cosine_similarity) で優先度をつけて、関連度の高い文章を検索します。

```TweetSearch202303.py
# search through the reviews for a specific product
def search_docs(df, user_query, top_n=3, to_print=True):
    embedding = get_embedding(
        user_query,
        engine="text-search-curie-query-001"
    )
    df["similarities"] = df.curie_search.apply(lambda x: cosine_similarity(x, embedding))

    res = (
        df.sort_values("similarities", ascending=False)
        .head(top_n)
    )
    if to_print:
        display(res)
    return res
```

> コサイン類似度を利用した検索については [Azure OpenAI Service の埋め込みについて](https://learn.microsoft.com/ja-jp/azure/cognitive-services/openai/concepts/understand-embeddings) に解説されています。

検索は自然言語で行います。search_docs に "can I get information how to use bing chat?" (Bing Chatの使い方について教えて？)、上位 4 件取得するようにセットします。

```TweetSearch202303.py
res = search_docs(df, "can I get information how to use bing chat", top_n=4)
```

すると、関連度の高いデータ行が表示されます。(similarities 列に関連度が示されます)

![スクリーンショット 2023-03-23 111825.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/f5e133ba-fc58-ec01-8f01-ad0a199d7fec.png)

text 列が省略されているので、確認します。

```TweetSearch202303.py
res["text"][2]
```

![スクリーンショット 2023-03-23 111857.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/6e0e09df-ef8a-d522-9c20-2ce34e60444d.png)


今度は質問を変えて、"can i get information how much people like Bing chat?" (どれくらい皆さんはBing Chat を気に入っているのかしら)、同じく上位 4 件を取得するようにセットします。

```TweetSearch202303.py
res = search_docs(df, "can i get information how much people like Bing chat?", top_n=4)
```

すると、違うデータが表示されるので、それぞれ Text 全文を確認します。

![スクリーンショット 2023-03-23 111858.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117583/09416a0f-9535-5854-14e1-cb62f21665d5.png)


# 参考: ご自分の Jupyter Notebook 環境で試すとき

詳しい手順などは [チュートリアル: Azure OpenAI Service の埋め込みとドキュメント検索を確認する](https://learn.microsoft.com/ja-jp/azure/cognitive-services/openai/tutorials/embeddings) に記載されています。ローカル環境など、ご自分の環境で試される場合はこちらをご確認ください。