【问题标题】:How can I improve the efficiency and/or performance of my relatively simple Java counting method?如何提高相对简单的 Java 计数方法的效率和/或性能?
【发布时间】:2016-04-02 17:16:31
【问题描述】:

我正在构建一个必须阅读大量文本文档的分类器,但我发现我的 countWordFrequenties 方法处理的文档越多,它的速度就越慢。下面的这个方法需要 60 毫秒(在我的 PC 上),而读取、规范化、标记化、更新我的词汇表和均衡不同的整数列表总共只需要 3-5 毫秒(在我的 PC 上)。我的countWordFrequencies方法如下:

public List<Integer> countWordFrequencies(String[] tokens) 
{
    List<Integer> wordFreqs = new ArrayList<>(vocabulary.size());
    int counter = 0;

    for (int i = 0; i < vocabulary.size(); i++) 
    {
        for (int j = 0; j < tokens.length; j++)
            if (tokens[j].equals(vocabulary.get(i)))
                counter++;

        wordFreqs.add(i, counter);
        counter = 0;
    }

    return wordFreqs;
}

对我来说加快这个过程的最佳方法是什么?这个方法有什么问题?

这是我的整个班级,还有另一个班级类别,把这个也发在这里是个好主意还是你们不需要?

public class BayesianClassifier 
{
    private Map<String,Integer>  vocabularyWordFrequencies;
    private List<String> vocabulary;
    private List<Category> categories;
    private List<Integer> wordFrequencies;
    private int trainTextAmount;
    private int testTextAmount;
    private GUI gui;

    public BayesianClassifier() 
    {
        this.vocabulary = new ArrayList<>();
        this.categories = new ArrayList<>();
        this.wordFrequencies = new ArrayList<>();
        this.trainTextAmount = 0;
        this.gui = new GUI(this);
        this.testTextAmount = 0;
    }

    public List<Category> getCategories() 
    {
        return categories;
    }

    public List<String> getVocabulary() 
    {
        return this.vocabulary;
    }

    public List<Integer> getWordFrequencies() 
    {
        return  wordFrequencies;
    }

    public int getTextAmount() 
    {
        return testTextAmount + trainTextAmount;
    }

    public void updateWordFrequency(int index, Integer frequency)
    {
        equalizeIntList(wordFrequencies);
        this.wordFrequencies.set(index, wordFrequencies.get(index) + frequency);
    }

    public String readText(String path) 
    {
        BufferedReader br;
        String result = "";

        try 
        {
            br = new BufferedReader(new FileReader(path));

            StringBuilder sb = new StringBuilder();
            String line = br.readLine();

            while (line != null) 
            {
                sb.append(line);
                sb.append("\n");
                line = br.readLine();
            }

            result = sb.toString();
            br.close();
        } 
        catch (IOException e) 
        {
            e.printStackTrace();
        }

        return result;
    }

    public String normalizeText(String text) 
    {
        String fstNormalized = Normalizer.normalize(text, Normalizer.Form.NFD);

        fstNormalized = fstNormalized.replaceAll("[^\\p{ASCII}]","");
        fstNormalized = fstNormalized.toLowerCase();
        fstNormalized = fstNormalized.replace("\n","");
        fstNormalized = fstNormalized.replaceAll("[0-9]","");
        fstNormalized = fstNormalized.replaceAll("[/()!?;:,.%-]","");
        fstNormalized = fstNormalized.trim().replaceAll(" +", " ");

        return fstNormalized;
    }

    public String[] handleText(String path) 
    {
        String text = readText(path);
        String normalizedText = normalizeText(text);

        return tokenizeText(normalizedText);
    }

    public void createCategory(String name, BayesianClassifier bc) 
    {
        Category newCategory = new Category(name, bc);

        categories.add(newCategory);
    }

    public List<String> updateVocabulary(String[] tokens) 
    {
        for (int i = 0; i < tokens.length; i++)
            if (!vocabulary.contains(tokens[i]))
                vocabulary.add(tokens[i]);

        return vocabulary;
    }

    public List<Integer> countWordFrequencies(String[] tokens)
    {
        List<Integer> wordFreqs = new ArrayList<>(vocabulary.size());
        int counter = 0;

        for (int i = 0; i < vocabulary.size(); i++) 
        {
            for (int j = 0; j < tokens.length; j++)
                if (tokens[j].equals(vocabulary.get(i)))
                    counter++;

            wordFreqs.add(i, counter);
            counter = 0;
        }

        return wordFreqs;
    }

    public String[] tokenizeText(String normalizedText) 
    {
        return normalizedText.split(" ");
    }

    public void handleTrainDirectory(String folderPath, Category category) 
    {
        File folder = new File(folderPath);
        File[] listOfFiles = folder.listFiles();

        if (listOfFiles != null) 
        {
            for (File file : listOfFiles) 
            {
                if (file.isFile()) 
                {
                    handleTrainText(file.getPath(), category);
                }
            }
        } 
        else 
        {
            System.out.println("There are no files in the given folder" + " " + folderPath.toString());
        }
    }

    public void handleTrainText(String path, Category category) 
    {
        long startTime = System.currentTimeMillis();

        trainTextAmount++;

        String[] text = handleText(path);

        updateVocabulary(text);
        equalizeAllLists();

        List<Integer> wordFrequencies = countWordFrequencies(text);
        long finishTime = System.currentTimeMillis();

        System.out.println("That took 1: " + (finishTime-startTime)+ " ms");

        long startTime2 = System.currentTimeMillis();

        category.update(wordFrequencies);
        updatePriors();

        long finishTime2 = System.currentTimeMillis();

        System.out.println("That took 2: " + (finishTime2-startTime2)+ " ms");
    }

    public void handleTestText(String path) 
    {
        testTextAmount++;

        String[] text = handleText(path);
        List<Integer> wordFrequencies = countWordFrequencies(text);
        Category category = guessCategory(wordFrequencies);
        boolean correct = gui.askFeedback(path, category);

        if (correct) 
        {
            category.update(wordFrequencies);
            updatePriors();
            System.out.println("Kijk eens aan! De tekst is succesvol verwerkt.");
        } 
        else 
        {
            Category correctCategory = gui.askCategory();
            correctCategory.update(wordFrequencies);
            updatePriors();
            System.out.println("Kijk eens aan! De tekst is succesvol verwerkt.");
        }
    }

    public void updatePriors()
    {
        for (Category category : categories)
        {
            category.updatePrior();
        }
    }

    public Category guessCategory(List<Integer> wordFrequencies) 
    {
        List<Double> chances = new ArrayList<>();

        for (int i = 0; i < categories.size(); i++)
        {
            double chance = categories.get(i).getPrior();

            System.out.println("The prior is:" + chance);

            for(int j = 0; j < wordFrequencies.size(); j++)
            {
                chance = chance * categories.get(i).getWordProbabilities().get(j);
            }

            chances.add(chance);
        }

        double max = getMaxValue(chances);
        int index = chances.indexOf(max);

        System.out.println(max);
        System.out.println(index);
        return categories.get(index);
    }

    public double getMaxValue(List<Double> values)
    {
        Double max = 0.0;

        for (Double dubbel : values)
        {
            if(dubbel > max)
            {
                max = dubbel;
            }
        }

        return max;
    }

    public void equalizeAllLists()
    {
        for(Category category : categories)
        {
            if (category.getWordFrequencies().size() < vocabulary.size())
            {
                category.setWordFrequencies(equalizeIntList(category.getWordFrequencies()));
            }
        }

        for(Category category : categories)
        {
            if (category.getWordProbabilities().size() < vocabulary.size())
            {
                category.setWordProbabilities(equalizeDoubleList(category.getWordProbabilities()));
            }
        }
    }

    public List<Integer> equalizeIntList(List<Integer> list)
    {
        while (list.size() < vocabulary.size())
        {
            list.add(0);
        }

        return list;
    }

    public List<Double> equalizeDoubleList(List<Double> list)
    {
        while (list.size() < vocabulary.size())
        {
            list.add(0.0);
        }

        return list;
    }

    public void selectFeatures()
    {
        for(int i = 0; i < wordFrequencies.size(); i++)
        {
            if(wordFrequencies.get(i) < 2)
            {
                vocabulary.remove(i);
                wordFrequencies.remove(i);

                for(Category category : categories)
                {
                    category.removeFrequency(i);
                }
            }
        }
    }
}

【问题讨论】:

  • 您能否更清楚地表达您的问题。什么需要 50 毫秒,什么需要 3-5 毫秒还不清楚
  • 对不起,编辑在那里,这个方法执行一个文本需要 50 毫秒,而其他六个方法只需要 2-3 毫秒(都比较简单)。我知道这个有点难,但 50 毫秒对我来说有点奇怪。
  • 这个方法列出了我词汇表中的单词出现在标记化文本“标记”中的次数的整数列表。
  • 你能显示更多代码吗?我们不知道真正的词汇变量是什么
  • 我看错了代码,你是对的,它是正确的。奇怪的编程,但正确。

标签: java performance methods classification word-count


【解决方案1】:

您的方法有O(n*m) 运行时间(n 是词汇量大小,m 是标记大小)。通过散列,这可以减少到O(m),这显然更好。

for (String token: tokens) {
  if(!map.containsKey(token)){
      map.put(token,0);
  }
  map.put(token,map.get(token)+1);
}

【讨论】:

  • @Voicu 最坏情况的循环。 containsKey 具有 O(1) 复杂度
  • @Voicu,我建议检查一下哈希映射是如何工作的。唯一可能发生 O(n^2) 的情况是所有令牌的所有哈希码都相同,这绝不是现实世界的情况。
  • @TotalCare 阅读了杰克链接的问题。一般是O(1),在最坏的情况下(坏散列)只有O(n)
  • 顺便说一句,HashMap 在最坏的情况下有 O(lgn),因为它在高冲突的情况下使用 TreeMap
【解决方案2】:

如果你不想使用 Java 8 的东西,你可以尝试使用来自 guava 的 MultiSet

【讨论】:

  • 我确实想使用现有的任何东西,您认为我可以从 Java 8 中使用什么?
  • @TotalCare Mureinik 的solution 是最好的。它使用 Java 8。
【解决方案3】:

正如Sleiman Jneidi 在他的回答中所建议的那样,使用Map 应该会显着提高性能。然而,这可以通过 Java 8 的流 API 更优雅地完成:

Map<String, Long> frequencies = 
    Arrays.stream(tokens)
          .collect(Collectors.groupingBy(Function.identity(), 
                                         Collectors.counting()));

【讨论】:

  • 有趣。我不知道Function.identity() - 这是风格问题,虽然我通常使用UnaryOperator.identity()。它扩展了Function,因此可以在需要两者之一的上下文中使用。然而,对于这种情况,这完全是一个见仁见智的问题。
  • 感谢您的建议,与仅制作 Map 相比,这究竟有什么好处?
  • @TotalCare 你的意思是与自己构建地图相比吗?主要是你不必这样做。主要是减少了您需要编写的代码量,并允许您处理代码的“业务逻辑”,并将样板化的部分卸载到 JDK。
  • @Mureink 为什么你使用 long 而不是 double?
  • 赞成。肯定是一个令人印象深刻的答案,但“简洁”和“优雅”并不是同义词:-)
【解决方案4】:

我不会使用一个列表作为词汇表,另一个用于频率,我会使用一个地图来存储单词->频率。这样你就可以避免双重循环,在我看来这会扼杀你的表现。

public Map<String,Integer> countWordFrequencies(String[] tokens) {
    // vocabulary is Map<String,Integer> initialized with all words as keys and 0 as value
    for (String word: tokens)
      if (vocabulary.containsKey(word)) {
        vocabulary.put(word, vocabulary.get(word)+1);
      }
    return vocabulary;
}

【讨论】:

  • 问题没有说词汇的数据类型是什么。
  • @vinay - 因为他使用get(int),我认为它是某种列表
  • @NirLevy 我用过这个,现在我还想制作 wordFrequencies 和 wordProbabilities 类别的映射,如何制作一个包含所有确切键且所有值为 0 的映射?
猜你喜欢
  • 2020-06-12
  • 1970-01-01
  • 2011-06-08
  • 2021-04-05
  • 1970-01-01
  • 2013-01-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多