poemai-utils 0.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. poemai-utils-0.0.0/LICENSE.txt +21 -0
  2. poemai-utils-0.0.0/PKG-INFO +19 -0
  3. poemai-utils-0.0.0/README.md +3 -0
  4. poemai-utils-0.0.0/pyproject.toml +12 -0
  5. poemai-utils-0.0.0/setup.cfg +74 -0
  6. poemai-utils-0.0.0/setup.py +21 -0
  7. poemai-utils-0.0.0/src/poemai_utils/__init__.py +16 -0
  8. poemai-utils-0.0.0/src/poemai_utils/basic_types_utils.py +52 -0
  9. poemai-utils-0.0.0/src/poemai_utils/configuration_api.py +2 -0
  10. poemai-utils-0.0.0/src/poemai_utils/embeddings/__init__.py +0 -0
  11. poemai-utils-0.0.0/src/poemai_utils/embeddings/cross_similarity_analyzer.py +121 -0
  12. poemai-utils-0.0.0/src/poemai_utils/embeddings/embedder_base.py +3 -0
  13. poemai-utils-0.0.0/src/poemai_utils/embeddings/embedding_cache.py +34 -0
  14. poemai-utils-0.0.0/src/poemai_utils/embeddings/embedding_store.py +60 -0
  15. poemai-utils-0.0.0/src/poemai_utils/embeddings/openai_embedder.py +16 -0
  16. poemai-utils-0.0.0/src/poemai_utils/embeddings/self_similarity_analyzer.py +70 -0
  17. poemai-utils-0.0.0/src/poemai_utils/embeddings/sentence_transformer_embedder.py +46 -0
  18. poemai-utils-0.0.0/src/poemai_utils/embeddings/sgpt_embedder.py +85 -0
  19. poemai-utils-0.0.0/src/poemai_utils/enum_utils.py +38 -0
  20. poemai-utils-0.0.0/src/poemai_utils/openai/ask.py +452 -0
  21. poemai-utils-0.0.0/src/poemai_utils/openai/llm_answer_cache.py +102 -0
  22. poemai-utils-0.0.0/src/poemai_utils/openai/llm_answer_cache_dao.py +46 -0
  23. poemai-utils-0.0.0/src/poemai_utils/openai/openai_model.py +43 -0
  24. poemai-utils-0.0.0/src/poemai_utils/utils_config.py +9 -0
  25. poemai-utils-0.0.0/src/poemai_utils.egg-info/PKG-INFO +19 -0
  26. poemai-utils-0.0.0/src/poemai_utils.egg-info/SOURCES.txt +29 -0
  27. poemai-utils-0.0.0/src/poemai_utils.egg-info/dependency_links.txt +1 -0
  28. poemai-utils-0.0.0/src/poemai_utils.egg-info/not-zip-safe +1 -0
  29. poemai-utils-0.0.0/src/poemai_utils.egg-info/requires.txt +12 -0
  30. poemai-utils-0.0.0/src/poemai_utils.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Markus Emmenegger, poemAI GmbH, Horw, Switzerland
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,19 @@
1
+ Metadata-Version: 2.1
2
+ Name: poemai-utils
3
+ Version: 0.0.0
4
+ Summary: Add a short description here!
5
+ Home-page: https://github.com/pyscaffold/pyscaffold/
6
+ Author: Markus Emmenegger
7
+ Author-email: markus.emmenegger@poemai.ch
8
+ License: MIT
9
+ Project-URL: Documentation, https://pyscaffold.org/
10
+ Platform: any
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Programming Language :: Python
13
+ Description-Content-Type: text/markdown; charset=UTF-8
14
+ Provides-Extra: testing
15
+ License-File: LICENSE.txt
16
+
17
+ # poemai-utils
18
+
19
+ This package is a collection of utilities for AI projects.
@@ -0,0 +1,3 @@
1
+ # poemai-utils
2
+
3
+ This package is a collection of utilities for AI projects.
@@ -0,0 +1,12 @@
1
+ [build-system]
2
+ # AVOID CHANGING REQUIRES: IT WILL BE UPDATED BY PYSCAFFOLD!
3
+ requires = ["setuptools>=46.1.0", "setuptools_scm[toml]>=5"]
4
+ build-backend = "setuptools.build_meta"
5
+
6
+ [tool.setuptools_scm]
7
+ # For smarter version schemes and other configuration options,
8
+ # check out https://github.com/pypa/setuptools_scm
9
+ version_scheme = "no-guess-dev"
10
+ [tool.black]
11
+ line-length = 88
12
+ target-version = ['py311']
@@ -0,0 +1,74 @@
1
+ [metadata]
2
+ name = poemai-utils
3
+ description = Add a short description here!
4
+ author = Markus Emmenegger
5
+ author_email = markus.emmenegger@poemai.ch
6
+ license = MIT
7
+ license_files = LICENSE.txt
8
+ long_description = file: README.md
9
+ long_description_content_type = text/markdown; charset=UTF-8
10
+ url = https://github.com/pyscaffold/pyscaffold/
11
+ project_urls =
12
+ Documentation = https://pyscaffold.org/
13
+ platforms = any
14
+ classifiers =
15
+ Development Status :: 4 - Beta
16
+ Programming Language :: Python
17
+
18
+ [options]
19
+ zip_safe = False
20
+ packages = find_namespace:
21
+ include_package_data = True
22
+ package_dir =
23
+ =src
24
+ install_requires =
25
+ importlib-metadata; python_version<"3.8"
26
+ sqlitedict
27
+ httpx
28
+ numpy
29
+
30
+ [options.packages.find]
31
+ where = src
32
+ exclude =
33
+ tests
34
+
35
+ [options.extras_require]
36
+ testing =
37
+ setuptools
38
+ pytest
39
+ pytest-cov
40
+ transformers
41
+
42
+ [options.entry_points]
43
+
44
+ [tool:pytest]
45
+ norecursedirs =
46
+ dist
47
+ build
48
+ .tox
49
+ testpaths = tests
50
+
51
+ [devpi:upload]
52
+ no_vcs = 1
53
+ formats = bdist_wheel
54
+
55
+ [flake8]
56
+ max_line_length = 88
57
+ extend_ignore = E203, W503
58
+ exclude =
59
+ .tox
60
+ build
61
+ dist
62
+ .eggs
63
+ docs/conf.py
64
+
65
+ [pyscaffold]
66
+ version = 4.4.1
67
+ package = poemai_utils
68
+
69
+ [options.package_data]
70
+
71
+ [egg_info]
72
+ tag_build =
73
+ tag_date = 0
74
+
@@ -0,0 +1,21 @@
1
+ """
2
+ Setup file for poemai-utils.
3
+ Use setup.cfg to configure your project.
4
+
5
+ This file was generated with PyScaffold 4.4.1.
6
+ PyScaffold helps you to put up the scaffold of your new Python project.
7
+ Learn more under: https://pyscaffold.org/
8
+ """
9
+ from setuptools import setup
10
+
11
+ if __name__ == "__main__":
12
+ try:
13
+ setup(use_scm_version={"version_scheme": "no-guess-dev"})
14
+ except: # noqa
15
+ print(
16
+ "\n\nAn error occurred while building the project, "
17
+ "please ensure you have the most updated version of setuptools, "
18
+ "setuptools_scm and wheel with:\n"
19
+ " pip install -U setuptools setuptools_scm wheel\n\n"
20
+ )
21
+ raise
@@ -0,0 +1,16 @@
1
+ import sys
2
+
3
+ if sys.version_info[:2] >= (3, 8):
4
+ # TODO: Import directly (no need for conditional) when `python_requires = >= 3.8`
5
+ from importlib.metadata import PackageNotFoundError, version # pragma: no cover
6
+ else:
7
+ from importlib_metadata import PackageNotFoundError, version # pragma: no cover
8
+
9
+ try:
10
+ # Change here if project is renamed and does not equal the package name
11
+ dist_name = "poemai-utils"
12
+ __version__ = version(dist_name)
13
+ except PackageNotFoundError: # pragma: no cover
14
+ __version__ = "unknown"
15
+ finally:
16
+ del version, PackageNotFoundError
@@ -0,0 +1,52 @@
1
+ def linebreak(text, max_length=80):
2
+ """Add linebreaks to text to make it more readable"""
3
+
4
+ out_lines = []
5
+ for line in text.split("\n"):
6
+ if len(line) <= max_length:
7
+ out_lines.append(line)
8
+ else:
9
+ # split line into words
10
+ words = line.split(" ")
11
+ out_line = ""
12
+ for word in words:
13
+ if len(out_line) + len(word) > max_length:
14
+ out_lines.append(out_line)
15
+ out_line = ""
16
+ out_line += word + " "
17
+ out_lines.append(out_line)
18
+ return "\n".join(out_lines)
19
+
20
+
21
+ def short_display(text, max_lenght=100):
22
+ if len(text) > max_lenght:
23
+ return text[:max_lenght] + "..."
24
+ return text
25
+
26
+
27
+ def remove_none_from_dict(
28
+ dictionary, remove_empty_lists=False, remove_empty_dicts=False
29
+ ):
30
+ retval = {}
31
+
32
+ for key, value in list(dictionary.items()):
33
+ if value is None:
34
+ continue
35
+ elif isinstance(value, dict):
36
+ new_value = remove_none_from_dict(value)
37
+ if not new_value and remove_empty_dicts:
38
+ continue
39
+ else:
40
+ retval[key] = new_value
41
+ elif isinstance(value, list):
42
+ if len(value) == 0 and remove_empty_lists:
43
+ continue
44
+ else:
45
+ retval[key] = [
46
+ remove_none_from_dict(item) if isinstance(item, dict) else item
47
+ for item in value
48
+ ]
49
+ else:
50
+ retval[key] = value
51
+
52
+ return retval
@@ -0,0 +1,2 @@
1
+ class ConfigurationAPI:
2
+ pass
@@ -0,0 +1,121 @@
1
+ import numpy as np
2
+ from poemai_utils.embeddings.self_similarity_analyzer import SelfSimilarityAnalyzer
3
+
4
+
5
+ class CrossSimilarityAnalyzer:
6
+ """A class to analyze the similarity between two lists of embeddings."""
7
+
8
+ def __init__(
9
+ self,
10
+ embedding_cache=None,
11
+ similarity_analyzer_1=None,
12
+ similarity_analyzer_2=None,
13
+ embedder=None,
14
+ ):
15
+ if similarity_analyzer_1 is None:
16
+ self.analyzer_1 = SelfSimilarityAnalyzer(
17
+ embedder,
18
+ embedding_cache=embedding_cache,
19
+ )
20
+ else:
21
+ self.analyzer_1 = similarity_analyzer_1
22
+ if similarity_analyzer_2 is None:
23
+ self.analyzer_2 = SelfSimilarityAnalyzer(
24
+ embedder, embedding_cache=embedding_cache
25
+ )
26
+ else:
27
+ self.analyzer_2 = similarity_analyzer_2
28
+
29
+ if (
30
+ self.analyzer_1.use_cosine_similarity
31
+ != self.analyzer_2.use_cosine_similarity
32
+ ):
33
+ raise ValueError("Both analyzers must have the same use_cosine_similarity")
34
+
35
+ self.use_cosine_similarity = self.analyzer_1.use_cosine_similarity
36
+
37
+ def add_text_for_1(self, text, metadata):
38
+ self.analyzer_1.add_text(text, metadata)
39
+
40
+ def add_embedding_for_1(self, text, embedding, metadata):
41
+ self.analyzer_1.add_embedding(text, embedding, metadata)
42
+
43
+ def add_text_for_2(self, text, metadata):
44
+ self.analyzer_2.add_text(text, metadata)
45
+
46
+ def add_embedding_for_2(self, text, embedding, metadata):
47
+ self.analyzer_2.add_embedding(text, embedding, metadata)
48
+
49
+ @staticmethod
50
+ def normalize_rows(x):
51
+ return x / np.linalg.norm(x, ord=2, axis=1, keepdims=True)
52
+
53
+ def cross_similarity_matrix(self):
54
+ m1 = self.analyzer_1.embedding_store.embedding_matrix
55
+ m2 = self.analyzer_2.embedding_store.embedding_matrix
56
+
57
+ if self.use_cosine_similarity:
58
+ m1 = self.normalize_rows(m1)
59
+ m2 = self.normalize_rows(m2)
60
+
61
+ return np.matmul(
62
+ m1,
63
+ m2.T,
64
+ )
65
+
66
+ def find_top_k_similar_pairs(self, k=5):
67
+ similarity_matrix = self.cross_similarity_matrix()
68
+
69
+ top_k_idxs = np.argsort(similarity_matrix, axis=None)[-k:]
70
+ rows, cols = np.unravel_index(top_k_idxs, similarity_matrix.shape)
71
+ values = similarity_matrix[rows, cols]
72
+
73
+ retval = []
74
+ for row, col, value in zip(rows, cols, values):
75
+ row_metadata = self.analyzer_1.metadata.get(row)
76
+ col_metadata = self.analyzer_2.metadata.get(col)
77
+
78
+ retval.append(
79
+ (
80
+ row_metadata,
81
+ col_metadata,
82
+ value,
83
+ )
84
+ )
85
+
86
+ return retval
87
+
88
+ def map_1_to_2(self, k=1):
89
+ similarity_matrix = self.cross_similarity_matrix()
90
+ best = np.argsort(similarity_matrix, axis=1)[:, ::-1][:, :k]
91
+
92
+ retval = []
93
+ for i, row in enumerate(best):
94
+ row_metadata = self.analyzer_1.metadata.get(i)
95
+ col_metadata = [self.analyzer_2.metadata.get(b) for b in row]
96
+
97
+ retval.append(
98
+ (
99
+ row_metadata,
100
+ col_metadata,
101
+ )
102
+ )
103
+ return retval
104
+
105
+ def map_1_to_2_with_scores(self, k=1):
106
+ similarity_matrix = self.cross_similarity_matrix()
107
+ best = np.argsort(similarity_matrix, axis=1)[:, ::-1][:, :k]
108
+
109
+ retval = []
110
+ for i, row in enumerate(best):
111
+ row_metadata = self.analyzer_1.metadata.get(i)
112
+ col_metadata = [self.analyzer_2.metadata.get(b) for b in row]
113
+
114
+ col_result = list(zip(col_metadata, similarity_matrix[i, row]))
115
+ retval.append(
116
+ (
117
+ row_metadata,
118
+ col_result,
119
+ )
120
+ )
121
+ return retval
@@ -0,0 +1,3 @@
1
+ class EbedderBase:
2
+ def __init__(self, use_cosine_similarity=False):
3
+ self.use_cosine_similarity = use_cosine_similarity
@@ -0,0 +1,34 @@
1
+ import logging
2
+ from hashlib import sha256
3
+
4
+ import numpy as np
5
+ from sqlitedict import SqliteDict
6
+
7
+ _logger = logging.getLogger(__name__)
8
+
9
+
10
+ class EmbeddingCache:
11
+ def __init__(self, cache_file):
12
+ self.cache = SqliteDict(cache_file, autocommit=True)
13
+
14
+ def get(self, text, model_name):
15
+ text_hash = self.text_hash(text)
16
+ key = self.calc_key(model_name, text_hash)
17
+ short_key = (model_name, text_hash[:6])
18
+ if key in self.cache:
19
+ _logger.debug(f"Cache hit for {short_key}")
20
+ return np.array(self.cache[key])
21
+ return None
22
+
23
+ def put(self, text, embedding, model_name):
24
+ text_hash = self.text_hash(text)
25
+ key = self.calc_key(model_name, text_hash)
26
+ self.cache[key] = embedding.tolist()
27
+
28
+ def text_hash(self, text):
29
+ # calculate sha256
30
+ return sha256(text.encode("utf-8")).hexdigest()
31
+
32
+ @staticmethod
33
+ def calc_key(model_name, text_hash):
34
+ return model_name + "_" + text_hash
@@ -0,0 +1,60 @@
1
+ from hashlib import sha256
2
+
3
+ import numpy as np
4
+
5
+
6
+ class EmbeddingStore:
7
+ def __init__(self, embedding_cache=None, embedder=None):
8
+ self.embedder = embedder
9
+
10
+ self.texts = []
11
+ self.embedding_matrix = None
12
+ self.embedding_cache = embedding_cache
13
+
14
+ def _store_embedding(self, embedding):
15
+ if self.embedding_matrix is None:
16
+ self.embedding_matrix = np.expand_dims(embedding, 0)
17
+ else:
18
+ self.embedding_matrix = np.concatenate(
19
+ [self.embedding_matrix, [embedding]], 0
20
+ )
21
+
22
+ def add_text(self, text, is_query: bool = False):
23
+ self.texts.append(text)
24
+ self._store_embedding(self._create_embedding(text, is_query=is_query))
25
+ return len(self.texts) - 1
26
+
27
+ def add_embedding(self, text, embedding):
28
+ self.texts.append(text)
29
+ self._store_embedding(embedding)
30
+ return len(self.texts) - 1
31
+
32
+ def query_by_text(self, query, k=5):
33
+ if self.embedding_matrix is None:
34
+ return []
35
+ query_embedding = self._create_embedding(query)
36
+ scores = self.embedding_matrix.dot(query_embedding)
37
+ idxs = np.argsort(scores)[-k:][::-1]
38
+
39
+ return [(self.texts[i], scores[i], i) for i in idxs]
40
+
41
+ def query_by_embedding(self, query_embedding, k=5):
42
+ if self.embedding_matrix is None:
43
+ return []
44
+ scores = self.embedding_matrix.dot(query_embedding)
45
+ idxs = np.argsort(scores)[-k:][::-1]
46
+
47
+ return [(self.texts[i], scores[i], i) for i in idxs]
48
+
49
+ def _create_embedding(self, text, is_query):
50
+ if self.embedding_cache is not None:
51
+ embedding = self.embedding_cache.get(text, self.embedder.model_name)
52
+ if embedding is not None:
53
+ return embedding
54
+
55
+ embedding = self.embedder.calc_embedding(text, is_query=is_query)
56
+
57
+ if self.embedding_cache is not None:
58
+ self.embedding_cache.put(text, embedding, self.embedder.model_name)
59
+
60
+ return embedding
@@ -0,0 +1,16 @@
1
+ import numpy as np
2
+ import openai
3
+ from poemai_utils.embeddings.embedder_base import EbedderBase
4
+
5
+
6
+ class OpenAIEmbedder(EbedderBase):
7
+ def __init__(self, model_name="text-embedding-ada-002"):
8
+ super().__init__()
9
+
10
+ self.model_name = model_name
11
+
12
+ def calc_embedding(self, text, is_query: bool = False):
13
+ response = openai.Embedding.create(input=text, model=self.model_name)
14
+ embedding = response["data"][0]["embedding"]
15
+ embedding = np.array(embedding, dtype=np.float32)
16
+ return embedding
@@ -0,0 +1,70 @@
1
+ import numpy as np
2
+ from poemai_utils.embeddings.embedding_store import EmbeddingStore
3
+
4
+
5
+ class SelfSimilarityAnalyzer:
6
+ def __init__(self, embedder, embedding_cache=None):
7
+ if embedder is None:
8
+ raise ValueError("embedder must be provided")
9
+ self.embedding_store = EmbeddingStore(embedding_cache, embedder=embedder)
10
+ self.use_cosine_similarity = embedder.use_cosine_similarity
11
+ self.metadata = {}
12
+
13
+ def add_text(self, text, metadata, is_query=False):
14
+ if text is None:
15
+ return
16
+ text_id = self.embedding_store.add_text(text, is_query=is_query)
17
+ self.metadata[text_id] = metadata
18
+
19
+ def add_embedding(self, text, embedding, metadata):
20
+ text_id = self.embedding_store.add_embedding(text, embedding)
21
+ self.metadata[text_id] = metadata
22
+
23
+ def query(self, query, k=5):
24
+ top_k = self.embedding_store.query_by_text(query, k)
25
+ return [
26
+ (text_id, score, self.metadata.get(text_id)) for _, score, text_id in top_k
27
+ ]
28
+
29
+ @staticmethod
30
+ def normalize_rows(x):
31
+ return x / np.linalg.norm(x, ord=2, axis=1, keepdims=True)
32
+
33
+ def similarity_matrix(self):
34
+ m1 = self.embedding_store.embedding_matrix
35
+ if self.use_cosine_similarity:
36
+ m1 = self.normalize_rows(m1)
37
+
38
+ return np.matmul(
39
+ m1,
40
+ m1.T,
41
+ )
42
+
43
+ def find_top_k_similar_pairs(self, k=5):
44
+ similarity_matrix = self.similarity_matrix()
45
+
46
+ similarity_matrix = np.tril(similarity_matrix)
47
+ np.fill_diagonal(similarity_matrix, 0)
48
+
49
+ # find the top k most similar sections
50
+ top_k_idxs = np.argsort(similarity_matrix, axis=None)[-k:]
51
+ rows, cols = np.unravel_index(top_k_idxs, similarity_matrix.shape)
52
+ values = similarity_matrix[rows, cols]
53
+
54
+ retval = []
55
+ for row, col, value in zip(rows, cols, values):
56
+ row_metadata = self.metadata.get(row)
57
+ col_metadata = self.metadata.get(col)
58
+
59
+ retval.append(
60
+ (
61
+ row_metadata,
62
+ col_metadata,
63
+ value,
64
+ )
65
+ )
66
+
67
+ return retval
68
+
69
+ def metadata(self, text_id):
70
+ return self.metadata.get(text_id)
@@ -0,0 +1,46 @@
1
+ from enum import Enum
2
+
3
+ from poemai_utils.embeddings.embedder_base import EbedderBase
4
+ from poemai_utils.enum_utils import add_enum_attrs, add_enum_repr
5
+ from sentence_transformers import SentenceTransformer
6
+
7
+
8
+ class SentenceTransformerEmbeddingModel(Enum):
9
+ LABSE = "sentence-transformers/LaBSE"
10
+ DISTILUSE = "distiluse-base-multilingual-cased-v1"
11
+ BI_ELECTRA_GERMAN = "svalabs/bi-electra-ms-marco-german-uncased"
12
+ DISTILBERT = "msmarco-distilbert-base-tas-b"
13
+
14
+
15
+ add_enum_attrs(
16
+ {
17
+ SentenceTransformerEmbeddingModel.LABSE: {
18
+ "use_cosine_similarity": False,
19
+ },
20
+ SentenceTransformerEmbeddingModel.DISTILUSE: {
21
+ "use_cosine_similarity": False,
22
+ },
23
+ SentenceTransformerEmbeddingModel.BI_ELECTRA_GERMAN: {
24
+ "use_cosine_similarity": True,
25
+ },
26
+ SentenceTransformerEmbeddingModel.DISTILBERT: {
27
+ "use_cosine_similarity": False,
28
+ },
29
+ }
30
+ )
31
+ add_enum_repr(SentenceTransformerEmbeddingModel)
32
+
33
+
34
+ class SentenceTransformerEmbedder(EbedderBase):
35
+ # msmarco-distilbert-base-tas-b
36
+ # distiluse-base-multilingual-cased-v1
37
+ # sentence-transformers/LaBSE'
38
+ # svalabs/bi-electra-ms-marco-german-uncased : use_cosine_similarity=True
39
+ def __init__(self, model_id, use_cosine_similarity=False):
40
+ super().__init__(use_cosine_similarity=model_id.use_cosine_similarity)
41
+
42
+ self.model_name = model_id.value
43
+ self.model = SentenceTransformer(model_id.value)
44
+
45
+ def calc_embedding(self, text, is_query: bool = False):
46
+ return self.model.encode(text, show_progress_bar=False)
@@ -0,0 +1,85 @@
1
+ import torch
2
+ from poemai_utils.embeddings.embedder_base import EbedderBase
3
+ from transformers import AutoModel, AutoTokenizer
4
+
5
+
6
+ class SGPTEmbedder(EbedderBase):
7
+ def __init__(
8
+ self, model_name="Muennighoff/SGPT-125M-weightedmean-msmarco-specb-bitfit"
9
+ ):
10
+ super().__init__()
11
+
12
+ self.use_cosine_similarity = True
13
+ self.model_name = model_name
14
+ # Get our models - The package will take care of downloading the models automatically
15
+ # For best performance: Muennighoff/SGPT-5.8B-weightedmean-msmarco-specb-bitfit
16
+ self.tokenizer = AutoTokenizer.from_pretrained(self.model_name)
17
+
18
+ self.SPECB_QUE_BOS = self.tokenizer.encode("[", add_special_tokens=False)[0]
19
+ self.SPECB_QUE_EOS = self.tokenizer.encode("]", add_special_tokens=False)[0]
20
+
21
+ self.SPECB_DOC_BOS = self.tokenizer.encode("{", add_special_tokens=False)[0]
22
+ self.SPECB_DOC_EOS = self.tokenizer.encode("}", add_special_tokens=False)[0]
23
+
24
+ self.model = AutoModel.from_pretrained(self.model_name)
25
+ # Deactivate Dropout (There is no dropout in the above models so it makes no difference here but other SGPT models may have dropout)
26
+ self.model.eval()
27
+
28
+ def tokenize_with_specb(self, texts, is_query):
29
+ # Tokenize without padding
30
+ batch_tokens = self.tokenizer(texts, padding=False, truncation=True)
31
+ # Add special brackets & pay attention to them
32
+ for seq, att in zip(batch_tokens["input_ids"], batch_tokens["attention_mask"]):
33
+ if is_query:
34
+ seq.insert(0, self.SPECB_QUE_BOS)
35
+ seq.append(self.SPECB_QUE_EOS)
36
+ else:
37
+ seq.insert(0, self.SPECB_DOC_BOS)
38
+ seq.append(self.SPECB_DOC_EOS)
39
+ att.insert(0, 1)
40
+ att.append(1)
41
+ # Add padding
42
+ batch_tokens = self.tokenizer.pad(
43
+ batch_tokens, padding=True, return_tensors="pt"
44
+ )
45
+ return batch_tokens
46
+
47
+ def get_weightedmean_embedding(self, batch_tokens):
48
+ # Get the embeddings
49
+ with torch.no_grad():
50
+ # Get hidden state of shape [bs, seq_len, hid_dim]
51
+ last_hidden_state = self.model(
52
+ **batch_tokens, output_hidden_states=True, return_dict=True
53
+ ).last_hidden_state
54
+
55
+ # Get weights of shape [bs, seq_len, hid_dim]
56
+ weights = (
57
+ torch.arange(start=1, end=last_hidden_state.shape[1] + 1)
58
+ .unsqueeze(0)
59
+ .unsqueeze(-1)
60
+ .expand(last_hidden_state.size())
61
+ .float()
62
+ .to(last_hidden_state.device)
63
+ )
64
+
65
+ # Get attn mask of shape [bs, seq_len, hid_dim]
66
+ input_mask_expanded = (
67
+ batch_tokens["attention_mask"]
68
+ .unsqueeze(-1)
69
+ .expand(last_hidden_state.size())
70
+ .float()
71
+ )
72
+
73
+ # Perform weighted mean pooling across seq_len: bs, seq_len, hidden_dim -> bs, hidden_dim
74
+ sum_embeddings = torch.sum(
75
+ last_hidden_state * input_mask_expanded * weights, dim=1
76
+ )
77
+ sum_mask = torch.sum(input_mask_expanded * weights, dim=1)
78
+
79
+ embeddings = sum_embeddings / sum_mask
80
+
81
+ return embeddings
82
+
83
+ def calc_embedding(self, text, is_query: bool = False):
84
+ tokens = self.tokenize_with_specb([text], is_query=is_query)
85
+ return self.get_weightedmean_embedding(tokens)[0]