这是在接单过程中得到的一个好东西,感觉这个包含了所有文本处理的问题和方式,主要通过分析文本进行转换,学到了好多东西,我觉得现在接单不算是只为了挣钱而是多练手,多掌握数据分析过程以及多任务处理,我需要的是平台而不是工作。
NLTK包的安装
什么是NLTK
一个完整的自然语言处理框架
- 自带语料库,词性分类库
- 自带分类,分词,等等功能
- 有强大的社区支持
- 框架设计上没有考虑中文
NLTK的主要模块
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xszk2FH9-1574612235177)(./nltkm.png)]
如何安装NLTK
Anaconda中已经默认安装
- pip install nltk
- nltk.download()
import nltk
nltk.download()
import nltk
import ssl
try:
_create_unverified_https_context = ssl._create_unverified_context
except AttributeError:
pass
else:
ssl._create_default_https_context = _create_unverified_https_context
nltk.download()
NLTK替代包
NLTK的使用稍显复杂,初学者也可以使用各种基于NLTK的简化包
TextBlob
语料库的准备
什么是语料库
# 布朗语料库示例
from nltk.corpus import brown
brown.categories()
len(brown.sents())
brown.sents()[0]
len(brown.words())
常见的语料库格式
外部文件
除直接网络抓取并加工的情况外,原始文档由于内容较多,往往会首先以单个/多个文本文件的形式保存在外部,然后读入程序
list
结构灵活松散,有利于对原始语料进行处理,也可以随时增删成员
[
‘大鱼吃小鱼也吃虾米,小鱼吃虾米。’,
‘我帮你,你也帮我。’
]
list of list
语料完成分词后的常见形式,每个文档成为词条构成的list,而这些list又是原文档list的成员
[
[‘大鱼’, ‘吃’, ‘小鱼’, ‘也’, ‘吃’, ‘虾米’, ‘,’, ‘小鱼’, ‘吃’, ‘虾米’, ‘。’],
[‘我’, ‘帮’, ‘你’, ‘,’, ‘你’, ‘也’, ‘帮’, ‘我’, ‘。’]
]
DataFrame
使用词袋模型进行后续数据分析时常见格式,行/列代表语料index,相应的列/行代表词条,或者需要加以记录的文档属性,如作者,原始超链接,发表日期等
词条/文档对应时,单元格记录相应的词条出现频率,或者相应的概率/分值
Doc2Term矩阵
Term2Doc矩阵
可以和原始语料的外部文件/list配合使用
对于单个文档,也可以建立DataFrame,用行/列代表一个句子/段落/章节。
准备《射雕》语料库
为使用Python还不熟练的学员提供一个基于Pandas的通用操作框架。
读入为数据框
import pandas as pd
# 有的环境配置下read_table出错,因此改用read_csv
raw = pd.read_csv("金庸-射雕英雄传txt精校版.txt",
names = ['txt'], sep ='aaa', encoding ="utf-8" ,engine='python')
print(len(raw))
raw
加入章节标识
# 章节判断用变量预处理
def m_head(tmpstr):
return tmpstr[:1]
def m_mid(tmpstr):
return tmpstr.find("回 ")
raw['head'] = raw.txt.apply(m_head)
raw['mid'] = raw.txt.apply(m_mid)
raw['len'] = raw.txt.apply(len)
# raw['chap'] = 0
raw.head(50)
# 章节判断
chapnum = 0
for i in range(len(raw)):
if raw['head'][i] == "第" and raw['mid'][i] > 0 and raw['len'][i] < 30 :
chapnum += 1
if chapnum >= 40 and raw['txt'][i] == "附录一:成吉思汗家族" :
chapnum = 0
raw.loc[i, 'chap'] = chapnum
# 删除临时变量
del raw['head']
del raw['mid']
del raw['len']
raw.head(50)
提取出所需章节
raw[raw.chap == 7]
tmpchap = raw[raw.chap == 7].copy()
##tmpchap = raw[raw.chap == 7].copy()
#firstIdx = tmpchap.index[0]
#firstIdx
tmpchap.reset_index(drop=True, inplace=True)
tmpchap['paraidx'] = tmpchap.index
tmpchap['paralen'] = tmpchap.txt.apply(len)
tmpchap[tmpchap.paralen == tmpchap.paralen.max()]
#tmpchap.head(10)
tmppara = tmpchap.txt[2]
tmppara
import re
tmppara = tmpchap[tmpchap['paraidx'] == 100].copy()
tmppara
#tmpstr = tmppara.txt[224]
#tmpstr
#sentences = re.findall('(.*?[?。!;:](’|”)?)',tmpstr)
#sentences
from matplotlib import pyplot as plt
%matplotlib inline
raw.txt.agg(len).plot.box()
rawgrp = raw.groupby('chap')
chapter = rawgrp.sum()##.agg(sum) # 只有字符串列的情况下,sum函数自动转为合并字符串
chapter = chapter[chapter.index != 0]
chapter.txt[2]
实战1:准备工具与素材
请自行完成分析用Anaconda环境的安装和配置。
请自行熟悉Jupyter notebook环境的操作。
自行提取《射雕》任意一回的文字,并完成如下操作:
- 将其读入为按整句分案例行的数据框格式,并用另一个变量标识其所在段落的流水号。
- 将上述数据框转换为以整段为成员的list格式。
说明:
最后一题主要涉及到Pandas的操作,对该模块不熟悉的学员可直接继续后续课程的学习,这部分知识的欠缺并不会影响对文本挖掘课程本身的学习。当然,能懂得相应的知识是最好的
分词
分词原理简介
分词的算法分类
-
基于字符串的匹配
- 即扫描字符串,如果发现字符串的子串和词相同,就算匹配。
- 通常会加入一些启发式算法,比如“正向/反向最大匹配”,“长词优先”等
- 优点是速度快,但对歧义和未登录词处理不好
-
基于统计以及机器学习的分词方式
- 基于人工标注的词性和统计特征进行建模,并通过模型计算分词概率
- 常见的序列标注模型有HMM(隐马尔可夫)和CRF(条件随机场)
- 这类分词算法能很好的处理歧义和未登录词问题,效果比前一类要好,但是需要大量的人工标注数据,分词速度也较慢
注意:分词算法本身在中文文本挖掘里就是一个“巨坑”
基于字符串匹配的分词算法原理
-
以现有的词典为基础进行
-
最大匹配法:以设定的最大词长度为框架,取出其中最长的匹配词
- “中华人民共和国”会被完整取出,而不会进一步被分词
- 最佳匹配法:按照词典中的频率高低,优先取高频词
-
最大概率法:对句子整体进行分词,找到最佳的词汇排列组合规律
- 例:早上好->早上/好
-
最短路径分词:寻找单词数最少的分词方式
分词的难点
- 分词歧义
- 我个人没有意见
- 三个人没有意见
- 未登录词识别:蔡国庆
- 数字
- 实体名称/专业术语
- 成语
- 虚词、语气词
常见的分词工具
- 中科院计算所NLPIR http://ictclas.nlpir.org/
- 哈工大的LTP https://www.ltp-cloud.com/
- 斯坦福分词器 http://nlp.stanford.edu/software/segmenter.shtml
- 结巴分词 https://github.com/fxsjy/jieba
结巴分词的基本用法
jieba是目前应用最广,评价也较高的分词工具包
安装
https://pypi.python.org/pypi/jieba/
pip install jieba
基本特点
三种分词模式
精确模式,试图将句子最精确地切开,适合做文本分析
全模式,把句子中所有的可以成词的词语都扫描出来,速度非常快,但是不能解决歧义
搜索引擎模式,在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词
支持繁体分词
支持自定义词典
import jieba
tmpstr = "郭靖和哀牢山三十六剑。"
res = jieba.cut(tmpstr) # 精确模式
print(res) # 是一个可迭代的 generator,可以使用 for 循环来遍历结果,本质上类似list
print(' '.join(res))
res = jieba.cut(tmpstr)
list(word for word in res) # 演示generator的用法
print(jieba.lcut(tmpstr)) # 结果直接输出为list
print('/'.join(jieba.cut(tmpstr, cut_all = True))) # 全模式
# 搜索引擎模式,还有jieba.lcut_for_search可用
print('/'.join(jieba.cut_for_search(tmpstr)))
#raw['new'] =jieba.lcut( raw.txt.)
raw.head()
修改词典
动态增删新词
在程序中可以动态根据分词的结果,对内存中的词库进行更新
add_word(word)
word:新词
freq=None:词频
tag=None:具体词性
del_word(word)
# 动态修改词典
jieba.add_word("哀牢山三十六剑")
'/'.join(jieba.cut(tmpstr))
jieba.del_word("哀牢山三十六剑")
'/'.join(jieba.cut(tmpstr))
使用自定义词典
load_userdict(file_name)
file_name:文件类对象或自定义词典的路径
词典基本格式
一个词占一行:词、词频(可省略)、词性(可省略),用空格隔开
词典文件必须为 UTF-8 编码
必要时可以使用Uedit32进行文件编码转换
云计算 5
李小福 2 nr
easy_install 3 eng
台中
dict = '金庸小说词库.txt'
jieba.load_userdict(dict) # dict为自定义词典的路径
'/'.join(jieba.cut(tmpstr))
使用搜狗细胞词库
https://pinyin.sogou.com/dict/
按照词库分类或者关键词搜索方式,查找并下载所需词库
使用转换工具,将其转换为txt格式
深蓝词库转换
奥创词库转换
在程序中导入相应词库
去除停用词
常见的停用词种类
超高频的常用词:基本不携带有效信息/歧义太多无分析价值
的、地、得
虚词:如介词,连词等
只、条、件
当、从、同
专业领域的高频词:基本不携带有效信息
视情况而定的停用词
呵呵
emoj
分词后去除停用词
基本步骤
读入停用词表文件
正常分词
在分词结果中去除停用词
新列表 = [ word for word in 源列表 if word not in 停用词列表 ]
该方法存在的问题:停用词必须要被分词过程正确拆分出来才行
newlist = [ w for w in jieba.cut(tmpstr) if w not in ['和', '。'] ]
print(newlist)
import pandas as pd
tmpdf = pd.read_csv('停用词.txt',
names = ['w'], sep = 'aaa', encoding = 'utf-8')
tmpdf.head()
tmpchap = raw[raw.chap == 7].copy()
##tmpchap = raw[raw.chap == 7].copy()
#firstIdx = tmpchap.index[0]
#firstIdx
tmpchap.reset_index(drop=True, inplace=True)
tmpchap['paraidx'] = tmpchap.index
tmpchap['paralen'] = tmpchap.txt.apply(len)
paratxt = tmpchap[tmpchap.paralen == tmpchap.paralen.max()].txt
paratxt.reset_index(drop=True, inplace=True)
# 熟悉Python的可以直接使用 open('stopWord.txt').readlines() 获取停用词list,效率更高
[ w for w in jieba.cut(paratxt[0]) if w not in list(tmpdf.w) ]
用extract_tags函数去除停用词
方法特点:
根据TF-IDF算法将特征词提取出来,在提取之前去掉停用词
可以人工指定停用词字典
jieba.analyse.set_stop_words()
# 使用预先准备的停用词表
import jieba.analyse as ana
ana.set_stop_words('停用词.txt')
jieba.lcut(tmpstr) # 读入的停用词列表对分词结果无效
ana.extract_tags(tmpstr, topK = 20) # 使用TF-IDF算法提取关键词,并同时去掉停用词
词性标注
import jieba.posseg
posseg.cut():给出附加词性的分词结果
词性标注采用和 ICTCLAS 兼容的标记法
后续可基于词性做进一步处理,如只提取出名词,动词等
import jieba.posseg as psg
tmpres = psg.cut(tmpstr) # 附加词性的分词结果
print(tmpres)
for item in tmpres:
print(item.word, item.flag)
psg.lcut(tmpstr) # 直接输出为list,成员为pair
分词的NLTK实现
NLTK只能识别用空格作为词条分割方式,因此不能直接用于中文文本的分词。
一般的做法是先用jieba分词,然后转换为空格分隔的连续文本,再转入NLTK框架使用。
rawtext = '周伯通笑道:“你懂了吗?…”
txt = ’ '.join(jieba.cut(rawtext)) # “周伯通 笑 道 :…”
toke = nltk.word_tokenize(txt) # [‘周伯通’, ‘笑’, ‘道’, ‘:’…]
实战2:《射雕》一书分词
选取第一回的文字,应用搜狗的细胞词库和停用词表,清理出干净的分词结果。
选取第一回中最长的1个段落,比较不使用词库、不使用停用词表前后的分词结果。
熟悉搜狗细胞词库网站中的资源,思考哪些词库可能是自己需要的,下载相应的资源并进行格式转换。
tmpchap = raw[raw.chap == 1].copy()
##tmpchap = raw[raw.chap == 1].copy()
#firstIdx = tmpchap.index[0]
#firstIdx
tmpchap.reset_index(drop=True, inplace=True)
tmpchap['paraidx'] = tmpchap.index
tmpchap['paralen'] = tmpchap.txt.apply(len)
paratxt = tmpchap[tmpchap.paralen == tmpchap.paralen.max()].txt
paratxt.reset_index(drop=True, inplace=True)
# 熟悉Python的可以直接使用 open('stopWord.txt').readlines() 获取停用词list,效率更高
[ w for w in jieba.cut(paratxt[0]) if w not in list(tmpdf.w) ]
词云展示
词频统计
绝大部分词频统计工具都是基于分词后构建词条的list进行,因此首先需要完成相应的分词工作。
import pandas as pd
# 载入语料
raw = pd.read_csv("金庸-射雕英雄传txt精校版.txt",
names = ['txt'], sep ='aaa', encoding ="utf-8" ,engine='python')
# 章节判断用变量预处理
def m_head(tmpstr):
return tmpstr[:1]
def m_mid(tmpstr):
return tmpstr.find("回 ")
raw['head'] = raw.txt.apply(m_head)
raw['mid'] = raw.txt.apply(m_mid)
raw['len'] = raw.txt.apply(len)
# 章节判断
chapnum = 0
for i in range(len(raw)):
if raw['head'][i] == "第" and raw['mid'][i] > 0 and raw['len'][i] < 30 :
chapnum += 1
if chapnum >= 40 and raw['txt'][i] == "附录一:成吉思汗家族" :
chapnum = 0
raw.loc[i, 'chap'] = chapnum
# 删除临时变量
del raw['head']
del raw['mid']
del raw['len']
#取出特定的某一回
chapidx = 1
raw[raw.chap == chapidx]
tmpchap = raw[raw.chap == chapidx].copy()
##tmpchap = raw[raw.chap == 7].copy()
#firstIdx = tmpchap.index[0]
#firstIdx
tmpchap.reset_index(drop=True, inplace=True)
tmpchap['paraidx'] = tmpchap.index
tmpchap['paralen'] = tmpchap.txt.apply(len)
#tmpchap[tmpchap.paralen == tmpchap.paralen.max()]
alltxt = "".join(tmpchap.txt[1:])
alltxt
import jieba
dict = '金庸小说词库.txt'
jieba.load_userdict(dict) # dict为自定义词典的路径
tmpdf = pd.read_csv('停用词.txt',
names = ['w'], sep = 'aaa', encoding = 'utf-8',engine='python')
#分词
#word_list = jieba.lcut(chapter.txt[1])
#word_list[:10]
word_list = [ w for w in jieba.cut(alltxt) if w not in list(tmpdf.w) ]
Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\CHENYA~1\AppData\Local\Temp\jieba.cache
Loading model cost 0.866 seconds.
Prefix dict has been built succesfully.
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
<ipython-input-1-01dab9076eb7> in <module>
2
3 dict = '金庸小说词库.txt'
----> 4 jieba.load_userdict(dict) # dict为自定义词典的路径
5 tmpdf = pd.read_csv('停用词.txt',
6 names = ['w'], sep = 'aaa', encoding = 'utf-8',engine='python')
E:\Anaconda3_2019.07\lib\site-packages\jieba\__init__.py in load_userdict(self, f)
372 if isinstance(f, string_types):
373 f_name = f
--> 374 f = open(f, 'rb')
375 else:
376 f_name = resolve_filename(f)
FileNotFoundError: [Errno 2] No such file or directory: '金庸小说词库.txt'
构建完list之后,也可以自行编写词频统计程序,框架如下:
遍历整个list,对每个词条:
if word in d:
d[word] += 1
else:
d[word] = 1
使用Pandas统计
df = pd.DataFrame(word_list, columns = ['word'])
result = df.groupby(['word']).size()
print(type(result))
freqlist = result.sort_values(ascending=False)
freqlist
freqlist[freqlist.index == '道']
freqlist[freqlist.index == '道'][0]
freqlist[freqlist.index == '黄蓉道']
使用NLTK统计
NLTK生成的结果为频数字典,在和某些程序包对接时比较有用
import nltk
fdist = nltk.FreqDist(word_list) # 生成完整的词条频数字典
fdist
# 带上某个单词, 可以看到它在整个文章中出现的次数
fdist['颜烈']
fdist.keys() # 列出词条列表
fdist.tabulate(10)
fdist.most_common(5)
词云概述¶
wordcloud包的安装
安装
docker的kaggle下推荐安装方法:
conda install -c conda-forge wordcloud
常规安装方法(docker或windows下):
pip install wordcloud
警告:常规方法安装wordcloud有可能非常顺利,也有可能会出各种问题
中文字体支持
.WordCloud(font_path=‘msyh.ttf’)
需要带路径写完整字体文件名
注意Win10的字体文件后缀可能不一样
绘制词云
WordCloud的基本语法
class wordcloud.WordCloud(
font_path,
width,
height,
max_words,
stopwords,
min_font_size,
font_step,
relative_scaling,
prefer_horizontal,
background_color,
mode,
color_func,
mask
)
常用功能:
font_path : 在图形中使用的字体,默认使用系统字体
width / height = 200 : 图形的宽度/高度
max_words = 200 : 需要绘制的最多词条数
stopwords = None : 停用词列表,不指定时会使用系统默认停用词列表
字体设定:
min_font_size = 4 / max_font_size = None : 字符大小范围
font_step = 1 : 字号增加的步长
relative_scaling = .5: 词条频数比例和字号大小比例的换算关系,默认为50%
prefer_horizontal = 0.90 : 图中词条水平显示的比例
颜色设定:
background_color = ”black” : 图形背景色
mode = ”RGB”: 图形颜色编码,如果指定为"RGBA"且背景色为None时,背景色为透明
color_func = None : 生成新颜色的函数,使用matplotlib的colormap
背景掩模:
mask = None : 词云使用的背景图(遮罩)
用原始文本直接分词并绘制
cloudobj = WordCloud().generate(text)
generate实际上是generate_from_text的别名
文本需要用空格/标点符号分隔单词,否则不能正确分词
import wordcloud
myfont = 'msyh.ttf'
text = 'this is shanghai, 郭靖, 和, 哀牢山 三十六剑'
cloudobj = wordcloud.WordCloud(font_path = myfont).generate(text)
print(cloudobj)
显示词云
import matplotlib.pyplot as plt
plt.imshow(cloudobj)
plt.axis(“off”)
plt.show()
import matplotlib.pyplot as plt
plt.imshow(cloudobj)
plt.axis("off")
plt.show()
# 更改词云参数设定
cloudobj = wordcloud.WordCloud(font_path = myfont,
width = 360, height = 180,
mode = "RGBA", background_color = None).generate(text)
plt.imshow(cloudobj)
plt.axis("off")
plt.show()
保存词云
wordcloud.to_file(保存文件的路径与名称) 该命令保存的是高精度图形
cloudobj.to_file("词云.png")
生成射雕第一章的词云
cloudobj = wordcloud.WordCloud(font_path = myfont,
width = 1200, height = 800,
mode = "RGBA", background_color = None).generate(' '.join(word_list))
plt.imshow(cloudobj)
plt.axis("off")
plt.show()
cloudobj.to_file("词云2.png")
基于分词频数绘制
generate()的实际操作
调用分词函数process_text()
调用基于频数的绘制函数fit_words()
fit_words(dict)
实际上是generate_from_frequencies的别名
Dict: 由词条和频数构成的字典
#基于分词频数绘制词云
txt_freq = {'张三':100,'李四':90,'王二麻子':50}
cloudobj = wordcloud.WordCloud(font_path = myfont).fit_words(txt_freq)
plt.imshow(cloudobj)
plt.axis("off")
plt.show()
用频数生成射雕第一章的词云
cloudobj = wordcloud.WordCloud(font_path = myfont).fit_words(fdist)
plt.imshow(cloudobj)
plt.axis("off")
plt.show()
词云的美化
各种详细的操作和设定可以参考官网的案例:
https://amueller.github.io/word_cloud/
设置背景图片
Mask / 掩模 / 遮罩
用于控制词云的整体形状
指定mask后,设置的宽高值将被忽略,遮罩形状被指定图形的形状取代。除全白的部分仍然保留外,其余部分会用于绘制词云。因此背景图片的画布一定要设置为白色(#FFFFFF)
字的大小,布局和颜色也会基于Mask生成
必要时需要调整颜色以增强可视效果
基本调用方式
from scipy.misc import imread
mask = imread(背景图片名称)
from scipy.misc import imread
cloudobj = wordcloud.WordCloud(font_path = myfont,
mask = imread("射雕背景1.png"),
mode = "RGBA", background_color = None
).generate(' '.join(word_list))
plt.imshow(cloudobj)
plt.axis("off")
plt.show()
指定图片色系
读取指定图片的色系设定
imgarray = np.array(imread(imgfilepath))
获取图片颜色
bimgColors = wordcloud.ImageColorGenerator(imgarray)
重置词云颜色
cloudobj.recolor(color_func=bimgColors)
# 利用已有词云对象直接重绘颜色,输出速度要比全部重绘快的多
import numpy as np
imgobj = imread("射雕背景2.png")
image_colors = wordcloud.ImageColorGenerator(np.array(imgobj))
cloudobj.recolor(color_func=image_colors)
plt.imshow(cloudobj)
plt.axis("off")
plt.show()
指定单词组颜色
理想的状况应该是分组比较词频,在两组中都高频的词条在图形中相互抵消。
Python目前只能实现词条分组上色。
color_to_words = {
‘#00ff00’: [‘颜烈’, ‘武官’, ‘金兵’, ‘小人’],
‘red’: [‘包惜弱’, ‘郭啸天’, ‘杨铁心’, ‘丘处机’]
} '#00ff00’为绿色的代码
default_color = ‘grey’ # 其余单词的默认颜色
cloudobj.recolor()
# 官网提供的颜色分组类代码,略有修改
from wordcloud import get_single_color_func
class GroupedColorFunc(object):
def __init__(self, color_to_words, default_color):
self.color_func_to_words = [
(get_single_color_func(color), set(words))
for (color, words) in color_to_words.items()]
self.default_color_func = get_single_color_func(default_color)
def get_color_func(self, word):
"""Returns a single_color_func associated with the word"""
try:
color_func = next(
color_func for (color_func, words) in self.color_func_to_words
if word in words)
except StopIteration:
color_func = self.default_color_func
return color_func
def __call__(self, word, **kwargs):
return self.get_color_func(word)(word, **kwargs)
######
# 指定分组色系
color_to_words = {
'#00ff00': ['颜烈', '武官', '金兵', '官兵'],
'red': ['包惜弱', '郭啸天', '杨铁心', '丘处机']
}
default_color = 'grey' # 指定其他词条的颜色
grouped_color_func = GroupedColorFunc(color_to_words, default_color)
cloudobj.recolor(color_func=grouped_color_func)
plt.imshow(cloudobj)
plt.axis("off")
plt.show()
实战3:优化射雕词云
尝试进一步清理分词结果,并且只保留所有的名称(人名、地名)。
提示:可以使用词性标注功能,只保留名词和未知词性的词。
可以考虑对自定义词典做优化,通过强行调整权重等方法改善分词效果。
将所有的人名按照蓝色系,地名按照红色系进行词云绘制。
自行制作两个纯色图片,分别为绿色和蓝色,然后将其分别指定为绘图所用的色系,观察图形效果。
尝试使用不同的背景图片作为掩模,思考怎样的图片才能使得绘图效果最佳。
文档信息的向量化
所谓文档信息的向量化,就是将文档信息***数值化*** ,从而便于进行建模分析。
词袋模型(One-hot表示方式)
- 几乎是最早的用于提取文本特征的方法
- 将文本直接简化为一系列词的集合
- 不考虑其语法和词序关系,每个词都是独立的
- 举例:
- 对语料进行清理,并完成分词
- 大鱼/吃/小鱼/也/吃/虾米,小鱼吃虾米。
- 对每个词进行编号,形成字典(顺序无关的流水号即可)
- {“大鱼”:1,“吃”:2,“小鱼”:3,“也”:4,“虾米”:5}
- 用0/1代表该词是否在文本中出现,从而将文本记录为一个特征向量
- 大鱼吃小鱼也吃虾米 ->[大鱼,吃,小鱼,也,虾米]->[1,2,1,1,1]
- 小鱼吃虾米 ->[小鱼,吃,虾米]->[0,1,1,0,1]
- 该方式也被称为词袋模型,Bag of Words,BOW
- 词和文本的关联就相当于文本是一个袋子,词只是直接装在袋子里
- 显然,词袋模型是比较简单的模型,对文本中的信息有较多丢失,但已经可以解决很多实际问题
- 词袋模型的提出最初是为了解决文档分类问题,目前主要应用在NLP(Natural Language Process),IR(Information Retrival),CV(Computer Vision)等领域
- 也可以不考虑词频,减少模型复杂度
- 词集模型:Set Of Words,单词构成的集合,常见于短文本分析
- 大鱼吃小鱼也吃虾米 ->[大鱼,吃,小鱼,也,虾米]->[1,1,1,1,1]
- 优点:
- 解决了分类器不好处理离散数据的问题
- 在一定程度上也起到了扩充特征的作用
- 缺点:
- 不考虑词与词之间的顺序
- 它假设词与词之间相互独立(在大多数情况下,词与词是相互有关联的)
- 老公 vs 老婆,老婆 vs 孩子他妈
- 它得到的特征是离散稀疏的(维度灾难)
- 每个词都是茫茫"0"海中的一个1:[0 0 0 0 0 1 0 0 0 0 0 0 …]
词袋模型的gensim实现
gensim的安装
pip install gensim
或者
conda install gensim
安装完成后如果使用word2vec时报错,建议去gensim官网下载MS windows install的exe程序进行安装:
https://pypi.python.org/pypi/gensim
建立字典
Dictionary类用于建立word<->id映射关系,把所有单词取一个set(),并对set中每个单词分配一个Id号的map
class gensim.corpora.dictionary.Dictionary(
documents=None : 若干个被拆成单词集合的文档的集合,一般以list in list形式出现
prune_at=2000000 : 字典中的最大词条容量
)
from gensim.corpora import Dictionary
texts = [['human', 'interface', 'computer']]
dct = Dictionary(texts) # fit dictionary
dct.num_nnz
Dictionary类的属性
token2id
dict of (str, int) – token -> tokenId.
id2token
dict of (int, str) – Reverse mapping for token2id, initialized in lazy manner to > save memory.
dfs
dict of (int, int) – Document frequencies: token_id -> in how many documents > > > contain this token.
num_docs
int – Number of documents processed.
num_pos
int – Total number of corpus positions (number of processed words).
num_nnz
int – Total number of non-zeroes in the BOW matrix.
# 向字典增加词条
dct.add_documents([["cat", "say", "meow"], ["dog"]])
dct.token2id
转换为BOW稀疏向量
dct.doc2bow( # 转换为BOW格式:list of (token_id, token_count)
document : 用于转换的词条list
allow_update = False : 是否直接更新所用字典
return_missing = False : 是否返回新出现的(不在字典中的)词
)
输出结果
[(0, 2), (1, 2)],表明在文档中id为0,1的词汇各出现了2次,至于其他词汇则没有出现
return_missing = True时,输出list of (int, int), dict of (str, int)
dct.doc2bow(["this", "is", "cat", "not", "a", "dog"])
dct.doc2bow(["this", "is", "cat", "not", "a", "dog"], return_missing = True)
转换为BOW长向量
可考虑的思路:
从稀疏格式自行转换。
直接生成文档-词条矩阵。
doc2idx( # 转换为list of token_id
document : 用于转换的词条list
unknown_word_index = -1 : 为不在字典中的词条准备的代码
输出结果
按照输入list的顺序列出所出现的各词条ID
dct.doc2idx(["this", "is", "a", "dog", "not", "cat"])
生成文档-词条矩阵
用Pandas库实现
基本程序框架:
原始文档分词并清理
拼接为同一个dataframe
汇总并转换为文档-词条矩阵格式
去除低频词
import pandas as pd
# 载入语料
raw = pd.read_csv("金庸-射雕英雄传txt精校版.txt",
names = ['txt'], sep ='aaa', encoding ="utf-8" ,engine='python')
# 章节判断用变量预处理
def m_head(tmpstr):
return tmpstr[:1]
def m_mid(tmpstr):
return tmpstr.find("回 ")
raw['head'] = raw.txt.apply(m_head)
raw['mid'] = raw.txt.apply(m_mid)
raw['len'] = raw.txt.apply(len)
# 章节判断
chapnum = 0
for i in range(len(raw)):
if raw['head'][i] == "第" and raw['mid'][i] > 0 and raw['len'][i] < 30 :
chapnum += 1
if chapnum >= 40 and raw['txt'][i] == "附录一:成吉思汗家族" :
chapnum = 0
raw.loc[i, 'chap'] = chapnum
# 删除临时变量
del raw['head']
del raw['mid']
del raw['len']
rawgrp = raw.groupby('chap')
chapter = rawgrp.agg(sum) # 只有字符串列的情况下,sum函数自动转为合并字符串
chapter = chapter[chapter.index != 0]
chapter.head()
# 设定分词及清理停用词函数
# 熟悉Python的可以使用 open('stopWord.txt').readlines() 获取停用词list,效率更高
stoplist = list(pd.read_csv('停用词.txt', names = ['w'], sep = 'aaa',
encoding = 'utf-8', engine='python').w)
import jieba
def m_cut(intxt):
return [ w for w in jieba.cut(intxt)
if w not in stoplist and len(w) > 1 ]
# 设定数据框转换函数
def m_appdf(chapnum):
tmpdf = pd.DataFrame(m_cut(chapter.txt[chapnum + 1]), columns = ['word'])
tmpdf['chap'] = chapter.index[chapnum] # 也可以直接 = chapnum + 1
return tmpdf
# 全部读入并转换为数据框
df0 = pd.DataFrame(columns = ['word', 'chap']) # 初始化结果数据框
for chapidx in range(len(chapter)):
df0 = df0.append(m_appdf(chapidx))
df0.tail(50)
# 输出为序列格式
df0.groupby(['word', 'chap']).agg('size').head()
# 直接输出为数据框
t2d = pd.crosstab(df0.word, df0.chap)
len(t2d)
t2d.head(100)
# 计算各词条的总出现频次,准备进行低频词删减,axis=1表示按行统计
totnum = t2d.agg(func = 'sum', axis=1)
totnum
#按行选取
t2dclean = t2d.iloc[list(totnum >= 10),:]
#求转置
t2dclean.T
用sklearn库实现
CountVectorizer类的基本用法
文本信息在向量化之前很难直接纳入建模分析,考虑到这一问题,专门用于数据挖掘的sklearn库提供了一个从文本信息到数据挖掘模型之间的桥梁,即CountVectorizer类,通过这一类中的功能,可以很容易地实现文档信息的向量化。
class sklearn.feature_extraction.text.CountVectorizer(
input = ‘content’ : {‘filename’, ‘file’, ‘content’}
#filename为所需读入的文件列表, file则为具体的文件名称。
encoding=‘utf-8’ #文档编码
stop_words = None #停用词列表,当analyzer == 'word’时才生效
min_df / max_df : float in range [0.0, 1.0] or int, default = 1 / 1.0
#词频绝对值/比例的阈值,在此范围之外的将被剔除
#小数格式说明提供的是百分比,如0.05指的就是5%的阈值)
CountVectorizer.build_analyzer()
#返回文本预处理和分词的可调用函数
from sklearn.feature_extraction.text import CountVectorizer
countvec = CountVectorizer(min_df = 2) # 在两个以上文档中出现的才保留
analyze = countvec.build_analyzer()
analyze('郭靖 和 哀牢山 三十六 剑 。')
countvec.fit(['郭靖 和 黄蓉 哀牢山 三十六 剑 。', '黄蓉 和 郭靖 郭靖'])
countvec.get_feature_names() # 词汇列表,实际上就是获取每个列对应的词条
countvec.vocabulary_ # 词条字典
x = countvec.transform(['郭靖 和 黄蓉 哀牢山 三十六 剑 。', '黄蓉 和 郭靖 郭靖'])
x
x.todense() # 将稀疏矩阵直接转换为标准格式矩阵
countvec.fit_transform(['郭靖 和 哀牢山 三十六 剑 。', '黄蓉 和 郭靖 郭靖']) # 一次搞定
使用sklearn生成射雕的章节d2m矩阵
将章节文档数据框处理为空格分隔词条的文本格式
使用fit_transform函数生成bow稀疏矩阵
转换为标准格式的d2m矩阵
rawchap = [ " ".join(m_cut(w)) for w in chapter.txt.iloc[:5]]
rawchap[0]
from sklearn.feature_extraction.text import CountVectorizer
countvec = CountVectorizer(min_df = 5) # 在5个以上章节中出现的才保留
res = countvec.fit_transform(rawchap)
res
res.todense()
countvec.get_feature_names()
从词袋模型到Bi-gram
-
词袋模型完全无法利用语序信息
-
我帮你 vs 你帮我
-
P(我帮你) = P(我)*P(帮)*P(你)
-
-
Bi-gram:进一步保留顺序信息,两个词一起看
-
P(我帮你) = P(我)*P(帮|我)*P(你|帮)
-
{“我帮”:1, “帮你”:2,“你帮”:3,“帮我”:4}
-
我帮你 -> [1,1,0,0]
-
你帮我 -> [0,0,1,1]
-
-
显然,Bi-gram可以保留更多的文本有效信息。
从Bi-gram到N-gram
-
考虑更多的前后词
- 可以直接扩展至tri-gram,4-gram直至N-gram
-
优点:考虑了词的顺序,信息量更充分
- 长度达到5之后,效果有明显提升
-
缺点:
-
词表迅速膨胀,数据出现大量的稀疏化问题
-
没增加一个词,模型参数增加40万倍
-
离散表示方式所面临的问题总结
-
无法衡量词向量之间的关系
-
老公 [0,1,0,0,0,0,0,0,0,0]
-
丈夫 [0,0,0,0,1,0,0,0,0,0]
-
当家的 [0,0,0,0,0,0,0,1,0,0]
-
挨千刀的 [0,0,0,0,0,0,0,0,1,0]
-
各种度量(与或费、距离)都不合适,只能靠字典进行补充
-
-
词表维度随着语料库增长膨胀
-
N-gram词序列随语料库膨胀更快
-
数据稀疏问题(导致分析性能成为严重瓶颈)
文档信息的分布式表示
分布式表示(Distributed representation)
-
如何定位围棋棋盘上的落子位置?
-
方法一:每个点单独记忆,共361个记忆单元
-
方法二:行坐标+列坐标,共19+19=38个记忆单元
-
-
将分布式表示用于NLP
-
不直接考虑词与词在原文中的相对位置、距离、语法结构等,先把每个词看作一个单独向量
-
根据一个词在上下文中的临近词的含义,应当可以归纳出词本身的含义
-
单个词的词向量不足以表示整个文本,能表示的仅仅只是这个词本身
-
事先决定用多少维度的向量来表示这个词条
-
维度以50维和100维比较常见
-
向量中每个维度的取值由模型训练决定,且不再是唯一的
- [0.762, 0.107, 0.307, -0.199, 0.521,…]
-
-
所有的词都在同一个高维空间中构成不同的向量
- 从而词与词之间的关系就可以用空间中的距离来加以表述
-
所有训练方法都是在训练语言模型的同时,顺便得到词向量的
- 语言模型其实就是看一句话是不是正常人说出来的,具体表现为词条先后出现的顺序和距离所对应的概率是否最大化
-
共现矩阵 (Cocurrence matrix)
-
例如:语料库如下:
-
I like deep learning.
-
I like NLP.
-
I enjoy flying.
-
-
确定取词长度:
- 取词长度为1的结果
-
窗口长度越长,则信息量越丰富,但数据量也越大
- 一般设为5–10
-
共现矩阵的行/列数值自然就表示出各个词汇的相似度
- 从而可以用作分析向量
则共现矩阵表示如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9xGo4dMr-1574612235181)(./concurrence_matrix.jpg)]
例如:“I like”出现在第1,2句话中,一共出现2次,所以=2。
对称的窗口指的是,“like I”也是2次
将共现矩阵行(列)作为词向量表示后,可以知道like,enjoy都是在I附近且统计数目大约相等,他们意思相近
仍然存在的问题
-
如果将共现矩阵(列)直接作为词向量
-
向量维数随着词典大小线性增长
-
存储整个词典的空间消耗非常大
-
一些模型如文本分类模型会面临稀疏性问题
-
高度的稀疏性导致模型会欠稳定
-
实战4:生成词向量
尝试编制以下程序:
以段为单位依次读入射雕第一回的内容。
为每一段分别生成bow稀疏向量。
生成稀疏向量的同时动态更新字典。
请自行编制bow稀疏向量和标准长向量互相转换的程序。
在文档词条矩阵中可以看到许多类似“黄蓉道”、“黄蓉说”之类的词条,请思考对此有哪些处理办法。
关键词提取
用途
-
用核心信息代表原始文档
-
在文本聚类、分类、自动摘要等领域中有着重要应用
需求:针对一篇文章,在不加人工干预的情况下提取关键词
当然,首先要进行分词
关键词分配: 事先给定关键词库,然后在文档中进行关键词检索
关键词提取:根据某种规则,从文档中抽取最重要的词作为关键词
-
有监督:抽取出候选词并标记是否为关键词,然后训练相应的模型
-
无监督:给词条打分,并基于最高分值抽取
无监督方式的分析思路——基于词频
分析思路1:按照词频高低进行提取
-
大量的高频词并无多少意义(停用词)
-
即使出现频率相同,常见词价值也明显低于不常见词
分析思路2:按照词条在文档中的重要性进行提取
- 如何确定词条在该文档中的重要性?
常见的方法:TF-IDF、网络图
TF-IDF算法
信息检索(IR)中最常用的一种文本关键信息表示法
基本思想:
- 如果某个词在一篇文档中出现频率较高,并且在语料库中其他文本中出现频率较低,甚至不出现,则认为这个词具有很好的类别区分能力
词频TF:Term Frequency,衡量一个词在文档中出现的频率
- 平均而言出现越频繁的词,其重要性可能就越高
考虑到文章长度的差异,需要对词频做标准化
-
TF(w) = (w出现在文档中的次数)/(文档中的词的总数)
-
TF(w) = (w出现在文档中的次数)/(文档中出现最多的词的次数)
逆文档频率IDF:Inverse Document Frequency,用于模拟在该语料库中,某一个词有多重要
-
有些词到处出现,但是明显是没有用的。比如各种停用词,过渡句用词等。
-
因此把罕见的词的重要性(weight)调高,把常见词的重要性调低
IDF的具体算法
- IDF(w) = log(语料库中的文档总数/(含有该w的文档总数+1))
TF-IDF = TF * IDF
-
TF-IDF与一个词在文档中的出现次数成正比
-
与该词在整个语料中的出现次数成反比
优点
-
简单快速
-
结果也比较符合实际情况
缺点
-
单纯以“词频”横量一个词的重要性,不够全面,有时重要的词可能出现的次数并不多
-
无法考虑词与词之间的相互关系
-
这种算法无法体现词的位置信息,出现位置靠前的词与出现位置靠后的词,都被视为重要性相同,这是不正确的
- 一种解决方式是,对全文的第一段和每一段的第一句话,给予较大的权重
TF-IDF的具体实现
jieba, NLTK, sklearn, gensim等程序包都可以实现TF-IDF的计算。除算法细节上会有差异外,更多的是数据输入/输出格式上的不同。
jieba
输出结果会自动按照TF-IDF值降序排列,并且直接给出的是词条而不是字典ID,便于阅读使用。
可在计算TF-IDF时直接完成分词,并使用停用词表和自定义词库,非常方便。
有默认的IDF语料库,可以不训练模型,直接进行计算。
以单个文本为单位进行分析。
jieba.analyse.extract_tags(
sentence 为待提取的文本
topK = 20 : 返回几个 TF/IDF 权重最大的关键词
withWeight = False : 是否一并返回关键词权重值
allowPOS = () : 仅包括指定词性的词,默认值为空,即不筛选
)
jieba.analyse.set_idf_path(file_name)
关键词提取时使用自定义逆向文件频率(IDF)语料库
劳动防护 13.900677652
生化学 13.900677652
奥萨贝尔 13.900677652
奧薩貝爾 13.900677652
考察队员 13.900677652
jieba.analyse.set_stop_words(file_name)
关键词提取时使用自定义停止词(Stop Words)语料库
jieba.analyse.TFIDF(idf_path = None)
新建 TFIDF模型实例
idf_path : 读取已有的TFIDF频率文件(即已有模型)
使用该实例提取关键词:TFIDF实例.extract_tags()
import pandas as pd
# 载入语料
raw = pd.read_csv("金庸-射雕英雄传txt精校版.txt",
names = ['txt'], sep ='aaa', encoding ="utf-8" ,engine='python')
# 章节判断用变量预处理
def m_head(tmpstr):
return tmpstr[:1]
def m_mid(tmpstr):
return tmpstr.find("回 ")
raw['head'] = raw.txt.apply(m_head)
raw['mid'] = raw.txt.apply(m_mid)
raw['len'] = raw.txt.apply(len)
# 章节判断
chapnum = 0
for i in range(len(raw)):
if raw['head'][i] == "第" and raw['mid'][i] > 0 and raw['len'][i] < 30 :
chapnum += 1
if chapnum >= 40 and raw['txt'][i] == "附录一:成吉思汗家族" :
chapnum = 0
raw.loc[i, 'chap'] = chapnum
# 删除临时变量
del raw['head']
del raw['mid']
del raw['len']
rawgrp = raw.groupby('chap')
chapter = rawgrp.agg(sum) # 只有字符串列的情况下,sum函数自动转为合并字符串
#chapter = chapter[chapter.index != 0]
chapter
txt | |
---|---|
chap | |
0.0 | 全本全集精校小说尽在:http://www.yimuhe.com/u/anglewing26... |
1.0 | 第一回 风雪惊变钱塘江浩浩江水,日日夜夜无穷无休的从两浙西路临安府牛家村边绕过,东流入海。江... |
2.0 | 第二回 江南七怪颜烈跨出房门,过道中一个中年士人拖着鞋皮,踢跶踢跶的直响,一路打着哈欠迎面过... |
3.0 | 第三回 黄沙莽莽寺里僧众见焦木圆寂,尽皆悲哭。有的便为伤者包扎伤处,抬入客舍。忽听得巨钟下的... |
4.0 | 第四回 黑风双煞完颜洪熙笑道:“好,再打他个痛快。”蒙古兵前哨报来:“王罕亲自前来迎接大金国... |
5.0 | 第五回 弯弓射雕一行人下得山来,走不多时,忽听前面猛兽大吼声一阵阵传来。韩宝驹一提缰,胯下黄... |
6.0 | 第六回 崖顶疑阵午饭以后,郭靖来到师父帐中。全金发道:“靖儿,我试试你的开山掌练得怎样了。”... |
7.0 | 第七回 比武招亲江南六怪与郭靖晓行夜宿,向东南进发,在路非止一日,过了大漠草原。这天离张家口... |
8.0 | 第八回 各显神通王处一脚步好快,不多时便带同郭靖到了城外,再行数里,到了一个山峰背后。他不住... |
9.0 | 第九回 铁枪破犁郭黄二人来到赵王府后院,越墙而进,黄蓉柔声道:“你轻身功夫好得很啊!”郭靖伏... |
10.0 | 第十回 往事如烟完颜康陡然见到杨铁心,惊诧之下,便即认出,大叫:“啊,是你!”提起铁枪,“行... |
11.0 | 第十一回 长春服输沙通天见师弟危殆,跃起急格,挡开了梅超风这一抓,两人手腕相交,都感臂酸心惊... |
12.0 | 第十二回 亢龙有悔黄蓉正要将鸡撕开,身后忽然有人说道:“撕作三份,鸡屁股给我。”两人都吃了一... |
13.0 | 第十三回 五湖废人黄蓉回到客店安睡,自觉做了一件好事,大为得意,一宵甜睡,次晨对郭靖说了。郭... |
14.0 | 第十四回 桃花岛主五男一女,走进厅来,却是江南六怪。他们自北南来,离故乡日近,这天经过太湖,... |
15.0 | 第十五回 神龙摆尾陆冠英扶起完颜康,见他给点中了穴道,动弹不得,只两颗眼珠光溜溜地转动。陆乘... |
16.0 | 第十六回 《九阴真经》郭黄二人自程府出来,累了半夜,正想回客店安歇,忽听马蹄声响,一骑马自南... |
17.0 | 第十七回 双手互搏周伯通道:“你道是我师哥死后显灵?还是还魂复生?都不是,他是假死。”郭靖“... |
18.0 | 第十八回 三道试题郭靖循着蛇声走去,走出数十步,月光下果见数千条青蛇排成长队蜿蜒而前。十多名... |
19.0 | 第十九回 洪涛群鲨洪七公万想不到这场背书比赛竟会如此收场,较之郭靖将欧阳克连摔十七八个筋斗都... |
20.0 | 第二十回 九阴假经洪七公与郭靖见欧阳锋叔侄领周伯通走入后舱,径行到前舱换衣。四名白衣少女过来... |
21.0 | 第二十一回 千钧巨岩欧阳锋只感身上炙热,脚下船板震动甚剧,知道这截船身转眼就要沉没,但洪七公... |
22.0 | 第二十二回 骑鲨遨游黄蓉见欧阳锋拖泥带水地将侄儿抱上岸来,他向来阴鸷的脸上竟也笑逐颜开,可是... |
23.0 | 第二十三回 大闹禁宫黄药师满腔悲愤,指天骂地,咒鬼斥神,痛责命运对他不公,命舟子将船驶往大陆... |
24.0 | 第二十四回 密室疗伤黄蓉向外走了两步,回过头来,见郭靖眼光中露出怀疑神色,料想是自己脸上的杀... |
25.0 | 第二十五回 荒村野店黄药师仰天一笑,说道:“冠英和这位姑娘留着。”陆冠英早知是祖师爷到了,但... |
26.0 | 第二十六回 新盟旧约黄药师心想不明不白地跟全真七子大战一场,更不明不白地结下了深仇,真是好没... |
27.0 | 第二十七回 轩辕台前两人正闹间,楼梯声响,适才随杨康下去的丐帮三长老又回了上来,走到郭黄二人... |
28.0 | 第二十八回 铁掌峰顶此时鲁有脚已经醒转,四长老聚在一起商议。鲁有脚道:“现下真相未明,咱们须... |
29.0 | 第二十九回 黑沼隐女郭靖在雕背连声呼叫,召唤小红马在地下跟来。转眼之间,双雕已飞出老远。雌雄... |
30.0 | 第三十回 一灯大师两人顺着山路向前走去,行不多时,山路就到了尽头,前面是条宽约尺许的石梁,横... |
31.0 | 第三十一回 鸳鸯锦帕一灯大师低低叹了口气道:“其实真正的祸根,还在我自己。我乃大理国小君,虽... |
32.0 | 第三十二回 湍江险滩穆念慈右手让黄蓉握着,望着水面的落花,说道:“我见他杀了欧阳克,只道他从... |
33.0 | 第三十三回 来日大难郭靖与黄蓉此刻心意欢畅,原不想理会闲事,但听到“老顽童”三字,心中一凛,... |
34.0 | 第三十四回 岛上巨变郭靖低声道:“蓉儿,你还要什么?”黄蓉道:“我还要什么?什么也不要啦!”... |
35.0 | 第三十五回 铁枪庙中船靠岸边,走上二三十人来,彭连虎、沙通天等人均在其内。最后上岸的一高一矮... |
36.0 | 第三十六回 大军西征黄蓉幽幽地道:“欧阳伯伯赞得我可太好了。现下郭靖中你之计,和我爹爹势不两... |
37.0 | 第三十七回 从天而降这一日郭靖驻军那密河畔,晚间正在帐中研读兵书,忽听帐外喀的一声轻响。帐门... |
38.0 | 第三十八回 锦囊密令郭靖陪了丘处机与他门下十八名弟子李志常、尹志平、夏志诚、于志可,张志素、... |
39.0 | 第三十九回 是非善恶郭靖纵马急驰数日,已离险地。缓缓南归,天时日暖,青草日长,沿途兵革之余,... |
40.0 | 第四十回 华山论剑欧阳锋冷冷地道:“早到早比,迟到迟比。老叫化,你今日跟我是比武决胜呢,还是... |
import jieba
import jieba.analyse
# 注意:函数是在使用默认的TFIDF模型进行分析!
jieba.analyse.extract_tags(chapter.txt[1])
Building prefix dict from the default dictionary ...
Loading model from cache /tmp/jieba.cache
Loading model cost 0.908 seconds.
Prefix dict has been built succesfully.
['杨铁心',
'包惜弱',
'郭啸天',
'颜烈',
'丘处机',
'武官',
'杨二人',
'官兵',
'曲三',
'金兵',
'那道人',
'道长',
'娘子',
'段天德',
'咱们',
'临安',
'说道',
'丈夫',
'杨家枪',
'两人']
jieba.analyse.extract_tags(chapter.txt[1], withWeight = True) # 要求返回权重值
[('杨铁心', 0.21886511509515091),
('包惜弱', 0.1685852913570757),
('郭啸天', 0.09908082913091291),
('颜烈', 0.05471627877378773),
('丘处机', 0.049556061537506184),
('武官', 0.04608486747703612),
('杨二人', 0.044305304110440376),
('官兵', 0.040144546232276104),
('曲三', 0.03439059290450272),
('金兵', 0.0336976598949901),
('那道人', 0.03117114380098961),
('道长', 0.02912588670625928),
('娘子', 0.026796070076125684),
('段天德', 0.025139911869037603),
('咱们', 0.023296768210644483),
('临安', 0.022991990912831523),
('说道', 0.022350916333591046),
('丈夫', 0.02221595763081643),
('杨家枪', 0.019765724469755074),
('两人', 0.0192267944114003)]
# 应用自定义词典改善分词效果
jieba.load_userdict('金庸小说词库.txt') # dict为自定义词典的路径
# 在TFIDF计算中直接应用停用词表
jieba.analyse.set_stop_words('停用词.txt')
TFres = jieba.analyse.extract_tags(chapter.txt[1], withWeight = True)
TFres[:10]
[('杨铁心', 0.24787133516800222),
('包惜弱', 0.1909279203321098),
('郭啸天', 0.11221202335308209),
('曲三', 0.06426483083720931),
('颜烈', 0.061967833792000555),
('丘处机', 0.056123732343681704),
('武官', 0.052192500516161394),
('杨二人', 0.050177091402185486),
('官兵', 0.04546490778113197),
('金兵', 0.038163614820832165)]
# 使用自定义TF-IDF频率文件
jieba.analyse.set_idf_path("idf.txt.big")
TFres1 = jieba.analyse.extract_tags(chapter.txt[1], withWeight = True)
TFres1[:10]
[('杨铁心', 0.24787133516800222),
('包惜弱', 0.1909279203321098),
('郭啸天', 0.11221202335308209),
('武官', 0.07034186538551414),
('颜烈', 0.061967833792000555),
('说道', 0.05861822115459512),
('丘处机', 0.056123732343681704),
('曲三', 0.055268608517189684),
('一个', 0.053593802198486966),
('杨二人', 0.053593802198486966)]
sklearn
输出格式为矩阵,直接为后续的sklearn建模服务。
需要先使用背景语料库进行模型训练。
结果中给出的是字典ID而不是具体词条,直接阅读结果比较困难。
class sklearn.feature_extraction.text.TfidfTransformer()
发现参数基本上都不用动,所以这里就不介绍了…
from sklearn.feature_extraction.text import TfidfTransformer
txtlist = [ " ".join(m_cut(w)) for w in chapter.txt.iloc[:5]]
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(txtlist) # 将文本中的词语转换为词频矩阵
transformer = TfidfTransformer()
tfidf = transformer.fit_transform(X) #基于词频矩阵X计算TF-IDF值
tfidf
tfidf.toarray() # 转换为数组
tfidf.todense() # 转换为矩阵
tfidf.todense().shape
print("字典长度:", len(vectorizer.vocabulary_))
vectorizer.vocabulary_
gensim
输出格式为list,目的也是为后续的建模分析服务。
需要先使用背景语料库进行模型训练。
结果中给出的是字典ID而不是具体词条,直接阅读结果比较困难。
gensim也提供了sklearn的API接口:sklearn_api.tfidf,可以在sklearn中直接使用。
# 文档分词及预处理
chaplist = [m_cut(w) for w in chapter.txt.iloc[:5]]
chaplist
from gensim import corpora, models
# 生成文档对应的字典和bow稀疏向量
dictionary = corpora.Dictionary(chaplist)
corpus = [dictionary.doc2bow(text) for text in chaplist] # 仍为list in list
corpus
tfidf_model = models.TfidfModel(corpus) # 建立TF-IDF模型
corpus_tfidf = tfidf_model[corpus] # 对所需文档计算TF-IDF结果
corpus_tfidf
corpus_tfidf[3] # 列出所需文档的TF-IDF计算结果
dictionary.token2id # 列出字典内容
TextRank算法
TextRank算法的jieba实现
jieba.analyse.textrank(
sentence, topK=20, withWeight=False,
allowPOS=(‘ns’, ‘n’, ‘vn’, ‘v’)
) # 注意默认过滤词性
实战练习
请使用《射雕》全文计算出jieba分词的IDF语料库,然后使用该语料库重新对第一章计算关键词。比较这样的分析结果和以前有何不同。
请自行编制将jieba分词的TF-IDF结果转换为文档-词条矩阵格式的程序。
请自行思考本章提供的三种TF-IDF实现方式的使用场景是什么。
抽取文档主题
什么是主题模型
LDA,Latent Dirichlet Allocation
-
Q: 有这么一篇文章,里面提到了詹姆斯、湖人队、季后赛,请问这篇文章最可能的主题是什么?
- 军事
- 体育
- 养生
- 教育
LDA由Blei于2003年提出,其基本思想是把文档看成各种隐含主题的混合,而每个主题则表现为跟该主题相关的词项的概率分布
- 该方法不需要任何关于文本的背景知识
- 隐含主题的引入使得分析者可以对“一词多义”和“一义多词”的语言现象进行建模,更接近人类语言交互的特征
LDA基于词袋模型构建,认为文档和单词都是可交换的,忽略单词在文档中的顺序和文档在语料库中的顺序,从而将文本信息转化为易于建模的数字信息
- 主题就是一个桶,里面装了出现概率较高的单词,这些单词与这个主题有很强的的相关性
LDA模型包含词项、主题和文档三层结构
本质上,LDA简单粗暴的认为:文章中的每个词都是通过“以一定概率选择某个主题,再从该主题中以一定概率选择某个词”得到的
一个词可能会关联很多主题,因此需要计算各种情况下的概率分布,来确定最可能出现的主题是哪种
- 体育:{姚明:0.3,篮球:0.5,拳击:0.2,李现:0.03,王宝强:0.03,杨紫:0.04}
- 娱乐:{姚明:0.03,篮球:0.03,足球:0.04,李现:0.6,王宝强:0.7,杨紫:0.8}
一篇文章可能会涉及到几个主题,因此也需要计算多个主题的概率
- 体育新闻:[废话,体育,体育,体育,…,娱乐,娱乐]
- 八卦消息:[废话,废话,废话,废话,…,娱乐,娱乐]
LDA中涉及到的数学知识
多项式分布:主题和词汇的概率分布服从多项式分布
- 如果1个词汇主题,就是大家熟悉的二项分布
Dirichlet分布:上述多项式分布的参数为随机变量,均服从Dirichlet分布
Gibbs抽样:直接求LDA的精确参数分布计算量太大,实际上不可行,因此通过Gibbs抽烟减小计算量,得到逼近的结果
- 通过现有文章(已有主题,或者需要提取主题)训练处LDA模型
- 用模型预测新的文章所属主题分类
主题模型对于 短文本 效果不好
主题模型的sklearn实现
在scikit-learn中,LDA主题模型的类被放置在sklearn.decomposition.LatentDirichletAllocation类中,其算法实现主要基于变分推断EM算法,而没有使用基于Gibbs采样的MCMC算法实现。
注意由于LDA是基于词频统计的,因此理论上一般不宜用TF-IDF来做文档特征,但并非不能尝试。实际分析中也确实会见到此类操作。
class sklearn.decomposition.LatentDirichletAllocation(
n_components = None : 隐含主题数K,需要设置的最重要参数。
K的设定范围和具体的研究背景有关。
K越大,需要的文档样本越多。doc_topic_prior = None : 文档主题先验Dirichlet分布的参数α,未设定则用1/K。
topic_word_prior = None : 主题词先验Dirichlet分布的参数η,未设定则用1/K。
learning_method = ‘online’ : 即LDA的求解算法。‘batch’ | ‘online’
batch: 变分推断EM算法,会将将训练样本分批用于更新主题词分布,新版默认算法。
样本量不大只是用来学习的话用batch比较好,这样可以少很多参数要调。
需注意n_components(K), doc_topic_prior(α), topic_word_prior(η)
online: 在线变分推断EM算法,大样本时首选。
需进一步注意learning_decay, learning_offset,
total_samples和batch_size等参数。仅在online算法时需要设定的参数
learning_decay = 0.7 :控制"online"算法的学习率,一般不用修改。
取值最好在(0.5, 1.0],以保证"online"算法渐进的收敛。learning_offset = 10. :用来减小前面训练样本批次对最终模型的影响。
取值要大于1。total_samples = 1e6 : 分步训练时每一批文档样本的数量。
使用partial_fit进行模型拟合时才需要此参数。batch_size = 128 : 每次EM算法迭代时使用的文档样本的数量。
)
将语料库转换为所需矩阵
除直接使用分词清理后文本进行转换外,也可以先计算关键词的TF-IDF值,然后使用关键词矩阵进行后续分析。
# 设定分词及清理停用词函数
# 熟悉Python的可以使用 open('stopWord.txt').readlines() 获取停用词list,效率更高
stoplist = list(pd.read_csv('停用词.txt', names = ['w'], sep = 'aaa',
encoding = 'utf-8', engine='python').w)
import jieba
def m_cut(intxt):
return [ w for w in jieba.cut(intxt)
if w not in stoplist and len(w) > 1 ]
# 生成分词清理后章节文本
cleanchap = [ " ".join(m_cut(w)) for w in chapter.txt]
# 将文本中的词语转换为词频矩阵
from sklearn.feature_extraction.text import CountVectorizer
countvec = CountVectorizer(min_df = 5)
wordmtx = countvec.fit_transform(cleanchap)
wordmtx
#基于词频矩阵X计算TF-IDF值
from sklearn.feature_extraction.text import TfidfTransformer
transformer = TfidfTransformer()
tfidf = transformer.fit_transform(wordmtx)
tfidf
# 设定LDA模型
from sklearn.decomposition import LatentDirichletAllocation
n_topics = 10
ldamodel = LatentDirichletAllocation(n_components = n_topics)
# 拟合LDA模型
ldamodel.fit(wordmtx)
# 拟合后模型的实质
print(ldamodel.components_.shape)
ldamodel.components_[:2]
# 主题词打印函数
def print_top_words(model, feature_names, n_top_words):
for topic_idx, topic in enumerate(model.components_):
print("Topic #%d:" % topic_idx)
print(" ".join([feature_names[i]
for i in topic.argsort()[:-n_top_words - 1:-1]]))
print()
n_top_words = 12
tf_feature_names = countvec.get_feature_names()
print_top_words(ldamodel, tf_feature_names, n_top_words)
gensim实现
class gensim.models.ldamodel.LdaModel(
corpus = None : 用于训练模型的语料
num_topics = 100 : 准备提取的主题数量
id2word = None : 所使用的词条字典,便于结果阅读
passes = 1 :模型遍历语料库的次数,次数越多模型越精确,但是也更花时间
)
用新出现的语料更新模型
ldamodel.update(other_corpus)
gensim也提供了sklearn的API接口:sklearn_api.ldamodel,可以在sklearn中直接使用。
# 设定分词及清理停用词函数
# 熟悉Python的可以使用 open('stopWord.txt').readlines() 获取停用词list,效率更高
stoplist = list(pd.read_csv('停用词.txt', names = ['w'], sep = 'aaa',
encoding = 'utf-8', engine='python').w)
import jieba
def m_cut(intxt):
return [ w for w in jieba.cut(intxt)
if w not in stoplist and len(w) > 1 ]
# 文档预处理,提取主题词
chaplist = [m_cut(w) for w in chapter.txt]
# 生成文档对应的字典和bow稀疏向量
from gensim import corpora, models
dictionary = corpora.Dictionary(chaplist)
corpus = [dictionary.doc2bow(text) for text in chaplist] # 仍为list in list
tfidf_model = models.TfidfModel(corpus) # 建立TF-IDF模型
corpus_tfidf = tfidf_model[corpus] # 对所需文档计算TF-IDF结果
corpus_tfidf
from gensim.models.ldamodel import LdaModel
# 列出所消耗的时间备查
%time ldamodel = LdaModel(corpus, id2word = dictionary, \
num_topics = 10, passes = 2)
列出最重要的前若干个主题
print_topics(num_topics=20, num_words=10)
ldamodel.print_topics()
# 计算各语料的LDA模型值
corpus_lda = ldamodel[corpus_tfidf] # 此处应当使用和模型训练时相同类型的矩阵
for doc in corpus_lda:
print(doc)
ldamodel.get_topics()
# 检索和文本内容最接近的主题
query = chapter.txt[1] # 检索和第1章最接近的主题
query_bow = dictionary.doc2bow(m_cut(query)) # 频数向量
query_tfidf = tfidf_model[query_bow] # TF-IDF向量
print("转换后:", query_tfidf[:10])
ldamodel.get_document_topics(query_bow) # 需要输入和文档对应的bow向量
# 检索和文本内容最接近的主题
ldamodel[query_tfidf]
结果的图形化呈现
pyLDAvis包引入自R,可以用交互式图形的方式呈现主题模型的分析结果。
同时支持sklearn和gensim包。
在许多系统配置下都会出现兼容问题。
# 对sklearn的LDA结果作呈现
import pyLDAvis
import pyLDAvis.sklearn
pyLDAvis.enable_notebook()
pyLDAvis.sklearn.prepare(ldamodel, tfidf, countvec)
pyLDAvis.gensim.prepare(ldamodel, corpus, dictionary)
pyLDAvis.disable_notebook() # 关闭notebook支持后,可以看到背后所生成的数据
实战练习
在其余参数全部固定不变的情况下,尝试分别用清理前矩阵、清理后原始矩阵、TF-IDF矩阵进行LDA模型拟合,比较分析结果。
在gensim拟合LDA时,分别将passes参数设置为1、5、10、50、100等,观察结果变化的情况,思考如何对该参数做最优设定。
请尝试对模型进行优化,得到对本案例较好的分析结果。
提示:使用gensim进行拟合更容易一些。
文档相似度
用途
- 搜索引擎的类似文章推荐
- 购物网站的类似商品推荐
- 点评网站/微博微信平台上的类似内容推荐
基于词袋模型的基本思路
- 如果两个文档/两句话的用词越相似,他们的内容就应该越相似。因此,可以从词频入手,计算他们的相似程度
- 文档向量化之后,相似度的考察就可以直接转化为计算空间中的距离问题
- 缺陷: 不能考虑否定词的巨大作用,不能考虑词序的差异
在本质上,向量空间中文本相似度的计算和任何聚类方法所考虑的问题***没有区别***
余弦相似度
两个向量间的夹角能够很好的反映其相似度
- 但夹角大小使用不便,因此用夹角的余弦值作为相似度衡量指标
- 思考:为什么只考虑夹角,不考虑相对距离?
余弦值越接近1,夹角越接近0度,两个向量也就越相似
可以证明余弦值的计算公式可以直接扩展到n维空间
因此在由n维向量所构成的空间中,可以利用余弦值来计算文档的相似度
相似度计算:基本分析思路
语料分词、清理
- 原始语料分词
- 语料清理
语料向量化
- 将语料转换为词频向量
- 为了避免文章长度的差异,长度悬殊时可以考虑使用相对词频
计算相似度
- 计算两个向量的余弦相似度,值越大表示越相似
仍然存在的问题
- 高频词不一定具有文档代表性,导致相似度计算结果变差
相似度计算:基本分析思路
语料分词、清理
- 原始语料分词
- 语料清理
语料向量化
- 将语料转换为基于关键词的词频向量
- 为了避免文章长度的差异,长度悬殊时可以考虑使用相对词频
使用TF-IDF算法,找出两篇文章的关键词
- 例如取前20个,或者前50个
计算相似度
- 计算两个向量的余弦相似度,值越大表示越相似
当向量表示概率分布式,其他相似度测量方法比余弦相似度更好
词条相似度:word2vec
词袋模型不考虑词条之间的相关性,因此无法用于计算词条相似度。
分布式表达会考虑词条的上下文关联,因此能够提取出词条上下文中的相关性信息,而词条之间的相似度就可以直接利用此类信息加以计算。
目前主要使用gensim实现相应的算法。
gensim也提供了sklearn的API接口:sklearn_api.w2vmodel,可以在sklearn中直接使用。
设置word2vec模型
class gensim.models.word2vec.Word2Vec(
sentences = None : 类似list of list的格式,对于特别大的文本,尽量考虑流式处理
size = 100 : 词条向量的维度,数据量充足时,300/500的效果会更好
window = 5 : 上下文窗口大小
workers = 3 : 同时运行的线程数,多核系统可明显加速计算
其余细节参数设定:
min_count = 5 : 低频词过滤阈值,低于该词频的不纳入模型
max_vocab_size = None : 每1千万词条需要1G内存,必要时设定该参数以节约内存
sample=0.001 : 负例采样的比例设定
negative=5 : 一般为5-20,设为0时不进行负例采样
iter = 5 : 模型在语料库上的迭代次数,该参数将被取消
与神经网络模型有关的参数设定:
seed=1, alpha=0.025, min_alpha=0.0001, sg=0, hs=0
)
chapter.head()
# 分词和预处理,生成list of list格式
import jieba
chapter['cut'] = chapter.txt.apply(jieba.lcut)
chapter.head()
# 初始化word2vec模型和词表
from gensim.models.word2vec import Word2Vec
n_dim = 300 # 指定向量维度,大样本量时300~500较好
w2vmodel = Word2Vec(size = n_dim, min_count = 10)
w2vmodel.build_vocab(chapter.cut) # 生成词表
w2vmodel
对word2vec模型进行训练
word2vecmodel.train(
sentences : iterable of iterables格式,对于特别大量的文本,尽量考虑流式处理
total_examples = None : 句子总数,int,可直接使用model.corpus_count指定
total_words = None : 句中词条总数,int,该参数和total_examples至少要指定一个
epochs = None : 模型迭代次数,需要指定
其他带默认值的参数设定:
start_alpha=None, end_alpha=None, word_count=0, queue_factor=2,
report_delay=1.0, compute_loss=False, callbacks=()
)
# 在评论训练集上建模(大数据集时可能会花费几分钟)
# 本例消耗内存较少
#time
w2vmodel.train(chapter.cut, \
total_examples = w2vmodel.corpus_count, epochs = 10)
# 训练完毕的模型实质
print(w2vmodel.wv["郭靖"].shape)
w2vmodel.wv["郭靖"]
w2v模型的保存和复用
w2vmodel.save(存盘路径及文件名称)
w2vmodel.load(存盘路径及文件名称)
词向量间的相似度
w2vmodel.wv.most_similar(词条)
w2vmodel.wv.most_similar("郭靖")
w2vmodel.wv.most_similar("黄蓉", topn = 20)
w2vmodel.wv.most_similar("黄蓉道")
# 寻找对应关系
w2vmodel.wv.most_similar(['郭靖', '小红马'], ['黄药师'], topn = 5)
w2vmodel.wv.most_similar(positive=['郭靖', '黄蓉'], negative=['杨康'], topn=10)
# 计算两个词的相似度/相关程度
print(w2vmodel.wv.similarity("郭靖", "黄蓉"))
print(w2vmodel.wv.similarity("郭靖", "杨康"))
print(w2vmodel.wv.similarity("郭靖", "杨铁心"))
# 寻找不合群的词
w2vmodel.wv.doesnt_match("小红马 黄药师 鲁有脚".split())
w2vmodel.wv.doesnt_match("杨铁心 黄药师 黄蓉 洪七公".split())
文档相似度
基于词袋模型计算
sklearn实现
sklearn.metrics.pairwise.pairwise_distances(
X : 用于计算距离的数组
[n_samples_a, n_samples_a] if metric == ‘precomputed’
[n_samples_a, n_features] otherwise
Y = None : 用于计算距离的第二数组,当metric != 'precomputed’时可用
metric = ‘euclidean’ : 空间距离计算方式
scikit-learn原生支持 : [‘cityblock’, ‘cosine’, ‘euclidean’,
'l1', 'l2', 'manhattan'\],可直接使用稀疏矩阵格式
来自scipy.spatial.distance : [‘braycurtis’, ‘canberra’,
‘chebyshev’, ‘correlation’, ‘dice’, ‘hamming’, ‘jaccard’,
'kulsinski', 'mahalanobis', 'matching', 'minkowski', 'rogerstanimoto', 'russellrao', 'seuclidean', 'sokalmichener', 'sokalsneath', 'sqeuclidean', 'yule'\] 不支持稀疏矩阵格式
n_jobs = 1 : 用于计算的线程数,为-1时,所有CPU内核都用于计算
)
cleanchap = [ " ".join(m_cut(w)) for w in chapter.txt.iloc[:5]]
from sklearn.feature_extraction.text import CountVectorizer
countvec = CountVectorizer()
resmtx = countvec.fit_transform(cleanchap)
resmtx
from sklearn.metrics.pairwise import pairwise_distances
pairwise_distances(resmtx, metric = 'cosine')
pairwise_distances(resmtx) # 默认值为euclidean
# 使用TF-IDF矩阵进行相似度计算
pairwise_distances(tfidf[:5], metric = 'cosine')
gensim实现
基于LDA计算余弦相似度
需要使用的信息:
拟合完毕的lda模型
按照拟合模型时矩阵种类转换的需检索文本
需检索的文本
建模时使用的字典
from gensim import similarities
simmtx = similarities.MatrixSimilarity(corpus)
simmtx
# 检索和第1章内容最相似(所属主题相同)的章节
simmtx = similarities.MatrixSimilarity(corpus) # 使用的矩阵种类需要和拟合模型时相同
simmtx
simmtx.index[:2]
# 使用gensim的LDA拟合结果进行演示
query = chapter.txt[1]
query_bow = dictionary.doc2bow(m_cut(query))
lda_vec = ldamodel[query_bow] # 转换为lda模型下的向量
sims = simmtx[lda_vec] # 进行矩阵内向量和所提供向量的余弦相似度查询
sims = sorted(enumerate(sims), key=lambda item: -item[1])
sims
doc2vec
word2vec用来计算词条相似度非常合适。
较短的文档如果希望计算文本相似度,可以将各自内部的word2vec向量分别进行平均,用平均后的向量作为文本向量,从而用于计算相似度。
但是对于长文档,这种平均的方式显然过于粗糙。
doc2vec是word2vec的拓展,它可以直接获得sentences/paragraphs/documents的向量表达,从而可以进一步通过计算距离来得到sentences/paragraphs/documents之间的相似性。
模型概况
分析目的:获得文档的一个固定长度的向量表达。
数据:多个文档,以及它们的标签,一般可以用标题作为标签。
影响模型准确率的因素:语料的大小,文档的数量,越多越高;文档的相似性,越相似越好。
import jieba
import gensim
from gensim.models import doc2vec
def m_doc(doclist):
reslist = []
for i, doc in enumerate(doclist):
reslist.append(doc2vec.TaggedDocument(jieba.lcut(doc), [i]))
return reslist
corp = m_doc(chapter.txt)
corp[:2]
d2vmodel = gensim.models.Doc2Vec(vector_size = 300,
window = 20, min_count = 5)
d2vmodel.build_vocab(corp)
d2vmodel.wv.vocab
# 将新文本转换为相应维度空间下的向量
newvec = d2vmodel.infer_vector(jieba.lcut(chapter.txt[1]))
d2vmodel.docvecs.most_similar([newvec], topn = 10)
文档聚类
在得到文档相似度的计算结果后,文档聚类问题在本质上已经和普通的聚类分析没有区别。
注意:最常用的Kmeans使用的是平方欧氏距离,这在文本聚类中很可能无法得到最佳结果。
算法的速度和效果同样重要。
# 为章节增加名称标签
chapter.index = [raw.txt[raw.chap == i].iloc[0] for i in chapter.index]
chapter.head()
import jieba
cuttxt = lambda x: " ".join(m_cut(x))
cleanchap = chapter.txt.apply(cuttxt)
cleanchap[:2]
# 计算TF-IDF矩阵
from sklearn.feature_extraction.text import TfidfTransformer
vectorizer = CountVectorizer()
wordmtx = vectorizer.fit_transform(cleanchap) # 将文本中的词语转换为词频矩阵
transformer = TfidfTransformer()
tfidf = transformer.fit_transform(wordmtx) #基于词频矩阵计算TF-IDF值
tfidf
# 进行聚类分析
from sklearn.cluster import KMeans
clf = KMeans(n_clusters = 5)
s = clf.fit(tfidf)
print(s)
clf.cluster_centers_
clf.cluster_centers_.shape
clf.labels_
chapter['clsres'] = clf.labels_
chapter.head()
chapter.sort_values('clsres').clsres
chapgrp = chapter.groupby('clsres')
chapcls = chapgrp.agg(sum) # 只有字符串列的情况下,sum函数自动转为合并字符串
cuttxt = lambda x: " ".join(m_cut(x))
chapclsres = chapcls.txt.apply(cuttxt)
chapclsres
# 列出关键词以刻画类别特征
import jieba.analyse as ana
ana.set_stop_words('停用词.txt')
for item in chapclsres:
print(ana.extract_tags(item, topK = 10))
文档分类
什么是文本分类
-
通过程序对文本按照一定的分类体系或标准自动分类标记
-
应用场景
- 对抓取到的新闻进行自动归类
- 邮件服务器对收到的邮件进行垃圾邮件甄别
- 监测系统对采集到的文本信息进行优先级评估,将高优先级的信息优先发送至人工处理流程
文本分类的基本步骤
-
文本有效信息的提取
- 文本预处理:分词、清理等工作
- 特征抽取:从文档中抽取出反映文档主题的特征
-
分类器的选择与训练
-
分类结果的评价与反馈
- 该步骤与普通的模型完全相同
-
基于词袋模型时可考虑的特征抽取方法:
- 词频(基于词袋模型的文档-词条矩阵)
- 关键词(TF-IDF)
- 文档主题(LDA模型)
基于词袋模型的文本分类算法选择
-
逻辑上所有用于分类因变量预测的模型都可以用于文本分类
- 判别分析、Logistics回归、树模型、神经网络、SVM、KNN、朴素贝叶斯、遗传算法、Bagging、Boosting
-
文本分类的数据特征
- 自变量(词条)数量极多
- 各自变量(词条)之间不可能完全独立
- 大部分自变量(词条)只是混杂项而已,对分类无贡献
-
选择算法的几个考虑方向
- 速度 变量筛选能力 容错性 共线性容忍度
-
算法简单的贝叶斯公式(朴素贝叶斯)往往会成为优先考虑的算法
-
模型中,改进后的随机森林,以及SVM相对应用较多
-
有的算法会在特定数据集下表现较好,不能一概而论
-
语料的事先清理至关重要,甚至可以考虑只使用关键词来分类
Python下的文本分类工具包选择
-
sklearn:
- 基于D2M矩阵结构,将文本分类问题看做标准的样本分类预测问题来处理
- 非常完善的模型拟合、诊断功能
- 同时也提供了朴素贝叶斯算法
-
NLTK:
- 主要基于朴素贝叶斯算法进行文本分类
- 可以直接使用稀疏向量格式进行分析,使用上很方便
-
gensim:
- 基于LDA等更先进的模型,提供文本分类功能
朴素贝叶斯的算法原理
-
乘法公式: P ( A B ) = P ( B ∣ A ) ∗ P ( A ) = P ( A ∣ B ) ∗ P ( B ) P(AB) = P(B|A) * P(A) = P(A|B) * P(B) P(AB)=P(B∣A)∗P(A)=P(A∣B)∗P(B)
- P ( A B ) P(AB) P(AB):联合概率
- P ( A ) P(A) P(A)或 P ( B ) P(B) P(B):先验概率
- P ( B ∣ A ) P(B|A) P(B∣A)或 P ( A ∣ B ) P(A|B) P(A∣B):后验概率
-
贝叶斯公式: P ( B ∣ A ) = P ( A ∣ B ) ∗ P ( B ) P ( A ) P(B|A) = \frac{P(A|B) * P(B)}{P(A)} P(B∣A)=P(A)P(A∣B)∗P(B)
- 拥有某特征的案例,属于某类的概率 = 该类出现的概率 * 该类有某特征的概率 / 该特征出现的概率
-
该公式可以很容易的扩展至多条件的情况
- 具体公式大家可以自己尝试写出来
-
应用案例
- 判断一下邮件正文中含有句子:“我司可办理正规发票/增值税发票,点数优惠!”的是垃圾邮件的概率有多大?
-
句子的变化形式很多,但是核心信息就包含在“办理”、“发票”、“优惠”等几个词条的组合中,因此考虑分词后建模
- 发垃圾邮件的人也很敬业的…
-
朴素贝叶斯(Naive Bayes)=贝叶斯公式+条件独立假设
- 抛弃词条建的关联,假设各个词条完全独立,完全基于词袋模型进行计算
- 如此***蠢萌***的朴素贝叶斯方法,实践证明至少在垃圾邮件的识别中的应用效果是非常好的
sklearn实现
sklearn是标准的数据挖掘建模工具包,在语料转换为d2m矩阵结构之后,就可以使用所有标准的DM建模手段在sklearn中进行分析。
在sklearn中也实现了朴素贝叶斯算法,使用方式上也和其他模型非常相似。
生成D2M矩阵
# 从原始语料df中提取出所需的前两章段落
raw12 = raw[raw.chap.isin([1,2])]
raw12ana = raw12.iloc[list(raw12.txt.apply(len) > 50), :] # 只使用超过50字的段落
raw12ana.reset_index(drop = True, inplace = True)
print(len(raw12ana))
raw12ana.head()
# 分词和预处理
import jieba
cuttxt = lambda x: " ".join(jieba.lcut(x)) # 这里不做任何清理工作,以保留情感词
raw12ana["cleantxt"] = raw12ana.txt.apply(cuttxt)
raw12ana.head()
from sklearn.feature_extraction.text import CountVectorizer
countvec = CountVectorizer()
wordmtx = countvec.fit_transform(raw12ana.cleantxt)
wordmtx
划分训练集和测试集
# 作用:将数据集划分为 训练集和测试集
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(wordmtx, raw12ana.chap,
test_size = 0.3, random_state = 111)
拟合朴素贝叶斯模型
from sklearn import naive_bayes
NBmodel = naive_bayes.MultinomialNB()
# 拟合模型
NBmodel.fit(x_train, y_train)
# 进行验证集预测
x_test
NBmodel.predict(x_test)
模型评估
# 预测准确率(给模型打分)
print('训练集:', NBmodel.score(x_train, y_train),
',验证集:', NBmodel.score(x_test, y_test))
from sklearn.metrics import classification_report
print(classification_report(y_test, NBmodel.predict(x_test)))
使用Logistic回归模型进行分类
from sklearn.linear_model import LogisticRegression
logitmodel = LogisticRegression() # 定义Logistic回归模型
# 拟合模型
logitmodel.fit(x_train, y_train)
print(classification_report(y_test, logitmodel.predict(x_test)))
模型预测
将需要预测的文本转换为和建模时格式完全对应的d2m矩阵格式,随后即可进行预测。
countvec.vocabulary_
string = "杨铁心和包惜弱收养穆念慈"
words = " ".join(jieba.lcut(string))
words_vecs = countvec.transform([words]) # 数据需要转换为可迭代的list格式
words_vecs
NBmodel.predict(words_vecs)
NLTK实现
NLTK中内置了朴素贝叶斯算法,可直接实现文档分类。
数据集中语料的格式
用于训练的语料必须是分词完毕的字典形式,词条为键名,键值则可以是数值、字符、或者T/F
{‘张三’ : True, ‘李四’ : True, ‘王五’ : False}
{‘张三’ : 1, ‘李四’ : 1, ‘王五’ : 0}
{‘张三’ : ‘有’, ‘李四’ : ‘有’, ‘王五’ : ‘无’}
# 使用Pandas的命令进行转换
freqlist.to_dict()
df0.groupby(['word']).agg('size').tail(10).to_dict()
训练用数据集的格式
训练用数据集为list of list格式,每个成员为list[语料字典, 结果变量]
[
[{‘张三’ : 1, ‘李四’ : 1, ‘王五’ : 0}, ‘合格’],
[{‘张三’ : 0, ‘李四’ : 1, ‘王五’ : 0}, ‘不合格’]
]
构建模型
考虑到过拟合问题,此处需要先拆分好训练集和测试集
model = NaiveBayesClassifier.train(training_data)
# 这里直接以章节为一个单元进行分析,以简化程序结构
import nltk
from nltk import FreqDist
# 生成完整的词条频数字典,这部分也可以用遍历方式实现
fdist1 = FreqDist(m_cut(chapter.txt[1]))
fdist2 = FreqDist(m_cut(chapter.txt[2]))
fdist3 = FreqDist(m_cut(chapter.txt[3]))
fdist1
from nltk.classify import NaiveBayesClassifier
training_data = [ [fdist1, 'chap1'], [fdist2, 'chap2'], [fdist3, 'chap3'] ]
# 训练分类模型
NLTKmodel = NaiveBayesClassifier.train(training_data)
print(NLTKmodel.classify(FreqDist(m_cut("杨铁心收养穆念慈"))))
print(NLTKmodel.classify(FreqDist(m_cut("钱塘江 日日夜夜 包惜弱 颜烈 使出杨家枪"))))
模型拟合效果的考察
nltk.classify.accuracy(NLTKmodel, training_data) # 准确度评价
NLTKmodel.show_most_informative_features(5)#得到似然比,检测对于哪些特征有用
分类结果评估
精确率和召回率
精确率和召回率主要用于二分类问题(从其公式推导也可看出),结合混淆矩阵有:
分类1 | 分类2 | |
---|---|---|
预测分类1 | TP | FP |
预测分类2 | FN | TN |
精确率 P r e c i s i o n = T P T P + F P Precision = \frac{TP}{TP+FP} Precision=TP+FPTP
召回率 R e c a l l = T P T P + F N Recall = \frac{TP}{TP+FN} Recall=TP+FNTP
理想情况下,精确率和召回率两者都越高越好。然而事实上这两者在某些情况下是矛盾的,精确率高时,召回率低;精确率低时,召回率高;关于这个性质通过观察PR曲线不难观察出来。比如在搜索网页时,如果只返回最相关的一个网页,那精确率就是100%,而召回率就很低;如果返回全部网页,那召回率为100%,精确率就很低。因此在不同场合需要根据实际需求判断哪个指标跟重要。
# 分类报告:precision/recall/fi-score/均值/分类个数
from sklearn.metrics import classification_report
class_true = [1, 2, 3, 3, 1] #正确的分类结果
class_pred = [1, 1, 3, 3, 1] #实际的分类结果
target_names = ['class 1', 'class 2', 'class 3']
print(classification_report(class_true, class_pred, target_names=target_names))
precision recall f1-score support
class 1 0.67 1.00 0.80 2
class 2 0.00 0.00 0.00 1
class 3 1.00 1.00 1.00 2
avg / total 0.67 0.80 0.72 5