Spaces:
Runtime error
Runtime error
taskswithcode
commited on
Commit
•
02d9efc
1
Parent(s):
1feb2b0
Fixes
Browse files- app.py +23 -15
- sim_app_models.json +61 -1
- twc_embeddings.py +6 -6
- twc_openai_embeddings.py +94 -0
app.py
CHANGED
@@ -6,6 +6,7 @@ from io import StringIO
|
|
6 |
import pdb
|
7 |
import json
|
8 |
from twc_embeddings import HFModel,SimCSEModel,SGPTModel
|
|
|
9 |
import torch
|
10 |
import requests
|
11 |
import socket
|
@@ -59,7 +60,7 @@ def get_views(action):
|
|
59 |
|
60 |
def construct_model_info_for_display(model_names):
|
61 |
options_arr = []
|
62 |
-
markdown_str = f"<div style=\"font-size:16px; color: #2f2f2f; text-align: left\"><br/><b>Models evaluated ({len(model_names)})</b><br/><i>
|
63 |
markdown_str += f"<div style=\"font-size:2px; color: #2f2f2f; text-align: left\"><br/></div>"
|
64 |
for node in model_names:
|
65 |
options_arr .append(node["name"])
|
@@ -101,15 +102,15 @@ def load_model(model_name,model_class,load_model_name):
|
|
101 |
|
102 |
|
103 |
@st.experimental_memo
|
104 |
-
def cached_compute_similarity(sentences,_model,model_name,main_index):
|
105 |
-
texts,embeddings = _model.compute_embeddings(sentences,is_file=False)
|
106 |
results = _model.output_results(None,texts,embeddings,main_index)
|
107 |
return results
|
108 |
|
109 |
|
110 |
-
def uncached_compute_similarity(sentences,_model,model_name,main_index):
|
111 |
with st.spinner('Computing vectors for sentences'):
|
112 |
-
texts,embeddings = _model.compute_embeddings(sentences,is_file=False)
|
113 |
results = _model.output_results(None,texts,embeddings,main_index)
|
114 |
#st.success("Similarity computation complete")
|
115 |
return results
|
@@ -121,7 +122,7 @@ def get_model_info(model_names,model_name):
|
|
121 |
return node,model_name
|
122 |
return get_model_info(model_names,DEFAULT_HF_MODEL)
|
123 |
|
124 |
-
def run_test(model_names,model_name,sentences,display_area,main_index,user_uploaded,custom_model):
|
125 |
display_area.text("Loading model:" + model_name)
|
126 |
#Note. model_name may get mapped to new name in the call below for custom models
|
127 |
orig_model_name = model_name
|
@@ -133,14 +134,18 @@ def run_test(model_names,model_name,sentences,display_area,main_index,user_uploa
|
|
133 |
if ("Note" in model_info):
|
134 |
fail_link = f"{model_info['Note']} [link]({model_info['alt_url']})"
|
135 |
display_area.write(fail_link)
|
|
|
|
|
|
|
|
|
136 |
model = load_model(model_name,model_info["class"],load_model_name)
|
137 |
display_area.text("Model " + model_name + " load complete")
|
138 |
try:
|
139 |
if (user_uploaded):
|
140 |
-
results = uncached_compute_similarity(sentences,model,model_name,main_index)
|
141 |
else:
|
142 |
display_area.text("Computing vectors for sentences")
|
143 |
-
results = cached_compute_similarity(sentences,model,model_name,main_index)
|
144 |
display_area.text("Similarity computation complete")
|
145 |
return results
|
146 |
|
@@ -250,15 +255,18 @@ def app_main(app_mode,example_files,model_name_files):
|
|
250 |
st.session_state["model_name"] = run_model
|
251 |
st.session_state["main_index"] = main_index
|
252 |
|
253 |
-
results = run_test(model_names,run_model,sentences,display_area,main_index - 1,(uploaded_file is not None),(len(custom_model_selection) != 0))
|
254 |
display_area.empty()
|
255 |
with display_area.container():
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
|
|
|
|
|
|
262 |
st.download_button(
|
263 |
label="Download results as json",
|
264 |
data= st.session_state["download_ready"] if st.session_state["download_ready"] != None else "",
|
|
|
6 |
import pdb
|
7 |
import json
|
8 |
from twc_embeddings import HFModel,SimCSEModel,SGPTModel
|
9 |
+
from twc_openai_embeddings import OpenAIModel
|
10 |
import torch
|
11 |
import requests
|
12 |
import socket
|
|
|
60 |
|
61 |
def construct_model_info_for_display(model_names):
|
62 |
options_arr = []
|
63 |
+
markdown_str = f"<div style=\"font-size:16px; color: #2f2f2f; text-align: left\"><br/><b>Models evaluated ({len(model_names)})</b><br/><i>The selected models satisfy one or more of the following (1) state-of-the-art (2) the most downloaded models on Huggingface (3) Large Language Models (e.g. GPT-3)</i></div>"
|
64 |
markdown_str += f"<div style=\"font-size:2px; color: #2f2f2f; text-align: left\"><br/></div>"
|
65 |
for node in model_names:
|
66 |
options_arr .append(node["name"])
|
|
|
102 |
|
103 |
|
104 |
@st.experimental_memo
|
105 |
+
def cached_compute_similarity(input_file_name,sentences,_model,model_name,main_index):
|
106 |
+
texts,embeddings = _model.compute_embeddings(input_file_name,sentences,is_file=False)
|
107 |
results = _model.output_results(None,texts,embeddings,main_index)
|
108 |
return results
|
109 |
|
110 |
|
111 |
+
def uncached_compute_similarity(input_file_name,sentences,_model,model_name,main_index):
|
112 |
with st.spinner('Computing vectors for sentences'):
|
113 |
+
texts,embeddings = _model.compute_embeddings(input_file_name,sentences,is_file=False)
|
114 |
results = _model.output_results(None,texts,embeddings,main_index)
|
115 |
#st.success("Similarity computation complete")
|
116 |
return results
|
|
|
122 |
return node,model_name
|
123 |
return get_model_info(model_names,DEFAULT_HF_MODEL)
|
124 |
|
125 |
+
def run_test(model_names,model_name,input_file_name,sentences,display_area,main_index,user_uploaded,custom_model):
|
126 |
display_area.text("Loading model:" + model_name)
|
127 |
#Note. model_name may get mapped to new name in the call below for custom models
|
128 |
orig_model_name = model_name
|
|
|
134 |
if ("Note" in model_info):
|
135 |
fail_link = f"{model_info['Note']} [link]({model_info['alt_url']})"
|
136 |
display_area.write(fail_link)
|
137 |
+
if (user_uploaded and "custom_load" in model_info and model_info["custom_load"] == "False"):
|
138 |
+
fail_link = f"{model_info['Note']} [link]({model_info['alt_url']})"
|
139 |
+
display_area.write(fail_link)
|
140 |
+
return {"error":fail_link}
|
141 |
model = load_model(model_name,model_info["class"],load_model_name)
|
142 |
display_area.text("Model " + model_name + " load complete")
|
143 |
try:
|
144 |
if (user_uploaded):
|
145 |
+
results = uncached_compute_similarity(input_file_name,sentences,model,model_name,main_index)
|
146 |
else:
|
147 |
display_area.text("Computing vectors for sentences")
|
148 |
+
results = cached_compute_similarity(input_file_name,sentences,model,model_name,main_index)
|
149 |
display_area.text("Similarity computation complete")
|
150 |
return results
|
151 |
|
|
|
255 |
st.session_state["model_name"] = run_model
|
256 |
st.session_state["main_index"] = main_index
|
257 |
|
258 |
+
results = run_test(model_names,run_model,st.session_state["file_name"],sentences,display_area,main_index - 1,(uploaded_file is not None),(len(custom_model_selection) != 0))
|
259 |
display_area.empty()
|
260 |
with display_area.container():
|
261 |
+
if ("error" in results):
|
262 |
+
st.error(results["error"])
|
263 |
+
else:
|
264 |
+
device = 'GPU' if torch.cuda.is_available() else 'CPU'
|
265 |
+
response_info = f"Computation time on {device}: {time.time() - start:.2f} secs for {len(sentences)} sentences"
|
266 |
+
if (len(custom_model_selection) != 0):
|
267 |
+
st.info("Custom model overrides model selection in step 2 above. So please clear the custom model text box to choose models from step 2")
|
268 |
+
display_results(sentences,main_index - 1,results,response_info,app_mode,run_model)
|
269 |
+
#st.json(results)
|
270 |
st.download_button(
|
271 |
label="Download results as json",
|
272 |
data= st.session_state["download_ready"] if st.session_state["download_ready"] != None else "",
|
sim_app_models.json
CHANGED
@@ -128,7 +128,67 @@
|
|
128 |
},
|
129 |
"paper_url":"https://arxiv.org/abs/2104.08821v4",
|
130 |
"mark":"True",
|
131 |
-
"class":"SimCSEModel","sota_link":"https://paperswithcode.com/sota/semantic-textual-similarity-on-sick"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
132 |
|
133 |
|
134 |
]
|
|
|
128 |
},
|
129 |
"paper_url":"https://arxiv.org/abs/2104.08821v4",
|
130 |
"mark":"True",
|
131 |
+
"class":"SimCSEModel","sota_link":"https://paperswithcode.com/sota/semantic-textual-similarity-on-sick"},
|
132 |
+
{ "name":"GPT-3-175B (text-similarity-davinci-001)" ,
|
133 |
+
"model":"text-similarity-davinci-001",
|
134 |
+
"fork_url":"https://openai.com/api/",
|
135 |
+
"orig_author_url":"https://openai.com/api/",
|
136 |
+
"orig_author":"OpenAI",
|
137 |
+
"sota_info": {
|
138 |
+
"task":"GPT-3 achieves strong zero-shot and few-shot performance on many NLP datasets etc.",
|
139 |
+
"sota_link":"https://paperswithcode.com/method/gpt-3"
|
140 |
+
},
|
141 |
+
"paper_url":"https://arxiv.org/abs/2005.14165v4",
|
142 |
+
"mark":"True",
|
143 |
+
"custom_load":"False",
|
144 |
+
"Note":"Custom file upload requires OpenAI API access to create embeddings. For API access, use this link ",
|
145 |
+
"alt_url":"https://openai.com/api/",
|
146 |
+
"class":"OpenAIModel","sota_link":"https://arxiv.org/abs/2005.14165v4"},
|
147 |
+
{ "name":"GPT-3-6.7B (text-similarity-curie-001)" ,
|
148 |
+
"model":"text-similarity-curie-001",
|
149 |
+
"fork_url":"https://openai.com/api/",
|
150 |
+
"orig_author_url":"https://openai.com/api/",
|
151 |
+
"orig_author":"OpenAI",
|
152 |
+
"sota_info": {
|
153 |
+
"task":"GPT-3 achieves strong zero-shot and few-shot performance on many NLP datasets etc.",
|
154 |
+
"sota_link":"https://paperswithcode.com/method/gpt-3"
|
155 |
+
},
|
156 |
+
"paper_url":"https://arxiv.org/abs/2005.14165v4",
|
157 |
+
"mark":"True",
|
158 |
+
"custom_load":"False",
|
159 |
+
"Note":"Custom file upload requires OpenAI API access to create embeddings. For API access, use this link ",
|
160 |
+
"alt_url":"https://openai.com/api/",
|
161 |
+
"class":"OpenAIModel","sota_link":"https://arxiv.org/abs/2005.14165v4"},
|
162 |
+
{ "name":"GPT-3-1.3B (text-similarity-babbage-001)" ,
|
163 |
+
"model":"text-similarity-babbage-001",
|
164 |
+
"fork_url":"https://openai.com/api/",
|
165 |
+
"orig_author_url":"https://openai.com/api/",
|
166 |
+
"orig_author":"OpenAI",
|
167 |
+
"sota_info": {
|
168 |
+
"task":"GPT-3 achieves strong zero-shot and few-shot performance on many NLP datasets etc.",
|
169 |
+
"sota_link":"https://paperswithcode.com/method/gpt-3"
|
170 |
+
},
|
171 |
+
"paper_url":"https://arxiv.org/abs/2005.14165v4",
|
172 |
+
"mark":"True",
|
173 |
+
"custom_load":"False",
|
174 |
+
"Note":"Custom file upload requires OpenAI API access to create embeddings. For API access, use this link ",
|
175 |
+
"alt_url":"https://openai.com/api/",
|
176 |
+
"class":"OpenAIModel","sota_link":"https://arxiv.org/abs/2005.14165v4"},
|
177 |
+
{ "name":"GPT-3-350M (text-similarity-ada-001)" ,
|
178 |
+
"model":"text-similarity-ada-001",
|
179 |
+
"fork_url":"https://openai.com/api/",
|
180 |
+
"orig_author_url":"https://openai.com/api/",
|
181 |
+
"orig_author":"OpenAI",
|
182 |
+
"sota_info": {
|
183 |
+
"task":"GPT-3 achieves strong zero-shot and few-shot performance on many NLP datasets etc.",
|
184 |
+
"sota_link":"https://paperswithcode.com/method/gpt-3"
|
185 |
+
},
|
186 |
+
"paper_url":"https://arxiv.org/abs/2005.14165v4",
|
187 |
+
"mark":"True",
|
188 |
+
"custom_load":"False",
|
189 |
+
"Note":"Custom file upload requires OpenAI API access to create embeddings. For API access, use this link ",
|
190 |
+
"alt_url":"https://openai.com/api/",
|
191 |
+
"class":"OpenAIModel","sota_link":"https://arxiv.org/abs/2005.14165v4"}
|
192 |
|
193 |
|
194 |
]
|
twc_embeddings.py
CHANGED
@@ -32,7 +32,7 @@ class CausalLMModel:
|
|
32 |
self.model.eval()
|
33 |
self.prompt = 'Documents are searched to find matches with the same content.\nThe document "{}" is a good search result for "'
|
34 |
|
35 |
-
def compute_embeddings(self,input_data,is_file):
|
36 |
if (self.debug):
|
37 |
print("Computing embeddings for:", input_data[:20])
|
38 |
model = self.model
|
@@ -160,7 +160,7 @@ class SGPTQnAModel:
|
|
160 |
|
161 |
return embeddings
|
162 |
|
163 |
-
def compute_embeddings(self,input_data,is_file):
|
164 |
if (self.debug):
|
165 |
print("Computing embeddings for:", input_data[:20])
|
166 |
model = self.model
|
@@ -215,7 +215,7 @@ class SimCSEModel:
|
|
215 |
self.tokenizer = AutoTokenizer.from_pretrained(model_name)
|
216 |
self.model = AutoModel.from_pretrained(model_name)
|
217 |
|
218 |
-
def compute_embeddings(self,input_data,is_file):
|
219 |
texts = read_text(input_data) if is_file == True else input_data
|
220 |
inputs = self.tokenizer(texts, padding=True, truncation=True, return_tensors="pt")
|
221 |
with torch.no_grad():
|
@@ -266,7 +266,7 @@ class SGPTModel:
|
|
266 |
# Deactivate Dropout (There is no dropout in the above models so it makes no difference here but other SGPT models may have dropout)
|
267 |
self.model.eval()
|
268 |
|
269 |
-
def compute_embeddings(self,input_data,is_file):
|
270 |
if (self.debug):
|
271 |
print("Computing embeddings for:", input_data[:20])
|
272 |
model = self.model
|
@@ -353,7 +353,7 @@ class HFModel:
|
|
353 |
input_mask_expanded = attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float()
|
354 |
return torch.sum(token_embeddings * input_mask_expanded, 1) / torch.clamp(input_mask_expanded.sum(1), min=1e-9)
|
355 |
|
356 |
-
def compute_embeddings(self,input_data,is_file):
|
357 |
#print("Computing embeddings for:", input_data[:20])
|
358 |
model = self.model
|
359 |
tokenizer = self.tokenizer
|
@@ -403,5 +403,5 @@ if __name__ == '__main__':
|
|
403 |
results = parser.parse_args()
|
404 |
obj = HFModel()
|
405 |
obj.init_model(results.model)
|
406 |
-
texts, embeddings = obj.compute_embeddings(results.input,is_file = True)
|
407 |
results = obj.output_results(results.output,texts,embeddings)
|
|
|
32 |
self.model.eval()
|
33 |
self.prompt = 'Documents are searched to find matches with the same content.\nThe document "{}" is a good search result for "'
|
34 |
|
35 |
+
def compute_embeddings(self,input_file_name,input_data,is_file):
|
36 |
if (self.debug):
|
37 |
print("Computing embeddings for:", input_data[:20])
|
38 |
model = self.model
|
|
|
160 |
|
161 |
return embeddings
|
162 |
|
163 |
+
def compute_embeddings(self,input_file_name,input_data,is_file):
|
164 |
if (self.debug):
|
165 |
print("Computing embeddings for:", input_data[:20])
|
166 |
model = self.model
|
|
|
215 |
self.tokenizer = AutoTokenizer.from_pretrained(model_name)
|
216 |
self.model = AutoModel.from_pretrained(model_name)
|
217 |
|
218 |
+
def compute_embeddings(self,input_file_name,input_data,is_file):
|
219 |
texts = read_text(input_data) if is_file == True else input_data
|
220 |
inputs = self.tokenizer(texts, padding=True, truncation=True, return_tensors="pt")
|
221 |
with torch.no_grad():
|
|
|
266 |
# Deactivate Dropout (There is no dropout in the above models so it makes no difference here but other SGPT models may have dropout)
|
267 |
self.model.eval()
|
268 |
|
269 |
+
def compute_embeddings(self,input_file_name,input_data,is_file):
|
270 |
if (self.debug):
|
271 |
print("Computing embeddings for:", input_data[:20])
|
272 |
model = self.model
|
|
|
353 |
input_mask_expanded = attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float()
|
354 |
return torch.sum(token_embeddings * input_mask_expanded, 1) / torch.clamp(input_mask_expanded.sum(1), min=1e-9)
|
355 |
|
356 |
+
def compute_embeddings(self,input_file_name,input_data,is_file):
|
357 |
#print("Computing embeddings for:", input_data[:20])
|
358 |
model = self.model
|
359 |
tokenizer = self.tokenizer
|
|
|
403 |
results = parser.parse_args()
|
404 |
obj = HFModel()
|
405 |
obj.init_model(results.model)
|
406 |
+
texts, embeddings = obj.compute_embeddings(results.input,results.input,is_file = True)
|
407 |
results = obj.output_results(results.output,texts,embeddings)
|
twc_openai_embeddings.py
ADDED
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from scipy.spatial.distance import cosine
|
2 |
+
import argparse
|
3 |
+
import json
|
4 |
+
import os
|
5 |
+
import openai
|
6 |
+
import pdb
|
7 |
+
|
8 |
+
def read_text(input_file):
|
9 |
+
arr = open(input_file).read().split("\n")
|
10 |
+
return arr[:-1]
|
11 |
+
|
12 |
+
|
13 |
+
class OpenAIModel:
|
14 |
+
def __init__(self):
|
15 |
+
self.debug = False
|
16 |
+
self.model_name = None
|
17 |
+
print("In OpenAI API constructor")
|
18 |
+
|
19 |
+
|
20 |
+
def init_model(self,model_name = None):
|
21 |
+
#print("Init model",model_name)
|
22 |
+
openai.api_key = os.getenv("OPENAI_API_KEY")
|
23 |
+
if (len(openai.api_key) == 0):
|
24 |
+
print("Open API key not set")
|
25 |
+
|
26 |
+
if (model_name is None):
|
27 |
+
self.model_name = "text-similarity-ada-001"
|
28 |
+
else:
|
29 |
+
self.model_name = model_name
|
30 |
+
|
31 |
+
|
32 |
+
def compute_embeddings(self,input_file_name,input_data,is_file):
|
33 |
+
if (len(openai.api_key) == 0):
|
34 |
+
print("Open API key not set")
|
35 |
+
return [],[]
|
36 |
+
in_file = self.model_name + '.'.join(input_file_name.split('.')[:-1]) + "_embed.json"
|
37 |
+
cached = False
|
38 |
+
try:
|
39 |
+
fp = open(in_file)
|
40 |
+
cached = True
|
41 |
+
embeddings = json.load(fp)
|
42 |
+
print("Using cached embeddings")
|
43 |
+
except:
|
44 |
+
pass
|
45 |
+
|
46 |
+
texts = read_text(input_data) if is_file == True else input_data
|
47 |
+
if (not cached):
|
48 |
+
print(f"Computing embeddings for {input_file_name} and model {self.model_name}")
|
49 |
+
response = openai.Embedding.create(
|
50 |
+
input=texts,
|
51 |
+
model=self.model_name
|
52 |
+
)
|
53 |
+
embeddings = []
|
54 |
+
for i in range(len(response['data'])):
|
55 |
+
embeddings.append(response['data'][i]['embedding'])
|
56 |
+
if (not cached):
|
57 |
+
with open(in_file,"w") as fp:
|
58 |
+
json.dump(embeddings,fp)
|
59 |
+
return texts,embeddings
|
60 |
+
|
61 |
+
def output_results(self,output_file,texts,embeddings,main_index = 0):
|
62 |
+
if (len(openai.api_key) == 0):
|
63 |
+
print("Open API key not set")
|
64 |
+
return {}
|
65 |
+
# Calculate cosine similarities
|
66 |
+
# Cosine similarities are in [-1, 1]. Higher means more similar
|
67 |
+
cosine_dict = {}
|
68 |
+
#print("Total sentences",len(texts))
|
69 |
+
for i in range(len(texts)):
|
70 |
+
cosine_dict[texts[i]] = 1 - cosine(embeddings[main_index], embeddings[i])
|
71 |
+
|
72 |
+
#print("Input sentence:",texts[main_index])
|
73 |
+
sorted_dict = dict(sorted(cosine_dict.items(), key=lambda item: item[1],reverse = True))
|
74 |
+
if (self.debug):
|
75 |
+
for key in sorted_dict:
|
76 |
+
print("Cosine similarity with \"%s\" is: %.3f" % (key, sorted_dict[key]))
|
77 |
+
if (output_file is not None):
|
78 |
+
with open(output_file,"w") as fp:
|
79 |
+
fp.write(json.dumps(sorted_dict,indent=0))
|
80 |
+
return sorted_dict
|
81 |
+
|
82 |
+
|
83 |
+
|
84 |
+
if __name__ == '__main__':
|
85 |
+
parser = argparse.ArgumentParser(description='OpenAI model for sentence embeddings ',formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
86 |
+
parser.add_argument('-input', action="store", dest="input",required=True,help="Input file with sentences")
|
87 |
+
parser.add_argument('-output', action="store", dest="output",default="output.txt",help="Output file with results")
|
88 |
+
parser.add_argument('-model', action="store", dest="model",default="text-similarity-ada-001",help="model name")
|
89 |
+
|
90 |
+
results = parser.parse_args()
|
91 |
+
obj = OpenAIModel()
|
92 |
+
obj.init_model(results.model)
|
93 |
+
texts, embeddings = obj.compute_embeddings(results.input,is_file = True)
|
94 |
+
results = obj.output_results(results.output,texts,embeddings)
|