不同模型对 Emoji 和普通文本的处理表现,Emoji的向量嵌入(含测试代码)

数据处理目标

  • 保留emoji和文本的原始形态
  • 分词时不拆分emoji符号,让emoji成为一个完整的Token。
  • 确保分词结果与模型兼容,既能表达语义,也能保留emoji的特性。

验证代码

from transformers import AutoTokenizer

# 测试的模型列表
models = [
    "bert-base-uncased",          # BERT
    "vinai/bertweet-base",        # BERTweet
    "roberta-base",               # RoBERTa
    "hfl/chinese-bert-wwm" 
]

# 示例句子
texts = [
    "I am ",  # 含有 emoji
    " is funny!",  # 多个相同的 emoji
    "No emoji here."  # 没有 emoji
]

# 遍历测试每个模型
for model_name in models:
    print(f"模型: {model_name}")
    tokenizer = AutoTokenizer.from_pretrained(model_name)

    for text in texts:
        encoded = tokenizer(text, return_tensors="pt")
        tokens = tokenizer.convert_ids_to_tokens(encoded["input_ids"][0])
        print(f"原始文本: {text}")
        print(f"Token IDs: {encoded['input_ids'][0].tolist()}")
        print(f"分词结果: {tokens}")
        print()
    print("-" * 50)
模型: bert-base-uncased
原始文本: I am 
Token IDs: [101, 1045, 2572, 100, 102]
分词结果: ['[CLS]', 'i', 'am', '[UNK]', '[SEP]']

原始文本:  is funny!
Token IDs: [101, 100, 2003, 6057, 999, 102]
分词结果: ['[CLS]', '[UNK]', 'is', 'funny', '!', '[SEP]']

原始文本: No emoji here.
Token IDs: [101, 2053, 7861, 29147, 2072, 2182, 1012, 102]
分词结果: ['[CLS]', 'no', 'em', '##oj', '##i', 'here', '.', '[SEP]']

--------------------------------------------------
模型: vinai/bertweet-base
emoji is not installed, thus not converting emoticons or emojis into text. Install emoji: pip3 install emoji==0.6.0
原始文本: I am 
Token IDs: [0, 8, 155, 3, 2]
分词结果: ['', 'I', 'am', '', '']

原始文本:  is funny!
Token IDs: [0, 3, 3, 3, 17, 55784, 12, 2]
分词结果: ['', '', '', '', 'is', 'funny@@', '!', '']

原始文本: No emoji here.
Token IDs: [0, 218, 6728, 3506, 3, 2]
分词结果: ['', 'No', 'emoji', 'her@@', '', '']

--------------------------------------------------
模型: roberta-base
原始文本: I am 
Token IDs: [0, 100, 524, 17841, 27969, 2]
分词结果: ['', 'I', 'Ġam', 'ĠðŁĺ', 'Ĭ', '']

原始文本:  is funny!
Token IDs: [0, 18636, 9264, 18636, 9264, 18636, 9264, 16, 6269, 328, 2]
分词结果: ['', 'ðŁĺ', 'Ĥ', 'ðŁĺ', 'Ĥ', 'ðŁĺ', 'Ĥ', 'Ġis', 'Ġfunny', '!', '']

原始文本: No emoji here.
Token IDs: [0, 3084, 21554, 259, 4, 2]
分词结果: ['', 'No', 'Ġemoji', 'Ġhere', '.', '']

--------------------------------------------------
模型: hfl/chinese-bert-wwm
原始文本: I am 
Token IDs: [101, 151, 8413, 100, 102]
分词结果: ['[CLS]', 'i', 'am', '[UNK]', '[SEP]']

原始文本:  is funny!
Token IDs: [101, 8104, 21126, 21126, 8310, 9575, 8680, 106, 102]
分词结果: ['[CLS]', '', '##', '##', 'is', 'fun', '##ny', '!', '[SEP]']

原始文本: No emoji here.
Token IDs: [101, 8275, 13152, 8167, 9343, 10815, 119, 102]
分词结果: ['[CLS]', 'no', 'em', '##o', '##ji', 'here', '.', '[SEP]']

--------------------------------------------------

问题 1:为什么连续的三个 emoji 被映射到同一个 [UNK],而不是占据三个 [UNK]?
        无法识别 emoji:BERT 的词表(vocab.txt)中并没有预定义 emoji 的 token。
        连续字符的处理规则:WordPiece 会将连续的未知字符视为一个整体。
        结果:连续的 emoji 被当作一个整体,分配一个 [UNK]。
问题 2:BERTweet 为什么生成的标签是 ,而不是 [SEP]
         表示句子开始,类似于 BERT 的 [CLS]。
        
表示句子结束,类似于 BERT 的 [SEP]。
问题 3:BERTweet 为什么将 "here" 分成了 her@@ 和
        这是 SentencePiece Tokenizer 的特点,它会尝试将无法完全匹配的词分割为子词或子字符。@@ 表示这是一个未完成的子词片段。SentencePiece 使用子词分割尝试匹配已知的词汇部分。例如,"here" 被分割为 her@@ 和 e(这里可能 e 无法匹配到已知 token,因此标记为 )。
问题 4:为什么 RoBERTa 给正常的单词(如 "here")前面加了奇怪的符号(如 Ġ)?
        这是 RoBERTa 的 Byte-Pair Encoding (BPE) 分词器的特性。在 BPE 分词中,Ġ 是特殊的空格标记,用于区分单词边界。例如:" here"(带空格的 here)被分割为 Ġhere。
"there"(不带空格的 there)被分割为 there。

不同模型对 Emoji 和普通文本的处理表现总结

模型 处理 Emoji 的行为 特殊标记 普通单词的分词情况 特点
bert-base-uncased - Emoji 被标记为 [UNK],无法识别。 [CLS][SEP] 使用 WordPiece 分词,分割成子词,例如 em##oji 无法识别 emoji,连续多个 emoji 会被视为一个 [UNK]
vinai/bertweet-base - 每个 Emoji 被标记为 ,无法单独识别。 here 被分割为 her@@,属于 SentencePiece 特性。 SentencePiece 分词器,emoji 和部分单词无法完全匹配,会有 标记。
roberta-base - Emoji 被拆分为多个子词(如 ðŁĺĤ)。 普通单词前添加 Ġ 表示空格(如 Ġhere)。 使用 BPE 分词器,支持部分 emoji,但会对其进行子词拆分。
hfl/chinese-bert-wwm - 部分 Emoji 被正确识别(如 ##)。 [CLS][SEP] 普通单词按子词拆分,例如 em##oji 能正确处理部分 emoji,但表现不稳定;针对中文优化,更适合中文语料。
模型 适用场景
bert-base-uncased 适合处理一般英语文本,但不适用于包含大量 emoji 的数据。
vinai/bertweet-base 适合社交媒体语料(如推文),但需要额外处理 以保证语义一致性。
roberta-base 适合处理需要精确分词和保留子词信息的任务,不适合直接用于 emoji 检索。
hfl/chinese-bert-wwm 中文语料优化模型,适合包含中文和部分 emoji 的文本,但需要验证分词一致性。

问题来了,如果转换为描述字符串,会被分开,导致本质上还是无法检索。

emoji 库 spaCy 对于 emoji 的处理方式有何区别?

emoji 这个 Python 库会将 emoji 转换为描述性字符串。例如:
import emoji
text = "I am "
print(emoji.demojize(text))  # 输出: "I am :smiling_face_with_smiling_eyes:"
spaCy 的处理方式

spaCyspacy-emoji 扩展能够直接识别 emoji 并将其作为独立的 Token。spaCy 不会将 emoji 转换为描述性字符串,而是保留原始 emoji 符号作为一个 Token。例如:

  • import spacy
    from spacy.lang.en import English
    from spacymoji import Emoji
    
    nlp = English()
    emoji_ext = Emoji(nlp)
    nlp.add_pipe("emoji", first=True)
    
    doc = nlp("I am ")
    print([token.text for token in doc])  # 输出: ["I", "am", ""]
    

使用描述字符串是否会被分词?

如果你将 emoji 转换为描述字符串(如 :smiling_face_with_smiling_eyes:),BERT 的分词器会将其视为普通文本,并进行子词分割。例如:

from transformers import BertTokenizer

tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
text = "I am :smiling_face_with_smiling_eyes:"
encoded = tokenizer(text)
print(tokenizer.convert_ids_to_tokens(encoded["input_ids"]))

输出:

['[CLS]', 'i', 'am', ':', 'smiling', '_', 'face', '_', 'with', '_', 'smiling', '_', 'eyes', ':', '[SEP]']
问题:
  1. 分词干扰语义:一个 emoji 描述字符串被分割成多个子词(如 smiling_face),导致其向量表示不再对应单个 emoji,而是描述字符串的组合。
  2. 无法进行直接检索:你无法通过描述字符串的嵌入直接检索原始 emoji 的语义表示。

如何避免这种干扰,确保 emoji 的一致性?

方法 :为 emoji 定义专属 Token

你可以扩展 BERT 的分词器,增加 emoji 的自定义 Token,这样每个 emoji 都会被视为单独的 Token,而不是 [UNK] 或描述字符串。

示例:

from transformers import BertTokenizer

# 加载预训练分词器
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")

# 添加自定义 Token
emoji_tokens = ["", "", ""]  # 添加常用 emoji
tokenizer.add_tokens(emoji_tokens)

# 更新模型
from transformers import BertModel
model = BertModel.from_pretrained("bert-base-uncased")
model.resize_token_embeddings(len(tokenizer))

# 测试分词器
text = "I am "
encoded = tokenizer(text)
print(tokenizer.convert_ids_to_tokens(encoded["input_ids"]))

总结

  1. emoji 库将 emoji 转换为描述字符串。spacy-emoji 能直接识别 emoji,并将其作为独立的 Token。

  2. BERT 对描述字符串的分词:默认情况下,描述字符串会被分词,破坏 emoji 的语义一致性。

  3. 解决方案:保留原始 emoji,不将其转换为描述字符串。为 emoji 定义自定义 Token,确保其生成独立的向量。
     


许多分词工具默认会忽略或拆分emoji,因此需要选择支持emoji的分词器:

  • 推荐工具:Hugging Face的BertTokenizeremoji库配合分词工具。
  • 特点:这些工具能直接将emoji作为完整的Token处理。

示例代码:

from transformers import BertTokenizer

# 加载支持emoji的分词器
tokenizer = BertTokenizer.from_pretrained("bert-base-multilingual-cased")

# 示例文本,含emoji
text = "这个视频笑死我了  太搞笑了!"

# 分词
tokens = tokenizer.tokenize(text)
print("分词结果:", tokens)

输出示例

['这', '个', '视频', '笑', '死', '我', '了', '', '', '太', '搞', '笑', '了', '!']

(2) 保证emoji作为自然语言处理的单位

对于不支持emoji的分词工具(如某些传统中文分词工具),可以预处理文本,将emoji保留为单独的符号单元。示例如下:

import re

# 分离emoji与文本
def split_emoji_and_text(text):
    emoji_pattern = re.compile("["
        u"\U0001F600-\U0001F64F"  # 表情符号
        u"\U0001F300-\U0001F5FF"  # 符号和图形
        u"\U0001F680-\U0001F6FF"  # 交通工具
        u"\U0001F700-\U0001F77F"  # 额外符号
        "]+", flags=re.UNICODE)
    tokens = re.split(f"({emoji_pattern.pattern})", text)
    return [t for t in tokens if t.strip()]

# 示例文本
text = "这个视频笑死我了太搞笑了!"
tokens = split_emoji_and_text(text)
print("分词结果:", tokens)

输出示例

['这个视频笑死我了', '', '', '太搞笑了!']

(3) 自定义分词与BERT Tokenizer结合

为了兼容BERT输入格式,同时确保emoji和文本被完整处理,可以自定义分词流程:

# 自定义分词函数与BERT结合
def custom_tokenize_with_emoji(text, tokenizer):
    tokens = split_emoji_and_text(text)  # 先用正则分离emoji
    final_tokens = []
    for token in tokens:
        if re.match("[\U0001F600-\U0001F77F]", token):  # 如果是emoji,直接保留
            final_tokens.append(token)
        else:
            final_tokens.extend(tokenizer.tokenize(token))  # 非emoji部分用BERT分词
    return final_tokens

# 示例
tokenizer = BertTokenizer.from_pretrained("bert-base-multilingual-cased")
text = "这个视频笑死我了太搞笑了!"
tokens = custom_tokenize_with_emoji(text, tokenizer)
print("分词结果:", tokens)

输出示例

['这', '个', '视频', '笑', '死', '我', '了', '', '', '太', '搞', '笑', '了', '!']

兼容emoji的模型推荐

  1. Emoji2Vec(若需单独处理emoji语义)

1. BERT 如何处理 Emoji

  1. 词汇表(Vocabulary)

    • BERT 的词汇表是由训练数据的 Tokenization 生成的,基于常见的词片段(subword)。
    • 如果 emoji 出现在 BERT 的词汇表中(如常见的 或 ❤️),则会被当作单独的 Token。
    • 如果某个 emoji 不在词汇表中(如一些不常见的符号组合),BERT 会将其拆分为多个 Token 或标记为 [UNK](未知符号)。
  2. 嵌入空间

    • 每个 Token(包括 emoji)的嵌入向量是基于上下文生成的,因此不同 emoji 的语义取决于其上下文。
    • 如果 emoji 在训练数据中出现频率高,则模型能够更准确地捕捉其语义关系。
  3. 语义关系

    • BERT 使用自注意力机制(Self-Attention)捕捉句子中 Token(包括 emoji)之间的语义关系。
    • 类似于单词,emoji 的语义关系可以通过其嵌入向量的相似度来衡量。

2. 可以像单词一样处理 Emoji 吗?

  • 是的,你可以像处理普通单词一样处理 emoji,例如计算嵌入向量的相似度、聚类或分类任务。
  • 注意:不同 emoji 的处理效果依赖于模型的训练数据。如果训练数据包含大量 emoji,上下文语义会更准确。

3. 测试代码:验证 BERT 对 Emoji 的处理

以下代码展示了如何加载 BERT 模型,提取 emoji 的嵌入向量,并验证它们之间的语义关系。

from transformers import BertTokenizer, BertModel
import torch
from sklearn.metrics.pairwise import cosine_similarity

# 加载 BERT 模型和分词器
tokenizer = BertTokenizer.from_pretrained("bert-base-multilingual-cased")
model = BertModel.from_pretrained("bert-base-multilingual-cased")

# 示例文本(包含 emoji)
text1 = "这个视频太搞笑了 "
text2 = "哈哈哈  我笑得停不下来"
text3 = "太可爱了 ❤️"

# 对文本进行分词并生成嵌入
def get_embeddings(text):
    inputs = tokenizer(text, return_tensors="pt")
    outputs = model(**inputs)
    # 返回最后一层的隐藏状态,取出 [CLS] Token 的向量表示
    return outputs.last_hidden_state[0][0].detach().numpy()

# 获取每个文本的嵌入向量
embedding1 = get_embeddings(text1)
embedding2 = get_embeddings(text2)
embedding3 = get_embeddings(text3)

# 计算语义相似度
similarity_1_2 = cosine_similarity([embedding1], [embedding2])[0][0]
similarity_1_3 = cosine_similarity([embedding1], [embedding3])[0][0]

print(" 和  的相似度:", similarity_1_2)
print(" 和 ❤️ 的相似度:", similarity_1_3)
输出示例
  • 如果 在语义上是相似的,你会看到较高的相似度值。
  • 如果 ❤️ 语义不同,相似度会较低。

4. 进一步验证:查看 Token 化结果

可以通过以下代码查看 BERT 如何将文本和 emoji 转换为 Token:

# 查看 Token 化结果
tokens = tokenizer.tokenize("这个视频太搞笑了 ")
print("Token 化结果:", tokens)

# 查看 Token ID
token_ids = tokenizer.convert_tokens_to_ids(tokens)
print("Token IDs:", token_ids)

输出示例

Token 化结果: ['这', '个', '视频', '太', '搞', '笑', '了', '']
Token IDs: [6821, 702, 6422, 1922, 3300, 3630, 749, 160002]
  • 如果 被当作一个 Token,则会有一个独立的 Token ID。
  • 如果 不在词汇表中,则可能会被拆分或标记为 [UNK]

5. 进一步分析 Emoji 嵌入

(1) 可视化嵌入空间

可以使用降维工具(如 t-SNE 或 PCA)将 emoji 和其他单词的嵌入向量可视化,观察其分布。

from sklearn.manifold import TSNE
import matplotlib.pyplot as plt

# 获取多个文本的嵌入向量
texts = ["", "", "❤️", "", "", "", ""]
embeddings = [get_embeddings(t) for t in texts]

# 使用 t-SNE 降维
tsne = TSNE(n_components=2, random_state=42)
reduced_embeddings = tsne.fit_transform(embeddings)

# 可视化
plt.figure(figsize=(8, 6))
for i, label in enumerate(texts):
    plt.scatter(reduced_embeddings[i, 0], reduced_embeddings[i, 1])
    plt.annotate(label, (reduced_embeddings[i, 0], reduced_embeddings[i, 1]))
plt.title("Emoji 嵌入空间分布")
plt.show()
(2) 聚类分析

可以对 emoji 嵌入向量进行聚类,观察相似语义的 emoji 是否被分为一组。

from transformers import BertForSequenceClassification, Trainer, TrainingArguments

# 模型加载
model = BertForSequenceClassification.from_pretrained("bert-base-multilingual-cased", num_labels=2)

# 示例输入
text = "这个视频笑死我了太搞笑了!"
inputs = tokenizer(text, return_tensors="pt", padding=True, truncation=True)

# 模型预测
outputs = model(**inputs)
logits = outputs.logits
predicted_label = logits.argmax().item()
print("预测类别:", "正面" if predicted_label == 1 else "负面")

你可能感兴趣的:(机器学习,人工智能,bert,transformer)