PySide2、nltk、wordcloud、gensim、sklearn、pyinstaller实现词嵌入可视化、绘制词云图、制作GUI并打包的踩坑总结

本文是一位物理专业的学生在完成英语词汇学项目的编程作业时的经验分享。项目涉及gensim的模型读取、TSNE降维、词云图绘制等,遇到的问题包括nltk_data的路径设置、预训练模型的选择、gensim内存占用、日志记录、wordcloud打包问题、UI设计保存以及多线程的使用等,并给出了相应的解决策略和建议。
摘要由CSDN通过智能技术生成

最近有个英语词汇学的期末作业,老师说可以写论文也可以写一个小程序。作为一个学物理的兔子,当然选择写程序啦(误)。不过其中遇到了不少坑,就来总结一下。

首先说一下这个程序的设计思路。其实就是做一个图形界面,主要功能有两个:1用gensim读取模型,搜索相似词,然后用sklearn的TSNE降维,最后用matplotlib画图;2读取一个txt文本,用nltk的tokenizer分词,再做一下词形还原和去除停用词,然后文本分析,最后用wordcloud画一张词云图。代码总体的逻辑还是很简单的,因此就不贴出来了。最后再用pyinstaller打了一个包,主要是怕老师那边没有运行环境。

在这个过程中遇到了不少问题,因此就在这记录一下。

1nltk_data要自己下载,不要通过nltk里的downloader。下载后要放在正确的位置。一般放在C:/或者D:/的根目录下就行。只解压需要用到的压缩包,不要一股脑全解压了。怎么判断需要用?其实很简单,就是在开发环境中看看console里面的报错。缺哪个解压哪个。最关键的来了!如果需要用pyinstaller打包,请把nltk_data中不用的文件都删掉,因为pyinstaller会把nltk_data中的全部文件都复制到dist目录里,然后打包的时间就会很长。最后文件就会很大。特别是corpora里的,解压出来好几个GB,一开始还一脸懵,想为啥打包时间这么长,后面发现大半时间在复制…

2如果要用word2vec,建议直接用预训练模型,比如googlenews-vectors-negative300,因为自己训练的话文本量太少了,模型效果就会很差。不过官方下载需要连接Google drive,我没法连上,最后是找了个网盘链接下载下来的。另外在nltk_data里似乎也有一个模型,在models/word2vector_samples目录下,不过我没用过。好像腾讯也有一些预训练模型,可以自行搜索。如果要针对特别的文本(比如特定体裁或是专业文献)进行分析,可能就需要使用针对性更强的预训练模型,还需要进行增量训练。

3gensim是一次性将模型读入内存的,这导致读取时间很长,也会占用很多内存。我在想能不能用更好的算法,只读入一部分进入内存。不过这样还能找到最类似的词吗(即调用model.wv.most_similar方法)?没有深入研究过源码,因此这点还不能确定。

4一定要用logging写日志。我没大型程序的开发经验,一开始没有写日志。在开发环境中还好,直接看console就行,结果到了发布版就懵逼了,根本不知道错在哪。另外,一开始用pyinstaller打包时不要加-w,因为这样不显示命令行了,然后nltk加载nltk_data失败的信息并没有写到logging里,是只在console里显示的,然后也不报异常,一开始搞得我不知所措。

5打包wordcloud时尤其要注意,其目录下的一个.ttf字库和一个stopwords文件没有被拷贝到dist/…/wordcloud文件夹下,然后在import wordcloud阶段就会直接闪退。看了下源码,它有几个全局的地址常量(我也不知道是不是这么叫的,反正都是大写的字母,按我的理解也许类似java中的static final String,或者类似C里面的宏,不过每个语言的细节都不一样,不能简单类比,而且这里的常量不属于任何一个类),然后或许在import wordcloud时就会把它们加载到内存里?

FILE = os.path.dirname(__file__)
FONT_PATH = os.environ.get('FONT_PATH', os.path.join(FILE, 'DroidSansMono.ttf'))
STOPWORDS = set(map(str.strip, open(os.path.join(FILE, 'stopwords')).readlines()))

因为找不到这个文件,结果就导致程序闪退。我真的…一开始我开发环境中好好的,咋打包后就不行了。搞了半天,最后用了个很傻的办法:打包时不加-w,保留命令行,然后用录屏软件,看闪退前一瞬间命令行的信息。最后才发现是import的问题。难不成以后还要…

tryimport wordcloud
except Exception as e:
	logger.exception("%s", e)

因此我后来就没有在pyinstaller 后面加 -F,这样就不会把它给打包成单个exe文件,然后手动拷贝以上两个文件到dist/…/wordcloud。

6用UIDesigner的时候,别忘了保存。建议最后点一下窗口里的空白部分,然后再ctrl+s,不然如果停留在修改过的对象上,ctrl+s后可能没有将全部修改保存。另外,由于是ui文件,pycharm不会给代码提示,在UIDesigner用的实例名称直接复制过来,自己打可能会打错,IDE也检查不了。另外,ui文件也要拷贝到打包后的根目录下

7用pyinstaller打包,要用纯净的虚拟环境,只pip要用的包。每打一次新的包,记得把dist、build文件夹和.spec文件删掉。不然可能会出现奇奇怪怪的问题,或者打出来的包过大。我还试了一下pipenv,感觉用起来不如pycharm自带的virtualenv来得顺手,而且有一些问题,是什么原因我最后也懒得研究了(ddl要到了)。我中间还试了一下用docker的pyinstaller镜像打包,好像还可以,不过好像也遇到了问题,具体是什么也忘了,因为后来直接放弃了(同样,ddl要到了)。
然后悲催的事情来了,因为装docker,开了Hyper-V啥的,我的MuMu打不开了(一开就蓝屏),试了半天都不行,包括在控制面板中关闭Hyper-V功能,在计算机管理里停止Hyper-V和VMware服务,或者在BOIS里打开VT。最后我放弃了,直接下载了最新版的MuMu,它提示检测到Hyper-V,需要关闭,我点确认,然后…就成功了。哎…昨晚痛失30勾玉。

8绘制降维后的聚类图,除了向TSNE算法输入要显示的词向量外,还要输入若干(至少1000吧)个无需在图中显示的词向量(也不要把模型中的词向量全部输入了,不然算不过来),这样才能展现出较好的聚类效果,否则就是一张均匀散布的散点图,没啥实际价值。

        self.text = self.ui.VocabularyInput.text().split(" ")
        self.text = [i for i in self.text if i != ""]
        for word in self.text:
            try:
                self.vectorList.append(self.model[word])  # 在模型中获取向量
            except Exception:  # 如果模型中没有这个词,就在窗口中显示没找到
                self.signal_load.text.emit(self.ui.textBrowser, word, 0)
            else:  # 如果找到了这个词,就记录下这个词和词向量
                self.wordList.append(word)  
                self.signal_load.text.emit(self.ui.textBrowser, word, 1)
            sleep(0.1)
        count = 0
        for word in self.model.index_to_key:  # 额外加入2000个在无需图中显示的词向量!!!!
            if word not in self.text:
                self.vectorList.append(self.model[word])
                count += 1
            if count >= 2000:
                break
  		self.signal_info.text.emit(self.ui.textBrowser, "loading finished")
      	self.signal_info.text.emit(self.ui.textBrowser, "正在计算中,请稍候...")
      	tsne_model = TSNE(perplexity=30, n_components=2, init='pca', n_iter=1000, random_state=23)  # 初始化模型参数
        new_values = tsne_model.fit_transform(self.vectorList)  # 用TSNE对这些高维向量降维
        for value in new_values[:len(self.wordList)]:  # 只将需要显示的二维向量输出
            self.plotX.append(value[0])
            self.plotY.append(value[1])

这段代码部分参考了“带鱼工作室”大佬的博文。链接如下:http://t.csdn.cn/tbET7
最后的效果还是很明显的。
大图局部1
局部2

9最后,I/O密集或者CPU密集时,要开新的子线程io或计算,主线程用来更新图形界面,线程之间用信号与槽函数通信。这其实是一个很简单的问题,系统学过qt的话应该都了解的。但我之前没学过,发现io时图形界面没反应,也纳闷了半天,最后懂了。哈哈,也趁此机会学了一些线程相关的知识。

刚刚发现有些内容没有加粗,后面发现是因为句末的标点符号出现在了****内。现在都改过来了。

抱歉审核大哥,辛苦了

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值