PythonTMTable of ContentsIn [ ]: # 学员交流微信群:请加拉群专用微信号 bigdatastar # 文彤老师微信公众号:统计之星import nltknltk.download() In [ ]: nltk.__version__ In [ ]: # 布朗语料库示例from nltk.corpus import brownbrown.categories() In [ ]: len(brown.sents()) In [ ]: len(brown.words()) 常见的语料库格式¶外部文件 除直接网络抓取并加工的情况外,原始文档由于内容较多,往往会首先以单个/多个文本文件的形式保存在外部,然后读入程序 list 结构灵活松散,有利于对原始语料进行处理,也可以随时增删成员
list of list 语料完成分词后的常见形式,每个文档成为词条构成的list,而这些list又是原文档list的成员
DataFrame 使用词袋模型进行后续数据分析时常见格式,行/列代表语料index,相应的列/行代表词条,或者需要加以记录的文档属性,如作者,原始超链接,发表日期等 词条/文档对应时,单元格记录相应的词条出现频率,或者相应的概率/分值 Doc2Term矩阵 Term2Doc矩阵 可以和原始语料的外部文件/list配合使用 对于单个文档,也可以建立DataFrame,用行/列代表一个句子/段落/章节。 准备《射雕》语料库¶为使用Python还不熟练的学员提供一个基于Pandas的通用操作框架。 读入为数据框¶In [ ]: import pandas as pd# 有的环境配置下read_table出错,因此改用read_csvraw = pd.read_csv("金庸-射雕英雄传txt精校版.txt", names = ['txt'], sep ='aaa', encoding ="GBK")print(len(raw))raw 加入章节标识¶In [ ]: # 章节判断用变量预处理def m_head(tmpstr): return tmpstr[:1]def m_mid(tmpstr): return tmpstr.find("回 ")# 注意:下面的raw.txt指的是raw数据框中的txt变量列,对pandas不熟悉的学员请复习相关知识raw['head'] = raw.txt.apply(m_head)raw['mid'] = raw.txt.apply(m_mid)raw['len'] = raw.txt.apply(len)# raw['chap'] = 0raw.head(50) In [ ]: # 章节判断chapnum = 0for 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 # 删除临时变量,这里必须删除,否则后续sum函数处会出错del raw['head']del raw['mid']del raw['len']raw.head(50) 提取出所需章节¶In [ ]: raw[raw.chap == 1].head() In [ ]: from matplotlib import pyplot as plt%matplotlib inlineraw.txt.agg(len).plot.box() In [ ]: rawgrp = raw.groupby('chap')chapter = rawgrp.agg(sum) # 只有字符串列的情况下,sum函数自动转为合并字符串,对pandas不熟悉的学员请复习相关知识chapter = chapter[chapter.index != 0]chapter.txt[1] 实战:准备工具与素材¶请自行完成分析用Anaconda环境的安装和配置。 请自行熟悉Jupyter notebook环境的操作。 自行提取《射雕》任意一回的文字,并完成如下操作:
说明: 最后一题主要涉及到Pandas的操作,对该模块不熟悉的学员可直接继续后续课程的学习,这部分知识的欠缺并不会影响对文本挖掘课程本身的学习。当然,能懂得相应的知识是最好的。 In [ ]: import jiebatmpstr = "郭靖和哀牢山三十六剑。"res = jieba.cut(tmpstr) # 精确模式print(res) # 是一个可迭代的 generator,可以使用 for 循环来遍历结果,本质上类似list In [ ]: print(' '.join(res)) In [ ]: res = jieba.cut(tmpstr)list(word for word in res) # 演示generator的用法 In [ ]: print(jieba.lcut(tmpstr)) # 结果直接输出为list In [ ]: print('/'.join(jieba.cut(tmpstr, cut_all = True))) # 全模式 In [ ]: # 搜索引擎模式,还有jieba.lcut_for_search可用print('/'.join(jieba.cut_for_search(tmpstr))) In [ ]: # 动态修改词典jieba.add_word("哀牢山三十六剑")'/'.join(jieba.cut(tmpstr)) In [ ]: jieba.del_word("哀牢山三十六剑")'/'.join(jieba.cut(tmpstr)) 使用自定义词典¶load_userdict(file_name) file_name:文件类对象或自定义词典的路径 词典基本格式 一个词占一行:词、词频(可省略)、词性(可省略),用空格隔开 词典文件必须为 UTF-8 编码 必要时可以使用Uedit32进行文件编码转换
In [ ]: dict = '金庸小说词库.txt'jieba.load_userdict(dict) # dict为自定义词典的路径'/'.join(jieba.cut(tmpstr)) 使用搜狗细胞词库¶https://pinyin.sogou.com/dict/ 按照词库分类或者关键词搜索方式,查找并下载所需词库 使用转换工具,将其转换为txt格式 深蓝词库转换 奥创词库转换 在程序中导入相应词库 In [ ]: newlist = [ w for w in jieba.cut(tmpstr) if w not in ['和', '。'] ] print(newlist) In [ ]: import pandas as pdtmpdf = pd.read_csv('停用词.txt', names = ['w'], sep = 'aaa', encoding = 'utf-8')tmpdf.head() In [ ]: # 熟悉Python的可以直接使用 open('stopWord.txt').readlines()获取停用词list,效率更高[ w for w in jieba.cut(tmpstr) if w not in list(tmpdf.w) ] In [ ]: # 使用预先准备的停用词表import jieba.analyse as anaana.set_stop_words('停用词.txt')jieba.lcut(tmpstr) # 读入的停用词列表对分词结果无效 In [ ]: ana.extract_tags(tmpstr, topK = 20) # 使用TF-IDF算法提取关键词,并同时去掉停用词 In [ ]: import jieba.posseg as psgtmpres = psg.cut(tmpstr) # 附加词性的分词结果print(tmpres)for item in tmpres: print(item.word, item.flag) In [ ]: psg.lcut(tmpstr) # 直接输出为list,成员为pair 分词的NLTK实现¶NLTK只能识别用空格作为词条分割方式,因此不能直接用于中文文本的分词。 一般的做法是先用jieba分词,然后转换为空格分隔的连续文本,再转入NLTK框架使用。 rawtext = '周伯通笑道:“你懂了吗?...” txt = ' '.join(jieba.cut(rawtext)) # "周伯通 笑 道 :..." toke = nltk.word_tokenize(txt) # ['周伯通', '笑', '道', ':'...] 实战:《射雕》一书分词¶选取第一回的文字,应用搜狗的细胞词库和停用词表,清理出干净的分词结果。 选取第一回中最长的1个段落,比较不使用词库、不使用停用词表前后的分词结果。 熟悉搜狗细胞词库网站中的资源,思考哪些词库可能是自己需要的,下载相应的资源并进行格式转换。 In [ ]: import jieba#分词word_list = jieba.lcut(chapter.txt[1])word_list[:10] In [ ]: word_list = jieba.lcut(" ".join(raw.txt)) In [ ]: word_list[:10] In [ ]: import pandas as pddf = pd.DataFrame(word_list, columns = ['word'])df.head(30) In [ ]: result = df.groupby(['word']).size()print(type(result))freqlist = result.sort_values(ascending=False)freqlist[:20] In [ ]: freqlist[freqlist.index == '道'] In [ ]: freqlist[freqlist.index == '黄蓉道'] In [ ]: jieba.add_word('道', freq = 50000) 使用NLTK统计¶NLTK生成的结果为频数字典,在和某些程序包对接时比较有用 In [ ]: import nltk# 分词等预处理工作# 这里可以根据需要做任何的preprocessing: stopwords, lemma, stemming, etc.word_list[:10] In [ ]: fdist = nltk.FreqDist(word_list) # 生成完整的词条频数字典fdist In [ ]: # 带上某个单词, 可以看到它在整个文章中出现的次数fdist['颜烈'] In [ ]: fdist.keys() # 列出词条列表 In [ ]: fdist.tabulate(10) In [ ]: fdist.most_common(5) 词云概述¶wordcloud包的安装¶安装¶警告:wordcloud的安装有可能非常顺利,也有可能非常痛苦,完全是拼人品的一件事情。。。 方法1:pip install wordcloud 安装后很可能不能用,直接成功的话,您的人品实在是爆棚 方法2:python setup.py install 在github.com/amueller/word_cloud下载安装包 方法3:下载第三方编译好的whl文件进行安装 https://www.lfd./~gohlke/pythonlibs/#wordcloud Visual C++ build tools支持 提示:Microsoft Visual C++ 14.0 is required. 需要:Visual C++ 2015 Build Tools 文件:visualcppbuildtools_full.exe ImportError: DLL load failed: 找不到指定的模块。 pip uninstall pillow,然后重新安装pillow包 或者uninstall pillow之后使用上面的方法2安装,会自动安装相关的支持包 中文字体支持¶.WordCloud(font_path='simhei.ttf') 需要带路径写完整字体文件名 注意Win10的字体文件后缀可能不一样 绘制词云¶WordCloud的基本语法¶class wordcloud.WordCloud( 常用功能: 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的别名 文本需要用空格/标点符号分隔单词,否则不能正确分词 In [ ]: import wordcloudmyfont = r'C:\Windows\Fonts\simkai.ttf'text = 'this is shanghai, 郭靖, 和, 哀牢山 三十六剑'cloudobj = wordcloud.WordCloud(font_path = myfont).generate(text) print(cloudobj) In [ ]: import matplotlib.pyplot as pltplt.imshow(cloudobj)plt.axis("off")plt.show() In [ ]: # 更改词云参数设定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(保存文件的路径与名称) 该命令保存的是高精度图形 In [ ]: cloudobj.to_file("词云.png")# wordcloud.WordCloud(font_path = myfont).generate(text).to_file(r"词云.png") 生成射雕第一章的词云¶In [ ]: import pandas as pdimport jiebastoplist = list(pd.read_csv('停用词.txt', names = ['w'], sep = 'aaa', encoding = 'utf-8', engine='python').w)def m_cut(intxt): return [ w for w in jieba.cut(intxt) if w not in stoplist ] In [ ]: cloudobj = wordcloud.WordCloud(font_path = myfont, width = 1200, height = 800, mode = "RGBA", background_color = None, stopwords = stoplist).generate(' '.join(jieba.lcut(chapter.txt[1]))) plt.imshow(cloudobj)plt.axis("off")plt.show() In [ ]: cloudobj.to_file("词云2.png") 基于分词频数绘制¶generate()的实际操作 调用分词函数process_text() 调用基于频数的绘制函数fit_words() fit_words(dict) 实际上是generate_from_frequencies的别名 Dict: 由词条和频数构成的字典 In [ ]: #基于分词频数绘制词云txt_freq = {'张三':100,'李四':90,'王二麻子':50}cloudobj = wordcloud.WordCloud(font_path = myfont).fit_words(txt_freq)plt.imshow(cloudobj)plt.axis("off")plt.show() 用频数生成射雕第一章的词云¶In [ ]: import nltkfrom nltk import FreqDisttokens = m_cut(chapter.txt[1])fdist = FreqDist(tokens) # 生成完整的词条频数字典type(fdist) In [ ]: cloudobj = wordcloud.WordCloud(font_path = myfont).fit_words(fdist)plt.imshow(cloudobj)plt.axis("off")plt.show() In [ ]: from imageio import imreaddef m_cut(intxt): return [ w for w in jieba.cut(intxt) if w not in stoplist and len(w) > 1 ] cloudobj = wordcloud.WordCloud(font_path = myfont, mask = imread("射雕背景1.png"), mode = "RGBA", background_color = None ).generate(' '.join(m_cut(chapter.txt[1]))) plt.imshow(cloudobj)plt.axis("off")plt.show() 指定图片色系¶读取指定图片的色系设定 imgarray = np.array(imread(imgfilepath)) 获取图片颜色 bimgColors = wordcloud.ImageColorGenerator(imgarray) 重置词云颜色 cloudobj.recolor(color_func=bimgColors) # 利用已有词云对象直接重绘颜色,输出速度要比全部重绘快的多 In [ ]: import numpy as npimgobj = 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() In [ ]: # 官网提供的颜色分组类代码,略有修改from wordcloud import get_single_color_funcclass 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() 实战:优化射雕词云¶尝试进一步清理分词结果,并且只保留所有的名称(人名、地名)。 提示:可以使用词性标注功能,只保留名词和未知词性的词。 可以考虑对自定义词典做优化,通过强行调整权重等方法改善分词效果。 将所有的人名按照蓝色系,地名按照红色系进行词云绘制。 自行制作两个纯色图片,分别为绿色和蓝色,然后将其分别指定为绘图所用的色系,观察图形效果。 尝试使用不同的背景图片作为掩模,思考怎样的图片才能使得绘图效果最佳。 文档信息的向量化¶词袋模型¶词袋模型的gensim实现¶gensim的安装¶pip install genism 安装完成后如果使用word2vec时报错,建议去gensim官网下载MS windows install的exe程序进行安装:https://pypi./pypi/gensim 建立字典¶Dictionary类用于建立word<->id映射关系,把所有单词取一个set(),并对set中每个单词分配一个Id号的map class gensim.corpora.dictionary.Dictionary( documents=None : 若干个被拆成单词集合的文档的集合,一般以list in list形式出现 prune_at=2000000 : 字典中的最大词条容量 ) In [ ]: from gensim.corpora import Dictionarytexts = [['human', 'interface', 'computer']]dct = Dictionary(texts) # fit dictionarydct.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. In [ ]: # 向字典增加词条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) In [ ]: dct.doc2bow(["this", "is", "cat", "not", "a", "dog"]) In [ ]: 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 In [ ]: dct.doc2idx(["this", "is", "a", "dog", "not", "cat"]) In [ ]: chapter.head() In [ ]: # 设定分词及清理停用词函数# 熟悉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 ] In [ ]: # 设定数据框转换函数def m_appdf(chapnum): tmpdf = pd.DataFrame(m_cut(chapter.txt[chapnum + 1]), columns = ['word']) tmpdf['chap'] = chapter.index[chapnum] # 也可以直接 = chapnum + 1 return tmpdf In [ ]: # 全部读入并转换为数据框df0 = pd.DataFrame(columns = ['word', 'chap']) # 初始化结果数据框for i in range(len(chapter)): df0 = df0.append(m_appdf(i))df0.tail() In [ ]: # 输出为序列格式df0.groupby(['word', 'chap']).agg('size').tail(10) In [ ]: # 直接输出为数据框t2d = pd.crosstab(df0.word, df0.chap)len(t2d) In [ ]: t2d.head() In [ ]: # 计算各词条的总出现频次,准备进行低频词删减totnum = t2d.agg(func = 'sum', axis=1)totnum In [ ]: 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() 返回文本预处理和分词的可调用函数 In [ ]: from sklearn.feature_extraction.text import CountVectorizercountvec = CountVectorizer(min_df = 2) # 在两个以上文档中出现的才保留analyze = countvec.build_analyzer()analyze('郭靖 和 哀牢山 三十六 剑 。') CountVectorizer.fit_transform(raw_documents) 对文档进行学习(处理),返回term-document matrix 等价于先调用fit函数,然后再调用transform函数,但是效率更高 In [ ]: countvec.fit(['郭靖 和 黄蓉 哀牢山 三十六 剑 。', '黄蓉 和 郭靖 郭靖']) In [ ]: countvec.get_feature_names() # 词汇列表,实际上就是获取每个列对应的词条 In [ ]: countvec.vocabulary_ # 词条字典 In [ ]: x = countvec.transform(['郭靖 和 黄蓉 哀牢山 三十六 剑 。', '黄蓉 和 郭靖 郭靖'])type(x) In [ ]: x.todense() # 将稀疏矩阵直接转换为标准格式矩阵 In [ ]: countvec.fit_transform(['郭靖 和 哀牢山 三十六 剑 。', '黄蓉 和 郭靖 郭靖']) # 一次搞定 In [ ]: rawchap = [ " ".join(m_cut(w)) for w in chapter.txt.iloc[:5]] rawchap[0] In [ ]: from sklearn.feature_extraction.text import CountVectorizercountvec = CountVectorizer(min_df = 5) # 在5个以上章节中出现的才保留res = countvec.fit_transform(rawchap)res In [ ]: res.todense() In [ ]: countvec.get_feature_names() 实战:生成词向量¶尝试编制以下程序: 以段为单位依次读入射雕第一章的内容。 为每一段分别生成bow稀疏向量。 生成稀疏向量的同时动态更新字典。 请自行编制bow稀疏向量和标准长向量互相转换的程序。 请自行思考基于BOW的分析模型和基于分布式表示向量的模型在文本挖掘中的适用范围和优缺点。 在文档词条矩阵中可以看到许多类似“黄蓉道”、“黄蓉说”之类的词条,请思考对此有哪些处理办法。 关键词提取¶关键词提取的基本思路¶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)语料库
jieba.analyse.set_stop_words(file_name) 关键词提取时使用自定义停止词(Stop Words)语料库 jieba.analyse.TFIDF(idf_path = None) 新建 TFIDF模型实例 idf_path : 读取已有的TFIDF频率文件(即已有模型) 使用该实例提取关键词:TFIDF实例.extract_tags() In [ ]: import jiebaimport jieba.analyse# 注意:函数是在使用默认的TFIDF模型进行分析!jieba.analyse.extract_tags(chapter.txt[1]) In [ ]: jieba.analyse.extract_tags(chapter.txt[1], withWeight = True) # 要求返回权重值 In [ ]: # 应用自定义词典改善分词效果jieba.load_userdict('金庸小说词库.txt') # dict为自定义词典的路径# 在TFIDF计算中直接应用停用词表jieba.analyse.set_stop_words('停用词.txt')TFres = jieba.analyse.extract_tags(chapter.txt[1], withWeight = True)TFres[:10] In [ ]: # 使用自定义TF-IDF频率文件jieba.analyse.set_idf_path("idf.txt.big")TFres1 = jieba.analyse.extract_tags(chapter.txt[1], withWeight = True)TFres1[:10] sklearn¶输出格式为矩阵,直接为后续的sklearn建模服务。 需要先使用背景语料库进行模型训练。 结果中给出的是字典ID而不是具体词条,直接阅读结果比较困难。 class sklearn.feature_extraction.text.TfidfTransformer() 发现参数基本上都不用动,所以这里就不介绍了... In [ ]: from sklearn.feature_extraction.text import TfidfTransformertxtlist = [ " ".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 In [ ]: tfidf.toarray() # 转换为数组 In [ ]: tfidf.todense() # 转换为矩阵 In [ ]: tfidf.todense().shape In [ ]: print("字典长度:", len(vectorizer.vocabulary_))vectorizer.vocabulary_ gensim¶输出格式为list,目的也是为后续的建模分析服务。 需要先使用背景语料库进行模型训练。 结果中给出的是字典ID而不是具体词条,直接阅读结果比较困难。 gensim也提供了sklearn的API接口:sklearn_api.tfidf,可以在sklearn中直接使用。 In [ ]: # 文档分词及预处理 chaplist = [m_cut(w) for w in chapter.txt.iloc[:5]]chaplist In [ ]: from gensim import corpora, models # 生成文档对应的字典和bow稀疏向量dictionary = corpora.Dictionary(chaplist) corpus = [dictionary.doc2bow(text) for text in chaplist] # 仍为list in list corpus In [ ]: tfidf_model = models.TfidfModel(corpus) # 建立TF-IDF模型 corpus_tfidf = tfidf_model[corpus] # 对所需文档计算TF-IDF结果corpus_tfidf In [ ]: corpus_tfidf[3] # 列出所需文档的TF-IDF计算结果 In [ ]: dictionary.token2id # 列出字典内容 In [ ]: jieba.analyse.textrank(chapter.txt[1], topK=20, withWeight = True) 实战练习¶请使用《射雕》全文计算出jieba分词的IDF语料库,然后使用该语料库重新对第一章计算关键词。比较这样的分析结果和以前有何不同。 请自行编制将jieba分词的TF-IDF结果转换为文档-词条矩阵格式的程序。 请自行思考本章提供的三种TF-IDF实现方式的使用场景是什么。 抽取文档主题¶主题模型的基本概念¶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值,然后使用关键词矩阵进行后续分析。 In [ ]: # 设定分词及清理停用词函数# 熟悉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 ] In [ ]: # 生成分词清理后章节文本cleanchap = [ " ".join(m_cut(w)) for w in chapter.txt] In [ ]: # 将文本中的词语转换为词频矩阵 from sklearn.feature_extraction.text import CountVectorizercountvec = CountVectorizer(min_df = 5) wordmtx = countvec.fit_transform(cleanchap) wordmtx In [ ]: #基于词频矩阵X计算TF-IDF值 from sklearn.feature_extraction.text import TfidfTransformertransformer = TfidfTransformer() tfidf = transformer.fit_transform(wordmtx) tfidf In [ ]: # 设定LDA模型from sklearn.decomposition import LatentDirichletAllocationn_topics = 10ldamodel = LatentDirichletAllocation(n_components = n_topics) In [ ]: # 拟合LDA模型,注意这里使用的是原始wordmtx矩阵ldamodel.fit(wordmtx) In [ ]: # 拟合后模型的实质print(ldamodel.components_.shape)ldamodel.components_[:2] In [ ]: # 主题词打印函数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() In [ ]: n_top_words = 12tf_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中直接使用。 In [ ]: # 设定分词及清理停用词函数# 熟悉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 ] In [ ]: # 文档预处理,提取主题词 chaplist = [m_cut(w) for w in chapter.txt] In [ ]: # 生成文档对应的字典和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 In [ ]: from gensim.models.ldamodel import LdaModel# 列出所消耗的时间备查%time ldamodel1 = LdaModel(corpus, id2word = dictionary, \ num_topics = 10, passes = 2) 列出最重要的前若干个主题¶print_topics(num_topics=20, num_words=10) In [ ]: ldamodel1.print_topics() In [ ]: # 计算各语料的LDA模型值corpus_lda = ldamodel1[corpus_tfidf] # 此处应当使用和模型训练时相同类型的矩阵for doc in corpus_lda: print(doc) In [ ]: ldamodel1.get_topics() In [ ]: # 检索和文本内容最接近的主题query = chapter.txt[1] # 检索和第1章最接近的主题query_bow = dictionary.doc2bow(m_cut(query)) # 频数向量query_tfidf = tfidf_model[query_bow] # TF-IDF向量print("转换后:", query_tfidf[:10])ldamodel1.get_document_topics(query_bow) # 需要输入和文档对应的bow向量 In [ ]: # 检索和文本内容最接近的主题ldamodel1[query_tfidf] 结果的图形化呈现¶文档主题在呈现时需要解决的需求: 每个主题的含义是什么? 每个主题的重要性如何?是否是重要的主题? 主题直接的联系是怎样的? pyLDAvis包引入自R,可以用交互式图形的方式呈现主题模型的分析结果。 同时支持sklearn和gensim包。 在许多系统配置下都会出现兼容问题。 安装时会先从最高版本的包进行下载,然后根据兼容性报错依次降级,直至找到适合的包为止(这都什么奇葩操作) pip install pyLDAvis pyLDAvis的结果呈现方式: 左侧:各个主题模型在模型空间中的相互关系和重要性。 空间定位使用MDS方式实现。 圆圈大小则代表该主题的流行程度(频数意义上的重要性)。 右侧:列出和当前选中主题频数关联最强的词条。 Lambda参数的调节方式: 1 : 重要性完全由词条的频数高低来决定 0:重要性完全由词条提升程度来决定 lift值:词条在某主题下的出现频度/词条在整个文档中的出现频度 class pyLDAvis.sklearn.prepare( lda_model : 用sklearn基于dtm训练而来的Latent Dirichlet Allocation model dtm : 用于训练lda_model的Document-term matrix vectorizer :将raw documents转换为dtm时使用的vectorizer ) # 返回值:用于可视化的数据结构 pyLDAvis.gensim.prepare()函数的参数设定与上面完全相同 In [ ]: # 对sklearn的LDA结果作呈现import pyLDAvisimport pyLDAvis.sklearnpyLDAvis.enable_notebook() In [ ]: pyLDAvis.sklearn.prepare(ldamodel, wordmtx, countvec) In [ ]: # 对gensim的LDA结果作呈现import pyLDAvis.gensimpyLDAvis.enable_notebook() In [ ]: pyLDAvis.gensim.prepare(ldamodel1, corpus, dictionary) In [ ]: pyLDAvis.disable_notebook() # 关闭notebook支持后,可以看到背后所生成的数据 实战练习¶在其余参数全部固定不变的情况下,尝试分别用清理前矩阵、清理后原始矩阵、TF-IDF矩阵进行LDA模型拟合,比较分析结果。 在gensim拟合LDA时,分别将passes参数设置为1、5、10、50、100等,观察结果变化的情况,思考如何对该参数做最优设定。 请尝试对模型进行优化,得到对本案例较好的分析结果。 提示:使用gensim进行拟合更容易一些。 文档相似度¶基本概念¶词条相似度:word2vec¶词袋模型不考虑词条之间的相关性,因此无法用于计算词条相似度。 分布式表达会考虑词条的上下文关联,因此能够提取出词条上下文中的相关性信息,而词条之间的相似度就可以直接利用此类信息加以计算。 目前主要使用gensim实现相应的算法。 gensim也提供了sklearn的API接口:sklearn_api.w2vmodel,可以在sklearn中直接使用。 设置word2vec模型¶class gensim.models.word2vec.Word2Vec( sentences = None : 类似list of list的格式,对于特别大的文本,尽量考虑流式处理 vector_size = 100 : 词条向量的维度,数据量充足时,300/500的效果会更好 老版本中该参数为size 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 ) In [ ]: chapter.head() In [ ]: # 分词和预处理,生成list of list格式import jiebachapter['cut'] = chapter.txt.apply(jieba.lcut)chapter.head() In [ ]: # 初始化word2vec模型和词表from gensim.models.word2vec import Word2Vecn_dim = 300 # 指定向量维度,大样本量时300~500较好w2vmodel = Word2Vec(vector_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=() ) In [ ]: # 在评论训练集上建模(大数据集时可能会花费几分钟)# 本例消耗内存较少%time w2vmodel.train(chapter.cut, \ total_examples = w2vmodel.corpus_count, epochs = 10) In [ ]: # 训练完毕的模型实质print(w2vmodel.wv["郭靖"].shape)w2vmodel.wv["郭靖"] In [ ]: w2vmodel.wv.most_similar("郭靖") In [ ]: w2vmodel.wv.most_similar("黄蓉", topn = 20) In [ ]: w2vmodel.wv.most_similar("黄蓉道") In [ ]: # 寻找对应关系w2vmodel.wv.most_similar(['郭靖', '小红马'], ['黄药师'], topn = 5) In [ ]: w2vmodel.wv.most_similar(positive=['郭靖', '黄蓉'], negative=['杨康'], topn=10) In [ ]: # 计算两个词的相似度/相关程度print(w2vmodel.wv.similarity("郭靖", "黄蓉"))print(w2vmodel.wv.similarity("郭靖", "杨康"))print(w2vmodel.wv.similarity("郭靖", "杨铁心")) In [ ]: # 寻找不合群的词w2vmodel.wv.doesnt_match("小红马 黄药师 鲁有脚".split()) In [ ]: 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内核都用于计算 ) In [ ]: cleanchap = [ " ".join(m_cut(w)) for w in chapter.txt.iloc[:5]] from sklearn.feature_extraction.text import CountVectorizercountvec = CountVectorizer() resmtx = countvec.fit_transform(cleanchap)resmtx In [ ]: from sklearn.metrics.pairwise import pairwise_distancespairwise_distances(resmtx, metric = 'cosine') In [ ]: pairwise_distances(resmtx) # 默认值为euclidean In [ ]: # 使用TF-IDF矩阵进行相似度计算pairwise_distances(tfidf[:5], metric = 'cosine') gensim实现¶In [ ]: from gensim import similaritiessimmtx = similarities.MatrixSimilarity(corpus)simmtx In [ ]: # 检索和第1章内容最相似(所属主题相同)的章节simmtx = similarities.MatrixSimilarity(corpus) # 使用的矩阵种类需要和拟合模型时相同simmtx In [ ]: simmtx.index[:2] In [ ]: # 使用gensim的LDA拟合结果进行演示query = chapter.txt[1] query_bow = dictionary.doc2bow(m_cut(query))lda_vec = ldamodel1[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之间的相似性。 模型概况 分析目的:获得文档的一个固定长度的向量表达。 数据:多个文档,以及它们的标签,一般可以用标题作为标签。 影响模型准确率的因素:语料的大小,文档的数量,越多越高;文档的相似性,越相似越好。 In [ ]: import jieba import gensimfrom gensim.models import doc2vecdef m_doc(doclist): reslist = [] for i, doc in enumerate(doclist): reslist.append(doc2vec.TaggedDocument(jieba.lcut(doc), [i])) return reslistcorp = m_doc(chapter.txt) In [ ]: corp[:2] In [ ]: d2vmodel = gensim.models.Doc2Vec(vector_size = 300, window = 20, min_count = 5)%time d2vmodel.build_vocab(corp) In [ ]: # The vocab attribute was removed from KeyedVector in Gensim 4.0.0.d2vmodel.wv.key_to_index In [ ]: # 将新文本转换为相应维度空间下的向量newvec = d2vmodel.infer_vector(jieba.lcut(chapter.txt[1])) In [ ]: d2vmodel.docvecs.most_similar([newvec], topn = 10) 文档聚类¶在得到文档相似度的计算结果后,文档聚类问题在本质上已经和普通的聚类分析没有区别。 注意:最常用的Kmeans使用的是平方欧氏距离,这在文本聚类中很可能无法得到最佳结果。 算法的速度和效果同样重要。 In [ ]: # 为章节增加名称标签chapter.index = [raw.txt[raw.chap == i].iloc[0] for i in chapter.index]chapter.head() In [ ]: import jiebacuttxt = lambda x: " ".join(m_cut(x)) cleanchap = chapter.txt.apply(cuttxt) cleanchap[:2] In [ ]: # 计算TF-IDF矩阵from sklearn.feature_extraction.text import TfidfTransformervectorizer = CountVectorizer() wordmtx = vectorizer.fit_transform(cleanchap) # 将文本中的词语转换为词频矩阵 transformer = TfidfTransformer() tfidf = transformer.fit_transform(wordmtx) #基于词频矩阵计算TF-IDF值 tfidf In [ ]: # 进行聚类分析from sklearn.cluster import KMeans clf = KMeans(n_clusters = 5) s = clf.fit(tfidf) print(s) clf.cluster_centers_ In [ ]: clf.cluster_centers_.shape In [ ]: clf.labels_ In [ ]: chapter['clsres'] = clf.labels_chapter.head() In [ ]: chapter.sort_values('clsres').clsres In [ ]: chapgrp = chapter.groupby('clsres')chapcls = chapgrp.agg(sum) # 只有字符串列的情况下,sum函数自动转为合并字符串cuttxt = lambda x: " ".join(m_cut(x)) chapclsres = chapcls.txt.apply(cuttxt) chapclsres In [ ]: # 列出关键词以刻画类别特征import jieba.analyse as anaana.set_stop_words('停用词.txt')for item in chapclsres: print(ana.extract_tags(item, topK = 10)) In [ ]: # 从原始语料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() In [ ]: # 分词和预处理import jiebacuttxt = lambda x: " ".join(jieba.lcut(x)) # 这里不做任何清理工作,以保留情感词raw12ana["cleantxt"] = raw12ana.txt.apply(cuttxt) raw12ana.head() In [ ]: from sklearn.feature_extraction.text import CountVectorizercountvec = CountVectorizer() wordmtx = countvec.fit_transform(raw12ana.cleantxt)wordmtx 划分训练集和测试集¶In [ ]: # 作用:将数据集划分为 训练集和测试集from sklearn.model_selection import train_test_splitx_train, x_test, y_train, y_test = train_test_split(wordmtx, raw12ana.chap, test_size = 0.3, random_state = 111) 拟合朴素贝叶斯模型¶In [ ]: from sklearn import naive_bayesNBmodel = naive_bayes.MultinomialNB() In [ ]: # 拟合模型NBmodel.fit(x_train, y_train) In [ ]: # 进行验证集预测x_test In [ ]: NBmodel.predict(x_test) 模型评估¶In [ ]: # 预测准确率(给模型打分)print('训练集:', NBmodel.score(x_train, y_train), ',验证集:', NBmodel.score(x_test, y_test)) In [ ]: from sklearn.metrics import classification_reportprint(classification_report(y_test, NBmodel.predict(x_test))) 使用Logistic回归模型进行分类¶In [ ]: from sklearn.linear_model import LogisticRegressionlogitmodel = LogisticRegression() # 定义Logistic回归模型 In [ ]: # 拟合模型logitmodel.fit(x_train, y_train)print(classification_report(y_test, logitmodel.predict(x_test))) 模型预测¶将需要预测的文本转换为和建模时格式完全对应的d2m矩阵格式,随后即可进行预测。 In [ ]: countvec.vocabulary_ In [ ]: string = "杨铁心和包惜弱收养穆念慈"words = " ".join(jieba.lcut(string))words_vecs = countvec.transform([words]) # 数据需要转换为可迭代的list格式words_vecs In [ ]: NBmodel.predict(words_vecs) In [ ]: # 使用Pandas的命令进行转换freqlist.to_dict() In [ ]: df0.groupby(['word']).agg('size').tail(10).to_dict() 训练用数据集的格式¶训练用数据集为list of list格式,每个成员为list[语料字典, 结果变量]
In [ ]: # 这里直接以章节为一个单元进行分析,以简化程序结构import nltkfrom 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 In [ ]: from nltk.classify import NaiveBayesClassifiertraining_data = [ [fdist1, 'chap1'], [fdist2, 'chap2'], [fdist3, 'chap3'] ] In [ ]: # 训练分类模型NLTKmodel = NaiveBayesClassifier.train(training_data) In [ ]: print(NLTKmodel.classify(FreqDist(m_cut("杨铁心收养穆念慈"))))print(NLTKmodel.classify(FreqDist(m_cut("钱塘江 日日夜夜 包惜弱 颜烈 使出杨家枪")))) 模型拟合效果的考察¶In [ ]: nltk.classify.accuracy(NLTKmodel, training_data) # 准确度评价 In [ ]: NLTKmodel.show_most_informative_features(5)#得到似然比,检测对于哪些特征有用 实战作业¶对射雕的前两个章节提取关键字,然后使用关键字而不是原始文本进行文档分类,比较这样两种方式的分类效果有何变化。 减少用于训练的样本量,考察使用朴素贝叶斯算法或者其他标准分类算法时,模型效果的变化趋势。 提示:对编程比较熟悉的学员可以自行编制循环程序,自动完成样本量和模型效果的曲线 自行实现基于NLTK的按段落为单位进行章节分类的程序。 自行下载金庸或者古龙的另一本武侠小说,构建任一文本段落在该小说和射雕之间的分类模型。 In [ ]: # 读入原始数据集import pandas as pddfpos = pd.read_excel("购物评论.xlsx", sheet_name = "正向", header=None)dfpos['y'] = 1dfneg = pd.read_excel("购物评论.xlsx", sheet_name = "负向", header=None)dfneg['y'] = 0df0 = dfpos.append(dfneg, ignore_index = True)df0.head() In [ ]: # 分词和预处理import jiebacuttxt = lambda x: " ".join(jieba.lcut(x)) # 这里不做任何清理工作,以保留情感词df0["cleantxt"] = df0[0].apply(cuttxt) df0.head() In [ ]: from sklearn.feature_extraction.text import CountVectorizercountvec = CountVectorizer(min_df = 5) # 出现5次以上的才纳入wordmtx = countvec.fit_transform(df0.cleantxt)wordmtx In [ ]: # 按照7:3的比例生成训练集和测试集from sklearn.model_selection import train_test_splitx_train, x_test, y_train, y_test = train_test_split( wordmtx, df0.y, test_size=0.3) # 这里可以直接使用稀疏矩阵格式x_train[0] In [ ]: # 使用SVM进行建模from sklearn.svm import SVCclf=SVC(kernel = 'rbf', verbose = True)clf.fit(x_train, y_train) # 内存占用可能较高clf.score(x_train, y_train) In [ ]: # 对模型效果进行评估from sklearn.metrics import classification_reportprint(classification_report(y_test, clf.predict(x_test))) In [ ]: clf.predict(countvec.transform([df0.cleantxt[0]]))[0] In [ ]: # 模型预测import jiebadef m_pred(string, countvec, model) : words = " ".join(jieba.lcut(string)) words_vecs = countvec.transform([words]) # 数据需要转换为可迭代格式 result = model.predict(words_vecs) if int(result[0]) == 1: print(string, ":正向") else: print(string, ":负向") comment = "外观美观,速度也不错。上面一排触摸键挺实用。应该对得起这个价格。当然再降点大家肯定也不反对。风扇噪音也不大。"m_pred(comment, countvec, clf) In [ ]: comment = "作为女儿6.1的礼物。虽然晚到了几天。等拿到的时候,女儿爱不释手,上洗手间也看,告知不好。竟以学习毛主席来反驳我。我反对了几句,还说我对主席不敬。晕。上周末,告诉我她把火鞋和风鞋拿到学校,好多同学羡慕她。呵呵,我也看了其中的人鸦,只可惜没有看完就在老公的催促下睡了。说了这么多,归纳为一句:这套书买的值。"m_pred(comment, countvec, clf) In [ ]: # 读入原始数据集,和上面完全相同import pandas as pddfpos = pd.read_excel("购物评论.xlsx", sheet_name = "正向", header=None)dfpos['y'] = 1dfneg = pd.read_excel("购物评论.xlsx", sheet_name = "负向", header=None)dfneg['y'] = 0df0 = dfpos.append(dfneg, ignore_index = True)df0.head() In [ ]: # 分词和预处理,生成list of list格式import jiebadf0['cut'] = df0[0].apply(jieba.lcut)df0.head() In [ ]: # 按照7:3的比例生成训练集和测试集from sklearn.model_selection import train_test_splitx_train, x_test, y_train, y_test = train_test_split( df0.cut, df0.y, test_size=0.3)x_train[:2] 设置word2vec模型¶In [ ]: # 初始化word2vec模型和词表from gensim.models.word2vec import Word2Vecn_dim = 300 # 指定向量维度,大样本量时300~500较好w2vmodel = Word2Vec(vector_size = n_dim, min_count = 10)w2vmodel.build_vocab(x_train) # 生成词表 In [ ]: # 在评论训练集上建模(大数据集时可能会花费几分钟)# 本例消耗内存较少%time w2vmodel.train(x_train, \ total_examples = w2vmodel.corpus_count, epochs = 10) In [ ]: # 情感词向量间的相似度w2vmodel.wv.most_similar("不错") In [ ]: w2vmodel.wv.most_similar("失望") 生成整句向量用于情感分值预测¶对购物评论、微博等短文本而言,一般是将所有词向量的平均值作为分类算法的输入值。 In [ ]: # 生成整句所对应的所有词条的词向量矩阵pd.DataFrame([w2vmodel.wv[w] for w in df0.cut[0] if w in w2vmodel.wv]).head() In [ ]: # 用各个词向量直接平均的方式生成整句对应的向量def m_avgvec(words, w2vmodel): return pd.DataFrame([w2vmodel.wv[w] for w in words if w in w2vmodel.wv]).agg("mean") In [ ]: # 生成建模用矩阵,耗时较长%time train_vecs = pd.DataFrame([m_avgvec(s, w2vmodel) for s in x_train])train_vecs.head() 情感分析模型拟合¶In [ ]: # 用转换后的矩阵拟合SVM模型from sklearn.svm import SVCclf2 = SVC(kernel = 'rbf', verbose = True)clf2.fit(train_vecs, y_train) # 占用内存小于1Gclf2.score(train_vecs, y_train) In [ ]: from sklearn.metrics import classification_reportprint(classification_report(y_train, clf2.predict(train_vecs))) # 此处未用验证集 In [ ]: # 保存训练完毕的模型以便今后使用# sklearn在0.23版之后已移除joblib,需直接安装joblib包并import joblibimport joblib # joblib.dump(modelname, 'filename')# modelname = joblib.load('filename') In [ ]: # 模型预测import jiebadef m_pred(string, model): words = jieba.lcut(string) words_vecs = pd.DataFrame(m_avgvec(words, w2vmodel)).T result = model.predict(words_vecs) if int(result[0]) == 1: print(string, ":正向") else: print(string, ":负向") comment = "作为女儿6.1的礼物。虽然晚到了几天。等拿到的时候,女儿爱不释手,上洗手间也看,告知不好。竟以学习毛主席来反驳我。我反对了几句,还说我对主席不敬。晕。上周末,告诉我她把火鞋和风鞋拿到学校,好多同学羡慕她。呵呵,我也看了其中的人鸦,只可惜没有看完就在老公的催促下睡了。说了这么多,归纳为一句:这套书买的值。"m_pred(comment, clf2) 实战作业¶自行完成基于情感词典的分析程序,比较该方法与其他方法的预测准确度。 提示:可使用《知网》情感词语集作为词典。 尝试使用关键词进行基于词袋模型的情感分析,评估效果的改进情况。 在基于分布式表达的模型中,进行去除停用词等清理工作,比较前后模型效果的改变情况。 在本章所用数据中,各抽取1千条正向、负向评论,重新拟合基于词袋的和基于分布式表达的模型,比较前两种模型效果的改变情况。 In [ ]: chapter.txt[1] In [ ]: def cut_sentence(intxt): delimiters = frozenset('。!?') buf = [] for ch in intxt: buf.append(ch) if delimiters.__contains__(ch): yield ''.join(buf) buf = [] if buf: yield ''.join(buf) In [ ]: sentdf = pd.DataFrame(cut_sentence(chapter.txt[1]))sentdf In [ ]: # 去除过短的句子,避免摘要出现无意义的内容sentdf['txtlen'] = sentdf[0].apply(len)sentdf.head() In [ ]: sentlist = sentdf[0][sentdf.txtlen > 20]print(len(sentlist))sentlist In [ ]: from sklearn.feature_extraction.text import CountVectorizerfrom sklearn.feature_extraction.text import TfidfTransformertxtlist = [ " ".join(jieba.lcut(w)) for w in sentlist]vectorizer = CountVectorizer() X = vectorizer.fit_transform(txtlist) # 将文本中的词语转换为词频矩阵 In [ ]: tfidf_matrix = TfidfTransformer().fit_transform(X) In [ ]: # 利用nx包实现pagerank算法import networkx as nx similarity = nx.from_scipy_sparse_matrix(tfidf_matrix * tfidf_matrix.T) scores = nx.pagerank(similarity) In [ ]: scores In [ ]: tops = sorted(scores.items(), key = lambda x: x[1], reverse = True) In [ ]: tops[:3] In [ ]: print(sentlist.iloc[tops[0][0]])print(sentlist.iloc[tops[1][0]])sentlist.iloc[tops[2][0]] In [ ]: topn = 5topsent = sorted(tops[:topn])abstract = ''for item in topsent: abstract = abstract + sentlist.iloc[item[0]] + "......"abstract[:-6] 实战作业¶请自行尝试完成利用TextRank、TF-IDF等指标来抽取句子并生成摘要的程序。 请尝试使用段落而不是句子来生成摘要。 提示:对于字数较长的段落,可以考虑进一步在其中提取关键句来代替整段用于摘要。 思考自动摘要和抽取文档主题的分析操作有什么异同之处。 In [ ]: # 载入所需工具包import numpy as npimport pandas as pdfrom keras.models import Sequentialfrom keras.layers import Densefrom keras.layers import Dropoutfrom keras.layers import LSTMfrom keras.callbacks import ModelCheckpointfrom keras.utils import np_utils In [ ]: rawtxt = pd.read_csv("r&j.txt", sep = 'aaaaa', names = ['txt'], engine = 'python')print(rawtxt.head())rawtxt.txt[1] In [ ]: # 处理大小写,末尾增加空格def m_perproc(tmpstr): return (tmpstr + " ").lower()rawtxt.txt = rawtxt.txt.apply(m_perproc)rawtxt.txt[1] In [ ]: raw_txt = rawtxt.txt.agg("sum")raw_txt In [ ]: # 将字符转换为数值代码以便处理chars = sorted(list(set(raw_txt))) # 生成字符listchar_to_int = dict((c, i) for i, c in enumerate(chars)) # 字符-数值对应字典int_to_char = dict((i, c) for i, c in enumerate(chars)) # 数值-字符对应字典chars 构造训练测试集¶In [ ]: seq_length = 100x = []; y = []for i in range(0, len(raw_txt) - seq_length): given = raw_txt[i:i + seq_length] # 将前seq_length个字符作为预测用变量 predict = raw_txt[i + seq_length] # 将当前字符作为因变量 x.append([char_to_int[char] for char in given]) y.append(char_to_int[predict]) In [ ]: x[:3] In [ ]: y[:3] 将文本的数值表达转换为LSTM需要的数组格式:[样本数,时间步伐,特征] In [ ]: n_patterns = len(x)n_vocab = len(chars)# 把x变成LSTM需要的格式,reshape最后的1表示每个数值均为单独一个向量(代表一个字母输入)x = np.reshape(x, (n_patterns, seq_length, 1)) x = x / float(n_vocab) # 转换为0-1之间的数值以方便计算x[0] In [ ]: # 将因变量的类型正确指定为类别y = np_utils.to_categorical(y) y[0] 建立LSTM模型¶In [ ]: model = Sequential() # LSTM层指定为128个神经元model.add(LSTM(128, input_shape = (x.shape[1], x.shape[2]))) model.add(Dropout(0.2)) # 抛弃20%的结果,防止过拟合model.add(Dense(y.shape[1], activation = 'softmax')) # 使用标准的NN作为内核# 指定损失函数model.compile(loss = 'categorical_crossentropy', optimizer = 'adam') In [ ]: # batch_size为分批量将数据用于训练,以减小计算资源的需求# epochs次数越多,模型训练效果越好,但所需时间也线性增加model.fit(x, y, epochs = 2, batch_size = 64) 进行文本预测¶In [ ]: def predict_next(input_array): # 进行下一个字符的预测 x = np.reshape([0 for i in range(seq_length - len(input_array))] + input_array, (1, seq_length, 1)) # 生成预测用的x序列 x = x / float(n_vocab) y = model.predict(x) return ydef string_to_index(raw_input): # 将输入的字符转换为索引值 res = [] for c in raw_input[(len(raw_input) - seq_length):]: res.append(char_to_int[c]) return resdef y_to_char(y): # 将预测结果由索引值转换回字符 largest_index = y.argmax() # 取最大数值对应的索引值 c = int_to_char[largest_index] return c In [ ]: def generate_article(init, rounds = 50): # 按照指定的字符长度进行预测 in_string = init.lower() for i in range(rounds): n = y_to_char(predict_next(string_to_index(in_string))) in_string += n # 将预测到的新字符合并,用于下一步预测 return in_string In [ ]: # 进行字母预测init = 'We produce about two million dollars for each hour we work. The fifty hours is one conservative estimate for how long'article = generate_article(init)article In [ ]: # 载入所需工具包import jiebafrom gensim.models.word2vec import Word2Vecimport numpy as npimport pandas as pdfrom keras.models import Sequentialfrom keras.layers import Densefrom keras.layers import Dropoutfrom keras.layers import LSTMfrom keras.callbacks import ModelCheckpointfrom keras.utils import np_utils In [ ]: dict = '金庸小说词库.txt'jieba.load_userdict(dict) # dict为自定义词典的路径# 以整句或者整段为基本单位进行分析显然更为合适corpus = [jieba.lcut(item) for item in raw.txt]corpus[:3] 将文本转换为word2vec向量,此处长度越长,则后续所需的训练时间也越长。 In [ ]: # 此处完全可以使用外部语料库进行更全面的训练w2v_model = Word2Vec(corpus, vector_size = 100, window = 5, min_count = 5, workers = 4) In [ ]: w2v_model.wv['郭啸天'] In [ ]: # 将数据还原为一个长listraw_input = [item for sublist in corpus for item in sublist]print(len(raw_input))raw_input[:10] In [ ]: # 列出模型中纳入的词条vocab = w2v_model.wv.index_to_keyvocab In [ ]: # min_count = 5参数会过滤掉低频词,因此需要在文本中同步清除这些低频词text_stream = []for word in raw_input: if word in vocab: text_stream.append(word)print(len(text_stream))text_stream[:10] 构造训练测试集¶In [ ]: seq_length = 10 # 取前面10个单词用于预测x = []; y = []for i in range(0, len(text_stream) - seq_length): given = text_stream[i : i + seq_length] predict = text_stream[i + seq_length] x.append(np.array([w2v_model.wv[word] for word in given])) y.append(w2v_model.wv[predict]) In [ ]: len(x) In [ ]: x[0][0] In [ ]: y[0] 随后将w2v格式的数值表达转换为LSTM需要的格式:[样本数,时间步伐,特征] In [ ]: x = np.reshape(x, (-1, seq_length, 100)) # 每一个词条,对应一个word2vec向量y = np.reshape(y, (-1, 100)) 建立LSTM模型¶In [ ]: model = Sequential()model.add(LSTM(128, input_shape = (seq_length, 100)))model.add(Dropout(0.2))model.add(Dense(100, activation = 'sigmoid'))model.compile(loss = 'mse', optimizer = 'adam') In [ ]: model.fit(x, y, epochs = 5, batch_size = 64) In [ ]: model.summary() In [ ]: model.save_weights('LSTM.h5') # 文件类型是HDF5 In [ ]: model.load_weights('LSTM.h5') In [ ]: model.fit(x, y, epochs = 10) # 按照指定的数据和参数继续训练模型 持续训练找到优化模型¶In [ ]: from keras.callbacks import ModelCheckpointcheckpointer = ModelCheckpoint(filepath = 'LSTM_best.hdf5', monitor = 'val_loss', save_best_only = True, verbose = 1) In [ ]: model.compile(loss = 'mse', optimizer = 'adam') In [ ]: model.fit(x, y, epochs = 50, validation_data = (x, y), callbacks = [checkpointer]) 进行文本预测¶In [ ]: def predict_next(input_array): x = np.reshape(input_array, (-1, seq_length, 100)) y = model.predict(x) return ydef string_to_index(raw_input): input_stream = [] for word in jieba.lcut(raw_input): if word in vocab: input_stream.append(word) res = [] for word in input_stream[(len(input_stream) - seq_length):]: res.append(w2v_model.wv[word]) return resdef y_to_word(y): word = w2v_model.wv.most_similar(positive = y, topn = 1) return word In [ ]: def generate_article(init, rounds = 50): in_string = init.lower() for i in range(rounds): n = y_to_word(predict_next(string_to_index(in_string))) in_string += n[0][0] return in_string In [ ]: init = '郭啸天、杨铁心越听越怒。郭啸天道:“靖康年间徽钦二帝被金兵掳去这件大耻,我们'article = generate_article(init)print(article) 实战作业¶有GPU计算条件的,请尝试安装和配置TensorFlow的GPU版本。 尝试对原始文本进行缩减,只筛选出包含郭靖、黄蓉的段落进行训练,然后进行郭靖、黄蓉之间对话的文本自动写作。 在其余参数基本保持不变的情况下,将按段落进行训练修改为按照整句进行训练,比较两者的效果。 |
|
来自: oipnfpte40pbqo > 《文件夹1》