句子/文本向量化表示的输出比较

我们从三种预训练模型的角度来进行比较

  1. 利用模型的CLS表示整个文本内容
  2. 利用pooler_output表示整个文本内容
  3. 利用最后隐藏状态的mean来表示文本内容
    整体代码
import time

import torch
from transformers import AutoTokenizer, AutoModelForMaskedLM, AutoModel


# simcse相似度分数
def get_model_output(model, tokenizer, text_str):
    """
    验证文本向量化表示的输出
    :param model:       模型的名称
    :param tokenizer:   tokenizer
    :param text_str:    文本内容 可以只一个str, 也可以是一个str_list
    :return:            返回该文本向量表示形式
    """
    # 返回的类似一个字典类型
    # padding 表示填充max_len  CLS + text + [SEP] return_tensors="pt" 返回pytorch类型的张量
    # 如果不加这个return_tensors="pt"参数 input_ids等key的value就不是tensor而是list
    inputs_text = tokenizer(text_str, return_tensors="pt",  padding=True)
    print_inputs_source(**inputs_text)

    with torch.no_grad():
        outputs_source = model(**inputs_text)
        last_hidden_states = outputs_source.last_hidden_state                   # shape (b_s, max_len, 768)
        # 一般我们会使用的三个模型输出 建议使用 pooling_output
        last_cls_embedding = outputs_source.last_hidden_state[:, 0, :]          # shape (b_s, 768) 每个文本的CLS 表示 这个是固定的
        last_hidden_states_mean = outputs_source.last_hidden_state.mean(dim=1)  # shape (b_s, 768) 这个表示会有问题 对(b_s, max_len, 768) 进行平均池化得到的
        pooling_output = outputs_source.pooler_output                           # shape (b_s, 768) 每个文本的CLS之后在进入一次池化(全连接层)得到pool_output   这个表示也是固定的
        last_hidden_states_pooling = model.pooler(last_hidden_states)
        print("last_hidden_states shape {}".format(last_hidden_states.shape))
        print("last_CLS_embedding shape {}".format(last_cls_embedding.shape))
        print("last_hidden_states_pool shape {}".format(last_hidden_states_pooling.shape))
        print("last_hidden_states_mean shape {}".format(last_hidden_states_mean.shape))
        print("pool_output shape {}".format(pooling_output.shape))
        print("equal last_pool and pool_output: {}".format(torch.equal(pooling_output, last_hidden_states_pooling)))

        print("last_hidden_states embedding {}".format(last_hidden_states))
        print("last_hidden_states mead {}".format(last_hidden_states_mean))
        print("last_cls_embedding embedding {}".format(last_cls_embedding))
        print("pool_output {}".format(pooling_output))


def print_inputs_source(input_ids, token_type_ids, attention_mask):
    """
    打印模型tokenizer之后的内容
    :param input_ids:
    :param token_type_ids:
    :param attention_mask:
    :return:
    """
    print("input_ids: {}".format(input_ids))
    print("token_type_ids: {}".format(token_type_ids))
    print("attention_mask: {}".format(attention_mask))



if __name__ == '__main__':
    """字典的拆包"""
    # "D:\program\pretrained_model\ESimCSE-ext-chinese-bert-wwm"
    """
    Erlangshen-SimCSE-110M-Chinese
    ESimCSE-ext-chinese-bert-wwm        这里的tokenier_config文件用的是sup-simcse的
    sup-simcse-bert-base-uncased
    "bert-base-uncased"
    """
    start_run_time = time.time()
    prefix_path = "D:/program/pretrained_model/"        # 本地路径前缀
    model_path = prefix_path + "bert-base-uncased"
    model = AutoModel.from_pretrained(model_path)
    tokenizer = AutoTokenizer.from_pretrained(model_path)
    text_str = "中国"
    text_list = ["中国", "今天是个好日子"]
    get_model_output(model, tokenizer, text_list)

1. CLS表示整个文本

outputs_source.hidden_states[-1][:, 0, :]这行代码是用来从模型的输出中提取最后一层的所有单词的第一个位置(通常是 [CLS] 标记)的隐藏状态。

1.1 关键代码:

outputs_source = model(**inputs_text) 
        last_hidden_states = outputs_source.last_hidden_state                   # shape (b_s, max_len, 768)
pooling_output
        sentence_embedding = outputs_source.last_hidden_state[:, 0, :]          # shape (b_s, 768) 每个文本的CLS 表示 这个是固定的

这里首先得到模型的最后一层隐藏状态的输出last_hidden_state,shape (b_s, max_len, 768)
然后选出CLS这个token的表示将其作为整个句子的表示

1.2 整体代码

import time

import torch
from transformers import AutoTokenizer, AutoModelForMaskedLM, AutoModel


# simcse相似度分数
def get_model_output(model, tokenizer, text_str):
    """
    验证文本向量化表示的输出
    :param model:       模型的名称
    :param tokenizer:   tokenizer
    :param text_str:    文本内容 可以只一个str, 也可以是一个str_list
    :return:            返回该文本向量表示形式
    """
    # 返回的类似一个字典类型
    # padding 表示填充max_len  CLS + text + [SEP] return_tensors="pt" 返回pytorch类型的张量
    inputs_text = tokenizer(text_str, return_tensors="pt",  padding=True)
    # print_inputs_source(**inputs_text)

    with torch.no_grad():
        outputs_source = model(**inputs_text)
        last_hidden_states = outputs_source.last_hidden_state                   # shape (b_s, max_len, 768)
        # 一般我们会使用的三个模型输出 建议使用 pooling_output
        sentence_embedding = outputs_source.last_hidden_state[:, 0, :]          # shape (b_s, 768) 每个文本的CLS 表示 这个是固定的

    print("last_hidden_state shape: {}".format(last_hidden_states.shape))
    print("cls_embedding shape: {}".format(sentence_embedding.shape))
    print("last_cls_embedding: {}".format(sentence_embedding))
    return sentence_embedding


def print_inputs_source(input_ids, token_type_ids, attention_mask):
    """
    打印模型tokenizer之后的内容  CLS + text + SEP
    :param input_ids:
    :param token_type_ids:
    :param attention_mask:
    :return:
    """
    print("input_ids: {}".format(input_ids))
    print("token_type_ids: {}".format(token_type_ids))
    print("attention_mask: {}".format(attention_mask))


if __name__ == '__main__':
    start_run_time = time.time()
    prefix_path = "D:/program/pretrained_model/"        # 本地路径前缀
    model_path = prefix_path + "bert-base-uncased"
    model = AutoModel.from_pretrained(model_path)
    tokenizer = AutoTokenizer.from_pretrained(model_path)
    text_str = "中国"
    text_list = ["中国", "今天是个好日子"]
    get_model_output(model, tokenizer, text_str)

    end_run_time = time.time()
    used_time = end_run_time-start_run_time
    print("消耗时间为: {}秒".format(used_time))

输出结果: 中国 的文本表示如下

BgeEmbeddings 中文向量化 文本向量化表示_BgeEmbeddings 中文向量化

2. pooler_output表示整个文本

outputs_source.pooler_output 表示从模型的输出中提取的池化器输出(pooler output)。

在BERT模型中,经过最后一层的所有隐藏状态被经过一些池化操作,得到一个被称为"pooler output"的表示。这个表示通常被用于下游任务的输入,因为它是整个句子/序列的一个紧凑的表示。

所以,outputs_source.pooler_output 就是源文本(或输入文本)通过池化器得到的表示。

2.1 关键代码

outputs_source = model(**inputs_text)
        sentence_embedding = outputs_source.pooler_output                   # shape (b_s, max_len, 768)

这里相当于是把最后一层隐藏状态通过一个全连接层然后在进行输出表示整个句子

2.2 整体代码

import time

import torch
from transformers import AutoTokenizer, AutoModelForMaskedLM, AutoModel


# simcse相似度分数
def get_model_output(model, tokenizer, text_str):
    """
    验证文本向量化表示的输出
    :param model:       模型的名称
    :param tokenizer:   tokenizer
    :param text_str:    文本内容 可以只一个str, 也可以是一个str_list
    :return:            返回该文本向量表示形式
    """
    # 返回的类似一个字典类型
    # padding 表示填充max_len  CLS + text + [SEP] return_tensors="pt" 返回pytorch类型的张量
    inputs_text = tokenizer(text_str, return_tensors="pt",  padding=True)
    # print_inputs_source(**inputs_text)

    with torch.no_grad():
        outputs_source = model(**inputs_text)
        sentence_embedding = outputs_source.pooler_output                   # shape (b_s, max_len, 768)
        # 一般我们会使用的三个模型输出 建议使用 pooling_output
        # sentence_embedding = outputs_source.last_hidden_state[:, 0, :]          # shape (b_s, 768) 每个文本的CLS 表示 这个是固定的

    print("pooling_embedding shape: {}".format(sentence_embedding.shape))
    print("pooling_embedding: {}".format(sentence_embedding))
    return sentence_embedding


def print_inputs_source(input_ids, token_type_ids, attention_mask):
    """
    打印模型tokenizer之后的内容  CLS + text + SEP
    :param input_ids:
    :param token_type_ids:
    :param attention_mask:
    :return:
    """
    print("input_ids: {}".format(input_ids))
    print("token_type_ids: {}".format(token_type_ids))
    print("attention_mask: {}".format(attention_mask))


if __name__ == '__main__':
    start_run_time = time.time()
    prefix_path = "D:/program/pretrained_model/"        # 本地路径前缀
    model_path = prefix_path + "bert-base-uncased"
    model = AutoModel.from_pretrained(model_path)
    tokenizer = AutoTokenizer.from_pretrained(model_path)
    text_str = "中国"
    text_list = ["中国", "今天是个好日子"]
    get_model_output(model, tokenizer, text_str)

    end_run_time = time.time()
    used_time = end_run_time-start_run_time
    print("消耗时间为: {}秒".format(used_time))

输出结果: 中国 的文本表示如下

BgeEmbeddings 中文向量化 文本向量化表示_人工智能_02

3. 利用最后隐藏状态的mean进行表示

3.1 关键代码

outputs_source = model(**inputs_text)
        sentence_embedding = outputs_source.last_hidden_state.mean(dim=1)         # shape (b_s, max_len, 768)

这里是利用last_hidden_state的mean进行表示 但这个表示如果利用批量文本向量化的时候可能会出现问题,因为mean的时候会考虑padding, cls_embedding, 和pool_embedding就不会出现这种情况

3.2 整体代码

import time

import torch
from transformers import AutoTokenizer, AutoModelForMaskedLM, AutoModel


# simcse相似度分数
def get_model_output(model, tokenizer, text_str):
    """
    验证文本向量化表示的输出
    :param model:       模型的名称
    :param tokenizer:   tokenizer
    :param text_str:    文本内容 可以只一个str, 也可以是一个str_list
    :return:            返回该文本向量表示形式
    """
    # 返回的类似一个字典类型
    # padding 表示填充max_len  CLS + text + [SEP] return_tensors="pt" 返回pytorch类型的张量
    inputs_text = tokenizer(text_str, return_tensors="pt",  padding=True)
    # print_inputs_source(**inputs_text)

    with torch.no_grad():
        outputs_source = model(**inputs_text)
        sentence_embedding = outputs_source.last_hidden_state.mean(dim=1)         # shape (b_s, max_len, 768)
        # 一般我们会使用的三个模型输出 建议使用 pooling_output
        # sentence_embedding = outputs_source.last_hidden_state[:, 0, :]          # shape (b_s, 768) 每个文本的CLS 表示 这个是固定的

    print("last_mean_embedding shape: {}".format(sentence_embedding.shape))
    print("last_mean_embedding: {}".format(sentence_embedding))
    return sentence_embedding


def print_inputs_source(input_ids, token_type_ids, attention_mask):
    """
    打印模型tokenizer之后的内容  CLS + text + SEP
    :param input_ids:
    :param token_type_ids:
    :param attention_mask:
    :return:
    """
    print("input_ids: {}".format(input_ids))
    print("token_type_ids: {}".format(token_type_ids))
    print("attention_mask: {}".format(attention_mask))


if __name__ == '__main__':
    start_run_time = time.time()
    prefix_path = "D:/program/pretrained_model/"        # 本地路径前缀
    model_path = prefix_path + "bert-base-uncased"
    model = AutoModel.from_pretrained(model_path)
    tokenizer = AutoTokenizer.from_pretrained(model_path)
    text_str = "中国"
    text_list = ["中国", "今天是个好日子"]
    get_model_output(model, tokenizer, text_str)

    end_run_time = time.time()
    used_time = end_run_time-start_run_time
    print("消耗时间为: {}秒".format(used_time))

输出结果: 中国 的文本表示如下

BgeEmbeddings 中文向量化 文本向量化表示_sed_03

输出结果: 如果输入文本为text_list(批量向量化)那么输出结果如下

BgeEmbeddings 中文向量化 文本向量化表示_深度学习_04

这里可以发现中国的文本表示变化了 说明padding填充影响了mean的结果
所以这种方法慎重使用

4. 总结:

4.1 三种表示方法

model(**inputs_text).last_hidden_state.mean(dim=1)
        model(**inputs_text).last_hidden_state[:, 0, :]
        model(**inputs_text).pooler_output

4.2 last_hidden_state 和 pooler_output的区别以及转化

参考博客:

last_hidden_states = model(**inputs_text).last_hidden_state 
model.pooler(last_hidden_state) == model(**inputs_text).pooler_output
import time

import torch
from transformers import AutoTokenizer, AutoModelForMaskedLM, AutoModel


# simcse相似度分数
def get_model_output(model, tokenizer, text_str):
    """
    验证文本向量化表示的输出
    :param model:       模型的名称
    :param tokenizer:   tokenizer
    :param text_str:    文本内容 可以只一个str, 也可以是一个str_list
    :return:            返回该文本向量表示形式
    """
    # 返回的类似一个字典类型
    # padding 表示填充max_len  CLS + text + [SEP] return_tensors="pt" 返回pytorch类型的张量
    inputs_text = tokenizer(text_str, return_tensors="pt",  padding=True)
    # print_inputs_source(**inputs_text)

    with torch.no_grad():
        outputs_source = model(**inputs_text)
        pooling_embedding = outputs_source.pooler_output                   # shape (b_s, max_len, 768)

        last_pool_embedding = model.pooler(outputs_source.last_hidden_state)

        # 一般我们会使用的三个模型输出 建议使用 pooling_output
        # sentence_embedding = outputs_source.last_hidden_state[:, 0, :]          # shape (b_s, 768) 每个文本的CLS 表示 这个是固定的

    print("valid equals {}".format(torch.equal(pooling_embedding, last_pool_embedding)))

    return pooling_embedding


if __name__ == '__main__':
    start_run_time = time.time()
    prefix_path = "D:/program/pretrained_model/"        # 本地路径前缀
    model_path = prefix_path + "bert-base-uncased"
    model = AutoModel.from_pretrained(model_path)
    tokenizer = AutoTokenizer.from_pretrained(model_path)
    text_str = "中国"
    text_list = ["中国", "今天是个好日子"]
    get_model_output(model, tokenizer, text_str)

    end_run_time = time.time()
    used_time = end_run_time-start_run_time
    print("消耗时间为: {:.2f}秒".format(used_time))

水平有限 如有问题欢迎指正交流