当前位置:   article > 正文

bert 适合 embedding 的模型_bge-small-zh-v1.5

bge-small-zh-v1.5

目录

背景

embedding

求最相似的 topk

结果查看


背景

想要求两个文本的相似度,就单纯相似度,不要语义相似度,直接使用 bert 先 embedding 然后找出相似的文本,效果都不太好,试过 bert-base-chinese,bert-wwm,robert-wwm 这些,都有一个问题,那就是明明不相似的文本却在结果中变成了相似,真正相似的有没有,

例如:手机壳迷你版,与这条数据相似的应该都是跟手机壳有关的才合理,但结果不太好,明明不相关的,余弦相似度都能有有 0.9 以上的,所以问题出在 embedding 上,找了适合做 embedding 的模型,再去计算相似效果好了很多,合理很多。

之前写了一篇 bert+np.memap+faiss文本相似度匹配 topN-CSDN博客 是把流程打通,现在是找适合文本相似的来操作。

模型

bge-small-zh-v1.5

bge-large-zh-v1.5

embedding

数据弄的几条测试数据,方便看那些相似

我用 bge-large-zh-v1.5 来操作,embedding 代码,为了知道 embedding 进度,加了进度条功能,同时打印了当前使用 embedding 的 bert 模型输出为度,这很重要,会影响求相似的 topk

  1. import numpy as np
  2. import pandas as pd
  3. import time
  4. from tqdm.auto import tqdm
  5. from transformers import AutoTokenizer, AutoModel
  6. import torch
  7. class TextEmbedder():
  8. def __init__(self, model_name="./bge-large-zh-v1.5"):
  9. # self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 自己电脑跑不起来 gpu
  10. self.device = torch.device("cpu")
  11. self.tokenizer = AutoTokenizer.from_pretrained(model_name)
  12. self.model = AutoModel.from_pretrained(model_name).to(self.device)
  13. self.model.eval()
  14. # 没加进度条的
  15. # def embed_sentences(self, sentences):
  16. # encoded_input = self.tokenizer(sentences, padding=True, truncation=True, return_tensors='pt')
  17. # with torch.no_grad():
  18. # model_output = self.model(**encoded_input)
  19. # sentence_embeddings = model_output[0][:, 0]
  20. # sentence_embeddings = torch.nn.functional.normalize(sentence_embeddings, p=2, dim=1)
  21. #
  22. # return sentence_embeddings
  23. # 加进度条
  24. def embed_sentences(self, sentences):
  25. embedded_sentences = []
  26. for sentence in tqdm(sentences):
  27. encoded_input = self.tokenizer([sentence], padding=True, truncation=True, return_tensors='pt')
  28. with torch.no_grad():
  29. model_output = self.model(**encoded_input)
  30. sentence_embedding = model_output[0][:, 0]
  31. sentence_embedding = torch.nn.functional.normalize(sentence_embedding, p=2)
  32. embedded_sentences.append(sentence_embedding.cpu().numpy())
  33. print('当前 bert 模型输出维度为,', embedded_sentences[0].shape[1])
  34. return np.array(embedded_sentences)
  35. def save_embeddings_to_memmap(self, sentences, output_file, dtype=np.float32):
  36. embeddings = self.embed_sentences(sentences)
  37. shape = embeddings.shape
  38. embeddings_memmap = np.memmap(output_file, dtype=dtype, mode='w+', shape=shape)
  39. embeddings_memmap[:] = embeddings[:]
  40. del embeddings_memmap # 关闭并确保数据已写入磁盘
  41. def read_data():
  42. data = pd.read_excel('新建 XLSX 工作表.xlsx')
  43. return data['addr'].to_list()
  44. def main():
  45. # text_data = ["这是第一个句子", "这是第二个句子", "这是第三个句子"]
  46. text_data = read_data()
  47. embedder = TextEmbedder()
  48. # 设置输出文件路径
  49. output_filepath = 'sentence_embeddings.npy'
  50. # 将文本数据向量化并保存到内存映射文件
  51. embedder.save_embeddings_to_memmap(text_data, output_filepath)
  52. if __name__ == "__main__":
  53. start = time.time()
  54. main()
  55. end = time.time()
  56. print(end - start)

求最相似的 topk

使用 faiss 索引需要设置 bert 模型的维度,所以我们前面打印出来了,要不然会报错,像这样的:

ValueError: cannot reshape array of size 10240 into shape (768)

所以  print('当前 bert 模型输出维度为,', embedded_sentences[0].shape[1]) 的值换上去,我这里打印的 1024

  1. index = faiss.IndexFlatL2(1024) # 假设BERT输出维度是768
  2. # 确保embeddings_memmap是二维数组,如有需要转换
  3. if len(embeddings_memmap.shape) == 1:
  4. embeddings_memmap = embeddings_memmap.reshape(-1, 1024)

完整代码 

  1. import pandas as pd
  2. import numpy as np
  3. import faiss
  4. from tqdm import tqdm
  5. def search_top4_similarities(index_path, data, topk=4):
  6. embeddings_memmap = np.memmap(index_path, dtype=np.float32, mode='r')
  7. index = faiss.IndexFlatL2(768) # 假设BERT输出维度是768
  8. # 确保embeddings_memmap是二维数组,如有需要转换
  9. if len(embeddings_memmap.shape) == 1:
  10. embeddings_memmap = embeddings_memmap.reshape(-1, 768)
  11. index.add(embeddings_memmap)
  12. results = []
  13. for i, text_emb in enumerate(tqdm(embeddings_memmap)):
  14. D, I = index.search(np.expand_dims(text_emb, axis=0), topk) # 查找前topk个最近邻
  15. # 获取对应的 nature_df_img_id 的索引
  16. top_k_indices = I[0][:topk] #
  17. # 根据索引提取 nature_df_img_id
  18. top_k_ids = [data.iloc[index]['index'] for index in top_k_indices]
  19. # 计算余弦相似度并构建字典
  20. cosine_similarities = [cosine_similarity(text_emb, embeddings_memmap[index]) for index in top_k_indices]
  21. top_similarity = dict(zip(top_k_ids, cosine_similarities))
  22. results.append((data['index'].to_list()[i], top_similarity))
  23. return results
  24. # 使用余弦相似度公式,这里假设 cosine_similarity 是一个计算两个向量之间余弦相似度的函数
  25. def cosine_similarity(vec1, vec2):
  26. return np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2))
  27. def main_search():
  28. data = pd.read_excel('新建 XLSX 工作表.xlsx')
  29. data['index'] = data.index
  30. similarities = search_top4_similarities('sentence_embeddings.npy', data)
  31. # 输出结果
  32. similar_df = pd.DataFrame(similarities, columns=['id', 'top'])
  33. similar_df.to_csv('similarities.csv', index=False)
  34. # 执行搜索并保存结果
  35. main_search()

结果查看

看一看到余弦数值还是比较合理的,没有那种明明不相关但余弦值是 0.9 的情况了,这两个模型还是可以的

实际案例

以前做过一个地址相似度聚合的,找出每个地址与它相似的地址,最多是 0-3 个相似的地址(当时人工验证过的,这里直接说明)

我们用 bge-small-zh-v1.5 模型来做 embedding,这个模型维度是 512,数据是店名id,地址两列

embedding 代码:

  1. import numpy as np
  2. import pandas as pd
  3. import time
  4. from tqdm.auto import tqdm
  5. from transformers import AutoTokenizer, AutoModel
  6. import torch
  7. class TextEmbedder():
  8. def __init__(self, model_name="./bge-small-zh-v1.5"):
  9. # self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 自己电脑跑不起来 gpu
  10. self.device = torch.device("cpu")
  11. self.tokenizer = AutoTokenizer.from_pretrained(model_name)
  12. self.model = AutoModel.from_pretrained(model_name).to(self.device)
  13. self.model.eval()
  14. # 没加进度条的
  15. # def embed_sentences(self, sentences):
  16. # encoded_input = self.tokenizer(sentences, padding=True, truncation=True, return_tensors='pt')
  17. # with torch.no_grad():
  18. # model_output = self.model(**encoded_input)
  19. # sentence_embeddings = model_output[0][:, 0]
  20. # sentence_embeddings = torch.nn.functional.normalize(sentence_embeddings, p=2, dim=1)
  21. #
  22. # return sentence_embeddings
  23. def embed_sentences(self, sentences):
  24. embedded_sentences = []
  25. for sentence in tqdm(sentences):
  26. encoded_input = self.tokenizer([sentence], padding=True, truncation=True, return_tensors='pt')
  27. with torch.no_grad():
  28. model_output = self.model(**encoded_input)
  29. sentence_embedding = model_output[0][:, 0]
  30. sentence_embedding = torch.nn.functional.normalize(sentence_embedding, p=2)
  31. embedded_sentences.append(sentence_embedding.cpu().numpy())
  32. print('当前 bert 模型输出维度为,', embedded_sentences[0].shape[1])
  33. return np.array(embedded_sentences)
  34. def save_embeddings_to_memmap(self, sentences, output_file, dtype=np.float32):
  35. embeddings = self.embed_sentences(sentences)
  36. shape = embeddings.shape
  37. embeddings_memmap = np.memmap(output_file, dtype=dtype, mode='w+', shape=shape)
  38. embeddings_memmap[:] = embeddings[:]
  39. del embeddings_memmap # 关闭并确保数据已写入磁盘
  40. def read_data():
  41. data = pd.read_excel('data.xlsx')
  42. return data['address'].to_list()
  43. def main():
  44. # text_data = ["这是第一个句子", "这是第二个句子", "这是第三个句子"]
  45. text_data = read_data()
  46. embedder = TextEmbedder()
  47. # 设置输出文件路径
  48. output_filepath = 'sentence_embeddings.npy'
  49. # 将文本数据向量化并保存到内存映射文件
  50. embedder.save_embeddings_to_memmap(text_data, output_filepath)
  51. if __name__ == "__main__":
  52. start = time.time()
  53. main()
  54. end = time.time()
  55. print(end - start)

求 embeddgin 是串行的,要想使用 gpu ,可以需修改 embed_sentences 函数:

  1. def embed_sentences(self, sentences, batch_size=32):
  2. inputs = self.tokenizer(sentences, padding=True, truncation=True, return_tensors='pt').to(self.device)
  3. # 计算批次数量
  4. batch_count = (len(inputs['input_ids']) + batch_size - 1) // batch_size
  5. embeddings_list = []
  6. with tqdm(total=len(sentences), desc="Embedding Progress") as pbar:
  7. for batch_idx in range(batch_count):
  8. start = batch_idx * batch_size
  9. end = min((batch_idx + 1) * batch_size, len(inputs['input_ids']))
  10. current_batch_input = inputs[start:end]
  11. with torch.no_grad():
  12. model_output = self.model(**current_batch_input)
  13. sentence_embeddings = model_output[0][:, 0]
  14. embedding_batch = torch.nn.functional.normalize(sentence_embeddings, p=2, dim=1).cpu().numpy()
  15. # 将当前批次的嵌入向量添加到列表中
  16. embeddings_list.extend(embedding_batch.tolist())
  17. # 更新进度条
  18. pbar.update(end - start)
  19. # 将所有批次的嵌入向量堆叠成最终的嵌入矩阵
  20. embeddings = np.vstack(embeddings_list)
  21. return embeddings

求 topk 的,我们求 top4 就可以了

  1. import pandas as pd
  2. import numpy as np
  3. import faiss
  4. from tqdm import tqdm
  5. def search_top4_similarities(data_target_embedding, data_ori_embedding, data_target, data_ori, topk=4):
  6. target_embeddings_memmap = np.memmap(data_target_embedding, dtype=np.float32, mode='r')
  7. ori_embeddings_memmap = np.memmap(data_ori_embedding, dtype=np.float32, mode='r')
  8. index = faiss.IndexFlatL2(512) # BERT输出维度
  9. # 确保embeddings_memmap是二维数组,如有需要转换
  10. if len(target_embeddings_memmap.shape) == 1:
  11. target_embeddings_memmap = target_embeddings_memmap.reshape(-1, 512)
  12. if len(ori_embeddings_memmap.shape) == 1:
  13. ori_embeddings_memmap = ori_embeddings_memmap.reshape(-1, 512)
  14. index.add(target_embeddings_memmap)
  15. results = []
  16. for i, text_emb in enumerate(tqdm(ori_embeddings_memmap)):
  17. D, I = index.search(np.expand_dims(text_emb, axis=0), topk) # 查找前topk个最近邻
  18. # 获取对应的 nature_df_img_id 的索引
  19. top_k_indices = I[0][:topk] #
  20. # 根据索引提取 nature_df_img_id
  21. top_k_ids = [data_target.iloc[index]['store_id'] for index in top_k_indices]
  22. # 计算余弦相似度并构建字典
  23. cosine_similarities = [cosine_similarity(text_emb, target_embeddings_memmap[index]) for index in top_k_indices]
  24. top_similarity = dict(zip(top_k_ids, cosine_similarities))
  25. results.append((data_ori['store_id'].to_list()[i], top_similarity))
  26. return results
  27. # 使用余弦相似度公式,这里假设 cosine_similarity 是一个计算两个向量之间余弦相似度的函数
  28. def cosine_similarity(vec1, vec2):
  29. return np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2))
  30. def main_search():
  31. data_target = pd.read_excel('data.xlsx')
  32. data_ori = pd.read_excel('data.xlsx')
  33. data_target_embedding = 'sentence_embeddings.npy'
  34. data_ori_embedding = 'sentence_embeddings.npy'
  35. similarities = search_top4_similarities(data_target_embedding, data_ori_embedding, data_target, data_ori)
  36. # 输出结果
  37. similar_df = pd.DataFrame(similarities, columns=['id', 'top'])
  38. similar_df.to_csv('similarities.csv', index=False)
  39. def format_res():
  40. similarities_data = pd.read_csv('similarities.csv')
  41. ori_data = pd.read_excel('data.xlsx')
  42. target_data = pd.read_excel('data.xlsx')
  43. res = pd.DataFrame()
  44. for index, row in similarities_data.iterrows():
  45. ori_id = row['id']
  46. tops = row['top']
  47. tmp_ori_data = ori_data[ori_data['store_id'] == ori_id]
  48. tmp_target_data = target_data[target_data['store_id'].isin(list(eval(tops).keys()))]
  49. res_tmp = pd.merge(tmp_ori_data, tmp_target_data, how='cross')
  50. res = pd.concat([res, res_tmp])
  51. print(f'进度 {index + 1}/{len(similarities_data)}')
  52. res.to_excel('format.xlsx', index=False)
  53. # 执行搜索并保存结果
  54. # main_search()
  55. # 格式化
  56. format_res()

在这里我们把原始数据当两份使用,一份作为目标数据,一份原始数据,要原始数据的每一个地址在目标数据中找相似的

最后为了人工方便查看验证,数据格式化了,开始我说了,这数据结果每个地址跟它相似的有 0-3 条,黄色的每一组,红色的是真正相似的,从结果上来看,还是符合预期的

代码链接:

链接:https://pan.baidu.com/s/1S951j1TNoN9XbRA286jU-w 
提取码:nb4b 
 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/616629
推荐阅读
相关标签
  

闽ICP备14008679号