From 2ad6d281c77643d85abb4447e17c1560adb88033 Mon Sep 17 00:00:00 2001 From: Forough Poursabzi Sangdeh Date: Thu, 21 Jul 2016 22:10:36 -0400 Subject: [PATCH] added tree TM --- Makefile | 2 +- lib/.DS_Store | Bin 0 -> 6148 bytes lib/wordnet.jar | Bin 0 -> 360493 bytes src/cc/mallet/topics/tree/CorpusWriter.java | 157 +++ .../topics/tree/HIntIntDoubleHashMap.java | 82 ++ .../mallet/topics/tree/HIntIntIntHashMap.java | 121 +++ .../topics/tree/HIntIntObjectHashMap.java | 79 ++ src/cc/mallet/topics/tree/Node.java | 194 ++++ src/cc/mallet/topics/tree/NonZeroPath.java | 31 + src/cc/mallet/topics/tree/OntologyWriter.java | 905 ++++++++++++++++++ src/cc/mallet/topics/tree/Path.java | 53 + src/cc/mallet/topics/tree/PriorTree.java | 363 +++++++ src/cc/mallet/topics/tree/TopicSampler.java | 278 ++++++ src/cc/mallet/topics/tree/TopicTreeWalk.java | 85 ++ .../tree/TreeMarginalProbEstimator.java | 380 ++++++++ .../topics/tree/TreeTopicInferencer.java | 384 ++++++++ src/cc/mallet/topics/tree/TreeTopicModel.java | 382 ++++++++ .../topics/tree/TreeTopicModelFast.java | 388 ++++++++ .../topics/tree/TreeTopicModelFastEst.java | 44 + .../tree/TreeTopicModelFastEstSortW.java | 44 + .../topics/tree/TreeTopicModelFastSortW.java | 297 ++++++ .../topics/tree/TreeTopicModelNaive.java | 76 ++ .../mallet/topics/tree/TreeTopicSampler.java | 300 ++++++ .../topics/tree/TreeTopicSamplerFast.java | 286 ++++++ .../topics/tree/TreeTopicSamplerFastEst.java | 157 +++ .../tree/TreeTopicSamplerFastEstSortD.java | 150 +++ .../tree/TreeTopicSamplerFastSortD.java | 138 +++ .../topics/tree/TreeTopicSamplerHashD.java | 648 +++++++++++++ .../tree/TreeTopicSamplerInterface.java | 80 ++ .../topics/tree/TreeTopicSamplerNaive.java | 98 ++ .../topics/tree/TreeTopicSamplerSortD.java | 684 +++++++++++++ src/cc/mallet/topics/tree/Utils.java | 82 ++ src/cc/mallet/topics/tree/VocabGenerator.java | 238 +++++ src/cc/mallet/topics/tree/testFast.java | 249 +++++ src/cc/mallet/topics/tree/testNaive.java | 157 +++ .../mallet/topics/tui/EvaluateTreeTopics.java | 96 ++ src/cc/mallet/topics/tui/GenerateTree.java | 42 + src/cc/mallet/topics/tui/GenerateVocab.java | 58 ++ src/cc/mallet/topics/tui/InferTreeTopics.java | 86 ++ .../mallet/topics/tui/Vectors2TreeTopics.java | 260 +++++ 40 files changed, 8153 insertions(+), 1 deletion(-) create mode 100644 lib/.DS_Store create mode 100755 lib/wordnet.jar create mode 100644 src/cc/mallet/topics/tree/CorpusWriter.java create mode 100755 src/cc/mallet/topics/tree/HIntIntDoubleHashMap.java create mode 100755 src/cc/mallet/topics/tree/HIntIntIntHashMap.java create mode 100755 src/cc/mallet/topics/tree/HIntIntObjectHashMap.java create mode 100755 src/cc/mallet/topics/tree/Node.java create mode 100755 src/cc/mallet/topics/tree/NonZeroPath.java create mode 100755 src/cc/mallet/topics/tree/OntologyWriter.java create mode 100755 src/cc/mallet/topics/tree/Path.java create mode 100755 src/cc/mallet/topics/tree/PriorTree.java create mode 100755 src/cc/mallet/topics/tree/TopicSampler.java create mode 100755 src/cc/mallet/topics/tree/TopicTreeWalk.java create mode 100644 src/cc/mallet/topics/tree/TreeMarginalProbEstimator.java create mode 100755 src/cc/mallet/topics/tree/TreeTopicInferencer.java create mode 100755 src/cc/mallet/topics/tree/TreeTopicModel.java create mode 100755 src/cc/mallet/topics/tree/TreeTopicModelFast.java create mode 100755 src/cc/mallet/topics/tree/TreeTopicModelFastEst.java create mode 100755 src/cc/mallet/topics/tree/TreeTopicModelFastEstSortW.java create mode 100755 src/cc/mallet/topics/tree/TreeTopicModelFastSortW.java create mode 100755 src/cc/mallet/topics/tree/TreeTopicModelNaive.java create mode 100755 src/cc/mallet/topics/tree/TreeTopicSampler.java create mode 100755 src/cc/mallet/topics/tree/TreeTopicSamplerFast.java create mode 100755 src/cc/mallet/topics/tree/TreeTopicSamplerFastEst.java create mode 100755 src/cc/mallet/topics/tree/TreeTopicSamplerFastEstSortD.java create mode 100755 src/cc/mallet/topics/tree/TreeTopicSamplerFastSortD.java create mode 100755 src/cc/mallet/topics/tree/TreeTopicSamplerHashD.java create mode 100755 src/cc/mallet/topics/tree/TreeTopicSamplerInterface.java create mode 100755 src/cc/mallet/topics/tree/TreeTopicSamplerNaive.java create mode 100755 src/cc/mallet/topics/tree/TreeTopicSamplerSortD.java create mode 100755 src/cc/mallet/topics/tree/Utils.java create mode 100755 src/cc/mallet/topics/tree/VocabGenerator.java create mode 100755 src/cc/mallet/topics/tree/testFast.java create mode 100755 src/cc/mallet/topics/tree/testNaive.java create mode 100644 src/cc/mallet/topics/tui/EvaluateTreeTopics.java create mode 100644 src/cc/mallet/topics/tui/GenerateTree.java create mode 100644 src/cc/mallet/topics/tui/GenerateVocab.java create mode 100755 src/cc/mallet/topics/tui/InferTreeTopics.java create mode 100755 src/cc/mallet/topics/tui/Vectors2TreeTopics.java diff --git a/Makefile b/Makefile index 53e5a13d1..d76cfc462 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ MALLET_DIR = $(shell pwd) JAVAC = javac JAVA_FLAGS = \ --classpath "$(MALLET_DIR)/class:$(MALLET_DIR)/lib/mallet-deps.jar:$(MALLET_DIR)/lib/jdom-1.0.jar:$(MALLET_DIR)/lib/grmm-deps.jar:$(MALLET_DIR)/lib/weka.jar " \ +-classpath "$(MALLET_DIR)/lib/wordnet.jar:$(MALLET_DIR)/class:$(MALLET_DIR)/lib/mallet-deps.jar:$(MALLET_DIR)/lib/jdom-1.0.jar:$(MALLET_DIR)/lib/grmm-deps.jar:$(MALLET_DIR)/lib/weka.jar " \ -sourcepath "$(MALLET_DIR)/src" \ -g:lines,vars,source \ -d $(MALLET_DIR)/class \ diff --git a/lib/.DS_Store b/lib/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 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

rCzy2Y;gtUF8>H8yvK_&%hZBTi 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 vocab = loadVocab(vocabname); + + PrintStream out = new PrintStream (new File(outfilename)); + + int count = -1; + for (Instance instance : training) { + count++; + if (count % 1000 == 0) { + System.out.println("Processed " + count + " number of documents!"); + } + FeatureSequence original_tokens = (FeatureSequence) instance.getData(); + String name = instance.getName().toString(); + + TIntArrayList tokens = new TIntArrayList(original_tokens.getLength()); + TIntIntHashMap topicCounts = new TIntIntHashMap (); + TIntArrayList topics = new TIntArrayList(original_tokens.getLength()); + TIntArrayList paths = new TIntArrayList(original_tokens.getLength()); + + String doc = ""; + for (int jj = 0; jj < original_tokens.getLength(); jj++) { + String word = (String) original_tokens.getObjectAtPosition(jj); + int token = vocab.indexOf(word); + doc += word + " "; + //if(token != -1) { + // doc += word + " "; + //} + } + System.out.println(name); + System.out.println(doc); + + if (!doc.equals("")) { + out.println(doc); + } + } + + out.close(); + } + + public static void writeCorpusMatrix(InstanceList training, String outfilename, String vocabname) throws FileNotFoundException { + + // each document is represented in a vector (vocab size), and each entry is the frequency of a word. + + ArrayList vocab = loadVocab(vocabname); + + PrintStream out = new PrintStream (new File(outfilename)); + + int count = -1; + for (Instance instance : training) { + count++; + if (count % 1000 == 0) { + System.out.println("Processed " + count + " number of documents!"); + } + FeatureSequence original_tokens = (FeatureSequence) instance.getData(); + String name = instance.getName().toString(); + + int[] tokens = new int[vocab.size()]; + for (int jj = 0; jj < tokens.length; jj++) { + tokens[jj] = 0; + } + + for (int jj = 0; jj < original_tokens.getLength(); jj++) { + String word = (String) original_tokens.getObjectAtPosition(jj); + int index = vocab.indexOf(word); + tokens[index] += 1; + } + + String doc = ""; + for (int jj = 0; jj < tokens.length; jj++) { + doc += tokens[jj] + "\t"; + } + + System.out.println(name); + System.out.println(doc); + + if (!doc.equals("")) { + out.println(doc); + } + } + + out.close(); + } + + public static ArrayList loadVocab(String vocabFile) { + + ArrayList vocab = new ArrayList(); + + try { + FileInputStream infstream = new FileInputStream(vocabFile); + DataInputStream in = new DataInputStream(infstream); + BufferedReader br = new BufferedReader(new InputStreamReader(in)); + + String strLine; + //Read File Line By Line + while ((strLine = br.readLine()) != null) { + strLine = strLine.trim(); + String[] str = strLine.split("\t"); + if (str.length > 1) { + vocab.add(str[1]); + } else { + System.out.println("Error! " + strLine); + } + } + in.close(); + + } catch (IOException e) { + System.out.println("No vocab file Found!"); + } + return vocab; + } + + + public static void main(String[] args) { + //String input = "input/nyt/nyt-topic-input.mallet"; + //String corpus = "../../pylda/variational/data/20_news/doc.dat"; + //String vocab = "../../pylda/variational/data/20_news/voc.dat"; + + String input = "input/synthetic/synthetic-topic-input.mallet"; + //String corpus = "../../spectral/input/synthetic-ordered.dat"; + //String vocab = "../../spectral/input/synthetic-ordered.voc"; + String corpus = "../../spectral/input/synthetic.dat"; + String vocab = "../../spectral/input/synthetic.voc"; + + //String input = "../../itm-evaluation/results/govtrack-109/input/govtrack-109-topic-input.mallet"; + //String corpus = "../../pylda/variational/data/20_news/doc.dat"; + //String vocab = "../../itm-evaluation/results/govtrack-109/input/govtrack-109.voc"; + + try{ + InstanceList data = InstanceList.load (new File(input)); + writeCorpusMatrix(data, corpus, vocab); + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/src/cc/mallet/topics/tree/HIntIntDoubleHashMap.java b/src/cc/mallet/topics/tree/HIntIntDoubleHashMap.java new file mode 100755 index 000000000..4b4bf47bf --- /dev/null +++ b/src/cc/mallet/topics/tree/HIntIntDoubleHashMap.java @@ -0,0 +1,82 @@ +package cc.mallet.topics.tree; + +import java.io.Serializable; + +import gnu.trove.TIntDoubleHashMap; +import gnu.trove.TIntIntHashMap; +import gnu.trove.TIntObjectHashMap; + +/** + * This class defines a two level hashmap, so a value will be indexed by two keys. + * The value is double, and two keys are both int. + * + * @author Yuening Hu + */ + +public class HIntIntDoubleHashMap implements Serializable{ + TIntObjectHashMap data; + + public HIntIntDoubleHashMap() { + this.data = new TIntObjectHashMap (); + } + + /** + * If keys do not exist, insert value. + * Else update with the new value. + */ + public void put(int key1, int key2, double value) { + if(! this.data.contains(key1)) { + this.data.put(key1, new TIntDoubleHashMap()); + } + TIntDoubleHashMap tmp = this.data.get(key1); + tmp.put(key2, value); + } + + /** + * Return the HashMap indexed by the first key. + */ + public TIntDoubleHashMap get(int key1) { + return this.data.get(key1); + } + + /** + * Return the value indexed by key1 and key2. + */ + public double get(int key1, int key2) { + if (this.data.contains(key1)) { + TIntDoubleHashMap tmp1 = this.data.get(key1); + if (tmp1.contains(key2)) { + return tmp1.get(key2); + } + } + System.out.println("HIntIntDoubleHashMap: key does not exist!"); + return -1; + } + + /** + * Return the first key set. + */ + public int[] getKey1Set() { + return this.data.keys(); + } + + /** + * Check whether key1 is contained in the first key set or not. + */ + public boolean contains(int key1) { + return this.data.contains(key1); + } + + /** + * Check whether the key pair (key1, key2) is contained or not. + */ + public boolean contains(int key1, int key2) { + if (this.data.contains(key1)) { + return this.data.get(key1).contains(key2); + } else { + return false; + } + } + +} + diff --git a/src/cc/mallet/topics/tree/HIntIntIntHashMap.java b/src/cc/mallet/topics/tree/HIntIntIntHashMap.java new file mode 100755 index 000000000..42ecded4c --- /dev/null +++ b/src/cc/mallet/topics/tree/HIntIntIntHashMap.java @@ -0,0 +1,121 @@ +package cc.mallet.topics.tree; + +import java.io.Serializable; + +import gnu.trove.TIntDoubleHashMap; +import gnu.trove.TIntIntHashMap; +import gnu.trove.TIntObjectHashMap; + +/** + * This class defines a two level hashmap, so a value will be indexed by two keys. + * The value is int, and two keys are both int. + * + * @author Yuening Hu + */ + +public class HIntIntIntHashMap implements Serializable{ + + TIntObjectHashMap data; + + public HIntIntIntHashMap() { + this.data = new TIntObjectHashMap (); + } + + /** + * If keys do not exist, insert value. + * Else update with the new value. + */ + public void put(int key1, int key2, int value) { + if(! this.data.contains(key1)) { + this.data.put(key1, new TIntIntHashMap()); + } + TIntIntHashMap tmp = this.data.get(key1); + tmp.put(key2, value); + } + + /** + * Return the HashMap indexed by the first key. + */ + public TIntIntHashMap get(int key1) { + if(this.contains(key1)) { + return this.data.get(key1); + } + return null; + } + + /** + * Return the value indexed by key1 and key2. + */ + public int get(int key1, int key2) { + if (this.contains(key1, key2)) { + return this.data.get(key1).get(key2); + } else { + System.out.println("HIntIntIntHashMap: key does not exist!"); + return 0; + } + } + + /** + * Return the first key set. + */ + public int[] getKey1Set() { + return this.data.keys(); + } + + /** + * Check whether key1 is contained in the first key set or not. + */ + public boolean contains(int key1) { + return this.data.contains(key1); + } + + /** + * Check whether the key pair (key1, key2) is contained or not. + */ + public boolean contains(int key1, int key2) { + if (this.data.contains(key1)) { + return this.data.get(key1).contains(key2); + } else { + return false; + } + } + + /** + * Adjust the value indexed by the key pair (key1, key2) by the specified amount. + */ + public void adjustValue(int key1, int key2, int increment) { + int old = this.get(key1, key2); + this.put(key1, key2, old+increment); + } + + + /** + * If the key pair (key1, key2) exists, adjust the value by the specified amount, + * Or insert the new value. + */ + public void adjustOrPutValue(int key1, int key2, int increment, int newvalue) { + if (this.contains(key1, key2)) { + int old = this.get(key1, key2); + this.put(key1, key2, old+increment); + } else { + this.put(key1, key2, newvalue); + } + } + + /** + * Remove the first key + */ + public void removeKey1(int key1) { + this.data.remove(key1); + } + + /** + * Remove the second key + */ + public void removeKey2(int key1, int key2) { + if (this.data.contains(key1)) { + this.data.get(key1).remove(key2); + } + } + +} diff --git a/src/cc/mallet/topics/tree/HIntIntObjectHashMap.java b/src/cc/mallet/topics/tree/HIntIntObjectHashMap.java new file mode 100755 index 000000000..57c3ce80e --- /dev/null +++ b/src/cc/mallet/topics/tree/HIntIntObjectHashMap.java @@ -0,0 +1,79 @@ +package cc.mallet.topics.tree; + +import java.io.Serializable; + +import gnu.trove.TIntArrayList; +import gnu.trove.TIntIntHashMap; +import gnu.trove.TIntObjectHashMap; + +/** + * This class defines a two level hashmap, so a value will be indexed by two keys. + * The value is int, and two keys are both int. + * + * @author Yuening Hu + */ + +public class HIntIntObjectHashMap implements Serializable{ + TIntObjectHashMap> data; + + public HIntIntObjectHashMap () { + this.data = new TIntObjectHashMap>(); + } + + /** + * If keys do not exist, insert value. + * Else update with the new value. + */ + public void put(int key1, int key2, V value) { + if(! this.data.contains(key1)) { + this.data.put(key1, new TIntObjectHashMap()); + } + TIntObjectHashMap tmp = this.data.get(key1); + tmp.put(key2, value); + } + + /** + * Return the HashMap indexed by the first key. + */ + public TIntObjectHashMap get(int key1) { + return this.data.get(key1); + } + + /** + * Return the value indexed by key1 and key2. + */ + public V get(int key1, int key2) { + if (this.contains(key1, key2)) { + return this.data.get(key1).get(key2); + } else { + System.out.println("HIntIntObjectHashMap: key does not exist! " + key1 + " " + key2); + return null; + } + } + + /** + * Return the first key set. + */ + public int[] getKey1Set() { + return this.data.keys(); + } + + /** + * Check whether key1 is contained in the first key set or not. + */ + public boolean contains(int key1) { + return this.data.contains(key1); + } + + /** + * Check whether the key pair (key1, key2) is contained or not. + */ + public boolean contains(int key1, int key2) { + if (this.data.contains(key1)) { + return this.data.get(key1).contains(key2); + } else { + return false; + } + } + +} diff --git a/src/cc/mallet/topics/tree/Node.java b/src/cc/mallet/topics/tree/Node.java new file mode 100755 index 000000000..9249f1fd8 --- /dev/null +++ b/src/cc/mallet/topics/tree/Node.java @@ -0,0 +1,194 @@ +package cc.mallet.topics.tree; + +import gnu.trove.TDoubleArrayList; +import gnu.trove.TIntArrayList; + +/** + * This class defines a node, which might have children, + * and a distribution scaled by the node prior over the children. + * A node is a synset, which might have children nodes and words + * at the same time. + * + * @author Yuening Hu + */ + +public class Node { + int offset; + double rawCount; + double hypoCount; + String hyperparamName; + + TIntArrayList words; + TDoubleArrayList wordsCount; + TIntArrayList childOffsets; + + int numChildren; + int numPaths; + int numWords; + + double transitionScalor; + TDoubleArrayList transitionPrior; + + public Node() { + this.words = new TIntArrayList (); + this.wordsCount = new TDoubleArrayList (); + this.childOffsets = new TIntArrayList (); + this.transitionPrior = new TDoubleArrayList (); + this.numChildren = 0; + this.numWords = 0; + this.numPaths = 0; + } + + /** + * Initialize the prior distribution. + */ + public void initializePrior(int size) { + for (int ii = 0; ii < size; ii++ ) { + this.transitionPrior.add(0.0); + } + } + + /** + * Initialize the prior distribution. + */ + public void setOffset(int val) { + this.offset = val; + } + + /** + * set the raw count. + */ + public void setRawCount(double count) { + this.rawCount = count; + } + + /** + * set the hypo count. + */ + public void setHypoCount(double count) { + this.hypoCount = count; + } + + /** + * set the hyperparameter name of this node. + */ + public void setHyperparamName(String name) { + this.hyperparamName = name; + } + + /** + * set the prior scaler. + */ + public void setTransitionScalor(double val) { + this.transitionScalor = val; + } + + /** + * set the prior for the given child index. + */ + public void setPrior(int index, double value) { + this.transitionPrior.set(index, value); + } + + /** + * Add a child, which is defined by the offset. + */ + public void addChildrenOffset(int childOffset) { + this.childOffsets.add(childOffset); + this.numChildren += 1; + } + + /** + * Add a word. + */ + public void addWord(int wordIndex, double wordCount) { + this.words.add(wordIndex); + this.wordsCount.add(wordCount); + this.numWords += 1; + } + + /** + * Increase the number of paths. + */ + public void addPaths(int inc) { + this.numPaths += inc; + } + + /** + * return the offset of current node. + */ + public int getOffset() { + return this.offset; + } + + /** + * return the number of children. + */ + public int getNumChildren() { + return this.numChildren; + } + + /** + * return the number of words. + */ + public int getNumWords() { + return this.numWords; + } + + /** + * return the child offset given the child index. + */ + public int getChild(int child_index) { + return this.childOffsets.get(child_index); + } + + /** + * return the word given the word index. + */ + public int getWord(int word_index) { + return this.words.get(word_index); + } + + /** + * return the word count given the word index. + */ + public double getWordCount(int word_index) { + return this.wordsCount.get(word_index); + } + + /** + * return the hypocount of the node. + */ + public double getHypoCount() { + return this.hypoCount; + } + + /** + * return the transition scalor. + */ + public double getTransitionScalor() { + return this.transitionScalor; + } + + /** + * return the scaled transition prior distribution. + */ + public TDoubleArrayList getTransitionPrior() { + return this.transitionPrior; + } + + /** + * normalize the prior to be a distribution and then scale it. + */ + public void normalizePrior() { + double norm = 0; + for (int ii = 0; ii < this.transitionPrior.size(); ii++) { + norm += this.transitionPrior.get(ii); + } + for (int ii = 0; ii < this.transitionPrior.size(); ii++) { + double tmp = this.transitionPrior.get(ii) / norm; + tmp *= this.transitionScalor; + this.transitionPrior.set(ii, tmp); + } + } +} diff --git a/src/cc/mallet/topics/tree/NonZeroPath.java b/src/cc/mallet/topics/tree/NonZeroPath.java new file mode 100755 index 000000000..b58dc5b61 --- /dev/null +++ b/src/cc/mallet/topics/tree/NonZeroPath.java @@ -0,0 +1,31 @@ +package cc.mallet.topics.tree; + +import gnu.trove.TIntDoubleHashMap; +import gnu.trove.TIntIntHashMap; +import gnu.trove.TIntObjectHashMap; + + +/** + * This class defines a structure for recording nonzeropath. + * key1: type; key2: path id; value: count. + * + * @author Yuening Hu + */ + +public class NonZeroPath { + + HIntIntIntHashMap data; + + public NonZeroPath () { + this.data = new HIntIntIntHashMap(); + } + + public void put(int key1, int key2, int value) { + this.data.put(key1, key2, value); + } + + public void get(int key1, int key2) { + this.data.get(key1, key2); + } + +} diff --git a/src/cc/mallet/topics/tree/OntologyWriter.java b/src/cc/mallet/topics/tree/OntologyWriter.java new file mode 100755 index 000000000..360d355c6 --- /dev/null +++ b/src/cc/mallet/topics/tree/OntologyWriter.java @@ -0,0 +1,905 @@ +package cc.mallet.topics.tree; + +import gnu.trove.TIntArrayList; +import gnu.trove.TIntHashSet; +import gnu.trove.TIntIntHashMap; +import gnu.trove.TIntObjectHashMap; + +import java.io.FileOutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Stack; +import java.util.TreeMap; +import java.util.TreeSet; + +import topicmod_projects_ldawn.WordnetFile.WordNetFile; +import topicmod_projects_ldawn.WordnetFile.WordNetFile.Synset; +import topicmod_projects_ldawn.WordnetFile.WordNetFile.Synset.Word; + + +/** + * Converts a set of user-selected constraints into Protocol Buffer form. + * This is an adaptation of Yuening's Python code that does the same thing. + * Following the style of Brianna's original code of OntologyWriter.java. + * + * @author Yuening Hu + */ + +public class OntologyWriter { + private Map> parents; + + private int numFiles; + private String filename; + + private int root; + private Map> vocab; + private boolean propagateCounts; + + private int maxLeaves; + private Map leafSynsets; + private Map internalSynsets; + private WordNetFile.Builder leafWn; + private WordNetFile.Builder internalWn; + private boolean finalized; + + static class WordTuple { + public int id; + public int language; + public String word; + public double count; + } + + static class VocabEntry { + public int index; + public int language; + public int flag; + } + + static class Constraint { + public ArrayList cl; + public ArrayList ml; + } + + static class Node { + public int index; + public boolean rootChild; + public String linkType; + public ArrayList children; + public int[] words; + } + + final static int ENGLISH_ID = 0; + + private OntologyWriter(String filename, boolean propagateCounts) { + this.filename = filename; + this.propagateCounts = propagateCounts; + + vocab = new TreeMap>(); + parents = new TreeMap>(); + + root = -1; + + maxLeaves = 10000; + leafSynsets = new TreeMap(); + internalSynsets = new TreeMap(); + leafWn = WordNetFile.newBuilder(); + leafWn.setRoot(-1); + internalWn = WordNetFile.newBuilder(); + internalWn.setRoot(-1); + finalized = false; + } + + private void addParent(int childId, int parentId) { + if (!parents.containsKey(childId)) { + parents.put(childId, new TreeSet()); + } + parents.get(childId).add(parentId); + } + + private List getParents(int id) { + List parentList = new ArrayList(); + if (!parents.containsKey(id) || parents.get(id).size() == 0) { + if (this.root < 0) + this.root = id; + return new ArrayList(); + } else { + parentList.addAll(parents.get(id)); + for (int parentId : parents.get(id)) { + parentList.addAll(getParents(parentId)); + } + } + return parentList; + } + + private int getTermId(int language, String term) { + if (!vocab.containsKey(language)) { + vocab.put(language, new TreeMap()); + } + if (!vocab.get(language).containsKey(term)) { + int length = vocab.get(language).size(); + vocab.get(language).put(term, length); + } + return vocab.get(language).get(term); + } + + private void findRoot(Map synsets) { + for (int synsetId : synsets.keySet()) { + if (synsetId % 1000 == 0) { + System.out.println("Finalizing " + synsetId); + } + for (int parentId : getParents(synsetId)) { + if (propagateCounts) { + double hypCount = this.internalSynsets.get(parentId).getHyponymCount(); + double rawCount = synsets.get(synsetId).getRawCount(); + this.internalSynsets.get(parentId).setHyponymCount(hypCount + rawCount); + } + } + } + } + + // Named this so it doesn't conflict with Object.finalize + private void finalizeMe() throws Exception { + findRoot(this.leafSynsets); + for(int id : this.leafSynsets.keySet()) { + this.leafWn.addSynsets(this.leafSynsets.get(id)); + } + write(this.leafWn); + + findRoot(this.internalSynsets); + if(this.root < 0) { + System.out.println("No root has been found!"); + throw new Exception(); + } + this.internalWn.setRoot(this.root); + for(int id : this.internalSynsets.keySet()) { + this.internalWn.addSynsets(this.internalSynsets.get(id)); + } + write(this.internalWn); + } + + private void write(WordNetFile.Builder wnFile) { + try { + String newFilename = filename + "." + numFiles; + WordNetFile builtFile = wnFile.build(); + builtFile.writeTo(new FileOutputStream(newFilename)); + System.out.println("Serialized version written to: " + newFilename); + this.numFiles ++; + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void addSynset(int numericId, String senseKey, List children, + List words) { + Synset.Builder synset = Synset.newBuilder(); + + double rawCount = 0.0; + synset.setOffset(numericId); + synset.setKey(senseKey); + + if(senseKey.startsWith("ML_")) { + synset.setHyperparameter("ML_"); + } else if(senseKey.startsWith("CL_")) { + synset.setHyperparameter("CL_"); + } else if(senseKey.startsWith("NL_")) { + synset.setHyperparameter("NL_"); + } else if(senseKey.startsWith("ROOT")) { + synset.setHyperparameter("NL_"); + } else if(senseKey.startsWith("LEAF_")) { + synset.setHyperparameter("NL_"); + } else { + synset.setHyperparameter("DEFAULT_"); + } + + if(children != null) { + for (int child : children){ + addParent(child, numericId); + synset.addChildrenOffsets(child); + } + } + + if(words != null) { + for (WordTuple tuple : words) { + Word.Builder word = Word.newBuilder(); + word.setLangId(tuple.language); + //word.setTermId(getTermId(tuple.language, tuple.word)); + word.setTermId(tuple.id); + word.setTermStr(tuple.word); + word.setCount(tuple.count); + rawCount += tuple.count; + synset.addWords(word); + synset.setRawCount(rawCount); + } + } + + synset.setHyponymCount(rawCount + 0.0); + + if(children != null && children.size() > 0) { + //this.internalWn.addSynsets(synset.clone()); + this.internalSynsets.put(numericId, synset); + } else { + //this.leafWn.addSynsets(synset.clone()); + this.leafSynsets.put(numericId, synset); + } + } + + private static ArrayList getVocab(String filename) { + ArrayList vocab = new ArrayList(); + int index = 0; + try { + List lines = Utils.readAll(filename); + for (String line : lines) + { + String[] words = line.trim().split("\t"); + if (words.length > 1) { + vocab.add(words[1]); + } else { + System.out.println("Error! " + index); + } + index++; + } + } catch (Exception e) { + e.printStackTrace(); + } + return vocab; + } + + private static void readConstraints(String consfilename, ArrayList vocab, + ArrayList ml, ArrayList cl) { + //List constraints = new ArrayList(); + + try { + List lines = Utils.readAll(consfilename); + for (String line : lines) { + String[] words = line.trim().split("\t"); + int[] indexWords = new int[words.length - 1]; + for(int ii = 1; ii < words.length; ii++) { + int index = vocab.indexOf(words[ii]); + if (index == -1) { + System.out.println("Found words that not contained in the vocab: " + words[ii]); + throw new Exception(); + } + indexWords[ii-1] = index; + } + + //for(int ii = 0; ii < indexWords.length; ii++) { + // System.out.print(indexWords[ii] + " "); + //} + + if (words[0].equals("SPLIT_")) { + cl.add(indexWords); + } else if (words[0].equals("MERGE_")) { + ml.add(indexWords); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + + } + + private static void generateGraph(ArrayList cons, int value, HIntIntIntHashMap graph) { + for (int[] con : cons) { + for (int w1 : con) { + for (int w2 : con) { + if ( w1 != w2) { + graph.put(w1, w2, value); + graph.put(w2, w1, value); + } + } + } + } + } + + private static ArrayList BFS(HIntIntIntHashMap graph, int[] consWords, int choice) { + ArrayList connected = new ArrayList (); + TIntIntHashMap visited = new TIntIntHashMap (); + for (int word : consWords) { + visited.put(word, 0); + } + + for (int word : consWords) { + if (visited.get(word) == 0) { + Stack queue = new Stack (); + queue.push(word); + TIntHashSet component = new TIntHashSet (); + while (queue.size() > 0) { + int node = queue.pop(); + component.add(node); + for(int neighbor : graph.get(node).keys()) { + if (choice == -1) { + if (graph.get(node, neighbor) > 0 && visited.get(neighbor) == 0) { + visited.adjustValue(neighbor, 1); + queue.push(neighbor); + } + } else { + if (graph.get(node, neighbor) == choice && visited.get(neighbor) == 0) { + visited.adjustValue(neighbor, 1); + queue.push(neighbor); + } + } + } + } + connected.add(component.toArray()); + } + } + + return connected; + + } + + private static ArrayList mergeML(ArrayList ml) { + HIntIntIntHashMap graph = new HIntIntIntHashMap (); + generateGraph(ml, 1, graph); + int[] consWords = graph.getKey1Set(); + + ArrayList ml_merged = BFS(graph, consWords, -1); + return ml_merged; + } + + private static void mergeCL(ArrayList cl_ml_merged, + ArrayList ml_remained, HIntIntIntHashMap graph) throws Exception { + int[] consWords = graph.getKey1Set(); + // get connected components + ArrayList connectedComp = BFS(graph, consWords, -1); + + // merge ml to cl + for(int[] comp : connectedComp) { + ArrayList cl_tmp = BFS(graph, comp, 1); + ArrayList ml_tmp = BFS(graph, comp, 2); + + ArrayList cl_new = new ArrayList (); + for(int[] cons : cl_tmp) { + if (cons.length > 1) { + cl_new.add(cons); + } + } + + ArrayList ml_new = new ArrayList (); + for(int[] cons : ml_tmp) { + if (cons.length > 1) { + ml_new.add(cons); + } + } + + if(cl_new.size() > 0) { + Constraint cons = new Constraint(); + cons.cl = cl_new; + cons.ml = ml_new; + cl_ml_merged.add(cons); + } else { + if (ml_new.size() != 1) { + System.out.println("ml_new.size != 1 && cl_new.size == 0"); + throw new Exception(); + } + Constraint cons = new Constraint(); + cons.cl = null; + cons.ml = ml_new; + ml_remained.add(cons); + } + } + } + + private static HIntIntIntHashMap flipGraph(ArrayList cl_merged, ArrayList ml_merged, + HIntIntIntHashMap graph) { + + HIntIntIntHashMap flipped = new HIntIntIntHashMap (); + TIntHashSet consWordsSet = getConsWords(cl_merged); + TIntHashSet set2 = getConsWords(ml_merged); + consWordsSet.addAll(set2.toArray()); + int[] consWords = consWordsSet.toArray(); + + for(int word : consWords) { + TIntHashSet cl_neighbor = new TIntHashSet (); + for(int neighbor : graph.get(word).keys()) { + if(graph.get(word, neighbor) == 1) { + cl_neighbor.add(neighbor); + } + } + consWordsSet.removeAll(cl_neighbor.toArray()); + for(int nonConnected : consWordsSet.toArray()) { + flipped.put(word, nonConnected, 1); + } + for(int neighbor : graph.get(word).keys()) { + flipped.put(word, neighbor, 0); + } + consWordsSet.addAll(cl_neighbor.toArray()); + } + + //printGraph(flipped, "flipped half"); + for(int[] ml : ml_merged) { + for(int w1 : ml) { + for(int w2 : flipped.get(w1).keys()) { + if(flipped.get(w1, w2) > 0) { + for(int w3 : ml) { + if(w1 != w3 && flipped.get(w3, w2) == 0) { + flipped.put(w1, w2, 0); + flipped.put(w2, w1, 0); + } + } + } + } + + for(int w2 : ml) { + if (w1 != w2) { + flipped.put(w1, w2, 2); + flipped.put(w2, w1, 2); + } + } + } + } + return flipped; + } + + private static TIntHashSet getUnion(TIntHashSet set1, TIntHashSet set2) { + TIntHashSet union = new TIntHashSet (); + union.addAll(set1.toArray()); + union.addAll(set2.toArray()); + return union; + } + + private static TIntHashSet getDifference(TIntHashSet set1, TIntHashSet set2) { + TIntHashSet diff = new TIntHashSet (); + diff.addAll(set1.toArray()); + diff.removeAll(set2.toArray()); + return diff; + } + + private static TIntHashSet getIntersection(TIntHashSet set1, TIntHashSet set2) { + TIntHashSet inter = new TIntHashSet (); + for(int ww : set1.toArray()) { + if(set2.contains(ww)) { + inter.add(ww); + } + } + return inter; + } + + private static void BronKerBosch_v2(TIntHashSet R, TIntHashSet P, TIntHashSet X, + HIntIntIntHashMap G, ArrayList C) { + if(P.size() == 0 && X.size() == 0) { + if(R.size() > 0) { + C.add(R.toArray()); + } + return; + } + + int d = 0; + int pivot = -1; + + TIntHashSet unionPX = getUnion(P, X); + for(int v : unionPX.toArray()) { + TIntHashSet neighbors = new TIntHashSet (); + for(int node : G.get(v).keys()) { + if(G.get(v, node) > 0 && v != node) { + neighbors.add(node); + } + } + if(neighbors.size() > d) { + d = neighbors.size(); + pivot = v; + } + } + + TIntHashSet neighbors = new TIntHashSet (); + if(pivot != -1) { + for(int node : G.get(pivot).keys()) { + if (G.get(pivot, node) > 0 && pivot != node) { + neighbors.add(node); + } + } + } + + TIntHashSet diffPN = getDifference(P, neighbors); + for(int v : diffPN.toArray()) { + TIntHashSet newNeighbors = new TIntHashSet(); + for(int node : G.get(v).keys()) { + if(G.get(v, node) > 0 && v != node) { + newNeighbors.add(node); + } + } + + TIntHashSet unionRV = new TIntHashSet(); + unionRV.add(v); + unionRV.addAll(R.toArray()); + BronKerBosch_v2(unionRV, getIntersection(P, newNeighbors), getIntersection(X, newNeighbors), G, C); + + P.remove(v); + X.add(v); + } + } + + private static ArrayList generateCliques(HIntIntIntHashMap graph) { + + TIntHashSet R = new TIntHashSet (); + TIntHashSet P = new TIntHashSet (); + TIntHashSet X = new TIntHashSet (); + ArrayList cliques = new ArrayList (); + P.addAll(graph.getKey1Set()); + + BronKerBosch_v2(R, P, X, graph, cliques); + + return cliques; + } + + private static int generateCLTree(ArrayList cl_merged, + HIntIntIntHashMap graph, TIntObjectHashMap subtree) { + // the index of root is 0 + int index = 0; + for(Constraint con : cl_merged) { + ArrayList cl = con.cl; + ArrayList ml = con.ml; + HIntIntIntHashMap flipped = flipGraph(cl, ml, graph); + //printGraph(flipped, "flipped graph"); + ArrayList cliques = generateCliques(flipped); + //printArrayList(cliques, "cliques found from flipped graph"); + + Node cl_node = new Node(); + cl_node.index = ++index; + cl_node.rootChild = true; + cl_node.linkType = "CL_"; + cl_node.children = new ArrayList(); + for(int[] clique : cliques) { + TIntHashSet clique_remained = new TIntHashSet(clique); + //printHashSet(clique_remained, "clique_remained"); + ArrayList ml_tmp = BFS(graph, clique, 2); + ArrayList ml_new = new ArrayList (); + for(int[] ml_con : ml_tmp) { + if (ml_con.length > 1) { + ml_new.add(ml_con); + for(int word : ml_con) { + clique_remained.remove(word); + } + } + } + //printHashSet(clique_remained, "clique_remained"); + + Node node = new Node(); + node.index = ++index; + node.rootChild = false; + cl_node.children.add(node.index); + if(ml_new.size() == 0) { + node.linkType = "NL_"; + node.children = null; + node.words = clique_remained.toArray(); + } else if(clique_remained.size() == 0 && ml_new.size() == 1) { + node.linkType = "ML_"; + node.children = null; + node.words = ml_new.get(0); + } else { + node.linkType = "NL_IN_"; + node.children = new ArrayList(); + node.words = null; + for(int[] ml_clique : ml_new) { + Node child_node = new Node(); + child_node.index = ++index; + node.rootChild = false; + child_node.linkType = "ML_"; + child_node.children = null; + child_node.words = ml_clique; + node.children.add(index); + subtree.put(child_node.index, child_node); + } + if(clique_remained.size() > 0) { + Node child_node = new Node(); + child_node.index = ++index; + node.rootChild = false; + child_node.linkType = "NL_"; + child_node.children = null; + child_node.words = clique_remained.toArray(); + node.children.add(index); + subtree.put(child_node.index, child_node); + } + } + subtree.put(node.index, node); + } + subtree.put(cl_node.index, cl_node); + } + + return index; + } + + private static int generateMLTree(ArrayList ml_remained, + TIntObjectHashMap subtree, int index) { + //printConstraintList(ml_remained, "remained"); + int ml_index = index; + for(Constraint con : ml_remained) { + for(int[] ml : con.ml) { + Node node = new Node(); + node.index = ++ml_index; + node.rootChild = true; + node.linkType = "ML_"; + node.children = null; + node.words = ml; + subtree.put(node.index, node); + } + } + return ml_index; + } + + private static TIntHashSet getConsWords(ArrayList cons) { + TIntHashSet consWords = new TIntHashSet(); + for(int[] con : cons) { + consWords.addAll(con); + } + return consWords; + } + + private static TIntObjectHashMap mergeAllConstraints(ArrayList ml, ArrayList cl) { + //printArrayList(ml, "read in ml"); + //printArrayList(cl, "read in cl"); + + // merge ml constraints + ArrayList ml_merged = mergeML(ml); + + // generate graph + HIntIntIntHashMap graph = new HIntIntIntHashMap (); + generateGraph(cl, 1, graph); + generateGraph(ml_merged, 2, graph); + //printGraph(graph, "original graph"); + + // merge cl: notice some ml can be merged into cl, the remained ml will be kept + ArrayList cl_ml_merged = new ArrayList (); + ArrayList ml_remained = new ArrayList (); + try { + mergeCL(cl_ml_merged, ml_remained, graph); + } catch (Exception e) { + e.printStackTrace(); + } + + //printConstraintList(cl_ml_merged, "cl ml merged"); + //printConstraintList(ml_remained, "ml_remained"); + + TIntObjectHashMap subtree = new TIntObjectHashMap(); + int index = generateCLTree(cl_ml_merged, graph, subtree); + int new_index = generateMLTree(ml_remained, subtree, index); + + return subtree; + } + + private static TIntObjectHashMap noMergeConstraints(ArrayList ml, ArrayList cl) { + TIntObjectHashMap subtree = new TIntObjectHashMap(); + int index = 0; + for(int[] cons : ml) { + Node node = new Node(); + node.index = ++index; + node.rootChild = true; + node.linkType = "ML_"; + node.children = null; + node.words = cons; + subtree.put(node.index, node); + } + + for(int[] cons : cl) { + Node node = new Node(); + node.index = ++index; + node.rootChild = true; + node.linkType = "CL_"; + node.children = null; + node.words = cons; + subtree.put(node.index, node); + } + + return subtree; + } + + private static void printHashSet(TIntHashSet set, String title){ + String tmp = title + ": "; + for(int word : set.toArray()) { + tmp += word + " "; + } + } + + private static void printConstraintList(ArrayList constraints, String title) { + System.out.println(title + ": "); + for(Constraint cons : constraints) { + String tmp = ""; + if(cons.ml != null) { + tmp = "ml: "; + for(int[] ml : cons.ml) { + tmp += "( "; + for(int ww : ml) { + tmp += ww + " "; + } + tmp += ") "; + } + } + if(cons.cl != null) { + tmp += "cl: "; + for(int[] cl : cons.cl) { + tmp += "( "; + for(int ww : cl) { + tmp += ww + " "; + } + tmp += ") "; + } + } + System.out.println(tmp); + } + } + + private static void printGraph(HIntIntIntHashMap graph, String title) { + System.out.println(title + ": "); + for(int w1 : graph.getKey1Set()) { + String tmp = ""; + for(int w2 : graph.get(w1).keys()) { + tmp += "( " + w1 + " " + w2 + " : " + graph.get(w1, w2) + " ) "; + } + System.out.println(tmp); + } + } + + private static void printArrayList(ArrayList result, String title) { + System.out.println(title + ": "); + for(int[] sent : result) { + String line = ""; + for (int word : sent) { + line += word + " "; + } + System.out.println(line); + } + } + + private static void printSubTree(TIntObjectHashMap subtree) { + for(int index : subtree.keys()) { + Node node = subtree.get(index); + String tmp = "Node " + index + " : "; + tmp += node.linkType + " "; + if(node.children != null) { + tmp += "chilren ["; + for(int child : node.children) { + tmp += child + " "; + } + tmp += "]"; + } + if (node.words != null) { + tmp += " words [ "; + for(int word : node.words) { + tmp += word + " "; + } + tmp += "]"; + } + System.out.println(tmp); + } + } + + /** + * This is the top-level method that creates the ontology from a set of + * Constraint objects. + * @param vocabFilename the .voc file corresponding to the data set + * being used + * @throws Exception + */ + public static void createOntology(String consFilename, String vocabFilename, + String outputDir, boolean mergeConstraints) throws Exception { + + // load vocab + int LANG_ID = 0; + ArrayList vocab = getVocab(vocabFilename); + System.out.println("Load vocab size: " + vocab.size()); + // load constraints and make sure all constraints words are contained in vocab + ArrayList ml = new ArrayList (); + ArrayList cl = new ArrayList (); + if(consFilename != null) { + readConstraints(consFilename, vocab, ml, cl); + } + + // merge constraints + TIntObjectHashMap subtree; + if (mergeConstraints) { + subtree = mergeAllConstraints(ml, cl); + } else { + subtree = noMergeConstraints(ml, cl); + } + printSubTree(subtree); + + // get constraint count (If count == 0, it is unconstraint words) + int[] vocabFlag = new int[vocab.size()]; + for(int ii = 0; ii < vocabFlag.length; ii++) { + vocabFlag[ii] = 0; + } + for(int index : subtree.keys()) { + Node node = subtree.get(index); + if(node.words != null) { + for(int wordIndex : node.words) { + vocabFlag[wordIndex]++; + } + } + } + + ///////////////// + + OntologyWriter writer = new OntologyWriter(outputDir, true); + List rootChildren = new ArrayList(); + + int leafIndex = subtree.size(); + for(int index : subtree.keys()) { + Node node = subtree.get(index); + List nodeChildren = null; + ArrayList nodeWords = null; + if(node.rootChild) { + rootChildren.add(node.index); + } + if(node.children != null && node.words != null) { + System.out.println("A node has both children and words! Wrong!"); + throw new Exception(); + } else if(node.children != null) { + nodeChildren = node.children; + } else if(node.words != null) { + if(node.words.length == 1) { + nodeWords = new ArrayList (); + WordTuple wt = new WordTuple(); + wt.id = node.words[0]; + wt.language = LANG_ID; + wt.word = vocab.get(wt.id); + wt.count = 1.0 / vocabFlag[wt.id]; + nodeWords.add(wt); + } else { + nodeChildren = new ArrayList(); + for(int wordIndex : node.words) { + leafIndex++; + nodeChildren.add(leafIndex); + List leafChildren = null; + ArrayList leafWords = new ArrayList (); + WordTuple wt = new WordTuple(); + wt.id = wordIndex; + wt.language = LANG_ID; + wt.word = vocab.get(wordIndex); + wt.count = 1.0 / vocabFlag[wordIndex]; + leafWords.add(wt); + String name = "LEAF_" + leafIndex + "_" + wt.word; + writer.addSynset(leafIndex, name, leafChildren, leafWords); + } + } + } + + if(node.words != null && node.words.length == 1) { + node.linkType = "LEAF_"; + String name = node.linkType + node.index + "_" + vocab.get(node.words[0]); + writer.addSynset(node.index, name, nodeChildren, nodeWords); + } else { + writer.addSynset(node.index, node.linkType + node.index, nodeChildren, nodeWords); + } + + } + + // Unused words + for(int wordIndex = 0; wordIndex < vocabFlag.length; wordIndex++) { + if (vocabFlag[wordIndex] == 0) { + rootChildren.add(++leafIndex); + List leafChildren = null; + ArrayList leafWords = new ArrayList (); + WordTuple wt = new WordTuple(); + wt.id = wordIndex; + wt.language = LANG_ID; + wt.word = vocab.get(wordIndex); + wt.count = 1.0; + leafWords.add(wt); + String name = "LEAF_" + leafIndex + "_" + wt.word; + writer.addSynset(leafIndex, name, leafChildren, leafWords); + } + } + + writer.addSynset(0, "ROOT", rootChildren, null); + writer.finalizeMe(); + } + + + public static void main(String[] args) { + String vocabFn = "input/toy/toy.voc"; + String consFile = "input/toy/toy.cons"; + String outputFn = "input/toy/toy_test.wn"; + boolean mergeConstraints = true; + try { + createOntology(consFile, vocabFn, outputFn, mergeConstraints); + } catch (Exception e){ + e.printStackTrace(); + } + } +} diff --git a/src/cc/mallet/topics/tree/Path.java b/src/cc/mallet/topics/tree/Path.java new file mode 100755 index 000000000..abcbf7e7a --- /dev/null +++ b/src/cc/mallet/topics/tree/Path.java @@ -0,0 +1,53 @@ +package cc.mallet.topics.tree; + +import gnu.trove.TIntArrayList; + +/** + * This class defines a path. + * A path is a list of nodes, and the last node emits a word. + * + * @author Yuening Hu + */ + +public class Path { + + TIntArrayList nodes; + //TIntArrayList children; + int finalWord; + + public Path () { + this.nodes = new TIntArrayList(); + this.finalWord = -1; + } + + /** + * Add nodes to this path. + */ + public void addNodes (TIntArrayList innodes) { + for (int ii = 0; ii < innodes.size(); ii++) { + int node_index = innodes.get(ii); + this.nodes.add(node_index); + } + } + + /** + * Add the final word of this path. + */ + public void addFinalWord(int word) { + this.finalWord = word; + } + + /** + * return the node list. + */ + public TIntArrayList getNodes() { + return this.nodes; + } + + /** + * return the final word. + */ + public int getFinalWord() { + return this.finalWord; + } +} diff --git a/src/cc/mallet/topics/tree/PriorTree.java b/src/cc/mallet/topics/tree/PriorTree.java new file mode 100755 index 000000000..93a190974 --- /dev/null +++ b/src/cc/mallet/topics/tree/PriorTree.java @@ -0,0 +1,363 @@ +package cc.mallet.topics.tree; + +import gnu.trove.TIntArrayList; +import gnu.trove.TIntObjectHashMap; +import gnu.trove.TIntObjectIterator; +import gnu.trove.TObjectDoubleHashMap; + +import java.io.BufferedReader; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; + +import cc.mallet.types.Alphabet; +import cc.mallet.types.InstanceList; + +import topicmod_projects_ldawn.WordnetFile.WordNetFile; +import topicmod_projects_ldawn.WordnetFile.WordNetFile.Synset; +import topicmod_projects_ldawn.WordnetFile.WordNetFile.Synset.Word; + +/** + * This class loads the prior tree structure from the proto buffer files of tree structure. + * Main entrance: initialize() + * + * @author Yuening Hu + */ + +public class PriorTree { + + int root; + int maxDepth; + + TObjectDoubleHashMap hyperparams; + TIntObjectHashMap nodes; + TIntObjectHashMap> wordPaths; + + public PriorTree () { + this.hyperparams = new TObjectDoubleHashMap (); + this.nodes = new TIntObjectHashMap (); + this.wordPaths = new TIntObjectHashMap> (); + } + + /** + * Get the input tree file lists from the given tree file names + */ + private ArrayList getFileList(String tree_files) { + + int split_index = tree_files.lastIndexOf('/'); + String dirname = tree_files.substring(0, split_index); + String fileprefix = tree_files.substring(split_index+1); + fileprefix = fileprefix.replace("*", ""); + + //System.out.println(dirname); + //System.out.println(fileprefix); + + File dir = new File(dirname); + String[] children = dir.list(); + ArrayList filelist = new ArrayList(); + + for (int i = 0; i < children.length; i++) { + if (children[i].startsWith(fileprefix)) { + System.out.println("Found one: " + dirname + "/" + children[i]); + String filename = dirname + "/" + children[i]; + filelist.add(filename); + } + } + return filelist; + } + + /** + * Load hyper parameters from the given file + */ + private void loadHyperparams(String hyperFile) { + try { + FileInputStream infstream = new FileInputStream(hyperFile); + DataInputStream in = new DataInputStream(infstream); + BufferedReader br = new BufferedReader(new InputStreamReader(in)); + + String strLine; + //Read File Line By Line + while ((strLine = br.readLine()) != null) { + strLine = strLine.trim(); + String[] str = strLine.split(" "); + if (str.length != 2) { + System.out.println("Hyperparameter file is not in the correct format!"); + System.exit(0); + } + double tmp = Double.parseDouble(str[1]); + hyperparams.put(str[0], tmp); + } + in.close(); + +// Iterator> it = hyperparams.entrySet().iterator(); +// while (it.hasNext()) { +// Map.Entry entry = it.next(); +// System.out.println(entry.getKey()); +// System.out.println(entry.getValue()); +// } + + } catch (IOException e) { + System.out.println("No hyperparameter file Found!"); + } + } + + /** + * Load tree nodes one by one: load the children, words of each node + */ + private void loadTree(String tree_files, ArrayList vocab) { + + ArrayList filelist = getFileList(tree_files); + + for (int ii = 0; ii < filelist.size(); ii++) { + String filename = filelist.get(ii); + WordNetFile tree = null; + try { + tree = WordNetFile.parseFrom(new FileInputStream(filename)); + } catch (IOException e) { + System.out.println("Cannot find tree file: " + filename); + } + + int new_root = tree.getRoot(); + assert( (new_root == -1) || (this.root == -1) || (new_root == this.root)); + if (new_root >= 0) { + this.root = new_root; + } + + for (int jj = 0; jj < tree.getSynsetsCount(); jj++) { + Synset synset = tree.getSynsets(jj); + Node n = new Node(); + n.setOffset(synset.getOffset()); + n.setRawCount(synset.getRawCount()); + n.setHypoCount(synset.getHyponymCount()); + + double transition = hyperparams.get(synset.getHyperparameter()); + n.setTransitionScalor(transition); + for (int cc = 0; cc < synset.getChildrenOffsetsCount(); cc++) { + n.addChildrenOffset(synset.getChildrenOffsets(cc)); + } + + for (int ww = 0; ww < synset.getWordsCount(); ww++) { + Word word = synset.getWords(ww); + int term_id = vocab.indexOf(word.getTermStr()); + //int term_id = vocab.lookupIndex(word.getTermStr()); + double word_count = word.getCount(); + n.addWord(term_id, word_count); + } + + nodes.put(n.getOffset(), n); + } + } + + assert(this.root >= 0) : "Cannot find a root node in the tree file. Have you provided " + + "all tree files instead of a single tree file? (e.g., use 'tree.wn' instead of 'tree.wn.0')"; + + } + + /** + * Get all the paths in the tree, keep the (word, path) pairs + * Note the word in the pair is actually the word of the leaf node + */ + private int searchDepthFirst(int depth, + int node_index, + TIntArrayList traversed, + TIntArrayList next_pointers) { + int max_depth = depth; + traversed.add(node_index); + Node current_node = this.nodes.get(node_index); + current_node.addPaths(1); + + // go over the words that current node emits + for (int ii = 0; ii < current_node.getNumWords(); ii++) { + int word = current_node.getWord(ii); + Path p = new Path(); + p.addNodes(traversed); + // p.addChildren(next_pointers); + p.addFinalWord(word); + if (! this.wordPaths.contains(word)) { + this.wordPaths.put(word, new ArrayList ()); + } + ArrayList tmp = this.wordPaths.get(word); + tmp.add(p); + } + + // go over the child nodes of the current node + for (int ii = 0; ii < current_node.getNumChildren(); ii++) { + int child = current_node.getChild(ii); + next_pointers.add(child); + int child_depth = this.searchDepthFirst(depth+1, child, traversed, next_pointers); + next_pointers.remove(next_pointers.size()-1); + max_depth = max_depth >= child_depth ? max_depth : child_depth; + } + + traversed.remove(traversed.size()-1); + return max_depth; + } + + /** + * Set the scaled prior distribution of each node + * According to the hypoCount of the nodes' children, generate a Multinomial + * distribution, then scaled by transitionScalor + */ + private void setPrior() { + for (TIntObjectIterator it = this.nodes.iterator(); it.hasNext(); ) { + it.advance(); + Node n = it.value(); + int numChildren = n.getNumChildren(); + int numWords = n.getNumWords(); + + // firstly set the hypoCount for each child + if (numChildren > 0) { + assert numWords == 0; + n.initializePrior(numChildren); + for (int ii = 0; ii < numChildren; ii++) { + int child = n.getChild(ii); + n.setPrior(ii, this.nodes.get(child).getHypoCount()); + } + } + + // this step is for tree structures whose leaf nodes contain more than one words + // if the leaf node contains multiple words, we will treat each word + // as a "leaf node" and set a multinomial over all the words + // if the leaf node contains only one word, so this step will be jumped over. + if (numWords > 1) { + assert numChildren == 0; + n.initializePrior(numWords); + for (int ii = 0; ii < numWords; ii++) { + n.setPrior(ii, n.getWordCount(ii)); + } + } + + // then normalize and scale + n.normalizePrior(); + } + } + + /** + * the entrance of this class + */ + public void initialize(String treeFiles, String hyperFile, ArrayList vocab) { + this.loadHyperparams(hyperFile); + this.loadTree(treeFiles, vocab); + + TIntArrayList traversed = new TIntArrayList (); + TIntArrayList next_pointers = new TIntArrayList (); + //this.maxDepth = this.searchDepthFirst(0, 0, traversed, next_pointers); + this.maxDepth = this.searchDepthFirst(0, this.root, traversed, next_pointers); + this.setPrior(); + + //System.out.println("**************************"); + // check the word paths + System.out.println("Number of words: " + this.wordPaths.size()); + //System.out.println("Initialized paths"); + + /* + for (TIntObjectIterator> it = this.wordPaths.iterator(); it.hasNext(); ) { + it.advance(); + ArrayList paths = it.value(); + System.out.print(it.key() + ", " + vocab.get(it.key())); + //System.out.print(it.key() + ", " + vocab.lookupObject(it.key())); + for (int ii = 0; ii < paths.size(); ii++) { + Path p = paths.get(ii); + System.out.print(", Path " + ii); + System.out.print(", Path nodes list: " + p.getNodes()); + System.out.println(", Path final word: " + p.getFinalWord()); + } + } + System.out.println("**************************"); + + // check the prior + System.out.println("Check the prior"); + for (TIntObjectIterator it = this.nodes.iterator(); it.hasNext(); ) { + it.advance(); + if (it.value().getTransitionPrior().size() > 0) { + System.out.print("Node " + it.key()); + System.out.println(", Transition prior " + it.value().getTransitionPrior()); + } + } + System.out.println("**************************"); + */ + + } + + public int getMaxDepth() { + return this.maxDepth; + } + + public int getRoot() { + return this.root; + } + + public TIntObjectHashMap getNodes() { + return this.nodes; + } + + public TIntObjectHashMap> getWordPaths() { + return this.wordPaths; + } + + /** + * Load vocab + */ + public ArrayList readVocab(String vocabFile) { + + ArrayList vocab = new ArrayList (); + + try { + FileInputStream infstream = new FileInputStream(vocabFile); + DataInputStream in = new DataInputStream(infstream); + BufferedReader br = new BufferedReader(new InputStreamReader(in)); + + String strLine; + //Read File Line By Line + while ((strLine = br.readLine()) != null) { + strLine = strLine.trim(); + String[] str = strLine.split("\t"); + if (str.length > 1) { + vocab.add(str[1]); + } else { + System.out.println("Error! " + strLine); + return null; + } + } + in.close(); + + } catch (IOException e) { + System.out.println("No vocab file Found!"); + } + + return vocab; + } + + /** + * test main + */ + public static void main(String[] args) throws Exception{ + + //String treeFiles = "../toy/toy_set1.wn.*"; + //String hyperFile = "../toy/tree_hyperparams"; + //String inputFile = "../input/toy-topic-input.mallet"; + //String vocabFile = "../toy/toy.voc"; + + //String treeFiles = "../synthetic/synthetic_set1.wn.*"; + //String hyperFile = "../synthetic/tree_hyperparams"; + //String inputFile = "../input/synthetic-topic-input.mallet"; + //String vocabFile = "../synthetic/synthetic.voc"; + + String treeFiles = "input/denews.all.wn"; + String hyperFile = "input/denews.hyper"; + String inputFile = "input/denews-topic-input.mallet"; + String vocabFile = "input/denews.filter.voc"; + + PriorTree tree = new PriorTree(); + ArrayList vocab = tree.readVocab(vocabFile); + + InstanceList ilist = InstanceList.load (new File(inputFile)); + tree.initialize(treeFiles, hyperFile, vocab); + } + +} diff --git a/src/cc/mallet/topics/tree/TopicSampler.java b/src/cc/mallet/topics/tree/TopicSampler.java new file mode 100755 index 000000000..e7ffe00fe --- /dev/null +++ b/src/cc/mallet/topics/tree/TopicSampler.java @@ -0,0 +1,278 @@ +package cc.mallet.topics.tree; + +import gnu.trove.TDoubleArrayList; +import gnu.trove.TIntArrayList; +import gnu.trove.TIntHashSet; +import gnu.trove.TIntIntHashMap; +import gnu.trove.TIntIntIterator; + +import java.io.BufferedReader; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; + +import cc.mallet.topics.tree.TreeTopicSamplerHashD.DocData; +import cc.mallet.types.Dirichlet; +import cc.mallet.types.FeatureSequence; +import cc.mallet.types.Instance; +import cc.mallet.types.InstanceList; +import cc.mallet.util.Randoms; + +/** + * Abstract class for TopicSampler. + * Defines the basic functions for input, output, resume. + * Also defines the abstract functions for child class. + * + * @author Yuening Hu + */ + +public abstract class TopicSampler{ + + int numTopics; + int numIterations; + int startIter; + Randoms random; + double[] alpha; + double alphaSum; + TDoubleArrayList lhood; + TDoubleArrayList iterTime; + ArrayList vocab; + + TreeTopicModel topics; + TIntHashSet cons; + + public TopicSampler (int numberOfTopics, double alphaSum, int seed) { + this.numTopics = numberOfTopics; + this.random = new Randoms(seed); + + this.alphaSum = alphaSum; + this.alpha = new double[numTopics]; + Arrays.fill(alpha, alphaSum / numTopics); + + this.vocab = new ArrayList (); + this.cons = new TIntHashSet(); + + this.lhood = new TDoubleArrayList(); + this.iterTime = new TDoubleArrayList(); + this.startIter = 0; + + // notice: this.topics and this.data are not initialized in this abstract class, + // in each sub class, the topics variable is initialized differently. + } + + + + public void setNumIterations(int iters) { + this.numIterations = iters; + } + + public int getNumIterations() { + return this.numIterations; + } + + + + /** + * This function returns the likelihood. + */ + public double lhood() { + return this.docLHood() + this.topics.topicLHood(); + } + + /** + * Resume lhood and iterTime from the saved lhood file. + */ + public void resumeLHood(String lhoodFile) throws IOException{ + FileInputStream lhoodfstream = new FileInputStream(lhoodFile); + DataInputStream lhooddstream = new DataInputStream(lhoodfstream); + BufferedReader brLHood = new BufferedReader(new InputStreamReader(lhooddstream)); + // the first line is the title + String strLine = brLHood.readLine(); + while ((strLine = brLHood.readLine()) != null) { + strLine = strLine.trim(); + String[] str = strLine.split("\t"); + // iteration, likelihood, iter_time + myAssert(str.length == 3, "lhood file problem!"); + this.lhood.add(Double.parseDouble(str[1])); + this.iterTime.add(Double.parseDouble(str[2])); + } + this.startIter = this.lhood.size(); + if (this.startIter > this.numIterations) { + System.out.println("Have already sampled " + this.numIterations + " iterations!"); + System.exit(0); + } + System.out.println("Start sampling for iteration " + this.startIter); + brLHood.close(); + } + + /** + * Resumes from the saved files. + */ + public void resume(InstanceList training, String resumeDir) { + try { + String statesFile = resumeDir + ".states"; + resumeStates(training, statesFile); + + String lhoodFile = resumeDir + ".lhood"; + resumeLHood(lhoodFile); + } catch (IOException e) { + System.out.println(e.getMessage()); + } + } + + /** + * This function prints the topic words of each topic. + */ + public void printTopWords(File file, int numWords) throws IOException { + PrintStream out = new PrintStream (file); + out.print(displayTopWords(numWords)); + out.close(); + } + + /** + * By implementing the comparable interface, this function ranks the words + * in each topic, and returns the top words for each topic. + */ + public String displayTopWords (int numWords) { + + class WordProb implements Comparable { + int wi; + double p; + public WordProb (int wi, double p) { this.wi = wi; this.p = p; } + public final int compareTo (Object o2) { + if (p > ((WordProb)o2).p) + return -1; + else if (p == ((WordProb)o2).p) + return 0; + else return 1; + } + } + + StringBuilder out = new StringBuilder(); + int numPaths = this.topics.getPathNum(); + //System.out.println(numPaths); + + for (int tt = 0; tt < this.numTopics; tt++){ + String tmp = "\n--------------\nTopic " + tt + "\n------------------------\n"; + //System.out.print(tmp); + out.append(tmp); + WordProb[] wp = new WordProb[numPaths]; + for (int pp = 0; pp < numPaths; pp++){ + int ww = this.topics.getWordFromPath(pp); + double val = this.topics.computeTopicPathProb(tt, ww, pp); + wp[pp] = new WordProb(pp, val); + } + Arrays.sort(wp); + for (int ii = 0; ii < wp.length; ii++){ + int pp = wp[ii].wi; + int ww = this.topics.getWordFromPath(pp); + //tmp = wp[ii].p + "\t" + this.vocab.lookupObject(ww) + "\n"; + tmp = wp[ii].p + "\t" + this.vocab.get(ww) + "\n"; + //System.out.print(tmp); + out.append(tmp); + if(ii > numWords) { + break; + } + } + } + return out.toString(); + } + + /** + * Prints likelihood and iter time. + */ + public void printStats (File file) throws IOException { + PrintStream out = new PrintStream (file); + String tmp = "Iteration\t\tlikelihood\titer_time\n"; + out.print(tmp); + for (int iter = 0; iter < this.lhood.size(); iter++) { + tmp = iter + "\t" + this.lhood.get(iter) + "\t" + this.iterTime.get(iter); + out.println(tmp); + } + out.close(); + } + + public void loadVocab(String vocabFile) { + + try { + FileInputStream infstream = new FileInputStream(vocabFile); + DataInputStream in = new DataInputStream(infstream); + BufferedReader br = new BufferedReader(new InputStreamReader(in)); + + String strLine; + //Read File Line By Line + while ((strLine = br.readLine()) != null) { + strLine = strLine.trim(); + String[] str = strLine.split("\t"); + if (str.length > 1) { + this.vocab.add(str[1]); + } else { + System.out.println("Error! " + strLine); + } + } + in.close(); + + } catch (IOException e) { + System.out.println("No vocab file Found!"); + } + + } + + /** + * Load constraints + */ + public void loadConstraints(String consFile) { + try { + FileInputStream infstream = new FileInputStream(consFile); + DataInputStream in = new DataInputStream(infstream); + BufferedReader br = new BufferedReader(new InputStreamReader(in)); + + String strLine; + //Read File Line By Line + while ((strLine = br.readLine()) != null) { + strLine = strLine.trim(); + String[] str = strLine.split("\t"); + if (str.length > 1) { + // str[0] is either "MERGE_" or "SPLIT_", not a word + for(int ii = 1; ii < str.length; ii++) { + int word = this.vocab.indexOf(str[ii]); + myAssert(word >= 0, "Constraint words not found in vocab: " + str[ii]); + cons.add(word); + } + this.vocab.add(str[1]); + } else { + System.out.println("Error! " + strLine); + } + } + in.close(); + + } catch (IOException e) { + System.out.println("No vocab file Found!"); + } + + } + + /** + * For testing~~ + */ + public static void myAssert(boolean flag, String info) { + if(!flag) { + System.out.println(info); + System.exit(0); + } + } + + abstract void addInstances(InstanceList training); + abstract void resumeStates(InstanceList training, String statesFile) throws IOException; + abstract void clearTopicAssignments(String option, String consFile); + abstract void changeTopic(int doc, int index, int word, int new_topic, int new_path); + abstract double docLHood(); + abstract void printDocumentTopics (File file) throws IOException; + abstract void sampleDoc(int doc); +} diff --git a/src/cc/mallet/topics/tree/TopicTreeWalk.java b/src/cc/mallet/topics/tree/TopicTreeWalk.java new file mode 100755 index 000000000..ac2fd5fd5 --- /dev/null +++ b/src/cc/mallet/topics/tree/TopicTreeWalk.java @@ -0,0 +1,85 @@ +package cc.mallet.topics.tree; + +import java.io.Serializable; + +import gnu.trove.TIntArrayList; +import gnu.trove.TIntHashSet; +import gnu.trove.TIntIntHashMap; + +/** + * This class counts each node and each edge for a topic with tree structure. + * + * @author Yuening Hu + */ + +public class TopicTreeWalk implements Serializable { + + // *** To be sorted + HIntIntIntHashMap counts; + TIntIntHashMap nodeCounts; + + public TopicTreeWalk() { + this.counts = new HIntIntIntHashMap(); + this.nodeCounts = new TIntIntHashMap(); + } + + /** + * Given a path (a list of nodes), increase the nodes and edges counts by + * the specified amount. When a node count is changed from zero or changed + * to zero, return this node. (When this happens, the non-zero path of this + * node might need to be changed, that's why we need this list.) + */ + public int[] changeCount(TIntArrayList path_nodes, int increment) { + for (int nn = 0; nn < path_nodes.size()-1; nn++) { + int parent = path_nodes.get(nn); + int child = path_nodes.get(nn+1); + this.counts.adjustOrPutValue(parent, child, increment, increment); + } + + // keep the nodes whose counts is changed from zero or changed to zero + TIntHashSet affected_nodes = new TIntHashSet(); + + for (int nn = 0; nn < path_nodes.size(); nn++) { + int node = path_nodes.get(nn); + if (! this.nodeCounts.contains(node)) { + this.nodeCounts.put(node, 0); + } + + int old_count = this.nodeCounts.get(node); + this.nodeCounts.adjustValue(node, increment); + int new_count = this.nodeCounts.get(node); + + // keep the nodes whose counts is changed from zero or changed to zero + if (nn != 0 && (old_count == 0 || new_count == 0)) { + affected_nodes.add(node); + } + } + + if (affected_nodes.size() > 0) { + return affected_nodes.toArray(); + } else { + return null; + } + } + + /** + * Return an edge count. + */ + public int getCount(int key1, int key2) { + if (this.counts.contains(key1, key2)) { + return this.counts.get(key1, key2); + } + return 0; + } + + /** + * Return a node count. + */ + public int getNodeCount(int key) { + if (this.nodeCounts.contains(key)) { + return this.nodeCounts.get(key); + } + return 0; + } + +} diff --git a/src/cc/mallet/topics/tree/TreeMarginalProbEstimator.java b/src/cc/mallet/topics/tree/TreeMarginalProbEstimator.java new file mode 100644 index 000000000..f8ee3b059 --- /dev/null +++ b/src/cc/mallet/topics/tree/TreeMarginalProbEstimator.java @@ -0,0 +1,380 @@ +package cc.mallet.topics.tree; + +import gnu.trove.TIntIntHashMap; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.PrintStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashSet; + +import cc.mallet.types.FeatureSequence; +import cc.mallet.types.Instance; +import cc.mallet.types.InstanceList; +import cc.mallet.util.Randoms; + + +/** + * An implementation of left-to-right algorithm for tree-based topic model marginal probability estimators + * presented in Wallach et al., "Evaluation Methods for Topic Models", ICML (2009) + * Followed the example in "cc.mallet.topics.MarginalProbEstimator" by David Mimno + * + * @author Yuening Hu + */ + +public class TreeMarginalProbEstimator implements Serializable { + int TOPIC_BITS = TreeTopicModelFastSortW.TOPIC_BITS; + + int numTopics; + double[] alpha; + double alphasum; + ArrayList vocab; + HashSet removed; + TreeTopicModel topics; + String modelType; + + Randoms random; + boolean sorted; + + public TreeMarginalProbEstimator(TreeTopicModel topics, ArrayList vocab, HashSet removed, double[] alpha) { + this.numTopics = topics.numTopics; + this.vocab = vocab; + this.removed = removed; + this.alpha = alpha; + this.topics = topics; + this.random = new Randoms(); + + this.alphasum = 0.0; + for(int tt = 0; tt < numTopics; tt++) { + this.alphasum += this.alpha[tt]; + } + + if (this.topics.nonZeroPathsBubbleSorted.size() > 0) { + this.sorted = true; + } else if (this.topics.nonZeroPaths.size() > 0) { + this.sorted = false; + } + //System.out.println(this.sorted); + } + + public void setRandomSeed(int seed) { + this.random = new Randoms(seed); + } + + public void setModelType(String modeltype) { + this.modelType = modeltype; + } + + + public double evaluateLeftToRight (InstanceList testing, int numParticles, boolean usingResampling, + PrintStream docProbabilityStream) { + + if(this.modelType.indexOf("fast-est") < 0) { + System.out.println("%%%%%%%%%%%%%%%%%%%"); + System.out.println("Your current tree-model-type"); + System.out.println("\t " + this.modelType); + System.out.println("is not supported by inferencer. "); + System.out.println("Inferencer only supports the following tree-model-type: "); + System.out.println("\t fast-est \n\t fast-est-sortW \n\t fast-est-sortD \n\t fast-est-sortD-sortW"); + System.out.println("%%%%%%%%%%%%%%%%%%%"); + return -1; + } + + double logNumParticles = Math.log(numParticles); + double totalLogLikelihood = 0; + for (Instance instance : testing) { + + FeatureSequence tokenSequence = (FeatureSequence) instance.getData(); + + // read in type index in vocab (different from the alphabet) + // remove tokens not in vocab + ArrayList tokens = new ArrayList (); + for (int position = 0; position < tokenSequence.size(); position++) { + String word = (String) tokenSequence.getObjectAtPosition(position); + if(this.vocab.indexOf(word) >= 0 && !this.removed.contains(word)) { + int type = this.vocab.indexOf(word); + tokens.add(type); + } + } + + double docLogLikelihood = 0; + + double[][] particleProbabilities = new double[ numParticles ][]; + for (int particle = 0; particle < numParticles; particle++) { + particleProbabilities[particle] = + leftToRight(tokens, usingResampling); + } + + for (int position = 0; position < particleProbabilities[0].length; position++) { + double sum = 0; + for (int particle = 0; particle < numParticles; particle++) { + sum += particleProbabilities[particle][position]; + } + + if (sum > 0.0) { + docLogLikelihood += Math.log(sum) - logNumParticles; + } + } + + if (docProbabilityStream != null) { + docProbabilityStream.println(docLogLikelihood); + } + totalLogLikelihood += docLogLikelihood; + } + + return totalLogLikelihood; + } + + protected double[] leftToRight (ArrayList tokens, boolean usingResampling) { + + int docLength = tokens.size(); + double[] wordProbabilities = new double[docLength]; + + int[] localtopics = new int[docLength]; + int[] localpaths = new int[docLength]; + TIntIntHashMap localTopicCounts = new TIntIntHashMap(); + + int tokensSoFar = 0; + int type; + for (int limit = 0; limit < docLength; limit++) { + if (usingResampling) { + + // Iterate up to the current limit + for (int position = 0; position < limit; position++) { + type = tokens.get(position); + + // change topic counts + int old_topic = localtopics[position]; + localtopics[position] = -1; + localpaths[position] = -1; + localTopicCounts.adjustValue(old_topic, -1); + + double smoothing_mass_est = this.topics.smoothingEst.get(type); + + double topic_beta_mass = this.topics.computeTermTopicBeta(localTopicCounts, type); + + ArrayList topic_term_score = new ArrayList(); + double topic_term_mass = this.topics.computeTopicTerm(this.alpha, localTopicCounts, type, topic_term_score); + + double norm_est = smoothing_mass_est + topic_beta_mass + topic_term_mass; + double sample = this.random.nextDouble(); + //double sample = 0.5; + sample *= norm_est; + + int new_topic = -1; + int new_path = -1; + + int[] paths = this.topics.getWordPathIndexSet(type); + + // sample the smoothing bin + if (sample < smoothing_mass_est) { + double smoothing_mass = this.topics.computeTermSmoothing(this.alpha, type); + double norm = smoothing_mass + topic_beta_mass + topic_term_mass; + sample /= norm_est; + sample *= norm; + if (sample < smoothing_mass) { + for (int tt = 0; tt < this.numTopics; tt++) { + for (int pp : paths) { + double val = alpha[tt] * this.topics.getPathPrior(type, pp); + val /= this.topics.getNormalizer(tt, pp); + sample -= val; + if (sample <= 0.0) { + new_topic = tt; + new_path = pp; + break; + } + } + if (new_topic >= 0) { + break; + } + } + } else { + sample -= smoothing_mass; + } + } else { + sample -= smoothing_mass_est; + } + + // sample topic beta bin + if (new_topic < 0 && sample < topic_beta_mass) { + for(int tt : localTopicCounts.keys()) { + for (int pp : paths) { + double val = localTopicCounts.get(tt) * this.topics.getPathPrior(type, pp); + val /= this.topics.getNormalizer(tt, pp); + sample -= val; + if (sample <= 0.0) { + new_topic = tt; + new_path = pp; + break; + } + } + if (new_topic >= 0) { + break; + } + } + } else { + sample -= topic_beta_mass; + } + + // sample topic term bin + if (new_topic < 0) { + for(int jj = 0; jj < topic_term_score.size(); jj++) { + double[] tmp = topic_term_score.get(jj); + int tt = (int) tmp[0]; + int pp = (int) tmp[1]; + double val = tmp[2]; + sample -= val; + if (sample <= 0.0) { + new_topic = tt; + new_path = pp; + break; + } + } + } + + // change topic counts + localtopics[position] = new_topic; + localpaths[position] = new_path; + localTopicCounts.adjustOrPutValue(new_topic, 1, 1); + } + } + + // sample current token at the current limit + type = tokens.get(limit); + + //double smoothing_mass_est = this.topics.smoothingEst.get(type); + double smoothing_mass = this.topics.computeTermSmoothing(this.alpha, type); + + double topic_beta_mass = this.topics.computeTermTopicBeta(localTopicCounts, type); + + ArrayList topic_term_score = new ArrayList(); + double topic_term_mass = this.topics.computeTopicTerm(this.alpha, localTopicCounts, type, topic_term_score); + + //double norm_est = smoothing_mass_est + topic_beta_mass + topic_term_mass; + double norm = smoothing_mass + topic_beta_mass + topic_term_mass; + double sample = this.random.nextDouble(); + sample *= norm; + + wordProbabilities[limit] += (smoothing_mass + topic_beta_mass + topic_term_mass) / + (this.alphasum + tokensSoFar); + + tokensSoFar++; + + int new_topic = -1; + int new_path = -1; + int[] paths = this.topics.getWordPathIndexSet(type); + + // sample the smoothing bin + if (sample < smoothing_mass) { + for (int tt = 0; tt < this.numTopics; tt++) { + for (int pp : paths) { + double val = alpha[tt] * this.topics.getPathPrior(type, pp); + val /= this.topics.getNormalizer(tt, pp); + sample -= val; + if (sample <= 0.0) { + new_topic = tt; + new_path = pp; + break; + } + } + if (new_topic >= 0) { + break; + } + } + } else { + sample -= smoothing_mass; + } + + // sample topic beta bin + if (new_topic < 0 && sample < topic_beta_mass) { + for(int tt : localTopicCounts.keys()) { + for (int pp : paths) { + double val = localTopicCounts.get(tt) * this.topics.getPathPrior(type, pp); + val /= this.topics.getNormalizer(tt, pp); + sample -= val; + if (sample <= 0.0) { + new_topic = tt; + new_path = pp; + break; + } + } + if (new_topic >= 0) { + break; + } + } + } else { + sample -= topic_beta_mass; + } + + // sample topic term bin + if (new_topic < 0) { + for(int jj = 0; jj < topic_term_score.size(); jj++) { + double[] tmp = topic_term_score.get(jj); + int tt = (int) tmp[0]; + int pp = (int) tmp[1]; + double val = tmp[2]; + sample -= val; + if (sample <= 0.0) { + new_topic = tt; + new_path = pp; + break; + } + } + } + + // change topic counts + localtopics[limit] = new_topic; + localpaths[limit] = new_path; + localTopicCounts.adjustOrPutValue(new_topic, 1, 1); + } + + return wordProbabilities; + } + + + // for serialize + private static final long serialVersionUID = 1L; + private static final int CURRENT_SERIAL_VERSION = 0; + private static final int NULL_INTEGER = -1; + + private void writeObject (ObjectOutputStream out) throws IOException { + out.writeInt (CURRENT_SERIAL_VERSION); + out.writeInt(this.numTopics); + out.writeInt(this.TOPIC_BITS); + out.writeBoolean(this.sorted); + out.writeObject(this.modelType); + out.writeObject(this.alpha); + out.writeDouble(this.alphasum); + out.writeObject(this.vocab); + out.writeObject(this.removed); + out.writeObject(this.topics); + } + + private void readObject (ObjectInputStream in) throws IOException, ClassNotFoundException { + int version = in.readInt(); + this.numTopics = in.readInt(); + this.TOPIC_BITS = in.readInt(); + this.sorted = in.readBoolean(); + this.modelType = (String) in.readObject(); + this.alpha = (double[]) in.readObject(); + this.alphasum = in.readDouble(); + this.vocab = (ArrayList) in.readObject(); + this.removed = (HashSet) in.readObject(); + this.topics = (TreeTopicModel) in.readObject(); + } + + public static TreeMarginalProbEstimator read (File f) throws Exception { + + TreeMarginalProbEstimator estimator = null; + + ObjectInputStream ois = new ObjectInputStream (new FileInputStream(f)); + estimator = (TreeMarginalProbEstimator) ois.readObject(); + ois.close(); + return estimator; + } + +} diff --git a/src/cc/mallet/topics/tree/TreeTopicInferencer.java b/src/cc/mallet/topics/tree/TreeTopicInferencer.java new file mode 100755 index 000000000..5b6cde552 --- /dev/null +++ b/src/cc/mallet/topics/tree/TreeTopicInferencer.java @@ -0,0 +1,384 @@ +package cc.mallet.topics.tree; + +import gnu.trove.TIntArrayList; +import gnu.trove.TIntIntHashMap; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.PrintWriter; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; + +import cc.mallet.topics.TopicInferencer; +import cc.mallet.types.Alphabet; +import cc.mallet.types.FeatureSequence; +import cc.mallet.types.IDSorter; +import cc.mallet.types.Instance; +import cc.mallet.types.InstanceList; +import cc.mallet.util.Randoms; + + +/** + * An implementation of inferencer for tree-based topic model + * Followed the example in "cc.mallet.topics.TopicInferencer" + * + * @author Yuening Hu + */ + +public class TreeTopicInferencer implements Serializable { + + int TOPIC_BITS = TreeTopicModelFastSortW.TOPIC_BITS; + + int numTopics; + double[] alpha; + ArrayList vocab; + HashSet removed; + TreeTopicModel topics; + String modelType; + + Randoms random; + boolean sorted; + + public TreeTopicInferencer(TreeTopicModel topics, ArrayList vocab, HashSet removed, double[] alpha) { + this.numTopics = topics.numTopics; + this.vocab = vocab; + this.removed = removed; + this.alpha = alpha; + this.topics = topics; + this.random = new Randoms(); + + if (this.topics.nonZeroPathsBubbleSorted.size() > 0) { + this.sorted = true; + } else if (this.topics.nonZeroPaths.size() > 0) { + this.sorted = false; + } + //System.out.println(this.sorted); + } + + public void setRandomSeed(int seed) { + this.random = new Randoms(seed); + } + + public void setModelType(String modeltype) { + this.modelType = modeltype; + } + + public String toString() { + String tmp = ""; + tmp += "numTopics: " + numTopics + "\n"; + tmp += "sorted: " + this.sorted + "\n"; + tmp += "removed: " + this.removed.size() + "\n"; + tmp += "nonzero: " + this.topics.nonZeroPaths.size() + "\n"; + tmp += "nonzerobubblesorted: " + this.topics.nonZeroPathsBubbleSorted.size() + "\n"; + return tmp; + } + + /** + * Use Gibbs sampling to infer a topic distribution. + * Topics are initialized to the (or a) most probable topic + * for each token. + */ + public double[] getSampledDistribution(Instance instance, int numIterations, int interval) { + + FeatureSequence alltokens = (FeatureSequence) instance.getData(); + ArrayList tokens = new ArrayList (); + for (int position = 0; position < alltokens.size(); position++) { + String word = (String) alltokens.getObjectAtPosition(position); + if(this.vocab.indexOf(word) >= 0 && !this.removed.contains(word)) { + int type = this.vocab.indexOf(word); + tokens.add(type); + } + } + + int docLength = tokens.size(); + int[] localtopics = new int[docLength]; + int[] localpaths = new int[docLength]; + TIntIntHashMap localTopicCounts = new TIntIntHashMap(); + + // Initialize all positions to the most common topic for that type. + for (int position = 0; position < docLength; position++) { + int type = tokens.get(position); + + int tt = -1; + int pp = -1; + + if (this.sorted) { + ArrayList pairs = this.topics.nonZeroPathsBubbleSorted.get(type); + int[] pair = pairs.get(0); + int key = pair[0]; + tt = key >> TOPIC_BITS; + pp = key - (tt << TOPIC_BITS); + } else { + HIntIntIntHashMap pairs1 = this.topics.nonZeroPaths.get(type); + int maxcount = 0; + for(int topic : pairs1.getKey1Set()) { + int[] paths = pairs1.get(topic).keys(); + for (int jj = 0; jj < paths.length; jj++) { + int path = paths[jj]; + int count = pairs1.get(topic, path); + if (count > maxcount) { + maxcount = count; + tt = topic; + pp = path; + } + } + } + } + + localtopics[position] = tt; + localpaths[position] = pp; + localTopicCounts.adjustOrPutValue(tt, 1, 1); + } + +// String tmpout = ""; +// for(int tt : localTopicCounts.keys()) { +// tmpout += tt + " " + localTopicCounts.get(tt) + "; "; +// } +// System.out.println(tmpout); + + double[] result = new double[numTopics]; + double sum = 0.0; + + for (int iteration = 1; iteration <= numIterations; iteration++) { + for (int position = 0; position < docLength; position++) { + int type = tokens.get(position); + + // change topic counts + int old_topic = localtopics[position]; + localtopics[position] = -1; + localpaths[position] = -1; + localTopicCounts.adjustValue(old_topic, -1); + + double smoothing_mass_est = this.topics.smoothingEst.get(type); + + double topic_beta_mass = this.topics.computeTermTopicBeta(localTopicCounts, type); + + ArrayList topic_term_score = new ArrayList(); + double topic_term_mass = this.topics.computeTopicTerm(this.alpha, localTopicCounts, type, topic_term_score); + + double norm_est = smoothing_mass_est + topic_beta_mass + topic_term_mass; + double sample = this.random.nextDouble(); + //double sample = 0.5; + sample *= norm_est; + + int new_topic = -1; + int new_path = -1; + + int[] paths = this.topics.getWordPathIndexSet(type); + + // sample the smoothing bin + if (sample < smoothing_mass_est) { + double smoothing_mass = this.topics.computeTermSmoothing(this.alpha, type); + double norm = smoothing_mass + topic_beta_mass + topic_term_mass; + sample /= norm_est; + sample *= norm; + if (sample < smoothing_mass) { + for (int tt = 0; tt < this.numTopics; tt++) { + for (int pp : paths) { + double val = alpha[tt] * this.topics.getPathPrior(type, pp); + val /= this.topics.getNormalizer(tt, pp); + sample -= val; + if (sample <= 0.0) { + new_topic = tt; + new_path = pp; + break; + } + } + if (new_topic >= 0) { + break; + } + } + } else { + sample -= smoothing_mass; + } + } else { + sample -= smoothing_mass_est; + } + + // sample topic beta bin + if (new_topic < 0 && sample < topic_beta_mass) { + for(int tt : localTopicCounts.keys()) { + for (int pp : paths) { + double val = localTopicCounts.get(tt) * this.topics.getPathPrior(type, pp); + val /= this.topics.getNormalizer(tt, pp); + sample -= val; + if (sample <= 0.0) { + new_topic = tt; + new_path = pp; + break; + } + } + if (new_topic >= 0) { + break; + } + } + } else { + sample -= topic_beta_mass; + } + + // sample topic term bin + if (new_topic < 0) { + for(int jj = 0; jj < topic_term_score.size(); jj++) { + double[] tmp = topic_term_score.get(jj); + int tt = (int) tmp[0]; + int pp = (int) tmp[1]; + double val = tmp[2]; + sample -= val; + if (sample <= 0.0) { + new_topic = tt; + new_path = pp; + break; + } + } + } + + // change topic counts + localtopics[position] = new_topic; + localpaths[position] = new_path; + localTopicCounts.adjustOrPutValue(new_topic, 1, 1); + +// if (iteration % interval == 0) { +// // Save a sample +// for (int topic=0; topic < numTopics; topic++) { +// if (localTopicCounts.containsKey(topic)) { +// result[topic] = alpha[topic] + localTopicCounts.get(topic); +// } else { +// result[topic] = alpha[topic]; +// } +// sum += result[topic]; +// } +// } + } + } + + + // save at least once + if (sum == 0.0) { + for (int topic=0; topic < numTopics; topic++) { + if (localTopicCounts.containsKey(topic)) { + result[topic] = alpha[topic] + localTopicCounts.get(topic); + } else { + result[topic] = alpha[topic]; + } + sum += result[topic]; + } + } + + // Normalize + for (int topic=0; topic < numTopics; topic++) { + result[topic] /= sum; + } + + return result; + } + + /** + * Infer topics for the provided instances and + * write distributions to the provided file. + * + * @param instances + * @param distributionsFile + * @param numIterations The total number of iterations of sampling per document + * @param interval The number of iterations between saved samples + */ + public void writeInferredDistributions(InstanceList instances, + File distributionsFile, + int numIterations, int interval) throws IOException { + + if(this.modelType.indexOf("fast-est") < 0) { + System.out.println("%%%%%%%%%%%%%%%%%%%"); + System.out.println("Your current tree-model-type"); + System.out.println("\t " + this.modelType); + System.out.println("is not supported by inferencer. "); + System.out.println("Inferencer only supports the following tree-model-type: "); + System.out.println("\t fast-est \n\t fast-est-sortW \n\t fast-est-sortD \n\t fast-est-sortD-sortW"); + System.out.println("%%%%%%%%%%%%%%%%%%%"); + return; + } + + PrintWriter out = new PrintWriter(distributionsFile); + + out.print ("#doc source topic proportion ...\n"); + + IDSorter[] sortedTopics = new IDSorter[ numTopics ]; + for (int topic = 0; topic < numTopics; topic++) { + // Initialize the sorters with dummy values + sortedTopics[topic] = new IDSorter(topic, topic); + } + + int doc = 0; + + for (Instance instance: instances) { + + double[] topicDistribution = + getSampledDistribution(instance, numIterations, interval); + out.print (doc); out.print (' '); + + // Print the Source field of the instance + if (instance.getSource() != null) { + out.print (instance.getSource()); + } else { + out.print ("null-source"); + } + out.print (' '); + + for (int topic = 0; topic < numTopics; topic++) { + sortedTopics[topic].set(topic, topicDistribution[topic]); + } + Arrays.sort(sortedTopics); + + for (int i = 0; i < numTopics; i++) { + out.print (sortedTopics[i].getID() + " " + + sortedTopics[i].getWeight() + " "); + } + out.print (" \n"); + doc++; + } + out.close(); + } + + + // for serialize + private static final long serialVersionUID = 1L; + private static final int CURRENT_SERIAL_VERSION = 0; + private static final int NULL_INTEGER = -1; + + private void writeObject (ObjectOutputStream out) throws IOException { + out.writeInt (CURRENT_SERIAL_VERSION); + out.writeInt(this.numTopics); + out.writeInt(this.TOPIC_BITS); + out.writeBoolean(this.sorted); + out.writeObject(this.modelType); + out.writeObject(this.alpha); + out.writeObject(this.vocab); + out.writeObject(this.removed); + out.writeObject(this.topics); + } + + private void readObject (ObjectInputStream in) throws IOException, ClassNotFoundException { + int version = in.readInt(); + this.numTopics = in.readInt(); + this.TOPIC_BITS = in.readInt(); + this.sorted = in.readBoolean(); + this.modelType = (String) in.readObject(); + this.alpha = (double[]) in.readObject(); + this.vocab = (ArrayList) in.readObject(); + this.removed = (HashSet) in.readObject(); + this.topics = (TreeTopicModel) in.readObject(); + } + + public static TreeTopicInferencer read (File f) throws Exception { + + TreeTopicInferencer inferencer = null; + + ObjectInputStream ois = new ObjectInputStream (new FileInputStream(f)); + inferencer = (TreeTopicInferencer) ois.readObject(); + ois.close(); + return inferencer; + } +} diff --git a/src/cc/mallet/topics/tree/TreeTopicModel.java b/src/cc/mallet/topics/tree/TreeTopicModel.java new file mode 100755 index 000000000..cae9ef3a2 --- /dev/null +++ b/src/cc/mallet/topics/tree/TreeTopicModel.java @@ -0,0 +1,382 @@ +package cc.mallet.topics.tree; + +import gnu.trove.TDoubleArrayList; +import gnu.trove.TIntArrayList; +import gnu.trove.TIntDoubleHashMap; +import gnu.trove.TIntDoubleIterator; +import gnu.trove.TIntHashSet; +import gnu.trove.TIntIntHashMap; +import gnu.trove.TIntObjectHashMap; +import gnu.trove.TIntObjectIterator; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Random; +import java.util.TreeMap; + +import cc.mallet.types.Dirichlet; + +/** + * This class defines the tree topic model. + * It implements most of the functions and leave four abstract methods, + * which might be various for different models. + * + * @author Yuening Hu + */ + +public abstract class TreeTopicModel implements Serializable { + + int numTopics; + Random random; + int maxDepth; + int root; + HIntIntObjectHashMap wordPaths; + TIntArrayList pathToWord; + TIntArrayList pathToWordPath; + TIntObjectHashMap nodeToPath; + + TIntDoubleHashMap betaSum; + HIntIntDoubleHashMap beta; // 2 levels hash map + TIntDoubleHashMap priorSum; + HIntIntDoubleHashMap priorPath; + + TIntObjectHashMap nonZeroPaths; + TIntObjectHashMap> nonZeroPathsBubbleSorted; + TIntObjectHashMap traversals; + + HIntIntDoubleHashMap normalizer; + TIntDoubleHashMap rootNormalizer; + TIntDoubleHashMap smoothingEst; + + /*********************************************/ + double smoothingNocons; // for unconstrained words + double topicbetaNocons; // for unconstrained words + double betaNocons; // for unconstrained words + /*********************************************/ + + public TreeTopicModel(int numTopics, Random random) { + this.numTopics = numTopics; + this.random = random; + + this.betaSum = new TIntDoubleHashMap (); + this.beta = new HIntIntDoubleHashMap (); + this.priorSum = new TIntDoubleHashMap (); + this.priorPath = new HIntIntDoubleHashMap (); + + this.wordPaths = new HIntIntObjectHashMap (); + this.pathToWord = new TIntArrayList (); + this.pathToWordPath = new TIntArrayList(); + this.nodeToPath = new TIntObjectHashMap (); + + this.nonZeroPaths = new TIntObjectHashMap (); + this.nonZeroPathsBubbleSorted = new TIntObjectHashMap> (); + this.traversals = new TIntObjectHashMap (); + + this.smoothingNocons = 0.0; + this.topicbetaNocons = 0.0; + } + + /** + * Initialize the parameters, including: + * (1) loading the tree + * (2) initialize betaSum and beta + * (3) initialize priorSum, priorPath + * (4) initialize wordPaths, pathToWord, NodetoPath + * (5) initialize traversals + * (6) initialize nonZeroPaths + */ + protected void initializeParams(String treeFiles, String hyperFile, ArrayList vocab) { + + PriorTree tree = new PriorTree(); + tree.initialize(treeFiles, hyperFile, vocab); + + // get tree depth + this.maxDepth = tree.getMaxDepth(); + // get root index + this.root = tree.getRoot(); + // get tree nodes + TIntObjectHashMap nodes = tree.getNodes(); + // get tree paths + TIntObjectHashMap> word_paths = tree.getWordPaths(); + + // if one node contains multiple words, we need to change each word to a leaf node + // (assigning a leaf index for each word). + int leaf_index = nodes.size(); + HIntIntIntHashMap tmp_wordleaf = new HIntIntIntHashMap(); + + // initialize betaSum and beta + for (TIntObjectIterator it = nodes.iterator(); it.hasNext(); ) { + it.advance(); + int index = it.key(); + Node node = it.value(); + TDoubleArrayList transition_prior = node.getTransitionPrior(); + + // when node has children + if (node.getNumChildren() > 0) { + //assert node.getNumWords() == 0; + this.betaSum.put(index, node.getTransitionScalor()); + for (int ii = 0; ii < node.getNumChildren(); ii++) { + int child = node.getChild(ii); + this.beta.put(index, child, transition_prior.get(ii)); + } + } + + // when node contains multiple words. + // we change a node containing multiple words to a node containing multiple + // leaf node and each leaf node containing one word + if (node.getNumWords() > 1) { + //assert node.getNumChildren() == 0; + this.betaSum.put(index, node.getTransitionScalor()); + for (int ii = 0; ii < node.getNumWords(); ii++) { + int word = node.getWord(ii); + leaf_index++; + this.beta.put(index, leaf_index, transition_prior.get(ii)); + + // one word might have multiple paths, + // so we keep the (word_index, word_parent) + // as the index for this leaf index, which is needed later + tmp_wordleaf.put(word, index, leaf_index); + } + } + } + + /*********************************************/ + // find beta for unconstrained words + Node rootnode = nodes.get(this.root); + for (int ii = 0; ii < rootnode.getNumChildren(); ii++) { + int child = rootnode.getChild(ii); + Node childnode = nodes.get(child); + double tmpbeta = this.beta.get(this.root, child); + //System.out.println("beta for root to " + child + ": " + tmpbeta); + if (childnode.getHypoCount() == 1.0) { + this.betaNocons = this.beta.get(this.root, child); + System.out.println("beta for unconstrained words from root to " + child + ": " + tmpbeta); + break; + } + } + /*********************************************/ + + // initialize priorSum, priorPath + // initialize wordPaths, pathToWord, NodetoPath + int path_index = -1; + TIntObjectHashMap tmp_nodeToPath = new TIntObjectHashMap(); + for (TIntObjectIterator> it = word_paths.iterator(); it.hasNext(); ) { + it.advance(); + + int word = it.key(); + ArrayList paths = it.value(); + this.priorSum.put(word, 0.0); + + int word_path_index = -1; + for (int ii = 0; ii < paths.size(); ii++) { + path_index++; + word_path_index++; + this.pathToWord.add(word); + this.pathToWordPath.add(word_path_index); + + double prob = 1.0; + Path p = paths.get(ii); + TIntArrayList path_nodes = p.getNodes(); + + // for a node that contains multiple words + // if yes, retrieve the leaf index for each word + // and that to nodes of path + int parent = path_nodes.get(path_nodes.size()-1); + if (tmp_wordleaf.contains(word, parent)) { + leaf_index = tmp_wordleaf.get(word, parent); + path_nodes.add(leaf_index); + } + + for (int nn = 0; nn < path_nodes.size() - 1; nn++) { + parent = path_nodes.get(nn); + int child = path_nodes.get(nn+1); + prob *= this.beta.get(parent, child); + } + + for (int nn = 0; nn < path_nodes.size(); nn++) { + int node = path_nodes.get(nn); + if (! tmp_nodeToPath.contains(node)) { + tmp_nodeToPath.put(node, new TIntHashSet()); + } + tmp_nodeToPath.get(node).add(path_index); + //tmp_nodeToPath.get(node).add(word_path_index); + } + + this.priorPath.put(word, path_index, prob); + this.priorSum.adjustValue(word, prob); + this.wordPaths.put(word, path_index, path_nodes); + } + } + + // change tmp_nodeToPath to this.nodeToPath + // this is because arraylist is much more efficient than hashset, when we + // need to go over the whole set multiple times + for(TIntObjectIterator it = tmp_nodeToPath.iterator(); it.hasNext(); ) { + it.advance(); + int node = it.key(); + TIntHashSet paths = (TIntHashSet)it.value(); + TIntArrayList tmp = new TIntArrayList(paths.toArray()); + +// System.out.println("Node" + node); +// for(int ii = 0; ii < tmp.size(); ii++) { +// System.out.print(tmp.get(ii) + " "); +// } +// System.out.println(""); + + this.nodeToPath.put(node, tmp); + } + + // initialize traversals + for (int tt = 0; tt < this.numTopics; tt++) { + TopicTreeWalk tw = new TopicTreeWalk(); + this.traversals.put(tt, tw); + } + + // initialize nonZeroPaths + int[] words = this.wordPaths.getKey1Set(); + for (int ww = 0; ww < words.length; ww++) { + int word = words[ww]; + this.nonZeroPaths.put(word, new HIntIntIntHashMap()); + } + } + + /** + * This function samples a path based on the prior + * and change the node and edge count for a topic. + */ + protected int initialize (int word, int topic) { + double sample = this.random.nextDouble(); + int path_index = this.samplePathFromPrior(word, sample); + this.changeCountOnly(topic, word, path_index, 1); + return path_index; + } + + /** + * This function changes the node and edge count for a topic. + */ + protected void changeCountOnly(int topic, int word, int path, int delta) { + TIntArrayList path_nodes = this.wordPaths.get(word, path); + TopicTreeWalk tw = this.traversals.get(topic); + tw.changeCount(path_nodes, delta); + } + + /** + * This function samples a path from the prior. + */ + protected int samplePathFromPrior(int term, double sample) { + int sampled_path = -1; + sample *= this.priorSum.get(term); + TIntDoubleHashMap paths = this.priorPath.get(term); + for(TIntDoubleIterator it = paths.iterator(); it.hasNext(); ) { + it.advance(); + sample -= it.value(); + if (sample <= 0.0) { + sampled_path = it.key(); + break; + } + } + + return sampled_path; + } + + /** + * This function computes a path probability in a topic. + */ + public double computeTopicPathProb(int topic, int word, int path_index) { + TIntArrayList path_nodes = this.wordPaths.get(word, path_index); + TopicTreeWalk tw = this.traversals.get(topic); + double val = 1.0; + for(int ii = 0; ii < path_nodes.size()-1; ii++) { + int parent = path_nodes.get(ii); + int child = path_nodes.get(ii+1); + val *= this.beta.get(parent, child) + tw.getCount(parent, child); + val /= this.betaSum.get(parent) + tw.getNodeCount(parent); + } + return val; + } + + /** + * This function computes the topic likelihood (by node). + */ + public double topicLHood() { + double val = 0.0; + for (int tt = 0; tt < this.numTopics; tt++) { + for (int nn : this.betaSum.keys()) { + double beta_sum = this.betaSum.get(nn); + //val += Dirichlet.logGamma(beta_sum) * this.beta.get(nn).size(); + val += Dirichlet.logGamma(beta_sum); + + double tmp = 0.0; + for (int cc : this.beta.get(nn).keys()) { + tmp += Dirichlet.logGamma(this.beta.get(nn, cc)); + } + //val -= tmp * this.beta.get(nn).size(); + val -= tmp; + + for (int cc : this.beta.get(nn).keys()) { + int count = this.traversals.get(tt).getCount(nn, cc); + val += Dirichlet.logGamma(this.beta.get(nn, cc) + count); + } + + int count = this.traversals.get(tt).getNodeCount(nn); + val -= Dirichlet.logGamma(beta_sum + count); + } + //System.out.println("likelihood " + val); + } + return val; + } + + public TIntObjectHashMap getPaths(int word) { + return this.wordPaths.get(word); + } + + public int[] getWordPathIndexSet(int word) { + return this.wordPaths.get(word).keys(); + } + + public int getPathNum() { + return this.pathToWord.size(); + } + + public int getWordFromPath(int pp) { + return this.pathToWord.get(pp); + } + + public double getPathPrior(int word, int path) { + return this.priorPath.get(word, path); + } + + // for TreeTopicSamplerFast + public double computeTermSmoothing(double[] alpha, int word) { + return 0; + } + + public double computeTermTopicBeta(TIntIntHashMap topic_counts, int word) { + return 0; + } + + public double computeTopicTermTest(double[] alpha, TIntIntHashMap local_topic_counts, int word, ArrayList dict){ + return 0; + } + + public double computeTermTopicBetaSortD(ArrayList topicCounts, int word) { + return 0; + } + + public double computeTopicTermSortD(double[] alpha, ArrayList local_topic_counts, int word, ArrayList dict){ + return 0; + } + + /*********************************************/ + public void computeSmoothingNocons(double[] alpha) {} + public void computeDocTopicBetaNocons(TIntIntHashMap topic_counts) {} + public void updateStatisticsNocons(double alpha, int topic, int topicCount, int delta){} + /*********************************************/ + + // shared methods + abstract double getNormalizer(int topic, int path); + abstract void updateParams(); + abstract void changeCount(int topic, int word, int path_index, int delta); + abstract double computeTopicTerm(double[] alpha, TIntIntHashMap local_topic_counts, int word, ArrayList dict); + +} diff --git a/src/cc/mallet/topics/tree/TreeTopicModelFast.java b/src/cc/mallet/topics/tree/TreeTopicModelFast.java new file mode 100755 index 000000000..26a6fbf9f --- /dev/null +++ b/src/cc/mallet/topics/tree/TreeTopicModelFast.java @@ -0,0 +1,388 @@ +package cc.mallet.topics.tree; + +import gnu.trove.TIntArrayList; +import gnu.trove.TIntDoubleHashMap; +import gnu.trove.TIntHashSet; +import gnu.trove.TIntIntHashMap; +import gnu.trove.TIntIterator; +import gnu.trove.TIntObjectIterator; + +import java.util.ArrayList; +import java.util.Random; + +/** + * This class extends the tree topic model + * It implemented the four abstract methods in a faster way: + * (1) normalizer is stored and updated accordingly + * (2) normalizer is split to two parts: root normalizer and normalizer to save computation + * (3) non-zero-paths are stored so when we compute the topic term score, we only compute + * the non-zero paths + * + * @author Yuening Hu + */ + +public class TreeTopicModelFast extends TreeTopicModel { + + int INTBITS = 31; + // use at most 10 bits to denote the mask + int MASKMAXBITS = 10; + + /** + * The normalizer is split to two parts: root normalizer and normalizer + * root normalizer is stored per topic, and normalizer is stored per path per topic + * both normalizers are updated when the count is changing. + */ + public TreeTopicModelFast(int numTopics, Random random) { + super(numTopics, random); + this.normalizer = new HIntIntDoubleHashMap (); + //this.normalizer = new HIntIntObjectHashMap (); + this.rootNormalizer = new TIntDoubleHashMap (); + } + + /** + * This function updates the real count with the path masked count. + * The format is: using the first Tree_depth number of bits of an integer + * to denote whether a node in path has count larger than zero, + * and plus the real count. + * If a node path is shorter than Tree_depth, use "1" to fill the remained part. + */ + protected void updatePathMaskedCount(int path, int topic) { + TopicTreeWalk tw = this.traversals.get(topic); + int ww = this.getWordFromPath(path); + TIntArrayList path_nodes = this.wordPaths.get(ww, path); + int leaf_node = path_nodes.get(path_nodes.size() - 1); + int original_count = tw.getNodeCount(leaf_node); + + int shift_count = this.INTBITS; + int count = this.maxDepth - 1; + if (count > this.MASKMAXBITS) count = this.MASKMAXBITS; + int val = 0; + boolean flag = false; + + // note root is not included here + // if count of a node in the path is larger than 0, denote as "1" + // else use "0" + // if path_nodes.size() > MASKMAXBITS, denote the first MASKMAXBITS-1 edges as usual + // then use the last bit to denote the sum of the remaining edges + int remain_sum = 0; + for(int nn = 1; nn < path_nodes.size(); nn++) { + int node = path_nodes.get(nn); + if (nn < (this.MASKMAXBITS - 1)) { + count--; + shift_count--; + if (tw.getNodeCount(node) > 0) { + flag = true; + val += 1 << shift_count; + } + } else { + if (tw.getNodeCount(node) > 0) + remain_sum += 1; + } + } + + // use the last bit to denote the sum of the remaining edges + if (remain_sum > 0) { + count--; + shift_count--; + flag = true; + val += 1 << shift_count; + } + + // if a path is shorter than tree depth, fill in "1" + // should we fit in "0" ??? + while (flag && count > 0) { + shift_count--; + val += 1 << shift_count; + count--; + } + + int maskedpath = val; + // plus the original count + val += original_count; + if (val > 0) { + this.nonZeroPaths.get(ww).put(topic, path, val); + } else if (val == 0) { + if (this.nonZeroPaths.get(ww).get(topic) != null) { + this.nonZeroPaths.get(ww).removeKey2(topic, path); + if (this.nonZeroPaths.get(ww).get(topic).size() == 0) { + this.nonZeroPaths.get(ww).removeKey1(topic); + } + } + } + +// int shift = this.INTBITS - this.maxDepth - 1; +// int testmaskedpath = val >> shift; +// maskedpath = maskedpath >> shift; +// int testcount = val - (testmaskedpath << shift); +// System.out.println(maskedpath + " " + testmaskedpath + " " + original_count + " " + testcount); + + //System.out.println(original_count + " " + this.nonZeroPaths.get(ww).get(topic, path)); + } + + /** + * Compute the root normalizer and the normalizer per topic per path + */ + protected void computeNormalizer(int topic) { + TopicTreeWalk tw = this.traversals.get(topic); + double val = this.betaSum.get(root) + tw.getNodeCount(root); + this.rootNormalizer.put(topic, val); + //System.out.println("Topic " + topic + " root normalizer " + this.rootNormalizer.get(topic)); + + for(int pp = 0; pp < this.getPathNum(); pp++) { + int ww = this.getWordFromPath(pp); + val = this.computeNormalizerPath(topic, ww, pp); + this.normalizer.put(topic, pp, val); + //System.out.println("Topic " + topic + " Path " + pp + " normalizer " + this.normalizer.get(topic, pp)); + } + } + + /** + * Compute the the normalizer given a path and a topic. + */ + private double computeNormalizerPath(int topic, int word, int path) { + TopicTreeWalk tw = this.traversals.get(topic); + TIntArrayList path_nodes = this.wordPaths.get(word, path); + + double norm = 1.0; + // do not consider the root + for (int nn = 1; nn < path_nodes.size() - 1; nn++) { + int node = path_nodes.get(nn); + norm *= this.betaSum.get(node) + tw.getNodeCount(node); + } + return norm; + } + + /** + * Compute the root normalizer and the normalizer per topic per path. + */ + protected int[] findAffectedPaths(int[] nodes) { + TIntHashSet affected = new TIntHashSet(); + for(int ii = 0; ii < nodes.length; ii++) { + int node = nodes[ii]; + TIntArrayList paths = this.nodeToPath.get(node); + for (int jj = 0; jj < paths.size(); jj++) { + int pp = paths.get(jj); + affected.add(pp); + } + } + return affected.toArray(); + } + + /** + * Updates a list of paths with the given amount. + */ + protected void updateNormalizer(int topic, TIntArrayList paths, double delta) { + for (int ii = 0; ii < paths.size(); ii++) { + int pp = paths.get(ii); + double val = this.normalizer.get(topic, pp); + val *= delta; + this.normalizer.put(topic, pp, val); + } + } + + /** + * Computes the observation part. + */ + protected double getObservation(int topic, int word, int path_index) { + TIntArrayList path_nodes = this.wordPaths.get(word, path_index); + TopicTreeWalk tw = this.traversals.get(topic); + double val = 1.0; + for(int ii = 0; ii < path_nodes.size()-1; ii++) { + int parent = path_nodes.get(ii); + int child = path_nodes.get(ii+1); + val *= this.beta.get(parent, child) + tw.getCount(parent, child); + } + val -= this.priorPath.get(word, path_index); + return val; + } + + /** + * After adding instances, update the parameters. + */ + public void updateParams() { + for(int tt = 0; tt < this.numTopics; tt++) { + for(int pp = 0; pp < this.getPathNum(); pp++) { + this.updatePathMaskedCount(pp, tt); + } + this.computeNormalizer(tt); + } + } + + /** + * This function updates the count given the topic and path of a word. + */ + public void changeCount(int topic, int word, int path_index, int delta) { + + TopicTreeWalk tw = this.traversals.get(topic); + TIntArrayList path_nodes = this.wordPaths.get(word, path_index); + + // for affected paths, firstly remove the old values + // do not consider the root + for(int nn = 1; nn < path_nodes.size() - 1; nn++) { + int node = path_nodes.get(nn); + double tmp = this.betaSum.get(node) + tw.getNodeCount(node); + tmp = 1 / tmp; + TIntArrayList paths = this.nodeToPath.get(node); + updateNormalizer(topic, paths, tmp); + } + + // change the count for each edge per topic + // return the node index whose count is changed from 0 or to 0 + int[] affected_nodes = tw.changeCount(path_nodes, delta); + // change path count + if (delta > 0) { + this.nonZeroPaths.get(word).adjustOrPutValue(topic, path_index, delta, delta); + } else { + this.nonZeroPaths.get(word).adjustValue(topic, path_index, delta); + } + + // if necessary, change the path mask of the affected nodes + if (affected_nodes != null && affected_nodes.length > 0) { + int[] affected_paths = this.findAffectedPaths(affected_nodes); + for(int ii = 0; ii < affected_paths.length; ii++) { + this.updatePathMaskedCount(affected_paths[ii], topic); + } + } + + // for affected paths, update the normalizer + for(int nn = 1; nn < path_nodes.size() - 1; nn++) { + int node = path_nodes.get(nn); + double tmp = this.betaSum.get(node) + tw.getNodeCount(node); + TIntArrayList paths = this.nodeToPath.get(node); + updateNormalizer(topic, paths, tmp); + } + + // update the root normalizer + double val = this.betaSum.get(root) + tw.getNodeCount(root); + this.rootNormalizer.put(topic, val); + } + + /** + * This function returns the real normalizer. + */ + public double getNormalizer(int topic, int path) { + return this.normalizer.get(topic, path) * this.rootNormalizer.get(topic); + } + + /** + * This function computes the smoothing bucket for a word. + */ + public double computeTermSmoothing(double[] alpha, int word) { + double smoothing = 0.0; + int[] paths = this.getWordPathIndexSet(word); + + for(int tt = 0; tt < this.numTopics; tt++) { + for(int pp : paths) { + double val = alpha[tt] * this.getPathPrior(word, pp); + val /= this.getNormalizer(tt, pp); + smoothing += val; + } + } + //myAssert(smoothing > 0, "something wrong with smoothing!"); + return smoothing; + } + + /** + * This function computes the topic beta bucket. + */ + public double computeTermTopicBeta(TIntIntHashMap topic_counts, int word) { + double topic_beta = 0.0; + int[] paths = this.getWordPathIndexSet(word); + for(int tt : topic_counts.keys()) { + if (topic_counts.get(tt) > 0 ) { + for (int pp : paths) { + double val = topic_counts.get(tt) * this.getPathPrior(word, pp); + val /= this.getNormalizer(tt, pp); + topic_beta += val; + } + } + } + //myAssert(topic_beta > 0, "something wrong with topic_beta!"); + return topic_beta; + } + + /** + * This function computes the topic beta bucket. + */ + public double computeTermTopicBetaSortD(ArrayList topic_counts, int word) { + double topic_beta = 0.0; + int[] paths = this.getWordPathIndexSet(word); + for(int ii = 0; ii < topic_counts.size(); ii++) { + int[] current = topic_counts.get(ii); + int tt = current[0]; + int count = current[1]; + if (count > 0 ) { + for (int pp : paths) { + double val = count * this.getPathPrior(word, pp); + val /= this.getNormalizer(tt, pp); + topic_beta += val; + } + } + } + //myAssert(topic_beta > 0, "something wrong with topic_beta!"); + return topic_beta; + } + + /** + * This function computes the topic term bucket. + */ + public double computeTopicTerm(double[] alpha, TIntIntHashMap local_topic_counts, int word, ArrayList dict) { + double norm = 0.0; + HIntIntIntHashMap nonzeros = this.nonZeroPaths.get(word); + + // Notice only the nonzero paths are considered + //for(int tt = 0; tt < this.numTopics; tt++) { + for(int tt : nonzeros.getKey1Set()) { + double topic_alpha = alpha[tt]; + int topic_count = local_topic_counts.get(tt); + int[] paths = nonzeros.get(tt).keys(); + for (int pp = 0; pp < paths.length; pp++) { + int path = paths[pp]; + double val = this.getObservation(tt, word, path); + val *= (topic_alpha + topic_count); + val /= this.getNormalizer(tt, path); + double[] tmp = {tt, path, val}; + dict.add(tmp); + norm += val; + } + } + return norm; + } + + public double computeTopicTermSortD(double[] alpha, ArrayList local_topic_counts, int word, ArrayList dict) { + double norm = 0.0; + HIntIntIntHashMap nonzeros = this.nonZeroPaths.get(word); + + int[] tmpTopics = new int[this.numTopics]; + for(int jj = 0; jj < this.numTopics; jj++) { + tmpTopics[jj] = 0; + } + for(int jj = 0; jj < local_topic_counts.size(); jj++) { + int[] current = local_topic_counts.get(jj); + int tt = current[0]; + tmpTopics[tt] = current[1]; + } + + // Notice only the nonzero paths are considered + //for(int tt = 0; tt < this.numTopics; tt++) { + for(int tt : nonzeros.getKey1Set()) { + double topic_alpha = alpha[tt]; + int topic_count = tmpTopics[tt]; + //local_topic_counts.get(ii); + int[] paths = nonzeros.get(tt).keys(); + for (int pp = 0; pp < paths.length; pp++) { + int path = paths[pp]; + double val = this.getObservation(tt, word, path); + val *= (topic_alpha + topic_count); + val /= this.getNormalizer(tt, path); + double[] tmp = {tt, path, val}; + dict.add(tmp); + norm += val; + } + } + return norm; + } + ////////////////////////////////////////////////////////// + + +} diff --git a/src/cc/mallet/topics/tree/TreeTopicModelFastEst.java b/src/cc/mallet/topics/tree/TreeTopicModelFastEst.java new file mode 100755 index 000000000..b90bfe971 --- /dev/null +++ b/src/cc/mallet/topics/tree/TreeTopicModelFastEst.java @@ -0,0 +1,44 @@ +package cc.mallet.topics.tree; + +import gnu.trove.TIntArrayList; +import gnu.trove.TIntDoubleHashMap; + +import java.util.Random; + +/** + * This class extends the tree topic model fast class + * Only add one more function, it computes the smoothing for each word + * only based on the prior (treat the real count as zero), so it + * serves as the upper bound of smoothing. + * + * @author Yuening Hu + */ + +public class TreeTopicModelFastEst extends TreeTopicModelFast { + public TreeTopicModelFastEst(int numTopics, Random random) { + super(numTopics, random); + this.smoothingEst = new TIntDoubleHashMap(); + } + + /** + * This function computes the upper bound of smoothing bucket. + */ + public void computeSmoothingEst(double[] alpha) { + for(int ww : this.wordPaths.getKey1Set()) { + this.smoothingEst.put(ww, 0.0); + for(int tt = 0; tt < this.numTopics; tt++) { + for(int pp : this.wordPaths.get(ww).keys()) { + TIntArrayList path_nodes = this.wordPaths.get(ww, pp); + double prob = 1.0; + for(int nn = 0; nn < path_nodes.size() - 1; nn++) { + int parent = path_nodes.get(nn); + int child = path_nodes.get(nn+1); + prob *= this.beta.get(parent, child) / this.betaSum.get(parent); + } + prob *= alpha[tt]; + this.smoothingEst.adjustValue(ww, prob); + } + } + } + } +} diff --git a/src/cc/mallet/topics/tree/TreeTopicModelFastEstSortW.java b/src/cc/mallet/topics/tree/TreeTopicModelFastEstSortW.java new file mode 100755 index 000000000..444e07348 --- /dev/null +++ b/src/cc/mallet/topics/tree/TreeTopicModelFastEstSortW.java @@ -0,0 +1,44 @@ +package cc.mallet.topics.tree; + +import gnu.trove.TIntArrayList; +import gnu.trove.TIntDoubleHashMap; + +import java.util.Random; + +/** + * This class extends the tree topic model fast class + * Only add one more function, it computes the smoothing for each word + * only based on the prior (treat the real count as zero), so it + * serves as the upper bound of smoothing. + * + * @author Yuening Hu + */ + +public class TreeTopicModelFastEstSortW extends TreeTopicModelFastSortW { + public TreeTopicModelFastEstSortW(int numTopics, Random random) { + super(numTopics, random); + this.smoothingEst = new TIntDoubleHashMap(); + } + + /** + * This function computes the upper bound of smoothing bucket. + */ + public void computeSmoothingEst(double[] alpha) { + for(int ww : this.wordPaths.getKey1Set()) { + this.smoothingEst.put(ww, 0.0); + for(int tt = 0; tt < this.numTopics; tt++) { + for(int pp : this.wordPaths.get(ww).keys()) { + TIntArrayList path_nodes = this.wordPaths.get(ww, pp); + double prob = 1.0; + for(int nn = 0; nn < path_nodes.size() - 1; nn++) { + int parent = path_nodes.get(nn); + int child = path_nodes.get(nn+1); + prob *= this.beta.get(parent, child) / this.betaSum.get(parent); + } + prob *= alpha[tt]; + this.smoothingEst.adjustValue(ww, prob); + } + } + } + } +} diff --git a/src/cc/mallet/topics/tree/TreeTopicModelFastSortW.java b/src/cc/mallet/topics/tree/TreeTopicModelFastSortW.java new file mode 100755 index 000000000..c1658a317 --- /dev/null +++ b/src/cc/mallet/topics/tree/TreeTopicModelFastSortW.java @@ -0,0 +1,297 @@ +package cc.mallet.topics.tree; + +import gnu.trove.TIntArrayList; +import gnu.trove.TIntIntHashMap; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +/** + * nonZeroPathsBubbleSorted: Arraylist sorted + * sorted[0] = (topic << TOPIC_BITS) + path + * sorted[1] = (masked_path) + real_count + * + * @author Yuening Hu + */ + +public class TreeTopicModelFastSortW extends TreeTopicModelFast { + + static int TOPIC_BITS = 16; + + public TreeTopicModelFastSortW(int numTopics, Random random) { + super(numTopics, random); + } + + /** + * After adding instances, update the parameters. + */ + public void updateParams() { + + for(int ww : this.nonZeroPaths.keys()) { + if (!this.nonZeroPathsBubbleSorted.containsKey(ww)) { + ArrayList sorted = new ArrayList (); + this.nonZeroPathsBubbleSorted.put(ww, sorted); + } + } + for(int tt = 0; tt < this.numTopics; tt++) { + for(int pp = 0; pp < this.getPathNum(); pp++) { + this.updatePathMaskedCount(pp, tt); + } + this.computeNormalizer(tt); + } + +// for(int ww : this.nonZeroPaths.keys()) { +// System.out.println("Word " + ww); +// ArrayList sorted = this.nonZeroPathsBubbleSorted.get(ww); +// for(int ii = 0; ii < sorted.size(); ii++) { +// int[] tmp = sorted.get(ii); +// System.out.println(tmp[0] + " " + tmp[1] + " " + tmp[2] + " " + tmp[3]); +// } +// } + } + + protected void updatePathMaskedCount(int path, int topic) { + TopicTreeWalk tw = this.traversals.get(topic); + int ww = this.getWordFromPath(path); + TIntArrayList path_nodes = this.wordPaths.get(ww, path); + int leaf_node = path_nodes.get(path_nodes.size() - 1); + int original_count = tw.getNodeCount(leaf_node); + + int shift_count = this.INTBITS; + int count = this.maxDepth - 1; + int val = 0; + boolean flag = false; + + // note root is not included here + // if count of a node in the path is larger than 0, denote as "1" + // else use "0" + for(int nn = 1; nn < path_nodes.size(); nn++) { + int node = path_nodes.get(nn); + shift_count--; + count--; + if (tw.getNodeCount(node) > 0) { + flag = true; + val += 1 << shift_count; + } + } + + // if a path is shorter than tree depth, fill in "1" + // should we fit in "0" ??? + while (flag && count > 0) { + shift_count--; + val += 1 << shift_count; + count--; + } + + val += original_count; + this.addOrUpdateValue(topic, path, ww, val, false); + + } + + private void addOrUpdateValueold(int topic, int path, int word, int newvalue, boolean flag) { + ArrayList sorted = this.nonZeroPathsBubbleSorted.get(word); + int key = (topic << TOPIC_BITS) + path; + //remove the old value + int oldindex = sorted.size(); + int oldvalue = 0; + for(int ii = 0; ii < sorted.size(); ii++) { + int[] tmp = sorted.get(ii); + if(tmp[0] == key) { + oldvalue = tmp[1]; + sorted.remove(ii); + break; + } + } + if(oldindex > sorted.size()) { + oldindex--; + } + + // flag is true, increase value, else just update value + int value = 0; + if(flag) { + value = oldvalue + newvalue; + } else { + value = newvalue; + } + + //add the new value + if (value > 0) { + int index; + if (value > oldvalue) { + index = 0; + for(int ii = oldindex - 1; ii >= 0; ii--) { + //System.out.println(ii + " " + oldindex + " " + sorted.size()); + int[] tmp = sorted.get(ii); + if(value <= tmp[1]) { + index = ii; + break; + } + } + } else { + index = sorted.size(); + for(int ii = oldindex; ii < sorted.size(); ii++) { + int[] tmp = sorted.get(ii); + if(value >= tmp[1]) { + index = ii; + break; + } + } + } + + int[] newpair = {key, value}; + sorted.add(index, newpair); + } + } + + private void addOrUpdateValue(int topic, int path, int word, int newvalue, boolean flag) { + ArrayList sorted = this.nonZeroPathsBubbleSorted.get(word); + int key = (topic << TOPIC_BITS) + path; + //remove the old value + int value = 0; + for(int ii = 0; ii < sorted.size(); ii++) { + int[] tmp = sorted.get(ii); + if(tmp[0] == key) { + value = tmp[1]; + sorted.remove(ii); + break; + } + } + + // flag is true, increase value, else just update value + if(flag) { + value += newvalue; + } else { + value = newvalue; + } + + //add the new value + if (value > 0) { + int index = sorted.size(); + for(int ii = 0; ii < sorted.size(); ii++) { + int[] tmp = sorted.get(ii); + if(value >= tmp[1]) { + index = ii; + break; + } + } + int[] newpair = {key, value}; + sorted.add(index, newpair); + } + } + + public void changeCount(int topic, int word, int path_index, int delta) { + TopicTreeWalk tw = this.traversals.get(topic); + TIntArrayList path_nodes = this.wordPaths.get(word, path_index); + + // for affected paths, firstly remove the old values + // do not consider the root + for(int nn = 1; nn < path_nodes.size() - 1; nn++) { + int node = path_nodes.get(nn); + double tmp = this.betaSum.get(node) + tw.getNodeCount(node); + tmp = 1 / tmp; + TIntArrayList paths = this.nodeToPath.get(node); + updateNormalizer(topic, paths, tmp); + } + + // change the count for each edge per topic + // return the node index whose count is changed from 0 or to 0 + int[] affected_nodes = tw.changeCount(path_nodes, delta); + + // change path count + this.addOrUpdateValue(topic, path_index, word, delta, true); + + // if necessary, change the path mask of the affected nodes + if (affected_nodes != null && affected_nodes.length > 0) { + int[] affected_paths = this.findAffectedPaths(affected_nodes); + for(int ii = 0; ii < affected_paths.length; ii++) { + this.updatePathMaskedCount(affected_paths[ii], topic); + } + } + + // for affected paths, update the normalizer + for(int nn = 1; nn < path_nodes.size() - 1; nn++) { + int node = path_nodes.get(nn); + double tmp = this.betaSum.get(node) + tw.getNodeCount(node); + TIntArrayList paths = this.nodeToPath.get(node); + updateNormalizer(topic, paths, tmp); + } + + // update the root normalizer + double val = this.betaSum.get(root) + tw.getNodeCount(root); + this.rootNormalizer.put(topic, val); + } + + /** + * This function computes the topic term bucket. + */ + public double computeTopicTerm(double[] alpha, TIntIntHashMap local_topic_counts, int word, ArrayList dict) { + double norm = 0.0; + ArrayList nonzeros = this.nonZeroPathsBubbleSorted.get(word); + + // Notice only the nonzero paths are considered + for(int ii = 0; ii < nonzeros.size(); ii++) { + int[] tmp = nonzeros.get(ii); + int key = tmp[0]; + int tt = key >> TOPIC_BITS; + int pp = key - (tt << TOPIC_BITS); + + double topic_alpha = alpha[tt]; + int topic_count = local_topic_counts.get(tt); + + double val = this.getObservation(tt, word, pp); + val *= (topic_alpha + topic_count); + val /= this.getNormalizer(tt, pp); + + //System.out.println(tt + " " + pp + " " + tmp[2] + " " + val); + + double[] result = {tt, pp, val}; + dict.add(result); + + norm += val; + } + + return norm; + } + + public double computeTopicTermSortD(double[] alpha, ArrayList local_topic_counts, int word, ArrayList dict) { + double norm = 0.0; + ArrayList nonzeros = this.nonZeroPathsBubbleSorted.get(word); + + + int[] tmpTopics = new int[this.numTopics]; + for(int jj = 0; jj < this.numTopics; jj++) { + tmpTopics[jj] = 0; + } + for(int jj = 0; jj < local_topic_counts.size(); jj++) { + int[] current = local_topic_counts.get(jj); + int tt = current[0]; + tmpTopics[tt] = current[1]; + } + + // Notice only the nonzero paths are considered + for(int ii = 0; ii < nonzeros.size(); ii++) { + int[] tmp = nonzeros.get(ii); + int key = tmp[0]; + int tt = key >> TOPIC_BITS; + int pp = key - (tt << TOPIC_BITS); + + double topic_alpha = alpha[tt]; + int topic_count = tmpTopics[tt]; + + double val = this.getObservation(tt, word, pp); + val *= (topic_alpha + topic_count); + val /= this.getNormalizer(tt, pp); + + //System.out.println(tt + " " + pp + " " + tmp[2] + " " + val); + + double[] result = {tt, pp, val}; + dict.add(result); + + norm += val; + } + return norm; + } +} diff --git a/src/cc/mallet/topics/tree/TreeTopicModelNaive.java b/src/cc/mallet/topics/tree/TreeTopicModelNaive.java new file mode 100755 index 000000000..c2df2380d --- /dev/null +++ b/src/cc/mallet/topics/tree/TreeTopicModelNaive.java @@ -0,0 +1,76 @@ +package cc.mallet.topics.tree; + +import java.util.ArrayList; +import java.util.Random; + +import cc.mallet.types.Dirichlet; + +import gnu.trove.TDoubleArrayList; +import gnu.trove.TIntArrayList; +import gnu.trove.TIntDoubleHashMap; +import gnu.trove.TIntDoubleIterator; +import gnu.trove.TIntHashSet; +import gnu.trove.TIntIntHashMap; +import gnu.trove.TIntObjectHashMap; +import gnu.trove.TIntObjectIterator; + +/** + * This class extends the tree topic model + * It implemented the four abstract methods in a naive way: given a word, + * (1) compute the probability for each topic every time directly + * + * @author Yuening Hu + */ + +public class TreeTopicModelNaive extends TreeTopicModel{ + + public TreeTopicModelNaive(int numTopics, Random random) { + super(numTopics, random); + } + + /** + * Just calls changeCountOnly(), nothing else. + */ + public void changeCount(int topic, int word, int path, int delta) { +// TIntArrayList path_nodes = this.wordPaths.get(word, path_index); +// TopicTreeWalk tw = this.traversals.get(topic); +// tw.changeCount(path_nodes, delta); + this.changeCountOnly(topic, word, path, delta); + } + + /** + * Given a word and the topic counts in the current document, + * this function computes the probability per path per topic directly + * according to the sampleing equation. + */ + public double computeTopicTerm(double[] alpha, TIntIntHashMap local_topic_counts, int word, ArrayList dict) { + double norm = 0.0; + int[] paths = this.getWordPathIndexSet(word); + for(int tt = 0; tt < this.numTopics; tt++) { + double topic_alpha = alpha[tt]; + int topic_count = local_topic_counts.get(tt); + for (int pp = 0; pp < paths.length; pp++) { + int path_index = paths[pp]; + double val = this.computeTopicPathProb(tt, word, path_index); + val *= (topic_alpha + topic_count); + double[] tmp = {tt, path_index, val}; + dict.add(tmp); + norm += val; + } + } + return norm; + } + + /** + * No parameter needs to be updated. + */ + public void updateParams() { + } + + /** + * Not actually used. + */ + public double getNormalizer(int topic, int path) { + return 0; + } +} diff --git a/src/cc/mallet/topics/tree/TreeTopicSampler.java b/src/cc/mallet/topics/tree/TreeTopicSampler.java new file mode 100755 index 000000000..638502d8d --- /dev/null +++ b/src/cc/mallet/topics/tree/TreeTopicSampler.java @@ -0,0 +1,300 @@ +package cc.mallet.topics.tree; + +import gnu.trove.TDoubleArrayList; +import gnu.trove.TIntHashSet; + +import java.io.BufferedReader; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; + +import cc.mallet.topics.TopicInferencer; +import cc.mallet.topics.tree.TreeTopicSamplerSortD.DocData; +import cc.mallet.types.InstanceList; +import cc.mallet.util.Randoms; + + +/** + * This class defines the tree topic sampler. + * Defines the basic functions for input, output, resume. + * Also defines the abstract functions for child class. + * + * @author Yuening Hu + */ + +public abstract class TreeTopicSampler { + + int numTopics; + int numIterations; + int startIter; + Randoms random; + double[] alpha; + double alphaSum; + TDoubleArrayList lhood; + TDoubleArrayList iterTime; + ArrayList vocab; + ArrayList removedWords; + ArrayList removedWordsNew; + TIntHashSet cons; + HashMap topickeep; + + public TreeTopicSampler (int numberOfTopics, double alphaSum, int seed) { + this.numTopics = numberOfTopics; + this.random = new Randoms(seed); + + this.alphaSum = alphaSum; + this.alpha = new double[numTopics]; + Arrays.fill(alpha, alphaSum / numTopics); + + this.vocab = new ArrayList (); + this.removedWords = new ArrayList (); + this.removedWordsNew = new ArrayList (); + this.cons = new TIntHashSet(); + this.topickeep = new HashMap(); + + this.lhood = new TDoubleArrayList(); + this.iterTime = new TDoubleArrayList(); + this.startIter = 0; + } + + ///////////////////////////////////////////////////////////// + + public void setNumIterations(int iters) { + this.numIterations = iters; + } + + /** + * Resumes from the saved files. + */ + public void resume(InstanceList[] training, String resumeDir) { + try { + String statesFile = resumeDir + ".states"; + resumeStates(training, statesFile); + + String lhoodFile = resumeDir + ".lhood"; + resumeLHood(lhoodFile); + } catch (IOException e) { + System.out.println(e.getMessage()); + } + } + + ////////////////////////////////////////////////////////// + + public int getNumIterations() { + return this.numIterations; + } + + /** + * Resume lhood and iterTime from the saved lhood file. + */ + public void resumeLHood(String lhoodFile) throws IOException{ + FileInputStream lhoodfstream = new FileInputStream(lhoodFile); + DataInputStream lhooddstream = new DataInputStream(lhoodfstream); + BufferedReader brLHood = new BufferedReader(new InputStreamReader(lhooddstream)); + // the first line is the title + String strLine = brLHood.readLine(); + while ((strLine = brLHood.readLine()) != null) { + strLine = strLine.trim(); + String[] str = strLine.split("\t"); + // iteration, likelihood, iter_time + myAssert(str.length == 3, "lhood file problem!"); + this.lhood.add(Double.parseDouble(str[1])); + this.iterTime.add(Double.parseDouble(str[2])); + } + this.startIter = this.lhood.size(); + +// if (this.startIter > this.numIterations) { +// System.out.println("Have already sampled " + this.numIterations + " iterations!"); +// System.exit(0); +// } +// System.out.println("Start sampling for iteration " + this.startIter); + + brLHood.close(); + } + + /** + * This function prints the topic words of each topic. + */ + public void printTopWords(File file, int numWords) throws IOException { + PrintStream out = new PrintStream (file); + out.print(displayTopWords(numWords)); + out.close(); + } + + /** + * Prints likelihood and iter time. + */ + public void printStats (File file) throws IOException { + PrintStream out = new PrintStream (file); + String tmp = "Iteration\t\tlikelihood\titer_time\n"; + out.print(tmp); + + for (int iter = 0; iter < this.lhood.size(); iter++) { + tmp = iter + "\t" + this.lhood.get(iter) + "\t" + this.iterTime.get(iter); + out.println(tmp); + } + out.close(); + } + + /** + * This function reports the detected topics, the documents topics, + * and saves states file and lhood file. + */ + public void report(String outputDir, int topWords) throws IOException { + + String topicKeysFile = outputDir + ".topics"; + this.printTopWords(new File(topicKeysFile), topWords); + + String docTopicsFile = outputDir + ".docs"; + this.printDocumentTopics(new File(docTopicsFile)); + + String stateFile = outputDir + ".states"; + this.printState (new File(stateFile)); + + String statsFile = outputDir + ".lhood"; + this.printStats (new File(statsFile)); + + String topicWordsFile = outputDir + ".topic-words"; + this.printTopicWords(new File(topicWordsFile)); + } + + public void loadVocab(String vocabFile) { + + try { + FileInputStream infstream = new FileInputStream(vocabFile); + DataInputStream in = new DataInputStream(infstream); + BufferedReader br = new BufferedReader(new InputStreamReader(in)); + + String strLine; + //Read File Line By Line + while ((strLine = br.readLine()) != null) { + strLine = strLine.trim(); + String[] str = strLine.split("\t"); + if (str.length > 1) { + this.vocab.add(str[1]); + } else { + System.out.println("Vocab file error at line: " + strLine); + } + } + in.close(); + + } catch (IOException e) { + System.out.println("No vocab file Found!"); + } + + } + + /** + * Load StopWords + */ + public void loadRemovedWords(String removedwordFile, ArrayList removed) { + + try { + + FileInputStream infstream = new FileInputStream(removedwordFile); + DataInputStream in = new DataInputStream(infstream); + BufferedReader br = new BufferedReader(new InputStreamReader(in)); + + String strLine; + //Read File Line By Line + while ((strLine = br.readLine()) != null) { + strLine = strLine.trim(); + removed.add(strLine); + } + in.close(); + + } catch (IOException e) { + System.out.println("No stop word file Found!"); + } + } + + /** + * Load constraints + */ + public void loadConstraints(String consFile) { + try { + FileInputStream infstream = new FileInputStream(consFile); + DataInputStream in = new DataInputStream(infstream); + BufferedReader br = new BufferedReader(new InputStreamReader(in)); + + String strLine; + //Read File Line By Line + while ((strLine = br.readLine()) != null) { + strLine = strLine.trim(); + String[] str = strLine.split("\t"); + if (str.length > 1) { + // str[0] is either "MERGE_" or "SPLIT_", not a word + for(int ii = 1; ii < str.length; ii++) { + int word = this.vocab.indexOf(str[ii]); + myAssert(word >= 0, "Constraint words not found in vocab: " + str[ii]); + cons.add(word); + } + this.vocab.add(str[1]); + } else { + System.out.println("Error! " + strLine); + } + } + in.close(); + + } catch (IOException e) { + System.out.println("No constraint file Found!"); + } + + } + + /** + * For words on this list, topic assignments will not be cleared. + */ + public void loadKeepList(String keepFile) { + try { + FileInputStream infstream = new FileInputStream(keepFile); + DataInputStream in = new DataInputStream(infstream); + BufferedReader br = new BufferedReader(new InputStreamReader(in)); + + String strLine; + //Read File Line By Line + while ((strLine = br.readLine()) != null) { + strLine = strLine.trim(); + String[] words = strLine.split(" "); + int word = this.vocab.indexOf(words[0]); + int topic = Integer.parseInt(words[1]); + if (!this.topickeep.containsKey(word)) { + this.topickeep.put(word, new TIntHashSet()); + } + TIntHashSet tmp = this.topickeep.get(word); + tmp.add(topic); + } + in.close(); + + } catch (IOException e) { + System.out.println("No keep file Found!"); + } + + } + + /** + * For testing~~ + */ + public static void myAssert(boolean flag, String info) { + if(!flag) { + System.out.println(info); + System.exit(0); + } + } + + abstract public String displayTopWords (int numWords); + abstract public void printState (File file) throws IOException; + abstract public void printTopicWords (File file) throws IOException; + abstract public void sampleDoc(int doc); + abstract public double docLHood(); + abstract public void printDocumentTopics (File file) throws IOException; + abstract public void resumeStates(InstanceList[] training, String statesFile) throws IOException; + abstract public TreeTopicInferencer getInferencer(); + abstract public TreeMarginalProbEstimator getProbEstimator(); +} diff --git a/src/cc/mallet/topics/tree/TreeTopicSamplerFast.java b/src/cc/mallet/topics/tree/TreeTopicSamplerFast.java new file mode 100755 index 000000000..6fb209da8 --- /dev/null +++ b/src/cc/mallet/topics/tree/TreeTopicSamplerFast.java @@ -0,0 +1,286 @@ +package cc.mallet.topics.tree; + +import gnu.trove.TDoubleArrayList; +import gnu.trove.TIntArrayList; +import gnu.trove.TIntIntHashMap; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; + +import cc.mallet.topics.tree.TreeTopicSamplerHashD.DocData; +import cc.mallet.util.Randoms; + +/** + * This class defines a fast tree topic sampler, which calls the fast tree topic model. + * (1) It divides the sampling into three bins: smoothing, topic beta, topic term. + * as Yao and Mimno's paper, KDD, 2009. + * (2) Each time the smoothing, topic beta, and topic term are recomputed. + * It is faster, because, + * (1) For topic term, only compute the one with non-zero paths (see TreeTopicModelFast). + * (2) The normalizer is saved. + * (3) Topic counts for each documents are ranked. + * + * @author Yuening Hu + */ + +public class TreeTopicSamplerFast extends TreeTopicSamplerHashD { + + public TreeTopicSamplerFast (int numberOfTopics, double alphaSum, int seed, boolean sort) { + super(numberOfTopics, alphaSum, seed); + + if (sort) { + this.topics = new TreeTopicModelFastSortW(this.numTopics, this.random); + //} else if (bubble == 1) { + // this.topics = new TreeTopicModelFastSortT1(this.numTopics, this.random); + //} else if (bubble == 2) { + // this.topics = new TreeTopicModelFastSortT2(this.numTopics, this.random); + } else { + this.topics = new TreeTopicModelFast(this.numTopics, this.random); + } + } + + /** + * For each word in a document, firstly covers its topic and path, then sample a + * topic and path, and update. + */ + public void sampleDoc(int doc_id){ + DocData doc = this.data.get(doc_id); + //System.out.println("doc " + doc_id); + + for(int ii = 0; ii < doc.tokens.size(); ii++) { + //int word = doc.tokens.getIndexAtPosition(ii); + int word = doc.tokens.get(ii); + + this.changeTopic(doc_id, ii, word, -1, -1); + + double smoothing_mass = this.topics.computeTermSmoothing(this.alpha, word); + double topic_beta_mass = this.topics.computeTermTopicBeta(doc.topicCounts, word); + + ArrayList topic_term_score = new ArrayList (); + double topic_term_mass = this.topics.computeTopicTerm(this.alpha, doc.topicCounts, word, topic_term_score); + + double norm = smoothing_mass + topic_beta_mass + topic_term_mass; + double sample = this.random.nextDouble(); + //double sample = 0.5; + sample *= norm; + + int new_topic = -1; + int new_path = -1; + + int[] paths = this.topics.getWordPathIndexSet(word); + + // sample the smoothing bin + if (sample < smoothing_mass) { + for (int tt = 0; tt < this.numTopics; tt++) { + for (int pp : paths) { + double val = alpha[tt] * this.topics.getPathPrior(word, pp); + val /= this.topics.getNormalizer(tt, pp); + sample -= val; + if (sample <= 0.0) { + new_topic = tt; + new_path = pp; + break; + } + } + if (new_topic >= 0) { + break; + } + } + myAssert((new_topic >= 0 && new_topic < numTopics), "something wrong in sampling smoothing!"); + } else { + sample -= smoothing_mass; + } + + // sample the topic beta bin + if (new_topic < 0 && sample < topic_beta_mass) { + for(int tt : doc.topicCounts.keys()) { + for (int pp : paths) { + double val = doc.topicCounts.get(tt) * this.topics.getPathPrior(word, pp); + val /= this.topics.getNormalizer(tt, pp); + sample -= val; + if (sample <= 0.0) { + new_topic = tt; + new_path = pp; + break; + } + } + if (new_topic >= 0) { + break; + } + } + myAssert((new_topic >= 0 && new_topic < numTopics), "something wrong in sampling topic beta!"); + } else { + sample -= topic_beta_mass; + } + + // sample the topic term bin + if (new_topic < 0) { + for(int jj = 0; jj < topic_term_score.size(); jj++) { + double[] tmp = topic_term_score.get(jj); + int tt = (int) tmp[0]; + int pp = (int) tmp[1]; + double val = tmp[2]; + sample -= val; + if (sample <= 0.0) { + new_topic = tt; + new_path = pp; + break; + } + } + myAssert((new_topic >= 0 && new_topic < numTopics), "something wrong in sampling topic term!"); + } + + this.changeTopic(doc_id, ii, word, new_topic, new_path); + } + } + + ///////////////////////////// + // The following methods are for testing only. + + public double callComputeTermTopicBeta(TIntIntHashMap topic_counts, int word) { + return this.topics.computeTermTopicBeta(topic_counts, word); + } + + public double callComputeTermSmoothing(int word) { + return this.topics.computeTermSmoothing(this.alpha, word); + } + + public double computeTopicSmoothTest(int word) { + double smooth = 0.0; + int[] paths = this.topics.getWordPathIndexSet(word); + for(int tt = 0; tt < this.numTopics; tt++) { + double topic_alpha = alpha[tt]; + for (int pp = 0; pp < paths.length; pp++) { + int path_index = paths[pp]; + + TIntArrayList path_nodes = this.topics.wordPaths.get(word, path_index); + TopicTreeWalk tw = this.topics.traversals.get(tt); + + double tmp = 1.0; + for(int ii = 0; ii < path_nodes.size()-1; ii++) { + int parent = path_nodes.get(ii); + int child = path_nodes.get(ii+1); + tmp *= this.topics.beta.get(parent, child); + tmp /= this.topics.betaSum.get(parent) + tw.getNodeCount(parent); + } + tmp *= topic_alpha; + smooth += tmp; + } + } + return smooth; + } + + public double computeTopicTermBetaTest(TIntIntHashMap local_topic_counts, int word) { + double topictermbeta = 0.0; + int[] paths = this.topics.getWordPathIndexSet(word); + for(int tt = 0; tt < this.numTopics; tt++) { + int topic_count = local_topic_counts.get(tt); + for (int pp = 0; pp < paths.length; pp++) { + int path_index = paths[pp]; + + TIntArrayList path_nodes = this.topics.wordPaths.get(word, path_index); + TopicTreeWalk tw = this.topics.traversals.get(tt); + + double tmp = 1.0; + for(int ii = 0; ii < path_nodes.size()-1; ii++) { + int parent = path_nodes.get(ii); + int child = path_nodes.get(ii+1); + tmp *= this.topics.beta.get(parent, child); + tmp /= this.topics.betaSum.get(parent) + tw.getNodeCount(parent); + } + tmp *= topic_count; + + topictermbeta += tmp; + } + } + return topictermbeta; + } + + public double computeTopicTermScoreTest(double[] alpha, TIntIntHashMap local_topic_counts, int word, HIntIntDoubleHashMap dict) { + double termscore = 0.0; + int[] paths = this.topics.getWordPathIndexSet(word); + for(int tt = 0; tt < this.numTopics; tt++) { + double topic_alpha = alpha[tt]; + int topic_count = local_topic_counts.get(tt); + for (int pp = 0; pp < paths.length; pp++) { + int path_index = paths[pp]; + + TIntArrayList path_nodes = this.topics.wordPaths.get(word, path_index); + TopicTreeWalk tw = this.topics.traversals.get(tt); + + double val = 1.0; + double tmp = 1.0; + double normalizer = 1.0; + for(int ii = 0; ii < path_nodes.size()-1; ii++) { + int parent = path_nodes.get(ii); + int child = path_nodes.get(ii+1); + val *= this.topics.beta.get(parent, child) + tw.getCount(parent, child); + tmp *= this.topics.beta.get(parent, child); + normalizer *= this.topics.betaSum.get(parent) + tw.getNodeCount(parent); + } + val -= tmp; + val *= (topic_alpha + topic_count); + val /= normalizer; + + dict.put(tt, path_index, val); + termscore += val; + } + } + return termscore; + } + + public double computeTopicTermTest(double[] alpha, TIntIntHashMap local_topic_counts, int word, ArrayList dict) { + double norm = 0.0; + int[] paths = this.topics.getWordPathIndexSet(word); + for(int tt = 0; tt < this.numTopics; tt++) { + double topic_alpha = alpha[tt]; + int topic_count = local_topic_counts.get(tt); + for (int pp = 0; pp < paths.length; pp++) { + int path_index = paths[pp]; + + TIntArrayList path_nodes = this.topics.wordPaths.get(word, path_index); + TopicTreeWalk tw = this.topics.traversals.get(tt); + + double smooth = 1.0; + for(int ii = 0; ii < path_nodes.size()-1; ii++) { + int parent = path_nodes.get(ii); + int child = path_nodes.get(ii+1); + smooth *= this.topics.beta.get(parent, child); + smooth /= this.topics.betaSum.get(parent) + tw.getNodeCount(parent); + } + smooth *= topic_alpha; + + double topicterm = 1.0; + for(int ii = 0; ii < path_nodes.size()-1; ii++) { + int parent = path_nodes.get(ii); + int child = path_nodes.get(ii+1); + topicterm *= this.topics.beta.get(parent, child); + topicterm /= this.topics.betaSum.get(parent) + tw.getNodeCount(parent); + } + topicterm *= topic_count; + + double termscore = 1.0; + double tmp = 1.0; + double normalizer = 1.0; + for(int ii = 0; ii < path_nodes.size()-1; ii++) { + int parent = path_nodes.get(ii); + int child = path_nodes.get(ii+1); + termscore *= this.topics.beta.get(parent, child) + tw.getCount(parent, child); + tmp *= this.topics.beta.get(parent, child); + normalizer *= this.topics.betaSum.get(parent) + tw.getNodeCount(parent); + } + termscore -= tmp; + termscore *= (topic_alpha + topic_count); + termscore /= normalizer; + + double val = smooth + topicterm + termscore; + double[] tmptmp = {tt, path_index, val}; + dict.add(tmptmp); + norm += val; + System.out.println("Fast Topic " + tt + " " + smooth + " " + topicterm + " " + termscore + " " + tmp + " " + topic_alpha + " " + topic_count + " " + termscore); + } + } + return norm; + } +} diff --git a/src/cc/mallet/topics/tree/TreeTopicSamplerFastEst.java b/src/cc/mallet/topics/tree/TreeTopicSamplerFastEst.java new file mode 100755 index 000000000..d0ee50c51 --- /dev/null +++ b/src/cc/mallet/topics/tree/TreeTopicSamplerFastEst.java @@ -0,0 +1,157 @@ +package cc.mallet.topics.tree; + +import java.util.ArrayList; + +import cc.mallet.topics.tree.TreeTopicSamplerHashD.DocData; +import gnu.trove.TIntArrayList; +import gnu.trove.TIntDoubleHashMap; + +/** + * This class improves the fast sampler based on estimation of smoothing. + * Most of the time, the smoothing is very small and not worth to recompute since + * it will hardly be hit. So we use an upper bound for smoothing. + * Only if the smoothing bin is hit, the actual smoothing is computed and resampled. + * + * @author Yuening Hu + */ + +public class TreeTopicSamplerFastEst extends TreeTopicSamplerHashD{ + + public TreeTopicSamplerFastEst (int numberOfTopics, double alphaSum, int seed, boolean sort) { + super(numberOfTopics, alphaSum, seed); + + if (sort) { + this.topics = new TreeTopicModelFastEstSortW(this.numTopics, this.random); + } else { + this.topics = new TreeTopicModelFastEst(this.numTopics, this.random); + } + } + + /** + * Use an upper bound for smoothing. Only if the smoothing + * bin is hit, the actual smoothing is computed and resampled. + */ + public void sampleDoc(int doc_id) { + DocData doc = this.data.get(doc_id); + //System.out.println("doc " + doc_id); + //int[] tmpstats = this.stats.get(this.stats.size()-1); + + for(int ii = 0; ii < doc.tokens.size(); ii++) { + //int word = doc.tokens.getIndexAtPosition(ii); + int word = doc.tokens.get(ii); + + this.changeTopic(doc_id, ii, word, -1, -1); + + //double smoothing_mass = this.topics.computeTermSmoothing(this.alpha, word); + double smoothing_mass_est = this.topics.smoothingEst.get(word); + + double topic_beta_mass = this.topics.computeTermTopicBeta(doc.topicCounts, word); + + ArrayList topic_term_score = new ArrayList(); + double topic_term_mass = this.topics.computeTopicTerm(this.alpha, doc.topicCounts, word, topic_term_score); + + double norm_est = smoothing_mass_est + topic_beta_mass + topic_term_mass; + double sample = this.random.nextDouble(); + //double sample = 0.5; + sample *= norm_est; + + int new_topic = -1; + int new_path = -1; + + int[] paths = this.topics.getWordPathIndexSet(word); + + // sample the smoothing bin + if (sample < smoothing_mass_est) { + //tmpstats[0] += 1; + double smoothing_mass = this.topics.computeTermSmoothing(this.alpha, word); + double norm = smoothing_mass + topic_beta_mass + topic_term_mass; + sample /= norm_est; + sample *= norm; + if (sample < smoothing_mass) { + //tmpstats[1] += 1; + for (int tt = 0; tt < this.numTopics; tt++) { + for (int pp : paths) { + double val = alpha[tt] * this.topics.getPathPrior(word, pp); + val /= this.topics.getNormalizer(tt, pp); + sample -= val; + if (sample <= 0.0) { + new_topic = tt; + new_path = pp; + break; + } + } + if (new_topic >= 0) { + break; + } + } + myAssert((new_topic >= 0 && new_topic < numTopics), "something wrong in sampling smoothing!"); + } else { + sample -= smoothing_mass; + } + } else { + sample -= smoothing_mass_est; + } + + // sample topic beta bin + if (new_topic < 0 && sample < topic_beta_mass) { + //tmpstats[2] += 1; + for(int tt : doc.topicCounts.keys()) { + for (int pp : paths) { + double val = doc.topicCounts.get(tt) * this.topics.getPathPrior(word, pp); + val /= this.topics.getNormalizer(tt, pp); + sample -= val; + if (sample <= 0.0) { + new_topic = tt; + new_path = pp; + break; + } + } + if (new_topic >= 0) { + break; + } + } + myAssert((new_topic >= 0 && new_topic < numTopics), "something wrong in sampling topic beta!"); + } else { + sample -= topic_beta_mass; + } + + + // sample topic term bin + if (new_topic < 0) { + //tmpstats[3] += 1; + for(int jj = 0; jj < topic_term_score.size(); jj++) { + double[] tmp = topic_term_score.get(jj); + int tt = (int) tmp[0]; + int pp = (int) tmp[1]; + double val = tmp[2]; + sample -= val; + if (sample <= 0.0) { + new_topic = tt; + new_path = pp; + break; + } + } + myAssert((new_topic >= 0 && new_topic < numTopics), "something wrong in sampling topic term!"); + } + + this.changeTopic(doc_id, ii, word, new_topic, new_path); + } + + } + + /** + * Before sampling start, compute smoothing upper bound for each word. + */ + public void estimate(int numIterations, String outputFolder, int outputInterval, int topWords) { + if(this.topics instanceof TreeTopicModelFastEst) { + TreeTopicModelFastEst tmp = (TreeTopicModelFastEst) this.topics; + tmp.computeSmoothingEst(this.alpha); + } else if (this.topics instanceof TreeTopicModelFastEstSortW) { + TreeTopicModelFastEstSortW tmp = (TreeTopicModelFastEstSortW) this.topics; + tmp.computeSmoothingEst(this.alpha); + } + + super.estimate(numIterations, outputFolder, outputInterval, topWords); + } + +} diff --git a/src/cc/mallet/topics/tree/TreeTopicSamplerFastEstSortD.java b/src/cc/mallet/topics/tree/TreeTopicSamplerFastEstSortD.java new file mode 100755 index 000000000..3c41ed0cd --- /dev/null +++ b/src/cc/mallet/topics/tree/TreeTopicSamplerFastEstSortD.java @@ -0,0 +1,150 @@ +package cc.mallet.topics.tree; + +import java.util.ArrayList; + +import cc.mallet.topics.tree.TreeTopicSamplerHashD.DocData; + +/** + * This class improves the fast sampler based on estimation of smoothing. + * Most of the time, the smoothing is very small and not worth to recompute since + * it will hardly be hit. So we use an upper bound for smoothing. + * Only if the smoothing bin is hit, the actual smoothing is computed and resampled. + * + * @author Yuening Hu + */ + +public class TreeTopicSamplerFastEstSortD extends TreeTopicSamplerSortD{ + + public TreeTopicSamplerFastEstSortD (int numberOfTopics, double alphaSum, int seed, boolean sort) { + super(numberOfTopics, alphaSum, seed); + + if (sort) { + this.topics = new TreeTopicModelFastEstSortW(this.numTopics, this.random); + } else { + this.topics = new TreeTopicModelFastEst(this.numTopics, this.random); + } + } + + /** + * Use an upper bound for smoothing. Only if the smoothing + * bin is hit, the actual smoothing is computed and resampled. + */ + public void sampleDoc(int doc_id) { + DocData doc = this.data.get(doc_id); + //System.out.println("doc " + doc_id); + + for(int ii = 0; ii < doc.tokens.size(); ii++) { + //int word = doc.tokens.getIndexAtPosition(ii); + int word = doc.tokens.get(ii); + + this.changeTopic(doc_id, ii, word, -1, -1); + + //double smoothing_mass = this.topics.computeTermSmoothing(this.alpha, word); + double smoothing_mass_est = this.topics.smoothingEst.get(word); + double topic_beta_mass = this.topics.computeTermTopicBetaSortD(doc.topicCounts, word); + + ArrayList topic_term_score = new ArrayList(); + double topic_term_mass = this.topics.computeTopicTermSortD(this.alpha, doc.topicCounts, word, topic_term_score); + + double norm_est = smoothing_mass_est + topic_beta_mass + topic_term_mass; + double sample = this.random.nextDouble(); + //double sample = 0.5; + sample *= norm_est; + + int new_topic = -1; + int new_path = -1; + + int[] paths = this.topics.getWordPathIndexSet(word); + + // sample the smoothing bin + if (sample < smoothing_mass_est) { + double smoothing_mass = this.topics.computeTermSmoothing(this.alpha, word); + double norm = smoothing_mass + topic_beta_mass + topic_term_mass; + sample /= norm_est; + sample *= norm; + if (sample < smoothing_mass) { + for (int tt = 0; tt < this.numTopics; tt++) { + for (int pp : paths) { + double val = alpha[tt] * this.topics.getPathPrior(word, pp); + val /= this.topics.getNormalizer(tt, pp); + sample -= val; + if (sample <= 0.0) { + new_topic = tt; + new_path = pp; + break; + } + } + if (new_topic >= 0) { + break; + } + } + myAssert((new_topic >= 0 && new_topic < numTopics), "something wrong in sampling smoothing!"); + } else { + sample -= smoothing_mass; + } + } else { + sample -= smoothing_mass_est; + } + + // sample topic beta bin + if (new_topic < 0 && sample < topic_beta_mass) { + for(int jj = 0; jj < doc.topicCounts.size(); jj++) { + int[] current = doc.topicCounts.get(jj); + int tt = current[0]; + int count = current[1]; + for(int pp : paths) { + double val = count * this.topics.getPathPrior(word, pp); + val /= this.topics.getNormalizer(tt, pp); + sample -= val; + if (sample <= 0.0) { + new_topic = tt; + new_path = pp; + break; + } + } + if (new_topic >= 0) { + break; + } + } + myAssert((new_topic >= 0 && new_topic < numTopics), "something wrong in sampling topic beta!"); + } else { + sample -= topic_beta_mass; + } + + // sample topic term bin + if (new_topic < 0) { + for(int jj = 0; jj < topic_term_score.size(); jj++) { + double[] tmp = topic_term_score.get(jj); + int tt = (int) tmp[0]; + int pp = (int) tmp[1]; + double val = tmp[2]; + sample -= val; + if (sample <= 0.0) { + new_topic = tt; + new_path = pp; + break; + } + } + myAssert((new_topic >= 0 && new_topic < numTopics), "something wrong in sampling topic term!"); + } + + this.changeTopic(doc_id, ii, word, new_topic, new_path); + } + + } + + /** + * Before sampling start, compute smoothing upper bound for each word. + */ + public void estimate(int numIterations, String outputFolder, int outputInterval, int topWords) { + if(this.topics instanceof TreeTopicModelFastEst) { + TreeTopicModelFastEst tmp = (TreeTopicModelFastEst) this.topics; + tmp.computeSmoothingEst(this.alpha); + } else if (this.topics instanceof TreeTopicModelFastEstSortW) { + TreeTopicModelFastEstSortW tmp = (TreeTopicModelFastEstSortW) this.topics; + tmp.computeSmoothingEst(this.alpha); + } + + super.estimate(numIterations, outputFolder, outputInterval, topWords); + } +} \ No newline at end of file diff --git a/src/cc/mallet/topics/tree/TreeTopicSamplerFastSortD.java b/src/cc/mallet/topics/tree/TreeTopicSamplerFastSortD.java new file mode 100755 index 000000000..243e8d5fd --- /dev/null +++ b/src/cc/mallet/topics/tree/TreeTopicSamplerFastSortD.java @@ -0,0 +1,138 @@ +package cc.mallet.topics.tree; + +import gnu.trove.TDoubleArrayList; +import gnu.trove.TIntArrayList; +import gnu.trove.TIntIntHashMap; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; + +import cc.mallet.topics.tree.TreeTopicSamplerHashD.DocData; +import cc.mallet.util.Randoms; + +/** + * This class defines a fast tree topic sampler, which calls the fast tree topic model. + * (1) It divides the sampling into three bins: smoothing, topic beta, topic term. + * as Yao and Mimno's paper, KDD, 2009. + * (2) Each time the smoothing, topic beta, and topic term are recomputed. + * It is faster, because, + * (1) For topic term, only compute the one with non-zero paths (see TreeTopicModelFast). + * (2) The normalizer is saved. + * (3) Topic counts for each documents are ranked. + * + * @author Yuening Hu + */ +public class TreeTopicSamplerFastSortD extends TreeTopicSamplerSortD { + + public TreeTopicSamplerFastSortD (int numberOfTopics, double alphaSum, int seed, boolean sort) { + super(numberOfTopics, alphaSum, seed); + this.topics = new TreeTopicModelFast(this.numTopics, this.random); + + if (sort) { + this.topics = new TreeTopicModelFastSortW(this.numTopics, this.random); + } else { + this.topics = new TreeTopicModelFast(this.numTopics, this.random); + } + } + + /** + * For each word in a document, firstly covers its topic and path, then sample a + * topic and path, and update. + */ + public void sampleDoc(int doc_id){ + DocData doc = this.data.get(doc_id); + //System.out.println("doc " + doc_id); + + for(int ii = 0; ii < doc.tokens.size(); ii++) { + //int word = doc.tokens.getIndexAtPosition(ii); + int word = doc.tokens.get(ii); + + this.changeTopic(doc_id, ii, word, -1, -1); + + double smoothing_mass = this.topics.computeTermSmoothing(this.alpha, word); + double topic_beta_mass = this.topics.computeTermTopicBetaSortD(doc.topicCounts, word); + + ArrayList topic_term_score = new ArrayList (); + double topic_term_mass = this.topics.computeTopicTermSortD(this.alpha, doc.topicCounts, word, topic_term_score); + + double norm = smoothing_mass + topic_beta_mass + topic_term_mass; + double sample = this.random.nextDouble(); + //double sample = 0.5; + sample *= norm; + + int new_topic = -1; + int new_path = -1; + + int[] paths = this.topics.getWordPathIndexSet(word); + + // sample the smoothing bin + if (sample < smoothing_mass) { + for (int tt = 0; tt < this.numTopics; tt++) { + for (int pp : paths) { + double val = alpha[tt] * this.topics.getPathPrior(word, pp); + val /= this.topics.getNormalizer(tt, pp); + sample -= val; + if (sample <= 0.0) { + new_topic = tt; + new_path = pp; + break; + } + } + if (new_topic >= 0) { + break; + } + } + myAssert((new_topic >= 0 && new_topic < numTopics), "something wrong in sampling smoothing!"); + } else { + sample -= smoothing_mass; + } + + // sample the topic beta bin + if (new_topic < 0 && sample < topic_beta_mass) { + + for(int jj = 0; jj < doc.topicCounts.size(); jj++) { + int[] current = doc.topicCounts.get(jj); + int tt = current[0]; + int count = current[1]; + for(int pp : paths) { + double val = count * this.topics.getPathPrior(word, pp); + val /= this.topics.getNormalizer(tt, pp); + sample -= val; + if (sample <= 0.0) { + new_topic = tt; + new_path = pp; + break; + } + } + if (new_topic >= 0) { + break; + } + } + myAssert((new_topic >= 0 && new_topic < numTopics), "something wrong in sampling topic beta!"); + } else { + sample -= topic_beta_mass; + } + + // sample the topic term bin + if (new_topic < 0) { + for(int jj = 0; jj < topic_term_score.size(); jj++) { + double[] tmp = topic_term_score.get(jj); + int tt = (int) tmp[0]; + int pp = (int) tmp[1]; + double val = tmp[2]; + sample -= val; + if (sample <= 0.0) { + new_topic = tt; + new_path = pp; + break; + } + } + myAssert((new_topic >= 0 && new_topic < numTopics), "something wrong in sampling topic term!"); + } + + this.changeTopic(doc_id, ii, word, new_topic, new_path); + } + } + +} diff --git a/src/cc/mallet/topics/tree/TreeTopicSamplerHashD.java b/src/cc/mallet/topics/tree/TreeTopicSamplerHashD.java new file mode 100755 index 000000000..d5afce768 --- /dev/null +++ b/src/cc/mallet/topics/tree/TreeTopicSamplerHashD.java @@ -0,0 +1,648 @@ +package cc.mallet.topics.tree; + +import gnu.trove.TDoubleArrayList; +import gnu.trove.TIntArrayList; +import gnu.trove.TIntHashSet; +import gnu.trove.TIntIntHashMap; +import gnu.trove.TIntIntIterator; +import gnu.trove.TIntObjectHashMap; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.zip.GZIPOutputStream; + +import cc.mallet.types.Dirichlet; +import cc.mallet.types.FeatureSequence; +import cc.mallet.types.IDSorter; +import cc.mallet.types.Instance; +import cc.mallet.types.InstanceList; +import cc.mallet.util.Randoms; + +/** + * This class defines the tree topic sampler, which loads the instances, + * reports the topics, and leaves the sampler method as an abstract method, + * which might be various for different methods. + * + * @author Yuening Hu + */ + +public abstract class TreeTopicSamplerHashD extends TreeTopicSampler implements TreeTopicSamplerInterface{ + + /** + * This class defines the format of a document. + */ + public class DocData { + TIntArrayList tokens; + TIntArrayList topics; + TIntArrayList paths; + // sort + TIntIntHashMap topicCounts; + String docName; + + public DocData (String name, TIntArrayList tokens, TIntArrayList topics, + TIntArrayList paths, TIntIntHashMap topicCounts) { + this.docName = name; + this.tokens = tokens; + this.topics = topics; + this.paths = paths; + this.topicCounts = topicCounts; + } + + public String toString() { + String result = "***************\n"; + result += docName + "\n"; + + result += "tokens: "; + for (int jj = 0; jj < tokens.size(); jj++) { + int index = tokens.get(jj); + String word = vocab.get(index); + result += word + " " + index + ", "; + } + + result += "\ntopics: "; + result += topics.toString(); + + result += "\npaths: "; + result += paths.toString(); + + result += "\ntopicCounts: "; + + for(TIntIntIterator it = this.topicCounts.iterator(); it.hasNext(); ) { + it.advance(); + result += "Topic " + it.key() + ": " + it.value() + ", "; + } + result += "\n*****************\n"; + return result; + } + } + + public class WordProb implements Comparable { + int wi; + double p; + public WordProb (int wi, double p) { this.wi = wi; this.p = p; } + public final int compareTo (Object o2) { + if (p > ((WordProb)o2).p) + return -1; + else if (p == ((WordProb)o2).p) + return 0; + else return 1; + } + } + + ArrayList data; + TreeTopicModel topics; + + public TreeTopicSamplerHashD (int numberOfTopics, double alphaSum, int seed) { + super(numberOfTopics, alphaSum, seed); + this.data = new ArrayList (); + + // notice: this.topics is not initialized in this abstract class, + // in each sub class, the topics variable is initialized differently. + } + + /** + * This function adds instances given the training data in mallet input data format. + * For each token in a document, sample a topic and then sample a path based on prior. + */ + public void addInstances(InstanceList[] training) { + boolean debug = false; + int count = 0; + for(int ll = 0; ll < training.length; ll++) { + int totalcount = 0; + for (Instance instance : training[ll]) { + count++; + FeatureSequence original_tokens = (FeatureSequence) instance.getData(); + String name = instance.getName().toString(); + //String name = "null-source"; + //if (instance.getSource() != null) { + // name = instance.getSource().toString(); + //} + + // *** remained problem: keep topicCounts sorted + TIntArrayList tokens = new TIntArrayList(original_tokens.getLength()); + TIntIntHashMap topicCounts = new TIntIntHashMap (); + TIntArrayList topics = new TIntArrayList(original_tokens.getLength()); + TIntArrayList paths = new TIntArrayList(original_tokens.getLength()); + + for (int jj = 0; jj < original_tokens.getLength(); jj++) { + String word = (String) original_tokens.getObjectAtPosition(jj); + int token = this.vocab.indexOf(word); + int removed = this.removedWordsNew.indexOf(word); + int removednew = this.removedWordsNew.indexOf(word); + if(token != -1 && removed == -1 && removednew == -1) { + int topic = random.nextInt(numTopics); + if(debug) { topic = count % numTopics; } + tokens.add(token); + topics.add(topic); + topicCounts.adjustOrPutValue(topic, 1, 1); + // sample a path for this topic + int path_index = this.topics.initialize(token, topic); + paths.add(path_index); + } + } + DocData doc = new DocData(name, tokens, topics, paths, topicCounts); + this.data.add(doc); + + totalcount += tokens.size(); + //if (totalcount > 200000) { + // System.out.println("total number of tokens: " + totalcount + " docs: " + count); + // break; + //} + } + System.out.println("total number of tokens: " + totalcount); + //System.out.println(doc); + } + } + + /** + * Resume instance states from the saved states file. + */ + public void resumeStates(InstanceList[] training, String statesFile) throws IOException{ + FileInputStream statesfstream = new FileInputStream(statesFile); + DataInputStream statesdstream = new DataInputStream(statesfstream); + BufferedReader states = new BufferedReader(new InputStreamReader(statesdstream)); + + // reading topics, paths + for(int ll = 0; ll < training.length; ll++) { + for (Instance instance : training[ll]) { + FeatureSequence original_tokens = (FeatureSequence) instance.getData(); + String name = instance.getName().toString(); + + // *** remained problem: keep topicCounts sorted + TIntArrayList tokens = new TIntArrayList(original_tokens.getLength()); + TIntIntHashMap topicCounts = new TIntIntHashMap (); + TIntArrayList topics = new TIntArrayList(original_tokens.getLength()); + TIntArrayList paths = new TIntArrayList(original_tokens.getLength()); + + // + String statesLine = states.readLine(); + myAssert(statesLine != null, "statesFile doesn't match with the training data"); + statesLine = statesLine.trim(); + String[] str = statesLine.split("\t"); + + int count = -1; + for (int jj = 0; jj < original_tokens.getLength(); jj++) { + String word = (String) original_tokens.getObjectAtPosition(jj); + int token = this.vocab.indexOf(word); + int removed = this.removedWords.indexOf(word); + int removednew = this.removedWordsNew.indexOf(word); + if(token != -1 && removed == -1) { + count++; + if (removednew == -1) { + String[] tp = str[count].split(":"); + myAssert(tp.length == 2, "statesFile problem!"); + int topic = Integer.parseInt(tp[0]); + //int path = Integer.parseInt(tp[1]); + + int wordpath = Integer.parseInt(tp[1]); + int path = -1; + int backoffpath = -1; + // find the path for this wordpath + TIntObjectHashMap allpaths = this.topics.wordPaths.get(token); + for(int pp : allpaths.keys()) { + if(backoffpath == -1 && this.topics.pathToWordPath.get(pp) == 0){ + backoffpath = pp; + } + if(this.topics.pathToWordPath.get(pp) == wordpath){ + path = pp; + break; + } + } + + if(path == -1) { + // this path must be in a correlation, it will be cleared later + path = backoffpath; + myAssert(path != -1, "path problem"); + + //String tmp = ""; + //tmp += "file " + name + "\n"; + //tmp += "word " + word + "\n"; + //tmp += "token " + token + "\n"; + //tmp += "index " + count + "\n"; + //tmp += "topic " + topic + "\n"; + //tmp += "wordpath " + wordpath + "\n"; + //tmp += "allpaths"; + //for(int pp : allpaths.keys()) { + // tmp += " " + pp; + //} + //System.out.println(tmp); + } + + tokens.add(token); + topics.add(topic); + paths.add(path); + topicCounts.adjustOrPutValue(topic, 1, 1); + this.topics.changeCountOnly(topic, token, path, 1); + } + } + } + if(count != -1) { + count++; + myAssert(str.length == count, "resume problem!"); + } + + DocData doc = new DocData(name, tokens, topics, paths, topicCounts); + this.data.add(doc); + } + } + states.close(); + } + + /** + * This function clears the topic and path assignments for some words: + * (1) term option: only clears the topic and path for constraint words; + * (2) doc option: clears the topic and path for documents which contain + * at least one of the constraint words. + */ + public void clearTopicAssignments(String option, String consFile, String keepFile) { + if (consFile != null) { + this.loadConstraints(consFile); + } + if (this.cons == null || this.cons.size() <= 0) { + return; + } + + if (keepFile != null) { + this.loadKeepList(keepFile); + } else { + this.topickeep = new HashMap(); + } + + for(int dd = 0; dd < this.data.size(); dd++) { + DocData doc = this.data.get(dd); + + for(int ii = 0; ii < doc.tokens.size(); ii++) { + int word = doc.tokens.get(ii); + int topic = doc.topics.get(ii); + int path = doc.paths.get(ii); + + boolean keepTopicFlag = false; + if(this.topickeep.containsKey(word)) { + TIntHashSet keeptopics = this.topickeep.get(word); + if(keeptopics.contains(topic)) { + keepTopicFlag = true; + } + } + + if (option.equals("term")) { + if(this.cons.contains(word) && (!keepTopicFlag)) { + // change the count for count and node_count in TopicTreeWalk + this.topics.changeCountOnly(topic, word, path, -1); + doc.topics.set(ii, -1); + doc.paths.set(ii, -1); + //myAssert(doc.topicCounts.get(topic) >= 1, "clear topic assignments problem"); + doc.topicCounts.adjustValue(topic, -1); + } + } else { // option.equals("doc") + if(!keepTopicFlag) { + this.topics.changeCountOnly(topic, word, path, -1); + doc.topics.set(ii, -1); + doc.paths.set(ii, -1); + doc.topicCounts.adjustValue(topic, -1); + } + } + } + } + +// for(int dd = 0; dd < this.data.size(); dd++) { +// DocData doc = this.data.get(dd); +// Boolean flag = false; +// for(int ii = 0; ii < doc.tokens.size(); ii++) { +// int word = doc.tokens.get(ii); +// int topic = doc.topics.get(ii); +// +// boolean keepTopicFlag = false; +// if(this.topickeep.containsKey(word)) { +// TIntHashSet keeptopics = this.topickeep.get(word); +// if(keeptopics.contains(topic)) { +// keepTopicFlag = true; +// } +// } +// +// if(this.cons.contains(word) && (!keepTopicFlag)) { +// if (option.equals("term")) { +// int path = doc.paths.get(ii); +// // change the count for count and node_count in TopicTreeWalk +// this.topics.changeCountOnly(topic, word, path, -1); +// doc.topics.set(ii, -1); +// doc.paths.set(ii, -1); +// myAssert(doc.topicCounts.get(topic) >= 1, "clear topic assignments problem"); +// doc.topicCounts.adjustValue(topic, -1); +// } else if (option.equals("doc")) { +// flag = true; +// break; +// } +// } +// } +// if (flag) { +// for(int ii = 0; ii < doc.tokens.size(); ii++) { +// int word = doc.tokens.get(ii); +// int topic = doc.topics.get(ii); +// int path = doc.paths.get(ii); +// this.topics.changeCountOnly(topic, word, path, -1); +// doc.topics.set(ii, -1); +// doc.paths.set(ii, -1); +// } +// doc.topicCounts.clear(); +// } +// } + + } + + /** + * This function defines how to change a topic during the sampling process. + * It handles the case where both new_topic and old_topic are "-1" (empty topic). + */ + public void changeTopic(int doc, int index, int word, int new_topic, int new_path) { + DocData current_doc = this.data.get(doc); + int old_topic = current_doc.topics.get(index); + int old_path = current_doc.paths.get(index); + + if (old_topic != -1) { + myAssert((new_topic == -1 && new_path == -1), "old_topic != -1 but new_topic != -1"); + this.topics.changeCount(old_topic, word, old_path, -1); + //myAssert(current_doc.topicCounts.get(old_topic) > 0, "Something wrong in changTopic"); + current_doc.topicCounts.adjustValue(old_topic, -1); + current_doc.topics.set(index, -1); + current_doc.paths.set(index, -1); + } + + if (new_topic != -1) { + myAssert((old_topic == -1 && old_path == -1), "new_topic != -1 but old_topic != -1"); + this.topics.changeCount(new_topic, word, new_path, 1); + current_doc.topicCounts.adjustOrPutValue(new_topic, 1, 1); + current_doc.topics.set(index, new_topic); + current_doc.paths.set(index, new_path); + } + } + + /** + * The function computes the document likelihood. + */ + public double docLHood() { + int docNum = this.data.size(); + + double val = 0.0; + val += Dirichlet.logGamma(this.alphaSum) * docNum; + double tmp = 0.0; + for (int tt = 0; tt < this.numTopics; tt++) { + tmp += Dirichlet.logGamma(this.alpha[tt]); + } + val -= tmp * docNum; + for (int dd = 0; dd < docNum; dd++) { + DocData doc = this.data.get(dd); + for (int tt = 0; tt < this.numTopics; tt++) { + val += Dirichlet.logGamma(this.alpha[tt] + doc.topicCounts.get(tt)); + } + val -= Dirichlet.logGamma(this.alphaSum + doc.topics.size()); + } + return val; + } + + /** + * Print the topic proportion for all documents. + */ + public void printDocumentTopics (File file) throws IOException { + PrintStream out = new PrintStream (file); + out.print ("#doc source topic proportion ...\n"); + + IDSorter[] sortedTopics = new IDSorter[ this.numTopics ]; + for (int topic = 0; topic < this.numTopics; topic++) { + // Initialize the sorters with dummy values + sortedTopics[topic] = new IDSorter(topic, topic); + } + + for (int dd = 0; dd < this.data.size(); dd++) { + DocData doc = this.data.get(dd); + + // compute topic proportion in one document + double sum = 0.0; + double[] prob = new double[this.numTopics]; + for (int topic=0; topic < this.numTopics; topic++) { + if (doc.topicCounts.containsKey(topic)) { + prob[topic] = this.alpha[topic] + doc.topicCounts.get(topic); + } else { + prob[topic] = this.alpha[topic]; + } + sum += prob[topic]; + } + + // normalize and sort + for (int topic=0; topic < this.numTopics; topic++) { + prob[topic] /= sum; + sortedTopics[topic].set(topic, prob[topic]); + } + Arrays.sort(sortedTopics); + + // print one document + out.print (dd); out.print (" "); + + if (doc.docName != null || !doc.docName.equals(" ")) { + out.print (doc.docName); + } else { + out.print ("null-source"); + } + out.print (" "); + for (int i = 0; i < numTopics; i++) { + out.print (sortedTopics[i].getID() + " " + + sortedTopics[i].getWeight() + " "); + } + out.print (" \n"); + } + out.close(); + } + + ////////////////////////////////////////////////////// + + /** + * This function loads vocab, loads tree, and initialize parameters. + */ + public void initialize(String treeFiles, String hyperFile, String vocabFile, String removedwordsFile) { + this.loadVocab(vocabFile); + if (removedwordsFile != null) { + this.loadRemovedWords(removedwordsFile + ".all", this.removedWords); + this.loadRemovedWords(removedwordsFile + ".new", this.removedWordsNew); + } + this.topics.initializeParams(treeFiles, hyperFile, this.vocab); + } + + /** + * This function defines the sampling process, computes the likelihood and running time, + * and specifies when to save the states files. + */ + public void estimate(int numIterations, String outputFolder, int outputInterval, int topWords) { + // update parameters + this.topics.updateParams(); + + if (this.startIter > this.numIterations) { + System.out.println("Have already sampled " + this.numIterations + " iterations!"); + System.exit(0); + } + System.out.println("Start sampling for iteration " + this.startIter); + + for (int ii = this.startIter; ii <= numIterations; ii++) { + //int[] tmpstats = {0, 0, 0, 0}; + //this.stats.add(tmpstats); + long starttime = System.currentTimeMillis(); + //System.out.println("Iter " + ii); + for (int dd = 0; dd < this.data.size(); dd++) { + this.sampleDoc(dd); + if (dd > 0 && dd % 10000 == 0) { + System.out.println("Sampled " + dd + " documents."); + } + } + + double totaltime = (double)(System.currentTimeMillis() - starttime) / 1000; + double lhood = 0; + if ((ii > 0 && ii % outputInterval == 0) || ii == numIterations) { + lhood = this.lhood(); + } + this.lhood.add(lhood); + this.iterTime.add(totaltime); + + if (ii % 10 == 0) { + String tmp = "Iteration " + ii; + tmp += " likelihood " + lhood; + tmp += " totaltime " + totaltime; + System.out.println(tmp); + } + + if ((ii > 0 && ii % outputInterval == 0) || ii == numIterations) { + try { + this.report(outputFolder, topWords); + } catch (IOException e) { + System.out.println(e.getMessage()); + } + } + } + } + + ////////////////////////////////////////////////////// + + /** + * This function returns the likelihood. + */ + public double lhood() { + return this.docLHood() + this.topics.topicLHood(); + } + + /** + * By implementing the comparable interface, this function ranks the words + * in each topic, and returns the top words for each topic. + */ + public String displayTopWords (int numWords) { + + + + StringBuilder out = new StringBuilder(); + int numPaths = this.topics.getPathNum(); + //System.out.println(numPaths); + + for (int tt = 0; tt < this.numTopics; tt++){ + String tmp = "\n--------------\nTopic " + tt + "\n------------------------\n"; + //System.out.print(tmp); + out.append(tmp); + WordProb[] wp = new WordProb[numPaths]; + for (int pp = 0; pp < numPaths; pp++){ + int ww = this.topics.getWordFromPath(pp); + double val = this.topics.computeTopicPathProb(tt, ww, pp); + wp[pp] = new WordProb(pp, val); + } + Arrays.sort(wp); + for (int ii = 0; ii < wp.length; ii++){ + if(ii >= numWords) { + break; + } + int pp = wp[ii].wi; + int ww = this.topics.getWordFromPath(pp); + String word = this.vocab.get(ww); + if (this.removedWords.indexOf(word) == -1 && this.removedWordsNew.indexOf(word) == -1) { + tmp = wp[ii].p + "\t" + word + "\n"; + out.append(tmp); + } + } + } + return out.toString(); + } + + /** + * Prints the topic word distributions. + */ + public void printTopicWords (File file) throws IOException { + + PrintStream out = new PrintStream (file); + int numPaths = this.topics.getPathNum(); + String tmp; + for (int tt = 0; tt < this.numTopics; tt++){ + WordProb[] wp = new WordProb[numPaths]; + for (int pp = 0; pp < numPaths; pp++){ + int ww = this.topics.getWordFromPath(pp); + double val = this.topics.computeTopicPathProb(tt, ww, pp); + wp[pp] = new WordProb(pp, val); + } + Arrays.sort(wp); + for (int ii = 0; ii < wp.length; ii++){ + int pp = wp[ii].wi; + int ww = this.topics.getWordFromPath(pp); + String word = this.vocab.get(ww); + if (this.removedWords.indexOf(word) == -1 && this.removedWordsNew.indexOf(word) == -1) { + tmp = tt + "\t" + word + "\t" + wp[ii].p; + out.println(tmp); + } + } + } + + out.close(); + } + + /** + * Prints the topic and path of each word for all documents. + */ + public void printState (File file) throws IOException { + //PrintStream out = + // new PrintStream(new GZIPOutputStream(new BufferedOutputStream(new FileOutputStream(file)))); + PrintStream out = new PrintStream(file); + + for (int dd = 0; dd < this.data.size(); dd++) { + DocData doc = this.data.get(dd); + String tmp = ""; + for (int ww = 0; ww < doc.topics.size(); ww++) { + int topic = doc.topics.get(ww); + int path = doc.paths.get(ww); + int wordpath = this.topics.pathToWordPath.get(path); + tmp += topic + ":" + wordpath + "\t"; + } + out.println(tmp); + } + out.close(); + } + + public TreeTopicInferencer getInferencer() { + //this.topics.updateParams(); + HashSet removedall = new HashSet (); + removedall.addAll(this.removedWords); + removedall.addAll(this.removedWordsNew); + TreeTopicInferencer inferencer = new TreeTopicInferencer(topics, vocab, removedall, alpha); + return inferencer; + } + + public TreeMarginalProbEstimator getProbEstimator() { + HashSet removedall = new HashSet (); + removedall.addAll(this.removedWords); + removedall.addAll(this.removedWordsNew); + TreeMarginalProbEstimator estimator = new TreeMarginalProbEstimator(topics, vocab, removedall, alpha); + return estimator; + } +} diff --git a/src/cc/mallet/topics/tree/TreeTopicSamplerInterface.java b/src/cc/mallet/topics/tree/TreeTopicSamplerInterface.java new file mode 100755 index 000000000..2ed1373ec --- /dev/null +++ b/src/cc/mallet/topics/tree/TreeTopicSamplerInterface.java @@ -0,0 +1,80 @@ +package cc.mallet.topics.tree; + +import gnu.trove.TDoubleArrayList; +import gnu.trove.TIntArrayList; +import gnu.trove.TIntHashSet; +import gnu.trove.TIntIntHashMap; +import gnu.trove.TIntIntIterator; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.zip.GZIPOutputStream; + +import cc.mallet.types.Dirichlet; +import cc.mallet.types.FeatureSequence; +import cc.mallet.types.Instance; +import cc.mallet.types.InstanceList; +import cc.mallet.util.Randoms; + +/** + * This class defines the interface of a tree topic sampler. + * + * @author Yuening Hu + */ + +public interface TreeTopicSamplerInterface { + + /* Implemented in TreeTopicSampler.java. + Shared code by TreeTopicSamplerSortD.java and TreeTopicSamplerHashD.java + */ + public void setNumIterations(int iters); + public void resume(InstanceList[] training, String resumeDir); + + // Also implemented in TreeTopicSampler.java, but do not need to be defined in interface. + //public int getNumIterations(); + //public void resumeLHood(String lhoodFile) throws IOException; + //public void report(String outputDir, int topWords) throws IOException; + //public void printTopWords(File file, int numWords) throws IOException; + //public void printStats (File file) throws IOException; + //public void loadVocab(String vocabFile); + //public void loadStopWords(String stopwordFile); + //public void loadConstraints(String consFile); + //abstract public void sampleDoc(int doc); + + + /* Same code for TreeTopicSamplerSortD.java and TreeTopicSamplerHashD.java + But related with this.topics, so not the the shared parent class. + */ + public void initialize(String treeFiles, String hyperFile, String vocabFile, String removedwordsFile); + public void estimate(int numIterations, String outputFolder, int outputInterval, int topWords); + public TreeTopicInferencer getInferencer(); + public TreeMarginalProbEstimator getProbEstimator(); + // do not need to be defined in interface. + //public double lhood(); + //public String displayTopWords (int numWords); + //public void printState (File file) throws IOException; + + + + /* Different code for TreeTopicSamplerSortD.java and TreeTopicSamplerHashD.java + Stay in these two java files separately. + */ + public void addInstances(InstanceList[] training); + public void clearTopicAssignments(String option, String consFile, String keepFile); + // Do not need to be defined in interface. + //public void resumeStates(InstanceList training, String statesFile) throws IOException; + //public void changeTopic(int doc, int index, int word, int new_topic, int new_path); + //public double docLHood(); + //public void printDocumentTopics (File file) throws IOException; + +} \ No newline at end of file diff --git a/src/cc/mallet/topics/tree/TreeTopicSamplerNaive.java b/src/cc/mallet/topics/tree/TreeTopicSamplerNaive.java new file mode 100755 index 000000000..e9fff4e1a --- /dev/null +++ b/src/cc/mallet/topics/tree/TreeTopicSamplerNaive.java @@ -0,0 +1,98 @@ +package cc.mallet.topics.tree; + +import gnu.trove.TDoubleArrayList; +import gnu.trove.TIntArrayList; +import gnu.trove.TIntDoubleHashMap; +import gnu.trove.TIntDoubleIterator; +import gnu.trove.TIntHashSet; +import gnu.trove.TIntIntHashMap; +import gnu.trove.TIntIntIterator; +import gnu.trove.TIntObjectHashMap; +import gnu.trove.TIntObjectIterator; +import gnu.trove.TObjectIntHashMap; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.zip.GZIPOutputStream; + +import cc.mallet.types.Alphabet; +import cc.mallet.types.Dirichlet; +import cc.mallet.types.FeatureSequence; +import cc.mallet.types.Instance; +import cc.mallet.types.InstanceList; +import cc.mallet.types.LabelAlphabet; +import cc.mallet.util.Randoms; + +/** + * This class defines a naive tree topic sampler. + * It calls the naive tree topic model. + * + * @author Yuening Hu + */ + +public class TreeTopicSamplerNaive extends TreeTopicSamplerHashD { + + public TreeTopicSamplerNaive (int numberOfTopics, double alphaSum) { + this (numberOfTopics, alphaSum, 0); + } + + public TreeTopicSamplerNaive (int numberOfTopics, double alphaSum, int seed) { + super (numberOfTopics, alphaSum, seed); + this.topics = new TreeTopicModelNaive(this.numTopics, this.random); + } + + /** + * For each word in a document, firstly covers its topic and path, then sample a + * topic and path, and update. + */ + public void sampleDoc(int doc_id){ + DocData doc = this.data.get(doc_id); + //System.out.println("doc " + doc_id); + + for(int ii = 0; ii < doc.tokens.size(); ii++) { + //int word = doc.tokens.getIndexAtPosition(ii); + int word = doc.tokens.get(ii); + + this.changeTopic(doc_id, ii, word, -1, -1); + ArrayList topic_term_score = new ArrayList(); + double norm = this.topics.computeTopicTerm(this.alpha, doc.topicCounts, word, topic_term_score); + //System.out.println(norm); + + int new_topic = -1; + int new_path = -1; + + double sample = this.random.nextDouble(); + //double sample = 0.8; + sample *= norm; + + for(int jj = 0; jj < topic_term_score.size(); jj++) { + double[] tmp = topic_term_score.get(jj); + int tt = (int) tmp[0]; + int pp = (int) tmp[1]; + double val = tmp[2]; + sample -= val; + if (sample <= 0.0) { + new_topic = tt; + new_path = pp; + break; + } + } + + myAssert((new_topic >= 0 && new_topic < numTopics), "something wrong in sampling!"); + + this.changeTopic(doc_id, ii, word, new_topic, new_path); + } + } + +} diff --git a/src/cc/mallet/topics/tree/TreeTopicSamplerSortD.java b/src/cc/mallet/topics/tree/TreeTopicSamplerSortD.java new file mode 100755 index 000000000..bdedd506b --- /dev/null +++ b/src/cc/mallet/topics/tree/TreeTopicSamplerSortD.java @@ -0,0 +1,684 @@ +package cc.mallet.topics.tree; + +import gnu.trove.TDoubleArrayList; +import gnu.trove.TIntArrayList; +import gnu.trove.TIntHashSet; +import gnu.trove.TIntIntHashMap; +import gnu.trove.TIntIntIterator; +import gnu.trove.TIntObjectHashMap; + +import java.io.BufferedReader; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; + +import cc.mallet.topics.tree.TreeTopicSamplerHashD.DocData; +import cc.mallet.types.Dirichlet; +import cc.mallet.types.FeatureSequence; +import cc.mallet.types.IDSorter; +import cc.mallet.types.Instance; +import cc.mallet.types.InstanceList; +import cc.mallet.util.Randoms; + +/** + * This class defines the tree topic sampler, which loads the instances, + * reports the topics, and leaves the sampler method as an abstract method, + * which might be various for different methods. + * Rathan than a HashMap for topicCounts in TreeTopicSamplerHashD, + * this class uses a sorted ArrayList for topicCounts. + * + * @author Yuening Hu + */ + +public abstract class TreeTopicSamplerSortD extends TreeTopicSampler implements TreeTopicSamplerInterface { + + /** + * This class defines the format of a document. + */ + public class DocData { + TIntArrayList tokens; + TIntArrayList topics; + TIntArrayList paths; + // sort + ArrayList topicCounts; + String docName; + + public DocData (String name, TIntArrayList tokens, TIntArrayList topics, + TIntArrayList paths, ArrayList topicCounts) { + this.docName = name; + this.tokens = tokens; + this.topics = topics; + this.paths = paths; + this.topicCounts = topicCounts; + } + + public String toString() { + String result = "***************\n"; + result += docName + "\n"; + + result += "tokens: "; + for (int jj = 0; jj < tokens.size(); jj++) { + int index = tokens.get(jj); + String word = vocab.get(index); + result += word + " " + index + ", "; + } + + result += "\ntopics: "; + result += topics.toString(); + + result += "\npaths: "; + result += paths.toString(); + + result += "\ntopicCounts: "; + + for(int ii = 0; ii < this.topicCounts.size(); ii++) { + int[] tmp = this.topicCounts.get(ii); + result += "Topic " + tmp[0] + ": " + tmp[1] + ", "; + } + + result += "\n*****************\n"; + return result; + } + } + + public class WordProb implements Comparable { + int wi; + double p; + public WordProb (int wi, double p) { this.wi = wi; this.p = p; } + public final int compareTo (Object o2) { + if (p > ((WordProb)o2).p) + return -1; + else if (p == ((WordProb)o2).p) + return 0; + else return 1; + } + } + + TreeTopicModel topics; + ArrayList data; + + public TreeTopicSamplerSortD (int numberOfTopics, double alphaSum, int seed) { + super(numberOfTopics, alphaSum, seed); + this.data = new ArrayList (); + + // notice: this.topics is not initialized in this abstract class, + // in each sub class, the topics variable is initialized differently. + } + + /** + * This function adds instances given the training data in mallet input data format. + * For each token in a document, sample a topic and then sample a path based on prior. + */ + public void addInstances(InstanceList[] training) { + boolean debug = false; + int count = 0; + for(int ll = 0; ll < training.length; ll++) { + for (Instance instance : training[ll]) { + count++; + FeatureSequence original_tokens = (FeatureSequence) instance.getData(); + String name = instance.getName().toString(); + //String name = "null-source"; + //if (instance.getSource() != null) { + // name = instance.getSource().toString(); + //} + + // *** remained problem: keep topicCounts sorted + TIntArrayList tokens = new TIntArrayList(original_tokens.getLength()); + ArrayList topicCounts = new ArrayList (); + TIntArrayList topics = new TIntArrayList(original_tokens.getLength()); + TIntArrayList paths = new TIntArrayList(original_tokens.getLength()); + + for (int jj = 0; jj < original_tokens.getLength(); jj++) { + String word = (String) original_tokens.getObjectAtPosition(jj); + int token = this.vocab.indexOf(word); + int removed = this.removedWordsNew.indexOf(word); + int removednew = this.removedWordsNew.indexOf(word); + if(token != -1 && removed == -1 && removednew == -1) { + int topic = random.nextInt(numTopics); + if(debug) { topic = count % numTopics; } + tokens.add(token); + topics.add(topic); + //topicCounts.adjustOrPutValue(topic, 1, 1); + this.updateTopicCounts(topicCounts, topic, 1, 1); + // sample a path for this topic + int path_index = this.topics.initialize(token, topic); + paths.add(path_index); + } + } + + DocData doc = new DocData(name, tokens, topics, paths, topicCounts); + this.data.add(doc); + } + + //System.out.println(doc); + } + + } + + /** + * This function keeps the topicCounts in order by bubble sort. + */ + private void updateTopicCounts(ArrayList topicCounts, int topic, int adjustvalue, int putvalue) { + + // remove old value + int value = -1; + for(int ii = 0; ii < topicCounts.size(); ii++) { + int[] tmp = topicCounts.get(ii); + if(tmp[0] == topic) { + value = tmp[1]; + topicCounts.remove(ii); + break; + } + } + + // adjust the value and update or insert + if (value == -1) { + value = putvalue; + } else { + value += adjustvalue; + } + + if (value > 0) { + int index = topicCounts.size(); + for(int ii = 0; ii < topicCounts.size(); ii++) { + int[] tmp = topicCounts.get(ii); + if(value >= tmp[1]) { + index = ii; + break; + } + } + int[] newpair = {topic, value}; + topicCounts.add(index, newpair); + } + + } + + /** + * Resume instance states from the saved states file. + */ + public void resumeStates(InstanceList[] training, String statesFile) throws IOException{ + FileInputStream statesfstream = new FileInputStream(statesFile); + DataInputStream statesdstream = new DataInputStream(statesfstream); + BufferedReader states = new BufferedReader(new InputStreamReader(statesdstream)); + + // reading topics, paths + for(int ll = 0; ll < training.length; ll++) { + for (Instance instance : training[ll]) { + FeatureSequence original_tokens = (FeatureSequence) instance.getData(); + String name = instance.getName().toString(); + + // *** remained problem: keep topicCounts sorted + TIntArrayList tokens = new TIntArrayList(original_tokens.getLength()); + ArrayList topicCounts = new ArrayList (); + TIntArrayList topics = new TIntArrayList(original_tokens.getLength()); + TIntArrayList paths = new TIntArrayList(original_tokens.getLength()); + + // + String statesLine = states.readLine(); + myAssert(statesLine != null, "statesFile doesn't match with the training data"); + statesLine = statesLine.trim(); + String[] str = statesLine.split("\t"); + + int count = -1; + for (int jj = 0; jj < original_tokens.getLength(); jj++) { + String word = (String) original_tokens.getObjectAtPosition(jj); + int token = this.vocab.indexOf(word); + int removed = this.removedWords.indexOf(word); + int removednew = this.removedWordsNew.indexOf(word); + if(token != -1 && removed == -1) { + count++; + if (removednew == -1) { + String[] tp = str[count].split(":"); + myAssert(tp.length == 2, "statesFile problem!"); + int topic = Integer.parseInt(tp[0]); + int wordpath = Integer.parseInt(tp[1]); + int path = -1; + int backoffpath = -1; + // find the path for this wordpath + TIntObjectHashMap allpaths = this.topics.wordPaths.get(token); + for(int pp : allpaths.keys()) { + if(backoffpath == -1 && this.topics.pathToWordPath.get(pp) == 0){ + backoffpath = pp; + } + if(this.topics.pathToWordPath.get(pp) == wordpath){ + path = pp; + break; + } + } + + if(path == -1) { + // this path must be in a correlation, it will be cleared later + path = backoffpath; + myAssert(path != -1, "path problem"); + } + tokens.add(token); + topics.add(topic); + paths.add(path); + //topicCounts.adjustOrPutValue(topic, 1, 1); + this.updateTopicCounts(topicCounts, topic, 1, 1); + this.topics.changeCountOnly(topic, token, path, 1); + } + } + } + if(count != -1) { + count++; + myAssert(str.length == count, "resume problem!"); + } + + DocData doc = new DocData(name, tokens, topics, paths, topicCounts); + this.data.add(doc); + } + } + states.close(); + } + + /** + * This function clears the topic and path assignments for some words: + * (1) term option: only clears the topic and path for constraint words; + * (2) doc option: clears the topic and path for documents which contain + * at least one of the constraint words. + */ + public void clearTopicAssignments(String option, String consFile, String keepFile) { + this.loadConstraints(consFile); + if (this.cons == null || this.cons.size() <= 0) { + return; + } + + if (keepFile != null) { + this.loadKeepList(keepFile); + } else { + this.topickeep = new HashMap(); + } + + for(int dd = 0; dd < this.data.size(); dd++) { + DocData doc = this.data.get(dd); + + for(int ii = 0; ii < doc.tokens.size(); ii++) { + int word = doc.tokens.get(ii); + int topic = doc.topics.get(ii); + int path = doc.paths.get(ii); + + boolean keepTopicFlag = false; + if(this.topickeep.containsKey(word)) { + TIntHashSet keeptopics = this.topickeep.get(word); + if(keeptopics.contains(topic)) { + keepTopicFlag = true; + } + } + + if (option.equals("term")) { + if(this.cons.contains(word) && (!keepTopicFlag)) { + // change the count for count and node_count in TopicTreeWalk + this.topics.changeCountOnly(topic, word, path, -1); + doc.topics.set(ii, -1); + doc.paths.set(ii, -1); + this.updateTopicCounts(doc.topicCounts, topic, -1, 0); + } + } else { // option.equals("doc") + if(!keepTopicFlag) { + this.topics.changeCountOnly(topic, word, path, -1); + doc.topics.set(ii, -1); + doc.paths.set(ii, -1); + this.updateTopicCounts(doc.topicCounts, topic, -1, 0); + } + } + } + } + +// for(int dd = 0; dd < this.data.size(); dd++) { +// DocData doc = this.data.get(dd); +// Boolean flag = false; +// for(int ii = 0; ii < doc.tokens.size(); ii++) { +// int word = doc.tokens.get(ii); +// int topic = doc.topics.get(ii); +// +// boolean keepTopicFlag = false; +// if(this.topickeep.containsKey(word)) { +// TIntHashSet keeptopics = this.topickeep.get(word); +// if(keeptopics.contains(topic)) { +// keepTopicFlag = true; +// } +// } +// +// if(this.cons.contains(word) && (!keepTopicFlag)) { +// if (option.equals("term")) { +// // change the count for count and node_count in TopicTreeWalk +// int path = doc.paths.get(ii); +// this.topics.changeCountOnly(topic, word, path, -1); +// doc.topics.set(ii, -1); +// doc.paths.set(ii, -1); +// //myAssert(doc.topicCounts.get(topic) >= 1, "clear topic assignments problem"); +// //doc.topicCounts.adjustValue(topic, -1); +// this.updateTopicCounts(doc.topicCounts, topic, -1, 0); +// } else if (option.equals("doc")) { +// flag = true; +// break; +// } +// } +// } +// if (flag) { +// for(int ii = 0; ii < doc.tokens.size(); ii++) { +// int word = doc.tokens.get(ii); +// int topic = doc.topics.get(ii); +// int path = doc.paths.get(ii); +// this.topics.changeCountOnly(topic, word, path, -1); +// doc.topics.set(ii, -1); +// doc.paths.set(ii, -1); +// } +// doc.topicCounts.clear(); +// } +// } + } + + /** + * This function defines how to change a topic during the sampling process. + * It handles the case where both new_topic and old_topic are "-1" (empty topic). + */ + public void changeTopic(int doc, int index, int word, int new_topic, int new_path) { + DocData current_doc = this.data.get(doc); + int old_topic = current_doc.topics.get(index); + int old_path = current_doc.paths.get(index); + + if (old_topic != -1) { + myAssert((new_topic == -1 && new_path == -1), "old_topic != -1 but new_topic != -1"); + this.topics.changeCount(old_topic, word, old_path, -1); + //myAssert(current_doc.topicCounts.get(old_topic) > 0, "Something wrong in changTopic"); + this.updateTopicCounts(current_doc.topicCounts, old_topic, -1, 0); + current_doc.topics.set(index, -1); + current_doc.paths.set(index, -1); + } + + if (new_topic != -1) { + myAssert((old_topic == -1 && old_path == -1), "new_topic != -1 but old_topic != -1"); + this.topics.changeCount(new_topic, word, new_path, 1); + this.updateTopicCounts(current_doc.topicCounts, new_topic, 1, 1); + current_doc.topics.set(index, new_topic); + current_doc.paths.set(index, new_path); + } + } + + /** + * The function computes the document likelihood. + */ + public double docLHood() { + int docNum = this.data.size(); + + double val = 0.0; + val += Dirichlet.logGamma(this.alphaSum) * docNum; + double tmp = 0.0; + for (int tt = 0; tt < this.numTopics; tt++) { + tmp += Dirichlet.logGamma(this.alpha[tt]); + } + val -= tmp * docNum; + for (int dd = 0; dd < docNum; dd++) { + DocData doc = this.data.get(dd); + + int[] tmpTopics = new int[this.numTopics]; + for(int ii = 0; ii < this.numTopics; ii++) { + tmpTopics[ii] = 0; + } + for(int ii = 0; ii < doc.topicCounts.size(); ii++) { + int[] current = doc.topicCounts.get(ii); + int tt = current[0]; + tmpTopics[tt] = current[1]; + } + for(int tt = 0; tt < tmpTopics.length; tt++) { + val += Dirichlet.logGamma(this.alpha[tt] + tmpTopics[tt]); + } + + val -= Dirichlet.logGamma(this.alphaSum + doc.topics.size()); + } + return val; + } + + /** + * Print the topic proportion for all documents. + */ + public void printDocumentTopics (File file) throws IOException { + PrintStream out = new PrintStream (file); + out.print ("#doc source topic proportion ...\n"); + + IDSorter[] sortedTopics = new IDSorter[ this.numTopics ]; + for (int topic = 0; topic < this.numTopics; topic++) { + // Initialize the sorters with dummy values + sortedTopics[topic] = new IDSorter(topic, topic); + } + + for (int dd = 0; dd < this.data.size(); dd++) { + DocData doc = this.data.get(dd); + + // compute topic proportion in one document + double sum = 0.0; + double[] prob = new double[this.numTopics]; + + // initialize + for (int topic=0; topic < this.numTopics; topic++) { + prob[topic] = -1; + } + + // topic counts + for (int ii = 0; ii < doc.topicCounts.size(); ii++) { + int[] current = doc.topicCounts.get(ii); + int topic = current[0]; + prob[topic] = this.alpha[topic] + current[1]; + } + + for (int topic=0; topic < this.numTopics; topic++) { + if (prob[topic] == -1) { + prob[topic] = this.alpha[topic]; + } + sum += prob[topic]; + } + + // normalize and sort + for (int topic=0; topic < this.numTopics; topic++) { + prob[topic] /= sum; + sortedTopics[topic].set(topic, prob[topic]); + } + Arrays.sort(sortedTopics); + + // print one document + out.print (dd); out.print (" "); + + if (doc.docName != null || !doc.docName.equals(" ")) { + out.print (doc.docName); + } else { + out.print ("null-source"); + } + out.print (" "); + for (int i = 0; i < numTopics; i++) { + out.print (sortedTopics[i].getID() + " " + + sortedTopics[i].getWeight() + " "); + } + out.print (" \n"); + } + out.close(); + } + + + + ///////////////////////////////////////////////////////////// + /** + * This function loads vocab, loads tree, and initialize parameters. + */ + public void initialize(String treeFiles, String hyperFile, String vocabFile, String removedwordsFile) { + this.loadVocab(vocabFile); + if (removedwordsFile != null) { + this.loadRemovedWords(removedwordsFile + ".all", this.removedWords); + this.loadRemovedWords(removedwordsFile + ".new", this.removedWordsNew); + } + this.topics.initializeParams(treeFiles, hyperFile, this.vocab); + } + + /** + * This function defines the sampling process, computes the likelihood and running time, + * and specifies when to save the states files. + */ + public void estimate(int numIterations, String outputFolder, int outputInterval, int topWords) { + // update parameters + this.topics.updateParams(); + + if (this.startIter > this.numIterations) { + System.out.println("Have already sampled " + this.numIterations + " iterations!"); + System.exit(0); + } + System.out.println("Start sampling for iteration " + this.startIter); + + for (int ii = this.startIter; ii <= numIterations; ii++) { + long starttime = System.currentTimeMillis(); + //System.out.println("Iter " + ii); + for (int dd = 0; dd < this.data.size(); dd++) { + this.sampleDoc(dd); + if (dd > 0 && dd % 10000 == 0) { + System.out.println("Sampled " + dd + " documents."); + } + } + double totaltime = (double)(System.currentTimeMillis() - starttime) / 1000; + double lhood = 0; + if ((ii > 0 && ii % outputInterval == 0) || ii == numIterations) { + lhood = this.lhood(); + } + this.lhood.add(lhood); + this.iterTime.add(totaltime); + + if (ii % 10 == 0) { + String tmp = "Iteration " + ii; + tmp += " likelihood " + lhood; + tmp += " totaltime " + totaltime; + System.out.println(tmp); + } + + if ((ii > 0 && ii % outputInterval == 0) || ii == numIterations) { + try { + this.report(outputFolder, topWords); + } catch (IOException e) { + System.out.println(e.getMessage()); + } + } + } + } + + ///////////////////////////////////////////////////////////// + + /** + * This function returns the likelihood. + */ + public double lhood() { + return this.docLHood() + this.topics.topicLHood(); + } + + /** + * By implementing the comparable interface, this function ranks the words + * in each topic, and returns the top words for each topic. + */ + public String displayTopWords (int numWords) { + + StringBuilder out = new StringBuilder(); + int numPaths = this.topics.getPathNum(); + //System.out.println(numPaths); + + for (int tt = 0; tt < this.numTopics; tt++){ + String tmp = "\n--------------\nTopic " + tt + "\n------------------------\n"; + //System.out.print(tmp); + out.append(tmp); + WordProb[] wp = new WordProb[numPaths]; + for (int pp = 0; pp < numPaths; pp++){ + int ww = this.topics.getWordFromPath(pp); + double val = this.topics.computeTopicPathProb(tt, ww, pp); + wp[pp] = new WordProb(pp, val); + } + Arrays.sort(wp); + for (int ii = 0; ii < wp.length; ii++){ + if(ii >= numWords) { + break; + } + int pp = wp[ii].wi; + int ww = this.topics.getWordFromPath(pp); + String word = this.vocab.get(ww); + if (this.removedWords.indexOf(word) == -1 && this.removedWordsNew.indexOf(word) == -1) { + tmp = wp[ii].p + "\t" + word + "\n"; + out.append(tmp); + } + } + } + return out.toString(); + } + + /** + * Prints the topic word distributions. + */ + public void printTopicWords (File file) throws IOException { + + PrintStream out = new PrintStream (file); + int numPaths = this.topics.getPathNum(); + String tmp; + + for (int tt = 0; tt < this.numTopics; tt++){ + + WordProb[] wp = new WordProb[numPaths]; + for (int pp = 0; pp < numPaths; pp++){ + int ww = this.topics.getWordFromPath(pp); + double val = this.topics.computeTopicPathProb(tt, ww, pp); + wp[pp] = new WordProb(pp, val); + } + Arrays.sort(wp); + for (int ii = 0; ii < wp.length; ii++){ + int pp = wp[ii].wi; + int ww = this.topics.getWordFromPath(pp); + String word = this.vocab.get(ww); + if (this.removedWords.indexOf(word) == -1 && this.removedWordsNew.indexOf(word) == -1) { + tmp = tt + "\t" + word + "\t" + wp[ii].p; + out.println(tmp); + } + } + } + out.close(); + } + + /** + * Prints the topic and path of each word for all documents. + */ + public void printState (File file) throws IOException { + //PrintStream out = + // new PrintStream(new GZIPOutputStream(new BufferedOutputStream(new FileOutputStream(file)))); + PrintStream out = new PrintStream(file); + + for (int dd = 0; dd < this.data.size(); dd++) { + DocData doc = this.data.get(dd); + String tmp = ""; + for (int ww = 0; ww < doc.topics.size(); ww++) { + int topic = doc.topics.get(ww); + int path = doc.paths.get(ww); + int wordpath = this.topics.pathToWordPath.get(path); + tmp += topic + ":" + wordpath + "\t"; + } + out.println(tmp); + } + out.close(); + } + + public TreeTopicInferencer getInferencer() { + //this.topics.updateParams(); + HashSet removedall = new HashSet (); + removedall.addAll(this.removedWords); + removedall.addAll(this.removedWordsNew); + TreeTopicInferencer inferencer = new TreeTopicInferencer(topics, vocab, removedall, alpha); + return inferencer; + } + + public TreeMarginalProbEstimator getProbEstimator() { + HashSet removedall = new HashSet (); + removedall.addAll(this.removedWords); + removedall.addAll(this.removedWordsNew); + TreeMarginalProbEstimator estimator = new TreeMarginalProbEstimator(topics, vocab, removedall, alpha); + return estimator; + } +} diff --git a/src/cc/mallet/topics/tree/Utils.java b/src/cc/mallet/topics/tree/Utils.java new file mode 100755 index 000000000..5a5c1db68 --- /dev/null +++ b/src/cc/mallet/topics/tree/Utils.java @@ -0,0 +1,82 @@ +package cc.mallet.topics.tree; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + + +public class Utils { + /** + * Add an item to a map of counts. + */ + public static void addToMap(Map map, String word) { + int count = 0; + if (map.containsKey(word)) + count = map.get(word); + map.put(word, count+1); + } + + /** + * Sort a map by value and return a list of the sorted keys. + * + * adapted from: + * http://www.programmersheaven.com/download/49349/download.aspx + * + */ + public static List sortByValue(Map map) { + List> list = new LinkedList>( + map.entrySet()); + Collections.sort(list, new Comparator() { + public int compare(Object o1, Object o2) { + return ((Comparable) ((Map.Entry) (o2)).getValue()) + .compareTo(((Map.Entry) (o1)).getValue()); + } + }); + // logger.info(list); + List result = new ArrayList(); + for (Iterator> it = list.iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + result.add(entry.getKey()); + } + return result; + } + + /** + * Read all the lines in a file and return them in a list. + */ + public static List readAll(String filename) throws Exception { + List lines = new ArrayList(); + BufferedReader reader = new BufferedReader(new FileReader(filename)); + String line = ""; + while ((line = reader.readLine()) != null) + lines.add(line); + reader.close(); + return lines; + } + + /** + * Converts a list of strings into a single space-separated string. + */ + public static String listToString(List words) { + String str = ""; + for (String word : words) { + str += " " + word; + } + return str.substring(1); + } + + /** + * Converts a space-separated string of words into list form. + */ + public static List stringToList(String str) { + String[] parts = str.toLowerCase().split("\\s+"); + return Arrays.asList(parts); + } +} diff --git a/src/cc/mallet/topics/tree/VocabGenerator.java b/src/cc/mallet/topics/tree/VocabGenerator.java new file mode 100755 index 000000000..8a6ea7360 --- /dev/null +++ b/src/cc/mallet/topics/tree/VocabGenerator.java @@ -0,0 +1,238 @@ +package cc.mallet.topics.tree; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; + +import gnu.trove.TIntArrayList; +import gnu.trove.TIntHashSet; +import gnu.trove.TIntIntHashMap; +import gnu.trove.TObjectDoubleHashMap; +import gnu.trove.TObjectIntHashMap; +import cc.mallet.topics.tree.TreeTopicSamplerHashD.DocData; +import cc.mallet.types.Alphabet; +import cc.mallet.types.FeatureSequence; +import cc.mallet.types.Instance; +import cc.mallet.types.InstanceList; +import cc.mallet.util.Maths; + + +/** + * This class generates the vocab file from mallet input. + * This generated vocab can be filtered by either frequency or tfidf. + * Tree-based topic model need this vocab for: + * (1) filter words more flexible + * (2) generate tree structure + * (3) allow removing words + * Main entrance: genVocab() + * + * @author Yuening Hu + */ + +public class VocabGenerator { + public static TObjectDoubleHashMap getIdf(InstanceList data) { + // get idf + TObjectDoubleHashMap idf = new TObjectDoubleHashMap (); + + for (Instance instance : data) { + FeatureSequence original_tokens = (FeatureSequence) instance.getData(); + HashSet words = new HashSet(); + for (int jj = 0; jj < original_tokens.getLength(); jj++) { + String word = (String) original_tokens.getObjectAtPosition(jj); + words.add(word); + } + for(String word : words) { + idf.adjustOrPutValue(word, 1, 1); + } + } + + int D = data.size(); + for(Object ob : idf.keys()){ + String word = (String) ob; + double value = D / (1 + idf.get(word)); + value = Math.log(value) - idf.get(word); + idf.adjustValue(word, value); + } + + System.out.println("Idf size: " + idf.size()); + return idf; + } + + public static TObjectDoubleHashMap computeTfidf(InstanceList data) { + // get idf + TObjectDoubleHashMap idf = getIdf(data); + + // compute tf-idf for each word + HashMap> tfidf = new HashMap> (); + + for (Instance instance : data) { + FeatureSequence original_tokens = (FeatureSequence) instance.getData(); + TObjectIntHashMap tf = new TObjectIntHashMap(); + for (int jj = 0; jj < original_tokens.getLength(); jj++) { + String word = (String) original_tokens.getObjectAtPosition(jj); + tf.adjustOrPutValue(word, 1, 1); + } + for(Object ob : tf.keys()) { + String word = (String) ob; + HashSet values; + if (tfidf.containsKey(word)) { + values = tfidf.get(word); + } else { + values = new HashSet (); + tfidf.put(word, values); + } + double value = tf.get(word) * idf.get(word); + values.add(value); + } + } + + // averaged tfidf + TObjectDoubleHashMap vocabtfidf = new TObjectDoubleHashMap(); + for(String word : tfidf.keySet()) { + double sum = 0; + int count = tfidf.get(word).size(); + for(double value : tfidf.get(word)) { + sum += value; + } + sum = sum / count; + vocabtfidf.put(word, sum); + } + + System.out.println("vocab tfidf size: " + vocabtfidf.size()); + return vocabtfidf; + } + + public static TObjectDoubleHashMap getFrequency (InstanceList data) { + TObjectDoubleHashMap freq = new TObjectDoubleHashMap (); + Alphabet alphabet = data.getAlphabet(); + for(int ii = 0; ii < alphabet.size(); ii++) { + String word = alphabet.lookupObject(ii).toString(); + freq.put(word, 0); + } + + for (Instance instance : data) { + FeatureSequence original_tokens = (FeatureSequence) instance.getData(); + for (int jj = 0; jj < original_tokens.getLength(); jj++) { + String word = (String) original_tokens.getObjectAtPosition(jj); + freq.adjustValue(word, 1); + } + } + + System.out.println("Alphabet size: " + alphabet.size()); + System.out.println("Frequency size: " + freq.size()); + return freq; + } + + public static void genVocab_all(InstanceList data, String vocab, Boolean tfidfRank, double tfidfthresh, double freqthresh, double wordlength) { + //public static void genVocab(InstanceList data, String vocab) { + try{ + File file = new File(vocab); + PrintStream out = new PrintStream (file); + + int language_id = 0; + Alphabet alphabet = data.getAlphabet(); + for(int ii = 0; ii < alphabet.size(); ii++) { + String word = alphabet.lookupObject(ii).toString(); + System.out.println(word); + out.println(language_id + "\t" + word); + } + out.close(); + } catch (IOException e) { + e.getMessage(); + } + + } + + /** + * After the preprocessing of mallet, a vocab is needed to generate + * the prior tree. So this function simply read in the alphabet + * of the training data, filter the words either by frequency or tfidf, + * then output the vocab. + * Currently, the language_id is fixed. + */ + public static void genVocab(InstanceList[] data, String vocab, Boolean tfidfRank, double tfidfthresh, double freqthresh, double wordlength) { + + class WordCount implements Comparable { + String word; + double value; + public WordCount (String word, double value) { this.word = word; this.value = value; } + public final int compareTo (Object o2) { + if (value > ((WordCount)o2).value) + return -1; + else if (value == ((WordCount)o2).value) + return 0; + else return 1; + } + } + + + try{ + File file = new File(vocab); + PrintStream out = new PrintStream (file, "UTF8"); + + HashSet allwords = new HashSet (); + for (int ll = 0; ll < data.length; ll++) { + System.out.println("Language " + ll); + TObjectDoubleHashMap freq = getFrequency(data[ll]); + TObjectDoubleHashMap tfidf = computeTfidf(data[ll]); + TObjectDoubleHashMap selected; + if (tfidfRank) { + selected = tfidf; + } else { + selected = freq; + } + + WordCount[] array = new WordCount[selected.keys().length]; + int index = -1; + for(Object o : selected.keys()) { + String word = (String)o; + double count = selected.get(word); + index++; + array[index] = new WordCount(word, count); + } + System.out.println("Array size: " + array.length); + Arrays.sort(array); + System.out.println("After sort array size: " + array.length); + + int language_id = ll; + int count = 0; + for(int ii = 0; ii < array.length; ii++) { + String word = array[ii].word; + if (word.length() >= wordlength && tfidf.get(word) > tfidfthresh && freq.get(word) > freqthresh) { + if (allwords.contains(word)) { + continue; + } + allwords.add(word); + out.println(language_id + "\t" + array[ii].word + "\t" + tfidf.get(word) + "\t" + (int)freq.get(word)); + count++; + } + } + System.out.println("Filtered vocab size: " + count); + System.out.println("*******************"); + } + out.close(); + + } catch (IOException e) { + e.getMessage(); + } + } + + public static void main(String[] args) { + //String input = "input/synthetic-topic-input.mallet"; + //String vocab = "input/synthetic.voc"; + + String input = "../../itm-evaluation/results/fbis-itm/input/fbis-itm-topic-input.mallet"; + String vocab = "../../itm-evaluation/results/fbis-itm/input/fbis-itm.voc"; + + InstanceList[] instances = new InstanceList[ 2 ]; + InstanceList data = InstanceList.load (new File(input)); + instances[0] = data; + InstanceList data1 = InstanceList.load (new File(input)); + instances[1] = data1; + genVocab(instances, vocab, true, 1, 10, 3); + System.out.println("Done!"); + } +} diff --git a/src/cc/mallet/topics/tree/testFast.java b/src/cc/mallet/topics/tree/testFast.java new file mode 100755 index 000000000..072c96563 --- /dev/null +++ b/src/cc/mallet/topics/tree/testFast.java @@ -0,0 +1,249 @@ +package cc.mallet.topics.tree; + +import gnu.trove.TIntArrayList; +import gnu.trove.TIntDoubleHashMap; +import gnu.trove.TIntIntHashMap; + +import java.io.File; +import java.util.ArrayList; + +import cc.mallet.topics.tree.TreeTopicSamplerHashD.DocData; +import cc.mallet.types.InstanceList; +import junit.framework.TestCase; + +/** + * This class tests the fast sampler. + * @author Yuening Hu + */ + +public class testFast extends TestCase{ + + public TreeTopicSamplerFast Initialize() { + + String inputFile = "input/toy/toy-topic-input.mallet"; + String treeFiles = "input/toy/toy.wn.*"; + String hyperFile = "input/toy/tree_hyperparams"; + String vocabFile = "input/toy/toy.voc"; + String removedFile = "input/toy/removed"; + int numTopics = 3; + double alpha_sum = 0.3; + int randomSeed = 0; + int numIterations = 10; + +// String inputFile = "../input/synthetic-topic-input.mallet"; +// String treeFiles = "../synthetic/synthetic_empty.wn.*"; +// String hyperFile = "../synthetic/tree_hyperparams"; +// String vocabFile = "../synthetic/synthetic.voc"; +// int numTopics = 5; +// double alpha_sum = 0.5; +// int randomSeed = 0; +// int numIterations = 10; + + InstanceList[] instances = new InstanceList[1]; + InstanceList ilist = InstanceList.load (new File(inputFile)); + System.out.println ("Data loaded."); + instances[0] = ilist; + + TreeTopicSamplerFast topicModel = null; + topicModel = new TreeTopicSamplerFast(numTopics, alpha_sum, randomSeed, false); + + topicModel.initialize(treeFiles, hyperFile, vocabFile, removedFile); + topicModel.addInstances(instances); + + topicModel.setNumIterations(numIterations); + + return topicModel; + } + + public void testUpdateParams() { + TreeTopicSamplerFast topicModel = this.Initialize(); + topicModel.topics.updateParams(); + + for(int dd = 0; dd < topicModel.data.size(); dd++) { + System.out.println(topicModel.data.get(dd)); + } + + System.out.println("**************\nNormalizer"); + int numPaths = topicModel.topics.pathToWord.size(); + for(int tt = 0; tt < topicModel.numTopics; tt++) { + for(int pp = 0; pp < numPaths; pp++) { + System.out.println("topic " + tt + " path " + pp + " normalizer " + topicModel.topics.normalizer.get(tt, pp)); + } + } + + System.out.println("**************\nNon zero paths"); + for(int ww : topicModel.topics.nonZeroPaths.keys()) { + for(int tt : topicModel.topics.nonZeroPaths.get(ww).getKey1Set()) { + for(int pp : topicModel.topics.nonZeroPaths.get(ww).get(tt).keys()) { + System.out.println("word " + ww + " topic " + tt + " path " + pp + " " + topicModel.topics.nonZeroPaths.get(ww).get(tt, pp)); + } + } + } + } + + public void testUpdatePathmaskedCount() { + TreeTopicSamplerFast topicModel = this.Initialize(); + topicModel.topics.updateParams(); + int numPaths = topicModel.topics.pathToWord.size(); + + TreeTopicModelFast topics = (TreeTopicModelFast)topicModel.topics; + + for (int ww : topics.nonZeroPaths.keys()) { + for(int tt : topics.nonZeroPaths.get(ww).getKey1Set()) { + for(int pp : topicModel.topics.nonZeroPaths.get(ww).get(tt).keys()) { + TIntArrayList path_nodes = topics.wordPaths.get(ww, pp); + int parent = path_nodes.get(path_nodes.size() - 2); + int child = path_nodes.get(path_nodes.size() - 1); + + int mask = topics.nonZeroPaths.get(ww).get(tt, pp) - topics.traversals.get(tt).getCount(parent, child); + + System.out.println("*************************"); + System.out.println("Topic " + tt + " Word " + ww + " path " + pp); + String tmp = "["; + for (int ii : path_nodes.toNativeArray()) { + tmp += " " + ii; + } + System.out.println("Real path " + tmp + " ]"); + System.out.println("Real count " + topics.traversals.get(tt).getCount(parent, child)); + System.out.println("Masked count " + topics.nonZeroPaths.get(ww).get(tt, pp)); + System.out.println("Masekd count " + Integer.toBinaryString(topics.nonZeroPaths.get(ww).get(tt, pp))); + System.out.println("*************************"); + } + } + } + } + + public void testChangeTopic() { + TreeTopicSamplerFast topicModel = this.Initialize(); + topicModel.topics.updateParams(); + TreeTopicModelFast topics = (TreeTopicModelFast)topicModel.topics; + //for(int dd = 0; dd < topicModel.data.size(); dd++){ + for(int dd = 0; dd < 1; dd++){ + DocData doc = topicModel.data.get(dd); + for(int ii = 0; ii < doc.tokens.size(); ii++) { + int word = doc.tokens.get(ii); + int old_topic = doc.topics.get(ii); + int old_path = doc.paths.get(ii); + TIntArrayList path_nodes = topicModel.topics.wordPaths.get(word, old_path); + int node = path_nodes.get(0); + int leaf = path_nodes.get(path_nodes.size() - 1); + int total = 0; + for(int nn : topics.traversals.get(word).counts.get(node).keys()){ + total += topics.traversals.get(word).getCount(node, nn); + } + + assertTrue(topics.traversals.get(word).getNodeCount(node) == total); + + System.out.println("*************************"); + System.out.println("old topic " + old_topic + " word " + word); + System.out.println("old normalizer " + topics.normalizer.get(old_topic, old_path)); + System.out.println("old root count " + topics.traversals.get(old_topic).getNodeCount(node) + " " + total); + System.out.println("old non zero count " + Integer.toBinaryString(topics.nonZeroPaths.get(word).get(old_topic, old_path))); + System.out.println("old leaf count " + topics.traversals.get(old_topic).getNodeCount(leaf)); + + topicModel.changeTopic(dd, ii, word, -1, -1); + + total = 0; + for(int nn : topics.traversals.get(old_topic).counts.get(node).keys()){ + total += topics.traversals.get(old_topic).getCount(node, nn); + } + assertTrue(topics.traversals.get(old_topic).getNodeCount(node) == total); + System.out.println("*************************"); + System.out.println("updated old topic " + old_topic + " word " + word); + System.out.println("updated old normalizer " + topics.normalizer.get(old_topic, old_path)); + System.out.println("updated old root count " + topics.traversals.get(old_topic).getNodeCount(node) + " " + total); + System.out.println("updated old non zero count " + Integer.toBinaryString(topics.nonZeroPaths.get(word).get(old_topic, old_path))); + System.out.println("updated old leaf count " + topics.traversals.get(old_topic).getNodeCount(leaf)); + + + int new_topic = topicModel.numTopics - old_topic - 1; + int new_path = old_path; + + total = 0; + for(int nn : topics.traversals.get(new_topic).counts.get(node).keys()){ + total += topics.traversals.get(new_topic).getCount(node, nn); + } + assertTrue(topics.traversals.get(new_topic).getNodeCount(node) == total); + + System.out.println("*************************"); + System.out.println("new topic " + new_topic + " word " + word); + System.out.println("new normalizer " + topics.normalizer.get(new_topic, new_path)); + System.out.println("new root count " + topics.traversals.get(new_topic).getNodeCount(node) + " " + total); + System.out.println("new non zero count " + Integer.toBinaryString(topics.nonZeroPaths.get(word).get(new_topic, new_path))); + System.out.println("new leaf count " + topics.traversals.get(new_topic).getNodeCount(leaf)); + + topicModel.changeTopic(dd, ii, word, new_topic, new_path); + + + total = 0; + for(int nn : topics.traversals.get(new_topic).counts.get(node).keys()){ + total += topics.traversals.get(new_topic).getCount(node, nn); + } + assertTrue(topics.traversals.get(new_topic).getNodeCount(node) == total); + System.out.println("*************************"); + System.out.println("updated new topic " + new_topic + " word " + word); + System.out.println("updated new normalizer " + topics.normalizer.get(new_topic, new_path)); + System.out.println("updated new root count " + topics.traversals.get(new_topic).getNodeCount(node) + " " + total); + System.out.println("updated new non zero count " + Integer.toBinaryString(topics.nonZeroPaths.get(word).get(new_topic, new_path))); + System.out.println("updated new leaf count " + topics.traversals.get(new_topic).getNodeCount(leaf)); + + System.out.println("*************************\n"); + } + } + } + + public void testBinValues() { + TreeTopicSamplerFast topicModelFast = this.Initialize(); + topicModelFast.topics.updateParams(); + + TreeTopicSamplerNaive topicModelNaive = testNaive.Initialize(); + topicModelNaive.topics.updateParams(); + + //for(int dd = 0; dd < topicModelFast.data.size(); dd++){ + for(int dd = 0; dd < 1; dd++){ + DocData doc = topicModelFast.data.get(dd); + DocData doc1 = topicModelNaive.data.get(dd); + + //for(int ii = 0; ii < doc.tokens.size(); ii++) { + for(int ii = 4; ii < 5; ii++) { + int word = doc.tokens.get(ii); + int topic = doc.topics.get(ii); + int path = doc.paths.get(ii); + + double smoothing = topicModelFast.callComputeTermSmoothing(word); + double topicbeta = topicModelFast.callComputeTermTopicBeta(doc.topicCounts, word); + ArrayList dict = new ArrayList(); + double topictermscore = topicModelFast.topics.computeTopicTerm(topicModelFast.alpha, + doc.topicCounts, word, dict); + double norm = smoothing + topicbeta + topictermscore; + + double smoothing1 = topicModelFast.computeTopicSmoothTest(word); + double topicbeta1 = topicModelFast.computeTopicTermBetaTest(doc.topicCounts, word); + HIntIntDoubleHashMap dict1 = new HIntIntDoubleHashMap(); + double topictermscore1 = topicModelFast.computeTopicTermScoreTest(topicModelFast.alpha, + doc.topicCounts, word, dict1); + double norm1 = smoothing1 + topicbeta1 + topictermscore1; + + System.out.println("*************"); + System.out.println("Index " + ii); + System.out.println(smoothing + " " + smoothing1); + System.out.println(topicbeta + " " + topicbeta1); + System.out.println(topictermscore + " " + topictermscore1); + + ArrayList dict2 = new ArrayList(); + double norm2 = topicModelFast.computeTopicTermTest(topicModelNaive.alpha, doc.topicCounts, word, dict2); + + ArrayList dict3 = new ArrayList(); + double norm3 = topicModelNaive.topics.computeTopicTerm(topicModelNaive.alpha, doc.topicCounts, word, dict3); + + System.out.println(norm + " " + norm1 + " " + norm2 + " " + norm3); +// if (norm1 != norm2) { +// System.out.println(norm + " " + norm1 + " " + norm2 + " " + norm3 ); +// } + System.out.println("*************"); + assert(norm == norm1); + assert(1 == 0); + } + } + } +} diff --git a/src/cc/mallet/topics/tree/testNaive.java b/src/cc/mallet/topics/tree/testNaive.java new file mode 100755 index 000000000..84143b62c --- /dev/null +++ b/src/cc/mallet/topics/tree/testNaive.java @@ -0,0 +1,157 @@ +package cc.mallet.topics.tree; + +import gnu.trove.TIntArrayList; +import gnu.trove.TIntIntHashMap; + +import java.io.File; +import java.util.ArrayList; + +import cc.mallet.topics.tree.TreeTopicSamplerHashD.DocData; +import cc.mallet.types.Alphabet; +import cc.mallet.types.FeatureSequence; +import cc.mallet.types.Instance; +import cc.mallet.types.InstanceList; +import junit.framework.TestCase; + +/** + * This class tests the naive sampler. + * @author Yuening Hu + */ + +public class testNaive extends TestCase{ + + public static TreeTopicSamplerNaive Initialize() { + + String inputFile = "input/toy/toy-topic-input.mallet"; + String treeFiles = "input/toy/toy.wn.*"; + String hyperFile = "input/toy/tree_hyperparams"; + String vocabFile = "input/toy/toy.voc"; + String removedFile = "input/toy/removed"; + int numTopics = 3; + double alpha_sum = 0.3; + int randomSeed = 0; + int numIterations = 10; + +// String inputFile = "../input/synthetic-topic-input.mallet"; +// String treeFiles = "../synthetic/synthetic.wn.*"; +// String hyperFile = "../synthetic/tree_hyperparams"; +// String vocabFile = "../synthetic/synthetic.voc"; +// int numTopics = 5; +// double alpha_sum = 0.5; +// int randomSeed = 0; +// int numIterations = 10; + + InstanceList[] instances = new InstanceList[1]; + InstanceList ilist = InstanceList.load (new File(inputFile)); + System.out.println ("Data loaded."); + instances[0] = ilist; + + TreeTopicSamplerNaive topicModel = null; + topicModel = new TreeTopicSamplerNaive(numTopics, alpha_sum, randomSeed); + + topicModel.initialize(treeFiles, hyperFile, vocabFile, removedFile); + topicModel.addInstances(instances); + + topicModel.setNumIterations(numIterations); + + return topicModel; + } + + public void testChangeTopic() { + TreeTopicSamplerNaive topicModel = this.Initialize(); + for (int dd = 0; dd < topicModel.data.size(); dd++ ) { + DocData doc = topicModel.data.get(dd); + for (int index = 0; index < doc.tokens.size(); index++) { + int word = doc.tokens.get(index); + int old_topic = doc.topics.get(index); + int old_path = doc.paths.get(index); + int old_count = doc.topicCounts.get(old_topic); + + topicModel.changeTopic(dd, index, word, -1, -1); + assertTrue(doc.topics.get(index) == -1); + assertTrue(doc.paths.get(index) == -1); + assertTrue(doc.topicCounts.get(old_topic) == old_count-1); + + int new_topic = topicModel.numTopics - old_topic - 1; + int new_path = old_path; + int new_count = doc.topicCounts.get(new_topic); + topicModel.changeTopic(dd, index, word, new_topic, new_path); + + assertTrue(doc.topics.get(index) == new_topic); + assertTrue(doc.paths.get(index) == new_path); + assertTrue(doc.topicCounts.get(new_topic) == new_count+1); + } + } + } + + public void testChangCount() { + TreeTopicSamplerNaive topicModel = this.Initialize(); + for (int dd = 0; dd < topicModel.data.size(); dd++ ) { + DocData doc = topicModel.data.get(dd); + + for (int index = 0; index < doc.tokens.size(); index++) { + int word = doc.tokens.get(index); + int old_topic = doc.topics.get(index); + int old_path = doc.paths.get(index); + + TopicTreeWalk tw = topicModel.topics.traversals.get(old_topic); + TIntArrayList path_nodes = topicModel.topics.wordPaths.get(word, old_path); + + int[] old_count = new int[path_nodes.size() - 1]; + for(int nn = 0; nn < path_nodes.size() - 1; nn++) { + int parent = path_nodes.get(nn); + int child = path_nodes.get(nn+1); + old_count[nn] = tw.getCount(parent, child); + } + + int[] old_node_count = new int[path_nodes.size()]; + for(int nn = 0; nn < path_nodes.size(); nn++) { + int node = path_nodes.get(nn); + old_node_count[nn] = tw.getNodeCount(node); + } + + int inc = 1; + tw.changeCount(path_nodes, inc); + + for(int nn = 0; nn < path_nodes.size() - 1; nn++) { + int parent = path_nodes.get(nn); + int child = path_nodes.get(nn+1); + assertTrue(old_count[nn] == tw.getCount(parent, child) - inc); + } + + for(int nn = 0; nn < path_nodes.size(); nn++) { + int node = path_nodes.get(nn); + assertTrue(old_node_count[nn] == tw.getNodeCount(node) - inc); + } + + } + } + + } + + public void testComputeTermScore() { + TreeTopicSamplerNaive topicModel = this.Initialize(); + for (int dd = 0; dd < topicModel.data.size(); dd++ ) { + DocData doc = topicModel.data.get(dd); + System.out.println("------------" + dd + "------------"); + for (int index = 0; index < doc.tokens.size(); index++) { + int word = doc.tokens.get(index); + + //topicModel.changeTopic(dd, index, word, -1, -1); + + ArrayList topic_term_score = new ArrayList(); + double norm = topicModel.topics.computeTopicTerm(topicModel.alpha, doc.topicCounts, word, topic_term_score); + System.out.println(norm); + + for(int jj = 0; jj < topic_term_score.size(); jj++) { + double[] tmp = topic_term_score.get(jj); + int tt = (int) tmp[0]; + int pp = (int) tmp[1]; + double val = tmp[2]; + System.out.println(tt + " " + pp + " " + val); + } + } + } + } + +} diff --git a/src/cc/mallet/topics/tui/EvaluateTreeTopics.java b/src/cc/mallet/topics/tui/EvaluateTreeTopics.java new file mode 100644 index 000000000..ce17fdf5b --- /dev/null +++ b/src/cc/mallet/topics/tui/EvaluateTreeTopics.java @@ -0,0 +1,96 @@ +package cc.mallet.topics.tui; + +import java.io.File; +import java.io.PrintStream; + +import cc.mallet.topics.MarginalProbEstimator; +import cc.mallet.topics.tree.TreeMarginalProbEstimator; +import cc.mallet.topics.tree.TreeTopicInferencer; +import cc.mallet.types.InstanceList; +import cc.mallet.util.CommandOption; + +public class EvaluateTreeTopics { + // common options in mallet + + static CommandOption.String inputFile = new CommandOption.String + (EvaluateTreeTopics.class, "input", "FILENAME", true, null, + "The filename from which to read the list of testing instances. Use - for stdin. " + + "The instances must be FeatureSequence or FeatureSequenceWithBigrams, not FeatureVector", null); + + static CommandOption.Integer randomSeed = new CommandOption.Integer + (EvaluateTreeTopics.class, "random-seed", "INTEGER", true, 0, + "The random seed for the Gibbs sampler. Default is 0, which will use the clock.", null); + + static CommandOption.String evaluatorFilename = new CommandOption.String + (EvaluateTreeTopics.class, "evaluator", "FILENAME", true, null, + "A serialized topic evaluator from a trained topic model.\n" + + "By default this is null, indicating that no file will be read.", null); + + static CommandOption.String docProbabilityFile = new CommandOption.String + (EvaluateTreeTopics.class, "output-doc-probs", "FILENAME", true, null, + "The filename in which to write the inferred log probabilities\n" + + "per document. " + + "By default this is null, indicating that no file will be written.", null); + + static CommandOption.String probabilityFile = new CommandOption.String + (EvaluateTreeTopics.class, "output-prob", "FILENAME", true, "-", + "The filename in which to write the inferred log probability of the testing set\n" + + "Use - for stdout, which is the default.", null); + + static CommandOption.Integer numParticles = new CommandOption.Integer + (EvaluateTreeTopics.class, "num-particles", "INTEGER", true, 10, + "The number of particles to use in left-to-right evaluation.", null); + + static CommandOption.Boolean usingResampling = new CommandOption.Boolean + (EvaluateTreeTopics.class, "use-resampling", "TRUE|FALSE", false, false, + "Whether to resample topics in left-to-right evaluation. Resampling is more accurate, but leads to quadratic scaling in the lenght of documents.", null); + + + public static void main (String[] args) throws java.io.IOException { + // Process the command-line options + CommandOption.setSummary (EvaluateTreeTopics.class, + "Estimate the marginal probability of new documents."); + CommandOption.process (EvaluateTreeTopics.class, args); + + if (evaluatorFilename.value == null) { + System.err.println("You must specify a serialized topic evaluator. Use --help to list options."); + System.exit(0); + } + + if (inputFile.value == null) { + System.err.println("You must specify a serialized instance list. Use --help to list options."); + System.exit(0); + } + + try { + + PrintStream docProbabilityStream = null; + if (docProbabilityFile.value != null) { + docProbabilityStream = new PrintStream(docProbabilityFile.value); + } + + PrintStream outputStream = System.out; + if (probabilityFile.value != null && + ! probabilityFile.value.equals("-")) { + outputStream = new PrintStream(probabilityFile.value); + } + + TreeMarginalProbEstimator evaluator = + TreeMarginalProbEstimator.read(new File(evaluatorFilename.value)); + + InstanceList instances = InstanceList.load (new File(inputFile.value)); + + evaluator.setRandomSeed(randomSeed.value); + + outputStream.println(evaluator.evaluateLeftToRight(instances, numParticles.value, + usingResampling.value, + docProbabilityStream)); + + + } catch (Exception e) { + e.printStackTrace(); + System.err.println(e.getMessage()); + } + + } +} diff --git a/src/cc/mallet/topics/tui/GenerateTree.java b/src/cc/mallet/topics/tui/GenerateTree.java new file mode 100644 index 000000000..d87df11e0 --- /dev/null +++ b/src/cc/mallet/topics/tui/GenerateTree.java @@ -0,0 +1,42 @@ +package cc.mallet.topics.tui; + +import java.io.File; +import cc.mallet.topics.tree.OntologyWriter; +import cc.mallet.util.CommandOption; + +public class GenerateTree { + + static CommandOption.String vocabFile = new CommandOption.String + (GenerateTree.class, "vocab", "FILENAME", true, null, + "The vocabulary file.", null); + + static CommandOption.String treeFiles = new CommandOption.String + (GenerateTree.class, "tree", "FILENAME", true, null, + "The files for tree structure.", null); + + static CommandOption.String consFile = new CommandOption.String + (GenerateTree.class, "constraint", "FILENAME", true, null, + "The constraint file.", null); + + static CommandOption.Boolean mergeCons = new CommandOption.Boolean + (GenerateTree.class, "merge-constraints", "true|false", false, true, + "Merge constraints or not. For example, if you want to merge A and B, " + + "and merge B and C and set merge-constraints as true, the new constraint" + + "will be merge A, B and C.", null); + + public static void main (String[] args) throws java.io.IOException { + // Process the command-line options + CommandOption.setSummary (GenerateTree.class, + "Generate a prior tree structure for LDA, in proto buffer format."); + CommandOption.process (GenerateTree.class, args); + + try { + OntologyWriter.createOntology(consFile.value, vocabFile.value, + treeFiles.value, mergeCons.value); + } catch (Exception e) { + e.printStackTrace(); + } + + } + +} diff --git a/src/cc/mallet/topics/tui/GenerateVocab.java b/src/cc/mallet/topics/tui/GenerateVocab.java new file mode 100644 index 000000000..1ab186349 --- /dev/null +++ b/src/cc/mallet/topics/tui/GenerateVocab.java @@ -0,0 +1,58 @@ +package cc.mallet.topics.tui; + +import java.io.File; + +import cc.mallet.topics.tree.VocabGenerator; +import cc.mallet.types.InstanceList; +import cc.mallet.util.CommandOption; + +public class GenerateVocab { + + // common options in mallet + static CommandOption.SpacedStrings inputFile = new CommandOption.SpacedStrings + (GenerateVocab.class, "input", "FILENAME [FILENAME ...]", true, null, + "The filename from which to read the list of training instances. " + + "Support multiple languages, each language should have its own file. " + + "The instances must be FeatureSequence or FeatureSequenceWithBigrams, not FeatureVector", null); + + static CommandOption.String vocabFile = new CommandOption.String + (GenerateVocab.class, "vocab", "FILENAME", true, null, + "The vocabulary file.", null); + + static CommandOption.Boolean tfidfRank = new CommandOption.Boolean + (GenerateVocab.class, "tfidf-rank", "true|false", false, true, + "Rank vocab by the averaged tfidf of words, or by frequency.", null); + + static CommandOption.Double tfidfThresh = new CommandOption.Double + (GenerateVocab.class, "tfidf-thresh", "DECIMAL", true, 1.0, + "The thresh for tfidf to filter out words.",null); + + static CommandOption.Double freqThresh = new CommandOption.Double + (GenerateVocab.class, "freq-thresh", "DECIMAL", true, 1.0, + "The thresh for frequency to filter out words.",null); + + static CommandOption.Double wordLength = new CommandOption.Double + (GenerateVocab.class, "word-length", "DECIMAL", true, 3.0, + "Keep words with length equal or large than the thresh.",null); + + + public static void main (String[] args) throws java.io.IOException { + // Process the command-line options + CommandOption.setSummary (GenerateVocab.class, + "Filtering words by tfidf, frequency, word-length, and generate the vocab."); + CommandOption.process (GenerateVocab.class, args); + + int numLanguages = inputFile.value.length; + InstanceList[] instances = new InstanceList[ numLanguages ]; + for (int i=0; i < instances.length; i++) { + instances[i] = InstanceList.load(new File(inputFile.value[i])); + System.out.println ("Data " + i + " loaded. Total number of documents: " + instances[i].size()); + } + + + VocabGenerator.genVocab(instances, vocabFile.value, tfidfRank.value, tfidfThresh.value, + freqThresh.value, wordLength.value); + + } + +} diff --git a/src/cc/mallet/topics/tui/InferTreeTopics.java b/src/cc/mallet/topics/tui/InferTreeTopics.java new file mode 100755 index 000000000..ea0efa4bc --- /dev/null +++ b/src/cc/mallet/topics/tui/InferTreeTopics.java @@ -0,0 +1,86 @@ +package cc.mallet.topics.tui; + +import java.io.File; + +import cc.mallet.topics.TopicInferencer; +import cc.mallet.topics.tree.VocabGenerator; +import cc.mallet.topics.tree.OntologyWriter; +import cc.mallet.topics.tree.TreeTopicInferencer; +import cc.mallet.topics.tree.TreeTopicSamplerFast; +import cc.mallet.topics.tree.TreeTopicSamplerFastEst; +import cc.mallet.topics.tree.TreeTopicSamplerFastEstSortD; +import cc.mallet.topics.tree.TreeTopicSamplerFastSortD; +import cc.mallet.topics.tree.TreeTopicSamplerInterface; +import cc.mallet.topics.tree.TreeTopicSamplerNaive; +import cc.mallet.types.InstanceList; +import cc.mallet.util.CommandOption; + +public class InferTreeTopics { + + // common options in mallet + + static CommandOption.String inputFile = new CommandOption.String + (InferTreeTopics.class, "input", "FILENAME", true, null, + "The filename from which to read the list of testing instances. Use - for stdin. " + + "The instances must be FeatureSequence or FeatureSequenceWithBigrams, not FeatureVector", null); + + static CommandOption.Integer numIterations = new CommandOption.Integer + (InferTreeTopics.class, "num-iterations", "INTEGER", true, 1000, + "The number of iterations of Gibbs sampling.", null); + + static CommandOption.String inferencerFilename = new CommandOption.String + (InferTreeTopics.class, "inferencer", "FILENAME", true, null, + "A topic inferencer applies a previously trained topic model to new documents." + + "By default this is null, indicating that no file will be written.", null); + + static CommandOption.String docTopicsFile = new CommandOption.String + (InferTreeTopics.class, "output-doc-topics", "FILENAME", true, null, + "The filename in which to write the inferred topic\n" + + "proportions per document. " + + "By default this is null, indicating that no file will be written.", null); + + static CommandOption.Integer randomSeed = new CommandOption.Integer + (InferTreeTopics.class, "random-seed", "INTEGER", true, 0, + "The random seed for the Gibbs sampler. Default is 0, which will use the clock.", null); + + static CommandOption.Integer outputInteval = new CommandOption.Integer + (InferTreeTopics.class, "output-interval", "INTEGER", true, 20, + "For each interval, the result files are output to the outputFolder.", null); + + + public static void main (String[] args) throws java.io.IOException { + // Process the command-line options + CommandOption.setSummary (InferTreeTopics.class, + "A tool for estimating, saving and printing diagnostics for topic models, such as LDA."); + CommandOption.process (InferTreeTopics.class, args); + + if (inferencerFilename.value == null) { + System.err.println("You must specify a serialized topic inferencer. Use --help to list options."); + System.exit(0); + } + + if (inputFile.value == null) { + System.err.println("You must specify a serialized instance list. Use --help to list options."); + System.exit(0); + } + + try { + InstanceList testlist = InstanceList.load (new File(inputFile.value)); + System.out.println ("Test data loaded."); + + TreeTopicInferencer inferencer = TreeTopicInferencer.read(new File(inferencerFilename.value)); + System.out.println("Inferencer loaded."); + + inferencer.setRandomSeed(randomSeed.value); + + inferencer.writeInferredDistributions(testlist, new File(docTopicsFile.value), + numIterations.value, outputInteval.value); + + } catch (Exception e) { + e.printStackTrace(); + System.err.println(e.getMessage()); + } + + } + +} diff --git a/src/cc/mallet/topics/tui/Vectors2TreeTopics.java b/src/cc/mallet/topics/tui/Vectors2TreeTopics.java new file mode 100755 index 000000000..a3a5d6764 --- /dev/null +++ b/src/cc/mallet/topics/tui/Vectors2TreeTopics.java @@ -0,0 +1,260 @@ +/* Copyright (C) 2005 Univ. of Massachusetts Amherst, Computer Science Dept. + This file is part of "MALLET" (MAchine Learning for LanguagE Toolkit). + http://www.cs.umass.edu/~mccallum/mallet + This software is provided under the terms of the Common Public License, + version 1.0, as published by http://www.opensource.org. For further + information, see the file `LICENSE' included with this distribution. */ + +package cc.mallet.topics.tui; + +import cc.mallet.util.CommandOption; +import cc.mallet.types.InstanceList; +import cc.mallet.topics.tree.TreeMarginalProbEstimator; +import cc.mallet.topics.tree.TreeTopicInferencer; +import cc.mallet.topics.tree.TreeTopicSamplerInterface; +import cc.mallet.topics.tree.TreeTopicSamplerFast; +import cc.mallet.topics.tree.TreeTopicSamplerFastEst; +import cc.mallet.topics.tree.TreeTopicSamplerFastEstSortD; +import cc.mallet.topics.tree.TreeTopicSamplerFastSortD; +import cc.mallet.topics.tree.TreeTopicSamplerNaive; + +import java.io.*; + +/** Perform topic analysis in the style of LDA and its variants. + * @author Andrew McCallum + */ + +public class Vectors2TreeTopics { + + // common options in mallet + static CommandOption.SpacedStrings inputFile = new CommandOption.SpacedStrings + (Vectors2TreeTopics.class, "input", "FILENAME [FILENAME ...]", true, null, + "The filename from which to read the list of training instances. " + + "Support multiple languages, each language should have its own file. " + + "The instances must be FeatureSequence or FeatureSequenceWithBigrams, not FeatureVector", null); + + static CommandOption.Integer numTopics = new CommandOption.Integer + (Vectors2TreeTopics.class, "num-topics", "INTEGER", true, 10, + "The number of topics to fit.", null); + + static CommandOption.Integer numIterations = new CommandOption.Integer + (Vectors2TreeTopics.class, "num-iterations", "INTEGER", true, 1000, + "The number of iterations of Gibbs sampling.", null); + + static CommandOption.Integer randomSeed = new CommandOption.Integer + (Vectors2TreeTopics.class, "random-seed", "INTEGER", true, 0, + "The random seed for the Gibbs sampler. Default is 0, which will use the clock.", null); + + static CommandOption.Integer topWords = new CommandOption.Integer + (Vectors2TreeTopics.class, "num-top-words", "INTEGER", true, 20, + "The number of most probable words to print for each topic after model estimation.", null); + + static CommandOption.Double alpha = new CommandOption.Double + (Vectors2TreeTopics.class, "alpha", "DECIMAL", true, 50.0, + "Alpha parameter: smoothing over topic distribution.",null); + + static CommandOption.String inferencerFilename = new CommandOption.String + (Vectors2TreeTopics.class, "inferencer-filename", "FILENAME", true, null, + "A topic inferencer applies a previously trained topic model to new documents." + + "By default this is null, indicating that no file will be written.", null); + + static CommandOption.String evaluatorFilename = new CommandOption.String + (Vectors2TreeTopics.class, "evaluator-filename", "FILENAME", true, null, + "A held-out likelihood evaluator for new documents. " + + "By default this is null, indicating that no file will be written.", null); + + //////////////////////////////////// + // new options + + static CommandOption.Integer outputInteval = new CommandOption.Integer + (Vectors2TreeTopics.class, "output-interval", "INTEGER", true, 20, + "For each interval, the result files are output to the outputFolder.", null); + + static CommandOption.String outputDir= new CommandOption.String + (Vectors2TreeTopics.class, "output-dir", "FOLDERNAME", true, null, + "The output folder.", null); + + static CommandOption.String vocabFile = new CommandOption.String + (Vectors2TreeTopics.class, "vocab", "FILENAME", true, null, + "The vocabulary file.", null); + + static CommandOption.String treeFiles = new CommandOption.String + (Vectors2TreeTopics.class, "tree", "FILENAME", true, null, + "The files for tree structure.", null); + + static CommandOption.String hyperFile = new CommandOption.String + (Vectors2TreeTopics.class, "tree-hyperparameters", "FILENAME", true, null, + "The hyperparameters for tree structure.", null); + + static CommandOption.Boolean resume = new CommandOption.Boolean + (Vectors2TreeTopics.class, "resume", "true|false", false, false, + "Resume from the previous output states.", null); + + static CommandOption.String resumeDir = new CommandOption.String + (Vectors2TreeTopics.class, "resume-dir", "FOLDERNAME", true, null, + "The resume folder.", null); + + static CommandOption.String consFile = new CommandOption.String + (Vectors2TreeTopics.class, "constraint", "FILENAME", true, null, + "The file constains the constrained words", null); + + static CommandOption.String forgetTopics = new CommandOption.String + (Vectors2TreeTopics.class, "forget-topics", "TYPENAME", true, null, + "Three options: term, doc, null." + + "Forget the previous sampled topic assignments of constrained words only (term), " + + "or the documents containing constrained words (doc)," + + "or not forget at all (keep everything)." + + "This option is for adding interaction.", null); + + static CommandOption.String removedFile = new CommandOption.String + (Vectors2TreeTopics.class, "remove-words", "FILENAME", true, null, + "The file contains the words that you want to be ignored in topic modeling. " + + "You need to have removed.all file, which is the removed words before this round of interaction," + + "and a removed.new file, which is the removed words that users just defined in this round of interaction" + + "This option is for adding interaction.", null); + + static CommandOption.String keepFile = new CommandOption.String + (Vectors2TreeTopics.class, "keep", "FILENAME", true, null, + "The topic assignments of words on this list will be kept instead of cleared," + + "even though it is on the list of constrained words." + + "This option is for adding interaction.", null); + + static CommandOption.String modelType = new CommandOption.String + (Vectors2TreeTopics.class, "tree-model-type", "TYPENAME", true, "fast-est", + "Possible types: naive, fast, fast-est, fast-sortD, fast-sortW, fast-sortD-sortW, " + + "fast-est-sortD, fast-est-sortW, fast-est-sortD-sortW.", null); + + public static void main (String[] args) throws java.io.IOException { + // Process the command-line options + CommandOption.setSummary (Vectors2TreeTopics.class, + "A tool for estimating, saving and printing diagnostics for topic models, such as LDA."); + CommandOption.process (Vectors2TreeTopics.class, args); + + int numLanguages = inputFile.value.length; + InstanceList[] instances = new InstanceList[ numLanguages ]; + for (int i=0; i < instances.length; i++) { + instances[i] = InstanceList.load(new File(inputFile.value[i])); + System.out.println ("Data " + i + " loaded. Total number of documents: " + instances[i].size()); + } + + TreeTopicSamplerInterface topicModel = null; + + // notice there are more inference methods available in this pacakge: + // naive, fast, fast-est, fast-sortD, fast-sortW, + // fast-sortD-sortW, fast-est-sortD, fast-est-sortW, fast-est-sortD-sortW + // by default, we set it as fast-est-sortD-sortW + // but you can change the modelType to any of them by exploring the source code + // also notice the inferencer and evaluator only support fast-est, fast-sortD-sortW, + // fast-est-sortD, fast-est-sortW, fast-est-sortD-sortW + boolean sortW = false; + String modeltype = "fast-est"; + //System.out.println("model type:" + modeltype); + modeltype = modelType.value; + + if (modeltype.equals("naive")) { + topicModel = new TreeTopicSamplerNaive( + numTopics.value, alpha.value, randomSeed.value); + } else if (modeltype.equals("fast")){ + topicModel = new TreeTopicSamplerFast( + numTopics.value, alpha.value, randomSeed.value, sortW); + } else if (modeltype.equals("fast-sortD")){ + topicModel = new TreeTopicSamplerFastSortD( + numTopics.value, alpha.value, randomSeed.value, sortW); + } else if (modeltype.equals("fast-sortW")){ + sortW = true; + topicModel = new TreeTopicSamplerFast( + numTopics.value, alpha.value, randomSeed.value, sortW); + } else if (modeltype.equals("fast-sortD-sortW")){ + sortW = true; + topicModel = new TreeTopicSamplerFastSortD( + numTopics.value, alpha.value, randomSeed.value, sortW); + + } else if (modeltype.equals("fast-est")) { + topicModel = new TreeTopicSamplerFastEst( + numTopics.value, alpha.value, randomSeed.value, sortW); + } else if (modeltype.equals("fast-est-sortD")) { + topicModel = new TreeTopicSamplerFastEstSortD( + numTopics.value, alpha.value, randomSeed.value, sortW); + } else if (modeltype.equals("fast-est-sortW")) { + sortW = true; + topicModel = new TreeTopicSamplerFastEst( + numTopics.value, alpha.value, randomSeed.value, sortW); + } else if (modeltype.equals("fast-est-sortD-sortW")) { + sortW = true; + topicModel = new TreeTopicSamplerFastEstSortD( + numTopics.value, alpha.value, randomSeed.value, sortW); + //} else if (modeltype.equals("fast-est-try")) { + // topicModel = new TreeTopicSamplerFastEstTry( + // numTopics.value, alpha.value, randomSeed.value, sortW); + } else { + System.out.println("model type wrong! please use " + + "'naive', 'fast', 'fast-est', " + + "'fast-sortD', 'fast-sortW', 'fast-sortD-sortW', " + + "'fast-est-sortD', 'fast-est-sortW', 'fast-est-sortD-sortW'!"); + System.exit(0); + } + + // load tree and vocab + topicModel.initialize(treeFiles.value, hyperFile.value, vocabFile.value, removedFile.value); + topicModel.setNumIterations(numIterations.value); + System.out.println("Prior tree loaded!"); + + if (resume.value == true) { + // resume instances from the saved states + topicModel.resume(instances, resumeDir.value); + } else { + // add instances + topicModel.addInstances(instances); + } + System.out.println("Model initialized!"); + + // if clearType is not null, clear the topic assignments of the + // constraint words + if (forgetTopics.value != null) { + if (forgetTopics.value.equals("term") || forgetTopics.value.equals("doc")) { + topicModel.clearTopicAssignments(forgetTopics.value, consFile.value, keepFile.value); + } else { + System.out.println("clear type wrong! please use either 'doc' or 'term'!"); + System.exit(0); + } + } + + // sampling and save states + topicModel.estimate(numIterations.value, outputDir.value, + outputInteval.value, topWords.value); + + // topic report + //System.out.println(topicModel.displayTopWords(topWords.value)); + + if (inferencerFilename.value != null) { + try { + ObjectOutputStream oos = + new ObjectOutputStream(new FileOutputStream(inferencerFilename.value)); + TreeTopicInferencer infer = topicModel.getInferencer(); + infer.setModelType(modeltype); + oos.writeObject(infer); + oos.close(); + } catch (Exception e) { + System.err.println(e.getMessage()); + } + + } + + if (evaluatorFilename.value != null) { + try { + ObjectOutputStream oos = + new ObjectOutputStream(new FileOutputStream(evaluatorFilename.value)); + TreeMarginalProbEstimator estimator = topicModel.getProbEstimator(); + estimator.setModelType(modeltype); + oos.writeObject(estimator); + oos.close(); + + } catch (Exception e) { + System.err.println(e.getMessage()); + } + + } + + } + +}