人民网2022赛题2-话题识别

摘要:

合集:AI案例-NLP-传媒业
赛题:2022人民网人工智能算法大赛
官网:https://app.people.cn/h5/detail/normal/5015943150896128
AI问题:文本分类
数据发布方:人民网
数据集:包括微博文本数据及对应的话题标签的数据集
数据集价值:利用包括微博文本数据及对应的话题标签的数据集,支持微博话题识别。
解决方案:PaddleNLP框架、基于模型ERNIE3进行微博话题分类训练。

一、赛题描述

简介

为推进人工智能领域的学术交流、人才培养、技术发展,鼓励广大学生积极学习和研发符合我国主流价值观的优秀算法,2022年11-12月举办“2022人民网人工智能算法大赛”,赛事由人民网股份有限公司主办,传播内容认知全国重点实验室承办。

  • 赛题一:对话生成
  • 赛题二:微博话题识别
  • 赛题三:微博流行度预测
  • 赛题四:微博转发行为预测
  • 赛题五:社交媒体机器人识别

赛题二:微博话题识别

新浪微博作为新型社交媒体积累了各领域的海量数据,从中挖掘出潜在的特征并及时识别出话题,能够带来可观的社会价值。本次比赛提供微博识别数据集,每条数据包括微博文本数据及对应的话题标签,每个数据样本可能包含一个或多个话题标签。参赛选手需要通过训练集数据建立预测模型,对测试集数据的话题标签作出识别。

二、数据集内容

训练集

训练集包含一批文本信息样本及其标签,文件名为train.csv,各字段以tab分隔,格式如下:

ID: 编号 
Text: 微博文本内容
Label: 话题标签

数据样例:

5   张老师讲物理挺好的,能听懂   label_878402
6 搜狐的张朝阳?这大哥这么学霸的吗? 真牛啊 啥也能干 label_878402
7 太厉害了,张朝阳老师的解释,我相信没人会听不懂吧,而且根本不用担心七星连珠会发生灾害啦 label_878402

测试集

测试集包含一批不含标签的样本,文件名为test.csv,格式如下:

ID: 编号 
Text: 微博文本内容

数据样例:

ID  Text
5077 @河南城建学院2022年第二学士学位招生简章已发布,招生对象为当年普通高校本科毕业并获得学士学位的应届毕业生,近三年普通高校本科毕业并获得学士学位、目前未就业的往届生。法学专业计划招生100人,还等什么赶快报名吧!

数据集版权许可协议

GPL2
人民网

三、解决方案样例

ERNIE模型

ERNIE 3.0 Mini 是百度推出的轻量级预训练语言模型,属于 ERNIE(Enhanced Representation through kNowledge IntEgration)系列的最新成员之一。它在保持较高性能的同时,显著减小了模型体积和计算开销,适合资源受限的场景(如移动端、边缘设备或中小型企业应用)。

模型架构:

  • Backbone: 基于 Transformer 的变体,优化了计算效率和内存占用。
  • 输入处理: 支持中英文混合文本,分词器(Tokenizer)使用 WordPiece 算法。
  • 知识注入: 在预训练阶段引入实体边界预测、关系分类等知识驱动任务。

核心特点:

特性说明
轻量化设计参数量仅 千万级(远小于 ERNIE 3.0 Base 的亿级参数),推理速度更快。
多任务统一架构采用 ERNIE 3.0 统一框架,支持文本分类、序列标注、问答等多种 NLP 任务。
知识增强通过知识图谱融合实体和关系信息,提升对中文语义的理解能力。
高效训练使用持续学习和大规模中文语料(如百科、新闻、论坛数据)预训练。
开箱即用提供 PaddleNLP 的 AutoModel API,支持快速微调(Fine-tuning)。

安装开发包

参考文章《安装Paddle系列深度学习框架》。除了安装PaddlePaddle和PaddleNLP外,还需安装:

conda install -c conda-forge wordcloud
conda install jieba

wordcloud 是一个用于生成词云(Word Cloud)的 Python 开发包,它可以根据文本中单词的频率或权重,生成视觉上吸引人的词云图。高频词通常会显示得更大、更突出,而低频词则较小。wordcloud 还支持基于图片形状和颜色生成定制化的词云。

jieba是一个开源的Python库,专门用于中文文本的分词,它支持简体和繁体中文,提供了精确模式、全模式和搜索引擎模式等多种分词模式,以及自定义词典和词性标注等功能。例如输入:”我来到北京清华大学”,输出:我/ 来到/ 北京/ 清华大学。

CPU版本PaddleNLP

使用conda安装paddlepaddle==3.0.0b2;使用pip安装paddlenlp==3.0.0b0。

conda install paddlepaddle==3.0.0b2 -c paddle
pip install paddlenlp==3.0.0b0

检查:

(paddle3-cpu) C:\Users\86138>conda list paddle
# packages in environment at D:\App-Data\conda3\envs\paddle3-cpu:
#
# Name                   Version                   Build Channel
paddle2onnx               1.3.1                   pypi_0   pypi
paddlefsl                 1.1.0                   pypi_0   pypi
paddlenlp                 3.0.0b0                 pypi_0   pypi
paddlepaddle             3.0.0b2         py312_cpu_windows   paddle

GPU版本PaddleNLP

基于CUDA11.8为例,使用conda安装paddlepaddle-gpu==3.0.0b2;使用pip安装 paddlenlp==2.6.1:

conda install paddlepaddle-gpu==3.0.0b1 paddlepaddle-cuda=11.8 -c paddle -c nvidia
pip install paddlenlp==2.6.1 # 稳定版本

验证:

python -c "import paddlenlp;print(paddlenlp.__version__)"
2.6.1.post

源码逻辑

源码:PeopleNet2022-2-Topic-Recognition.ipynb 为竞赛基线版本。

1、导入相关系统库

import pandas as pd
import numpy as np

import matplotlib.pyplot as plt
import jieba
from wordcloud import WordCloud, ImageColorGenerator
from collections import Counter
from tqdm import tqdm
import paddle

import paddle
from paddlenlp.datasets import load_dataset
from paddlenlp.transformers import AutoModelForSequenceClassification, AutoTokenizer

from sklearn.preprocessing import MultiLabelBinarizer, LabelEncoder

from paddle.io import Dataset, DataLoader

2、读入数据集

train_data = pd.read_csv('train.csv', sep='\t')
test_data = pd.read_csv('test.csv', sep='\t')

3、分析数据集概况

# 句子长度分析
plt.figure(figsize=(6, 3))

plt.subplot(121)
train_data['Text'].apply(len).plot(kind='box')
plt.title('Train')

plt.subplot(122)
test_data['Text'].apply(len).plot(kind='box')
plt.title('Test')
# 话题分布分析
plt.figure(figsize=(6, 3))

train_data['Label'].apply(lambda x: len(x.split(','))).value_counts().plot(kind='barh')
# 统计一个微博包含多个话题的个数,大部分的微博只包含1个话题,最多有7个话题

pd.Series(','.join(train_data['Label']).split(',')).value_counts().head(20)
# 话题label_1191241样本最多,且类别样本严重不均衡

对微博话题的统计输出样例:

label_1191241    10235
label_1008181   10023
label_1281707     4680
label_472394     4671
label_1515062     4671
label_742793     2783
label_1411524     1906
label_1265038     1881
label_896157     1730
label_1064693     1719
label_467023     1641
label_1474127     1421
label_1227838     1066
label_1166118     1066
label_19479       1060
label_287908       828
label_753343       719
label_1056127     716
label_529001       711
label_512340       704
Name: count, dtype: int64

话题词云展示:

# 显示 label_1191241 话题词云
content = ''.join(train_data[train_data['Label'].str.contains('label_1191241')]['Text'])
wordcloud = WordCloud(background_color = 'white', max_words = 1000, font_path = 'simsun.ttc')
wordcloud.generate(''.join(jieba.lcut(content)))

plt.imshow(wordcloud)
plt.xticks([]); plt.yticks([])

4、划分数据集

这段代码主要用于文本数据的随机打乱、划分训练集/验证集,并进行中文分词处理。

  • train_data(DataFrame)进行随机打乱,避免原始数据存在顺序偏差。
  • 将打乱后的数据分为两部分:X:训练集(除最后15000条外的所有数据)。X_valid:验证集(最后15000条数据)。
  • Text 列的中文文本进行分词,结果存储到新列 word 中。
train_data = train_data.sample(frac=1.0)
X, X_valid = train_data.iloc[:-15000], train_data.iloc[-15000:]

X['word'] = X['Text'].apply(jieba.lcut)
X_valid['word'] = X_valid['Text'].apply(jieba.lcut)

5、话题分类训练

这段代码使用了 PaddlePaddle 和 PaddleNLP 库来加载一个预训练的中文文本分类模型(ERNIE 3.0 Mini)及其对应的分词器(Tokenizer)。并对一段中文文本进行编码。

  • 加载模型:使用 AutoModelForSequenceClassification 加载预训练模型,并调整输出层以适应分类任务。
  • 加载 Tokenizer:使用 AutoTokenizer 加载与模型匹配的分词器。
  • 文本编码:用 tokenizer.encode() 将文本转换为模型可处理的数字 ID 序列。
import paddle
from paddlenlp.datasets import load_dataset
from paddlenlp.transformers import AutoModelForSequenceClassification, AutoTokenizer

# 加载模型
model = AutoModelForSequenceClassification.from_pretrained("ernie-3.0-mini-zh", num_classes=1399)

# 加载字符编码器
tokenizer = AutoTokenizer.from_pretrained("ernie-3.0-mini-zh")
tokenizer.encode('你好,我是阿水。')

这段代码使用了 scikit-learn 的 LabelEncoder 对单分类标签进行编码,将文本标签转换为数值形式,以便机器学习模型处理。

from sklearn.preprocessing import MultiLabelBinarizer, LabelEncoder

# 单分类标签编码
label_encoder = LabelEncoder()
label_encoder.fit(train_data['Label'])
X_muti_label = label_encoder.transform(X['Label'])
X_valid_muti_label = label_encoder.transform(X_valid['Label'])

这段代码使用 PaddlePaddle 的 DatasetDataLoader 类来创建自定义的数据集和数据加载器,以便在训练和验证过程中高效地加载和处理数据。

from paddle.io import Dataset, DataLoader

# 自定义数据集
class MyDataset(Dataset):
  def __init__(self, data, label):
      self.data = data
      self.label = label

  def __getitem__(self, idx):
      return self.data[idx], self.label[idx]

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

train_loader = DataLoader(MyDataset(X['Text'].values[:], X_muti_label[:]), batch_size=50, shuffle=True)
valid_loader = DataLoader(MyDataset(X_valid['Text'].values[:], X_valid_muti_label[:]), batch_size=50)

这段代码是一个典型的深度学习模型训练和验证流程,使用的是PaddlePaddle框架。

# AdamW 是Adam优化器的一个变种,主要区别在于它对权重衰减(weight decay)的处理方式更为有效
# 0.00005 是学习率(learning rate),控制每次参数更新的步长。学习率的选择对模型的训练效果有显著影响。过大的学习率可能导致训练不稳定,而过小的学习率可能导致训练速度过慢
# parameters=model.parameters() 指定了优化器需要更新的模型参数。model.parameters() 返回模型中所有可训练的参数
optimizer = paddle.optimizer.AdamW(0.00005, parameters=model.parameters())

# CrossEntropyLoss 是交叉熵损失函数,常用于分类任务,特别是多类别分类问题
# reduction='mean' 指定了如何对损失进行缩减(reduce)。'mean' 表示将所有样本的损失取平均值,作为最终的损失值
loss_fn = paddle.nn.loss.CrossEntropyLoss(reduction='mean')

for epoch in range(3):
  # 训练过程
  model.train()
  for batch_x, batch_y in tqdm(train_loader):
      # tokenizer 用于将文本数据转换为模型可以理解的输入格式(如 token IDs)
      batch_x = tokenizer(batch_x, max_length=70, padding=True)

      # 将处理后的数据转换为PaddlePaddle张量
      batch_x = {key: paddle.to_tensor(value) for key, value in batch_x.items()}
       
      # 将输入数据传递给模型进行前向传播,得到预测结果
      pred = model(batch_x['input_ids'], batch_x['token_type_ids'])

      # 使用定义的损失函数计算预测结果与真实标签之间的损失
      loss = loss_fn(pred, paddle.to_tensor(batch_y, dtype="int32"))

      loss.backward() # 计算梯度
      optimizer.step() # 更新模型参数
      optimizer.clear_gradients() # 清除梯度,为下一批数据的训练做准备
   
  # 验证过程
  model.eval()
  val_loss = []
  with paddle.no_grad(): # 禁用了梯度计算
      for batch_x, batch_y in tqdm(valid_loader):
          batch_x = tokenizer(batch_x, max_length=70, padding=True)
          batch_x = {key: paddle.to_tensor(value) for key, value in batch_x.items()}
          batch_y = paddle.to_tensor(batch_y, dtype="int32") # 将真实标签转换为PaddlePaddle张量

          pred = model(batch_x['input_ids'], batch_x['token_type_ids'])
          loss = loss_fn(pred, batch_y)
          val_loss.append(loss.item())
   
  print('Epoch: {0}, Val loss: {1:3f}, Val Accuracy: {2:3f}'.format(epoch, np.mean(val_loss),
        (pred.argmax(1) == batch_y.astype('int64')).astype('float').mean().item()))

训练过程展示:

 36%|███▋      | 454/1247 [32:11<1:00:24,  4.57s/it]

6、保存模型

# 保存模型
paddle.save(model.state_dict(), 'model.pdparams')

# 加载模型
# model.set_state_dict(paddle.load('model.pdparams'))

保存模型参数信息到文件:model.pdparams

四、获取案例套装

需要登录后才允许下载文件包。登录

发表评论