快速文本预处理

Ven*_*tus 6 c# regex text-processing

在我的项目中,我一般都使用文本.我发现预处理可能非常慢.所以我想问你是否知道如何优化我的代码.流程是这样的:

获取HTML页面 - >(以纯文本 - >词干 - >删除停用词) - >进一步文本处理

括号中有预处理步骤.该应用程序运行在大约10.265秒,但预处理需要9.18秒!这是预处理50个HTML页面的时间(不包括下载).

我使用HtmlAgilityPack库将HTML转换为纯文本.这很快.转换1个文档需要2.5ms,所以它相对比较好.

问题出现了.阻止一个文档需要120毫秒.不幸的是,那些HTML页面是波兰语.用C#编写的波兰语不存在词干.我知道只有2个免费使用Java编写:stempel和morfologic.我借助IKVM软件将stempel.jar预编译为stempel.dll.所以没有更多的事要做.

消除停用词也需要很多时间(1个文档约70毫秒).它是这样完成的:


result = Regex.Replace(text.ToLower(), @"(([-]|[.]|[-.]|[0-9])?[0-9]*([.]|[,])*[0-9]+)|(\b\w{1,2}\b)|([^\w])", " ");
while (stopwords.MoveNext())
{
   string stopword = stopwords.Current.ToString();                
   result = Regex.Replace(result, "(\\b"+stopword+"\\b)", " ");                               
}
return result;
Run Code Online (Sandbox Code Playgroud)

首先,我删除所有数字,特殊字符,单词和双字母单词.然后在循环中删除停用词.大概有270个停用词.

有可能让它更快吗?

编辑:

我想要做的是删除所有不超过2个字母的单词.所以我想把所有特殊的字符(包括'.',',','?','!'等)数字,停止字样.我只需要用于数据挖掘的纯语言.

LBu*_*kin 15

迭代替换单词将成为您实现中的最大瓶颈.在每次迭代时,您必须扫描整个字符串以获取禁用词,然后替换操作必须分配一个新字符串并使用替换后的文本填充它.那不会很快.

一种更有效的方法是对字符串进行标记化并以流式方式执行替换.将输入划分为由适当的空格或分隔符分隔的单个单词.您可以逐步执行此操作,因此您无需分配任何额外的内存来执行此操作.对于每个单词(标记),您现在可以在停用词的哈希集中执行查找 - 如果找到匹配项,则在将最终文本流式化为单独文本时将替换它StringBuilder.如果令牌不是停用词,只需将其流出StringBuilder未经修改的.此方法应具有O(n)性能,因为它只扫描字符串一次并使用a HashSet来执行禁用字查找.

以下是我期望表现更好的一种方法.虽然它不是完全流式传输(它使用String.Split()分配了一系列附加字符串的数据),但它在一次传递中完成所有处理.优化代码以避免分配额外的字符串可能不会提供太多改进,因为您仍需要提取子字符串以执行与停用词的比较.

下面的代码返回一个排除所有停用词和单词的单词列表,结果是两个字母或更短的字母.它还对停用词使用不区分大小写的比较.

public IEnumerable<string> SplitIntoWords( string input,
                                           IEnumerable<string> stopwords )
{
    // use case-insensitive comparison when matching stopwords
    var comparer = StringComparer.InvariantCultureIgnoreCase;
    var stopwordsSet = new HashSet<string>( stopwords, comparer );
    var splitOn = new char[] { ' ', '\t', '\r' ,'\n' };

    // if your splitting is more complicated, you could use RegEx instead...
    // if this becomes a bottleneck, you could use loop over the string using
    // string.IndexOf() - but you would still need to allocate an extra string
    // to perform comparison, so it's unclear if that would be better or not
    var words = input.Split( splitOn, StringSplitOptions.RemoveEmptyEntries );

    // return all words longer than 2 letters that are not stopwords...
    return words.Where( w => !stopwordsSet.Contains( w ) && w.Length > 2 );
}
Run Code Online (Sandbox Code Playgroud)


Ven*_*tus 2

好吧,我知道 SO 不是一个纯粹的论坛,也许我不应该回答我自己的问题,但我想分享我的结果。

最后,感谢你们,我成功地优化了我的文本预处理。首先,我从我的问题中简化了长表达(遵循乔什·凯利的回答):

[0-9]|[^\w]|(\b\w{1,2}\b)

它的作用与第一个相同,但非常简单。然后再次按照 Josh Kelley 的建议,我将这个正则表达式放入汇编中。我在这里找到了将表达式编译成程序集的好例子。我这样做是因为这个正则表达式被使用了很多很多次。在听了几篇有关编译正则表达式的文章后,这是我的决定。我在消除停用词后删除了最后一个表达式(这没有真正的意义)。

因此 12KiB 文本文件的执行时间约为 15 毫秒。这只是为了上面提到的表达。

最后一步是停用词。我决定对 3 个不同的选项进行测试(执行时间针对相同的 12KiB 文本文件)。

一大正则表达式

包含所有停用词并编译成程序集(mquander 的建议)。这里没有什么需要澄清的。

  • 执行时间:~215ms

字符串替换()

人们说这比正则表达式更快。所以对于每个停用词我都使用了string.Replace()方法。需要进行多次循环才能得到结果:

  • 执行时间:~65ms

LINQ

LBushkin提出的方法。没什么可说的了。

  • 执行时间:~2.5ms

我只能说哇。只需比较第一个和最后一个的执行时间即可!非常感谢LBushkin!