[Oracle Cloud]インポートしたONNXフォーマットモデルでOracle Database 23ai表データをEmbeddingしてみた。(2024/05/06)

# はじめに
Oracle Database 23ai では、ONNXフォーマットでエクスポートした学習済みEmbeddingモデルをインポート(ロード)し、Embeddingに使用することができます。インポートしたモデルは、VECTOR_EMBEDDINGまたはUTL_TO_EMBEDDINGをコールすることで使用することができます。
今回はOCI Base Database上のOracle Database 23aiを使用し、モデルのインポート、表データのEmbeddingを試してみました。


# 作業ステップ

- 事前作業
- モデルのインポート
- Embeddingの確認
- 表データのEmbedding
- セマンティック検索の実行

# 事前作業
- ONNXフォーマットでのモデルのエクスポート
[Oracle Database 23ai FreeとAPEXでRAGを使った生成AIアプリをローコード開発してみた (事前準備編)](https://qiita.com/ssfujita/items/34d9e3c39ca730d29351)
こちらの記事の(オプション)の項を参考にHugging Face上にあるモデルをONNXフォーマットにエクスポートします。
sentence-transformers/all-MiniLM-L6-v2をエクスポートした例

```python
$ python3

>>> from oml.utils import EmbeddingModel, EmbeddingModelConfig
>>> config = EmbeddingModelConfig.from_template("text",max_seq_length=512,quantize_model=True)
>>> em = EmbeddingModel(model_name="sentence-transformers/all-MiniLM-L6-v2", config=config)
>>> em.export2file("all-MiniLM-L6-v2",output_dir=".")
```


エクスポートしたファイル(all-MiniLM-L6-v2.onnx)をデータベースサーバー上のディレクトリオブジェクトが作成されているディレクトリにコピーします。



- 作業用データベースユーザの作成
PDBにSYSDBA権限で接続し、データベースユーザを作成し、「DB_DEVELOPER_ROLE」、「create mining model」権限を付与します。エクスポートしたONNXファイルを保管したディレクトリオブジェクトの操作権限を付与します。

```sql
$ export NLS_LANG=American_America.AL32UTF8
$ sqlplus / as sysdba

alter session set container=pdb1;
CREATE USER docuser identified by <password> DEFAULT TABLESPACE USERS quota unlimited on USERS;
GRANT DB_DEVELOPER_ROLE to docuser;
grant create mining model to docuser;
grant read on directory data_pump_dir to docuser;
grant write on directory data_pump_dir to docuser;
```



- サンプル表、データの作成
作成したデータベースユーザで表を作成し、データを格納します。

```sql
create table documentation_tab (id number,info varchar2(512));
insert into documentation_tab (id,info) values(	1,'San Francisco is in California.');
-- 省略
commit;
```

# モデルのインポート(ロード)

DBMS_VECTOR.LOAD_ONNX_MODELプロシージャを使用して、エクスポートしたONNXファイル(all-MiniLM-L6-v2.onnx)をインポート(ロード)します。

引数
- ディレクトリオブジェクト名 ： DATA_PUMP_DIR
- インポートするONNXファイル名 ： all-MiniLM-L6-v2.onnx
- 登録するモデル名 ： minilm_model
- JSON形式のメタ情報
    - function : embedding (モデルの種類)
    - embeddingOutput : emveddingOutput (生成されたエンベディングを保持するモデル出力)
    - input ： モデルの入力マッピングについて説明

```sql
EXECUTE DBMS_VECTOR.LOAD_ONNX_MODEL( 'DATA_PUMP_DIR', 'all-MiniLM-L6-v2.onnx', 'minilm_model', JSON('{"function" : "embedding", "embeddingOutput" : "embedding", "input": {"input": ["DATA"]}}'));
```

登録されたモデルの確認(user_mining_modelsビュー)

```sql
SELECT MODEL_NAME, MINING_FUNCTION, ALGORITHM,ALGORITHM_TYPE, MODEL_SIZE FROM user_mining_models ;


MODEL_NAME
------------------------------------------------------------------------------------------------------------------------
MINING_FUNCTION                ALGORITHM                      ALGORITHM_ MODEL_SIZE
------------------------------ ------------------------------ ---------- ----------
MINILM_MODEL
EMBEDDING                      ONNX                           NATIVE       23026809
```

# Embeddingの確認

インポートしたモデルはVECTOR_EMBEDDINGまたはUTL_TO_EMBEDDINGをコールしてEmbeddingすることができます。

- VECTOR_EMBEDDING

```sql
SQL> SELECT TO_VECTOR(VECTOR_EMBEDDING(MINILM_MODEL USING 'こんにちは' as data)) AS embedding;

EMBEDDING
--------------------------------------------------------------------------------
[-6.46025836E-002,1.02711894E-001,-3.69809195E-002,5.51512986E-002,-7.37261847E-
002,6.80525526E-002,1.30890697E-001,-4.8027765E-002,-1.7900113E-002,-4.08140272E
-002,-1.55771477E-002,-1.62137017E-001,9.53541975E-003,-5.44515364E-002,-5.17098
<以下略>

1 row selected.
```

- DBMS_VECTOR.UTL_TO_EMBEDDING
    - provider : database
    - model : モデル名 (MINILM_MODEL)

```sql
SQL> exec :embed_genai_params := '{"provider": "database", "model": "MINILM_MODEL"}';

PL/SQL procedure successfully completed.

SQL> select et.* from dbms_vector.utl_to_embeddings('こんにちは', json(:embed_genai_params)) et;

COLUMN_VALUE
--------------------------------------------------------------------------------
{"embed_id":1,"embed_data":"こんにちは","embed_vector":"[-6.46025
836E-002,1.02711894E-001,-3.69809195E-00
2,5.51512986E-002,-7.37261847E-002,6.805
25526E-002,1.30890697E-001,-4.8027765E-0
<以下略>

1 row selected.
```

# 表データのEmbedding

表のVarchar2列データをEmbeddingしてみます。

- DBMS_VECTOR.UTL_TO_EMBEDDINGの使用
    - 操作を簡略するためにEmbedding用のファンクションを作成します。
    ```sql
    CREATE OR REPLACE FUNCTION vector_get_minilm(keywd IN CLOB)
    RETURN vector IS
        v vector;
    BEGIN
        select et.embed_vector into v
        from dbms_vector.utl_to_embeddings(
           keywd, 
           json('{
	        "provider": "database",
	        "model": "MINILM_MODEL"
	        }')
	) t, 
  JSON_TABLE(
      t.column_value, 
      '$[*]' COLUMNS (
        embed_id NUMBER PATH '$.embed_id', 
        embed_data VARCHAR2(4000) PATH '$.embed_data', 
        embed_vector CLOB PATH '$.embed_vector'
        )
    ) et;

        RETURN v;
    END;
    /
    ```
     
    - Vector列を持つ表(documentation_table)を作成し、サンプル表(documentation_tab)のデータEmbeddingします。
    ```sql
    create table documentation_table_minilm (id number, text clob,v vector);
    insert into documentation_Table_minilm select id,info,vector_get_minilm(info) from docuser.documentation_tab;
    commit;
    ```


    - VECTOR_EMBEDDING
        - Vector列を持つ表(documentation_table)を作成し、サンプル表(documentation_tab)のデータEmbeddingします。
        ```sql
        create table documentation_table_minilm (id number, text clob,v vector);
        insert into documentation_Table_minilm select id,info,TO_VECTOR(VECTOR_EMBEDDING(MINILM_MODEL USING info as data)) from docuser.documentation_tab;
        commit;
        ```

# セマンティック検索の実行
Embeddingされたデータを使ってセマンティック検索を実施してみます。

```sql
SQL> select text from documentation_Table_minilm order by vector_distance(v, TO_VECTOR(VECTOR_EMBEDDING(MINILM_MODEL USING '&Keyword' as data)), EUCLIDEAN) fetch first 5 rows only;
Enter value for keyword: Cars
old   1: select text from documentation_Table_minilm order by vector_distance(v, TO_VECTOR(VECTOR_EMBEDDING(MINILM_MODEL USING '&Keyword' as data)), EUCLIDEAN) fetch first 5 rows only
new   1: select text from documentation_Table_minilm order by vector_distance(v, TO_VECTOR(VECTOR_EMBEDDING(MINILM_MODEL USING 'Cars' as data)), EUCLIDEAN) fetch first 5 rows only

TEXT
--------------------------------------------------------------------------------
Nissan GTRs are great.
Volvos are safe.
Porsches are fast and reliable.
Ferraris are often red.
Toyotas are reliable.

5 rows selected.

SQL> /
Enter value for keyword: NY
old   1: select text from documentation_Table_minilm order by vector_distance(v, TO_VECTOR(VECTOR_EMBEDDING(MINILM_MODEL USING '&Keyword' as data)), EUCLIDEAN) fetch first 5 rows only
new   1: select text from documentation_Table_minilm order by vector_distance(v, TO_VECTOR(VECTOR_EMBEDDING(MINILM_MODEL USING 'NY' as data)), EUCLIDEAN) fetch first 5 rows only

TEXT
--------------------------------------------------------------------------------
Manhattan is in New York.
Brooklyn is in New York.
The Bronx is in New York.
Harlem is in New York.
Staten Island is in New York.

5 rows selected.

```

「Cars」のキーワードで検索した結果
Nissan / Volvos / Porsche / Ferrari / Toyota の文字が含まれた文が返されました。
「NY」のキーワードで検索した結果
New York の文字が含まれた文が返されました。

# おわりに

Oracle Database 23ai の AI Vestor Search機能でONNXのEmveddingモデルをデータベースにインポートし表データのEmveddingを実施してみました。Embeddingされたデータに対してセマンティック検索をすることができました。

## 参考情報
- [Import ONNX Models and Generate Embeddings](https://docs.oracle.com/en/database/oracle/oracle-database/23/vecse/import-onnx-models-and-generate-embeddings.html)
- [DBMS_VECTOR ](https://docs.oracle.com/en/database//oracle/oracle-database/23/arpls/dbms_vector1.html)
- [Oracle Database 23ai FreeとAPEXでRAGを使った生成AIアプリをローコード開発してみた (事前準備編)](https://qiita.com/ssfujita/items/34d9e3c39ca730d29351)


- セマンティック検索の留意
    - 表に格納されているEmbeddingの次元数と検索で指定する次元数が違う場合、ORA-51808が発生します。
```
ERROR:
ORA-51808: VECTOR_DISTANCE() or L2_DISTANCE() is not supported for vectors with
different dimension counts (384, 768).
Help: https://docs.oracle.com/error-help/db/ora-51808/
```