Stanford Topic Modeling Toolbox0.4.0翻译

本文内容翻译自 http://nlp.stanford.edu/software/tmt/tmt-0.4/ 目前斯坦福大学的TMT(Topic Modeling Toolbox)已经更新至0.4.0版本。笔者最近在研究这个小工具,特此做一些笔记,在使用中遇到一些问题也进行说明。
这个小工具是scala写的,但是本机不需要安装scala,需要安装jre1.5以上版本,笔者使用的是1.7.79版本。
按照官网的说明,运行一个最简单的例子:
首先下载tmt-0.4.0.jar,example-0-test.scalapubmed-oa-subset.csv,这三个文件要放在同一个文件夹里。第一个文件是一个程序的jar包,也就是程序的主体;第二个文件是程序所运行的脚本,定义了处理的数据和显示结果等;第三个文件就是进行操作的数据了。 以下就是主界面,点击File——>open script...打开脚本,比如之前下载的example-0-test.scala

Stanford Topic Modeling Toolbox0.4.0翻译_第1张图片

这些按钮中,Edit script可以修改脚本,比如修改处理的数据集。修改完成之后,按run就可以了。



需要注意的是,在官网下载的示例数据集pubmed-oa-subset.csv中,存在类似中文的乱码,这些乱码会导致报错,所以要把这些乱码删除。至于网上说的要把字符集修改成utf-8,笔者没有遇到这样的问题。


准备数据集

本例中使用的代码是example-1-dataset.scala文件

从一个CSV文件提取并且准备文本的过程可以被看做一个流水线:一个CSV文件经过一系列过程最终成为可以用来训练模型的结果。这里就是pubmed-oa-subset.csv数据文件的案例:

01. val source = CSVFile("pubmed-oa-subset.csv") ~> IDColumn(1);
02.  
03. val tokenizer = {
04. SimpleEnglishTokenizer() ~>            // tokenize on space and punctuation
05. CaseFolder() ~>                        // lowercase everything
06. WordsAndNumbersOnlyFilter() ~>         // ignore non-words and non-numbers
07. MinimumLengthFilter(3)                 // take terms with >=3 characters
08. }
09.  
10. val text = {
11. source ~>                              // read from the source file
12. Column(4) ~>                           // select column containing text
13. TokenizeWith(tokenizer) ~>             // tokenize with tokenizer above
14. TermCounter() ~>                       // collect counts (needed below)
15. TermMinimumDocumentCountFilter(4) ~>   // filter terms in <4 docs
16. TermDynamicStopListFilter(30) ~>       // filter out 30 most common terms
17. DocumentMinimumLengthFilter(5)         // take only docs with >=5 terms
18. }

输入的数据文件(在代码变量中)是一个指向你先前下载的CSV文件的指针,随后我们将经过一系列的变形、过滤或者其他与数据交互的操作。第一行代码中,制定了TMT使用第一列(column 1)的值作为记录ID,这对文件中每一条记录都是独一无二的标志。如果你的sheet中的记录ID不在第一列,就把上文代码第一行中的1改成你自己的列数。如果你的sheet没有记录ID这一列,你可以删掉“~> IDColumn(1)”,TMT会用文件的行号作为记录ID。
如果你的CSV文件第一行包含了列名,你可以删除第一行代码,改用Drop步骤:

1. val source = CSVFile("your-csv-file.csv") ~> IDColumn(yourIdColumn) ~> Drop(1);

分词Tokenizing

第一步是定义分词器(tokenizer),以将数据集中包含文本的单元转化成话题模型分析的term。从第三行到第七行定义的分词器,制定了一系列的将一个字符串转化成一系列字符串的变形操作。
笔者注:每两个步骤之间需要有~>符号,最后一个不需要
首先,我们用SimpleEnglishTokenizer()去除单词结尾的标点符号,然后用空白符(tab、空格、回车等)将输入文本分解。如果你的文件已经进行过清洗了,你也可以用 WhitespaceTokenizer()。或者,你可以用RegexSplitTokenizer("your-regex-pattern"),通过正则表达式定制你自己的分词器。
CaseFolder随后被用来将每个单词变成小写,这样“The”、“tHE”、“THE”都变成了“the”。CaseFolder通过把所有字符变成小写形式,减少了单词的不同形式。
下面,使用WordsAndNumbersOnlyFilter(),纯标点、非单词非数字的字符会从产生的分词后的文档列表中删除。
最后,使用MinimumLengthFilter()将短于3个字符的term去除
作为可选功能,token可以用 PorterStemmer()在MinimumLengthFilter()之前提取词干。提取词干在信息检索中是一种常用的技术,将比如多元词转化成简单的常用term(“books”和“book”都映射成“book”)。但是,提取词干并不总对话题建模有益,因为有时提取词干会把一些term合并在一起,但是他们最好还是分开,而且同一个单词的变形会变成同一个话题。
如果你想要去除标准的英语停用词(stop word),可以在分词器的最后一步用StopWordFilter("en")(笔者注:如果使用了这一步,那么这些很常用的停用词都会被过滤掉,在下面的步骤中,被过滤掉的前30个常用单词就很可能是有用单词了)


在一个CSV文件中提取并且分词Extracting and tokenizing text in a CSV file

定义好分词器之后,我们就可以用它从CSV文件中合适的列中提取文本了。如果你的文本数据存在于一列中(这里是第四列):

1.source ~> Column(4) ~> TokenizeWith(tokenizer)以上的代码会加载CSV文件中的第四列文本
如果你的文本不止存在于一列中:

1.source ~> Columns(3,4) ~> Join(" ") ~> TokenizeWith(tokenizer)以上的代码会选择第三和第四列,然后把他们的内容用一个空格连在一起。


寻找有意义的单词(Finding meaningful words)

话题建模对于有意义单词的模式提取(extracting patterns)非常有用,但是在决定什么单词是有意义时并不一定奏效。通常,使用常见的单词比如“the”,并不代表着文档之间的相似性。为了在有意义的单词中提取模式,我们使用一系列的标准启发式算法:

1. ... ~>
2. TermCounter ~>
3. TermMinimumDocumentCountFilter(4) ~>
4. TermDynamicStopListFilter(30) ~>
5. ...
上面的代码去除了在少于四篇文档中出现的term(因为很少见的单词几乎不对文档相似度做出贡献),还有在文本库中最常见的30个单词(因为太普遍的单词同样对文档相似度不做出贡献,他们通常被定义为停用词)。当你在处理很大或者很小(少于几千单词的文档)时,这些值可能需要更新。
如果你有一个你想要出去的停用词的详细列表,你可以像这样额外增加一个过程:
TermStopListFilter(List("positively","scrumptious")). 这里,在引号的List里添加你需要过滤的单词。记住,TermStopListFilter 运行在文档被分词之后,所以你提供的List要和你的分词器输出保持一致,就是说,如果你的分词器包括了CaseFolder和PorterStemmer,过滤的单词必须也要是小写的和词干。
TermCounter步骤首先必须计算下一步骤需要的一些统计。这些数据存储在元数据中,使得任何下游步骤可以使用这些数据。这些步骤也会在硬盘上CSV文件的同一个文件夹下产生缓存文件,以保存文档数据。文件名会以CSV文件的名称开头,并且会包含流水线的标记"term-counts.cache"。

去除空文档(Removing Empty Documents)

数据集中的一些文档可能会丢失或者是空的(一些单词可能在最后一步被过滤掉)。可以通过使用DocumentMinimumLengthFilter(length) 在训练中舍弃一些文档,去除短于特定长度的文档。

组合起来(Putting it all together)

运行example1 (example-1-dataset.scala)。这个程序会首先加载数据流水线,然后打印加载数据集的信息,包括数据集的标志和文本库中的30个停用词。(注意在PubMed,因为“gene”被广泛使用所以被过滤掉了)

学习一个话题模型

这个例子展示了如何用你上面准备的数据集进行LDA训练。
这个例子的代码在example-2-Ida-learn。scala里。


载入数据文件(Load the data file

01. val source = CSVFile("pubmed-oa-subset.csv") ~> IDColumn(1);
02.  
03. val tokenizer = {
04. SimpleEnglishTokenizer() ~>            // tokenize on space and punctuation
05. CaseFolder() ~>                        // lowercase everything
06. WordsAndNumbersOnlyFilter() ~>         // ignore non-words and non-numbers
07. MinimumLengthFilter(3)                 // take terms with >=3 characters
08. }
09.  
10. val text = {
11. source ~>                              // read from the source file
12. Column(4) ~>                           // select column containing text
13. TokenizeWith(tokenizer) ~>             // tokenize with tokenizer above
14. TermCounter() ~>                       // collect counts (needed below)
15. TermMinimumDocumentCountFilter(4) ~>   // filter terms in <4 docs
16. TermDynamicStopListFilter(30) ~>       // filter out 30 most common terms
17. DocumentMinimumLengthFilter(5)         // take only docs with >=5 terms
18. }

这个代码片段和之前的一样,它从示例数据集中提取并且准备了文本。


选择训练LDA模型的参数(Select parameters for training an LDA model

1. // turn the text into a dataset ready to be used with LDA
2. val dataset = LDADataset(text);
3.  
4. // define the model parameters
5. val params = LDAModelParams(numTopics = 30, dataset = dataset);

这里你可以指定一定数量想要学习的topic。你也指定可以指定LDA模型使用的Dirichlet term和 topic smoothing参数,这些参数在第五行作为LDAModelParams额外的参数提供给构造函数。在默认情况下,第五行等价于已经设定了termSmoothing=SymmetricDirichletParams(.1) 和topicSmoothing=SymmetricDirichletParams(.1) 

训练符合文档的模型(Train the model to fit the documents)

从0.3版本起,本工具开始支持大多数模型上的多种形式的学习和推理,包括默认支持的多线程训练和多核机器上的推理。特别的,这个模型可以使用collapsed Gibbs sampler [T. L. Griffiths and M. Steyvers. 2004. Finding scientific topics. PNAS, 1:5228–35]或者collapsed variational Bayes approximation to the LDA objective [Asuncion, A., Welling, M., Smyth, P., & Teh, Y. W. (2009)). On Smoothing and Inference for Topic Models. UAI 2009]。


01. // Name of the output model folder to generate
02. val modelPath = file("lda-"+dataset.signature+"-"+params.signature);
03.  
04. // Trains the model: the model (and intermediate models) are written to the
05. // output folder.  If a partially trained model with the same dataset and
06. // parameters exists in that folder, training will be resumed.
07. TrainCVB0LDA(params, dataset, output=modelPath, maxIterations=1000);
08.  
09. // To use the Gibbs sampler for inference, instead use
10. // TrainGibbsLDA(params, dataset, output=modelPath, maxIterations=1500);


该模型会在训练时产生状态信息,并且会把产生的模型写入当前目录的一个文件夹,在这个例子里名称为"lda-59ea15c7-30-75faccf7"。注意,默认情况下,使用CVB0LDA进行训练会使用本地所有可用的内核,而且因为它的收敛速率很快,CVB0LDA比GibbsLDA迭代次数更少,然而GibbsLDA在训练时需要更少的内存。


产生输出文件夹之旅(A tour of the generated output folder)

在这个例子中,产生的模型输出文件夹 lda-59ea15c7-30-75faccf7,包含了分析这个学习过程和把模型从磁盘加载回去所需要的一切。

description.txt A description of the model saved in this folder.
document-topic-distributions.csv A csv file containing the per-document topic distribution for each document in the dataset.
[Snapshot]: 00000 - 01000 Snapshots of the model during training.
[Snapshot]/params.txt Model parameters used during training.
[Snapshot]/tokenizer.txt Tokenizer used to tokenize text for use with this model.
[Snapshot]/summary.txt Human readable summary of the topic model, with top-20 terms per topic and how many words instances of each have occurred.
[Snapshot]/log-probability-estimate.txt Estimate of the log probability of the dataset at this iteration.
[Snapshot]/term-index.txt Mapping from terms in the corpus to ID numbers (by line offset).
[Snapshot]/topic-term-distributions.csv.gz For each topic, the probability of each term in that topic.

决定这个模型是否已收敛(Determining if the model has converged)

一种简单的判断模型的训练是否已经收敛的办法,是看计数文件夹oflog-probability-estimate.txt.的值。这个文件包含了模型在训练时对数据概率估计的非正式估计。这些数字趋向于形成逐步向下但不会完全停止改变的曲线。如果这些数字看起来还没有稳定下来,你可能会会需要设定更高的迭代次数。


新文本库的话题模型交互(topic model inference on a new corpus)

在训练中,这个工具在产生的模型文件夹中的 document-topic-distributions.csv中记录了每个训练文档的话题分布。模型训练之后,它可以用来分析另外一个可能更大的文本,这个过程称作推理。这个教程展示了如何在一个新的数据集中用已经存在的话题模型中进行推理。
这个例子的代码在example-3-Ida-infer.scala中

载入训练好的LDA模型

1. // the path of the model to load
2. val modelPath = file("lda-59ea15c7-30-75faccf7");
3.  
4. println("Loading "+modelPath);
5. val model = LoadCVB0LDA(modelPath);
6. // Or, for a Gibbs model, use:
7. // val model = LoadGibbsLDA(modelPath);

这里我们再次载入上个例子中训练好的模型。

载入新的数据集进行推理(Load the new dataset for inference)

01. // A new dataset for inference.  (Here we use the same dataset
02. // that we trained against, but this file could be something new.)
03. val source = CSVFile("pubmed-oa-subset.csv") ~> IDColumn(1);
04.  
05. val text = {
06. source ~>                              // read from the source file
07. Column(4) ~>                           // select column containing text
08. TokenizeWith(model.tokenizer.get)      // tokenize with existing model's tokenizer
09. }
10.  
11. // Base name of output files to generate
12. val output = file(modelPath, source.meta[java.io.File].getName.replaceAll(".csv",""));
13.  
14. // turn the text into a dataset ready to be used with LDA
15. val dataset = LDADataset(text, termIndex = model.termIndex);


这里我们准备了一个新的数据集,用已载入模型的原始分词器进行了分词。注意:在这个特别的例子中,我们实际上使用的是之前训练的同样的文件。在实际使用中,推理的CSV文件将是磁盘中的其他文件。
我们还创建了输出路径的文件名,下面输出的文件将出现在模型文件夹里,这些文件名将以推理的数据集名字开头。

在潜在的话题上推测每个文档的分布(Infer per-document distributions over latent topic)

1. println("Writing document distributions to "+output+"-document-topic-distributions.csv");
2. val perDocTopicDistributions = InferCVB0DocumentTopicDistributions(model, dataset);
3. CSVFile(output+"-document-topic-distributuions.csv").write(perDocTopicDistributions);
4.  
5. println("Writing topic usage to "+output+"-usage.csv");
6. val usage = QueryTopicUsage(model, dataset, perDocTopicDistributions);
7. CSVFile(output+"-usage.csv").write(usage)

我们推测对推理数据集中的每个文档推测话题分布,这些分布会写入模型文件夹中的一个新CSV文件。我们也会写入一个文件,其中包含了在推测数据集中,每个话题被使用的频率。

在潜在话题上推测每个单词的分布(Infer per-word distributions over latent topics)

. println("Estimating per-doc per-word topic distributions");
2. val perDocWordTopicDistributions = EstimatePerWordTopicDistributions(
3. model, dataset, perDocTopicDistributions);
4.  
5. println("Writing top terms to "+output+"-top-terms.csv");
6. val topTerms = QueryTopTerms(model, dataset, perDocWordTopicDistributions, numTopTerms=50);
7. CSVFile(output+"-top-terms.csv").write(topTerms);

因为和已经产生训练模型的数据集不同,我们希望以不同于话题在训练中被使用的方式,推理数据集可以充分利用已经学习好的话题。这个工具可以产生数据集中每个话题的top-k个term。这里我们把这top-k个term放入-top-terms.csv。这个文件会和summary.txt文件中的输出结果或者在训练的数据集上的推理进行比较。

你可能感兴趣的:(topic,LDA,Modeling,TMT,toolb)