在上一章节,我们给大家简单介绍了赛题的内容和几种解决方案。从本章开始我们将会逐渐带着大家使用思路1到思路4来完成本次赛题。在讲解工具使用的同时,我们还会讲解一些算法的原理和相关知识点,并会给出一定的参考文献供大家深入学习。
数据读取与数据分析
本章主要内容为数据读取和数据分析,具体使用Pandas
库完成数据读取操作,并对赛题数据进行分析构成。
学习目标
- 学习使用
Pandas
读取赛题数据 - 分析赛题数据的分布规律
数据读取
赛题数据虽然是文本数据,每个新闻是不定长的,但任然使用csv格式进行存储。因此可以直接用Pandas
完成数据读取的操作。
import pandas as pd
train_df = pd.read_csv('../input/train_set.csv', sep='t', nrows=100)
这里的read_csv
由三部分构成:
- 读取的文件路径,这里需要根据改成你本地的路径,可以使用相对路径或绝对路径;
- 分隔符
sep
,为每列分割的字符,设置为t
即可; - 读取行数
nrows
,为此次读取文件的函数,是数值类型(由于数据集比较大,建议先设置为100);
上图是读取好的数据,是表格的形式。第一列为新闻的类别,第二列为新闻的字符。
数据分析
在读取完成数据集后,我们还可以对数据集进行数据分析的操作。虽然对于非结构数据并不需要做很多的数据分析,但通过数据分析还是可以找出一些规律的。
train_df = pd.read_csv('./input/train_set.csv', sep='t')
此步骤我们读取了所有的训练集数据,在此我们通过数据分析希望得出以下结论:
- 赛题数据中,新闻文本的长度是多少?
- 赛题数据的类别分布是怎么样的,哪些类别比较多?
- 赛题数据中,字符分布是怎么样的?
句子长度分析
在赛题数据中每行句子的字符使用空格进行隔开,所以可以直接统计单词的个数来得到每个句子的长度。统计训练集如下:
%pylab inline
train_df['text_len'] = train_df['text'].apply(lambda x: len(x.split(' ')))
print(train_df['text_len'].describe())
输出结果为:
Populating the interactive namespace from numpy and matplotlib
count 200000.000000
mean 907.207110
std 996.029036
min 2.000000
25% 374.000000
50% 676.000000
75% 1131.000000
max 57921.000000
Name: text_len, dtype: float64
对新闻句子的统计可以得出,本次赛题给定的文本比较长,每个句子平均由907个字符构成,最短的句子长度为2,最长的句子长度为57921。
测试集a的分布和训练集类似:
count 50000.000000
mean 909.844960
std 1032.313375
min 14.000000
25% 370.000000
50% 676.000000
75% 1133.000000
max 41861.000000
Name: text_len, dtype: float64
下图将句子长度绘制了直方图,可见大部分句子的长度都几种在2000以内。
import matplotlib.pyplot as plt
_ = plt.hist(train_df['text_len'], bins=200)
plt.xlabel('Text char count')
plt.title("Histogram of char count")
参数bins是指箱子的个数,即条状图的个数,这里是200,表示将范围内的文本长度均分成200个bin。
新闻类别分布
接下来可以对数据集的类别进行分布统计,具体统计每类新闻的样本个数。
train_df['label'].value_counts().plot(kind='bar')
plt.title('News class count')
plt.xlabel("category")
在数据集中标签的对应的关系如下:{'科技': 0, '股票': 1, '体育': 2, '娱乐': 3, '时政': 4, '社会': 5, '教育': 6, '财经': 7, '家居': 8, '游戏': 9, '房产': 10, '时尚': 11, '彩票': 12, '星座': 13}
从统计结果可以看出,赛题的数据集类别分布存在较为不均匀的情况。在训练集中科技类新闻最多,其次是股票类新闻,最少的新闻是星座新闻。
字符分布统计
接下来可以统计每个字符出现的次数,首先可以将训练集中所有的句子进行拼接进而划分为字符,并统计每个字符的个数。
from collections import Counter
# 所有的字符都是以空格分隔
all_lines = ' '.join(list(train_df['text']))
# 统计每个字符的个数,以字典{字符:出现次数}的形式
word_count = Counter(all_lines.split(" "))
# 按照字符的出现次数降序排序
word_count = sorted(word_count.items(), key=lambda d:d[1], reverse = True)
print(len(word_count))
# 6869
print(word_count[0])
# ('3750', 7482224)
print(word_count[-1])
# ('3133', 1)
print(word_count[:10])
# [('3750', 7482224), ('648', 4924890), ('900', 3262544), ('3370', 2020958), ('6122', 1602363), ('4464', 1544962), ('7399', 1455864), ('4939', 1387951), ('3659', 1251253), ('4811', 1159401)]
从统计结果中可以看出,在训练集中总共包括6869个字,其中编号3750的字出现的次数最多,编号3133的字出现的次数最少。
如果上述代码报错,出现"MemoryError",说明内存不够用了,可以选择换电脑、加内存条,或者采用下列代码:
%%time
vocab = dict()
for text in train_df['text']:
for word in text.split():
if vocab.get(word):
vocab[word] += 1
else:
vocab[word] = 1
vocab = sorted(vocab.items(), key=lambda d:d[1], reverse = True)
print(len(vocab))
print(vocab[0])
print(vocab[-1])
print(vocab[:10])
可以看看字符的范围:
chars = sorted(vocab, key=lambda x: int(x[0]))
print(len(chars), chars[0], chars[-1])
# 6869 ('0', 24) ('7549', 12)
从0到7549一共6869个字符,说明中间有缺失。
这里还可以根据字在每个句子的出现情况,反推出标点符号。下面代码统计了不同字符在句子中出现的次数,其中字符3750,字符900和字符648在20w新闻的覆盖率接近99%,很有可能是标点符号。
# 句子中出现的字符集合(无重复字符)
train_df['text_unique'] = train_df['text'].apply(lambda x: ' '.join(list(set(x.split(' ')))))
# 所有数据的字符集合拼接
all_lines = ' '.join(list(train_df['text_unique']))
word_count = Counter(all_lines.split(" "))
word_count = sorted(word_count.items(), key=lambda d:int(d[1]), reverse = True)
print(word_count[0])
# ('3750', 197997)
print(word_count[1])
# ('900', 197653)
print(word_count[2])
# ('648', 191975)
print(word_count[:5])
# [('3750', 197997), ('900', 197653), ('648', 191975), ('2465', 177310), ('6122', 176543)]
看看测试集的:
all_lines = ' '.join(list(test_df['text']))
word_count_test = Counter(all_lines.split(" "))
word_count_test = sorted(word_count_test.items(), key=lambda d:d[1], reverse = True)
print(len(word_count_test))
# 6203
print(word_count_test[0])
# ('3750', 1879488)
print(word_count_test[-1])
# ('1224', 1)
test_df['text_unique'] = test_df['text'].apply(lambda x: ' '.join(list(set(x.split(' ')))))
all_lines = ' '.join(list(test_df['text_unique']))
word_count_test = Counter(all_lines.split(" "))
word_count_test = sorted(word_count_test.items(), key=lambda d:int(d[1]), reverse = True)
print(word_count_test[0])
# ('3750', 49455)
print(word_count_test[1])
# ('900', 49366)
print(word_count_test[2])
# ('648', 47966)
和训练集的情况类似。
训练集和测试集合在一起:
vocab_all = dict()
for text in train_df['text']:
for word in text.split():
if vocab_all.get(word):
vocab_all[word] += 1
else:
vocab_all[word] = 1
for text in test_df['text']:
for word in text.split():
if vocab_all.get(word):
vocab_all[word] += 1
else:
vocab_all[word] = 1
chars_all = sorted(vocab_all.items(), key=lambda x: int(x[0]))
print(len(chars_all), chars_all[0], chars_all[-1])
word_count_all = sorted(vocab_all.items(), key=lambda d:d[1], reverse = True)
print(len(word_count_all), word_count_all[0], word_count_all[-1], word_count_all[:5])
输出:
6977 ('0', 31) ('7549', 14)
6977 ('3750', 9361712) ('1085', 1) [('3750', 9361712), ('648', 6157412), ('900', 4081309), ('3370', 2532394), ('6122', 2004576)]
假设字符3750,字符900和字符648是句子的标点符号,请分析赛题每篇新闻平均由多少个句子构成?
import re
# 训练集
train_df['sent_len'] = train_df['text'].apply(lambda x: len([x for x in re.split('3750|900|648', x) if x.strip()!='']))
print(train_df['sent_len'].describe())
# 测试集
test_df['sent_len'] = test_df['text'].apply(lambda x: len([x for x in re.split('3750|900|648', x) if x.strip()!='']))
print(test_df['sent_len'].describe())
输出结果:
# 训练集
count 200000.000000
mean 79.466030
std 85.383165
min 1.000000
25% 28.000000
50% 56.000000
75% 102.000000
max 3393.000000
Name: sent_len, dtype: float64
# 测试集
count 50000.000000
mean 79.747140
std 86.844506
min 1.000000
25% 28.000000
50% 56.000000
75% 102.000000
max 2751.000000
Name: sent_len, dtype: float64
所以每篇新闻平均由80个句子构成,最少的是1个句子,最多的是3000个左右。
统计每类新闻中出现次数最多的字符
for i in range(14):
all_lines = ' '.join(list(train_df[train_df['label']==i]['text']))
word_count_label = Counter([x for x in all_lines.split(" ") if x not in ['3750', '648', '900']])
print(i, word_count_label.most_common(1))
这里去掉了可能是标点符号的字符的影响,输出结果
0 [('3370', 503768)]
1 [('3370', 626708)]
2 [('7399', 351894)]
3 [('6122', 187933)]
4 [('4411', 120442)]
5 [('6122', 159125)]
6 [('6248', 193757)]
7 [('3370', 159156)]
8 [('6122', 57345)]
9 [('7328', 46477)]
10 [('3370', 67780)]
11 [('4939', 18591)]
12 [('4464', 51426)]
13 [('4939', 9651)]
数据分析的结论
在数据集中标签的对应的关系如下:{'科技': 0, '股票': 1, '体育': 2, '娱乐': 3, '时政': 4, '社会': 5, '教育': 6, '财经': 7, '家居': 8, '游戏': 9, '房产': 10, '时尚': 11, '彩票': 12, '星座': 13}
1.文本长度:
- 训练集和测试集a的文本长度分布基本一致
- 训练集:文本长度的均值约为907字符,中值为676字符,最短文本为2字符,最长文本为57921字符
- 测试集a:文本长度的均值约为910字符,中值为676字符,最短文本为14字符,最长文本为41861字符
2.新闻类别:
- 科技类的样本数量最多:38918
- 星座类的样本数量最少:908
3.字符分布:
- 训练集总共6869个字符,从0到7549,中间不连续,有缺失
- 训练集+测试集总共6977个字符,从0到7549
- 出现频率最高的字符Top10:3750,648,900,3370,6122,4464,7399,4939,3659,4811
- 字符3750,字符900和字符648在20w新闻的覆盖率接近99%,很有可能是标点符号。
- 如果字符3750,900和648是标点符号,那么每篇新闻平均由80个句子构成,中值是56,最少的是1个句子,最多的是3000个左右。
通过上述分析我们可以得出以下结论:
- 赛题中每个新闻包含的字符个数平均为1000个,还有一些新闻字符较长;
- 赛题中新闻类别分布不均匀,科技类新闻样本量接近4w,星座类新闻样本量不到1k;
- 赛题总共包括7000-8000个字符;
通过数据分析,我们还可以得出以下结论:
- 每个新闻平均字符个数较多,可能需要截断;
- 由于类别不均衡,会严重影响模型的精度;
本章小结
本章对赛题数据进行读取,并新闻句子长度、类别和字符进行了可视化分析。
参考资料
https://github.com/datawhalechina/team-learning-nlp/tree/master/NewsTextClassification