文本分类(二)-文本预处理I-词频统计

文本数据与图像数据本质一样,图像本身的每个通道,像素点值就是数据本身。而文本数据要被计算机理解,首先要被处理成数值型,也就是说需要给文本编码,如embedding。这篇笔记记录将文本数值化。

实例

一个样本文件有5000条记录,存在于txt中。如下为样本文本中的索引为204的记录:

1
2
3
4
with open(train_file, 'r') as f:
lines = f.readlines()
print(len(lines))
print(lines[204])

输出:

1
2
5000
体育 绿衫军vs热火首发:新老三巨头对抗 小奥单挑大Z新浪体育讯北京时间5月4日,热火和凯尔特人迎来第二回合巅峰对决,本场比赛双方主帅均未对首发名单做出任何更改。而凯尔特人主帅道格-里弗斯赛前也向媒体表示,本场比赛“大鲨鱼”沙奎尔-奥尼尔将继续作壁上观,以下为双方首发名单——凯尔特人:隆多、雷-阿伦、皮尔斯、加内特、杰梅因-奥尼尔热火:毕比、韦德、詹姆斯、波什、伊尔戈斯卡斯(小林)

格式是:类别+样本描述。

分成label和content:

1
label, content = lines[204].strip('\r\n').split('\t')

分词

使用jieba对content进行分词:

1
2
3
4
5
6
7
8
9
word_iter = jieba.cut(content)

word_content = '' # 保存每一个词
for word in word_iter: # 对切分结果中每个词作如下操作
word = word.strip(' ')
if word != '':
word_content += word + ' '
out_line = '%s\t%s\n' % (label, word_content.strip(' '))
print(out_line)

写入文件的每一条数据格式:

1
体育	vs 热火 首发 : 新 老三 巨头 对抗 小奥 单挑 大 Z 新浪 体育讯 北京 时间 5 月 4 日 , 热火 和 凯尔特人 迎来 第二 回合 巅峰 对决 , 本场 比赛 双方 主帅 均 未 对 首发 名单 做出 任何 更改 。 而 凯尔特人 主帅 道 格 - 里 弗斯 赛前 也 向 媒体 表示 , 本场 比赛 “ 大 鲨鱼 ” 沙奎尔 - 奥尼尔 将 继续 作壁上观 , 以下 为 双方 首发 名单 — — 凯尔特人 : 隆多 、 雷 - 阿伦 、 皮尔斯 、 加内特 、 杰 梅因 - 奥尼尔 热火 : 毕比 、 韦德 、 詹姆斯 、 波什 、 伊尔 戈斯卡 斯 ( 小林 )

如上述过程将源文件中每一条记录执行此操作后写入目标文件。整理成函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def generate_seg_file(input_file, output_seg_file):
"""对input_file内容分词"""
with open(input_file, 'r') as f: # 读文件
lines = f.readlines()
with open(output_seg_file, 'w') as f: # 写的方式打开写文件
for line in lines: # 对于每一行
label, content = line.strip('\r\n').split('\t') # 分出lenbel和content
word_iter = jieba.cut(content) # 对content切分成词
word_content = '' # 保存每个次
for word in word_iter: # 对切分结果中每个词作如下操作:
word = word.strip(' ')
if word != '':
word_content += word + ' '
out_line = '%s\t%s\n' % (label, word_content.strip(' ')) # 输入文件每一行的格式

f.write(out_line) # 写入文件

# 对三个数据集作同样操作
generate_seg_file(train_file, seg_train_file)
generate_seg_file(val_file, seg_val_file)
generate_seg_file(test_file, seg_test_file)

此时三个样本集各自的分词结果。

统计词频

对上一步得到的分词后的文件进行词频统计。如对索引为204的记录统计:

1
2
3
4
5
6
7
8
9
with open(output_seg_file, 'r') as f:
lines = f.readlines()

word_dict = {}
label, content = lines[204].strip('\r\n').split('\t')
for word in content.split():
word_dict.setdefault(word, 0)
word_dict[word] += 1
print(word_dict)

结果为:

1
{'绿衫': 1, '军': 1, 'vs': 1, '热火': 3, '首发': 3, ':': 3, '新': 1, '老三': 1, '巨头': 1, '对抗': 1, '小奥': 1, '单挑': 1, '大': 2, 'Z': 1, '新浪': 1, '体育讯': 1, '北京': 1, '时间': 1, '5': 1, '月': 1, '4': 1, '日': 1, ',': 4, '和': 1, '凯尔特人': 3, '迎来': 1, '第二': 1, '回合': 1, '巅峰': 1, '对决': 1, '本场': 2, '比赛': 2, '双方': 2, '主帅': 2, '均': 1, '未': 1, '对': 1, '名单': 2, '做出': 1, '任何': 1, '更改': 1, '。': 1, '而': 1, '道': 1, '格': 1, '-': 4, '里': 1, '弗斯': 1, '赛前': 1, '也': 1, '向': 1, '媒体': 1, '表示': 1, '“': 1, '鲨鱼': 1, '”': 1, '沙奎尔': 1, '奥尼尔': 2, '将': 1, '继续': 1, '作壁上观': 1, '以下': 1, '为': 1, '—': 2, '隆多': 1, '、': 8, '雷': 1, '阿伦': 1, '皮尔斯': 1, '加内特': 1, '杰': 1, '梅因': 1, '毕比': 1, '韦德': 1, '詹姆斯': 1, '波什': 1, '伊尔': 1, '戈斯卡': 1, '斯': 1, '(': 1, '小林': 1, ')': 1}

此字典中键为词语,值为键出现的频数。当然这只是对一条记录的统计,对所有记录统计才有意义。

按词语频数排序,方便之后输入模型的截取操作。****

1
2
3
sorted_word_dict = sorted(
word_dict.items(), key = lambda d:d[1], reverse=True)
print(sorted_word_dict)

结果:

1
[('、', 8), (',', 4), ('-', 4), ('热火', 3), ('首发', 3), (':', 3), ('凯尔特人', 3), ('大', 2), ('本场', 2), ('比赛', 2), ('双方', 2), ('主帅', 2), ('名单', 2), ('奥尼尔', 2), ('—', 2), ('绿衫', 1), ('军', 1), ('vs', 1), ('新', 1), ('老三', 1), ('巨头', 1), ('对抗', 1), ('小奥', 1), ('单挑', 1), ('Z', 1), ('新浪', 1), ('体育讯', 1), ('北京', 1), ('时间', 1), ('5', 1), ('月', 1), ('4', 1), ('日', 1), ('和', 1), ('迎来', 1), ('第二', 1), ('回合', 1), ('巅峰', 1), ('对决', 1), ('均', 1), ('未', 1), ('对', 1), ('做出', 1), ('任何', 1), ('更改', 1), ('。', 1), ('而', 1), ('道', 1), ('格', 1), ('里', 1), ('弗斯', 1), ('赛前', 1), ('也', 1), ('向', 1), ('媒体', 1), ('表示', 1), ('“', 1), ('鲨鱼', 1), ('”', 1), ('沙奎尔', 1), ('将', 1), ('继续', 1), ('作壁上观', 1), ('以下', 1), ('为', 1), ('隆多', 1), ('雷', 1), ('阿伦', 1), ('皮尔斯', 1), ('加内特', 1), ('杰', 1), ('梅因', 1), ('毕比', 1), ('韦德', 1), ('詹姆斯', 1), ('波什', 1), ('伊尔', 1), ('戈斯卡', 1), ('斯', 1), ('(', 1), ('小林', 1), (')', 1)]

整理成函数,对所有记录统计。注意该操作只针对训练集:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def generate_vocab_file(input_seg_file, output_vocab_file):
"""将input_seg_file中做词频统计,输出到out_put_file中"""
with open(input_seg_file, 'r') as f:
lines = f.readlines()
word_dict = {}
for line in lines:
label, content = line.strip('\r\n').split('\t')
for word in content.split():
word_dict.setdefault(word, 0)
word_dict[word] += 1
# [(word, frequency), ..., ()]
sorted_word_dict = sorted(
word_dict.items(), key = lambda d:d[1], reverse=True)
with open(output_vocab_file, 'w') as f:
f.write('<UNK>\t10000000\n') # 当在测试集中找不到一个词时,用<UNK> 代替
for item in sorted_word_dict:
f.write('%s\t%d\n' % (item[0], item[1]))

# 只对已知的训练集执行此操作
generate_vocab_file(seg_train_file, vocab_file)

打开结果文件vocab_file查看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1 <UNK>   10000000
2 , 1871208
3 的 1390830
4 。 822140
5 在 303879
6 、 258508
7 了 248160
8 是 240938
...
508 同样 5874
509 正式 5868
510 故事 5867
511 13 5855
512 建筑 5854
513 代表 5850
514 主持人 5843
515 水平 5833
...
359234 各偏 1
359235 1.8782 1
359236 1.0307 1
359237 0.763 1
359238 87.82% 1
359239 0.5376 1

第一列为序号,表示第几个词,第二列为词(出现的数组和百分比都是文本中的实际数字),第三列为该词出现的频数。两点说明:

  • 频数太小的词无意义,因为神经网络为概率模型,只出现一次没有统计意义。
  • 而频数很高但通用的词(停用词)同样无意义,因为通用的词在分类问题中不能提供有用信息,从信息论角度看,使用通用词训练模型不能减少模型的不确定性。这两点之后会处理。

对label统计:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def generate_category_dict(input_file, category_file):
"""统计类别"""
with open(input_file, 'r') as f:
lines = f.readlines()
category_dict = {}
for line in lines:
label, content = line.strip('\r\n').split('\t')
category_dict.setdefault(label, 0)
category_dict[label] += 1
category_number = len(category_dict)
with open(category_file, 'w') as f:
for category in category_dict:
line = '%s\n' % category
print('%s\t%d' % (category, category_dict[category]))
f.write(line)

generate_category_dict(train_file, category_file)

结果写入catecory_file, 并且控制台输出:

1
2
3
4
5
6
7
8
9
10
体育	5000
娱乐 5000
家居 5000
房产 5000
教育 5000
时尚 5000
时政 5000
游戏 5000
科技 5000
财经 5000

类别频数相同,表示这是一个极度均匀数据集。这是理想的!

统计词的频率,是为了截取,每个词的数值化,可用其序号代替。

整个过程生产5个文件:

  • seg_train_file: 分词后的训练集
  • seg_val_file: 分词后的验证集
  • seg_test_file: 分词后的测试集
  • vocab_file: 训练集的词频统计
  • category_file: 训练集的类别统计

完整程序与数据文件这里。

敲黑板对于如本笔记所处理的极度均匀的数据集,评价模型时可以使用准确率。但是对于极度偏斜(Skewed Data)的数据,如在数据集中某类病症的发病样本数与未发病样本书之比远小于1,只使用准确率评价模型是远远不够的。这时就需要使用其他模型评估方法如混淆矩阵(Confusion Matrix)等。