本文为学习Datawhale 2021.9组队学习情感分析笔记
原学习文档地址:https://github.com/datawhalechina/team-learning-nlp/tree/master/EmotionalAnalysis
baseline笔记见https://blog.csdn.net/weixin_43634785/article/details/120289701?spm=1001.2014.3001.5502
主要写需要注意的与baseline的不同点。
详细情况见原学习文档task3
fasttext不是一个时序模型,不需要include_lengths=True了,但是它与其他文本分类模型的一个不同之处在于其计算了输入句子的n-gram,所以需要对数据进行生成n-grams的操作并读入这部分数据
下面是生成bigrams的函数
def generate_bigrams(x):
n_grams = set(zip(*[x[i:] for i in range(2)]))
for n_gram in n_grams:
x.append(' '.join(n_gram))
return x
分步查看得到
x = ['This', 'film', 'is', 'terrible']
print([x[i:] for i in range(2)])
y = [['This', 'film', 'is', 'terrible'], ['film', 'is', 'terrible']]
print(*y)
# 列表前面加个*,就是列表中所有元素解开成独立的参数,效果相当于去了一个[]
print(zip(*[x[i:] for i in range(2)]))
# 外面再加个zip就是把两个列表对应位置的元素组合起来,数量按少的那个算
print(*zip(*[x[i:] for i in range(2)]))
# 外面再加个*就是解压出来
print(set(zip(*[x[i:] for i in range(2)])))
# 利用set创建集合并去除重复元素
输出
[['This', 'film', 'is', 'terrible'], ['film', 'is', 'terrible']]
['This', 'film', 'is', 'terrible'] ['film', 'is', 'terrible']
<zip object at 0x0000024962F77088>
('This', 'film') ('film', 'is') ('is', 'terrible')
{('film', 'is'), ('is', 'terrible'), ('This', 'film')}
例子
generate_bigrams(['This', 'film', 'is', 'terrible'])
# ['This', 'film', 'is', 'terrible', 'film is', 'is terrible', 'This film']
加载数据时不需要include_lengths了,但需要preprocessing把bigrams加进去
TEXT = data.Field(tokenize = 'spacy',
tokenizer_language = 'en_core_web_sm',
preprocessing = generate_bigrams)
MAX_VOCAB_SIZE = 25_000
TEXT.build_vocab(train_data,
max_size = MAX_VOCAB_SIZE,
vectors = "glove.6B.100d",
unk_init = torch.Tensor.normal_)
LABEL.build_vocab(train_data)
pretrained_embeddings = TEXT.vocab.vectors
# 用预训练的embedding词向量替换原始模型初始化的权重参数
model.embedding.weight.data.copy_(pretrained_embeddings)
# 将unk和padding token 设置为0
UNK_IDX = TEXT.vocab.stoi[TEXT.unk_token]
# 注意:与初始化嵌入一样,这应该在“weight.data”而不是“weight”上完成!
model.embedding.weight.data[UNK_IDX] = torch.zeros(EMBEDDING_DIM)
model.embedding.weight.data[PAD_IDX] = torch.zeros(EMBEDDING_DIM)
同样地,使用了glove预训练词向量
class FastText(nn.Module):
def __init__(self, vocab_size, embedding_dim, output_dim, pad_idx):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx=pad_idx)
self.fc = nn.Linear(embedding_dim, output_dim)
def forward(self, text):
# text = [sent len, batch size]
embedded = self.embedding(text)
# embedded = [sent len, batch size, emb dim]
embedded = embedded.permute(1, 0, 2)
# embedded = [batch size, sent len, emb dim]
# avg_pool2d input的要求是batch放第一位
pooled = F.avg_pool2d(embedded, (embedded.shape[1], 1)).squeeze(1)
# pooled = [batch size, emb dim] !!
# 相当于将一个sent len长度句子的表示 [batch size, sent len, emb dim] 压缩(取平均值)成了 [batch size, emb dim]
return self.fc(pooled)
结构很简单,输入的id先通过embedding得到一个句子里每一个单词的表示,然后再对所有单词的向量取个平均值,再加个线性层,就得到了输出。
复习一下text经过embedding的维度变化
以batch_size = 64, 输入的句子长度sent_len = 60. 词典大小vocab_size = 25002, 经过embedding后的输出维度emb_dim = 100为例
embedding: [25002, 100]
text: [60, 64]
输出embedded维度: [60, 64, 100]
相当于 [64, 25002] * [25002, 100] - > [64, 100]
再解释一下padding_idx的作用
self.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx=pad_idx)
参考:https://blog.csdn.net/qq_42961603/article/details/119851180
其实作用就是在embedding矩阵里面加入了一条pad对应的词条,如果不需要加pad,就不需要设置这个参数。
举链接中的那例子,
embedding4 = nn.Embedding(10,3,padding_idx=10-1)
embedding4.weight
Parameter containing:
tensor([[-1.4354, 0.8168, 0.4477],
[-0.4925, -0.3006, 0.7584],
[-0.2400, 1.0259, -0.5391],
[-0.5411, 0.9602, 0.1372],
[-0.6848, 0.0278, -0.1112],
[-0.2092, -1.8230, 1.0283],
[ 0.5441, 0.6374, -0.9901],
[ 0.1115, 0.2792, -0.1808],
[-3.7124, -0.6969, -0.6027],
[ 0.0000, 0.0000, 0.0000]], requires_grad=True)
input = torch.tensor([[1,9,9,9],[9,3,9,6]])
embedding4(input)
tensor([[[-0.4925, -0.3006, 0.7584],
[ 0.0000, 0.0000, 0.0000],
[ 0.0000, 0.0000, 0.0000],
[ 0.0000, 0.0000, 0.0000]],
[[ 0.0000, 0.0000, 0.0000],
[-0.5411, 0.9602, 0.1372],
[ 0.0000, 0.0000, 0.0000],
[ 0.5441, 0.6374, -0.9901]]], grad_fn=<EmbeddingBackward>)
[1,9,9,9] 是第一个句子,每个数字代表1个token,输入的形式是一个one-hot向量,tensor的形状是[2, 4] embedding是[10, 3]
embedding(input)就相当于[4, 10] * [10, 3] -> [4, 3]
四行向量都是one-hot向量
FastText与Word2Vec的结构很像,不同之处是fastText预测标签而CBOW预测的是中间词,即模型架构类似但是模型的任务不同。
都可以用预训练的Embedding,加个Embedding层做个转换就行了
FastText的效果不错,训练速度也快。他引入了n-grams特征对效果有提升,在训练的过程中还使用了一些技巧,比如层次softmax。