最近在做一个项目,遇到一个比较棘手的问题,那就是在用python 处理数据的时候效率非常低,在查阅了相关问题的同时,学习到不少小窍门,先记录一下供学习。

先给出一些方法,最后结合笔者自己的一个例子看一下实际效果

第一招:

numba神器

第二招:

多进程的使用,不得不说的是为什么不使用多线程,因为多线程其实在python 里面是个表象,其本质上还是切分时间片,所以要想更好的利用其多核cpu,Python的话还是推荐多进程

主要就是使用了multiprocessing这个包,关于该包的使用网上资料多多,这里就不再多述大家可以查看便是

实际应用的话主要就是使用了pool池,一般模板的话就是:

import multiprocessing
import time
import os
print("温馨提示:本机为",os.cpu_count(),"核CPU")  

def func(msg):
    print "msg:", msg
    time.sleep(3)
    print "end"

if __name__ == "__main__":
    #这里开启了4个进程
    pool = multiprocessing.Pool(processes = 4)
    for i in xrange(4):
        msg = "hello %d" %(i)
        pool.apply_async(func, (msg, ))   

    pool.close()
    pool.join()   #调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束
    print "Successfully"

 --------------------------------------------------------------------------------------------------------------------------------------------------------------------

在用python 处理数据的时候,经常需要使用pandas,尤其是需要操作dataframe这个数据结构,比如在机器学习特征工程的时候,需要对某一列(某个特征)做某一种操作(时间转化等等),其实这时候问题就是:

df.map(func) 而我们知道这种形式其实是非常适合做并行处理的(主要我们这里强调的是并行不是并发),那么就以笔者当前做的事情为例:

背景:我是要对一个生物分子提取指纹,说白了就是对'ROMol'这一字段下的数据做一定操作

如果什么优化方法也不加,即使用单进程做的话,大概需要多长时间呢(以处理50个分子为例):

python并行执行request python并行处理数据_numba

可以看到大概需要465秒,其实当前待处理的数据量大概是40万,这样算下去的话,大概要。。。。。。。。。呵呵

那么使用多进程呢?首先将50个分子分片成了10个小文件,即每个文件下有5个分子,然后为每个文件开启一个进程进行处理,相当于开启了10个进程,实验结果如下:

python并行执行request python并行处理数据_numba_02

按理论计算的话,效率应该是提升10被吧,但是这里仅仅提高了大约4.7倍的样子

------------------------------------------------------------------------------------------------------------------------------------------------------------------

效果呢还是有的,但是上面这样貌似还是太麻烦了,还得对文件进行划分,最后使用多进程处理完了得到的应该也是一堆小文件,还得进行合并,尽管拆分和合并的过程很容易就可以写出来

那么有没有别的方法,就是说直接在dataframe上面进行多线程呢?肯定是有的啦

这里给出一个讨论:python - Parallelize apply after pandas groupby - Stack Overflow

大家可以看一下

笔者将最关键的部分写出来:

import pandas as pd
from sklearn.externals.joblib import Parallel, delayed
from tqdm import tqdm, tqdm_notebook
tqdm_notebook().pandas()


df = pd.read_csv(inputfile)
df = df.groupby(df.index)
retlist = Parallel(n_jobs=jobNum)(delayed(youFunc)(group,Funcarg1,Funcarg12) for name, group in tqdm(df))
df = pd.concat(retlist)

首先tqdm就是一个进度条,不用多理会

讨论区使用的源joblib即

from joblib import Parallel, delayed

两则用法本无区别,但有时候sklearn继承的Parallel更优化

jobNum就是要开启的进程数目,youFunc就是自己定义的处理数据函数

比如说

def youFunc(df,ops):
    if ops=='add':
        df['c'] = df['a'] + df['b']
    else:
        df['c'] = df['a'] - df['b']
    return df

这里的df = df.groupby(df.index)

就是就是为了Parallel中的迭代,当然啦,讨论区也给出了直接使用multiprocessing这个包的做法,大家可以参考

在使用了这一种方法后,笔者的实验结果大概是1min6s 相比于97秒还是又提了不少。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------

题外话:

为什么没有成倍的提高效率呢?答案多种多样,网上解释的也是天花烂醉,因为笔者这里的资源是12核,按1:1的话开了12个进程看了下效果:

python并行执行request python并行处理数据_Parallel_03

大概是61秒的样子,那么随着数据量的增大,带来的效率是不是会更好点呢?

于是乎又试了下150个分子:

python并行执行request python并行处理数据_multiprocessing_04

貌似是会更好点,按50个需要1min计算的话,大概还是会需要5天半,这是何等的。。。。。。。

其实笔者问题的根源是fun函数中提取指纹这一函数的问题,看了一下,有的分子需要2秒,有的分子居然需要20秒,

二者有区别吗:

python并行执行request python并行处理数据_Parallel_05

python并行执行request python并行处理数据_pandas _06

哈哈哈,扯远了扯远了,,

--------------------------------------------------------------------------------------------------------------------------------------------------------------

总结一下本文主题和核心:

import pandas as pd
from sklearn.externals.joblib import Parallel, delayed
from tqdm import tqdm, tqdm_notebook
tqdm_notebook().pandas()


df = pd.read_csv(inputfile)
df = df.groupby(df.index)
retlist = Parallel(n_jobs=jobNum)(delayed(youFunc)(group,Funcarg1,Funcarg12) for name, group in tqdm(df))
df = pd.concat(retlist)

所以使用这种方法的关键在于怎么构建一个类似迭代器的东西,在daatframe上面是使用了groupby

-----------------------------------------------------------------------------------------------------------------------------------------

多说一句:

这里是对index索引进行了分组,显然index是唯一的,分组之后没有意义,但是带来的好处就是其构成了迭代器,但是有的时候我们可以在此基础上进一步优化,比如现在dataframe有一列特征是动物类别【狗,猪,兔】,假设我们现在的处理是对一种类别进行同一种处理操作,假设对遇到狗时我们进行的特征操作是A,遇到猪时进行的操作是B,试想假如还是上面的代码跑的话,我们是不是重复了很多操作,即有很多都是狗,本来我们只需要进行一次A操作即可,而因为现在是单独对每一个样本都进行处理即相当于重复了很多次A操作,所以我们可以这样优化:

首先我们处理特征的函数是Transanimal:

def Transanimal(animal):
        if animal=='dog':
            return A(animal)
        if animal=='pig':
            return B(animal)

比如该对应动物列特征的column 名字是animal,我们现在需要的是通过Transanimal转化,新生产一列假如叫做animal_values吧

没有优化前:

import pandas as pd
from sklearn.externals.joblib import Parallel, delayed
from tqdm import tqdm, tqdm_notebook
tqdm_notebook().pandas()

def youFunc(name,df):
    df['animal_values'] = Transanimal(df['animal'])
    return df


df = pd.read_csv(inputfile)
df = df.groupby(df.index)
retlist = Parallel(n_jobs=jobNum)(delayed(youFunc)(group) for name, group in tqdm(df))
df = pd.concat(retlist)

优化后

import pandas as pd
from sklearn.externals.joblib import Parallel, delayed
from tqdm import tqdm, tqdm_notebook
tqdm_notebook().pandas()

def youFunc(name,df):
    animal_values =  Transanimal(name)
    df['animal_values'] = animal_values
    return df


df = pd.read_csv(inputfile)
df = df.groupby(df['animal'])
retlist = Parallel(n_jobs=jobNum)(delayed(youFunc)(name,group) for name, group in tqdm(df))
df = pd.concat(retlist)

两则对比:

后者对animal分组后,会将很多是同一组的聚集在一次,而这些数据只需要进行一次Transanimal函数即可

假设一共有8组,也就是说进行了8次Transanimal

而没有优化前呢,有多少数据就进行了多少次Transanimal,

极端一点来说,假设所有数据都是狗这一种类,Transanimal处理过程需要1min,现在样本数是10000条

那么优化前需要10000min,优化后1min,当前这样计算时间是不准确的,这里仅仅为了对比一下

Transanimal需要的运行时间越长,组数越小,样本数越多

这种优化带来的效果越明显

-----------------------------------------------------------------------------------------------------------------------------------------

更一般的情况比如说我们处理的数据不是dataframe呢?

怎么构建一个迭代器呢?

这时候可能就要相当python中的yield

假设现在是读一个文件,然后对每一行进行T_T操作

def _read_file(file_name):
    while True:
        line = file_name.readline()
        if not line:
            break
        yield T_T(line)

那么

df = _read_file('animal.txt')

那么此时df就是一个迭代器啦,有了这个东西我们就又可以愉快的并行化啦:

retlist = Parallel(n_jobs=jobNum)(delayed(youFunc)(name,group) for name, group in tqdm(df))

到这里基本就讲完了,切记使用并行化的前提是顺序不重要,比如dataframe,原始的样本索引是0,1,2,3,4这样的

但处理完后完全有可能变成2,3,0,4,1 所幸的是我们在处理数据尤其是处理特征的时候,我们大多时候是不在乎这样顺序的。