Java中使用Weka的基本文本分类

Posted

技术标签:

【中文标题】Java中使用Weka的基本文本分类【英文标题】:Basic text classification with Weka in Java 【发布时间】:2012-03-31 06:11:55 【问题描述】:

我正在尝试使用 Weka 在 JAVA 中构建文本分类器。 我已经阅读了一些教程,并且正在尝试构建自己的分类器。

我有以下分类:

    computer,sport,unknown 

以及以下已经训练好的数据

 cs belongs to computer
 java -> computer
 soccer -> sport
 snowboard -> sport

例如,如果用户想要对 java 一词进行分类,它应该返回类别 computer(毫无疑问,java 只存在于该类别中!)。

它确实可以编译,但会产生奇怪的输出。

输出是:

      ====== RESULT ======  CLASSIFIED AS:  [0.5769230769230769, 0.2884615384615385, 0.1346153846153846]
      ====== RESULT ======  CLASSIFIED AS:  [0.42857142857142855, 0.42857142857142855, 0.14285714285714285]

但是要分类的第一个文本是java,它只出现在计算机类别中,因此应该是

      [1.0 0.0 0.0] 

而另一个根本不应该被发现,所以它应该被归类为未知

      [0.0 0.0 1.0].

代码如下:

    import java.io.FileNotFoundException;
    import java.io.Serializable;
    import java.util.Arrays;

    import weka.classifiers.Classifier;
    import weka.classifiers.bayes.NaiveBayesMultinomialUpdateable;
    import weka.core.Attribute;
    import weka.core.FastVector;
    import weka.core.Instance;
    import weka.core.Instances;
    import weka.filters.Filter;
    import weka.filters.unsupervised.attribute.StringToWordVector;

    public class TextClassifier implements Serializable 

        private static final long serialVersionUID = -1397598966481635120L;
        public static void main(String[] args) 
            try 
                TextClassifier cl = new TextClassifier(new NaiveBayesMultinomialUpdateable());
                cl.addCategory("computer");
                cl.addCategory("sport");
                cl.addCategory("unknown");
                cl.setupAfterCategorysAdded();

                //
                cl.addData("cs", "computer");
                cl.addData("java", "computer");
                cl.addData("soccer", "sport");
                cl.addData("snowboard", "sport");

                double[] result = cl.classifyMessage("java");
                System.out.println("====== RESULT ====== \tCLASSIFIED AS:\t" + Arrays.toString(result));

                result = cl.classifyMessage("asdasdasd");
                System.out.println("====== RESULT ======\tCLASSIFIED AS:\t" + Arrays.toString(result));
             catch (Exception e) 
                e.printStackTrace();
            
        
        private Instances trainingData;
        private StringToWordVector filter;
        private Classifier classifier;
        private boolean upToDate;
        private FastVector classValues;
        private FastVector attributes;
        private boolean setup;

        private Instances filteredData;

        public TextClassifier(Classifier classifier) throws FileNotFoundException 
            this(classifier, 10);
        

        public TextClassifier(Classifier classifier, int startSize) throws FileNotFoundException 
            this.filter = new StringToWordVector();
            this.classifier = classifier;
            // Create vector of attributes.
            this.attributes = new FastVector(2);
            // Add attribute for holding texts.
            this.attributes.addElement(new Attribute("text", (FastVector) null));
            // Add class attribute.
            this.classValues = new FastVector(startSize);
            this.setup = false;

        

        public void addCategory(String category) 
            category = category.toLowerCase();
            // if required, double the capacity.
            int capacity = classValues.capacity();
            if (classValues.size() > (capacity - 5)) 
                classValues.setCapacity(capacity * 2);
            
            classValues.addElement(category);
        

        public void addData(String message, String classValue) throws IllegalStateException 
            if (!setup) 
                throw new IllegalStateException("Must use setup first");
            
            message = message.toLowerCase();
            classValue = classValue.toLowerCase();
            // Make message into instance.
            Instance instance = makeInstance(message, trainingData);
            // Set class value for instance.
            instance.setClassValue(classValue);
            // Add instance to training data.
            trainingData.add(instance);
            upToDate = false;
        

        /**
         * Check whether classifier and filter are up to date. Build i necessary.
         * @throws Exception
         */
        private void buildIfNeeded() throws Exception 
            if (!upToDate) 
                // Initialize filter and tell it about the input format.
                filter.setInputFormat(trainingData);
                // Generate word counts from the training data.
                filteredData = Filter.useFilter(trainingData, filter);
                // Rebuild classifier.
                classifier.buildClassifier(filteredData);
                upToDate = true;
            
        

        public double[] classifyMessage(String message) throws Exception 
            message = message.toLowerCase();
            if (!setup) 
                throw new Exception("Must use setup first");
            
            // Check whether classifier has been built.
            if (trainingData.numInstances() == 0) 
                throw new Exception("No classifier available.");
            
            buildIfNeeded();
            Instances testset = trainingData.stringFreeStructure();
            Instance testInstance = makeInstance(message, testset);

            // Filter instance.
            filter.input(testInstance);
            Instance filteredInstance = filter.output();
            return classifier.distributionForInstance(filteredInstance);

        

        private Instance makeInstance(String text, Instances data) 
            // Create instance of length two.
            Instance instance = new Instance(2);
            // Set value for message attribute
            Attribute messageAtt = data.attribute("text");
            instance.setValue(messageAtt, messageAtt.addStringValue(text));
            // Give instance access to attribute information from the dataset.
            instance.setDataset(data);
            return instance;
        

        public void setupAfterCategorysAdded() 
            attributes.addElement(new Attribute("class", classValues));
            // Create dataset with initial capacity of 100, and set index of class.
            trainingData = new Instances("MessageClassificationProblem", attributes, 100);
            trainingData.setClassIndex(trainingData.numAttributes() - 1);
            setup = true;
        

    

顺便说一句,找到一个好页面:

http://www.hakank.org/weka/TextClassifierApplet3.html

【问题讨论】:

【参考方案1】:

贝叶斯分类器为您提供一个单词属于某个类别的(加权)概率。这几乎永远不会恰好是 0 或 1。您可以设置一个硬截止值(例如 0.5)并基于此决定一个类的成员资格,或者检查计算的概率并基于此做出决定(即最高映射到 1,最低到 0)。

【讨论】:

是的,我知道。但是在这个例子中,当我尝试分类时: result = cl.classifyMessage("asdasdasd");结果应归类为未知,但不是:/但是,我可以看到它可能不起作用。因为我根本没有该类别的任何文档......有没有添加“uknown”类别或类似类别的智能解决方案?而且对于 java 这个词,我认为它会更偏向于计算机类别,因为它甚至没有在其他类别中提及。 如果每个类的成员概率太低,您将不得不手动将内容添加到“未知”类别。 但概率加起来总是 1.0。因此,如果我尝试对任何文档中不存在的某个单词进行分类,则概率加在一起(对于所有类别)仍然是 1.0 例如,它们仍然可以全部低于 0.5。无论如何,您必须决定选择哪个概率来表示“属于该类别”。 拉斯:确实如此。可能是一种解决方案。另一种解决方案可能是检查已经训练过的文档中的这个词,如果它在任何文档中都不存在,我可以跳过实际的分类器并只返回“未知”【参考方案2】:

我想我只是提出,您可以通过从http://lightsidelabs.com 下载和使用 LightSIDE 来完成大多数此类文本分类工作而无需编码。这个开源 Java 包包含 WEKA,可用于 Windows 和 Mac 上的发行版——可以非常灵活地处理大多数 WEKA 友好的数据集,允许您迭代各种模型、设置和参数,并为快照提供良好的支持和在任何时候保存您的数据和模型以及分类结果,直到您建立一个您满意的模型。该产品去年在 Kaggle.com 上的 ASAP 竞赛中证明了自己,并获得了很大的关注。当然,人们想要或需要“自己动手”总是有原因的,但也许即使作为检查,如果您正在编写 WEKA 解决方案,了解和使用 LightSIDE 可能会非常方便。

【讨论】:

【参考方案3】:

如果您尝试获得明确的类而不是发行版,请尝试切换

return classifier.distributionForInstance(filteredInstance);

return classifier.classifyInstance(filteredInstance);

【讨论】:

这个答案很难理解——贴代码的时候应该尽量解释一下 好吧,如果 joxxe 在他的代码中尝试一下,他就会明白我的意思。无论如何,distributionForInstance() 方法返回每个目标类的概率。而classifyInstance() 方法选择概率最高的类并返回类索引。所以我猜joxxe需要第二种方法。

以上是关于Java中使用Weka的基本文本分类的主要内容,如果未能解决你的问题,请参考以下文章

使用 weka 进行文本分类

在 Java 中构建/运行流式 Weka 文本分类器

使用 Weka 进行文本分类

weka 文本分类

使用 Weka 进行文本分类

weka 3.7 explorer 无法对文本进行分类