Task04:基于深度学习的文本分类1

学习目标

  • 学习FastText的使用和基础原理
  • 学会使用验证集进行调参

深度学习可以用于文本表示,可以将词典其映射到一个低纬空间。其中比较典型的例子有:FastText、Word2Vec和Bert。在本章我们将介绍FastText,将在后面的内容介绍Word2Vec和Bert。

FastText

FastText是一种典型的深度学习词向量的表示方法,它非常简单通过Embedding层将单词映射到稠密空间,然后将句子中所有的单词在Embedding空间中进行平均,进而完成分类操作。

FastText是一个三层的神经网络,输入层、隐含层和输出层。在论文中,除了描述了网络的基础结构以外,FastText还用到了ngram和分层softmax的技巧,可以提高精度和加快运算速度。

FastText中还提到了subword,他讲英文中word也拆成不同的subword进行考虑,因为英文中的前缀和后缀都是有意义的,但是在中文中只能作为以字为单位进行考虑了。

pytorch版的代码:

代码已经上传至Github

我们通过Embedding层将单词映射到稠密空间,
tensor的维度是(batchsize,length_of_sentence,embedding_dim),再经过平均操作之后,
tensor的维度变为(batchsize,embedding_dim),因为我们是对句子中的所有单词做平均,这时候的向量或者下一层的向量可以表示整个句子,我们可以用这些向量来做句子相似度的实验。
输出层则是分层softmax,tensor的维度变为(batchsize,class_num)

我们针对这次的匿名数据集,使用torchtext进行数据的预处理,构造了迭代器,注意里的'./data/train_torch.csv'是被我重新保存过的csv,第一列是text,第二列是label

class MyDataset:
    def __init__(self, train_path='./data/train_torch.csv', test_path='./data/test_a.csv', fix_length=600):
        self.train_path = train_path
        self.test_path = test_path
        self.fix_length = fix_length

    def get_data_by_torchtext(self):
        print("读取数据,需要花挺长时间")
        def x_tokenize(x):
            return [w for w in x.split()]

        def y_tokenize(y):
            return int(y)

        TEXT = Field(sequential=True, tokenize=x_tokenize,
                     fix_length=self.fix_length, use_vocab=True,
                     init_token=None, eos_token=None,
                     include_lengths=True, batch_first=True)

        LABEL = Field(sequential=False, tokenize=y_tokenize,
                      use_vocab=False, is_target=True)

        fields_train = [('text', TEXT), ('label', LABEL)]
        fields_test = [('text', TEXT)]

        train = TabularDataset(
            path=self.train_path, format='csv',
            skip_header=True, fields=fields_train
        )
        test = TabularDataset(
            path=self.test_path, format='csv',
            skip_header=True, fields=fields_test
        )

        TEXT.build_vocab(train)
        return TEXT.vocab, train, test

    def split(self, train, split_ratio=0.9):
        train_dataset, valid_dataset = train.split(split_ratio, stratified=True)
        return train_dataset, valid_dataset

    def get_iter(self, train, valid, test, train_batch=64, valid_batch=64, test_batch=128):
        # 构造迭代器
        train_iter, valid_iter = BucketIterator.splits(
            (train, valid),
            batch_sizes=(train_batch, valid_batch),
            device=torch.device("cuda"),
            sort_key=lambda x: len(x.text),
            sort_within_batch=True
        )

        test_iter = Iterator(test, batch_size=test_batch, device=torch.device("cuda"), sort=False, repeat=False,
                             sort_within_batch=False,shuffle=False)
        return train_iter, valid_iter, test_iter

我们搭建了一个最简单的FastText的模型,但它不包含ngram和分层softmax。

class Fasttext(nn.Module):
    def __init__(self, vocab, embedding_dim=300,hidden_dim=64,out_dim=14):
        super(Fasttext, self).__init__()
        
        self.num_vocab=len(vocab)
        self.embedding_dim=embedding_dim
        self.hidden_dim=hidden_dim
        self.out_dim=out_dim

        self.embed = nn.Embedding(num_embeddings=self.num_vocab,embedding_dim=self.embedding_dim)
        self.embed.weight.requires_grad = True 
        
        self.pred = nn.Sequential(            
            nn.Linear(self.embedding_dim, self.hidden_dim),
            nn.Dropout(0.5),
            nn.BatchNorm1d(self.hidden_dim),
            nn.ReLU(inplace=True),
            nn.Linear(self.hidden_dim, self.out_dim),   
#             nn.ReLU(inplace=True),
        )
        
        nn.init.xavier_uniform_(self.embed.weight)
        self.xavier(self.pred)

    def xavier(self,layers):
        for index,net in enumerate(layers):
            if index==0:
                nn.init.xavier_uniform_(net.weight)
    
    def forward(self, x):
        x = self.embed(x)
        out = self.pred(torch.mean(x, dim=1))
        return out

这里所谓的句子向量即为 embedding 层之后的torch.mean(x, dim=1)
我们还构造了训练,验证,预测的整个迭代过程。

class TrainFunc():
    def __init__(self, model, train_iter=None, valid_iter=None, test_iter=None):
        self.model = model
        self.best_model = model
        self.best_score = 0
        self.train_iter = train_iter
        self.valid_iter = valid_iter
        self.test_iter = test_iter

    def train(self, epoch):
        self.model.train()

        for i in range(epoch):
            train_acc = 0
            train_loss = 0
            for batch_idx, batch in enumerate(iter(self.train_iter)):
                data, label = batch.text[0], batch.label
                batchsize = data.shape[0]
                output = self.model(data)
                opt.zero_grad()
                loss = criterion(output, label)
                loss.backward()
                opt.step()
                train_loss += loss.item()
                train_acc += (output.argmax(1) == label).sum().item()
                if batch_idx % int(200 * (64 / batchsize)) == 0:
                    print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
                        i + 1, batch_idx * len(data), len(self.train_iter.dataset),
                        100. * batch_idx / len(self.train_iter), loss.item()))
            #             assert False
            print(
                f'\tLoss: {train_loss / len(self.train_iter):.4f}(train)\t|\tAcc: {train_acc / len(self.train_iter.dataset) * 100:.1f}%(train)')

            score = self.valid_func()
            if score > self.best_score:
                self.best_score = score
                self.best_model = deepcopy(self.model)
                print("Now_best:{:.4f}".format(self.best_score))
        #         scheduler.step()
        return self.best_model

    def valid_func(self):
        valid_acc = 0
        valid_loss = 0
        ans_box = []
        label_box = []

        self.model.eval()
        with torch.no_grad():
            for batch_idx, batch in enumerate(iter(self.valid_iter)):
                data, label = batch.text[0], batch.label

                output = self.model(data)
                pred = output.argmax(1)

                loss = criterion(output, label)

                ans_box.extend(pred.cpu().tolist())
                label_box.extend(label.cpu().tolist())
                valid_loss += loss.item()
                valid_acc += (pred == label).sum().item()

            score1 = f1_score(ans_box, label_box, average='macro')
            score2 = f1_score(ans_box, label_box, average='micro')
            print(
                f'\tLoss: {valid_loss / len(self.valid_iter):.4f}(valid)\t|\tAcc: {valid_acc / len(self.valid_iter.dataset) * 100:.1f}%(valid)')
            print(f'\tMicro: {score2:.4f}(valid)\t|\tMacro: {score1:.4f}(valid)')

        self.model.train()
        return score1

    def predict(self):
        self.best_model.eval()
        ans_box = []
        with torch.no_grad():
            for batch_idx, batch in enumerate(iter(self.test_iter)):
                data = batch.text[0]
                
                output = self.best_model(data)
                pred = output.argmax(1)
                
                ans_box.extend(pred.cpu().tolist())
        return ans_box

我们给出一个参考的跑程序过程:

# 构造迭代器
my_data=MyDataset(train_path='./data/train_torch.csv',test_path='./data/test_a.csv',fix_length=600)
vocab,train,test=my_data.get_data_by_torchtext()
train_dataset, valid_dataset=my_data.split(train)
train_iter, valid_iter,test_iter=my_data.get_iter(train_dataset, valid_dataset,test,train_batch=64)
# 构造模型的参数
model=Fasttext(vocab)
model=model.cuda()
criterion = nn.NLLLoss()
lr = 1e-4
opt = torch.optim.Adam(model.parameters(), lr)
# 开始训练,使用验证集获得做好的参数,并进行预测
mytrain=TrainFunc(model,train_iter, valid_iter,test_iter)
best_model=mytrain.train(15)
ans=mytrain.predict()  # 因为没有打乱过,所以这个ans是按顺序的。
df_sub=pd.read_csv(data_root["sub_path"])
df_sub.label=np.array(ans)
df_sub.to_csv('./fasttext_submission.csv', index=False)
官方开源的FastTex使用:

FastText可以快速的在CPU上进行训练,最好的实践方法就是官方开源的版本。
既然我们自己的实验搭建那么多代码,那我们尝试一下官方的FastText版本的愉快版本吧!

其实操作起来非常简单,分为两步,保存成为需要的格式,进行训练得到模型即可。

# 保存格式
train_df = pd.read_csv(data_root["train_path"])
train_df['label_ft'] = '__label__' + train_df['label'].astype(str)
train_df[['text','label_ft']].iloc[:200000].to_csv('./data/train_for_fast.csv', index=None, header=None, sep='\t')
# 训练
model = fasttext.train_supervised(data_root["fast_path"],lr=1.0, 
			dim=300,wordNgrams=2, minn=4, 
			minCount=2, epoch=25, loss="hs",thread=16)
# 预测
val_pred = [model.predict(x)[0][0].split('__')[-1] for x in test_df.text.values]

关于train_supervised中的参数可以参考官方文档的说明

这里给出我定义的参数的意义:

  • input: 训练数据文件路径
  • lr: 学习率
  • dim: 字向量维度
  • minn: 构造subword时最小char个数
  • epoch: 迭代次数
  • loss: 损失函数类型, softmax, ns: 负采样, hs: 分层softmax
  • minCountLabel: 类别阈值,类别小于该值初始化时会过滤掉

返回的是一个分类器,可以通过下面这些方法或者属性来进行查看你需要的东西:

  • model.words
  • model.labels
  • model[‘king’]
  • model.predict ()

本章作业

1.阅读FastText的文档,尝试修改参数,得到更好的分数

使用官方开源的FastText程序,在线上获得0.917的成绩。

2.基于验证集的结果调整超参数,使得模型性能更优

使用自己编写的FastText程序(pytorch),使用验证集进行调参,在线上获得了0.904的成绩。
于7月28日更改了损失函数,logsoftmax 之后应该接 NLLLoss(原来的CrossEntropy 我思考之后应该是错的),但是再跑一遍提交就算了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值