ollamaでdocumemtをembeddingしてqueryとcoscine比較どうするねん

## langchainで ollama使ってembedding

文章をベクトル化する方法はググってすぐ出てきました[^4]。

```python
from langchain_community.embeddings import OllamaEmbeddings
ollama_emb = OllamaEmbeddings(
    model="llama:7b",
)
r1 = ollama_emb.embed_documents(
    [
        "Alpha is the first letter of Greek alphabet",
        "Beta is the second letter of Greek alphabet",
    ]
)
r2 = ollama_emb.embed_query(
    "What is the second letter of Greek alphabet"
)
```

[^4]: [langchain_community.embeddings.ollama.OllamaEmbeddings — 🦜🔗 LangChain 0.1.16](https://api.python.langchain.com/en/latest/embeddings/langchain_community.embeddings.ollama.OllamaEmbeddings.html)


## embedding_utils has gone

2023年11月にopenai.embedding_utilsが使えなくなってる[^5]。

[^5]: [Embeddings_utils / distance formulas - where did it move?](https://community.openai.com/t/embeddings-utils-distance-formulas-where-did-it-move/479868)

代わりに案内されているのが`scipy.spatial.distance`

```python
from scipy.spatial import distance
In [19]: distance.cosine(r1[0],r2)                                                                                     │      ¦   )
Out[19]: 0.2281260476912138
```

文章をベクトル化してcosine比較します。

[scipy.spatial.distance.cosine — SciPy v1.13.0 Manual](https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cosine.html)

簡単に説明すると、cosine距離は2つのベクトルの類似度を表す指標です。

cosine距離は0から1の範囲の値をとり、2つのベクトルが完全に同じ方向を向いている場合は0、完全に逆方向を向いている場合は1となります。

具体的な計算方法は以下の通りです:

1. 2つのベクトルu, vを用意する
2. u・vはuとvの内積(ドット積)を表す
3. ||u||_2は2ノルム(ユークリッド距離)を表す
4. cosine距離 = 1 - (u・v) / (||u||_2 * ||v||_2)

つまり、2つのベクトルが近いほど、cosine距離は小さくなり、遠いほど大きくなるということです。

## 複数の文章を比べて検索器を作ろう

たまたま過去に[モデル就業規則](https://www.mhlw.go.jp/content/001018385.pdf)を用いて500トークンごとに分割したJSONがあります[^1][^2][^3]ので、それを使いましょう。

[^1]: [【業務効率化】ChatGPTを活用した就業規則の自動回答システムの開発](https://qiita.com/u1and0/items/593781e4a20388a83238)
[^2]: [model-embedding.json](https://drive.google.com/file/d/1DUvsg1K3x9OuIdFCU9UfKtxrq7lH52mA/view?usp=share_link)
[^3]: [厚生労働省 / モデル就業規則について](https://www.mhlw.go.jp/stf/seisakunitsuite/bunya/koyou_roudou/roudoukijun/zigyonushi/model/index.html)

```python


In [21]: pd.read_json("data/model-embeddings.json")
Out[21]:
text  length                                          embedding
0    １モデル就業規則令和４年 11 月版厚生労働省労働基準局監督課２はじめに１ 就業規則の意義労...     500  [-0.0031310103
, -0.0081168134, 0.0068422724, -...
1     以上に分けて交替に就業させる場合においては就業時転換に関する事項（２） 賃金関係賃金の決定...     500  [-0.0118839
83400000001, -0.017528709, 0.027896...
2    その他事業場の労働者すべてに適用されるルールに関する事項なお、就業規則の内容は、法令及び当該...     500  [-0.00675
76934000000005, -0.0051615201, 0.0119...
3    する企業については、営業所、店舗等の就業規則が変更前、変更後ともに本社の就業規則と同一の内容...     500  [-0.00412
3474, -0.0041268268, 0.00174996220000...
4    労基則」といいます。）第６条の２）。就業規則の作成又は変更に当たっては、その内容をよく吟味す...     500  [-0.01065
88136, -0.00044822950000000003, 0.011...
..                                                 ...     ...                                                ...
154   時間の通算や簡便な労働時間管理の方法について考え方を示していますので、その考え方に基づき通...     500  [-0.001495
9859000000001, -0.007987556, 0.01083...
155  日労働の合計の時間数について、１か月１００時間未満及び２～６か月平均８０時間以内とすること）...     500  [0.000400
4154, -0.0113613438, 0.0087518478, -0...
156  ような場合に該当しないかの確認や、該当しない場合であって労働時間の通算の対象となるときにおい...     500  [0.003357
9303, -0.0182754919, 0.01175449600000...
157  。・十和田運輸事件（東京地判平成１３年６月５日）運送会社の運転手が年に１、２回の貨物運送のア...     500  [-0.01777
30452, -0.0215087812, 0.0167825092, -...
158   則により使用者の業務上の秘密を守る義務を負うとしたうえで、会社が機密漏洩防止に特段の配慮を...     434  [-0.021447
9137, -0.0269076359, -0.0009704622, ...

[159 rows x 3 columns]

In [22]: df = pd.read_json("data/model-embeddings.json")

In [24]: len(df.text)
Out[24]: 159   # 159項目のモデル就業規則

In [27]: df = df[:20]  # 試しなので、先頭から20項目だけに絞ります。

In [28]: df
Out[28]:
text  length                                          embedding
0   １モデル就業規則令和４年 11 月版厚生労働省労働基準局監督課２はじめに１ 就業規則の意義労...     500  [-0.0031310103,
-0.0081168134, 0.0068422724, -...
1    以上に分けて交替に就業させる場合においては就業時転換に関する事項（２） 賃金関係賃金の決定...     500  [-0.01188398
3400000001, -0.017528709, 0.027896...
2   その他事業場の労働者すべてに適用されるルールに関する事項なお、就業規則の内容は、法令及び当該...     500  [-0.006757
6934000000005, -0.0051615201, 0.0119...
3   する企業については、営業所、店舗等の就業規則が変更前、変更後ともに本社の就業規則と同一の内容...     500  [-0.004123
474, -0.0041268268, 0.00174996220000...
4   労基則」といいます。）第６条の２）。就業規則の作成又は変更に当たっては、その内容をよく吟味す...     500  [-0.010658
8136, -0.00044822950000000003, 0.011...
5   力発生時期は、就業規則が何らかの方法によって労働者に周知された時期以降で、就業規則に施行期日...     500  [-0.013857
6785, -0.0050431425, 0.0154229077, -...
6   日制を採用する場合）の規程例第１９条第２項中の「７ 時間１５分」などの部分や、第４３条５第２...     500  [0.00226065
07, 0.0079080816, 0.0182731748, -0....
7   用労働者の雇用管理の改善等に関する法律（平成５年法律第７６号。以下「パートタイム・有期雇用労...     500  [-0.014668
452600000001, 0.0034421473, 0.012050...
8    位の変形労働時間制（隔週週休２日制を採用する場合）の規程例７第１９条（労働時間及び休憩時間...     500  [-0.0046148
431, 0.009364034, 0.0174114592, -0....
9   （家族手当）第３６条（通勤手当）第３７条（役付手当）第３８条（技能・資格手当）第３９条（精勤...     500  [-0.011253
117600000001, -0.0004266213, 0.02345...
10  ）第５６条（退職金の支払方法及び支払時期）第９章 無期労働契約への転換……………………………...     500  [-0.010817763400000001
, -0.0086236252, 0.00612...
11  就業に関する事項を定めるものである。２ この規則に定めた事項のほか、就業に関する事項について...     500  [0.00205670
39, -0.0088717984, 0.00623263560000...
12  に関する事項については、別に定めるところによる。３ 前項については、別に定める規則に定めのな...     500  [0.00169449
40000000002, -0.0068744714, 0.01481...
13  2.html）。２ 働き方改革を推進するための関係法律の整備に関する法律（平成３０年法律第７...     500  [-0.0082969703, -
0.0068931733, 0.0128659317000...
14   ）第３条 会社は、この規則に定める労働条件により、労働者に就業させる義務を負う。また、労働...     500  [-0.01525460
9300000001, -0.015014798400000001,...
15  ことを要件とすること等は、間接差別として禁止されています（均等法第７条）。（採用時の提出書類...     500  [-0.017160
1791, -0.0022645593000000003, 0.0070...
16   か月間を試用期間とする。２ 前項について、会社が特に認めたときは、試用期間を短縮し、又は設...     500  [-0.01202933
9600000001, -0.008874323200000001,...
17  を記した労働条件通知書及びこの規則を交付して労働条件を明示するものとする。【第７条 労働条件...     500  [-0.0181804
337, 0.0012214353, 0.0092371562, -0...
18  れかの方法を希望した場合には、当該方法により労働条件の明示を行うことができます。・ ファクシ...     500  [-0.0210897
569, 0.0112395687, 0.0040894994, -0...
19  り労働条件の明示を行うことはできません。さらに、パートタイム・有期雇用労働者については、雇入...     500  [-0.002099
7389, -0.0057025515, 0.0071730209, -...

In [29]: df.embedding = ollama_emb.embed_documents(df.text)

```

ここで、質問をベクトル化します。

```python
In [30]: query = "パートタイムについて"

In [31]: query_vector = ollama_emb.embed_query(query)

-3.123553991317749,
1.3652770519256592,
-0.0066312928684055805,
...]
```

比較する関数を作り、df.embeddingのすべての行にapplyします。

```python
In [32]: from scipy.spatial import distance
In [33]: def compairer(x):
…             return distance.cosine(x, query_vector)

In [34]: df.embedding.apply(compairer)
Out[34]:
0     0.613002
1     0.504562
2     0.799061
3     0.611230
4     0.693914
5     0.644467
6     0.805258
7     0.674750
8     0.764879
9     0.793973
10    0.657400
11    0.632991
12    0.895237
13    0.639640
14    0.513790
15    0.597422
16    0.710070
17    0.798069
18    0.729097
19    0.591398
Name: embedding, dtype: float64
```

最も高いスコア0.8を示したデータを選択します。
“パートタイム”という言葉が結構含まれています。

```python
In [35]: df.text[6]
Out[35]: '日制を採用する場合）の規程例第１９条第２項中の「７ 時間１５分」などの部分や、第４３条５第２項中の「無給／通常
の賃金を支払うこと」の部分）には、あらかじめ数字や文言を記入しているものがありますが、これらは規程例の内容を分かりやす
く解説するために便宜的に記入したものですので、これらについても、法令に従い各事業場の実情に応じて具体的な数字等を定めて
ください。また、本規則は、主として通常の労働者への適用を想定して作成しています。したがって、パートタイム労働者や有期雇
用労働者等を雇用している場合、就業規則の作成に当たっては、本規則の各条項についてパートタイム労働者や有期雇用労働者等へ
の適用の可否について必ず検討し、必要に応じて別個の就業規則を作成してください。なお、パートタイム労働者や有期雇用労働者
に関する事項について就業規則を作成したり、変更する場合には、その事業場において雇用するパートタイム労働者や有期雇用労働
者の過半数を代表すると認められる者の意見を聴くように努めなければなりません（短時間労働者及び有期雇'
```

最も低いスコア0.5を示したデータを選択します。
“パートタイム”という言葉から遠そうです。

```python
In [36]: df.text[1]
Out[36]: ' 以上に分けて交替に就業させる場合においては就業時転換に関する事項（２） 賃金関係賃金の決定、計算及び支払の方
法、賃金の締切り及び支払の時期並びに昇給に関する事項（３） 退職関係退職に関する事項（解雇の事由を含みます。）相対的必要
記載事項は次のとおりです。（１） 退職手当関係適用される労働者の範囲、退職手当の決定、計算及び支払の方法並びに退職手当の
支払の時期に関する事項（２） 臨時の賃金・最低賃金額関係臨時の賃金等(退職手当を除きます。)及び最低賃金額に関する事項（３
） 費用負担関係３労働者に食費、作業用品その他の負担をさせることに関する事項（４） 安全衛生関係安全及び衛生に関する事項
（５） 職業訓練関係職業訓練に関する事項（６） 災害補償・業務外の傷病扶助関係災害補償及び業務外の傷病扶助に関する事項（
７） 表彰・制裁関係表彰及び制裁の種類及び程度に関する事項（８） '
```


## 自前で組込みやってみよう
たまたまベクトル化されたデータがあったからさくっとできたものの、今はopenaiのembeddingを使わかなくてもできそうなので、それもOllamaにやってもらいましょう。


組込み用のモデルには次のようなものがあります。
 
|Model|Parameter Size    | 
|-------|---------| 
|mxbai-embed-large|334M| 
|nomic-embed-text|137M| 
|all-minilm|23M| 

[ollama.com/Embedding models](https://ollama.com/blog/embedding-models)

Chat用モデル(llama3とか)でも組込できますが、パラメータが多いためか、処理が遅かったです。
組込み処理用のモデルを使うと高速です。

text: 文字列の配列を渡して、query: 文字列との関連性を数値で返してくれるクラスを作りました。

クラスプロパティurl, modelを適宜変更してください。
ベクトルを一つ一つ比較するなら`distance.cosine()`が良いですが、複数のベクトルの各々の距離を調べるには`distance.cdist(ArrayA, ArrayB, "cosine")`を使いましょう[^6]。

[^6]: [scipy.spatial.distance.cdist](https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html)


```python
class Embedding:
    url = "http://localhost:11434"
    model = "all-minilm"
    embedder = OllamaEmbeddings(base_url=url, model=model)

    def __init__(self, text: list[str]):
        """組込みLLMを使ってドキュメントをベクトル化し、
        compareメソッドにより比較したスコアを返す

        Usage:
            documents = [
                # ...文字列list
            ]
            emb = Embedding(documents)
            result = emb.compare("質問")
            # retult == array([[0.65659238], [0.79229155], [0.69539101],...
        """
        self.document_vector = Embedding.embedder.embed_documents(text)

    def compare(self, query: str) -> list[list[float]]:
        """queryとtextをコサイン比較する。
        スコアが0に近いほど関連性が高く、
        1に近いほど関連性が低い
        """
        query_vector = Embedding.embedder.embed_query(query)
        return distance.cdist(self.document_vector, [query_vector], "cosine")
```