Kev*_*vin 5 python transformer-model bert-language-model huggingface-transformers
背景:
遵循这个问题,当使用 bert 对序列进行分类时,模型使用表示分类任务的“[CLS]”标记。据该论文称:
每个序列的第一个标记始终是一个特殊的分类标记([CLS])。与该标记对应的最终隐藏状态用作分类任务的聚合序列表示。
查看 Huggingfaces 存储库,他们的 BertForSequenceClassification 使用 bert pooler 方法:
class BertPooler(nn.Module):
def __init__(self, config):
super().__init__()
self.dense = nn.Linear(config.hidden_size, config.hidden_size)
self.activation = nn.Tanh()
def forward(self, hidden_states):
# We "pool" the model by simply taking the hidden state corresponding
# to the first token.
first_token_tensor = hidden_states[:, 0]
pooled_output = self.dense(first_token_tensor)
pooled_output = self.activation(pooled_output)
return pooled_output
Run Code Online (Sandbox Code Playgroud)
我们可以看到他们采用第一个标记(CLS)并将其用作整个句子的表示。具体来说,他们执行的操作hidden_states[:, 0]看起来很像从每个状态中获取第一个元素,而不是获取第一个标记隐藏状态?
我的问题:
我不明白的是他们如何将整个句子的信息编码到这个标记中?CLS 标记是一个常规标记,它有自己的嵌入向量来“学习”句子级别表示吗?为什么我们不能只使用隐藏状态的平均值(编码器的输出)并用它来分类?
编辑:经过一番思考:因为我们使用 CLS 令牌隐藏状态来预测,所以 CLS 令牌嵌入是否正在接受分类任务的训练,因为这是用于分类的令牌(因此是导致错误的主要因素)哪个会传播到它的权重?)
CLS 标记是一个常规标记,它有自己的嵌入向量来“学习”句子级别表示吗?
是的:
from transformers import BertTokenizer, BertModel
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained('bert-base-uncased')
clsToken = tokenizer.convert_tokens_to_ids('[CLS]')
print(clsToken)
#or
print(tokenizer.cls_token, tokenizer.cls_token_id)
print(model.get_input_embeddings()(torch.tensor(clsToken)))
Run Code Online (Sandbox Code Playgroud)
输出:
101
[CLS] 101
tensor([ 1.3630e-02, -2.6490e-02, -2.3503e-02, -7.7876e-03, 8.5892e-03,
-7.6645e-03, -9.8808e-03, 6.0184e-03, 4.6921e-03, -3.0984e-02,
1.8883e-02, -6.0093e-03, -1.6652e-02, 1.1684e-02, -3.6245e-02,
...
5.4162e-03, -3.0037e-02, 8.6773e-03, -1.7942e-03, 6.6826e-03,
-1.1929e-02, -1.4076e-02, 1.6709e-02, 1.6860e-03, -3.3842e-03,
8.6805e-03, 7.1340e-03, 1.5147e-02], grad_fn=<EmbeddingBackward>)
Run Code Online (Sandbox Code Playgroud)
您可以通过以下方式获取您的模型的所有其他特殊令牌的列表:
101
[CLS] 101
tensor([ 1.3630e-02, -2.6490e-02, -2.3503e-02, -7.7876e-03, 8.5892e-03,
-7.6645e-03, -9.8808e-03, 6.0184e-03, 4.6921e-03, -3.0984e-02,
1.8883e-02, -6.0093e-03, -1.6652e-02, 1.1684e-02, -3.6245e-02,
...
5.4162e-03, -3.0037e-02, 8.6773e-03, -1.7942e-03, 6.6826e-03,
-1.1929e-02, -1.4076e-02, 1.6709e-02, 1.6860e-03, -3.3842e-03,
8.6805e-03, 7.1340e-03, 1.5147e-02], grad_fn=<EmbeddingBackward>)
Run Code Online (Sandbox Code Playgroud)
输出:
['[CLS]', '[UNK]', '[PAD]', '[SEP]', '[MASK]']
Run Code Online (Sandbox Code Playgroud)
我不明白的是他们如何将整个句子的信息编码到这个标记中?
和
因为我们使用 CLS 令牌隐藏状态来预测,所以 CLS 令牌嵌入是否正在接受分类任务的训练,因为这是用于分类的令牌(因此是传播到其权重的误差的主要贡献者?)
也是的。正如您在问题中已经指出的那样,BertForSequenceClassification利用BertPooler来训练 Bert 之上的线性层:
print(tokenizer.all_special_tokens)
Run Code Online (Sandbox Code Playgroud)
为什么我们不能只使用隐藏状态的平均值(编码器的输出)并用它来分类?
我一般无法真正回答这个问题,但为什么你认为作为线性层会更容易或更好?您还需要训练隐藏层以生成平均值映射到您的类别的输出。因此,你还需要一个“平均层”作为你损失的主要贡献者。一般来说,当你可以证明它比当前的方法能带来更好的结果时,没有人会拒绝它。