在我的项目中,我一般都使用文本.我发现预处理可能非常慢.所以我想问你是否知道如何优化我的代码.流程是这样的:
获取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;
首先,我删除所有数字,特殊字符,单词和双字母单词.然后在循环中删除停用词.大概有270个停用词.
有可能让它更快吗?
编辑:
我想要做的是删除所有不超过2个字母的单词.所以我想把所有特殊的字符(包括'.',',','?','!'等)数字,停止字样.我只需要用于数据挖掘的纯语言.
迭代替换单词将成为您实现中的最大瓶颈.在每次迭代时,您必须扫描整个字符串以获取禁用词,然后替换操作必须分配一个新字符串并使用替换后的文本填充它.那不会很快.
一种更有效的方法是对字符串进行标记化并以流式方式执行替换.将输入划分为由适当的空格或分隔符分隔的单个单词.您可以逐步执行此操作,因此您无需分配任何额外的内存来执行此操作.对于每个单词(标记),您现在可以在停用词的哈希集中执行查找 - 如果找到匹配项,则在将最终文本流式化为单独文本时将替换它StringBuilder
.如果令牌不是停用词,只需将其流出StringBuilder
未经修改的.此方法应具有O(n)性能,因为它只扫描字符串一次并使用a HashSet
来执行禁用字查找.
以下是我期望表现更好的一种方法.虽然它不是完全流式传输(它使用String.Split()
分配了一系列附加字符串的数据),但它在一次传递中完成所有处理.优化代码以避免分配额外的字符串可能不会提供太多改进,因为您仍需要提取子字符串以执行与停用词的比较.
下面的代码返回一个排除所有停用词和单词的单词列表,结果是两个字母或更短的字母.它还对停用词使用不区分大小写的比较.
public IEnumerableSplitIntoWords( string input, IEnumerable stopwords ) { // use case-insensitive comparison when matching stopwords var comparer = StringComparer.InvariantCultureIgnoreCase; var stopwordsSet = new HashSet ( 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 ); }