预处理数据的时候使用多进程可以大大减少程序运行时间。
上代码
with Pool(15)as proc:
results = list(
tqdm(
proc.imap(convert_one,files,
),
total=len(files)
))
results = list(chain.from_iterable(results))
解释:files是传入的一个list,里边的每一个元素都需要进行convert_one
操作,如果返回的是一个单独元素的话,results就直接得到的是一个一维list,但是上边的代码是具体业务的,convert_one
返回的是一个list,则得到的是一个二维list: [[item,item item], [item item item] …] , 最后使用了chain.from_iterable()
转换为 [item , item , item …]形式的一维list。
使用imap
而不使用map
, 是因为可以提前返回值给tqdm,具体差异建议百度。
如果convert_one 需要一些固定的参数,那么可以使用偏函数,实例:
from functools import partial
with Pool(threads,initializer=squad_convert_example_to_features_init, initargs=(tokenizer,)) as p:
annotate_ = partial( #构造偏函数,第一个是原函数,后边是传入的固定参数
squad_convert_one_example_to_features,
max_seq_length=max_seq_length,
doc_stride=doc_stride,
max_query_length=max_query_length,
is_training=is_training,
)
features = list(
tqdm(
p.imap(annotate_, examples, chunksize=32),
total=len(examples),
desc="convert squad examples to features",
)
)
如果是要传入随机变化的参数,比如files 列表还有一个对应的files2 列表,那么可以考虑zip一下。
对于一些可能会竞争到的资源比如上边代码的tokenizer,需要使用那个init函数把tokenizer给global,不然十几个进程使用一个tokenizer,根本跑不动,具体为什么要使用一个函数,我也不清楚。initargs=(tokenizer,)
不要写错,传入的需要是一个iterable的参数。在convert_one里边,不需要tokenizer的传入,直接使用global的。其他只是个数值传入的则不需要,直接在偏函数里写就是了。
def squad_convert_example_to_features_init(tokenizer_for_convert):
global tokenizer
tokenizer = tokenizer_for_convert
def squad_convert_one_example_to_features_init(example):
blabla...
xx = tokenizer.encode('haha')
参考:HuggingFace的transformers仓库
Contact me : jianshu[AT]std.uestc.edu.cn