原創(chuàng)|使用教程|編輯:鄭恭琳|2018-01-10 11:23:30.000|閱讀 745 次
概述:本文將帶您了解如何開發(fā)和使用您自己的基于機(jī)器學(xué)習(xí)的電子郵件垃圾郵件分類系統(tǒng)。因為,誰會喜歡垃圾郵件呢?
# 界面/圖表報表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
相關(guān)鏈接:
在這篇文章中,我們將開發(fā)一個應(yīng)用程序來檢測垃圾郵件。將使用的算法是從SPARK MLib實現(xiàn)的邏輯回歸。對這個領(lǐng)域不需要深入的了解,因為這些主題是從高層次的角度來描述的。完整的工作代碼將與一個正在運行的應(yīng)用程序一起提供,以供您選擇電子郵件的進(jìn)一步實驗。
邏輯回歸是一種用于分類問題的算法。在分類問題中,我們給了很多標(biāo)簽化的數(shù)據(jù)(垃圾郵件,非垃圾郵件),當(dāng)一個新的例子來臨時,我們想知道它屬于哪個類別。由于它是一種機(jī)器學(xué)習(xí)算法,Logistic回歸用標(biāo)記數(shù)據(jù)進(jìn)行訓(xùn)練,并基于訓(xùn)練給出了關(guān)于新的例子的預(yù)測。
一般來說,當(dāng)大量數(shù)據(jù)可用時,我們需要檢測一個例子屬于哪個類別,可以使用邏輯回歸(即使結(jié)果并不總是令人滿意)。
例如,當(dāng)分析數(shù)百萬患者的健康狀況以預(yù)測患者是否有心肌梗塞時,可以使用邏輯回歸。同樣的邏輯可以用來預(yù)測患者是否會患上特定的癌癥,是否會受到抑郁癥等的影響。在這個應(yīng)用程序中,我們有相當(dāng)數(shù)量的數(shù)據(jù),所以邏輯回歸通常會給出很好的提示。
基于圖像密度的顏色,我們可以分類,比如說,圖像是否包含人或包含汽車。此外,由于這是一個分類問題,我們也可能使用邏輯回歸來檢測圖片是否有字符,甚至是檢測手寫。
邏輯回歸最常見的應(yīng)用之一是分類垃圾郵件。在這個應(yīng)用程序中,算法確定傳入的電子郵件或消息是否是垃圾郵件。當(dāng)建立一個非個性化的算法時,需要大量的數(shù)據(jù)。個性化過濾器通常表現(xiàn)更好,因為垃圾郵件分類器在某種程度上取決于個人的興趣和背景。
我們有很多標(biāo)記的例子,并且想要訓(xùn)練我們的算法足夠聰明,可以說出新的例子是否屬于其中一個類別。為了簡化,我們將首先參考二進(jìn)制分類(1或0)。算法也容易擴(kuò)展到多分類。
通常情況下,我們有多維數(shù)據(jù)或具有許多特征的數(shù)據(jù)。這些功能中的每一個都以某種方式有助于最終決定新范例屬于哪個范疇。例如,在癌癥分類問題中,我們可以具有年齡、吸煙與否、體重、身高、家族基因組等特征。這些功能中的每一個都有助于最終的類別決定。特征并不等于決定權(quán),而是在確定最終狀態(tài)時有不同的影響。例如,在癌癥預(yù)測中,體重比家族基因組的影響更小。在邏輯回歸中,這正是我們試圖找出的結(jié)果:數(shù)據(jù)特征的權(quán)重/影響。一旦我們有了大量的數(shù)據(jù)例子,我們就可以確定每個特征的權(quán)重,當(dāng)新的例子出現(xiàn)時,我們使用權(quán)重來看看這個例子是如何分類的。在癌癥預(yù)測的例子中,我們可以這樣寫:
更正式地說:
n =例子的數(shù)量
k =特征的數(shù)量
θj=特征j的權(quán)重
Xji =具有特征j的第i個例子X
為了將數(shù)據(jù)分類,我們需要一個函數(shù)(假設(shè)),根據(jù)示例、值和特征,可以將數(shù)據(jù)放入兩個類別之一。我們使用的函數(shù)被稱為Sigmoid函數(shù),如下圖所示:
正如我們所看到的那樣,當(dāng)X軸上的值是正值時,Sigmoid函數(shù)值往往趨于1;當(dāng)X軸上的值為負(fù)值時,趨向于0。基本上,我們有一個模型來表示兩個類別和數(shù)學(xué),功能如下所示:
Z是在“Insight”下解釋的功能。
要獲得離散值(1或0),可以說當(dāng)一個函數(shù)值(Y軸)大于0.5時,我們將其歸類為1;當(dāng)函數(shù)值(Y軸)小于0.5時,我們將其歸類為0。如下所述:
我們不希望僅僅找到任何權(quán)重,而是要求實際數(shù)據(jù)的最佳權(quán)重。為了找到最好的權(quán)重,我們需要另一個函數(shù)來計算我們找到的特定權(quán)重的解決方案。有了這個功能,我們可以比較不同解決方案與不同的權(quán)重,找到最好的一個。這個功能被稱為成本函數(shù)(Cost Function)。它將假設(shè)(Sigmoid)函數(shù)值與實際數(shù)據(jù)值進(jìn)行比較。由于我們用于培訓(xùn)的數(shù)據(jù)被標(biāo)記(垃圾郵件,非垃圾郵件),我們將假設(shè)(Sigmoid)預(yù)測與實際值進(jìn)行比較,我們知道這是肯定的。我們希望假設(shè)和實際價值之間的差距越小越好, 理想情況下,我們希望成本函數(shù)為零。更正式地說,成本函數(shù)被定義為:
其中yi是真正的價值/類別,如垃圾郵件/不是垃圾郵件或1/0,h(x)是假設(shè)。
基本上,這個公式計算我們的預(yù)測與實際標(biāo)記數(shù)據(jù)(y)的比較(平均)有多好。因為我們有兩個情況(1和0),所以我們有兩個Hs(假設(shè)):h1和h0。我們將log用于假設(shè),使得函數(shù)是凸的,找到全局最小值更安全。
我們來看看h1,這是與類別1的成本函數(shù)有關(guān)的假設(shè)。
我們將log用于我們的假設(shè),而不是直接使用它,因為我們希望實現(xiàn)一種關(guān)系,當(dāng)假設(shè)接近1時,成本函數(shù)為零。請記住,我們希望我們的成本函數(shù)為零,以便在假設(shè)預(yù)測和標(biāo)記數(shù)據(jù)之間沒有差異。如果假設(shè)要預(yù)測0,我們的成本函數(shù)增長很大,所以我們知道這不屬于第一類;如果假設(shè)要預(yù)測1,則成本函數(shù)變?yōu)?,表明該例子屬于類別1。
我們來看看h2,這是關(guān)于類別0的成本函數(shù)的假設(shè)。
在這種情況下,我們再次應(yīng)用log,但是當(dāng)假設(shè)還要預(yù)測零時,使成本函數(shù)變?yōu)榱恪H绻僭O(shè)要預(yù)測1,我們的成本函數(shù)就會變大,所以我們知道這不屬于0類;如果假設(shè)要預(yù)測0,則成本函數(shù)變?yōu)?,表示該例子屬于0類。
現(xiàn)在,我們有兩個成本函數(shù),我們需要把它們合并成一個。在這之后,等式變得有些雜亂,但原則上,這只是我們上面解釋的兩個成本函數(shù)的合并:
注意,第一項是h1的成本函數(shù),第二項是h0的成本函數(shù)。所以,如果y = 1,那么第二項被消除,如果y = 0,則第一項被消除。
正如我們上面看到的,我們希望我們的成本函數(shù)為零,以便我們的預(yù)測盡可能接近真實值(標(biāo)記)。幸運的是,已經(jīng)有一個算法來最小化成本函數(shù):梯度下降(gradient descent)。一旦我們有成本函數(shù)(基本上將我們的假設(shè)與真實值相比較),我們可以把我們的權(quán)重(θ)同樣盡可能降低成本函數(shù)。首先,我們選擇θ的隨機(jī)值只是為了獲得一些值。然后,我們計算成本函數(shù)。根據(jù)結(jié)果,我們可以減少或增加我們的θ值,使成本函數(shù)優(yōu)化為零。我們重復(fù)這一點,直到成本函數(shù)幾乎為零(0.0001),或從迭代到迭代沒有太大改善。
梯度下降原則上是這樣做的;它只是成本函數(shù)的一個導(dǎo)數(shù),以決定是減小還是增加θ值。它還使用系數(shù)α來定義改變θ值的數(shù)量。改變θ值太大(大α)會使梯度下降在優(yōu)化成本函數(shù)為零時失敗,因為大的增加可能會克服實際值或遠(yuǎn)離期望值。雖然θ(小α)的小變化意味著我們是安全的,但是算法需要大量的時間才能達(dá)到成本函數(shù)的最小值(幾乎為零),因為我們正朝著想要的或?qū)嶋H值進(jìn)展太慢(為更多的可視化解釋,請看這里)。更正式的,我們有:
右邊的項是成本函數(shù)的導(dǎo)數(shù)(僅針對特征k改變X的倍數(shù))。由于我們的數(shù)據(jù)是多維的(k個特征),我們對每個特征權(quán)重(θk)都做了這個。
讓我們看看準(zhǔn)備數(shù)據(jù)、轉(zhuǎn)換數(shù)據(jù)、執(zhí)行和結(jié)果。
在執(zhí)行數(shù)據(jù)之前,我們需要做一些數(shù)據(jù)預(yù)處理來清理不需要的信息。數(shù)據(jù)后處理的主要思想是從這個Coursera作業(yè)。我們做以下工作:
代碼實現(xiàn)將如下所示:
private ListfilesToWords(String fileName) throws Exception { URI uri = this.getClass().getResource("/" + fileName).toURI(); Path start = getPath(uri); List< String > collect = Files.walk(start).parallel() .filter(Files::isRegularFile) .flatMap(file -> { try { return Stream.of(new String(Files.readAllBytes(file)).toLowerCase()); } catch (IOException e) { e.printStackTrace(); } return null; }).collect(Collectors.toList()); return collect.stream().parallel().flatMap(e -> tokenizeIntoWords(prepareEmail(e)).stream()).collect(Collectors.toList()); }
private String prepareEmail(String email) { int beginIndex = email.indexOf("\n\n"); String withoutHeader = email; if (beginIndex > 0) { withoutHeader = email.substring(beginIndex, email.length()); } String tagsRemoved = withoutHeader.replaceAll("< [^< >]+>", ""); String numberedReplaced = tagsRemoved.replaceAll("[0-9]+", "XNUMBERX "); String urlReplaced = numberedReplaced.replaceAll("(http|https)://[^\\s]*", "XURLX "); String emailReplaced = urlReplaced.replaceAll("[^\\s]+@[^\\s]+", "XEMAILX "); String dollarReplaced = emailReplaced.replaceAll("[$]+", "XMONEYX "); return dollarReplaced; }
private List< String > tokenizeIntoWords(String dollarReplaced) { String delim = "[' @$/#.-:&*+=[]?!(){},''\\\">_<;%'\t\n\r\f"; StringTokenizer stringTokenizer = new StringTokenizer(dollarReplaced, delim); List< String > wordsList = new ArrayList<>(); while (stringTokenizer.hasMoreElements()) { String word = (String) stringTokenizer.nextElement(); String nonAlphaNumericRemoved = word.replaceAll("[^a-zA-Z0-9]", ""); PorterStemmer stemmer = new PorterStemmer(); stemmer.setCurrent(nonAlphaNumericRemoved); stemmer.stem(); String stemmed = stemmer.getCurrent(); wordsList.add(stemmed); } return wordsList; }
一旦電子郵件準(zhǔn)備好了,我們需要將數(shù)據(jù)轉(zhuǎn)換成算法理解的結(jié)構(gòu),如矩陣和特征。
第一步是建立一個“垃圾郵件詞匯(spam vocabulary)”,通過閱讀所有的垃圾郵件的詞匯和計數(shù)。例如,我們計算了使用“transaction”、“XMONEYX”、“finance”、“win”和“free”的次數(shù),然后拿出10個(featureSize)最常見的單詞,此時我們有地圖的大小為10(featureSize),其中的關(guān)鍵是單詞,值是從0到9.999的索引。這將作為可能的垃圾郵件詞的參考。請參閱下面的代碼:
public Map< String, Integer > createVocabulary() throws Exception { String first = "allInOneSpamBase/spam"; String second = "allInOneSpamBase/spam_2"; List< String > collect1 = filesToWords(first); List< String > collect2 = filesToWords(second); ArrayList< String > all = new ArrayList<>(collect1); all.addAll(collect2); HashMap< String, Integer > countWords = countWords(all); List< Map.Entry< String, Integer >> sortedVocabulary = countWords.entrySet().stream().parallel().sorted((o1, o2) -> o2.getValue().compareTo(o1.getValue())).collect(Collectors.toList()); final int[] index = {0}; return sortedVocabulary.stream().limit(featureSIze).collect(Collectors.toMap(e -> e.getKey(), e -> index[0]++)); }
HashMap< String, Integer > countWords(Listall) { HashMap< String, Integer > countWords = new HashMap<>(); for (String s : all) { if (countWords.get(s) == null) { countWords.put(s, 1); } else { countWords.put(s, countWords.get(s) + 1); } } return countWords; }
下一步是統(tǒng)計這些詞在我們的垃圾郵件和非垃圾郵件中的詞頻。然后,我們查看垃圾郵件詞匯表中的每個單詞,看它是否在那里。如果是(表示電子郵件有可能是垃圾郵件詞),我們把這個詞放在垃圾郵件詞匯表中包含的同一個索引中,并且把這個詞放在頻率上。最后,我們建立一個矩陣Nx10.000,其中N是所考慮的電子郵件的數(shù)量,10.000是包含電子郵件中的垃圾郵件詞匯映射詞的頻率的向量(如果在電子郵件中沒有發(fā)現(xiàn)垃圾郵件詞,我們設(shè)為0)。
例如,假設(shè)我們有如下的垃圾郵件詞匯表:
還有一個像下面這樣的電子郵件:
anyon know how much it cost to host a web portal well it depend on how mani visitor your expect thi can be anywher from less than number buck a month to a coupl of dollarnumb you should checkout XURLX or perhap amazon ecnumb if your run someth big to unsubscrib yourself from thi mail list send an email to XEMAILX
轉(zhuǎn)型后,我們將有:
0 2 0 1 1 1 0 0
所以我們有0 aa、2 how、0 abil、1 anyon、1 know、0 zero、0 zip。這是一個1X7的矩陣,因為我們有一個電子郵件和7個字的垃圾郵件詞匯。代碼如下所示:
private Vector transformToFeatureVector(Email email, Map< String, Integer > vocabulary) { List< String > words = email.getWords(); HashMap< String, Integer > countWords = prepareData.countWords(words); double[] features = new double[featureSIze];//featureSIze==10.000 for (Map.Entry< String, Integer > word : countWords.entrySet()) { Integer index = vocabulary.get(word.getKey());//see if it is in //spam vocabulary if (index != null) { //put frequency the same index as the vocabulary features[index] = word.getValue(); } } return Vectors.dense(features); }
盡管Java必須安裝在您的計算機(jī)上,但應(yīng)用程序可以在沒有任何Java知識的情況下下載和執(zhí)行。隨意用自己的電子郵件測試算法。
我們可以通過執(zhí)行RUN類來從源代碼運行應(yīng)用程序。或者,如果您不想用IDE打開它,只需運行mvn clean install exec:java。
之后,你應(yīng)該看到這樣的情況:
首先,通過點擊使用Train with LR SGD或使用Train with LR LBFGS訓(xùn)練算法。這可能需要一到兩分鐘的時間。完成后,彈出窗口將顯示所達(dá)到的精度。不要擔(dān)心SGD與LBFGS的區(qū)別——它們只是使成本函數(shù)最小化的不同方法,并且會得到幾乎相同的結(jié)果。之后,將您選擇的電子郵件復(fù)制并粘貼到白色區(qū)域,然后按“Test”。之后,彈出窗口將顯示算法的預(yù)測。
在執(zhí)行過程中達(dá)到的精確度大約為97%,使用隨機(jī)80%的訓(xùn)練數(shù)據(jù)和20%的測試數(shù)據(jù)。沒有交叉驗證測試——在這個例子中只使用了訓(xùn)練和測試(對于準(zhǔn)確性)集合。要了解有關(guān)劃分?jǐn)?shù)據(jù)的更多信息,請參閱此處。
訓(xùn)練算法的代碼相當(dāng)簡單:
public MulticlassMetrics execute() throws Exception { vocabulary = prepareData.createVocabulary(); List< LabeledPoint > labeledPoints = convertToLabelPoints(); sparkContext = createSparkContext(); JavaRDD< LabeledPoint > labeledPointJavaRDD = sparkContext.parallelize(labeledPoints); JavaRDD< LabeledPoint >[] splits = labeledPointJavaRDD.randomSplit(new double[]{0.8, 0.2}, 11L); JavaRDD< LabeledPoint > training = splits[0].cache(); JavaRDD< LabeledPoint > test = splits[1]; linearModel = model.run(training.rdd());//training with 80% data //testing with 20% data JavaRDD< Tuple2< Object, Object >> predictionAndLabels = test.map( (Function< LabeledPoint, Tuple2< Object, Object >>) p -> { Double prediction = linearModel.predict(p.features()); return new Tuple2<>(prediction, p.label()); } ); return new MulticlassMetrics(predictionAndLabels.rdd()); }
就是這樣!
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請郵件反饋至chenjj@fc6vip.cn