亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

如何實(shí)現(xiàn)一個(gè)基本的微信文章分類器

dackel / 3223人閱讀

摘要:本文源地址,轉(zhuǎn)發(fā)請(qǐng)注明該地址或地址,謝謝微信公眾號(hào)發(fā)布的文章和一般門戶網(wǎng)站的新聞文本類型有所不同,通常不能用現(xiàn)有的文本分類器直接對(duì)這些文章進(jìn)行分類,不過(guò)文本分類的原理是相通的,本文以微信公眾號(hào)文章為對(duì)象,介紹樸素貝葉斯分類器的實(shí)現(xiàn)過(guò)程。

本文源地址:http://www.fullstackyang.com/...,轉(zhuǎn)發(fā)請(qǐng)注明該地址或segmentfault地址,謝謝!

微信公眾號(hào)發(fā)布的文章和一般門戶網(wǎng)站的新聞文本類型有所不同,通常不能用現(xiàn)有的文本分類器直接對(duì)這些文章進(jìn)行分類,不過(guò)文本分類的原理是相通的,本文以微信公眾號(hào)文章為對(duì)象,介紹樸素貝葉斯分類器的實(shí)現(xiàn)過(guò)程。

文本分類的科學(xué)原理和數(shù)學(xué)證明在網(wǎng)上有很多,這里就不做贅述,本文盡量使用通熟易懂的表述方式,簡(jiǎn)明扼要地梳理一下文本分類器的各個(gè)知識(shí)點(diǎn)。

參考了一下Github,發(fā)現(xiàn)少有Java 8風(fēng)格的實(shí)現(xiàn),所以這里的實(shí)現(xiàn)盡量利用Java 8的特性,相比之前優(yōu)勢(shì)有很多,例如stream在統(tǒng)計(jì)聚合等運(yùn)算上比較方便,代碼不僅簡(jiǎn)潔,而且更加語(yǔ)義化,另外在多線程并行控制上也省去不少的工作。

本項(xiàng)目的地址:https://github.com/fullstacky...

一、文本分類器的概述

文本分類器可以看作是一個(gè)預(yù)測(cè)函數(shù),在給定的文本時(shí),在預(yù)定的類別集合中,判斷該文本最可能屬于哪個(gè)類。

這里需要注意兩個(gè)問(wèn)題:

在文本中含有比較多的標(biāo)點(diǎn)符號(hào)和停用詞(的,是,了等),直接使用整段文本處理肯定會(huì)產(chǎn)生很多不必要的計(jì)算,而且計(jì)算量也非常大,因此需要把給定的文本有效地進(jìn)行表示,也就是選擇一系列的特征詞來(lái)代表這篇文本,這些特征詞既可以比較好地反應(yīng)所屬文本的內(nèi)容,又可以對(duì)不同文本有比較好的區(qū)分能力。

在進(jìn)行文本表示之后,如何對(duì)這些特征詞進(jìn)行預(yù)測(cè),這就是分類器的算法設(shè)計(jì)問(wèn)題了,比較常見(jiàn)的模型有樸素貝葉斯,基于支持向量機(jī)(SVM),K-近鄰(KNN),決策樹(shù)等分類算法。這里我們選擇簡(jiǎn)單易懂的樸素貝葉斯算法。在機(jī)器學(xué)習(xí)中,樸素貝葉斯建模屬于有監(jiān)督學(xué)習(xí),因此需要收集大量的文本作為訓(xùn)練語(yǔ)料,并標(biāo)注分類結(jié)果

綜上,實(shí)現(xiàn)一個(gè)分類器通常分為以下幾個(gè)步驟:

收集并處理訓(xùn)練語(yǔ)料,以及最后測(cè)試用的測(cè)試語(yǔ)料

在訓(xùn)練集上進(jìn)行特征選擇,得到一系列的特征項(xiàng)(詞),這些特征項(xiàng)組成了所謂的特征空間

為了表示某個(gè)特征項(xiàng)在不同文檔中的重要程度,計(jì)算該特征項(xiàng)的權(quán)重,常用的計(jì)算方法有TF-IDF,本文采用的是“經(jīng)典”樸素貝葉斯模型,這里不考慮特征項(xiàng)的權(quán)重(當(dāng)然,一定要做也可以)

訓(xùn)練模型,對(duì)于樸素貝葉斯模型來(lái)說(shuō),主要的是計(jì)算每個(gè)特征項(xiàng)在不同類別中的條件概率,這點(diǎn)下面再做解釋。

預(yù)測(cè)文本,模型訓(xùn)練完成之后可以保存到文件中,在預(yù)測(cè)時(shí)直接讀入模型的數(shù)據(jù)進(jìn)行計(jì)算。

二、準(zhǔn)備訓(xùn)練語(yǔ)料

這里需要的語(yǔ)料就是微信公眾號(hào)的文章,我們可以抓取搜狗微信搜索網(wǎng)站(http://weixin.sogou.com/)首頁(yè)上已經(jīng)分類好的文章,直接采用其分類結(jié)果,這樣也省去了標(biāo)注的工作。至于如何開(kāi)發(fā)爬蟲(chóng)去抓取文章,這里就不再討論了。

“熱門”這類別下的文章不具有一般性,因此不把它當(dāng)作一個(gè)類別。剔除“熱門”類別之后,最終我們抓取了30410篇文章,總共20個(gè)類別,每個(gè)類別的文章數(shù)并不均衡,其中最多 的是“養(yǎng)生堂”類別,有2569篇文章,最少的是“軍事”類別,有654篇,大體符合微信上文章的分布情況。在保存時(shí),我們保留了文章的標(biāo)題,公眾號(hào)名稱,文章正文。

三、特征選擇

如前文所述,特征選擇的目的是降低特征空間的維度,避免維度災(zāi)難。簡(jiǎn)單地說(shuō),假設(shè)我們選擇了2萬(wàn)個(gè)特征詞,也就是說(shuō)計(jì)算機(jī)通過(guò)學(xué)習(xí),得到了一張有2萬(wàn)個(gè)詞的“單詞表”,以后它遇到的所有文本可以夠用這張單詞表中的詞去表示其內(nèi)容大意。這些特征詞針對(duì)不同的類別有一定的區(qū)分能力,舉例來(lái)說(shuō),“殲擊機(jī)”可能來(lái)自“軍事”,“越位”可能來(lái)自“體育”,“漲?!笨赡軄?lái)自“財(cái)經(jīng)”等等,而通常中文詞匯量要比這個(gè)數(shù)字大得多,一本常見(jiàn)的漢語(yǔ)詞典收錄的詞條數(shù)可達(dá)數(shù)十萬(wàn)。

常見(jiàn)的特征選擇方法有兩個(gè),信息增益法和卡方檢驗(yàn)法。

3.1 信息增益

信息增益法的衡量標(biāo)準(zhǔn)是,這個(gè)特征項(xiàng)可以為分類系統(tǒng)帶來(lái)多少信息量,所謂的信息增益就是該特征項(xiàng)包含的能夠幫預(yù)測(cè)類別的信息量,這里所說(shuō)的信息量可以用熵來(lái)衡量,計(jì)算信息增益時(shí)還需要引入條件熵的概念,公式如下

可能有些見(jiàn)到公式就頭大的小伙伴不太友好,不過(guò)這個(gè)公式雖然看起來(lái)有點(diǎn)復(fù)雜,其實(shí)在計(jì)算中還是比較簡(jiǎn)單的,解釋一下:

P(Cj):Cj類文檔在整個(gè)語(yǔ)料中出現(xiàn)的概率;

P(ti):語(yǔ)料中包含特征項(xiàng)ti的文檔的概率,取反就是不包含特征項(xiàng)ti的文檔的概率;

P(Cj|ti):文檔包含特征項(xiàng)ti且屬于Cj類的條件概率,取反就是文檔不包含特征項(xiàng)ti且屬于Cj類的條件概率

上面幾個(gè)概率值,都可以比較方便地從訓(xùn)練語(yǔ)料上統(tǒng)計(jì)得到。若還有不明白的小伙伴,推薦閱讀這篇博客:文本分類入門(十一)特征選擇方法之信息增益

3.2 卡方檢驗(yàn)

卡方檢驗(yàn),基于χ2統(tǒng)計(jì)量(CHI)來(lái)衡量特征項(xiàng)ti和類別Cj之間的相關(guān)聯(lián)程度,CHI統(tǒng)計(jì)值越高,該特征項(xiàng)與該類的相關(guān)性越大,如果兩者相互獨(dú)立,則CHI統(tǒng)計(jì)值接近零。計(jì)算時(shí)需要根據(jù)一張相依表(contingency table),公式也比較簡(jiǎn)單:

其中N就是文檔總數(shù),如果想繼續(xù)討論這個(gè)公式,推薦閱讀這篇博客:特征選擇(3)-卡方檢驗(yàn)

3.3 算法實(shí)現(xiàn)

不論何種方式都需要對(duì)每個(gè)特征項(xiàng)進(jìn)行估算,然后根據(jù)所得的數(shù)值進(jìn)行篩選,通??梢栽O(shè)定一個(gè)閾值,低于閾值的特征項(xiàng)可以直接從特征空間中移除,另外也可以按照數(shù)值從高到低排序,并指定選擇前N個(gè)。這里我們采用后者,總共截取前2萬(wàn)個(gè)特征項(xiàng)。

特征選擇實(shí)現(xiàn)類的代碼如下,其中,不同特征選擇方法需實(shí)現(xiàn)Strategy接口,以獲得不同方法計(jì)算得到的估值,這里在截取特征項(xiàng)時(shí)為了避免不必要的麻煩,剔除了字符串長(zhǎng)度為1的詞。

Doc對(duì)象表示一篇文檔,其中包含了該文檔的所屬分類,以及分詞結(jié)果(已經(jīng)濾掉了停用詞等),即Term集合;

Term對(duì)象主要包含3個(gè)字段,詞本身的字符串,詞性(用于過(guò)濾),詞頻TF;

Feature表示特征項(xiàng),一個(gè)特征項(xiàng)對(duì)應(yīng)一個(gè)Term對(duì)象,還包含兩個(gè)hashmap,一個(gè)用來(lái)統(tǒng)計(jì)不同類別下該特征項(xiàng)出現(xiàn)的文檔數(shù)量(categoryDocCounter),另一個(gè)用來(lái)統(tǒng)計(jì)不同類別下該特征項(xiàng)出現(xiàn)的頻度(categoryTermCounter)(對(duì)應(yīng)樸素貝葉斯兩種不同模型,下文詳述)

統(tǒng)計(jì)時(shí)引入FeatureCounter對(duì)象,使用stream的reduce方法進(jìn)行歸約。主要的思想就是把每一個(gè)文檔中的Term集合,映射為Term和Feature的鍵值對(duì),然后再和已有的Map進(jìn)行合并,合并時(shí)如果遇到相同的Term,則調(diào)用Feature的Merge方法,該方法會(huì)將雙方term的詞頻,以及categoryDocCounter和categoryTermCounter中的統(tǒng)計(jì)結(jié)果進(jìn)行累加。最終將所有文檔全部統(tǒng)計(jì)完成返回Feature集合。

@AllArgsConstructor
public class FeatureSelection {

    interface Strategy {
        Feature estimate(Feature feature);
    }

    private final Strategy strategy;
    private final static int FEATURE_SIZE = 20000;

    public List select(List docs) {
        return createFeatureSpace(docs.stream())
                .stream()
                .map(strategy::estimate)
                .filter(f -> f.getTerm().getWord().length() > 1)
                .sorted(comparing(Feature::getScore).reversed())
                .limit(FEATURE_SIZE)
                .collect(toList());
    }

    private Collection createFeatureSpace(Stream docs) {

        @AllArgsConstructor
        class FeatureCounter {
            private final Map featureMap;

            private FeatureCounter accumulate(Doc doc) {
                Map temp = doc.getTerms().parallelStream()
                        .map(t -> new Feature(t, doc.getCategory()))
                        .collect(toMap(Feature::getTerm, Function.identity()));
                if (!featureMap.isEmpty())
                    featureMap.values().forEach(f -> temp.merge(f.getTerm(), f, Feature::merge));
                return new FeatureCounter(temp);
            }

            private FeatureCounter combine(FeatureCounter featureCounter) {
                Map temp = Maps.newHashMap(featureMap);
                featureCounter.featureMap.values().forEach(f -> temp.merge(f.getTerm(), f, Feature::merge));
                return new FeatureCounter(temp);
            }
        }

        FeatureCounter counter = docs.parallel()
                .reduce(new FeatureCounter(Maps.newHashMap()),
                        FeatureCounter::accumulate,
                        FeatureCounter::combine);
        return counter.featureMap.values();
    }
}
public class Feature {
...
    public Feature merge(Feature feature) {
        if (this.term.equals(feature.getTerm())) {
            this.term.setTf(this.term.getTf() + feature.getTerm().getTf());
            feature.getCategoryDocCounter()
                    .forEach((k, v) -> categoryDocCounter.merge(k, v, (oldValue, newValue) -> oldValue + newValue));
            feature.getCategoryTermCounter()
                    .forEach((k, v) -> categoryTermCounter.merge(k, v, (oldValue, newValue) -> oldValue + newValue));
        }
        return this;
    }
}

信息增益實(shí)現(xiàn)如下,在計(jì)算條件熵時(shí),利用了stream的collect方法,將包含和不包含特征項(xiàng)的兩種情況用一個(gè)hashmap分開(kāi)再進(jìn)行歸約。

@AllArgsConstructor
public class IGStrategy implements FeatureSelection.Strategy {

    private final Collection categories;

    private final int total;

    public Feature estimate(Feature feature) {
        double totalEntropy = calcTotalEntropy();
        double conditionalEntrogy = calcConditionEntropy(feature);
        feature.setScore(totalEntropy - conditionalEntrogy);
        return feature;
    }

    private double calcTotalEntropy() {
        return Calculator.entropy(categories.stream().map(c -> (double) c.getDocCount() / total).collect(toList()));
    }

    private double calcConditionEntropy(Feature feature) {
        int featureCount = feature.getFeatureCount();
        double Pfeature = (double) featureCount / total;

        Map> Pcondition = categories.parallelStream().collect(() -> new HashMap>() {{
                    put(true, Lists.newArrayList());
                    put(false, Lists.newArrayList());
                }}, (map, category) -> {
                    int countDocWithFeature = feature.getDocCountByCategory(category);
                    //出現(xiàn)該特征詞且屬于類別key的文檔數(shù)量/出現(xiàn)該特征詞的文檔總數(shù)量
                    map.get(true).add((double) countDocWithFeature / featureCount);
                    //未出現(xiàn)該特征詞且屬于類別key的文檔數(shù)量/未出現(xiàn)該特征詞的文檔總數(shù)量
                    map.get(false).add((double) (category.getDocCount() - countDocWithFeature) / (total - featureCount));
                },
                (map1, map2) -> {
                    map1.get(true).addAll(map2.get(true));
                    map1.get(false).addAll(map2.get(false));
                }
        );
        return Calculator.conditionalEntrogy(Pfeature, Pcondition.get(true), Pcondition.get(false));
    }
}

卡方檢驗(yàn)實(shí)現(xiàn)如下,每個(gè)特征項(xiàng)要在每個(gè)類別上分別計(jì)算CHI值,最終保留其最大值

@AllArgsConstructor
public class ChiSquaredStrategy implements Strategy {

    private final Collection categories;

    private final int total;

    @Override
    public Feature estimate(Feature feature) {

        class ContingencyTable {
            private final int A, B, C, D;

            private ContingencyTable(Feature feature, Category category) {
                A = feature.getDocCountByCategory(category);
                B = feature.getFeatureCount() - A;
                C = category.getDocCount() - A;
                D = total - A - B - C;
            }
        }

        Double chisquared = categories.stream()
                .map(c -> new ContingencyTable(feature, c))
                .map(ct -> Calculator.chisquare(ct.A, ct.B, ct.C, ct.D))
                .max(Comparator.comparingDouble(Double::valueOf)).get();
        feature.setScore(chisquared);
        return feature;
    }
}
四、樸素貝葉斯模型 4.1 原理簡(jiǎn)介

樸素貝葉斯模型之所以稱之“樸素”,是因?yàn)槠浼僭O(shè)特征之間是相互獨(dú)立的,在文本分類中,也就是說(shuō),一篇文檔中出現(xiàn)的詞都是相互獨(dú)立,彼此沒(méi)有關(guān)聯(lián),顯然文檔中出現(xiàn)的詞都是有邏輯性的,這種假設(shè)在現(xiàn)實(shí)中幾乎是不成立的,但是這種假設(shè)卻大大簡(jiǎn)化了計(jì)算,根據(jù)貝葉斯公式,文檔Doc屬于類別Ci的概率為:

P(Ci|Doc)是所求的后驗(yàn)概率,我們?cè)谂卸ǚ诸悤r(shí),根據(jù)每個(gè)類別計(jì)算P(Ci|Doc),最終把P(Ci|Doc)取得最大值的那個(gè)分類作為文檔的類別。其中,P(Doc)對(duì)于類別Ci來(lái)說(shuō)是常量,在比較大小時(shí)可以不用參與計(jì)算,而P(Ci)表示類別Ci出現(xiàn)的概率,我們稱之為先驗(yàn)概率,這可以方便地從訓(xùn)練集中統(tǒng)計(jì)得出,至于P(Doc|Ci),也就是類別的條件概率,如果沒(méi)有樸素貝葉斯的假設(shè),那么計(jì)算是非常困難的。

舉例來(lái)說(shuō),假設(shè)有一篇文章,內(nèi)容為“王者榮耀:兩款傳說(shuō)品質(zhì)皮膚將優(yōu)化,李白最新模型海報(bào)爆料”,經(jīng)過(guò)特征選擇,文檔可以表示為Doc=(王者榮耀,傳說(shuō),品質(zhì),皮膚,優(yōu)化,李白,最新,模型,海報(bào),爆料),那么在預(yù)測(cè)時(shí)需要計(jì)算P(王者榮耀,傳說(shuō),品質(zhì),皮膚,優(yōu)化,李白,最新,模型,海報(bào),爆料|Ci),這樣一個(gè)條件概率是不可計(jì)算的,因?yàn)榈谝粋€(gè)特征取值為“王者榮耀”,第二個(gè)特征取值“傳說(shuō)”……第十個(gè)特征取值“爆料”的文檔很可能為沒(méi)有,那么概率就為零,而基于樸素貝葉斯的假設(shè),這個(gè)條件概率可以轉(zhuǎn)化為:

P(王者榮耀,傳說(shuō),品質(zhì),皮膚,優(yōu)化,李白,最新,模型,海報(bào),爆料|Ci)=P(王者榮耀|Ci)P(傳說(shuō)|Ci)……P(爆料|Ci)

于是我們就可以統(tǒng)計(jì)這些特征詞在每個(gè)類別中出現(xiàn)的概率了,在這個(gè)例子中,游戲類別中“王者榮耀”這個(gè)特征項(xiàng)會(huì)頻繁出現(xiàn),因此P(王者榮耀|游戲)的條件概率要明顯高于其他類別,這就是樸素貝葉斯模型的樸素之處,粗魯?shù)穆斆鳌?/p> 4.2 多項(xiàng)式模型與伯努利模型

在具體實(shí)現(xiàn)中,樸素貝葉斯又可以分為兩種模型,多項(xiàng)式模型(Multinomial)和伯努利模型(Bernoulli),另外還有高斯模型,主要用于處理連續(xù)型變量,在文本分類中不討論。

多項(xiàng)式模型和伯努利模型的區(qū)別在于對(duì)詞頻的考察,在多項(xiàng)式模型中文檔中特征項(xiàng)的頻度是參與計(jì)算的,這對(duì)于長(zhǎng)文本來(lái)說(shuō)是比較公平的,例如上面的例子,“王者榮耀”在游戲類的文檔中頻度會(huì)比較高,而伯努利模型中,所有特征詞都均等地對(duì)待,只要出現(xiàn)就記為1,未出現(xiàn)就記為0,兩者公式如下:

在伯努利模型計(jì)算公式中,N(Doc(tj)|Ci)表示Ci類文檔中特征tj出現(xiàn)的文檔數(shù),|D|表示類別Ci的文檔數(shù),P(Ci)可以用類別Ci的文檔數(shù)/文檔總數(shù)來(lái)計(jì)算,

在多項(xiàng)式模型計(jì)算公式中,TF(ti,Doc)是文檔Doc中特征ti出現(xiàn)的頻度,TF(ti,Ci)就表示類別Ci中特征ti出現(xiàn)的頻度,|V|表示特征空間的大小,也就是特征選擇之后,不同(即去掉重復(fù)之后)的特征項(xiàng)的總個(gè)數(shù),而P(Ci)可以用類別Ci中特征詞的總數(shù)/所有特征詞的總數(shù),所有特征詞的總數(shù)也就是所有特征詞的詞頻之和。

至于分子和分母都加上一定的常量,這是為了防止數(shù)據(jù)稀疏而產(chǎn)生結(jié)果為零的現(xiàn)象,這種操作稱為拉普拉斯平滑,至于背后的原理,推薦閱讀這篇博客:貝葉斯統(tǒng)計(jì)觀點(diǎn)下的拉普拉斯平滑

4.3 算法實(shí)現(xiàn)

這里使用了枚舉類來(lái)封裝兩個(gè)模型,并實(shí)現(xiàn)了分類器NaiveBayesClassifier和訓(xùn)練器NaiveBayesLearner中的兩個(gè)接口,其中Pprior和Pcondition是訓(xùn)練器所需的方法,前者用來(lái)計(jì)算先驗(yàn)概率,后者用來(lái)計(jì)算不同特征項(xiàng)在不同類別下的條件概率;getConditionProbability是分類器所需的方法,NaiveBayesKnowledgeBase對(duì)象是模型數(shù)據(jù)的容器,它的getPconditionByWord方法就是用于查詢不同特征詞在不同類別下的條件概率

public enum NaiveBayesModels implements NaiveBayesClassifier.Model, NaiveBayesLearner.Model {
    Bernoulli {
        @Override
        public double Pprior(int total, Category category) {
            int Nc = category.getDocCount();
            return Math.log((double) Nc / total);
        }

        @Override
        public double Pcondition(Feature feature, Category category, double smoothing) {
            int Ncf = feature.getDocCountByCategory(category);
            int Nc = category.getDocCount();
            return Math.log((double) (1 + Ncf) / (Nc + smoothing));
        }

        @Override
        public List getConditionProbability(String category, List terms, final NaiveBayesKnowledgeBase knowledgeBase) {
            return terms.stream().map(term -> knowledgeBase.getPconditionByWord(category, term.getWord())).collect(toList());
        }
    },
    Multinomial {
        @Override
        public double Pprior(int total, Category category) {
            int Nt = category.getTermCount();
            return Math.log((double) Nt / total);
        }

        @Override
        public double Pcondition(Feature feature, Category category, double smoothing) {
            int Ntf = feature.getTermCountByCategory(category);
            int Nt = category.getTermCount();
            return Math.log((double) (1 + Ntf) / (Nt + smoothing));
        }

        @Override
        public List getConditionProbability(String category, List terms, final NaiveBayesKnowledgeBase knowledgeBase) {
            return terms.stream().map(term -> term.getTf() * knowledgeBase.getPconditionByWord(category, term.getWord())).collect(toList());
        }
    };
}
五、訓(xùn)練模型

根據(jù)樸素貝葉斯模型的定義,訓(xùn)練模型的過(guò)程就是計(jì)算每個(gè)類的先驗(yàn)概率,以及每個(gè)特征項(xiàng)在不同類別下的條件概率,NaiveBayesKnowledgeBase對(duì)象將訓(xùn)練器在訓(xùn)練時(shí)得到的結(jié)果都保存起來(lái),訓(xùn)練完成時(shí)寫入文件,啟動(dòng)分類時(shí)從文件中讀入數(shù)據(jù)交由分類器使用,那么在分類時(shí)就可以直接參與到計(jì)算過(guò)程中。

訓(xùn)練器的實(shí)現(xiàn)如下:

public class NaiveBayesLearner {
    ……
    ……
    public NaiveBayesLearner statistics() {
        log.info("開(kāi)始統(tǒng)計(jì)...");
        this.total = total();
        log.info("total : " + total);
        this.categorySet = trainSet.getCategorySet();
        featureSet.forEach(f -> f.getCategoryTermCounter().forEach((category, count) -> category.setTermCount(category.getTermCount() + count)));
        return this;
    }

    public NaiveBayesKnowledgeBase build() {
        this.knowledgeBase.setCategories(createCategorySummaries(categorySet));
        this.knowledgeBase.setFeatures(createFeatureSummaries(featureSet, categorySet));
        return knowledgeBase;
    }

    private Map createFeatureSummaries(final Set featureSet, final Set categorySet) {
        return featureSet.parallelStream()
                .map(f -> knowledgeBase.createFeatureSummary(f, getPconditions(f, categorySet)))
                .collect(toMap(NaiveBayesKnowledgeBase.FeatureSummary::getWord, Function.identity()));
    }

    private Map createCategorySummaries(final Set categorySet) {
        return categorySet.stream().collect(toMap(Category::getName, c -> model.Pprior(total, c)));
    }

    private Map getPconditions(final Feature feature, final Set categorySet) {
        final double smoothing = smoothing();
        return categorySet.stream()
                .collect(toMap(Category::getName, c -> model.Pcondition(feature, c, smoothing)));
    }

    private int total() {
        if (model == Multinomial)
            return featureSet.parallelStream().map(Feature::getTerm).mapToInt(Term::getTf).sum();//總詞頻數(shù)
        else if (model == Bernoulli)
            return trainSet.getTotalDoc();//總文檔數(shù)
        return 0;
    }

    private double smoothing() {
        if (model == Multinomial)
            return this.featureSet.size();
        else if (model == Bernoulli)
            return 2.0;
        return 0.0;
    }

    public static void main(String[] args) {
        TrainSet trainSet = new TrainSet(System.getProperty("user.dir") + "/trainset/");

        log.info("特征選擇開(kāi)始...");
        FeatureSelection featureSelection = new FeatureSelection(new ChiSquaredStrategy(trainSet.getCategorySet(), trainSet.getTotalDoc()));
        List features = featureSelection.select(trainSet.getDocs());
        log.info("特征選擇完成,特征數(shù):[" + features.size() + "]");

        NaiveBayesModels model = NaiveBayesModels.Multinomial;
        NaiveBayesLearner learner = new NaiveBayesLearner(model, trainSet, Sets.newHashSet(features));
        learner.statistics().build().write(model.getModelPath());
        log.info("模型文件寫入完成,路徑:" + model.getModelPath());
    }
}

在main函數(shù)中執(zhí)行整個(gè)訓(xùn)練過(guò)程,首先執(zhí)行特征選擇,這里使用卡方檢驗(yàn)法,然后將得到特征空間,樸素貝葉斯模型(多項(xiàng)式模型),以及訓(xùn)練集TrainSet對(duì)象作為參數(shù),初始化訓(xùn)練器,接著訓(xùn)練器開(kāi)始進(jìn)行統(tǒng)計(jì)的工作,事實(shí)上有一部分的統(tǒng)計(jì)工作,在初始化訓(xùn)練集對(duì)象時(shí),就已經(jīng)完成了,例如總文檔數(shù),每個(gè)類別下的文檔數(shù)等,這些可以直接拿過(guò)來(lái)使用,最終將數(shù)據(jù)都裝載到NaiveBayesKnowledgeBase對(duì)象當(dāng)中去,并寫入文件,格式為第一行是不同類別的先驗(yàn)概率,余下每一行對(duì)應(yīng)一個(gè)特征項(xiàng),每一列對(duì)應(yīng)不同類別的條件概率值。

六,測(cè)試模型

分類器預(yù)測(cè)過(guò)程就相對(duì)于比較簡(jiǎn)單了,通過(guò)NaiveBayesKnowledgeBase讀入數(shù)據(jù),然后將指定的文本進(jìn)行分詞,匹配特征項(xiàng),然后計(jì)算在不同類別下的后驗(yàn)概率,返回取得最大值對(duì)應(yīng)的那個(gè)類別。

public class NaiveBayesClassifier {
    ……
    private final Model model;

    private final NaiveBayesKnowledgeBase knowledgeBase;

    public NaiveBayesClassifier(Model model) {
        this.model = model;
        this.knowledgeBase = new NaiveBayesKnowledgeBase(model.getModelPath());
    }

    public String predict(String content) {
        Set allFeatures = knowledgeBase.getFeatures().keySet();
        List terms = NLPTools.instance().segment(content).stream()
                .filter(t -> allFeatures.contains(t.getWord())).distinct().collect(toList());

        @AllArgsConstructor
        class Result {
            final String category;
            final double probability;
        }

        Result result = knowledgeBase.getCategories().keySet().stream()
                .map(c -> new Result(c, Calculator.Ppost(knowledgeBase.getCategoryProbability(c),
                        model.getConditionProbability(c, terms, knowledgeBase))))
                .max(Comparator.comparingDouble(r -> r.probability)).get();
        return result.category;
    }
}

在實(shí)際測(cè)試時(shí),我們又多帶帶抓取了搜狗微信搜索網(wǎng)站上的文章,按照100篇一組,一共30組進(jìn)行分類的測(cè)試,最終結(jié)果每一組的準(zhǔn)確率均在90%以上,最高達(dá)98%,效果良好。當(dāng)然正規(guī)的評(píng)測(cè)需要同時(shí)評(píng)估準(zhǔn)確率和召回率,這里就偷懶不做了。

另外還需要說(shuō)明一點(diǎn)的是,由于訓(xùn)練集是來(lái)源于搜狗微信搜索網(wǎng)站的文章,類別僅限于這20個(gè),這不足以覆蓋所有微信公眾號(hào)文章的類別,因此在測(cè)試其他來(lái)源的微信文章準(zhǔn)確率一定會(huì)有所影響。當(dāng)然如果有更加豐富的微信文章訓(xùn)練集的話,也可以利用這個(gè)模型重新訓(xùn)練,那么效果也會(huì)越來(lái)越好。

七、參考文獻(xiàn)與引用

宗成慶. 統(tǒng)計(jì)自然語(yǔ)言處理[M]. 清華大學(xué)出版社, 2013.

T.M.Mitchell. 機(jī)器學(xué)習(xí)[M]. 機(jī)械工業(yè)出版社, 2003.

吳軍. 數(shù)學(xué)之美[M]. 人民郵電出版社, 2012.

Raoul-Gabriel Urma, Mario Fusco, Alan Mycroft. Java 8 實(shí)戰(zhàn)[M]. 人民郵電出版社, 2016.

Ansj中文分詞器,https://github.com/NLPchina/a...

HanLP中文分詞器,https://github.com/hankcs/HanLP

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/68627.html

相關(guān)文章

  • 關(guān)于Java IO與NIO知識(shí)都在這里

    摘要:從通道進(jìn)行數(shù)據(jù)寫入創(chuàng)建一個(gè)緩沖區(qū),填充數(shù)據(jù),并要求通道寫入數(shù)據(jù)。三之通道主要內(nèi)容通道介紹通常來(lái)說(shuō)中的所有都是從通道開(kāi)始的。從中選擇選擇器維護(hù)注冊(cè)過(guò)的通道的集合,并且這種注冊(cè)關(guān)系都被封裝在當(dāng)中停止選擇的方法方法和方法。 由于內(nèi)容比較多,我下面放的一部分是我更新在我的微信公眾號(hào)上的鏈接,微信排版比較好看,更加利于閱讀。每一篇文章下面我都把文章的主要內(nèi)容給列出來(lái)了,便于大家學(xué)習(xí)與回顧。 Ja...

    Riddler 評(píng)論0 收藏0
  • 26天學(xué)通前端開(kāi)發(fā)(配資料)

    摘要:網(wǎng)上有很多前端的學(xué)習(xí)路徑文章,大多是知識(shí)點(diǎn)羅列為主或是資料的匯總,數(shù)據(jù)量讓新人望而卻步。天了解一個(gè)前端框架。也可以關(guān)注微信公眾號(hào)曉舟報(bào)告,發(fā)送獲取資料,就能收到下載密碼,網(wǎng)盤地址在最下方,獲取教程和案例的資料。 前言 好的學(xué)習(xí)方法可以事半功倍,好的學(xué)習(xí)路徑可以指明前進(jìn)方向。這篇文章不僅要寫學(xué)習(xí)路徑,還要寫學(xué)習(xí)方法,還要發(fā)資料,干貨滿滿,準(zhǔn)備接招。 網(wǎng)上有很多前端的學(xué)習(xí)路徑文章,大多是知...

    blair 評(píng)論0 收藏0
  • SegmentFault 技術(shù)周刊 Vol.30 - 學(xué)習(xí) Python 來(lái)做一些神奇好玩的事情吧

    摘要:學(xué)習(xí)筆記七數(shù)學(xué)形態(tài)學(xué)關(guān)注的是圖像中的形狀,它提供了一些方法用于檢測(cè)形狀和改變形狀。學(xué)習(xí)筆記十一尺度不變特征變換,簡(jiǎn)稱是圖像局部特征提取的現(xiàn)代方法基于區(qū)域圖像塊的分析。本文的目的是簡(jiǎn)明扼要地說(shuō)明的編碼機(jī)制,并給出一些建議。 showImg(https://segmentfault.com/img/bVRJbz?w=900&h=385); 前言 開(kāi)始之前,我們先來(lái)看這樣一個(gè)提問(wèn): pyth...

    lifesimple 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<