第一次參加阿里天池的比賽沫屡,雖然總成績沒能進(jìn)前十,但是網(wǎng)頁題做的還不錯(最終排名第二撮珠,成績0.8312分)沮脖。在這里記錄一下網(wǎng)頁文本分類題的解題思路和賽后總結(jié):
出題背景是阿里云服務(wù)器上存在著很多非法網(wǎng)頁金矛,需要通過機(jī)器學(xué)習(xí)算法識別出網(wǎng)頁的類別,對非法網(wǎng)頁進(jìn)行打擊勺届。題目的詳細(xì)描述見賽題官方網(wǎng)址賽題二驶俊。原始數(shù)據(jù)是網(wǎng)頁靜態(tài)html代碼,但可轉(zhuǎn)換為文本分類問題免姿。訓(xùn)練集的標(biāo)簽已給出饼酿,共有4個類別:
- fake_card(證件卡票):提供偽造證件、發(fā)票胚膊、證明材料等制作服務(wù)故俐;
- gambling(賭博類):包括賭博交易、網(wǎng)絡(luò)博彩紊婉、賭博器具等药版;
- sexy(色情類):包括色情傳播、文圖視頻喻犁、網(wǎng)絡(luò)招嫖等槽片;
- normal(正常類):不包含以上內(nèi)容的網(wǎng)頁。
算法整體流程圖:
關(guān)鍵步驟:
數(shù)據(jù)預(yù)處理
訓(xùn)練數(shù)據(jù)中肢础,id字段是經(jīng)過hash處理的还栓,會出現(xiàn)id重復(fù)的情況,需要根據(jù)id對數(shù)據(jù)進(jìn)行去重传轰;另外對各類型網(wǎng)頁html文本長度的統(tǒng)計結(jié)果如下所示:
網(wǎng)頁文本長度大于200000的不到網(wǎng)頁數(shù)據(jù)總量的1%剩盒,且基本不包含風(fēng)險網(wǎng)頁,所以對其進(jìn)行清除慨蛙,減少后續(xù)的計算量勃刨;觀察出字符長度小于200的網(wǎng)頁中大部分內(nèi)容為"404 ERROR","正在等待響應(yīng)...",'"排隊中..."等內(nèi)容,對模型的訓(xùn)練沒有用處股淡,也要進(jìn)行清除身隐。代碼在下方:
--計算id的出現(xiàn)次數(shù)
select id, risk, html, count(id) over (partition by id) as count from ${t1} ;
--計算html文本的長度
select *, length(html) as html_length from ${t1} ;
--數(shù)據(jù)清理
select * from ${t1} where count = 1 and html_length > 200 and html_length < 200000 ;
網(wǎng)頁文本提取
html字段是網(wǎng)頁的源代碼,包含了結(jié)構(gòu)化的html標(biāo)簽唯灵,<style>贾铝、<script>等標(biāo)簽中的內(nèi)容不包含語義信息,參考文獻(xiàn)[5]中結(jié)論埠帕,實驗中只對title垢揩、keywords、description敛瓷、body標(biāo)簽中的文本進(jìn)行提取叁巨,另外去除文本中的特殊字符,僅保留英文呐籽、中文以及常見的標(biāo)點(diǎn)符號锋勺。html中文本內(nèi)容的提取通過在MapReduce的map函數(shù)中調(diào)用jsoup庫實現(xiàn)(因為ODPS有沙箱隔離的限制蚀瘸,實驗中抽取了jsoup的核心代碼放到自定義的包中打包上傳)
package cn.edu.whu.tianchi_htmlclassify;
import cn.edu.whu.tianchi_htmlclassify.nodes.Document;
import cn.edu.whu.tianchi_htmlclassify.nodes.Element;
import cn.edu.whu.tianchi_htmlclassify.select.Elements;
import com.aliyun.odps.OdpsException;
import com.aliyun.odps.data.Record;
import com.aliyun.odps.data.TableInfo;
import com.aliyun.odps.mapred.JobClient;
import com.aliyun.odps.mapred.MapperBase;
import com.aliyun.odps.mapred.conf.JobConf;
import com.aliyun.odps.mapred.utils.InputUtils;
import com.aliyun.odps.mapred.utils.OutputUtils;
import com.aliyun.odps.mapred.utils.SchemaUtils;
import java.io.IOException;
public class HtmlExtractor {
public static void main(String[] args) throws OdpsException {
JobConf job = new JobConf();
job.setMapperClass(MapperClass.class);
job.setMapOutputKeySchema(SchemaUtils.fromString("id:string"));
job.setMapOutputValueSchema(SchemaUtils.fromString(
"title:string,keywords:string,description:string,bodytext:string"));
job.setNumReduceTasks(0);
InputUtils.addTable(TableInfo.builder().tableName(args[0]).build(), job);
OutputUtils.addTable(TableInfo.builder().tableName(args[1]).build(), job);
JobClient.runJob(job);
}
public static class MapperClass extends MapperBase {
private Record output;
@Override
public void setup(TaskContext context) throws IOException {
output = context.createOutputRecord();
}
@Override
public void map(long key, Record record, TaskContext context)
throws IOException {
//id原樣設(shè)置
output.set(0, record.get("id").toString());
String html = record.get("html").toString();
String bodyText = new String();
String title = new String();
String description = new String();
String keywords = new String();
Document doc = null;
try {
//使用Jsoup解析html
doc = Jsoup.parse(html);
//去除隱藏的html標(biāo)簽
doc.select("*[style*=display:none]").remove();
//獲取title文本
Elements titles = doc.select("title");
for (Element element : titles) {
title = title + element.text();
}
//獲取keywords文本
Elements metaKey = doc.select("meta[name=keywords]");
for (Element element : metaKey) {
keywords = keywords + element.attr("content");
}
//獲取description文本
Elements metaDes = doc.select("meta[name=description]");
for (Element element : metaDes) {
description = description + element.attr("content");
}
//獲取body文本
Elements body = doc.select("body");
for (Element element : body) {
bodyText = bodyText + element.text();
}
} catch (Exception e) {
bodyText = "";
} finally {
//僅保留數(shù)字、字母庶橱、中文贮勃、常用的標(biāo)點(diǎn)符號
output.set(1,title.replaceAll(
"[^0-9a-zA-Z\u4e00-\u9fa5.,苏章、,寂嘉。?“”]+", ""));
output.set(2,keywords.replaceAll(
"[^0-9a-zA-Z\u4e00-\u9fa5.枫绅,泉孩、,。并淋?“”]+", ""));
output.set(3,description.replaceAll(
"[^0-9a-zA-Z\u4e00-\u9fa5.棵譬,、,预伺。?“”]+", ""));
output.set(4,bodyText.replaceAll(
"[^0-9a-zA-Z\u4e00-\u9fa5.曼尊,酬诀、,。骆撇?“”]+", ""));
context.write(output);
}
}
}
}
文本分詞
在中文文本分類中瞒御,常用的特征單元有字、詞神郊、字串(character n-gram)及其組合肴裙,中文中單個字的語義質(zhì)量不是很好,一般不作為特征單元涌乳,詞與二字串的效果相似蜻懦。在天池PAI平臺上,有基于AliWS(Alibaba Word Segmenter的簡稱)的分詞組件夕晓,若用戶指定了詞性標(biāo)注或語義標(biāo)注相關(guān)參數(shù)宛乃,則會將分詞結(jié)果、詞性標(biāo)注結(jié)果和語義標(biāo)注結(jié)果一同輸出蒸辆。在這里勾選詞性標(biāo)注征炼,在詞的初步過濾中會用到詞性。
詞初步過濾
- 停用詞過濾
使用 https://github.com/JNU-MINT/TextBayesClassifier 中停用詞躬贡; - 詞性過濾(僅保留動詞谆奥、名詞)
參考[7]中實驗結(jié)果,僅適用動詞拂玻、名詞作為特征詞酸些。
詞頻統(tǒng)計
在對文章進(jìn)行分詞的基礎(chǔ)上宰译,按行保序輸出對應(yīng)文章ID列(docId)對應(yīng)文章的詞,統(tǒng)計指定文章ID列(docId)對應(yīng)文章內(nèi)容(docContent)的詞頻擂仍。
使用詞頻統(tǒng)計組件分別對title囤屹、keywords、description逢渔、bodytext進(jìn)行詞頻統(tǒng)計肋坚,得到文檔i中詞j出現(xiàn)在title、keywords肃廓、description智厌、bodytext中的個數(shù)
可以對title、keywords盲赊、description铣鹏、body設(shè)置不同的權(quán)重,測試了不同權(quán)重的組合哀蘑,當(dāng)四個標(biāo)簽權(quán)重都為1時分類精度最高诚卸,詞頻權(quán)重組合的方法如下所示:
select *, (title_count * 1 + key_count * 1 + des_count * 1 + body_count) as count from ${t1}
特征詞選擇
特征詞的選擇在很多博客與論文[1][6]中都有敘述,常見的特征提取方法有文檔詞頻(Document Frequency, DF)绘迁、信息增益(Information Gain, IG)合溺、互信息(Mutual Information, MI)、卡方統(tǒng)計(CHI)缀台、期望交叉熵(Expected Cross Entropy)等等棠赛, 由于時間限制,我們試驗了幾種通常情況表現(xiàn)好的方法進(jìn)行結(jié)合:文檔詞頻 + 信息增益 + 卡方增益膛腐,對比了幾組不同的閾值睛约,得到了較好的分類效果。
- 文檔詞頻:在訓(xùn)練文本集中對每個詞計算其文檔頻數(shù)哲身,若該詞的DF值小于某個閾值或者大于某個閾值都要進(jìn)行去除辩涝,因為小于某個閾值代表了該詞“沒有代表性”,大于某個閾值代表了“沒有區(qū)分度”勘天。
- 信息增益:信息增益是一種基于熵的評估方法膀值,定義為某個詞為分類能夠提供的信息量, 其值為不考慮任何詞的熵與考慮該詞后的熵的差值误辑。計算公式如下:
- 卡方統(tǒng)計:卡方統(tǒng)計體現(xiàn)特征詞與類別之間獨(dú)立性的缺乏程度沧踏,它同時考慮了特征詞存在與不存在的情況〗矶ぃ卡方統(tǒng)計值越大翘狱,獨(dú)立性越小,相關(guān)性越大砰苍。計算公式如下:
- 計算步驟:
- 統(tǒng)計訓(xùn)練樣本的文檔的總數(shù)記為total_count潦匈;
- 統(tǒng)計每個類別的文檔總數(shù)分別為sexy_total_count阱高、gamble_total_count、fake_total_count茬缩、normal_total_count赤惊;
- 計算每個詞在各類別文檔中出現(xiàn)的總次數(shù)分別記為sexy_count、gamble_count凰锡、fake_count未舟、normal_count;
- 每個詞的文檔頻數(shù)doc_count = sexy_count + gamble_count + fake_count + normal_count掂为;
- 根據(jù)下圖中對各類型ABCD值的定義裕膀,求出sexyA、sexyB勇哗、sexyC昼扛、sexyD、gambleA欲诺、gambleB抄谐、gambleC、gambleD扰法、fakeA蛹含、fakeB、fakeC迹恐、fakeD、normalA卧斟、normalB殴边、normalC、normalD珍语;
--計算特征詞在各類型中的ABCD值
select *,
sexy_count as sexyA,
doc_count - sexy_count as sexyB,
sexy_total_count - sexy_count as sexyC,
total_count - doc_count - sexy_total_count + sexy_count as sexyD,
gamble_count as gambleA,
doc_count - gamble_count as gambleB,
gamble_total_count - gamble_count as gambleC,
total_count - doc_count - gamble_total_count + gamble_count as gambleD,
fake_count as fakeA,
doc_count - fake_count as fakeB,
fake_total_count - fake_count as fakeC,
total_count - doc_count - fake_total_count + fake_count as fakeD,
normal_count as normalA,
doc_count - normal_count as normalB,
normal_total_count - normal_count as normalC,
total_count - doc_count - normal_total_count + normal_count as normalD
from ${t1}
- 根據(jù)卡方統(tǒng)計計算出各類型CHI值锤岸,再用下式計算詞t對于整個樣本的CHI值;
--計算特征詞在各類型中的卡方統(tǒng)計
select *,
total_count * (sexyA * sexyD - sexyC * sexyB) * (sexyA * sexyD - sexyC * sexyB) /
((sexyA + sexyC) * (sexyB + sexyD) * (sexyA + sexyB) * (sexyC + sexyD))
as sexy_x2,
total_count * (gambleA * gambleD - gambleC * gambleB) * (gambleA * gambleD - gambleC * gambleB) /
((gambleA + gambleC) * (gambleB + gambleD) * (gambleA + gambleB) * (gambleC + gambleD))
as gamble_x2,
total_count * (fakeA * fakeD - fakeC * fakeB) * (fakeA * fakeD - fakeC * fakeB) /
((fakeA + fakeC) * (fakeB + fakeD) * (fakeA + fakeB) * (fakeC + fakeD))
as fake_x2,
total_count * (normalA * normalD - normalC * normalB) * (normalA * normalD - normalC * normalB) /
((normalA + normalC) * (normalB + normalD) * (normalA + normalB) * (normalC + normalD))
as normal_x2
from ${t1};
select *, GREATEST(sexy_x2, gamble_x2, fake_x2, normal_x2) as x2 from ${t1};
- 代入信息增益公式計算出word_ig值板乙;
p(csexy) = sexy_total_count / total_count
p(t) = doc_count / total_count
p(csexy| t) = sexy_count / doc_count
p(t━) = 1 - doc_count / total_count
p(csexy| t━) = (sexy_total_count - sexy_count) / (total_count- doc_count)
select *,
- ((sexy_total_count / total_count * log(2, sexy_total_count / total_count )
+ gamble_total_count / total_count * log(2, gamble_total_count / total_count )
+ fake_total_count / total_count * log(2, fake_total_count / total_count )
+ normal_total_count / total_count * log(2, normal_total_count / total_count ) )
+ doc_count / total_count *
(sexy_count / doc_count * log(2, sexy_count / doc_count + 0.01) +
gamble_count / doc_count * log(2, gamble_count / doc_count + 0.01) +
fake_count / doc_count * log(2, fake_count / doc_count + 0.01) +
normal_count / doc_count * log(2, normal_count / doc_count + 0.01))
+ (1 - doc_count / total_count ) *
((sexy_total_count- sexy_count) / (total_count- doc_count) *
log(2, (sexy_total_count - sexy_count) / (total_count- doc_count) + 0.01) +
(gamble_total_count - gamble_count) / (total_count - doc_count) *
log(2, (gamble_total_count - gamble_count) / (total_count- doc_count) + 0.01) +
(fake_total_count - fake_count) / (total_count - doc_count) *
log(2, (fake_total_count - fake_count) / (total_count - doc_count) + 0.01) +
(normal_total_count- normal_count) / (total_count - doc_count) *
log(2, (normal_total_count - normal_count) / (total_count - doc_count) + 0.01))
as word_ig from ${t1}
- 結(jié)合doc_count是偷,信息增益,卡方統(tǒng)計選擇特征詞(設(shè)置各自的閾值)募逞。
特征計算
特征計算常用tfidf蛋铆,它的形式為:
其中tf(ti, dj)是詞在當(dāng)前文本中的局部因子,idf(ti)是只與ti相關(guān)的全局因子放接,在論文[2]中認(rèn)為idf不能充分反映詞的重要程度刺啦,總結(jié)了一些改進(jìn)的方法:
我們實驗了幾種在論文結(jié)論中表現(xiàn)較好的幾種方法:TFIDF、Probability based纠脾、tf * info gain玛瘸,感到驚訝的是傳統(tǒng)的TFIDF的特征計算方法表現(xiàn)最好(當(dāng)然實驗次數(shù)有限蜕青,不能充分證明)。tfidf的計算方法如下:
-- total_count 為總文檔數(shù)
-- doc_count 為特征詞在文檔中出現(xiàn)總次數(shù)
-- count 為在文檔j中特征詞i出現(xiàn)的頻數(shù)
select *, sum(tfidf_square) over ( partition by id) as tfidf_square_sum from
(select *, count * log(2, total_count / doc_count + 0.01) * count * log(2, total_count / doc_count + 0.01)
as tfidf_square from ${t1} ) c
select *, count * log(2, total_count / doc_count + 0.01) / sqrt(tfidf_square_sum) as tfidf from ${t1}
模型訓(xùn)練及預(yù)測
在訓(xùn)練樣本中糊渊,各類型的比值大約為50:5:3:3右核,需要考慮類別不平衡對模型訓(xùn)練的影響[4]。常見的解決方法有隨機(jī)采樣渺绒、SMOTE算法贺喝、代價矩陣等。我們采用的是隨機(jī)采樣算法芒篷,將各類型的比值降采樣到10:5:3:3時分類效果最好搜变。
總結(jié)
比賽之前對我們對特征工程、類別不平衡問題针炉、特征穿越這些基本問題一無所知挠他,都是在做題的過程中遇到了問題再去查找解決方法,加上比賽經(jīng)驗不足篡帕,過程上可謂是磕磕絆絆殖侵。給自己總結(jié)幾個經(jīng)驗和教訓(xùn):
- 經(jīng)典的算法適合入手,學(xué)習(xí)成本低镰烧,通過完善往往能夠得到不錯的結(jié)果拢军;
- 比賽的前期要不斷的嘗試不同的思路,關(guān)注賽題官方論壇怔鳖、查找相關(guān)文獻(xiàn)茉唉、和隊友交流思路、動手實驗要同步進(jìn)行结执;
- 比賽后期要考慮試錯的成本度陆,特別是最后關(guān)鍵時刻還是要采用最穩(wěn)妥的方法提高成績;
- 選擇好的隊友很關(guān)鍵献幔,要找和自己有同樣的激情和目標(biāo)的人懂傀,在這里感謝我的隊友們,一起努力走到最后蜡感。
比賽中和隊友一起進(jìn)步了很多蹬蚁,對學(xué)習(xí)機(jī)器學(xué)習(xí)的熱情也增加了不少,感謝阿里天池給我們提供了這么好的學(xué)習(xí)平臺郑兴。
參考文獻(xiàn)
- Zheng Z, Wu X, Srihari R. Feature selection for text categorization on imbalanced data[J]. Acm Sigkdd Explorations Newsletter, 2004, 6(1):80-89.
- Liu Y, Han T L, Sun A. Imbalanced text classification: A term weighting approach[J]. Expert Systems with Applications, 2009, 36(1):690-701.
- Shen D, Chen Z, Yang Q, et al. Web-page classification through summarization[C]// International ACM SIGIR Conference on Research and Development in Information Retrieval. ACM, 2004:242-249.
- He H, Garcia E A. Learning from Imbalanced Data[J]. IEEE Transactions on Knowledge & Data Engineering, 2009, 21(9):1263-1284.
- Golub K, Ard? A. Importance of HTML structural elements and metadata in automated subject classification[C]// European Conference on Research and Advanced Technology for Digital Libraries. Springer-Verlag, 2005:368-378.
- 王小青. 中文文本分類特征選擇方法研究[D]. 西南大學(xué), 2010.
- 段軍峰, 黃維通, 陸玉昌. 中文網(wǎng)頁分類研究與系統(tǒng)實現(xiàn)[J]. 計算機(jī)科學(xué), 2007, 34(6):210-213