Weaviateのcollectionオブジェクトについて詳しく調べる

こんにちはぺいぺいです。今回はオープソースベクトルDBのWeaviateのcollectionオブジェクトについて公式ドキュメントを読みつつまとめます。基本的には、以下の記事をさらに深掘りした内容になります。

https://qiita.com/paypay-1126/items/d53669e6c5b197202032

また、OpenAIのAPIなどを使用せず、Hugging face から撮ってきたBERTを使用してテキストを埋め込んだ記事として以下の記事も参考になるかと思います。

https://qiita.com/kongo-jun/items/8be61733db5b3c810866

基本的には、以下の公式ドキュメントを参考に記事を作成します。基本的には、スクリプトはPythonで記述することとします。

https://weaviate.io/developers/weaviate/config-refs/schema

# Weaviateにおけるcollectionオブジェクトの役割
Weaviate は　"collections" という形式でデータを保存する。collections は、同じデータ構造を共有するオブジェクトの集合である。実際に使用する際は、以下のスクリプトは、collectionを定義しているものである。しかしながらこのcollectionは定義されただけであり、データの追加などは行なっていないことに注意。

``` python
import weaviate
import weaviate.classes.config as wc
import os

api_key = os.environ.get('OPENAI_API_KEY')
headers = {"X-OpenAI-Api-Key": api_key}  # Replace with your OpenAI API key
client = weaviate.connect_to_local(headers=headers)
print('------connected------')
client.collections.delete("Agriculture")

try:
    client.collections.create(
        name="Agriculture",
        properties=[
            wc.Property(name="task_name", data_type=wc.DataType.TEXT),
            wc.Property(name="purpose", data_type=wc.DataType.TEXT),
            wc.Property(name="action", data_type=wc.DataType.TEXT),
            wc.Property(name="target", data_type=wc.DataType.TEXT),
            wc.Property(name="location", data_type=wc.DataType.TEXT)
        ],
        # Define the vectorizer module
        vectorizer_config=wc.Configure.Vectorizer.text2vec_openai(),
        # Define the generative module
        generative_config=wc.Configure.Generative.openai()
    )
finally:
    client.close()

```

各オブジェクトは複数のプロパティ（Property）で構成される。プロパティには名前とデータのタイプが決められている。また、collectionは複数のオブジェクトで構成される。テーブルデータを例に挙げると、collection=テーブル、オブジェクト=行、プロパティ=列と考えることができる。

# Collection schema
Shemaとは、Weaviateにおいてどのようにデータをストアし、組織化し、データをとってくるかを定義するものである。schemaは必ずデータが実際にアップロードされる前に定義されなければならない。Weaviateはschemaを自動で推測するauto-schemaという機能があるものの、一般的にはマニュアルでセットすることが推奨される。

## はじめに
"collection schema"は、Weaviateにおいて、どのようにオブジェクトをストアし、どのように索引してくるかを記述するものである。また、本記事では、"class"と"collection"を同じ意味で扱う。

## Collection object
プロパティを含んだ完全な形式のcollectionオブジェクトの例を以下に示す。

```python
{
  "class": "Article",                       // collectionの名前(string型）
  "description": "An article",              // 参照のための情報
  "vectorIndexType": "hnsw",                // デフォルトはhnsw
  "vectorIndexConfig": {
    ...                                     // ベクトル間距離を含めたベクトルindexの型の特殊設定
  },
  "vectorizer": "text2vec-contextionary",   // 埋め込みモデル
  "moduleConfig": {
    "text2vec-contextionary": {
      "vectorizeClassName": true            // collection名を埋め込むテキストに含めるか(default true)
    }
  },
  "properties": [                           // collectionに追加するプロパティリスト
    {
      "name": "title",                     // プロパティ名
      "description": "title of the article",              // プロパティの説明
      "dataType": [                         // データタイプ。 coss-referencesを作成する
                                            // と、複数のデータ型を持てるので、dataTypeはリストで指定している。
        "text"
      ],
      "moduleConfig": {                     // モジュール特有の設定
        "text2vec-contextionary": {
          "skip": true,                     // trueにすると全てのプロパティの値が埋め込みの入力に使用されるという設定がなくなる。
                                            // デフォルトはfalseであり、基本的には全てのプロパティ値が埋め込みの入力とされることを意味する。
          "vectorizePropertyName": true,    // プロパティの名前が埋め込みに使用されるかを定義。デフォルトはfalse
        }
      },
      "indexFilterable": true,              // Optional, default is true. By default each property
                                            //    is indexed with a roaring bitmap index where
                                            //     available for efficient filtering.
      "indexSearchable": true               // Optional, default is true. By default each property
                                            //    is indexed with a searchable index for
                                            //    BM25-suitable Map index for BM25 or hybrid
                                            //    searching.
    }
  ],
  "invertedIndexConfig": {                  // Optional, index configuration
    "stopwords": {
      ...                                   // Optional, controls which words should be ignored in the inverted index, see section below
    },
    "indexTimestamps": false,               // Optional, maintains inverted indexes for each object by its internal timestamps
    "indexNullState": false,                // Optional, maintains inverted indexes for each property regarding its null state
    "indexPropertyLength": false            // Optional, maintains inverted indexes for each property by its length
  },
  "shardingConfig": {
    ...                                     // Optional, controls behavior of the collection in a
                                            //    multi-node setting, see section below
  },
  "multiTenancyConfig": {"enabled": true}   // Optional, for enabling multi-tenancy for this
                                            //    collection (default: false)
}
```

## Collection の作成

### 可変性
Collectionを作成する際、作成後にも可変なパラメータと可変でないパラメータがともに存在する。もし、可変でないパラメータを変更したい場合は一度collectionを削除する必要がある。collectionを作成した後でもプロパティの追加はできるが、作成時に定義したプロパティを変更することはできない。

### Auto-schema
Auto-schemaはcollectionの定義を追加されたデータから自動的に推測する機能である。デフォルトでこれが動作するように設定されており、`docker-compose.yml`において`AUTOSCHEMA_ENABLED: 'false'`と記述することで解除することができる。Auto-schemaの主な機能は以下の通り。

* オブジェクトが定義されていないcollectionに対して追加された際、collectionを作成
* 追加されたオブジェクトから足りていないプロパティを追加
* `int[], text[], number[], boolean[],date[],object[]`などの配列のタイプの推論
* `object`と`object[]`のネストされたプロパティのデータ方を予測
* もともと存在するschemaと矛盾するプロパティを含むオブジェクトを追加しようとするとエラーを投げる

### 複数のベクトル
collectionは複数の名前のついたベクトルを保持することができる。これらのベクトルは、それぞれの設定を持っている。それぞれのベクトル空間はそれぞれのindexに紐付き、それぞれの圧縮アルゴリズムを持ち、それぞれの埋め込みモデルを持つ。つまり、一つのcollectionに対し、複数の埋め込み手法を同時に使用し、複数の検索方法を適用することができるということである。例えば、複数の埋め込みモデルを使用した異なる埋め込み表現による検索が可能であるということである。

### collectionを作成した後にプロパティを追加する
オブジェクトをimportつまり、connectionに追加した後、プロパティを追加しようとすると、nullを含むフィルタリングや特定の長さ以上の文字列の検索などのフィルタリング処理がうまく働かないことがある。

これは、inverted index（反転インデックス）と呼ばれる、各フィールドやプロパティの値を逆にマッピングすることで検索を効率化する手法が追加プロパティに適用されないためである。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2718862/09f4eebf-87de-09ea-dade-24abbafe7e06.png)


これを防ぐためには以下のどちらかの対応を取る必要がある。
* プロパティをオブジェクトのimport前に追加する
* 一度collectionを破棄し、再度作り直す

# 主要なパラメータ
ここからは、collection オブジェクトの主要なパラメータについてまとめていく。マイナーなものに関しては省略する。

## class
`class`は、collectionオブジェクトの名前パラメータである。collection名は大文字で始まる必要がある。一方で、全て小文字の場合をプロパティ名にすることで、区別する。

## description
Weaviateを使用するユーザーに与えるための追加情報。特に処理に使用されるわけではない。

## vectorizer
テキストの埋め込みモデルに関するパラメータ。以下のサイトに使用可能なVectorizerが記載されている。例えば、OpenAIの埋め込みモデルを使用したい場合などには、`"vectorizer": "text2vec-openai"`などと指定する。

https://weaviate.io/developers/weaviate/modules/retriever-vectorizer-modules

## moduleConfig
以下のように設定することで、collection名を埋め込むテキストに含めるかを決定することができる。デフォルトでは、Trueになっている。Trueの時、埋め込むテキストの先頭に`"class": "Article"`で指定したクラス名=collection名が連結される。

```python
  "moduleConfig": {
    "text2vec-contextionary": {
      "vectorizeClassName": true            // collection名を埋め込むテキストに含めるか(default true)
    }
```

## properties 
このパラメータに追加したいデータを記述することで、それらを格納することを想定したcollectionが作成される。プロパティは複数追加することができるので、リスト形式で追加していく。それぞれのプロパティには、プロパティ名`name`と、データの型`class`を指定する必要がある。


```python
            "properties":[
                {
                    "name": "task_name",
                    "dataType": ["text"],
                    "moduleConfig": {           
                        "text2vec-contextionary": {
                            "vectorizePropertyName": true
                        }
                    }
                },
                {
                    "name": "purpose",
                    "dataType": ["text"],
                    "moduleConfig": {           
                        "text2vec-contextionary": {
                            "vectorizePropertyName": true
                        }
                    }
                },
```
また、`"vectorizePropertyName": true`とすることで、プロパティ名を含めて埋め込み表現を得ることができる。

# JSONファイルでのSchemaの設定
Schema は、JSON形式で外部に保存しておき、dict型データとして読み込み、collection の定義に使用することができる。

```json
{
            "class": "Agriculuture",
            "description": "農作業基本オントロジーに関するデータ。URL:https://cavoc.org/aao/ns/4/A1.html",
            "vectorizer": "text2vec-openai",
            "moduleConfig": {
                "text2vec-contextionary": {
                    "vectorizeClassName": true
                }
            },
            "properties":[
                {
                    "name": "task_name",
                    "dataType": ["text"],
                    "moduleConfig": {           
                        "text2vec-contextionary": {
                            "vectorizePropertyName": true
                        }
                    }
                },
                {
                    "name": "purpose",
                    "dataType": ["text"],
                    "moduleConfig": {           
                        "text2vec-contextionary": {
                            "vectorizePropertyName": true
                        }
                    }
                }
            ]
        }
```
上記のJSONファイルを読み込む際は、以下のようなPythonスクリプトを実行。上記のJSONファイルを`./config.json`としている。

```python
config_dict = load_json('./config.json')

    if config_dict['embedding_model']=='openai':
        api_key = os.environ.get('OPENAI_API_KEY')
        headers = {"X-OpenAI-Api-Key": api_key}  # Replace with your OpenAI API key
        client = weaviate.connect_to_local(headers=headers)

    else:
        print('Invalid embedding model. Please check embedding_model in the config file')
        sys.exit()
    
    print('------connected------')

    try:
        client.collections.create_from_dict(config_dict)
```

上記のスクリプトを実行することで、collectionを作成することができる。