试了下速度最快的KenLM

KenLM〕〔kenlm的训练及使用〕〔图解N-gram语言模型的原理

先下载编译KenLM

wget -O - https://kheafield.com/code/kenlm.tar.gz | tar xz
# 7z最大压缩后 315K

libboost-program-options-dev libboost-system-dev libboost-thread-dev libboost-test-dev
# 需要下载14.2MB。解压后占据185MB。
# 作者推荐libboost-all-dev,我没装也编译出来了。 mkdir kenlm/build cd kenlm/build cmake .. make -j2 # make -j 2路编译,make只是慢点而已

再写个分字程序:

enum { YI = 0x4E00 }; // 第1个汉字的编码是U+4E00,“一”‌

void do_file (FILE* f) {
  static char buf[4 * 1024 * 1024];
  setvbuf(f, buf, _IOFBF, sizeof(buf));
  for (wint_t b = 0, c;;) {
    int n = 0;
    while ((c = getwc(f)) != WEOF && c != '\n' && c < YI) ++n;
    if (c == WEOF) return;
    else if (c == '\n') putwc(c, stdout);
    else {
      if (n || b >= YI) putwc(' ', stdout);
      putwc(c, stdout);
    }
    b = c;
  }
}

int main (int argc, char** argv) {
  static char buf[4 * 1024 * 1024];
  setvbuf(stdout, buf, _IOFBF, sizeof(buf));
  setlocale(LC_CTYPE, "");
  for (int i = 1; i < argc; i++) {
    if (FILE* f = fopen(argv[i], "r")) do_file(f), fclose(f);
  }
}

#include <stdio.h>
#include <locale.h>
#include <wchar.h>
View Code

然后分字,seg *.txt | lmplz -o 2 >slm.arpa

lmplz默认从stdin读,写到stdout. 

  • --arpa file Write ARPA to a file instead of stdout
  • --text file  Read text from a file instead of stdin

接着‌build_binary‌ slm.arpa slm.bin. 它默认使用mmap: map a file into memory.

不带输出文件名的build_binary‌给出将要使用多大空间的信息。


然后准备q.txt for query,它和seg的输出都是用一个空格把汉字分开,如:

  三 亚 市
  四 亚 县

$ query slm.bin <q.txt
This binary file contains probing hash tables.
三=94 2 -2.183814	亚=222 2 -2.062365	市=61 2 -1.069804	</s>=2 2 -0.59742165 Total: -5.9134045 OOV: 0
四=116 2 -2.7783926	亚=222 1 -4.9270372	县=13 1 -1.9410045	</s>=2 2 -1.3998605	Total: -11.046295 OOV: 0
</s>=2 1 -2.6049278	Total: -2.6049278 OOV: 0
Perplexity including OOVs:	149.2270280712436
Perplexity excluding OOVs:	149.2270280712436
OOVs:	0
Tokens:	9
Name:query	VmPeak:7196 kB	VmRSS:4332 kB	RSSMax:5188 kB
user:0.004243 sys:0 CPU:0.00431801 real:0.000905842

里面的概率是以10为底的对数,所以算概率时用+而不是*。<s>是句首,</s>是句尾。

  -2.183814 <s> 三
  -2.062365 三 亚
  -1.069804 亚 市
  -0.59742165 市 </s>

把它们加起来得到-5.9134045。再看个例子:

  -2.7783926 <s> 四 # 有bi-gram
  -4.9270372 四 亚 # 无
  -1.9410045 亚 县 # 无
  -1.3998605 县 </s> # 有

没有的概率如何计算?

  -2.6125543 四 -0.95765805
  -3.969379-0.2033539
  -1.7376506 县 -1.2394043

-3.969379 + -0.95765805 = -4.92703705

-1.7376506 + -0.2033539  = -1.9410045

-0.95765805和-0.2033539叫做backoff penality. 不能因为“四”和“亚”都出现了很多次,就认为“四亚”出现很多次(把它俩的概率乘起来)——它俩不一定挨着。

image

困惑度


image

Ken是NB的;我是对的:Hash确实比Trie快。

  • L: Linear; P: Polynomial‌, 用多项式函数计算探测序列。不妨想像用探针乱戳
  • trie不考虑字符集大小是耍流氓
  • 也无非hash或sorted array;鄙人处理拼音时搞了个16叉的, half-byte :-)

统计好后进入大小固定、只读阶段,二分就不错。能用Perfect Hash么?数据库呢?

We built an unpruned model on 126 billion tokens. Estimation used a machine with 140 GB RAM and six hard drives in a RAID5 configuration (sustained read: 405 MB/s). It took 123 GB RAM, 2.8 days wall time, and 5.4 CPU days -- Scalable Modified Kneser-Ney Language Model Estimation, Kenneth Heafield et al.

  • 2.8的两倍可是5.6,勉强双核?还是说大部分时间I/O?

所谓ARPA格式,恐怕没有标准except \n-grams:。那么多字符,它非用\,我们就得"\\"。

posted @ 2025-11-09 20:33  华容道专家  阅读(3)  评论(0)    收藏  举报