文本分类(二)-文本预处理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
21871208
31390830
4822140
5303879
6258508
7248160
8240938
...
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)等。