文本情感分类

本文详细介绍如何使用IMDB数据集进行情感分类,包括数据预处理、词典构建、文本序列化和构建简单的情感分类模型。重点讲解了如何解决batch中文本长度不一致的问题以及使用word2sequence技术将文本转化为数字序列的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.知道文本处理的基本方法
2.能够使用数据实现情感分类



一、案例介绍

现在我们有一个经典的数据集IMDB数据集,地址http://ai.stanford.edu/~amaas/data/sentiment/:,这是一份包含了5万条流行电影的评论数据,其中训练集25000条,测试集25000条,数据格式如下:
下边左边为名称,其中名称包含两部分,分别是序号和情感评分(1-4位neg,5-10位pos),右边为评论内容。
在这里插入图片描述

二、思路分析

首先可以把上述问题定义为分类问题,情感评分为1-10,10个类别(也可以理解为回归问题,这样当做分类问题考虑)也可以分为2类(neg和pos),流程大致为:

  1. 准备数据集
  2. 构建模型
  3. 模型训练
  4. 模型评估

1.准备数据集

Dataset: 对数据集的封装,用来将数据包装为Dataset类,提供索引方式的对数据样本进行读取。
DataLoader:对Dataset进行封装,提供批量读取的迭代读取。
准备数据集的方法是实例化dataset,准备dataloader,最终处理成如下格式:
在这里插入图片描述
其中有两点需要注意:

  1. 如何完成基础Dataset的构建和Dataloader的准备
  2. 每个batch中文本的长度不一致的问题如何解决
  3. 每个batch中的文本如何转换为数字序列

代码如下(示例):

2.读入数据

代码如下(示例):

"""
完后数据集的准备
"""
from torch.utils.data import Dataset,DataLoader
import os
import re

def tokenlize(content):
    """对句子进行预处理"""
    content = re.sub("<.*?>"," ",content)
    filter = ['\t','\n','\x97','\x96','#','$','%','&']
    content = re.sub("|".join(filter)," ",content)
    tokens = [word.strip() for word in content.split()]
    return tokens

class ImdbDataset(Dataset):
    def __init__(self,train = True):
        """获取数据集位置"""
        self.train_path = "data/aclimdb/train/"
        self.test_path = "data/aclimdb/test/"
        data_path = self.train_path if train else self.test_path

        # 把所有的文件名称放在列表中
        data_file = [os.path.join(data_path,"pos"),os.path.join(data_path,"neg")]
        self.total_file_path = []
        for index in data_file:
            filename = os.listdir(index)
            file_list = [os.path.join(index,i) for i in filename]
            self.total_file_path.extend(file_list)

    def __getitem__(self, index):
        file_path = self.total_file_path[index]
        # 获取label
        label = file_path.split("\\")[-2]
        label = 0 if label=="neg" else 1
        # 获取内容
        content = open(file_path).read()
        tokens = tokenlize(content)

        return tokens,label

    def __len__(self):
        return len(self.total_file_path)
def get_dataloader(train=True):
    imdbdataset = ImdbDataset(train)
    dataloader = DataLoader(imdbdataset,batch_size=2,shuffle=True)
    return dataloader

if __name__ == '__main__':
    for idx,(content,target) in enumerate(get_dataloader()):
        print(idx)
        print(content)
        print(target)
        break

结果如下:
在这里插入图片描述
很明显,其中的text内容和想象的不太相似,现在是将一个batch中的句子进行了zip处理,出现问题的原因在于Dataloader中的参数collate_fncollate_fn的默认值为torch自定义的default_collatecollate_fn的作用就是对每个batch进行处理,而默认的default_collate处理出错。
解决问题的思路:
方法1:考虑先把数据转化为数字序列,观察其结果是否符合要求,之前使用Dataloader并未出现类似错误。
方法2:考虑自定义一个collate_fn,观察结果。

def collate_fn(batch):
    """
    :param batch: ([tokens,label],[tokens,label])
    :return:
    """
    result = list(zip(*batch))
    return result

文本序列化(word2sequence)

在介绍word embedding时,需要先把文本转化为数字,再把数字转化为向量。
这里我么可以考虑把文本中的每个词语和其对应的数字保存在字典中,同时实现把句子通过字典映射为包含数字的列表
实现文本序列化之前,考虑以下几点:

  1. 如何使用字典把词语和数字进行对应。
  2. 不同的词语出现的次数不相同,是否需要对高频或者低频词语进行过滤,以及总的词语数量是否需要进行限制。
  3. 得到词典之后,如何把句子转化为数字序列,如何把数字序列转化为句子。
  4. 不同句子长度不相同,每个batch的句子如何构造成相同的长度(可以对短句子进行填充,填充特殊字符
  5. 对新出现的词语在词典中没有出现怎么办(可以用特殊字符处理

思路分析:

  • 对所有句子进行分词
  • 将词语存入词典,根据次数对词语进行过滤,并统计次数
  • 实现文本转数字序列的方法
  • 实现数字序列转文本方法
"""
实现:构建词典,实现把句子转化为数字序列并将其反转
"""
class word2sequence():
    UNK_TAG = "UNK"
    PAD_TAG = "PAD"
    UNK = 0
    PAD = 1

    def __init__(self):
        self.dict = {
            self.UNK_TAG : self.UNK,
            self.PAD_TAG : self.PAD
        }
        self.count = {} # 统计词频

    def fit(self,sentence):
        """
        把单个句子保存到dict中
        :param sentence:
        :return:
        """
        for word in sentence:
            self.count[word] = self.count.get(word,0)+1 # 字典的get方法获取value,有就返回,没有返回默认值

    def build_vocab(self,min=5,max=None,max_feature=None):
        """
        生成词典
        :param min:最小出现的次数
        :param max:最大出现的次数
        :param max_feature:一共保留多少个词语
        :return:
        """
        # 删除count中词频小于min的word
        if min is not None:
            self.count = {word:value for word,value in self.count.items() if value>min}
        # 删除count中词频大于max的word
        if max is not None:
            self.count = {word: value for word, value in self.count.items() if value < max}
        # 限制保留的词语数
        if max_feature is not None:
            temp = sorted(self.count.items(),key = lambda x:x[-1],reverse=True)[:max_feature]   # reverse=True降序
            self.count = dict(temp)

        for word in self.count:
            self.dict[word] = len(self.dict)

        # 得到一个翻转的dict字典
        self.reversed_dict = dict(zip(self.dict.values(),self.dict.keys()))

    def transform(self,sentence,max_len=None):
        """
        把句子转化为序列
        :param sentence:
        :return:
        """
        if max_len is not None:
            if len(sentence)>max_len:
                sentence = sentence[:max_len]   # 裁剪
            if len(sentence) < max_len:
                sentence = sentence + [self.PAD_TAG]*(max_len-len(sentence))    # 填充

        return [self.dict.get(word,self.UNK) for word in sentence]

    def inverse_transform(self,indices):
        """
        把序列转化为句子
        :param indices:
        :return:
        """
        return [self.reversed_dict.get(index) for index in indices]

if __name__ == '__main__':
    ws = word2sequence()
    ws.fit(["我","是","谁"])
    ws.fit(["我", "是", "我"])
    ws.build_vocab(min=0)
    ret = ws.transform(["我","在","河大"],max_len=10)
    print(ret)
    ret = ws.inverse_transform(ret)
    print(ret)

word2senquence结果的保存

# _*_ coding:utf-8 _*_
from word_sequence import word2sequence
from dataset import tokenlize
import pickle
import os
from tqdm import tqdm   # 进度展示
# pickle能够实现任意对象与文本之间的相互转化,也可以实现任意对象与二进制之间的相互转化。
# 也就是说,pickle可以实现 Python 对象的存储及恢复。
if __name__ == '__main__':

    ws = word2sequence()

    data_path = r"data/aclimdb/train/"
    temp_data_file = [os.path.join(data_path, "pos"), os.path.join(data_path, "neg")]
    for data_file in temp_data_file:

        file_name_list = os.listdir(data_file)
        for file_name in tqdm(file_name_list):
            if not file_name.endswith("txt"):
                continue
            file_path = os.path.join(data_file,file_name)
            sentence = tokenlize(open(file_path,encoding="utf-8").read())
            ws.fit(sentence)
    ws.build_vocab(min=10,max_feature=10000)
    pickle.dump(ws,open("./model/ws.pkl","wb"))
    print(len(ws))

在使用的时候直接使用pickle.load()就可以

ws = pickle.load(open("./model/ws.pkl","rb"))

模型搭建

这里我们只练习使用word embedding,所以模型只有一层,即:

  1. 数据经过word embedding
  2. 数据通过全连接层返回结果,计算log_softmax

后面的文章中有使用LSTM网络进行训练的。

from lib import max_len,ws
import torch.nn as nn
from dataset import get_dataloader
from torch.optim import Adam
import torch.nn.functional as F

class MyModel(nn.Module):

    def __init__(self):
        super().__init__()
        super(MyModel,self).__init__()
        self.embedding = nn.Embedding(len(ws),100)  # 参数为[词的数量,词的维度]
        self.fc = nn.Linear(max_len*100,2)          # 参数为[input_feature,output_feature]

    def forward(self, input):
        """
        :param input:[batch_size,max_len]
        :return:
        """
        x = self.embedding(input)   # 进行embedding操作,形状为:[batch_size,max_len]
        x = x.view([-1,max_len*100])
        out = self.fc(x)
        return F.softmax(out,dim=-1)

model = MyModel()
optimizer = Adam(model.parameters(),0.001)

def train(epoch):
    for idx,(input,target) in enumerate(get_dataloader(train=True)):
        optimizer.zero_grad()
        output = model(input)
        loss = F.nll_loss(output,target)
        loss.backward()
        optimizer.step()
        print(loss.item())
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值