bert 适合 embedding 的模型

目录

背景

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

import numpy as np
import pandas as pd
import time
from tqdm.auto import tqdm
from transformers import AutoTokenizer, AutoModel
import torch


class TextEmbedder():
    def __init__(self, model_name="./bge-large-zh-v1.5"):
        # self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")  # 自己电脑跑不起来 gpu
        self.device = torch.device("cpu")
        self.tokenizer = AutoTokenizer.from_pretrained(model_name)
        self.model = AutoModel.from_pretrained(model_name).to(self.device)
        self.model.eval()

    # 没加进度条的
    # def embed_sentences(self, sentences):
    #     encoded_input = self.tokenizer(sentences, padding=True, truncation=True, return_tensors='pt')
    #     with torch.no_grad():
    #         model_output = self.model(**encoded_input)
    #         sentence_embeddings = model_output[0][:, 0]
    #     sentence_embeddings = torch.nn.functional.normalize(sentence_embeddings, p=2, dim=1)
    #
    #     return sentence_embeddings
    
    # 加进度条
    def embed_sentences(self, sentences):
        embedded_sentences = []

        for sentence in tqdm(sentences):
            encoded_input = self.tokenizer([sentence], padding=True, truncation=True, return_tensors='pt')
            with torch.no_grad():
                model_output = self.model(**encoded_input)
                sentence_embedding = model_output[0][:, 0]
            sentence_embedding = torch.nn.functional.normalize(sentence_embedding, p=2)

            embedded_sentences.append(sentence_embedding.cpu().numpy())

        print('当前 bert 模型输出维度为,', embedded_sentences[0].shape[1])
        return np.array(embedded_sentences)

    def save_embeddings_to_memmap(self, sentences, output_file, dtype=np.float32):
        embeddings = self.embed_sentences(sentences)
        shape = embeddings.shape
        embeddings_memmap = np.memmap(output_file, dtype=dtype, mode='w+', shape=shape)
        embeddings_memmap[:] = embeddings[:]
        del embeddings_memmap  # 关闭并确保数据已写入磁盘


def read_data():
    data = pd.read_excel('新建 XLSX 工作表.xlsx')
    return data['addr'].to_list()


def main():
    # text_data = ["这是第一个句子", "这是第二个句子", "这是第三个句子"]
    text_data = read_data()

    embedder = TextEmbedder()

    # 设置输出文件路径
    output_filepath = 'sentence_embeddings.npy'

    # 将文本数据向量化并保存到内存映射文件
    embedder.save_embeddings_to_memmap(text_data, output_filepath)


if __name__ == "__main__":
    start = time.time()
    main()
    end = time.time()
    print(end - start)

求最相似的 topk

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

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

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

index = faiss.IndexFlatL2(1024)  # 假设BERT输出维度是768

# 确保embeddings_memmap是二维数组,如有需要转换
if len(embeddings_memmap.shape) == 1:
    embeddings_memmap = embeddings_memmap.reshape(-1, 1024)

完整代码 

import pandas as pd
import numpy as np
import faiss
from tqdm import tqdm


def search_top4_similarities(index_path, data, topk=4):
    embeddings_memmap = np.memmap(index_path, dtype=np.float32, mode='r')

    index = faiss.IndexFlatL2(768)  # 假设BERT输出维度是768

    # 确保embeddings_memmap是二维数组,如有需要转换
    if len(embeddings_memmap.shape) == 1:
        embeddings_memmap = embeddings_memmap.reshape(-1, 768)

    index.add(embeddings_memmap)

    results = []
    for i, text_emb in enumerate(tqdm(embeddings_memmap)):
        D, I = index.search(np.expand_dims(text_emb, axis=0), topk)  # 查找前topk个最近邻

        # 获取对应的 nature_df_img_id 的索引
        top_k_indices = I[0][:topk]  #
        # 根据索引提取 nature_df_img_id
        top_k_ids = [data.iloc[index]['index'] for index in top_k_indices]

        # 计算余弦相似度并构建字典
        cosine_similarities = [cosine_similarity(text_emb, embeddings_memmap[index]) for index in top_k_indices]
        top_similarity = dict(zip(top_k_ids, cosine_similarities))

        results.append((data['index'].to_list()[i], top_similarity))

    return results


# 使用余弦相似度公式,这里假设 cosine_similarity 是一个计算两个向量之间余弦相似度的函数
def cosine_similarity(vec1, vec2):
    return np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2))


def main_search():
    data = pd.read_excel('新建 XLSX 工作表.xlsx')
    data['index'] = data.index
    similarities = search_top4_similarities('sentence_embeddings.npy', data)

    # 输出结果
    similar_df = pd.DataFrame(similarities, columns=['id', 'top'])
    similar_df.to_csv('similarities.csv', index=False)

# 执行搜索并保存结果
main_search()

结果查看

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

实际案例

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

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

embedding 代码:

import numpy as np
import pandas as pd
import time
from tqdm.auto import tqdm
from transformers import AutoTokenizer, AutoModel
import torch


class TextEmbedder():
    def __init__(self, model_name="./bge-small-zh-v1.5"):
        # self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")  # 自己电脑跑不起来 gpu
        self.device = torch.device("cpu")
        self.tokenizer = AutoTokenizer.from_pretrained(model_name)
        self.model = AutoModel.from_pretrained(model_name).to(self.device)
        self.model.eval()

    # 没加进度条的
    # def embed_sentences(self, sentences):
    #     encoded_input = self.tokenizer(sentences, padding=True, truncation=True, return_tensors='pt')
    #     with torch.no_grad():
    #         model_output = self.model(**encoded_input)
    #         sentence_embeddings = model_output[0][:, 0]
    #     sentence_embeddings = torch.nn.functional.normalize(sentence_embeddings, p=2, dim=1)
    #
    #     return sentence_embeddings

    def embed_sentences(self, sentences):
        embedded_sentences = []

        for sentence in tqdm(sentences):
            encoded_input = self.tokenizer([sentence], padding=True, truncation=True, return_tensors='pt')
            with torch.no_grad():
                model_output = self.model(**encoded_input)
                sentence_embedding = model_output[0][:, 0]
            sentence_embedding = torch.nn.functional.normalize(sentence_embedding, p=2)

            embedded_sentences.append(sentence_embedding.cpu().numpy())

        print('当前 bert 模型输出维度为,', embedded_sentences[0].shape[1])
        return np.array(embedded_sentences)

    def save_embeddings_to_memmap(self, sentences, output_file, dtype=np.float32):
        embeddings = self.embed_sentences(sentences)
        shape = embeddings.shape
        embeddings_memmap = np.memmap(output_file, dtype=dtype, mode='w+', shape=shape)
        embeddings_memmap[:] = embeddings[:]
        del embeddings_memmap  # 关闭并确保数据已写入磁盘


def read_data():
    data = pd.read_excel('data.xlsx')
    return data['address'].to_list()


def main():
    # text_data = ["这是第一个句子", "这是第二个句子", "这是第三个句子"]
    text_data = read_data()

    embedder = TextEmbedder()

    # 设置输出文件路径
    output_filepath = 'sentence_embeddings.npy'

    # 将文本数据向量化并保存到内存映射文件
    embedder.save_embeddings_to_memmap(text_data, output_filepath)


if __name__ == "__main__":
    start = time.time()
    main()
    end = time.time()
    print(end - start)

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

    def embed_sentences(self, sentences, batch_size=32):
        inputs = self.tokenizer(sentences, padding=True, truncation=True, return_tensors='pt').to(self.device)

        # 计算批次数量
        batch_count = (len(inputs['input_ids']) + batch_size - 1) // batch_size

        embeddings_list = []
        with tqdm(total=len(sentences), desc="Embedding Progress") as pbar:
            for batch_idx in range(batch_count):
                start = batch_idx * batch_size
                end = min((batch_idx + 1) * batch_size, len(inputs['input_ids']))

                current_batch_input = inputs[start:end]
                with torch.no_grad():
                    model_output = self.model(**current_batch_input)
                    sentence_embeddings = model_output[0][:, 0]
                embedding_batch = torch.nn.functional.normalize(sentence_embeddings, p=2, dim=1).cpu().numpy()

                # 将当前批次的嵌入向量添加到列表中
                embeddings_list.extend(embedding_batch.tolist())

                # 更新进度条
                pbar.update(end - start)

        # 将所有批次的嵌入向量堆叠成最终的嵌入矩阵
        embeddings = np.vstack(embeddings_list)
        return embeddings

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

import pandas as pd
import numpy as np
import faiss
from tqdm import tqdm


def search_top4_similarities(data_target_embedding, data_ori_embedding, data_target, data_ori, topk=4):
    target_embeddings_memmap = np.memmap(data_target_embedding, dtype=np.float32, mode='r')
    ori_embeddings_memmap = np.memmap(data_ori_embedding, dtype=np.float32, mode='r')

    index = faiss.IndexFlatL2(512)  # BERT输出维度

    # 确保embeddings_memmap是二维数组,如有需要转换
    if len(target_embeddings_memmap.shape) == 1:
        target_embeddings_memmap = target_embeddings_memmap.reshape(-1, 512)
    if len(ori_embeddings_memmap.shape) == 1:
        ori_embeddings_memmap = ori_embeddings_memmap.reshape(-1, 512)

    index.add(target_embeddings_memmap)

    results = []
    for i, text_emb in enumerate(tqdm(ori_embeddings_memmap)):
        D, I = index.search(np.expand_dims(text_emb, axis=0), topk)  # 查找前topk个最近邻

        # 获取对应的 nature_df_img_id 的索引
        top_k_indices = I[0][:topk]  #
        # 根据索引提取 nature_df_img_id
        top_k_ids = [data_target.iloc[index]['store_id'] for index in top_k_indices]

        # 计算余弦相似度并构建字典
        cosine_similarities = [cosine_similarity(text_emb, target_embeddings_memmap[index]) for index in top_k_indices]
        top_similarity = dict(zip(top_k_ids, cosine_similarities))

        results.append((data_ori['store_id'].to_list()[i], top_similarity))

    return results


# 使用余弦相似度公式,这里假设 cosine_similarity 是一个计算两个向量之间余弦相似度的函数
def cosine_similarity(vec1, vec2):
    return np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2))


def main_search():
    data_target = pd.read_excel('data.xlsx')
    data_ori = pd.read_excel('data.xlsx')

    data_target_embedding = 'sentence_embeddings.npy'
    data_ori_embedding = 'sentence_embeddings.npy'

    similarities = search_top4_similarities(data_target_embedding, data_ori_embedding, data_target, data_ori)

    # 输出结果
    similar_df = pd.DataFrame(similarities, columns=['id', 'top'])
    similar_df.to_csv('similarities.csv', index=False)


def format_res():
    similarities_data = pd.read_csv('similarities.csv')
    ori_data = pd.read_excel('data.xlsx')
    target_data = pd.read_excel('data.xlsx')

    res = pd.DataFrame()
    for index, row in similarities_data.iterrows():
        ori_id = row['id']
        tops = row['top']

        tmp_ori_data = ori_data[ori_data['store_id'] == ori_id]
        tmp_target_data = target_data[target_data['store_id'].isin(list(eval(tops).keys()))]

        res_tmp = pd.merge(tmp_ori_data, tmp_target_data, how='cross')
        res = pd.concat([res, res_tmp])
        print(f'进度 {index + 1}/{len(similarities_data)}')

    res.to_excel('format.xlsx', index=False)


# 执行搜索并保存结果
# main_search()

# 格式化
format_res()

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

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

代码链接:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/501374.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

浪潮信息极致存储 助力垦丰破解种子密码

近几年,我国育种行业迈向数字化转型新阶段,以北大荒垦丰种业为代表的育种企业,正持续通过前沿技术赋能,打造研发创新体系,为中国育种行业的高质量发展贡献力量。值得一提的是,在应对存储问题期间&#xff0…

Linux ssh免密登录配置

步骤 在本地机器上生成公钥和私钥对。将本地公钥复制到远程机器的~/.ssh/authorized_keys文件中。 实现1 在服务器上生成SSH密钥对 ssh-keygen -t rsa -f /home/id_rsa1ssh-keygen: 这是一个用于生成、管理和转换 SSH 密钥的 OpenSSH 工具。-t rsa: 用于指定要生成的密钥类…

Centos安装部署

Centos安装部署 linux安装JDK 下载地址:https://www.oracle.com/java/technologies/oracle-java-archive-downloads.html 创建文件夹,输入命令: mkdir /usr/local/jdk 查看JDK信息,输入命令: java -version 将下载的…

无尘布的多重应用:保持洁净,细致无遗

在现代社会中,随着科技的不断进步和人们对卫生环境要求的提高,无尘布作为一种多功能的擦拭材料,正被广泛应用于各种需要高洁净度环境的领域。其多重应用不仅为电子行业、医疗行业、生物工程和光学仪器等专业领域提供了便利,同时也…

(分享)一个图片添加水印的小demo的页面,可自定义样式

有时候想给某张图片添加一个自己的水印,但是又懒的下载相应软件,用js canvas制作一个静态页面,对于单张图片添加自定义文字水印,大小 间距,角度可调。 页面如下: 选择图片,设置相应参数&#x…

内网靶机~~dc-2

一、信息收集 1.端口扫描: nmap -sV -p 1-10000 10.1.1.4 2.CMS识别 3.目录扫描: dirsearch http://10.1.1.4/ 4.FLAG1 似乎让我们用cewl生成密码字典,并爆破登录。 cewl -w rewl_passwd.txt http://dc-2/index.php/flag/ 总结&#xff…

MySQL文件系统解密:binlog、redolog与undolog如何守护数据安全与一致性

目录 一、MySQL文件组成 1.1 数据库层面的文件 错误日志文件 慢查询日志 查询日志 二进制日志(binlog) pid文件 表结构定义文件 1.2 存储引擎层面的文件 表空间文件 重做日志 撤销日志 二、MySQL 单机的二阶段提交 两阶段提交 从概念上说数据库是文件的集合,是依照…

基于java高校社团招新系统设计与实现

摘要 :大学学生社团的不断壮大发展,让对社团的招新管理越来越重要,如何高效的管理社团,促进社团有效的运行和发展变得尤为关键。学生社团在学生的成长发展过程中有着一定的积极作用,要发挥好社团的优势,管…

电脑ip地址如何改?这些修改方法请收好!

在数字化日益深入的今天,电脑作为我们日常工作和生活中的重要工具,其网络功能显得尤为关键。而在网络世界中,IP地址则是电脑连接互联网的身份证,它标识着电脑在网络中的位置与身份。然而,在某些特定情境下,…

基于jsp+mysql+Spring的SpringBoot招聘网站项目

基于jspmysqlSpring的SpringBoot招聘网站项目(完整源码sql)主要实现了管理员登录,简历管理,问答管理,职位管理,用户管理,职位申请进度更新,查看简历 博主介绍:多年java开发经验,专注Java开发、定制、远程、文档编写指导等,csdn特邀…

线性回归模型

线性回归模型是一种用于建模因变量与一个或多个自变量之间线性关系的统计模型。它被广泛应用于回归分析中,用于预测或解释连续型因变量的取值。 线性回归模型假设因变量(或称响应变量) y 与自变量(或称特征) x 之间存…

【WEEK5】 【DAY4】数据库操作【中文版】

2024.3.28 Thursday 目录 2.数据库操作2.1.数据库2.1.1.新建数据库(右键的方法)2.1.2.查询:点击“查询”->“新建查询表”即可输入所需要的语句,点击“运行”,如: 2.2.结构化查询语句分类2.3.数据库操作…

CSS3 (一)

一、CSS3 2D转换 转换(transform)是CSS3中具有颠覆性的特征之一,可以实现元素的位移、旋转、缩放等效果。转换(transform)你可以简单理解为变形。 移动:translate 、旋转:rotate 、缩放&#xf…

双向长短期BiLSTM的回归预测-附MATLAB代码

BiLSTM是一种带有正反向连接的长短期记忆网络(LSTM)。 BiLSTM通过两个独立的LSTM层,一个按时间顺序处理输入,另一个按时间倒序处理输入,分别从正向和反向两个方向捕捉输入序列的特征。具体地,正向LSTM按时…

接口自动化框架搭建(三):pytest库安装

1,使用命令行安装 前提条件:已安装python环境 pip install pytest 或者 pip3 install pytest2,从编译器pycharme中安装

物理寻址和功能寻址,服务器不同的应答策略和NRC回复策略

1:功能寻址,服务器应答与NRC回复策略 详细策略上,又分为服务有子功能,和不存在子功能。 1.1功能寻址,存在子功能 存在子功能的情况下,又分为supress postive response (即子功能字节的bit7&a…

WMware虚拟机配置静态IP

注意:如果是克隆的虚拟机,需要先重新生成mac地址,如下图所示 修改配置文件 :/etc/sysconfig/network-scripts/ifcfg-ens33 注意:1. BOOTPROTO设置为static 2.将下面的IPADDR地址替换为你实际要设置的ip地址 3.NAT模式…

聚酰亚胺PI材料难于粘接,用什么胶水粘接?那么让我们先一步步的从认识它开始(十三): 聚酰亚胺PI纤维的缺点

聚酰亚胺PI纤维的缺点 聚酰亚胺PI纤维虽然是一种具有许多优异特性的高性能纤维,但也存在一些缺点和局限性,这些缺点可能会影响其在某些应用领域的适用性。主要缺点包括: 1.成本高:聚酰亚胺PI纤维的生产过程复杂,原料成…

【倪琴神品品鉴】全新倪诗韵神品级古琴

倪琴朱砂神品仲尼,仅此放漏一张;龙池侧签海门倪诗韵制,雁足上方刻“雷音琴坊”方章,凤沼下方有随形章“神品”二字;老木材纹理竖直,共振良好,是难得的佳器;附带倪老师亲笔签名收藏证…

以太网/USB 数据采集卡 24位16通道 labview 256K同步采样

XM7016以太网SUB数据采集卡 XM7016是一款以太网/USB高速数据采集卡,具有16通道真差分输入,24位分辨率,单通道最高采样率256ksps. 16通道同步共计4.096Msps、精密前置增益放大、集成IEPE/ICP硬件支持的特点。本产品采用了多个高精度24位ADC单元…