作业简述
本次作業為 Twitter 上收集到的推文,每則推文都會被標注為正面或負面
数据集:
labeled training data :20萬
unlabeled training data :120萬
testing data :20萬(10 萬 public,10 萬 private)
训练数据:
基本网络模型:
- 利用 Word Embedding 來代表每一個單字,(用一些方法 pretrain 出 word embedding (e.g., skip-gram, CBOW. )
) - 並藉由 RNN model 得到一個代表該句的 vector。
优化思想:
- semi-supervised 簡單來說就是讓機器利用 unlabeled data ,而方法有很多種,這邊簡單介紹其中一種比較好實作的方法 Self-Training
Self-Training: - 把 train 好的 model 對 unlabel data 做預測,並將這些預測後的值轉成該筆 unlabel data 的 label,並加入這些新的 data 做 training。 你可以調整不同的 threshold,或是多次取樣來得到比較有信心的 data。
- e.g., 設定 pos_threshold = 0.8,只有 prediction > 0.8 的 data 會被標上 1
汇报 (完成下面会收获很多)
- (1%) 請說明你實作的 RNN 的模型架構、word embedding 方法、訓練過程 (learning curve) 和準確率為何? (盡量是過 public strong baseline 的 model)
- (2%) 請比較 BOW + DNN 與 RNN 兩種不同 model 對於 “today is a good day, but it is hot” 與 “today is hot, but it is a good day” 這兩句的分數 (過 softmax 後的數值),並討論造成差異的原因。
- (1%) 請敘述你如何 improve performance(preprocess、embedding、架構等等),並解釋為何這些做法可以使模型進步,並列出準確率與 improve 前的差異。(semi-supervised 的部分請在下題回答)
- (2%) 請描述你的semi-supervised方法是如何標記label,並比較有無semi-supervised training對準確率的影響並試著探討原因(因為 semi-supervise learning 在 labeled training data 數量較少時,比較能夠發揮作用,所以在實作本題時,建議把有 label 的training data從 20 萬筆減少到 2 萬筆以下,在這樣的實驗設定下,比較容易觀察到semi-supervise learning所帶來的幫助)
我的调优过程
step 1
1) 根据这个篇对从数据处理出发:https://www.cnblogs.com/dogecheng/p/11565530.html 去除标点符号和停用词
因为标点符号也有感情色彩,所以我保留了感叹号!和问号?
发现英文的停用词太多了,所以没有使用。
1.5)可以shuffle数据集
2)我发现了数据集存在严重的问题
存在单引号’的地方,如can ’ t,单引号和英文存在空格。(应该是can’t),如果按空格处理会出现这样的情况:‘can’ , ’ ’ ’ ,‘t’,即被认为是3个单词。
所以用txt的替换功能,将单引号与英文不留空格
结果
数据处理并没什么用,我感觉是推特的评论不好区别,而且有很多奇怪的单词。即数据集虽多但质量不高。
step2
源代码的Word2Vec使用了无标签数据集导致词表过大,所以我后面改了只用train data和test data。
但其实后面人家改进的方法就是把无标签数据集加道Word2Vec里面使用。
我还多此一举,不过准确率没多大区别,稍微改改网络结构还是差不多的。
所以我又一次认为是数据集的问题。(因为分析文本情感的有很多例子和数据集,他们的准确率不止于80%-81%之间)
step3
修改网络结构:
可以加全连接,修改dropout,单向RNN变双向RNN,增加RNN层数,修改Embedding的特征维度,RNN的隐藏层维度
总结
1)训练10个epoch 测试集没提升,训练集可以到acc到90%——>但过拟合 (训练集数量为19万和18万没区别)
3)网络(加多一个全连接或者变bidirection RNN),测试集没有提升——>数据的问题
结束
我一直修改,最优也只是81.多。真让人沮丧。
不过可得出,其实修改网络结构对RNN模型的任务没多大的改进,不像cv,你用VGG和ResNet区别就大了。
然后我就尝试找博客看别人怎么做的,果真有一个很厉害:
链接1:https://blog.csdn.net/iteapoy/article/details/105931612
链接2:这篇结果没什么改进,但他尝试用了transformer的QKV,勇气可嘉,但结果没如他所愿。
https://www.cnblogs.com/zyb993963526/p/13784199.html
我觉得可先自己改进模型,再看别人的博客,会对任务更加深刻。在借鉴别人的方法。
这个hw4确实要看很久,因为它算是自然语言处理的入门,你要了解很多知识及技术,不过学习就是慢慢来,一步一个脚印。
下面主要分析链接1他用的注意力机制
⭐ 李宏毅2020机器学习作业4-RNN:句子情感分类
另外有3篇博客,小任务,我觉得可以看看。我是一上来就做hw4的。
修改1:加入无标记的数据
修改2:self-training
修改3:去除标点符号 + Bi-LSTM+ Attention
对于修改3,我也不知道他这样做的原理是什么,有什么科学或案例支撑。为什么这样就完成注意力机制,可能是我对注意力机制还不够了解。
简单介绍
网络模型:
self.lstm = nn.LSTM(embedding_dim, hidden_dim, num_layers=num_layers, batch_first=True)
self.classifier = nn.Sequential( nn.Dropout(dropout),
nn.Linear(hidden_dim, 64),
nn.Dropout(dropout),
nn.Linear(64, 1),
nn.Sigmoid() )
使用sk-learn分训练集和验证集,还是使用DataLoader
#把 data 分为 training data 和 validation data(将一部分 training data 作为 validation data)
X_train, X_val, y_train, y_val = train_test_split(
train_x, y, test_size = 0.1, random_state = 1, stratify = y)
区别在于,你是在main函数生成dataloader,把dataloader作为training函数的参数。还是把x_train、x_val作为training函数参数,再函数里面生成dataloader。
定义Dataset
- 在 Pytorch 中,我们可以利用 torch.utils.data 的 Dataset 及 DataLoader 来"包装" data,使后续的 training 和 testing 更方便。
- Dataset 需要 overload 两个函数:len 及 getitem
- len 必须要传回 dataset 的大小
- getitem 则定义了当函数利用 [ ] 取值时,dataset 应该要怎么传回数据。
- 实际上,在我们的代码中并不会直接使用到这两个函数,但是当 DataLoader 在 enumerate Dataset 时会使用到,如果没有这样做,程序运行阶段会报错。
修改2,通过模型对无标签数据打标,然后作为新的数据集参与训练。
打标:
id = … 可以使用jupyter notebook举例,id是怎么来的,内容是什么。
>>> import numpy as np
>>> a = np.array([1,4,5,2,3,6],dtype=int32)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'int32' is not defined
>>> a = np.array([1,4,5,2,3,6],dtype=int)
>>> id = a > 3
>>> print(id)
[False True True False False True]
def add_label(outputs, threshold=0.9):
id = (outputs>=threshold) | (outputs<1-threshold)
outputs[outputs>=threshold] = 1 # 大于等于 threshold 为正面
outputs[outputs<1-threshold] = 0 # 小于 threshold 为负面
return outputs.long(), id
training函数for循环epoch里面
model.eval() # 将 model 的模式设为 eval,这样 model 的参数就会被固定住
# self-training
if epoch >= 4 :
train_no_label_dataset = TwitterDataset(X=train_x_no_label, y=None)
train_no_label_loader = DataLoader(train_no_label_dataset, batch_size = batch_size, shuffle = False, num_workers = 0)
train_x_no_label_tmp = torch.Tensor([[]])
with torch.no_grad():
for i, (inputs) in enumerate(train_no_label_loader):
inputs = inputs.to(device, dtype=torch.long) # 因为 device 为 "cuda",将 inputs 转成 torch.cuda.LongTensor
outputs = model(inputs) # 模型输入Input,输出output
outputs = outputs.squeeze() # 去掉最外面的 dimension,好让 outputs 可以丢进 loss()
labels, id = add_label(outputs)
# 加入新标注的数据
X_train = torch.cat((X_train.to(device), inputs[id]), dim=0)
y_train = torch.cat((y_train.to(device), labels[id]), dim=0)
if i == 0:
train_x_no_label = inputs[~id]
else:
train_x_no_label = torch.cat((train_x_no_label.to(device), inputs[~id]), dim=0)
修改3,去除标点符号 + Bi-LSTM+ Attention
标点我自己处理了,还是得训练一下代码能力。每个人的风格不一样,重点是你得知道数据处理成什么样子。
下面才是文章研究重点:
class Atten_BiLSTM(nn.Module):
def __init__(self, embedding, embedding_dim, hidden_dim, num_layers, dropout=0.5, fix_embedding=True):
super(Atten_BiLSTM, self).__init__()
# embedding layer
self.embedding = torch.nn.Embedding(embedding.size(0), embedding.size(1))
self.embedding.weight = torch.nn.Parameter(embedding)
# 是否将 embedding 固定住,如果 fix_embedding 为 False,在训练过程中,embedding 也会跟着被训练
self.embedding.weight.requires_grad = False if fix_embedding else True
self.embedding_dim = embedding.size(1)
self.hidden_dim = hidden_dim
self.num_layers = num_layers
self.dropout = nn.Dropout(dropout)
self.lstm = nn.LSTM(embedding_dim, hidden_dim, num_layers=num_layers, batch_first=True, bidirectional=True)
self.classifier = nn.Sequential(nn.Dropout(dropout),
nn.Linear(hidden_dim, 64),
nn.Dropout(dropout),
nn.Linear(64, 32),
nn.Dropout(dropout),
nn.Linear(32, 16),
nn.Dropout(dropout),
nn.Linear(16, 1),
nn.Sigmoid())
self.attention_layer = nn.Sequential(
nn.Linear(hidden_dim, hidden_dim),
nn.ReLU()
)
def attention(self, output, hidden):
# output (batch_size, seq_len, hidden_size * num_direction)
# hidden (batch_size, num_layers * num_direction, hidden_size)
output = output[:,:,:self.hidden_dim] + output[:,:,self.hidden_dim:] # (batch_size, seq_len, hidden_size)
hidden = torch.sum(hidden, dim=1)
hidden = hidden.unsqueeze(1) # (batch_size, 1, hidden_size)
atten_w = self.attention_layer(hidden) # (batch_size, 1, hidden_size)
m = nn.Tanh()(output) # (batch_size, seq_len, hidden_size)
atten_context = torch.bmm(atten_w, m.transpose(1, 2))
softmax_w = F.softmax(atten_context, dim=-1)
context = torch.bmm(softmax_w, output)
return context.squeeze(1)
def forward(self, inputs):
inputs = self.embedding(inputs)
# x (batch, seq_len, hidden_size)
# hidden (num_layers *num_direction, batch_size, hidden_size)
x, (hidden, _) = self.lstm(inputs, None)
hidden = hidden.permute(1, 0, 2) # (batch_size, num_layers *num_direction, hidden_size)
# atten_out [batch_size, 1, hidden_dim]
atten_out = self.attention(x, hidden)
return self.classifier(atten_out)
-
self.classifier(atten_out)的atten_out,要求【batch,hidden_dim】
-
因为是双向lstm:所以两个lstm的output对应元素相加
output = output[:,:,:self.hidden_dim] + output[:,:,self.hidden_dim:] -
#(batch_size, num_layers * num_direction, hidden_size)
hidden = torch.sum(hidden, dim=1)
每一层的隐藏状态对应元素相加——(疑问)
hidden = hidden.unsqueeze(1) # (batch_size, 1, hidden_size)
atten_w = self.attention_layer(hidden)
#(batch_size, 1, hidden_size)
这里应该错了吧!这个维度怎么输进去全连接
https://pytorch.org/docs/master/generated/torch.nn.Linear.html#torch.nn.Linear
self.attention_layer = nn.Sequential(
nn.Linear(hidden_dim, hidden_dim),
nn.ReLU()
)
-
先忽略这个继续往下:
m = nn.Tanh()(output) # (batch_size, seq_len, hidden_size)
atten_context = torch.bmm(atten_w, m.transpose(1, 2))
#【batch_size, 1, hidden_size】* 【batch_size, hidden_size, seq_len】——> 【batch_size,1,seq_len】
bmm函数忽略batch_size这一维 -
softmax_w = F.softmax(atten_context, dim=-1)
#【batch_size,1,seq_len】 -
context = torch.bmm(softmax_w, output)
#【batch_size,1,seq_len】* 【batch_size, seq_len, hidden_size】——> 【batch_size, 1, hidden_size】 -
context.squeeze(1) # 【batch_size, hidden_size】
注意力机制体现在
atten_context :【batch_size,1,seq_len】提取每个词信息
softmax_w :【batch_size,1,seq_len】对每个词的分配权值
https://github.com/luzijian9/LHY-hw4/tree/master
reference:https://blog.csdn.net/iteapoy/article/details/105931612