第N4周:中文文本分类——Pytorch实现

第N4周:中文文本分类——Pytorch实现

码农世界 2024-05-24 前端 71 次浏览 0个评论
  • 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
  • 🍖 原作者:K同学啊 | 接辅导、项目定制

    数据集:train

     一.加载数据

    import torch
    import torch.nn as nn
    import torchvision
    from torchvision import transforms,datasets
    import os,PIL,pathlib,warnings
     
    warnings.filterwarnings("ignore")
     
     
    device = torch.device("cuda"if torch.cuda.is_available() else"cpu")
    device
    #获取数据
    import pandas as pd
    train_data = pd.read_csv('D:/BaiduNetdiskDownload/train.csv',sep='\t',header=None)
    train_data.head()

     

    #构建数据的迭代器
    def coustom_data_iter(texts,labels):
        for x,y in zip(texts,labels):
            yield x,y
            
    train_iter = coustom_data_iter(train_data[0].values[:],train_data[1].values[:])

     二.数据处理

    1.构建词典

    from torchtext.data.utils import get_tokenizer
    from torchtext.vocab import build_vocab_from_iterator
    import jieba 
     
    tokenizer =jieba.lcut
     
    def yield_tokens(data_iter):
        for text,_ in data_iter:
            yield tokenizer(text)  
    vocab = build_vocab_from_iterator(yield_tokens(train_iter),specials=[""])
    vocab.set_default_index(vocab[""])
    vocab(['随便','播放','一','首','歌','我'])
    out:[173, 4, 181, 0, 108, 2]
    text_pipeline = lambda x: vocab(tokenizer(x))
    label_pipeline = lambda x: label_name.index(x)
    label_name =list(set(train_data[1].values[:]))
    print(label_name)
    print(text_pipeline('我想看'))
    print(label_pipeline('HomeAppliance-Control'))

    创建了两个lambda函数,一个用于将文本转换成词汇索引,另一个用于将标签文本转换成它们在label_name列表中的索引。

    除了一般使用def声明的函数外,Python中还支持lambda匿名函数,可以在任何场合替代def函数。

    匿名函数,通常指的是运行时临时创建的,没有显示命名的函数,它允许快速定义简单的函数。

    语法

    lambda arguments :expression

    lambda argument1,argument2...,argumentn : expression using arguments

            lambda 是关键字

      arguments是参数,可以是0个或多个,用逗号分割

      expression是一个表达式,描述了函数的返回值

    lambda关键字用于创建小巧的匿名函数。lambda a, b: a+b 函数返回两个参数的和。Lambda 函数可用于任何需要函数对象的地方。在语法上,匿名函数只能是单个表达式。在语义上,它只是常规函数定义的语法糖。与嵌套函数定义一样,lambda 函数可以引用包含作用域中的变量。

    优点:

      简洁:lambda表达式可以快速定义简单的函数,无需使用def语句。

      匿名:由于lambda表达式没有正式的函数名称,因此它们是匿名的,可以用于需要短生命周期函数的情况。

      轻量级:lambda表达式只包含一个表达式,因此它们占用内存空间较小,适合用于小型任务。

      可嵌套:Lambda表达式可以嵌套在其他函数或代码块中使用,使代码更加紧凑。

    缺点:

      只能包含一个表达式:Lambda表达式只能包含一个表达式,这意味着它们不能包含多个语句或复杂逻辑。

      调试困难:由于Lambda表达式通常很短,因此很难在调试时设置断点和查看执行流程。

      不支持变量定义:Lambda表达式不能定义变量,只能使用已存在的变量。这意味着它们在处理复杂逻辑时可能会受到限制。

      性能问题:Lambda表达式可能在性能方面不如使用def语句定义的函数。由于它们通常是轻量级的,因此可能不会进行优化。

    2.生成数据批次和迭代器

    from torch.utils.data import DataLoader
     
    def collate_batch(batch):
        label_list,text_list,offsets=[],[],[0]
        
        for(_text,_label)in batch:
            label_list.append(label_pipeline(_label))
            
            processed_text= torch.tensor(text_pipeline(_text),dtype=torch.int64)
            text_list.append(processed_text)
            
            offsets.append(processed_text.size(0))
        
        label_list=torch.tensor(label_list,dtype=torch.int64)
        text_list=torch.cat(text_list)
        offsets=torch.tensor(offsets[:-1]).cumsum(dim=0)
        
        return text_list.to(device),label_list.to(device),offsets.to(device)
     
    dataloader=DataLoader(train_iter,
                         batch_size=8,
                         shuffle =False,
                         collate_fn=collate_batch)

    collate_batch函数用于处理数据加载器中的批次。它接收一个批次的数据,处理它,并返回适合模型训练的数据格式。在这个函数内部,它遍历批次中的每个文本和标签对,将标签添加到label_list,将文本通过text_pipeline函数处理后转换为tensor,并添加到text_list。

    三.模型构建

    1.搭建模型

    from torch import nn
     
    class TextClassificationModel(nn.Module):
        
        def __init__(self,vocab_size,embed_dim,num_class):
           super(TextClassificationModel,self).__init__()
            
           self.embedding = nn.EmbeddingBag(vocab_size,
                                            embed_dim,
                                            sparse=False)
            
           self.fc = nn.Linear(embed_dim,num_class)
           self.init_weights()
     
        def init_weights(self):
            initrange = 0.5
            self.embedding.weight.data.uniform_(-initrange,initrange)
            self.fc.weight.data.uniform_(-initrange,initrange)
            self.fc.bias.data.zero_()
     
        def forward(self,text,offsets):
            embedded=self.embedding(text,offsets)
            return self.fc(embedded)

    Embedding 层 (nn.EmbeddingBag):

    通过 nn.EmbeddingBag 定义了一个嵌入层,用于将文本数据嵌入到低维空间中。

    vocab_size 参数指定词典的大小,embed_dim 参数指定嵌入的维度,sparse=False 表示不使用稀疏张量。

    全连接层 (nn.Linear):

    使用 nn.Linear 定义了一个全连接层,将嵌入后的文本表示映射到最终的类别分数。

    输入维度为 embed_dim,输出维度为 num_class(类别的数量)。

    初始化权重 (init_weights):

    在模型初始化时调用,用于初始化 Embedding 层和全连接层的权重和偏置。

    使用均匀分布初始化权重,偏置值被归零。

    前向传播 (forward):

    接受文本数据 text 和对应的偏移量 offsets 作为输入。

    使用 Embedding 层将文本嵌入到低维空间,通过调用 self.embedding(text, offsets) 实现。

    将嵌入后的文本表示传递给全连接层,得到最终的类别分数。

    2.初始化模型

    num_class = len(label_name)
    vocab_size = len(vocab)
    em_size = 64
    model = TextClassificationModel(vocab_size,em_size,num_class).to(device)

    3.定义训练和评估函数

    import time
     
    def train(dataloader):
        model.train()
        total_acc,train_loss,total_count =0,0,0
        log_interval = 50
        start_time =time.time()
        
        for idx,(text,label,offsets) in enumerate(dataloader):
            
            predicted_label = model(text,offsets)
            
            optimizer.zero_grad()
            loss= criterion(predicted_label,label)
            loss.backward()
            torch.nn.utils.clip_grad_norm_(model.parameters(),0.1)
            optimizer.step()
        
            total_acc +=(predicted_label.argmax(1)==label).sum().item()
            train_loss += loss.item()
            total_count += label.size(0)
            
            if idx % log_interval == 0 and idx >0:
                elapsed = time.time() - start_time
                print('| epoch {:1d}|{:4d}/{:4d} batches'
                      '| train_acc {:4.3f} train_loss{:4.5f}'.format(epoch,idx,len(dataloader),
                                                 total_acc/total_count,train_loss/total_count))
                total_acc,train_loss,total_count = 0,0,0
                start_time =time.time()
     
    def evaluate(dataloader):
        model.eval()
        total_acc,train_loss,total_count = 0,0,0
        
        with torch.no_grad():
            for idx, (text,label,offsets) in enumerate(dataloader):
                predicted_label =model(text,offsets)
                
                loss = criterion(predicted_label,label)
                
                total_acc +=(predicted_label.argmax(1) == label).sum().item()
                train_loss +=loss.item()
                total_count +=label.size(0)
                
        return total_acc/total_count,train_loss/total_count

    train 和 evaluate分别用于训练和评估文本分类模型。

    ·训练函数 train 的工作流程如下:将模型设置为训练模式。初始化总准确率、训练损失和总计数变量。记录训练开始的时间。遍历数据加载器,对每个批次:进行预测、清零优化器的梯度、计算损失(使用一个损失函数,例如交叉熵)、反向传播计算梯度、通过梯度裁剪防止梯度爆炸、执行一步优化器更新模型权重、更新总准确率和总损失、每隔一定间隔,打印训练进度和统计信息。

    ·评估函数 evaluate 的工作流程如下:将模型设置为评估模式、初始化总准确率和总损失、不计算梯度(为了节省内存和计算资源)、遍历数据加载器,对每个批次:进行预测、计算损失、

    更新总准确率和总损失、返回整体的准确率和平均损失。

    四、训练模型 

    1.拆分数据集并运行模型

    from torch.utils.data.dataset import random_split
    from torchtext.data.functional import to_map_style_dataset
     
    EPOCHS = 10
    LR = 5
    BATCH_SIZE= 64
     
     
    criterion = torch.nn.CrossEntropyLoss()
    optimizer =torch.optim.SGD(model.parameters(),lr=LR)
    scheduler =torch.optim.lr_scheduler.StepLR(optimizer,1.0,gamma=0.1)
    total_accu =None
     
    train_iter = coustom_data_iter(train_data[0].values[:],train_data[1].values[:])
    train_dataset = to_map_style_dataset(train_iter)
     
     
    split_train_,split_valid_=random_split(train_dataset,
                                          [int(len(train_dataset)*0.8),int(len(train_dataset)*0.2)])
     
    train_dataloader =DataLoader(split_train_,batch_size=BATCH_SIZE,
                                 shuffle=True,collate_fn=collate_batch)
    valid_dataloader = DataLoader(split_valid_,batch_size=BATCH_SIZE,
                                  shuffle=True,collate_fn=collate_batch)
    for epoch in range(1,EPOCHS + 1):
        epoch_start_time =time.time()
        train(train_dataloader)
        val_acc,val_loss = evaluate(valid_dataloader)
        
        lr= optimizer.state_dict()['param_groups'][0]['lr']
        
        if total_accu is not None and total_accu > val_acc:
            scheduler.step()
        else:
            total_accu = val_acc
        print('-'* 69)
        print('|epoch {:1d} |time: {:4.2f}s | '
              'valid_acc{:4.3f} valid_loss{:4.3f} | lr{:4.6f}'.format(epoch,
                                           time.time()- epoch_start_time,
                                           val_acc,val_loss,lr))
        print('-'* 69)

    2.测试数据

    def predict(text,text_pipeline):
        with torch.no_grad():
            text = torch.tensor(text_pipeline(text))
            output =model(text,torch.tensor([0]))
            return output.argmax(1).item()
     
    ex_text_str="还有双鸭山到淮阴的汽车票吗13号的"
     
    model =model.to("cpu")
    print("该文本的类别是:%s"%label_name[predict(ex_text_str,text_pipeline)])
    该文本的类别是:Travel-Query

转载请注明来自码农世界,本文标题:《第N4周:中文文本分类——Pytorch实现》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,71人围观)参与讨论

还没有评论,来说两句吧...

Top