『NLP练习赛』中⽂新闻标题分类baseline
试题说明
基于THUCNews数据集的⽂本分类, THUCNews是根据新浪新闻RSS订阅频道2005~2011年间的历史数据筛选过滤⽣成,包含74万篇新闻⽂档,参赛者需要根据新闻标题的内容⽤算法来判断该新闻属于哪⼀类别。
数据说明
THUCNews是根据新浪新闻RSS订阅频道2005~2011年间的历史数据筛选过滤⽣成,包含74万篇新闻⽂档(2.19 GB),均为UTF-8纯⽂本格式。在原始新浪新闻分类体系的基础上,重新整合划分出14个候选分类类别:财经、、房产、股票、家居、教育、科技、社会、时尚、时政、体育、星座、游戏、娱乐。
训练集,验证集按照“原⽂标题+\t+标签”的格式抽取出来,可以直接根据新闻标题进⾏⽂本分类任务,希望答题者能够给出⾃⼰的解决⽅案。
测试集仅提供“原⽂标题”,答题者需要对其预测相应的分类类别。
训练集:
验证集:
测试集:
提交答案
考试提交,需要提交模型代码项⽬版本和结果⽂件。结果⽂件为TXT⽂件格式,命名为,⽂件内的字段需要按照指定格式写⼊。
1.每个类别的⾏数和测试集原始数据⾏数应⼀⼀对应,不可乱序
2.输出结果应检查是否为83599⾏数据,否则成绩⽆效
3.输出结果⽂件命名为,⼀⾏⼀个类别,样例如下:
···
最近一周新闻
游戏
财经
时政
股票
家居
科技 ···
基线系统
数据处理
构建词汇表
在搭建模型之前,我们需要对整体语料构造词表。通过切词统计词频,去除低频词,从⽽完成构造词表。我们使⽤jieba作为中⽂切词⼯具。
停⽤词表,我们从⽹上直接获取:github/goto456/stopwords/blob/master/
In [1]
!pip install --upgrade paddlenlp
!pip install paddlepaddle
In [2]
import os
import time
from collections import Counter
from itertools import chain
import jieba
def sort_and_write_words(all_words, file_path):
words =list(chain(*all_words))
words_vocab =Counter(words).most_common()
words_vocab =Counter(words).most_common()
with open(file_path,"w", encoding="utf8") as f:
f.write('[UNK]\n[PAD]\n')
# filter the count of words below 5
# 过滤低频词,词频<5
for word, num in words_vocab:
if num <5:
continue
f.write(word +"\n")
(root, directory, files),=list(os.walk("./work/data"))
all_words =[]
for file_name in files:
with open(os.path.join(root, file_name),"r", encoding="utf8") as f:
for line in f:
if file_name in ["",""]:
text, label = line.strip().split("\t")
elif file_name =="":
text = line.strip()
else:
continue
words = jieba.lcut(text)
words =[word for word in words if word.strip()!='']
all_words.append(words)
# 写⼊词表
sort_and_write_words(all_words,"work/")
Building prefix dict from the default dictionary ...
Dumping model to file cache /tmp/jieba.cache
Loading model cost 0.812 seconds.
Prefix dict has been built successfully.
In [3]
# 词汇表⼤⼩
!wc -l work/
# 停⽤词表⼤⼩
!wc -l work/data/
79441 work/
1395 work/data/
加载⾃定义数据集
构建词汇表完毕之后,我们可以加载⾃定义数据集。加载⾃定义数据集可以通过继承paddle.io.Dataset完成。
更多⾃定义数据集⽅式参考:⾃定义数据集
同时,PaddleNLP提供了⽂本分类、序列标注、阅读理解等多种任务的常⽤数据集,⼀键即可加载,详细信息参考数据集:
In [4]
import paddle
class NewsData(paddle.io.Dataset):
def __init__(self, data_path, mode="train"):
is_test = True if mode =="test"else False
self.label_map ={ item:index for index, item in enumerate(self.label_list)}
def _read_file(self, data_path, is_test):
examples =[]
with open(data_path,'r', encoding='utf-8') as f:
for line in f:
if is_test:
text = line.strip()
examples.append((text,))
else:
text, label = line.strip('\n').split('\t')
label = self.label_map[label]
examples.append((text, label))
examples.append((text, label))
return examples
def __getitem__(self, idx):
amples[idx]
def __len__(self):
return amples)
@property
def label_list(self):
return['财经','','房产','股票','家居','教育','科技','社会','时尚','时政','体育','星座','游戏','娱乐'] In [5]
# Loads dataset.
train_ds =NewsData("work/", mode="train")
dev_ds =NewsData("work/", mode="dev")
test_ds =NewsData("work/", mode="test")
print("Train data:")
for text, label in train_ds[:5]:
print(f"Text: {text}; Label ID {label}")
print()
print("Test data:")
for text, in test_ds[:5]:
print(f"Text: {text}")
Train data:
Text:⽹易第三季度业绩低于分析师预期; Label ID 6
Text:巴萨1年前地狱重现这次却是天堂再赴魔⿁客场必翻盘; Label ID 10
Text:美国称⽀持向朝鲜提供紧急⼈道主义援助; Label ID 9
Text:增资交银康联交⾏夺参股险商⾸单; Label ID 3
Text:午盘:原材料板块领涨⼤盘; Label ID 3
Test data:
Text:北京君太百货璀璨秋⾊满100省353020元
Text:教育部:⼩学⾼年级将开始学习性知识
Text:专业级单反相机佳能7D单机售价9280元
Text:星展银⾏起诉内地客户银⾏强硬客户⽆奈
Text:脱离中国的实际强压⼈民币⼤幅升值只能是梦想
读⼊数据
加载数据集之后,还需要将原始⽂本转化为word id,读⼊数据。
PaddleNLP提供了许多关于NLP任务中构建有效的数据pipeline的常⽤API
API 简介
paddlenlp.data.Stack 堆叠N个具有相同shape的输⼊数据来构建⼀个batch
paddlenlp.data.Pad 将长度不同的多个句⼦padding到统⼀长度,取N个输⼊数据中的最⼤长度paddlenlp.data.Tuple 将多个batchify函数包装在⼀起
更多数据处理操作详见: github/PaddlePaddle/PaddleNLP/blob/develop/docs/data.md
In [6]
from paddlenlp.data import Stack, Pad, Tuple
a =[1,2,3,4]
b =[3,4,5,6]
c =[5,6,7,8]
result =Stack()([a, b, c])
print("Stacked Data: \n", result)
print()
a =[1,2,3,4]
b =[5,6,7]
c =[8,9]
result =Pad(pad_val=0)([a, b, c])
print("Padded Data: \n", result)
print()
data =[
[[1,2,3,4],[1]],
[[5,6,7],[0]],
[[8,9],[1]],
]
batchify_fn =Tuple(Pad(pad_val=0),Stack())
ids, labels =batchify_fn(data)
print("ids: \n", ids)
print()
print("labels: \n", labels)
print()
Stacked Data:
[[1234]
[3456]
[5678]]
Padded Data:
[[1234]
[5670]
[8900]]
ids:
[[1234]
[5670]
[8900]]
labels:
[[1]
[0]
[1]]
本基线将对数据作以下处理:
将原始数据处理成模型可以读⼊的格式。⾸先使⽤jieba切词,之后将jieba切完后的单词映射词表中单词id。使⽤paddle.io.DataLoader接⼝多线程异步加载数据。
In [7]
from functools import partial
import paddlenlp
from paddlenlp.datasets import MapDataset
from utils import convert_example, read_vocab, write_results
def create_dataloader(dataset,
trans_fn=None,
mode='train',
batch_size=1,
use_gpu=False,
batchify_fn=None):
if trans_fn:
dataset =MapDataset(dataset)
dataset = dataset.map(trans_fn)
if mode =='train'and use_gpu:
sampler = paddle.io.DistributedBatchSampler(
dataset=dataset, batch_size=batch_size, shuffle=True)
else:
shuffle = True if mode =='train'else False
sampler = paddle.io.BatchSampler(
dataset=dataset, batch_size=batch_size, shuffle=shuffle)
dataloader = paddle.io.DataLoader(
dataset,
dataset,
batch_sampler=sampler,
return_list=True,
collate_fn=batchify_fn)
return dataloader
In [8]
vocab =read_vocab("work/")
stop_words =read_vocab("work/data/")
batch_size =128
epochs =2
trans_fn =partial(convert_example, vocab=vocab, stop_words=stop_words, is_test=False)
batchify_fn = lambda samples, fn=Tuple(
Pad(axis=0, pad_('[PAD]',0)),  # input_ids
Stack(dtype="int64"),  # seq len
Stack(dtype="int64")  # label
):[data for data in fn(samples)]
train_loader =create_dataloader(
train_ds,
trans_fn=trans_fn,
batch_size=batch_size,
mode='train',
use_gpu=True,
batchify_fn=batchify_fn)
dev_loader =create_dataloader(
dev_ds,
trans_fn=trans_fn,
batch_size=batch_size,
mode='validation',
use_gpu=True,
batchify_fn=batchify_fn)
组⽹、配置、训练
定义模型结构
读⼊了数据之后,即可定义模型结构。此处,我们选择BiLSTM作为baseline。
PaddleNLP提供了序列化建模模块paddlenlp.seq2vec模块,该模块可以将⽂本抽象成⼀个携带语义的⽂本向量。关于seq2vec模块更多信息参考:paddlenlp.seq2vec是什么?快来看看如何⽤它完成情感分析任务
本基线模型选⽤LSTMencoder搭建⼀个BiLSTM模型⽤于⽂本分类任务。
paddlenlp.seq2vec.LSTMEncoder组建句⼦建模层
In [9]
as nn
functional as F
class LSTMModel(nn.Layer):
def __init__(self,
vocab_size,
num_classes,
emb_dim=128,
padding_idx=0,
lstm_hidden_size=198,
direction='forward',
lstm_layers=1,
dropout_rate=0.0,
pooling_type=None,
fc_hidden_size=96):
super().__init__()
# ⾸先将输⼊word id 查表后映射成 word embedding
num_embeddings=vocab_size,