文本情感分类TextCNN原理+IMDB数据集实战

news2024/11/27 4:32:13

在这里插入图片描述
在这里插入图片描述

1.任务背景

情感分类:

在这里插入图片描述
发展历程:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.数据集

在这里插入图片描述
本次使用IMDB数据集进行训练。

3.模型结构

在这里插入图片描述
在这里插入图片描述

3.1 CNN基础

卷积
在这里插入图片描述
单通道卷积:每组卷积核只包含一个。
单通道输入 单输出:设置一组卷积核。
单通道输入 多输出:设置多组卷积核。
在这里插入图片描述
RGB三通道卷积:每组卷积核只包含三个。
三通道输入 单输出:设置一组卷积核。
三通道输入 多输出:设置多组卷积核。

步长、池化
在这里插入图片描述
全连接层在这里插入图片描述

激活函数
在这里插入图片描述

3.2 Text基础

字向量、词向量
字向量——多用于古诗生成
词向量——多用于翻译、生成小说、文本分类
在这里插入图片描述

语料库
word2index:先要做分词,语料库找出唯一不重复的词语,再给它分配一个唯一的id。
index2word:word2index反过来。
word2onehot:onehot编码。
在这里插入图片描述
在这里插入图片描述

预处理:

  • 固定句子长度(超过截断、缺少补0).
  • 构建词表、给每个word设置索引.
  • embedding 将每个训练转化为词向量.
    在这里插入图片描述
    在这里插入图片描述

3.3 TextCNN

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 输入层:输入表示的是一句话或者一段文字,文字不像图片、语音信号,不是天然的数值类型,需要将一句话处理成数字之后,才能作为神经网络的输入。每个词使用一个向量进行表示,这个向量称为词向量。每一句话可以表示成一个二维的矩阵,如上图,“I like this movie very much!”,先对这句话进行分词,得到7个词(此处包括!),倘若每个词的词向量的维度d=5,则这句话可以表示成一个7x5的矩阵。需要注意的是,神经网络的结构输入的shape是固定的,但是每一篇评论的长度是不固定的,所以我们要固定神经网络输入的词数量。比如人为设定一篇评论的最大词数量sentence_max_size=300,d=5,则输入为300x5。对超过300词的评论进行截断,不足300词的进行padding,补0
  • 卷积层:图像的卷积核一般为正方形,而NLP中的卷积核一般为矩形。对于一个7x5的input,卷积核的宽度width=词向量的大小,长度的取值按需选取。因为词向量长度=5三组卷积核(每组2个卷积核,分别为4x5,3x5,2x5)纵向维度均=5,这样只需要卷积核向下滑动即可实现卷积。卷积完成后,每个卷积核 都会得到 两个特征图(3组,6个特征图,分别为3x1,4x1,5x1)。
  • 池化层:将6个特征图进行Maxpooling后,拼接在一起,组成一个6x1的特征图。
  • 全连接层:在经过一个softmax,求得情感2分类概率分布。

4.代码训练

baseline超参数:
在这里插入图片描述

4.1数据预处理

下载IMDB数据集
下载glove的300维的词向量模型
在这里插入图片描述

4.1.1扫描数据集文件

get_file_list(source_dir):扫描文件夹source_dir下的所有文件,并将该文件夹下 所有文件的路径名 保存在file_list中
get_label_list(file_list):根据file_list,从文件路径名中提取出文件对应的label

def get_file_list(source_dir):
    file_list = []  # 文件路径名列表
    # os.walk()遍历给定目录下的所有子目录,每个walk是三元组(root,dirs,files)
    # root 所指的是当前正在遍历的这个文件夹的本身的地址
    # dirs 是一个 list ,内容是该文件夹中所有的目录的名字(不包括子目录)
    # files 同样是 list , 内容是该文件夹中所有的文件(不包括子目录)
    # 遍历所有评论
    for root, dirs, files in os.walk(source_dir):
        file = [os.path.join(root, filename) for filename in files]
        file_list.extend(file)
    return file_list

def get_label_list(file_list):
    # 提取出标签名
    label_name_list = [file.split("\\")[4] for file in file_list]
    # 标签名对应的数字
    label_list = []
    for label_name in label_name_list:
        if label_name == "neg":
            label_list.append(0)
        elif label_name == "pos":
            label_list.append(1)
    return label_list

4.1.2载入预训练的词向量模型

获得gensim可用的glove词向量模型

  • 可以使用google或者glove训练好的词向量模型,本文使用glove的300维的词向量模型,下载地址:glove vectors
  • 但是下载的glove词向量模型,gensim不能拿来直接使用,运行以下代码得到文件glove.model.6B.300d.txt,该文件可供gensim直接使用
import gensim
import shutil
from sys import platform

# 计算行数,就是单词数
def getFileLineNums(filename):
    f = open(filename, 'r', encoding="utf8")
    count = 0
    for line in f:
        count += 1
    return count

# Linux或者Windows下打开词向量文件,在开始增加一行
def prepend_line(infile, outfile, line):
    with open(infile, 'r', encoding="utf8") as old:
        with open(outfile, 'w', encoding="utf8") as new:
            new.write(str(line) + "\n")
            shutil.copyfileobj(old, new)

def prepend_slow(infile, outfile, line):
    with open(infile, 'r', encoding="utf8") as fin:
        with open(outfile, 'w', encoding="utf8") as fout:
            fout.write(line + "\n")
            for line in fin:
                fout.write(line)

def load(filename):
    num_lines = getFileLineNums(filename)
    gensim_file = 'E:/data_source/glove.6B/glove.model.6B.300d.txt'
    gensim_first_line = "{} {}".format(num_lines, 200)
    # Prepends the line.
    if platform == "linux" or platform == "linux2":
        prepend_line(filename, gensim_file, gensim_first_line)
    else:
        prepend_slow(filename, gensim_file, gensim_first_line)

    model = gensim.models.KeyedVectors.load_word2vec_format(gensim_file)

load('E:/data_source/glove.6B/glove.6B.300d.txt')

加载预训练的词向量

wv.index2word:包含了词向量模型中所有的词
wv.vectors:包含了词向量模型中所有词的词向量
embedding对象:将wv.vectors中的词向量表示成Tensor
其中wv.index2word与wv.vectors(embedding.weight)相同位置的word与vector是一一对应的,为了从embedding.weight中获得word的vector,需要得到word在的index2word中的index,所以需要使用字典word2id 将其保存起来

word2vec_dir="glove.6B.300d.txt"# 训练好的词向量文件
# 加载词向量模型
wv = KeyedVectors.load_word2vec_format(datapath(word2vec_dir), binary=False)
word2id = {}  # word2id是一个字典,存储{word:id}的映射
for i, word in enumerate(wv.index2word):
	word2id[word] = i
# 根据已经训练好的词向量模型,生成Embedding对象
embedding = nn.Embedding.from_pretrained(torch.FloatTensor(wv.vectors))

4.1.3生成训练集与测试集

根据评论内容生成tensor
sentence是一个list,对输入的一篇评论的内容进行分词,过滤停用词之后,便得到sentence
根据sentence,得到一篇评论的Tensor表示,需要注意的是:我们定义的神经网络的输入是四维的[batch_size,channel,sentence_max_size,vec_dim],第一维是批大小,第二维是通道数,这里输入通道均为1,第三维是词数量,第四维是词向量的维度

def generate_tensor(sentence, sentence_max_size, embedding, word2id):
    """
    对一篇评论生成对应的词向量矩阵
    :param sentence:一篇评论的分词列表
    :param sentence_max_size:认为设定的一篇评论的最大分词数量
    :param embedding:词向量对象
    :param word2id:字典{word:id}
    :return:一篇评论的词向量矩阵
    """
    tensor = torch.zeros([sentence_max_size, embedding.embedding_dim])
    for index in range(0, sentence_max_size):
        if index >= len(sentence):
            break
        else:
            word = sentence[index]
            if word in word2id:
                vector = embedding.weight[word2id[word]]
                tensor[index] = vector
            elif word.lower() in word2id:
                vector = embedding.weight[word2id[word.lower()]]
                tensor[index] = vector
    return tensor.unsqueeze(0)  # tensor是二维的,必须扩充为三维,否则会报错

4.2实现Dataset、生成Dataloader

训练集保存在一个个小文件中,对于小数据集来说,一次性将所有数据读入内存勉强可行,但对于大数据集则是不可行的。此时,通过继承Dataset来实现自己的MyDataset,主要重写以下几个方法(方法名前后均有两道下划线,显示不出):

  • init(self, file_list, label_list, sentence_max_size, embedding, word2id, stopwords):初始化参数
  • getitem(self, index):MyDataset的实现原理就是通过遍历file_list,得到每一个文件路径名,根据路径名,将其内容读到内存中,通过generate_tensor()函数将文件内容转化为tensor,函数返回tensor与对应的label,其中index就是list的下标
  • len(self):返回list的长度
class MyDataset(Dataset):

    def __init__(self, file_list, label_list, sentence_max_size, embedding, word2id, stopwords):
        self.x = file_list
        self.y = label_list
        self.sentence_max_size = sentence_max_size
        self.embedding = embedding
        self.word2id = word2id
        self.stopwords = stopwords

    def __getitem__(self, index):
        # 读取评论内容
        words = []
        with open(self.x[index], "r", encoding="utf8") as file:
            for line in file.readlines():
                words.extend(segment(line.strip(), stopwords))
        # 生成评论的词向量矩阵
        tensor = generate_tensor(words, self.sentence_max_size, self.embedding, self.word2id)
        return tensor, self.y[index]

    def __len__(self):
        return len(self.x)

get_file_list()与get_label_list()函数详见2.2
Dataloader是个可遍历的对象,batch_size表示批大小,shuffle表示是否打乱数据

# 获取训练数据
logging.info("获取训练数据")
train_set = get_file_list(train_dir)
train_label = get_label_list(train_set)
train_dataset = MyDataset(train_set, train_label, sentence_max_size, embedding, word2id, stopwords)
train_dataloader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
# 获取测试数据
logging.info("获取测试数据")
test_set = get_file_list(test_dir)
test_label = get_label_list(test_set)
test_dataset = MyDataset(test_set, test_label, sentence_max_size, embedding, word2id, stopwords)
test_dataloader = DataLoader(test_dataset, batch_size=batch_size, shuffle=True)

4.3定义TextCNN模型

class TextCNN(nn.Module):
    def __init__(self, vec_dim, filter_num, sentence_max_size, label_size, kernel_list):
        """

        :param vec_dim: 词向量的维度
        :param filter_num: 每种卷积核的个数
        :param sentence_max_size:一篇文章的包含的最大的词数量
        :param label_size:标签个数,全连接层输出的神经元数量=标签个数
        :param kernel_list:卷积核列表
        """
        super(TextCNN, self).__init__()
        chanel_num = 1
        # nn.ModuleList相当于一个卷积的列表,相当于一个list
        # nn.Conv1d()是一维卷积。in_channels:词向量的维度, out_channels:输出通道数
        # nn.MaxPool1d()是最大池化,此处对每一个向量取最大值,所有kernel_size为卷积操作之后的向量维度
        self.convs = nn.ModuleList([nn.Sequential(
            nn.Conv2d(chanel_num, filter_num, (kernel, vec_dim)),
            nn.ReLU(),
            # 经过卷积之后,得到一个维度为sentence_max_size - kernel + 1的一维向量
            nn.MaxPool2d((sentence_max_size - kernel + 1, 1))
        )
            for kernel in kernel_list])
        # 全连接层,因为有2个标签
        self.fc = nn.Linear(filter_num * len(kernel_list), label_size)
        # dropout操作,防止过拟合
        self.dropout = nn.Dropout(0.5)
        # 分类
        self.sm = nn.Softmax(0)

    def forward(self, x):
        # Conv2d的输入是个四维的tensor,每一位分别代表batch_size、channel、length、width
        in_size = x.size(0)  # x.size(0),表示的是输入x的batch_size
        out = [conv(x) for conv in self.convs]
        out = torch.cat(out, dim=1)
        out = out.view(in_size, -1)  # 设经过max pooling之后,有output_num个数,将out变成(batch_size,output_num),-1表示自适应
        out = F.dropout(out)
        out = self.fc(out)  # nn.Linear接收的参数类型是二维的tensor(batch_size,output_num),一批有多少数据,就有多少行
        return out

4.4训练测试脚本

train_loader就是一个Dataloader对象,是个可遍历对象。迭代次数为epoch,每训练一批数据则输出该批数据的平均loss
可以下载我已经训练好模型进行测试:链接:https://pan.baidu.com/s/1Gxu9Wt0lTcTNUsZlg0dLyQ 提取码:8fd8
复制这段内容后打开百度网盘手机App,操作更方便哦

def train_textcnn_model(net, train_loader, epoch, lr):
    print("begin training")
    net.train()  # 必备,将模型设置为训练模式
    optimizer = optim.Adam(net.parameters(), lr=lr)
    criterion = nn.CrossEntropyLoss()
    for i in range(epoch):  # 多批次循环
        for batch_idx, (data, target) in enumerate(train_loader):
            optimizer.zero_grad()  # 清除所有优化的梯度
            output = net(data)  # 传入数据并前向传播获取输出
            loss = criterion(output, target)
            loss.backward()
            optimizer.step()

            # 打印状态信息
            logging.info("train epoch=" + str(i) + ",batch_id=" + str(batch_idx) + ",loss=" + str(loss.item() / 64))
    print('Finished Training')

test_loader也是一个Dataloader对象,累计每个batch的正确个数,并且每个batch都计算一次当前的accuracy
最终在测试集上的预测正确率为84%

def textcnn_model_test(net, test_loader):
    net.eval()  # 必备,将模型设置为训练模式
    correct = 0
    total = 0
    test_acc = 0.0
    with torch.no_grad():
        for i, (data, label) in enumerate(test_loader):
            logging.info("test batch_id=" + str(i))
            outputs = net(data)
            # torch.max()[0]表示最大值的值,troch.max()[1]表示回最大值的每个索引
            _, predicted = torch.max(outputs.data, 1)  # 每个output是一行n列的数据,取一行中最大的值
            total += label.size(0)
            correct += (predicted == label).sum().item()
            print('Accuracy of the network on test set: %d %%' % (100 * correct / total))

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/177604.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

国企避坑:to B服务性质的业务线不要来!又卷又累,互联网和它比简直是小巫见大巫!...

国企好归好,但不是所有的国企都能闭眼入,一位网友友情提示大家:不管是国企还是央企,to b服务性质的业务线不要来,不要来,不要来!又卷又累,苦哈哈,互联网和这个比&#xf…

在CSS世界的权力——权重

在CSS的世界中也存在着权力即CSS权重 1. 概念 CSS权重指的是样式的优先级,有两条或多条样式作用于一个元素,权重高的那条样式对元素起作用,权重相同的,后写的样式会覆盖前面写的样式 2. 以前的BUG 在实际开发中,我…

代码随想录--双指针章节总结

代码随想录–双指针章节总结 1.LeetCode27 移除元素 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 …

C++程序设计——动态内存管理

一、C/C内存分布 1.栈(堆栈) 存储非静态局部变量、函数参数、返回值等等,栈是向下增长。 2.内存映射段 是高效的I/O映射方式,用于转载一个共享的动态内存库。用户可使用系统接口创建共享内存,做进程间通信。 3.堆 用…

WPS的简单JS宏应用

有一阵子没写博客了,各种琐事忙碌;前段时间接触了下WPS的宏功能,抽点时间写个学习笔记吧。 案例背景简单说一下,主任让我统计OA后台在建工程项目的概况,后台数据导出一张表,再问隔壁经营部的同事要了一张中…

java类的初始化2023018

类的初始化: 第一次使用某个类,例如Person类,系统通常会在第一次使用Person类时加载这个类并初始化这个类。在类的准备阶段,系统将会为该类的类变量分配内存空间,并指定默认初始值。当Person类初始化完成后&#xff0c…

机器学习笔记之深度玻尔兹曼机(二)深度玻尔兹曼机的预训练过程

机器学习笔记之深度玻尔兹曼机——深度玻尔兹曼机的预训练过程引言深度信念网络预训练过程的问题深度玻尔兹曼机的预训练过程(2023/1/24)引言 上一节介绍了玻尔兹曼机系列的相关模型,本节将介绍深度玻尔兹曼机的预训练过程。 深度信念网络预训练过程的问题 在玻尔…

Escher 愛雪磁磚設計法則 - 高雄燕巢深水國小科展指導

“Talk is cheap. Show me the code.” ― Linus Torvalds 老子第41章 上德若谷 大白若辱 大方無隅 大器晚成 大音希聲 大象無形 道隱無名 拳打千遍, 身法自然 “There’s no shortage of remarkable ideas, what’s missing is the will to execute them.” – Seth Godin …

GreenPlum AOCO列存如何将数据刷写磁盘

GreenPlum AOCO列存如何将数据刷写磁盘AOCO列存表每个字段一个文件,前面我们介绍了列存表如何加载数据页,本文我们重点介绍AOCO表如何进行刷写。AOCO表进行insert、update、delete会产生脏数据,和heap表的异步脏页刷写不同,AOCO表…

写一个锅炉温控系统用python编写

简单来说就是锅炉水热了之后循环泵自动开启,然后将热水输送走,送到暖气,热水抽走,凉水进入锅炉,温度降低,循环泵关闭,等待下一次水烧热。因为需要取暖的房子距离烧锅炉的地方比较远,所以需要循环泵,如果距离近的话水烧热后利用热水上流冷水回流的原理会自动完成循环。…

前言技术之mybatis-plus

目录 1.什么是mybatis-plus 2.初体验 3.日志 4.主键生成策略 5.更新 6.自动填充 1.什么是mybatis-plus 升级版的mybatis,目的是让mybatis更易于使用, 用官方的话说“为简化而生” 官网: MyBatis-Plus 2.初体验 1.准备数据库脚本 数据…

BI 解决方案:BimlStudio 22.3.0 Crack

全功能开发环境:::: 导入现有解决方案 通过添加 BimlScript 自动化进行更改并重新生成包;使您的解决方案更好、更快。 可视化整个 BI 解决方案 通过我们的可视化设计器在一个位置进行更改,观察您的整个解决方案自行更新…

【ArcGIS微课1000例】0061:ArcGIS打开xyz格式点云数据的方法

本文讲述ArcMap和ArcScene中如何打开xyz格式的点云数据并做可视化的方法。 文章目录 一、xyz格式点云简介二、ArcMap打开xyz点云三、ArcScene打开xyz点云四、注意事项一、xyz格式点云简介 本实验使用的数据是配套数据包中的0061.rar,斯坦福大学的点云数据,格式为X,Y,Z,如下…

【My Electronic Notes系列——晶闸管】

目录 序言: 🏮🏮新年的钟声响,新年的脚步迈,祝新年的钟声,敲响你心中快乐的音符,幸运与平安,如春天的脚步紧紧相随,春节快乐!春华秋实,我永远与你…

Linux下动静态库的打包与使用C C++

目录前言为什么用动静态库动态链接与静态链接底层优缺点Linux下的动静态库动静态库的对比打包静态库使用静态库打包动态库使用动态库小结win下打包动静态库前言 为什么用动静态库 我们在实际开发中,经常要使用别人已经实现好的功能,这是为了开发效率和…

移动窗口下的LiDAR点云区域生长滤波算法教程

一、前言LiDAR 滤波的现有方法包括:数学形态学滤波法、基于地形坡度滤波、最小二乘内插法滤波等滤波方法。最小二乘内插法能够较好的获取地形趋势面,但是算法中无法根据地形自适应设置参数;在地形起伏较大的地区提取结果精度低;无…

Linux进程的后台运行

文章目录一. 什么是进程?二. 进程后台运行在了解三种进程后台运行的方式前,小编觉得有必要先简单讲解一下什么是进程。 PS: 本篇博客技术参考价值不大,只是类似随笔比较水,详细的知识点可以关注一下nohup命令的使用。 一. 什么是进程? 什…

00开篇词:带你玩转gRPC框架

前言 大家好,先做一下自我介绍 我叫Barry Yan,目前是一名互联网公司的研发工程师,同时也是后端技术领域的狂热爱好者和技术博主,在GitHub、CSDN社区、51CTO博客社区、阿里云技术社区、掘金技术社区和InfoQ写作社区等都有自己的博…

详解1242:网线主管(二分答案经典习题)

题目1242:网线主管时间限制: 1000 ms 内存限制: 65536 KB提交数: 23180 通过数: 5566【题目描述】仙境的居民们决定举办一场程序设计区域赛。裁判委员会完全由自愿组成,他们承诺要组织一次史上最公正的比赛。他们决定将选手的电脑用星形拓扑结构连接在一…

【SVM原理推导】核SVM为什么能分类非线性问题?

核SVM为什么能分类非线性问题?要解决这个问题,首先应该先深入理解SVM的原理与本质。(涉及SVM的问题是很常见的,因为SVM可以算是传统机器学习领域非常成功的算法之一了,现在仍有许多research运用SVM解决问题。) 一、支持向量机(SVM) 1. 基本介绍与提出背景 支持向量机…