文本挖掘(Text Mining)是从非结构化文本信息中获取用户感兴趣或者有用的模式的过程。
文本挖掘是指从大量文本数据中抽取事先未知的、可理解的、最终可用的知识的过程,同时运用这些知识更好地组织信息以便将来参考。
文本预处理
文本处理的核心任务是把非结构化和半结构化的文本转换为结构化的形式,即向量空间模型。
具体步骤:
1. 选择处理的文本范围
选择恰当的范围取决于文本挖掘任务的目标:
- 对于分类或聚类的任务,往往把整个文档作为处理单位;
- 对于情感分析、文档自动摘要或信息检索,段落或章节可能更合适。
2. 建立分类文本语库
- 训练集语料:已经分好类的文本资源。
- 测试集语料:待分类的文本语料,可以是训练集的一部分,也可以是外部来源的文本语料。
中文语料库
3. 文本格式转换
import sys
import os
import time
from lxml import etree, html
from sklearn.datasets.base import Bunch
import pickle
from time import time
root = 'D:/MLBook' + '/chapter02'
# htm文件路径,以及读取文件
path = root + "/1.htm"
with open(path, "rb") as fp:
content = fp.read().decode()
page = html.document_fromstring(content) # 解析文件
text = page.text_content() # 去除所有标签
print(text[:100]) # 输出去除标签后解析结果
百度百科_百度百科(function(){window.PDC={_timing:{},_opt:{sample:0.01},_analyzer:{loaded:false,url:"http://s
4. 检测句子边界:标记句子的结束
完全解决中文分词的算法是基于概率图模型的条件随机场(CRF)
import jieba
seg_list = jieba.cut("小明1995年毕业于北京清华大学", cut_all=False)
print("Default Mode:", " ".join(seg_list)) # 默认模式
seg_list = jieba.cut("小明1995年毕业于北京清华大学")
print(" ".join(seg_list))
seg_list = jieba.cut("小明1995年毕业于北京清华大学", cut_all=True)
print("Full Mode:", "/ ".join(seg_list)) # 全模式
seg_list = jieba.cut_for_search("小明硕士毕业于中国科学院计算所,\
后在日本京都大学深造") # 搜索引擎模式
print("/ ".join(seg_list))
Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\q7356\AppData\Local\Temp\jieba.cache
Loading model cost 0.992 seconds.
Prefix dict has been built succesfully.
Default Mode: 小明 1995 年 毕业 于 北京 清华大学
小明 1995 年 毕业 于 北京 清华大学
Full Mode: 小/ 明/ 1995/ 年/ 毕业/ 于/ 北京/ 清华/ 清华大学/ 华大/ 大学
小明/ 硕士/ 毕业/ 于/ 中国/ 科学/ 学院/ 科学院/ 中国科学院/ 计算/ 计算所/ ,/ 后/ 在/ 日本/ 京都/ 大学/ 日本京都大学/ 深造
seg_list = jieba.cut("小明终于在1995年从北京清华大学毕业了。")
print(" ".join(seg_list))
小明 终于 在 1995 年 从 北京 清华大学 毕业 了 。
os.listdir(root+ '/train_corpus_seg')
['art',
'computer',
'economic',
'education',
'environment',
'medical',
'military',
'politics',
'sports',
'traffic']
class Segment:
def __init__(self, root):
self.root = root
def save_file(self, save_path, content):
'''
保存文件
'''
with open(save_path, 'wb') as fp:
fp.write(content.encode())
def read_file(self, path):
with open(path, 'rb') as fp:
content = fp.read().decode()
return content
def get_seg(self, corpus_path, seg_path):
'''
获取每个目录下所有的文件 mydir in catelist
catelist = os.listdir(corpus_path)
参数
=====
corpus_path::未分词分类语料库路径,例如:"train_corpus_small/"
seg_path::分词后分类语料库路径,例如:"train_corpus_seg/"
'''
corpus_path = self.root + corpus_path
seg_path = self.root + seg_path
catelist = os.listdir(corpus_path)
start = time()
for i, mydir in enumerate(catelist):
class_path = corpus_path + mydir + "/" # 拼出分类子目录的路径
seg_dir = seg_path + mydir + "/" # 拼出分词后语料分类目录
if not os.path.exists(seg_dir): # 是否存在目录,如果没有创建
os.makedirs(seg_dir)
file_list = os.listdir(class_path) # 获取 class_path 下的所有文件
for k, file_path in enumerate(file_list): # 遍历类别目录下文件
fullname = class_path + file_path # 拼出文件名全路径
content = self.read_file(fullname).strip() # 读取文件内容
content = content.replace("\r\n", "") # 删除换行和多余的空格
content_seg = jieba.cut(content.strip()) # 为文件内容分词
self.save_file(seg_dir + file_path, " ".join(content_seg)) # 将处理后的文件保存到分词后语料目录
if k == 0 and i == 0:
print('保存分词后语料中......')
print('--' * 20)
if i == 0:
print(' 完成语料分词的类别依次为:')
print('\t%i: %s'%(i, mydir))
print('--' * 20)
print("总计花费时间 %g 秒,中文语料分词结束!!!"%(time() - start))
def get_bunch(self, wordbag_path, seg_path):
'''
获取每个目录下所有的文件 mydir in catelist
catelist = os.listdir(corpus_path)
参数
=====
wordbag_path::分词语料 Bunch 对象持久化路径,例如:"train_word_bag/train_set.dat"
seg_path::分词后分类语料库路径,例如:"train_corpus_seg/"
'''
wordbag_path = self.root + wordbag_path
seg_path = self.root + seg_path
catelist = os.listdir(seg_path)
bunch = Bunch(target_name=[], label=[], filenames=[], contents=[])
bunch.target_name.extend(catelist) # 将类别信息保存到 Bunch 对象
start = time()
for i, mydir in enumerate(catelist):
class_path = seg_path + mydir + "/" # 拼出分类子目录的路径
file_list = os.listdir(class_path) # 获取 class_path 下的所有文件
for k, file_path in enumerate(file_list): # 遍历类别目录下文件
fullname = class_path + file_path # 拼出文件名全路径
bunch.label.append(mydir) # 保存当前文件的分类标签
bunch.filenames.append(fullname) # 保存当前文件的文件路径
bunch.contents.append(self.read_file(fullname).strip()) # 保存文件词向量
if k == 0 and i == 0:
print('构建文本对象中......')
print('--' * 20)
if i == 0:
print(' 文本对象构建的类别依次为:')
print('\t%i: %s'%(i, mydir))
print('--' * 20)
# 对象持久化
with open(wordbag_path, "wb") as file_obj:
pickle.dump(bunch, file_obj)
print("总计花费时间 %g 秒,构建文本对象结束!!!"%(time() - start))
print("")
return bunch
root = 'D:/MLBook/chapter02/'
corpus_path = "train_corpus_small/" # 未分词分类语料库路径
seg_path = "train_corpus_seg/" # 分词后分类语料库路径
S = Segment(root)
S.get_seg(corpus_path, seg_path)
保存分词后语料中......
----------------------------------------
完成语料分词的类别依次为:
0: art
1: computer
2: economic
3: education
4: environment
5: medical
6: military
7: politics
8: sports
9: traffic
----------------------------------------
总计花费时间 19.8525 秒,中文语料分词结束!!!
在实际应用中,为了后续生成向量空间模型的方便,这些分词后的文本信息还要转换为文本向量信息并对象化。这里我们使用 Scikit-Learn 库的 Bunch 数据结构:
- Bunch 类提供一种
(key, value)
的对象形式 -
target_name
:所有分类集名称列表 -
label
:每个文件的分类标签列表 -
filenames
:文件路径 -
contents
:分词后文件词向量形式
from sklearn.datasets.base import Bunch
将分好词的文本转换并持久化为 Bunch 类形式:
wordbag_path = "train_word_bag/train_set.dat" # 分词语料 Bunch 对象持久化路径
seg_path = "train_corpus_seg/" # 分词后分类语料库路径
S = Segment(root)
bunch = S.get_bunch(wordbag_path, seg_path)
构建文本对象中......
----------------------------------------
文本对象构建的类别依次为:
0: art
1: computer
2: economic
3: education
4: environment
5: medical
6: military
7: politics
8: sports
9: traffic
----------------------------------------
总计花费时间 0.891003 秒,构建文本对象结束!!!
这样就在目录下生成了一个 train_set.dat
文件。此文件保存了所有训练集文件的所有分类信息,以及每个文件的文件名、文件所属分类和词向量。
Scikit-Learn 简介
向量空间模型
为节省存储空间和提高搜索效率,在文本分类之前会自动过滤掉某些字或词,这些字或词被称为停用词。这些词一般都是意义模糊的常用词和语气助词,通常它们对文本起不了分类特征的意义。
权重策略——TF-IDF 方法
文本1:My dog ate my homework
文本2:My cat ate the sandwich
文本3:A dolphin ate the homework
a = 'My dog ate my homework '
b = 'My cat ate the sandwich '
c = 'A dolphin ate the homework '
def str2list(s):
return s.lower().strip(' ').split(' ')
def wordbag(*args):
wb = []
for t in args:
wb.extend(str2list(t))
return set(wb)
def word2vec(wb, t):
a2 = []
for k in wb:
if k in str2list(t):
a2.append(1)
else:
a2.append(0)
return a2
wb = wordbag(*[a, b, c])
wb
{'a', 'ate', 'cat', 'dog', 'dolphin', 'homework', 'my', 'sandwich', 'the'}
将上述文本信息转换为词向量:
a2 = word2vec(wb, a)
b2 = word2vec(wb, b)
c2 = word2vec(wb, c)
print('文本1:', a2)
print('文本2:', b2)
print('文本3:', c2)
文本1: [1, 1, 0, 0, 1, 0, 0, 1, 0]
文本2: [0, 1, 1, 0, 0, 0, 1, 1, 1]
文本3: [1, 0, 0, 1, 0, 1, 1, 1, 0]
词频统计
def get_word_stat(w):
d = {}
for v in str2list(w):
d[v] = d.get(v, 0) + 1
return d
def word2vec(wb, t):
L = []
for k in wb:
d = get_word_stat(t)
if k in d.keys():
L.append(d[k])
else:
L.append(0)
return L
a2 = word2vec(wb, a) # 'my' 出现了两次
b2 = word2vec(wb, b)
c2 = word2vec(wb, c)
print('文本1:', a2)
print('文本2:', b2)
print('文本3:', c2)
文本1: [1, 2, 0, 0, 1, 0, 0, 1, 0]
文本2: [0, 1, 1, 0, 0, 0, 1, 1, 1]
文本3: [1, 0, 0, 1, 0, 1, 1, 1, 0]
对词向量进行归一化,将其转换为概率分布:
import numpy as np
def TF(t):
a3 = np.array(t)
return a3 / a3.sum()
a3 = TF(a2) # 'my' 出现了两次
b3 = TF(b2)
c3 = TF(c2)
print('文本1:', a3)
print('文本2:', b3)
print('文本3:', c3)
文本1: [ 0.2 0.4 0. 0. 0.2 0. 0. 0.2 0. ]
文本2: [ 0. 0.2 0.2 0. 0. 0. 0.2 0.2 0.2]
文本3: [ 0.2 0. 0. 0.2 0. 0.2 0.2 0.2 0. ]
将词频信息变成了概率分布,这就是文档的 TF 信息。
def word2vec2(wb, t):
L = []
for k in wb:
D = get_word_stat(a + b + c)
d = get_word_stat(t)
if k in d.keys():
L.append(D[k])
else:
L.append(0)
return L
def IDF(wb, t):
e = word2vec2(wb, t)
g = 3 / (np.array(e) + 1)
return np.log(g)
a4 = IDF(wb, a)
b4 = IDF(wb, b)
c4 = IDF(wb, c)
TFIDF1 = a3 * a4
TFIDF2 = b3 * b4
TFIDF3 = c3 * c4
TFIDF1
array([ 0. , -0.11507283, 0. , 0. , 0.08109302,
0. , 0. , -0.05753641, 0. ])
下面我们使用 Scikit-Learn 包来实现 TF-IDF 算法:
def read_file(path):
'''
读取停用词表
'''
with open(path, "rb") as fp:
content = fp.read()
return content.decode() # 转换为 string
class Word2Vector:
def __init__(self, root):
self.root = root
def read_bunch(self, path):
'''
读取 bunch 对象
'''
with open(self.root + path, "rb") as file_obj:
bunch = pickle.load(file_obj)
return bunch
def write_bunch(self, path, bunchobj):
'''
写入 bunch 对象
'''
with open(self.root + path, "wb") as file_obj:
pickle.dump(bunchobj, file_obj)
def read_stopword(self, stopword_path):
'''
读取停用词表
示例
=======
path = "train_word_bag/hlt_stop_words.txt"
'''
return read_file(self.root + stopword_path).splitlines()
def tfidf(self, stopword_path, path, space_path):
'''
参数
======
stopword_path::停用词路径
path::bunch 保存路径
space_path::词向量词袋保存路径
'''
start = time()
# 1. 读取停用词表
stpwrdlst = self.read_stopword(stopword_path)
# 2. 导入分词后的词向量 bunch 对象
bunch = self.read_bunch(path)
# 3. 构建 tf-idf 词向量空间对象
tfidfspace = Bunch(target_name=bunch.target_name, label=bunch.label,
filenames=bunch.filenames, tdm=[], vocabulary={})
# 4. 使用 TfidfVectorizer 初始化向量空间模型
vectorizer = TfidfVectorizer(stop_words=stpwrdlst, sublinear_tf = True, max_df = 0.5)
transformer = TfidfTransformer() # 该类会统计每个词语的 tf-idf 权值
# 文本转为词频矩阵,单独保存字典文件
tfidfspace.tdm = vectorizer.fit_transform(bunch.contents)
tfidfspace.vocabulary = vectorizer.vocabulary_
# 创建词袋的持久化
self.write_bunch(space_path, tfidfspace)
print("花费时间:%g 秒,TF-IDF 词向量空间创建成功!!!"%(time() - start))
from sklearn import feature_extraction
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.feature_extraction.text import TfidfVectorizer
WV = Word2Vector(root)
path = "train_word_bag/train_set.dat" # 词向量空间保存路径
stopword_path = "train_word_bag/hlt_stop_words.txt"
space_path = "train_word_bag/tfdifspace.dat" # 词向量空间保存路径
WV.tfidf(stopword_path, path, space_path)
花费时间:1.6075 秒,TF-IDF 词向量空间创建成功!!!
探寻有趣之事!