【问题标题】:Replace words not in a dict to <unk>将不在字典中的单词替换为
【发布时间】:2018-11-25 01:54:28
【问题描述】:

我有一本字典(超过 10k 个单词)和一篇文章(超过 1000 万个单词)。我想用&lt;unk&gt;替换所有没有出现在字典中的单词。

我试过str.maketrans,但它的键应该是单个字符。

然后我尝试了这个https://stackoverflow.com/a/40348578/5634636,但正则表达式非常慢。

有更好的解决方案吗?

【问题讨论】:

  • 我认为您必须遵循与您列出的解决方案类似的方法。但根据我的经验,set 不是迭代列表,而是更快地检查成员资格。

标签: python string


【解决方案1】:

我们将问题分为两部分:

  • 给定单词列表passage,找到索引i,其中passage[i] 不在另一个单词列表dictionary 中。
  • 然后简单地将&lt;unk&gt; 放在这些索引处。

1 中需要做的主要工作。为此,我们首先将字符串列表转换为 2D numpy 数组,以便我们可以有效地执行操作。此外,我们对二进制搜索中所需的字典进行排序。此外,我们用 0 填充字典以具有与 passage_enc 相同的列数。

# assume passage, dictionary are initially lists of words
passage = np.array(passage)  # np array of dtype='<U4'
passage_enc = passage.view(np.uint8).reshape(-1, passage.itemsize)[:, ::4]  # 2D np array of size len(passage) x max(len(x) for x in passage), with ords of chars

dictionary = np.array(dictionary)
dictionary = np.sort(dictionary)    
dictionary_enc = dictionary.view(np.uint8).reshape(-1, dictionary.itemsize)[:, ::4]
pad = np.zeros((len(dictionary), passage_enc.shape[1] - dictionary_enc.shape[1]))    
dictionary_enc = np.hstack([dictionary_enc, pad]).astype(np.uint8)

然后我们只遍历段落,并检查字符串(现在是数组)是否在字典中。它需要 O(n * m), n, m 分别是段落和字典的大小。 但是,我们可以通过事先对字典进行排序并在其中进行二进制搜索来改进这一点。所以,它变成了 O(n * logm)。

此外,我们 JIT 编译代码以使其更快。下面,我使用numba

import numba as nb
import numpy as np

@nb.njit(cache=True)  # cache as being used multiple times
def smaller(a, b):
    n = len(a)
    i = 0
    while(i<n and a[i] == b[i]):
        i+=1
    if(i==n):
        return False
    return a[i] < b[i]

@nb.njit(cache=True)
def bin_index(array, item):
    first, last = 0, len(array) - 1

    while first <= last:
        mid = (first + last) // 2
        if np.all(array[mid] == item):
            return mid

        if smaller(item, array[mid]):
            last = mid - 1
        else:
            first = mid + 1

    return -1

@nb.njit(cache=True)
def replace(dictionary, passage):
    unknown_indices = []
    n = len(passage)
    for i in range(n):
        ind = bin_index(dictionary, passage[i])
        if(ind == -1):
            unknown_indices.append(i)
    return unknown_indices

检查样本数据

import nltk
emma = nltk.corpus.gutenberg.words('austen-emma.txt')
passage = np.array(emma)
passage = np.repeat(passage, 50)  # bloat coprus to have around 10mil words
passage_enc = passage.view(np.uint8).reshape(-1, passage.itemsize)[:, ::4]

persuasion = nltk.corpus.gutenberg.words('austen-persuasion.txt')
dictionary = np.array(persuasion)
dictionary = np.sort(dictionary)  # sort for binary search

dictionary_enc = dictionary.view(np.uint8).reshape(-1, dictionary.itemsize)[:, ::4]
pad = np.zeros((len(dictionary), passage_enc.shape[1] - dictionary_enc.shape[1]))

dictionary_enc = np.hstack([dictionary_enc, pad]).astype(np.uint8)  # pad with zeros so as to make dictionary_enc and passage_enc of same shape[1]

段落和字典的大小,最终符合 OP 要求的顺序,用于计时目的。这个电话:

unknown_indices = replace(dictionary_enc, passage_enc)

在我的 8 核 16G 系统上耗时 17.028s(包括预处理时间,显然不包括加载语料库的时间)。

那么,很简单:

passage[unknown_indices] = "<unk>"

P.S : 我想,我们可以通过在 replace 的 njit 装饰器中使用 parallel=True 来获得更快的速度。我遇到了一些奇怪的错误,如果我能够解决它会编辑。

【讨论】:

    猜你喜欢
    • 2014-02-05
    • 2020-01-17
    • 2014-05-20
    • 1970-01-01
    • 1970-01-01
    • 2022-06-13
    • 2016-02-26
    • 1970-01-01
    • 2022-12-09
    相关资源
    最近更新 更多