李宏毅hw4,训练+调优

作业简述

本次作業為 Twitter 上收集到的推文,每則推文都會被標注為正面或負面

数据集:
labeled training data :20萬
unlabeled training data :120萬
testing data :20萬(10 萬 public,10 萬 private)

训练数据:
在这里插入图片描述
基本网络模型:

  1. 利用 Word Embedding 來代表每一個單字,(用一些方法 pretrain 出 word embedding (e.g., skip-gram, CBOW. )
  2. 並藉由 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 两个函数:lengetitem
  • 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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值