静态词嵌入场景下BERT子词平均是否需排除CLS与SEP?
问题:BERT静态词嵌入时是否需排除CLS/SEP嵌入?
我尝试以静态词嵌入的方式使用BERT,将其与Word2Vec对比,以此展示两者差异及BERT并不适用于无上下文场景的特性。以下是我的实现代码:
def get_hidden_states(encoded, model, layers): with torch.no_grad(): output = model(**encoded) states = output.hidden_states # Stack final 4 layers output = torch.stack([states[i] for i in layers]).sum(0).squeeze() ## shape torch.Size([5, 768]) return output.mean(dim=0) ##average def get_word_vector(sent, tokenizer, model, layers): encoded = tokenizer.encode_plus(sent, return_tensors="pt") ##{'input_ids': tensor([[ 101, 9712, 4774, 3408, 102]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0]]), 'attention_mask': tensor([[1, 1, 1, 1, 1]])} return get_hidden_states(encoded, model, layers) word = "embeddings" layers = [-4, -3, -2, -1] tokenizer = BertTokenizerFast.from_pretrained("bert-base-cased") model = BertModel.from_pretrained("bert-base-cased", output_hidden_states=True) word_embed = get_word_vector(word, tokenizer, model, layers) ## shape torch.Size([768])
我的核心问题是:在通过平均子词嵌入得到完整词表示时,是否需要排除CLS和SEP嵌入?还是理论上应该包含它们?
必须排除CLS和SEP,原因很直接:
- CLS是专为句子级任务设计的标记,它的编码承载的是整个输入序列的全局信息,和单个目标词的语义毫无关联,把它加入平均会稀释目标词子词嵌入的有效信息。
- SEP只是分隔标记,本身没有实际语义,仅用于区分不同输入片段(比如句子对任务)。在你只输入单个词的场景下,SEP纯粹是为了满足BERT的输入格式要求而添加的,完全不应该参与平均计算。
看你的代码,输入单个词embeddings后,tokenizer会自动加上CLS([101])和SEP([102]),编码后的序列是[CLS, embeddings的子词, SEP]。你当前的代码直接对整个序列的所有token做平均,相当于把两个无关标记的嵌入混了进去,得到的向量根本不是目标词的有效静态表示。
正确的做法是在计算平均前,先提取仅属于目标词的子词标记对应的隐藏状态,也就是去掉第一个(CLS)和最后一个(SEP)的token,再做平均。修改后的代码示例:
def get_hidden_states(encoded, model, layers): with torch.no_grad(): output = model(**encoded) states = output.hidden_states # Stack final 4 layers output = torch.stack([states[i] for i in layers]).sum(0).squeeze() # 剔除CLS和SEP,仅保留目标词的子词状态 target_token_states = output[1:-1] return target_token_states.mean(dim=0)
另外提一句:用BERT做静态嵌入本身就违背了它的设计初衷——BERT是上下文相关模型,这种静态使用方式的效果大概率不如专门的静态嵌入模型(比如Word2Vec),这也正好能支撑你想展示的两者差异及BERT的特性。
内容的提问来源于stack exchange,提问作者Paschalis
相关产品推荐
相关产品推荐

