NLTK 分词优化

NLTK Tokeninizing Optimization

我有一个 NLTK 解析函数,用于解析 TREC 数据集的 ~2GB 文本文件。此数据集的目标是标记整个集合,执行一些计算(例如计算 TF-IDF 权重等),然后 运行 对我们的集合进行一些查询以使用余弦相似度和 return最好的结果。

就目前而言,我的程序可以运行,但需要一个多小时(通常在 44-61 分钟之间)才能完成 运行。时间细分如下:

TOTAL TIME TO COMPLETE: 4487.930628299713
TIME TO GRAB SORTED COSINE SIMS: 35.24157094955444
TIME TO CREATE TFIDF BY DOC: 57.06743311882019
TIME TO CREATE IDF LOOKUP: 0.5097501277923584
TIME TO CREATE INVERTED INDEX: 2.5217013359069824
TIME TO TOKENIZE: 4392.5711488723755

很明显,令牌化占了大约 98% 的时间。我正在寻找加快速度的方法。

标记化代码如下:

def remove_nums(arr): 
    pattern = '[0-9]'  
    arr = [re.sub(pattern, '', i) for i in arr]    
    return arr


def get_words(para):   
    stop_words = list(stopwords.words('english'))    
    words = RegexpTokenizer(r'\w+')
    lower = [word.lower() for word in words.tokenize(para)]
    nopunctuation = [nopunc.translate(str.maketrans('', '', string.punctuation)) for nopunc in lower]
    no_integers = remove_nums(nopunctuation)
    dirty_tokens = [data for data in no_integers if data not in stop_words]
    tokens = [data for data in dirty_tokens if data.strip()]

def driver(file):
   myfile = get_input(file)
    p = r'<P ID=\d+>.*?</P>'       
    paras = RegexpTokenizer(p)   
    document_frequency = collections.Counter()   
    collection_frequency = collections.Counter()   
    all_lists = []    
    currWordCount = 0   
    currList = [] 
    currDocList = []
    all_doc_lists = []
    num_paragraphs = len(paras.tokenize(myfile))  


    print()
    print(" NOW BEGINNING TOKENIZATION ")
    print()
    for para in paras.tokenize(myfile):             
        group_para_id = re.match("<P ID=(\d+)>", para)
        para_id = group_para_id.group(1)       
        tokens = get_words(para)
        tokens = list(set(tokens))     
        collection_frequency.update(tokens)      
        document_frequency.update(set(tokens))       
        para = para.translate(str.maketrans('', '', string.punctuation))     
        currPara = para.lower().split()      
        for token in tokens:          
            currWordCount = currPara.count(token)          
            currList = [token, tuple([para_id, currWordCount])]          
            all_lists.append(currList)

            currDocList = [para_id, tuple([token, currWordCount])]
            all_doc_lists.append(currDocList)

    d = {}
    termfreq_by_doc = {}    
    for key, new_value in all_lists:       
        values = d.setdefault(key, [])       
        values.append(new_value)

    for key, new_value in all_doc_lists:
        values = termfreq_by_doc.setdefault(key, [])
        values.append(new_value)

我对优化还很陌生,正在寻找一些反馈。我确实看到 this post 谴责我的很多列表理解为 "evil",但我想不出解决我正在做的事情的方法。

代码没有很好的注释,所以如果由于某种原因它无法理解,那没关系。我在这个论坛上看到其他问题:在没有很多反馈的情况下加速 NLTK 标记化,所以我希望有一个关于标记化优化编程实践的积极话题。

来自: https://codereview.stackexchange.com/users/25834/reinderien

开启:https://codereview.stackexchange.com/questions/230393/tokenizing-sgml-text-for-nltk-analysis

正则表达式编译

如果性能是一个问题,这:

arr = [re.sub(pattern, '', i) for i in arr]

是个问题。您要在每次函数调用和每次循环迭代时重新编译您的正则表达式!相反,将正则表达式移动到函数外部的 re.compile()d 符号。

同样适用于re.match("<P ID=(\d+)>", para)。换句话说,你应该发布类似

group_para_re = re.compile(r"<P ID=(\d+)>")

循环外,然后

group_para_id = group_para_re.match(para)

循环内部。

过早的生成器实现

同一行还有另一个问题 - 您将 return 值强制为列表。查看您的 no_integers 用法,您只需再次迭代它,因此将整个结果保存在内存中没有任何价值。相反,将其保留为生成器 - 用圆括号替换括号。

同样适用于nopunctuation

设置会员资格

stop_words 不应该是 list - 它应该是 set。了解其性能 here。查找是平均 O(1),而不是列表的 O(n)。

变量名

nopunctuation 应该是 no_punctuation.