Hugging Faceからダウンロードしたembedding modelをローカルで利用する

## 概要

- LangChainでの利用やChromaでダウンロード済みのモデルを利用したいくていろいろ試したので記録用に抜粋してまとめた次第

### なぜやろうと思のか


- OpenAIのAPIでEmbeddingするのは費用が掛かる
- ↑のモデルより性能の良いモデルがある
- 利用するたびに毎回HuggingFaceから落としてくるの時間かるしめんどい…
- ダウンロード済みの使えばええやん！って思ったけど公式ドキュメントに詳しく書いてない…

### 中身の補足

- 本来は[multilingual-e5-large](https://huggingface.co/intfloat/multilingual-e5-large)を利用する予定だが、容量がでかいので`base`を利用して試した（`model_name`を変更すればいける）

## モデルのダウンロード

公式ページの該当部分: [Download a snapshot of the repo](https://huggingface.co/docs/huggingface_hub/package_reference/file_download#huggingface_hub.snapshot_download)

~~~python
pip install sentence_transformers

from huggingface_hub import snapshot_download

model_name = "intfloat/multilingual-e5-base"
download_path = snapshot_download(
    repo_id=model_name,
    local_dir = f"path_to_model/{model_name}",
    local_dir_use_symlinks=False # ※1
    )
~~~

`download_path`が実際にダウンロードされたモデルのパスになるので以降の~pathにはこれを使えばOK


**※1**: デフォルトは`auto`。サイズの大きいファイルはダウンロード時のキャッシュへシンボリックリンクが張られるこれによりファイルサイズの最適化ができる。今回`False`にしているのは、シンボリックを作成せず、完全なダウンロードを実行するため（Jupyterのカーネルや、コンテナを再起動する場合困るので）


- [HuggingFaceモデルをローカルに落とす・シンボリックリンクを無効にする](https://wonderhorn.net/programming/hfdownload.html)


## SentenceTransformerで利用

~~~python
from sentence_transformers import SentenceTransformer

model_name = "intfloat/multilingual-e5-base"
model_path = f"path_to_model/{model_name}"
model = SentenceTransformer(model_path)

# 例えば
# テキストをembedding
embedding = model.encode('ベクトル化したい文字列')

# dfの列すべてembedding
df["vector"] = df["text"].apply(model.encode)
~~~

## LangChainで利用

~~~python
from langchain.embeddings import HuggingFaceEmbeddings

embeddings = HuggingFaceEmbeddings(model_name=model_path)
~~~
~~~python
embedded_texts = embeddings.embed_documents(
    [
        "Hi there!",
        "Oh, hello!",
        "What's your name?",
        "My friends call me World",
        "Hello World!"
    ]
)
len(embedded_texts), len(embedded_texts[0])
# →(5, 1536)
~~~

~~~python
text = "This is a test document."
query_result = embeddings.embed_query(text)
~~~

~~~python
doc_result = embeddings.embed_documents([text])
~~~


- LangChain - [Hugging Face](https://python.langchain.com/docs/integrations/text_embedding/huggingfacehub)
- LangChian - [Sentence Transformers on Hugging Face](https://python.langchain.com/docs/integrations/text_embedding/sentence_transformers)（Hugging Faceのエイリアス）

## ChromaDBでの利用

### 純正Chromaの場合

~~~python
import chromadb
from chromadb.utils import embedding_functions

client = chromadb.PersistentClient(path="/path/to/save/to")

ef = embedding_functions.SentenceTransformerEmbeddingFunction(model_name=model_path)

collection = client.create_collection(name="my_collection", embedding_function=ef)
~~~

- Chroma - [Getting started](https://docs.trychroma.com/getting-started)
- Chroma - [Usage Guide](https://docs.trychroma.com/usage-guide)
- Chroma - Embeddings - [Sentence Transformers](https://docs.trychroma.com/embeddings#sentence-transformers)
### LangChain利用の場合([LangChainで利用](#langchainで利用)と一緒ではある)

~~~python
from langchain.vectorstores import Chroma
from langchain.embeddings import SentenceTransformerEmbeddings
ef = SentenceTransformerEmbeddings(model_name=model_path) # HuggingFaceEmbeddingsでもOK
db = Chroma.from_documents(documents, embeddings)
~~~

- LangChain- [Chroma](https://python.langchain.com/docs/integrations/vectorstores/chroma)
- LangChain GitHub - [chroma.py](https://github.com/langchain-ai/langchain/blob/master/libs/community/langchain_community/vectorstores/chroma.py)