Performance 从大文件访问n-gram频率

Performance 从大文件访问n-gram频率,performance,nlp,large-files,n-gram,memory-efficient,Performance,Nlp,Large Files,N Gram,Memory Efficient,我对自然语言处理和java编程相当陌生。我有一个非常大的文本文件,包含ngrams和相关频率(aaprox.250MB)。我需要在程序运行时获得给定ngram的频率值。文件中提供的ngram频率如下(仅示例): 我尝试在启动时通过填充哈希集来读取该文件……但对于一个18mb的文件(使用System.currentTimeMillis()进行测试)需要将近1500毫秒。现在,我正在考虑对n-gram计数进行排序,并将250mb的文件分成小块,然后填充一个列表,并通过在单独的索引中索引文件集并引用它

我对自然语言处理和java编程相当陌生。我有一个非常大的文本文件,包含ngrams和相关频率(aaprox.250MB)。我需要在程序运行时获得给定ngram的频率值。文件中提供的ngram频率如下(仅示例):

我尝试在启动时通过填充哈希集来读取该文件……但对于一个18mb的文件(使用System.currentTimeMillis()进行测试)需要将近1500毫秒。现在,我正在考虑对n-gram计数进行排序,并将250mb的文件分成小块,然后填充一个列表,并通过在单独的索引中索引文件集并引用它来按需获取频率


但是,我不确定是否有其他更简单或更有效的方法来做到这一点。如果有更好的方法,请告诉我。(最好不要使用任何脚本或库…)。谢谢大家。

我同意@mbatchkarov的观点,加载时间通常不是最重要的优化目标。但运行时通常与内存占用密切相关(内存访问速度较慢,因此缓存中可以容纳的工作集越多越好)

您最初将每个bigram映射到整数计数的方法(可能是在java.util.HashMap中)是合理的,但非常占用内存。您的count文件包含数百万个bigram,每个bigram必须表示为单独的字符串。这些字符串消耗(至少)大约40字节的内存,每个计数需要一个整数对象——在大多数JVM实现中大约20字节。我粗略的猜测是数据结构超过了千兆字节

但您可以做得更好,因为您可以观察到,虽然二元内存只在文件(和数据结构)中出现一次,但大多数单个单词都会重复很多次,而且您可以不重复存储它们而逃之夭夭

我将从单词到整数索引的映射开始——例如,从您的示例中,单词=0、quick=1、brown=2等等。我不知道你的词库有多大,但是一个常见英语单词的典型映射可能有几十个或几十万个单词。因此字符串存储空间必须更小

要存储计数,可以将这些整数单词索引组合成一个复合键,并将该键用于地图。一种简单的“组合”方法是简单地对第一个单词的索引和第二个索引中的或进行位移位

在伪代码中:

HashMap<String, Integer> lexicon = new HashMap<String, Integer>();

// Iterate through the file, mapping each word to 
for (String file line) {
  ... Parse out word1 and word2
  if (!lexicon.containsKey(word1)) {
      lexicon.put(word1, lexicon.size());
  }
  if (!lexicon.containsKey(word2)) {
      lexicon.put(word2, lexicon.size());
  }
}
HashMap lexicon=newhashmap();
//迭代文件,将每个单词映射到
用于(字符串文件行){
…解析出word1和word2
if(!lexicon.containsKey(word1)){
lexicon.put(word1,lexicon.size());
}
if(!lexicon.containsKey(word2)){
lexicon.put(word2,lexicon.size());
}
}
现在,再次遍历该文件,将计数添加到单独的计数映射中

HashMap<Long, Integer> countMap = new HashMap<Long, Integer>();

for (String file line) {
    ... Parse out word1, word2, and count
    int i1 = lexicon.get(word1);
    int i2 = lexicon.get(word2);
    long key = (i1 << 32) | i2;
    countMap.put(key, count);
}
HashMap countMap=newhashmap();
用于(字符串文件行){
…解析出word1、word2和count
int i1=lexicon.get(word1);
int i2=lexicon.get(word2);

long key=(i1我同意@mbatchkarov的观点,即加载时间通常不是最重要的优化目标。但运行时通常与内存占用密切相关(内存访问速度较慢,因此缓存中可以容纳的工作集越多越好)

您最初将每个bigram映射到整数计数的方法(可能是在java.util.HashMap中)是合理的,但非常占用内存。您的计数文件包含数百万个bigram,每个bigram必须表示为一个单独的字符串。这些字符串消耗(至少)大约40字节的内存,每个计数需要一个整数对象——在大多数JVM实现中大约是20字节

但您可以做得更好,因为您可以观察到,虽然二元内存只在文件(和数据结构)中出现一次,但大多数单个单词都会重复很多次,而且您可以不重复存储它们而逃之夭夭

我将从单词到整数索引的映射开始——例如,从您的示例中,the=0、quick=1、brown=2等等。我不知道您的词典的大小,但常见英语单词的典型映射可能有几十个或几十万个单词。因此字符串存储必须更小

要存储计数,您可以将这些整数单词索引组合成一个复合键,并将该键用于地图。一种简单的“组合”方法是简单地对第一个单词的索引和/或第二个索引进行位移位

在伪代码中:

HashMap<String, Integer> lexicon = new HashMap<String, Integer>();

// Iterate through the file, mapping each word to 
for (String file line) {
  ... Parse out word1 and word2
  if (!lexicon.containsKey(word1)) {
      lexicon.put(word1, lexicon.size());
  }
  if (!lexicon.containsKey(word2)) {
      lexicon.put(word2, lexicon.size());
  }
}
HashMap lexicon=newhashmap();
//迭代文件,将每个单词映射到
用于(字符串文件行){
…解析出word1和word2
if(!lexicon.containsKey(word1)){
lexicon.put(word1,lexicon.size());
}
if(!lexicon.containsKey(word2)){
lexicon.put(word2,lexicon.size());
}
}
现在,再次遍历该文件,将计数添加到单独的计数映射中

HashMap<Long, Integer> countMap = new HashMap<Long, Integer>();

for (String file line) {
    ... Parse out word1, word2, and count
    int i1 = lexicon.get(word1);
    int i2 = lexicon.get(word2);
    long key = (i1 << 32) | i2;
    countMap.put(key, count);
}
HashMap countMap=newhashmap();
用于(字符串文件行){
…解析出word1、word2和count
int i1=lexicon.get(word1);
int i2=lexicon.get(word2);

长键=(i1看看这是一个处理ngram的特殊库。

看看这是一个处理ngram的特殊库。

如果读取18mb需要1.5s,那么读取整个250mb大约需要20s。这真的是你程序的瓶颈吗?根据我的经验,你在读入n-gram后处理它们的速度很慢部分。我写的一些代码运行了几天,所以20秒没有任何区别。我真的需要减少程序启动时间,并提高内存效率。如果读取18 MB需要1.5秒,读取完整的250 MB大约需要20秒。这真的是你程序的瓶颈吗?根据我的经验,你在使用n-gram后会做什么我在书中读到的是慢的部分。我写的一些代码运行了几天,所以20秒没有任何区别。我真的需要