基于 Kashgari 2 的短文本分类: 数据分析和预处理

文本分类是自然语言处理核心任务之一,常见用文本审核、广告过滤、情感分析、语音控制和反黄识别等NLP领域。本文介绍如何获取、分析和预处理数据集,为下一篇做数据准备。

获取数据

相比英文数据集,中文数据集还是比较匮乏。不过好在有不少好心同学们精心整理了现有中文语料,比如 SimmerChan 同学整理的 https://github.com/SimmerChan/corpus

此次试验我选择了 今日头条中文新闻(短文本)分类数据集,读者们也可以选择别的数据集进行试验测试。

这个数据集下载 toutiao_cat_data.txt.zip 文件到本地,然后解压即可。

该数据集来自今日头条客户端,数据格式如下。其中 _!_ 为分隔符,字段分别为新闻ID、分类 Code、分类名称和新闻标题和新闻关键词。

1
6552431613437805063_!_102_!_news_entertainment_!_谢娜为李浩菲澄清网络谣言,之后她的两个行为给自己加分_!_佟丽娅,网络谣言,快乐大本营,李浩菲,谢娜,观众们

数据集涵盖了一下 15 个分类

ID 类别 Code
100 故事 news_story
101 文化 news_culture
102 娱乐 news_entertainment
103 体育 news_sports
104 财经 news_finance
106 房产 news_house
107 汽车 news_car
108 教育 news_edu
109 科技 news_tech
110 军事 news_military
112 旅游 news_travel
113 国际 news_world
114 股票 stock
115 农业 news_agriculture
116 游戏 news_game

安装依赖

数据分析和预处理过程我们需要用到一下几个框架:

  • jieba:最常用的中文分词框架。
  • wordcloud:词云框架,通过可视化词云来分析关键词。
  • sklearn:经典机器学习库,提供了大量用于数据挖掘和分析的工具。

用下面的命令安装这三个依赖:

1
2
3
pip install jieba
pip install wordcloud
pip install scikit-learn

读取数据集

数据每一行为一个样本,每个样本使用 _!_ 隔符,字段分别为新闻ID、分类 Code、分类名称和新闻标题和新闻关键词。本实验我们只关注新闻标题和分类 code。使用以下代码读取样本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from typing import Dict


def parse_line_to_sample(line: str) -> Dict:
cols = line.split('_!_')
return {
'title': cols[3],
'category': cols[2]
}


corpus = []

with open('data/toutiao_cat_data.txt', 'r') as f:
for line in f.readlines():
sample = parse_line_to_sample(line)
corpus.append(sample)

# 输出前三个样本
print(json.dumps(corpus[:3], indent=2, ensure_ascii=False))

此时输出样本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[
{
"title": "京城最值得你来场文化之旅的博物馆",
"category": "news_culture"
},
{
"title": "发酵床的垫料种类有哪些?哪种更好?",
"category": "news_culture"
},
{
"title": "上联:黄山黄河黄皮肤黄土高原。怎么对下联?",
"category": "news_culture"
}
]

由于我们用的是新闻标题,标题中的标点符号也是有一定的语义含义,所以此处预处理比较简单,只做分词即可。如果原始文本里面包含不必要的标点符号等,那么预处理的时候也可以选择去掉特定的标点符号。

1
2
3
4
5
6
7
8
9
10
11
12
def pre_process_sample(sample: Dict) -> Dict:
"""
预处理数据
:param sample: 原始样本
:return: 预处理后的样本
"""
sample['segmented_title'] = list(jieba.cut(sample['title']))
return sample


processed_corpus = [pre_process_sample(s) for s in corpus]
print(json.dumps(processed_corpus[:3], indent=2, ensure_ascii=False))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[
{
"title": "京城最值得你来场文化之旅的博物馆",
"category": "news_culture",
"segmented_title": [
"京城", "最", "值得", "你", "来场", "文化", "之旅", "的", "博物馆"
]
},
{
"title": "发酵床的垫料种类有哪些?哪种更好?",
"category": "news_culture",
"segmented_title": [
"发酵", "床", "的", "垫料", "种类", "有", "哪些", "?", "哪", "种", "更好", "?"
]
},
{
"title": "上联:黄山黄河黄皮肤黄土高原。怎么对下联?",
"category": "news_culture",
"segmented_title": [
"上联", ":", "黄山", "黄河", "黄皮肤", "黄土高原", "。", "怎么", "对", "下联", "?"
]
}
]

分析数据

我们使用词云(WordCloud)分析高频词语,词云可以直观看一下文本关键词信息。
如果让人来进行分类,扫一眼词云就可以在很短时间内做出分类判断,比阅读原始文本要快的多,其原理就是利用了文本中的高频词信息,
频率高的词(去除停用词之后)比较能代表文本的主旨信息,其实后面的分类过程大概就是这个思路。
WordCloud 默认不显示中文字体,会显示成方块,可以在网上下载中文字体文件,比如 simhei.ttf,放在当前目录下即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from wordcloud import WordCloud
import matplotlib.pyplot as plt
import matplotlib as mpl
mpl.rcParams['figure.dpi'] = 300


def visualize_category_word_cloud(processed_corpus: List[Dict], category: str):
"""
构建词云并展示
:param processed_corpus: 分词后的数据集
:param category: 目标分类
"""
# 筛选具体分类下的所有的样本
target_samples = [s for s in processed_corpus if s['category'] == category]

# 把所有的样本整合成一段大文本,方便词云分析和自动去除停用词
all_text = ""
for sample in target_samples:
all_text += ' '.join(sample['segmented_title'])

# 初始化词云对象,然后使用上面的文本构建词云
word_cloud = WordCloud(scale=4,
width=1024,
height=600,
font_path='SimHei.ttf',
background_color='white',
max_words=200,
random_state=20)

word_cloud.generate(all_text)

# 利用 matplotlib 展示词云
plt.imshow(word_cloud, interpolation='bilinear')
plt.title(f"Category: {category}")
plt.axis('off')

# 可视化科技类新闻关键词
visualize_category_word_cloud(processed_corpus, 'news_tech')

教育、经济、体育和科技类新闻标题关键词构建的词云如下,可以看到非常的直观。这里停用词只用了 wordcloud 框架提供的部分,如果自定义停用词,去掉类似怎么,如何之类的停用词,效果会更好。

准备训练、评估、测试数据集

训练模型我们需要用到训练,评估和测试数据集。sklearn 提供的 train_test_split 可以很方便为我们实现这个功能。

1
2
3
4
5
6
7
8
9
10
11
from sklearn.model_selection import train_test_split

x_data = [s['segmented_title'] for s in processed_corpus]
y_data = [s['category'] for s in processed_corpus]

remain_x, train_x, remain_y, train_y = train_test_split(x_data, y_data, test_size=0.7, random_state=42)
valid_x, test_x, valid_y, test_y = train_test_split(remain_x, remain_y, test_size=0.5, random_state=42)

print(f"训练数据样本: {len(train_x)}")
print(f"评估数据样本: {len(valid_x)}")
print(f"测试数据样本: {len(test_x)}")

此时数据准备工作结束,训练样本共 267882 条,评估样本 57403 条,测试样本 57403 条。