数据处理目标
验证代码
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
的 spacy-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]']
smiling
、_
、face
),导致其向量表示不再对应单个 emoji,而是描述字符串的组合。你可以扩展 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"]))
总结
emoji
库将 emoji 转换为描述字符串。spacy-emoji
能直接识别 emoji,并将其作为独立的 Token。
BERT 对描述字符串的分词:默认情况下,描述字符串会被分词,破坏 emoji 的语义一致性。
解决方案:保留原始 emoji,不将其转换为描述字符串。为 emoji 定义自定义 Token,确保其生成独立的向量。
许多分词工具默认会忽略或拆分emoji,因此需要选择支持emoji的分词器:
BertTokenizer
或emoji
库配合分词工具。示例代码:
from transformers import BertTokenizer
# 加载支持emoji的分词器
tokenizer = BertTokenizer.from_pretrained("bert-base-multilingual-cased")
# 示例文本,含emoji
text = "这个视频笑死我了 太搞笑了!"
# 分词
tokens = tokenizer.tokenize(text)
print("分词结果:", tokens)
输出示例:
['这', '个', '视频', '笑', '死', '我', '了', '', '', '太', '搞', '笑', '了', '!']
对于不支持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)
输出示例:
['这个视频笑死我了', '', '', '太搞笑了!']
为了兼容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)
输出示例:
['这', '个', '视频', '笑', '死', '我', '了', '', '', '太', '搞', '笑', '了', '!']
词汇表(Vocabulary):
[UNK]
(未知符号)。嵌入空间:
语义关系:
以下代码展示了如何加载 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)
和
在语义上是相似的,你会看到较高的相似度值。
和 ❤️
语义不同,相似度会较低。可以通过以下代码查看 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]
。可以使用降维工具(如 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()
可以对 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 "负面")