本文利用之前專案寫好的PTT爬蟲程式來抓取板上的資料,並將資料存進SQLite資料庫,再從中撈取資料作為分析,主要目的是想用機器學習的演算法來透過文章標題來預測這篇文章是出自於哪一板。
PTT爬蟲
先前因為某專案需求,有寫了一個可以自訂板名、截止日期的PTT爬蟲程式,並將它模組化。這裡就直接呼叫程式來做爬蟲,這篇文章爬取的板名有 HatePolitics(政黑板)
、Beauty(表特板)
、Soft_Job(軟體板)
、NBA
、Stock(股票板)
、Tech_Job(科技板)
等六個板。
因為不想花太多時間在等爬蟲,所以截止日期設定成2020/12/1,大約一星期的資料應該就足夠。廢話不多說,直接執行爬蟲:
程式會把爬下來的資料依照 文章ID、推噓數量、作者、文章標題、發文日期、內文、留言推噓、留言內容、留言者ID、爬取時間存入sqlite資料庫。
此外,為了不想一直待在電腦旁邊無法抽身,我在程式裡面額外串接line-notify的通知服務,當每個板爬蟲完畢後就透過line的訊息通知我,並記錄每個板的總爬取時間。爬完六個板之後,大約花了20分鐘。
到這裡,PTT爬蟲的資料就完成了。
接著,就是希望能夠簡單的應用機器學習來透過文章標題去預測該文章是出自於哪一板。
機器學習預測文章出處
首先,引入需要的套件
1 | # sqlite |
接著,建立讀取資料庫相關的函式
1 | # 定義資料庫位置 |
正式從sqlite資料庫撈取資料
1 | # 建立sqlite的engine |
要進行分類之前,需要先初步觀察一下資料的分布
1 | # 觀察資料分布 |
從圖裡面可以看出 Soft_Job
的數量相比其他板來的少,依照機器學習的邏輯來說,可以透過SMOTE的方式去過採樣資料的數量,但因為Soft_Job
的文章數量實在太少,若是硬要合成資料,會造成機器學習錯誤的機率更大,所以這邊就直接把Soft_Job
剔除,以剩下的五個板作為分類。
1 | # 去掉 Soft_Job |
用0.8的比例去拆分訓練跟測試的資料集
1 | TRAIN_TEST_RATIO = 0.8 |
因為是採用文章標題去預測板名,所以先透過jieba
套件來將文章標題做斷詞。
雖然在這裡文章標題都只有一句話,斷詞後可能幫助不大,但如果今天換成是用內文去預測板名,斷詞這步驟就顯得相當重要了。
1 | def jieba_tokenizer(text): |
用字典把板名轉換成index去代表,ex: Beauty:1, HatePolitics:3, NBA:2, Stock:0, Tech_Job:4
,當預測完之後,會得到一串各類別的機率,就可以透過另外一組字典反向查index回來看看板名是什麼。
1 | # 類別轉化為數字類別(1-N類) |
kind_mapping: {‘Tech_Job’: 0, ‘Stock’: 1, ‘HatePolitics’: 2, ‘Beauty’: 3, ‘NBA’: 4}
inversed_kind_mapping: {0: ‘Tech_Job’, 1: ‘Stock’, 2: ‘HatePolitics’, 3: ‘Beauty’, 4: ‘NBA’}
將文章標題的所有出現過的詞作成一個大字典,並透過這個字典把所有詞彙都轉成向量。
1 | # 準備排序的文字list(keywordindex) |
Shape of X_train: (570, 1370)
Shape of Y_train: (570,)
訓練、驗證資料的切分
1 | TRAIN_VALID_RATIO = 0.2 |
Shape of X_train: (456, 1370)
Shape of X_Valid: (114, 1370)
Shape of Y_train: (456,)
Shape of Y_Valid: (114,)
建立計算預測後的準確率的函式
1 | # 定義函式,input分類器,output準確率 |
有了函式之後,就可以分別得到四個分類器(RandomForest、SVC、KNN、XGboost)的準確度了
1 | print('RandomForest: ', get_accuracy(RandomForestClassifier)) |
RandomForest: 0.8596491228070176
SVC(linear): 0.868421052631579
KNN: 0.7192982456140351
XGboost 0.8157894736842105
可以看到結果是 SVC(linear)
準確度愈高,次之是RandomForest
,第三名則是XGboost
。最後選擇用第三名的XGboost
來做最終預測的分類器。(因為XGboost一直是Kaggle上的霸主)
1 | clf = XGBClassifier() |
定義好分類器之後,接著要有預測的函式才能將測試資料做預測
1 | # 定義預測函式 |
正式將測試資料拿來做預測
1 | df_test = df_test_origin |
Accuracy: 0.823943661971831
預測完之後,準確率大概82%左右,跟驗證資料集的分數差不多。
接著,快速地用classification_report
來看一下precision
、recall
、f1-score
、accuracy
等指標
1 | print(classification_report(df_test['boardName'], df_test['ML_Classify'])) |
指標結果如下,Beauty(表特板)
的f1-score最高,可見precision
跟recall
都有不錯的表現,代表這個板分類不錯。
1 | precision recall f1-score support |
接著,透過confusion matrix(混淆矩陣)
來判斷一下分類結果的好壞。
1 | # 計算混淆矩陣 |
透過熱圖跟classification_report,可以看到Beauty(表特板)
的f1-score是最高,因為他的排他性強烈,比較不會有分錯的問題。
此外,Stock(股票板)
跟Tech_Job(科技板)
彼此之間會互相誤判,可能原因是這兩種分類的特徵是互相耦合的。
再者,因為Stock(股票板)
的資料量較多,模型會學到特別多關於Stock(股票板)
的資訊,導致容易把其他類別誤判成是Stock(股票板)
。
…
最後的最後,就是回頭看一下資料裏頭到底是那些文章被分錯
1 | df_test[df_test['boardName']!=df_test['ML_Classify']] |
其中有一筆資料長這樣:
Title | boardName | ML_Classify | ML_Classify_prob |
---|---|---|---|
[新聞] 生涯總薪資達4.35億美元 詹皇躍居史上第一 | NBA | Stock | [[ 0.74948156 83.570015 0.8006586 0.31720236 14.562643 ]] |
可能是因為談到薪資和美元的關係才把NBA
誤分成Stock
。仔細看一下分類後的各類別機率,雖然第一名分錯但NBA
是排名在分類的第二名,所以整體來說分類表現還算差強人意。
後記
資料前處理的時候,有試過把
[新聞]
、[請益]
…等等字樣透過正則表達式去剔除,想說這樣應該比較不會讓模型學壞,結果…1
2regex = r'[[\w]*]'
df['Title'].replace(to_replace=regex, value='', regex=True, inplace=True)事實證明,準確率掉到79.5%…
可見這些資料對於模型訓練其實是有幫助的。
XGboost有結合cross-validation的套件可以使用,理論上表現會更好,但因為這裡資料量太少,所以做cross-validation的效益看不出來,對於準確率來說無法有效提升。
XGboost + CV(Cross-Validation)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20params = {}
params['objective'] = 'multi:softprob' # 多分類
params['num_class'] = len(df_test_origin['boardName'].unique())
d_train = xgboost.DMatrix(x_train, label=y_train)
d_valid = xgboost.DMatrix(x_valid, label=y_valid)
watchlist = [(d_train, 'train'), (d_valid, 'valid')]
bst = xgboost.train(params, d_train, 100, watchlist, early_stopping_rounds=100, verbose_eval=10)
y_pred = bst.predict(xgboost.DMatrix(x_valid))
# 手動計算準確率
cnt = 0
for i in range(len(y_valid)):
if y_valid.iloc[i] == kind_mapping[find_maxProb(y_pred[i])]:
cnt+=1
acc = cnt/len(y_valid)
print('Accuracy: ',acc)[0] train-merror:0.26316 valid-merror:0.29825
Multiple eval metrics have been passed: ‘valid-merror’ will be used for early stopping.Will train until valid-merror hasn’t improved in 100 rounds.
[10] train-merror:0.11842 valid-merror:0.24561
[20] train-merror:0.05482 valid-merror:0.21930
[30] train-merror:0.03070 valid-merror:0.21930
[40] train-merror:0.01974 valid-merror:0.21053
[50] train-merror:0.01097 valid-merror:0.19298
[60] train-merror:0.01097 valid-merror:0.20175
[70] train-merror:0.00877 valid-merror:0.20175
[80] train-merror:0.00877 valid-merror:0.20175
[90] train-merror:0.00877 valid-merror:0.21053
[99] train-merror:0.00658 valid-merror:0.21053Accuracy: 0.7894736842105263
總結
這篇文章主要透過爬蟲去爬取PTT板上的資訊,最後存入sqlite資料庫,再從sqlite資料庫裏頭撈取資料來做機器學習的分類,並比較常見幾種演算法如:KNN、RandomForest、XGboost、SVM的結果,最終整體分類結果尚可接受。
當然除了機器學習以外,也可以用深度學習的模型來做分類,ex: LSTM、BERT…等等模型,但就需要調參,而且要自己設計Network Structure,相對麻煩一點,但能夠客製化分類的模型,也算是一種可以嘗試的方向。
TODO:
後續會再寫個小網站把sqlite資料庫爬取下來的文章資訊透過網頁的方式去查詢,並嘗試做成文字雲或是將分類的模型上傳到網站以便隨時透過UI去測試分類,並寫成部落格文章以供未來的自己查閱。
TO BE CONTINUE…