はじめに
ジャングルパーティー!!
異次元フェス、行ってきました、nikkieです🙌
E5はtransformersから触ってもいいのですが、sentence-transformersからだと(特定のニーズを満たすコードは)数行で書けるということを見ていきます。
目次
- はじめに
- 目次
- multilingual-e5のUsage
- sentence-transformersなら数行でできるよ
- 同じembeddingsが得られていることの検証
- なぜ同じになるのか?
- 終わりに
multilingual-e5のUsage
文の埋め込み表現(embeddings)を得る手法の1つに、E5というモデルがあります。
サイズがいくつかありますが、largeはOpenAIのadaに匹敵するとか。
Hugging Face Hubで公開されています。
Usageのaverage_pool
の実装について、先日読み解きました。
これと同じコードをsentence-transformersを使うと数行で書けることを示します。
sentence-transformersなら数行でできるよ
from sentence_transformers import SentenceTransformer input_texts = [ "query: how much protein should a female eat", "query: 南瓜的家", ] model = SentenceTransformer("intfloat/multilingual-e5-small") embeddings = model.encode(input_texts)
実はこちら、Hugging Face Hubにも記載があります。
Support for Sentence Transformersの箇所ですね
処理内容は同じですが、少しだけ異なる点があります
- 返り値の型
- Usageのコードはtorch.Tensorです
- sentence-transformersを使うとNumPyのndarrayです
- 勾配計算
- Usageのコードは勾配計算が有効です
- average_pool関数を
@torch.inference_mode
(や@torch.no_grad
)でデコレートすると無効にできます1
- average_pool関数を
- sentence-transformersを使うと勾配計算が無効になります
- Usageのコードは勾配計算が有効です
同じembeddingsが得られていることの検証
Python 3.11.4
ライブラリのバージョン
certifi==2023.11.17 charset-normalizer==3.3.2 click==8.1.7 filelock==3.13.1 fsspec==2023.12.1 huggingface-hub==0.19.4 idna==3.6 Jinja2==3.1.2 joblib==1.3.2 MarkupSafe==2.1.3 mpmath==1.3.0 networkx==3.2.1 nltk==3.8.1 numpy==1.26.2 packaging==23.2 Pillow==10.1.0 PyYAML==6.0.1 regex==2023.10.3 requests==2.31.0 safetensors==0.4.1 scikit-learn==1.3.2 scipy==1.11.4 sentence-transformers==2.2.2 sentencepiece==0.1.99 sympy==1.12 threadpoolctl==3.2.0 tokenizers==0.15.0 torch==2.1.1 torchvision==0.16.1 tqdm==4.66.1 transformers==4.35.2 typing_extensions==4.9.0 urllib3==2.1.0
なぜ同じになるのか?
以前、SentenceTransformerクラスをインスタンス化して、encodeメソッドを呼ぶだけで済む仕組みについてソースコードを追いかけました。
SentenceTransformerはtorchのnn.Moduleを活用していて、3つのModuleからなります(順番にforwardを呼びます)。
- sentence-transformersのTransformerクラス
- Pooling
- Normalize
ここで、2はUsageのaverage_pool
関数と同じです。
実装に違いがあるのですが、結果としては同じになります2。
3もUsageのF.normalize
と同じです。
同じ引数で呼んでいます3
1ですが、Transformerクラスがtokenizerも持ちます。
この呼び出しが同じです。
Usage
tokenizer(input_texts, max_length=512, padding=True, truncation=True, return_tensors='pt')
sentence-transformers
https://github.com/UKPLab/sentence-transformers/blob/v2.2.2/sentence_transformers/models/Transformer.py#L113
self.tokenizer(*to_tokenize, padding=True, truncation='longest_first', return_tensors="pt", max_length=self.max_seq_length)
max_length
はどちらも512トークンで揃っていますtruncation
ですが、True
と'longest_first'
は同じ意味です- https://huggingface.co/docs/transformers/main_classes/tokenizer#transformers.PreTrainedTokenizer.__call__.truncation
- 「True or 'longest_first'」という項目!
SentenceTransformerの実装を見ていくことで、Usageと同様の実装をしていることが確認できました!
注意点としては、tokenizerの呼び出しがハードコードになっているために、呼び出しの引数の変更が難しいです。
すなわち、Usageのコードと同じ引数で呼び出す分には数行のコードで簡単に使えますが、Usageのコードから引数を変える(例:padding=False
)場合、変更が大変になっています4。
引数を変える場合、SentenceTransformerクラスを使う旨味が激減していて、私だったらUsageのコードを書くことになるかなと考えています(プルリクチャンスかも)
終わりに
E5のUsageのコードと全く同じコードに限れば、sentence-transformersで数行で書けることを見てきました。
最初E5をHugging Face Hubで見たときに、Usageとsentence-transformersのコードが本当に同じことをやっているかが分かりませんでした。
一つ一つの要素を見た今なら自信を持って言えます、同じです!
- https://pytorch.org/docs/stable/notes/autograd.html#grad-modes の表から、Data processingで使うのでinference modeを選びました↩
- 読み解き記事のP.S.に書いていますが、sentence-transformersはattention maskで内積をとる実装です↩
- https://github.com/UKPLab/sentence-transformers/blob/v2.2.2/sentence_transformers/models/Normalize.py#L14↩
- sentence-transformersはsimpleを謳っていますが、この点を考えると私の見地からはsimpleというよりもeasyであるように見受けられます(simpleというんだったら、tokenizer呼び出しの引数を変更する方法も提供されてほしいです)↩