学习笔记--python中使用多进程、多线程加速文本预处理
一.任务描述
最近尝试自行构建skip-gram模型训练word2vec词向量表。其中有一步需要统计各词汇的出现频率,截取出现频率最高的10000个词汇进行保留,形成常用词词典。对于这个问题,我建立了两个list,词汇list 和 词汇数量list,分别记录新出现的词汇和该词汇出现的次数。遍历整个语料文件,收集各个词汇并计算其出现次数。最后,对词汇数量list进行降序排序,留下出现频率最高的10000个词汇。流程大致如下图:

二.问题描述
在程序实际运行的过程中,发现程序运行的速度实在是太慢。对于一个4000行的示例语料文件,分词+统计词频一共花费384秒。而此次目标的语料文件一共有上百万行,随着语料的扩充,词汇量也势必增大,每次对词汇list的检索开销也会有所上升。那么如何提高程序的运行速度呢?
三.问题解决思路
在未优化的程序运行过程中,我们发现电脑的CPU利用率一直处于20%左右,远未达到其最大负载功率,因此便想到使用多线程、多进程来并行的对文本进行处理,以提高处理效率。
思路如下:
由于我的电脑是八核CPU,因此我将语料文件平均分成八份,开启8个线程/进程来对这些语料数据进行处理。待八个进程/线程的工作结束后,再对结果进行拼接。
四.使用多线程程序处理语料文件。
python中多线程可以使用threading模块进行调用
具体调用语句为threading.Thread(target=函数名,args=(参数列tuple)),因此,首先的工作就是编写线程工作函数。
1.预设如下全局变量用于存储每个线程的输出结果。
# TODO 全局变量 TODO
pured_file = '../dataset/news_sohusite_xml_pure_200M_lines_content.txt' # 未切分语料地址
corp_list_all = [] # jieba分词过后的语料文件
for i in range(8):
corp_list_all.append([])
dict_list_all = [] # 用于每个进程的词汇列表
for i in range(8):
dict_list_all.append([])
dict_list_index_all = [] #用于每个进程的 词汇列表对于的出现次数。
for i in range(8):
dict_list_index_all.append([])
finish_tag = [0,0,0,0,0,0,0,0]
# TODO 全局变量 TODO
线程工作函数如下
def cut_content_to_dict(c_lines,thread_index):
print("thread %s, length %s"%(str(thread_index),str(len(c_lines))))
splited_list = []
for sentences in c_lines:
words = jieba.cut(sentences,cut_all=False)
words = ' '.join(words) # 使用 空格 将每一个词汇隔开 此时是str类型变量。
splited_list.append(words[:-1]) # 去除句末的 '\n'
corp_list_all[thread_index] = splited_list # 将分词结果存入全局变量
word_index_list = [] #临时存储词汇列表
word_num_list = [] #临时存储词汇出现次数
count = 0
for item in splited_list:
words = item.split(' ')
for wd in words:
if wd in word_index_list:
word_num_list[word_index_list.index(wd)] += 1 # 有这个单词,则对应计数加1
else:
word_index_list.append(wd)
word_num_list.append(1)
if count%250==0:
print("thread %s, finished %s"%(str(thread_index),str(count)))
count+=1
dict_list_all[thread_index] = word_index_list #将词汇表存入全局变量对应位置
dict_list_index_all[thread_index] = word_num_list #将词汇频率存入全局变量对应位置
finish_tag[thread_index] = 1
if 0 not in finish_tag: # finish_tag 全为1 时,启用拼接函数
put_together()
其中,finish_tag全局变量用于检测是否所有线程都已工作完毕,其初始化为[0,0,0,0,0,0,0,0],待所有线程工作完毕后会成为全1向量,此时调用拼接函数put_together()对dict_list_all中的词汇进行拼接。
在main函数中,将语料数据分成8块,分别分配给各个线程,再对线程轮流进行启动:
with open(pured_file,'r',encoding='utf-8') as content_file:
lines = content_file.readlines()
lines = lines[:4000]
block_size = int(len(lines)/8)
tasks_8 = []
for i in range(7):
tasks_8.append(lines[i*block_size:(i+1)*block_size])
tasks_8.append(lines[7*block_size:])
thread_list = []
for i in range(8):
thread_list.append(threading.Thread(target=cut_content_to_dict, args=(tasks_8[i],i)))
for i in range(8):
thread_list[i].start()
经过这番改造,使用多线程处理4000行的示例语料文件共花费197秒,比未优化的代码速度提高了几乎一倍。但是通过观察CPU使用率,发现CPU并未全功率运行,占用率仍在20%多徘徊。因此尝试使用多进程来完成语料分词、统计任务。
五.使用多进程处理语料文件
python中多进程可以使用multiprocessing模块进行调用,与多线程的不同之处在于,每个子进程会单独划分一份资源,因此不能使用全局变量的方式来对子进程输出结果进行存储。multiprocessing模块提供了特殊的变量类型multiprocessing.Manager().list(),multiprocessing.Value()来获取子进程的输出结果。
首先编写进程工作函数:
# array_c,array_di,array_dn 是三个multiprocessing.Manager().list 类型变量,可以用于返回输出结果
def cut_content_to_dict(c_lines,proc_index,array_c,array_di,array_dn):
print("process %s, length %s"%(str(proc_index),str(len(c_lines))))
for sentences in c_lines:
words = jieba.cut(sentences,cut_all=False)
words = ' '.join(words) # 使用 空格 将每一个词汇隔开 此时是str类型变量。
array_c.append(words[:-1]) # 去除句末的 '\0'
count = 0
for item in array_c:
words = item.split(' ')
for wd in words:
if wd in array_di:
array_dn[array_di.index(wd)] += 1 # 有这个单词,则对应计数加1
else:
array_di.append(wd)
array_dn.append(1)
if count%250==0:
print("process %s, finished %s"%(str(proc_index),str(count)))
count+=1
与之前的线程函数相比,这个进程函数多了很多参数,其中,array_c代表传入的语料数据,array_di,array_dn对应多线程代码中dict_list_all和dict_list_index_all,分别表示词汇list和词汇数量list。这种传递返回值的方式类似C语音中的指针调用。另外,multiprocessing.Value()类型的变量proc_index用于对子进程进行标注,记录该进程的执行进度。
在main函数中,需要额外一步初始化multiprocessing.Manager().list()类型和multiprocessing.Value()变量。
proc_id = []
corp_list_all = [] # jieba分词过后的语料文件
dict_list_all = [] # 用于每个进程的词汇列表
dict_list_index_all = [] #用于每个进程的 词汇列表对于的出现次数。
for i in range(8):
corp_list_all.append(multiprocessing.Manager().list([])) # 主进程与子进程的共享list
for i in range(8):
dict_list_all.append(multiprocessing.Manager().list([])) # 主进程与子进程的共享list
for i in range(8):
dict_list_index_all.append(multiprocessing.Manager().list([])) # 主进程与子进程的共享list
for i in range(8):
proc_id.append(multiprocessing.Value("i",i)) # "i" 表示int类型, 第二个i表示初始数值
在对每个进程进行调用时,分别传入这些参数。
p_list = [] # 用于存储8个进程
for i in range(8):
p = multiprocessing.Process(target=cut_content_to_dict,args=(tasks_8[i],proc_id[i],\
corp_list_all[i],dict_list_all[i],dict_list_index_all[i]))
p_list.append(p)
for p in p_list:
p.start()
for p in p_list:
p.join()
# 所有进程结束后,调用拼接函数
put_together(corp_list_all,dict_list_all,dict_list_index_all)
经过测试,使用多进程运行的代码处理4000行语料数据共花费159秒的时间,比多线程代码的运行速度又有所提高,同时CPU利用率也提升到了100%。
以上代码可用在我的GitHub中找到(https://github.com/NosenLiu/DIY_Word2Vec)。后续还将根据此次预处理结果构建一个skip-gram以训练出一个word2vec词向量表,有兴趣的同学可以下载进行参考。
学习笔记--python中使用多进程、多线程加速文本预处理的更多相关文章
- 聊聊Python中的多进程和多线程
今天,想谈一下Python中的进程和线程. 最近在学习Django的时候,涉及到了多进程和多线程的知识点,所以想着一下把Python中的这块知识进行总结,所以系统地学习了一遍,将知识梳理如下. 1. ...
- python中的多进程与多线程(二)
1.使用多线程可以有效利用CPU资源,线程享有相同的地址空间和内存,这些线程如果同时读写变量,导致互相干扰,就会产生并发问题,为了避免并发问题,绝不能让多个线程读取或写入相同的变量,因此python中 ...
- 深入浅析python中的多进程、多线程、协程
深入浅析python中的多进程.多线程.协程 我们都知道计算机是由硬件和软件组成的.硬件中的CPU是计算机的核心,它承担计算机的所有任务. 操作系统是运行在硬件之上的软件,是计算机的管理者,它负责资源 ...
- Python学习笔记6-Python中re(正则表达式)模块学习
今天学习了Python中有关正则表达式的知识.关于正则表达式的语法,不作过多解释,网上有许多学习的资料.这里主要介绍Python中常用的正则表达式处理函数. re.match re.match 尝试从 ...
- Python中使用多进程来实现并行处理的方法小结
进程和线程是计算机软件领域里很重要的概念,进程和线程有区别,也有着密切的联系,先来辨析一下这两个概念: 1.定义 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和 ...
- MongoDB学习笔记:Python 操作MongoDB
MongoDB学习笔记:Python 操作MongoDB Pymongo 安装 安装pymongopip install pymongoPyMongo是驱动程序,使python程序能够使用Mong ...
- Python中的鸡肋多线程
作者:DarrenChan陈驰链接:https://www.zhihu.com/question/23474039/answer/269526476来源:知乎著作权归作者所有.商业转载请联系作者获得授 ...
- ArcGIS案例学习笔记-点集中最近点对和最远点对
ArcGIS案例学习笔记-点集中最近点对和最远点对 联系方式:谢老师,135-4855-4328,xiexiaokui@qq.com 目的:对于点图层,查找最近的点对和最远的点对 数据: 方法: 1. ...
- 《Cocos2d-x游戏开发实战精解》学习笔记3--在Cocos2d-x中播放声音
<Cocos2d-x游戏开发实战精解>学习笔记1--在Cocos2d中显示图像 <Cocos2d-x游戏开发实战精解>学习笔记2--在Cocos2d-x中显示一行文字 之前的内 ...
随机推荐
- js如何实现类的继承
方法一:借助构造函数实现继承 这种方法的缺点:原型链上的东西并没有被继承. 方法二:借助原型链实现继承 这种方法的缺点:改变了一个实例对象,另一个实例对象也跟着改变,因为s1.__proto__ == ...
- 【数论】卢卡斯定理模板 洛谷P3807
[数论]卢卡斯定理模板 洛谷P3807 >>>>题目 [题目] https://www.luogu.org/problemnew/show/P3807 [输入格式] 第一行一个 ...
- h5页面避免两个页面反复跳转死循环
项目中经常会碰到在一个页面加判断之后跳转另一个页面,并且无法返回,来回跳转,死循环,遇到这种情况可以在跳转时使用location,replace,关闭之前页面,与location.href 两者的不同 ...
- ubuntu 安装nginx, 出现 Unable to locate package
今天在初始化一台新的ubuntu 服务器时,敲上了 sudo apt-get install nginx 来安装nginx, 却发现提示: Reading package lists... Done ...
- ASP.NET 发送邮件 SMTP
目录 // 邮件信息 对象 MailMessage mail = new MailMessage(); //新建邮件SMTP客户端 对象 用于发送 ...
- linux 下的read write 和fread fwrite
待进一步测试啊,先占坑 --------2017/7/17 忘记之前要写什么了,只记得当时测试完得出的结论是,无论是写设备还是写文件,都用read/write是既安全又省事情的举动.还熟悉. 尽多少力 ...
- 跟随我在oracle学习php(18)
修改表: 一般概述 通常,创建一个表,能搞定(做到)的事情,修改表也能做到.大体来说,就可以做到: 增删改字段: 增:alter table 表名 add [column] 字段名 字段类 ...
- C#如何在生成文件夹或者文件时候自动重命名
如果你在一个文件夹里面, 连续添加文件夹或者文件(不改名字), 那么系统会自动加上(1),(2),(3)... 这个效果我在网上搜不到, 自己写一下也不太难. 文件夹的:(不断点击按钮就有效果) pr ...
- Tomcat各个版本下载路径
http://archive.apache.org/dist/tomcat/tomcat-7/ 修改对应版本号即可比如 tomcat-8
- js 表格操作(兼容模式
用insertRow,insertRow操作表格时,发现在谷歌浏览器中顺序和IE是反的 // 表格新增行 function addTableRow($id,$arr,$rowAttr){ var ta ...