WEKA使用(基礎配置+垃圾郵件過濾+聚類分析+關聯挖掘)


聲明:

1)本文由我bitpeach原創撰寫,轉載時請注明出處,侵權必究。

2)本小實驗工作環境為Windows系統下的WEKA,實驗內容主要有三部分,第一是分類挖掘(垃圾郵件過濾),第二是聚類分析,第三是關聯挖掘。

  3)本文由於過長,且實驗報告內的評估觀點有時不一定正確,希望拋磚引玉。

(一)WEKA在Ubuntu下的配置

下載解壓

  • 下載和解壓weka

    。下載:

    創建目錄:sudo mkdir /usr/weka。

    解壓weka到該目錄:unzip weka-3-6-10.zip -d /usr/weka。

配置Ubuntu的環境變量

  • 配置環境變量。還是使用vim 在/etc/environment 中配置。(新增WEKAROOT,修改CLASSPATH)

    export WEKAROOT=/usr/weka/weka-3-6-10

    export CLASSPATH=.:$JAVA_HOME/lib:$JAVA_HOME/jre/lib:$WEKAROOT/weka.jar

    最后別忘了 source /etc/environment,加載新的配置文件。

    當然了WEKAROOT換成別的名字也可以的,如WEKA_PATH等,切記如果想換名字的話,首先用echo命令查看環境變量參數,指令如下:

    echo $WEKAROOT

    然后使用unset命令刪除掉,指令為unset WEKAROOT

    最后新建一個WEKA_PATH環境,新建方法仍然是往environment文件里寫入並更新。

運行WEKA

  • 運行WEKA,運行命令:cd $WEKAROOT(或者cd $WEKA_PATH

    運行命令:java -Xmx1000M -jar weka.jar(或者java -jar weka.jar

    Java虛擬機進程能構從操作系統那里挖到的最大的內存,以字節為單位,如果在運行java程序的時候,沒有添加-Xmx參數,那么就是64兆,這是java虛擬機默認情況下能從操作系統那里挖到的最大的內存。如果添加了-Xmx參數,將以這個參數后面的值為准,例如本指令下最大內存就是1000兆。

快速啟動WEKA

  • 更加快捷的運行weka,輸入以下命令:
    sudo touch /usr/bin/weka
    sudo chmod 775 /usr/bin/weka
    sudo vi /usr/bin/weka


    輸入以下代碼:
    #!/bin/bash
    cd $WEKAROOT
cd $WEKA_PATH
    java -Xmx1000M -jar weka.jar

    保存退出vim
    以后要運行weka,只要在命令行里直接鍵入命令 weka 就可以了。這里附上vim編輯的一些經驗:

    1)vi 的進入

    在 shell 模式下,鍵入vi 及需要編輯的文件名,即可進入vi。

    例如:

        vi example.txt

    即可編輯 example.txt 文件。如果該文件存在,則編輯界面中會顯示該文件的內容,並將光標定位在文件的第一行;如果文件不存在,則編輯界面中無任何內容。如果需要在進入vi 編輯界面后,將光標置於文件的第n 行,則在vi命令后面加上+n 參數即可。例如需要從example.txt 文件的第5 行開始顯示,則使用如下命令:

        vi +5 example.txt

    2)退出 vi

    退出 vi 時,需要在末行模式中輸入退出命令q。

    如果在文本輸入模式下,首先按ESC 鍵進入命令模式,然后輸入": "進入末行模式。在末行模式下,可使用如下退出命令:

    :q             直接退出如果在文本輸入模式下修改了文檔內容則不能退出

    :wq             保存后退出

    :x              wq

    :q!             不保存內容強制退出

 

(二)WEKA在Windows下的配置

安裝WEKA的EXE程序包

  • 下載weka在Windows下的可執行包

    官方網站提供兩類Windows下的可執行包,一類是OS直接運行的,另一類是在JVM里運行的,我下載的是基於JVM運行的類型的可執行包。

    安裝過程沒有什么可說的,Windows下的安裝過程非常簡單易懂。

運行WEKA

  • 運行Weka

    運行Weka有兩類方式,一類是通過OS的操作方式運行,如雙擊運行或調用exe等等。還有一類是通過Java命令運行。

    OS操作下運行Weka感覺上是沒有什么問題,注意是"感覺上"沒有問題!事實上通過OS運行,由於什么錯誤也沒有,可能發覺不到Weka的一些Warning。直到你需要某項Weka讀取數據庫的功能操作時,才發現怎么也運行不通,而卻不知道為什么。

    在調用Java命令運行的過程中就發現,幾個關於數據庫讀取的Warning!處理需要一定時間。相關Warning如下:

>java –Xmx1024M –jar weka.jar

    Trying to add database driver (JDBC): RmiJdbc.RJDriver - Error, not in CLASSPATH?

    Trying to add database driver (JDBC): jdbc.idbDriver - Error, not in CLASSPATH?

    Trying to add database driver (JDBC): org.gjt.mm.mysql.Driver - Error, not in CLASSPATH?

    Trying to add database driver (JDBC): com.mckoi.JDBCDriver - Error, not in CLASSPATH?

    Trying to add database driver (JDBC): org.hsqldb.jdbcDriver - Error, not in CLASSPATH?

WEKA啟動的驅動報錯

  • 彌補數據庫連接的問題方法(Ubuntu和Windows皆出現)

    查詢網絡論壇,說linux和windows均會出現此類問題,我在Ubunntu和Win8都配置過了,確實都會出現這些Warning。

    查詢網絡論壇的一些大神的實驗記錄與心得,兩大OS的解決辦法基本相同,先說說Ubuntu

    1)Ubuntu解決方法

    第一步、下載相關數據驅動包,大約有5個jar包,注意Weka3-6對應的這5個jar包版本各自不同,一定要找對版本的這5個jar包。例如:對於Weka(version 3.5.5) 對於RmiJdbc,一定選擇版本3.05或2.5。

    名字分別為:hsqldb.jaridb.jarmkjdbc.jarmysql-connector-java-5.1.6-bin.jarRmiJdbc.jar

    我的Weka版本為3-6-11,使用3-6-8版本的此5個jar包,沒有問題。

    

    第二步、解壓后把這5個jar包拷貝到/usr/lib/jvm/jdk1.7.0_25 /jre/lib/ext目錄下,你的路徑可能和我的不一樣,記着是你的jdk安裝路徑就行了。(吐個槽,在這一點上,Ubuntu比Windows好使多了,復制一下這幾個jar包就行了,就不會報錯了。可是Windows你懂的,修改環境變量起來好累。)

 

    第三步、修改weka數據類型和數據庫數據類型的映射。

    (對於這步驟操作,Weka官方wiki提供相關文檔操作方法http://weka.wikispaces.com/Properties+file)

    在weka.jar里面有一個文件/weka/experiment/DatabaseUtils.props,記錄了數據庫操作的相關參數。還有很多文件DatabaseUtils.props.msaccess,DatabaseUtils.props.mssqlserver等,分別對應了各個數據庫的操作參數,

    如果你使用msaccess,可以把DatabaseUtils.props.msaccess的內容覆蓋DatabaseUtils.props。

    如果不對DatabaseUtils.props修改,可能在連接數據庫時一切順利,但在將數據裝入准備預處理時,卻出現找不到數據類型(can not read from database,unknown data type)之類錯誤。

    沒關系,在DatabaseUtils.props加入類型映射就OK了。文件中一般有下面的內容(這里是我用mysql對應的文件覆蓋了):

# JDBC driver (comma-separated list)

jdbcDriver=com.mysql.jdbc.Driver

# database URL

jdbcURL=jdbc:mysql://server_name:3306/database_name

# specific data types

string, getString() = 0; --> nominal

boolean, getBoolean() = 1; --> nominal

double, getDouble() = 2; --> numeric

byte, getByte() = 3; --> numeric

short, getByte()= 4; --> numeric

int, getInteger() = 5; --> numeric

long, getLong() = 6; --> numeric

float, getFloat() = 7; --> numeric

date, getDate() = 8; --> date

text, getString() = 9; --> string

time, getTime() = 10; --> date

BigDecimal,getBigDecimal()=11; -->nominal

#mysql-conversion

CHAR=0

TEXT=0

    #mysql-conversion下提供的類型一般是不夠的,比如int unsigned就找不到,

所以要加入int是如何映射到weka類型的。

    在# specific data types下找到Int對應的java類型,這里是

int, getInteger() = 5; --> numeric

    所以在#mysql-conversion下新增INT=5

    再加上UNSIGNED類型,INT_UNSIGNED=6(因為unsigned比signed多一倍的數,為防止截斷,要取大的類型)

    其他類型的映射依次類推。

    注意INT和UNSIGNED之間的下划線,缺了的話錯誤解決不了,我就在這里搞了好久。(Note: in case database types have blanks, one needs to replace those blanks with an underscore, e.g., DOUBLE PRECISION must be listed like this:DOUBLE_PRECISION=2. Notes from http://weka.wikispaces.com/weka_experiment_DatabaseUtils.props#toc4)

 

    第四步,最后最重要的是,把DatabaseUtils.props放到home目錄下,重啟Weka后生效。並請注意:不同的數據庫,書寫方法與格式不同,上面是MySql類型,還有SQL Server2000或Oracle等等,格式是不同的,可百度參考相關格式或論壇博客。

 

    2)Windows解決方法

    對於找不到jdbc驅動的問題,主要是修改RunWeka.ini,因為屢次修改classpath都不成功。干脆將jar文件的地址加到weka指定的運行時classpath中。RunWeka.ini的位置在Weka總目錄下,與Weka.jar是平行目錄。打開后內容底部有如下內容:

# The classpath placeholder. Add any environment variables or jars to it that

# you need for your Weka environment.

# Example with an enviroment variable (e.g., THIRD_PARTY_LIBS):

# cp=%CLASSPATH%;%THIRD_PARTY_LIBS%

# Example with an extra jar (located at D:\libraries\libsvm.jar):

# cp=%CLASSPATH%;D:\\\\libraries\\\\libsvm.jar

# Or in order to avoid quadrupled backslashes, you can also use slashes "/":

# cp=%CLASSPATH%;D:/libraries/libsvm.jar

cp=%CLASSPATH%

    根據提示,將jdbc驅動jar文件地址附加到cp變量。

    比如mysql jdbc驅動(mysql-connector.jar)在E:/jars/下,則加入下面這句:

    cp=%CLASSPATH%;E:/jars/mysql-connector.jar

    同時把cp=%CLASSPATH%注釋掉,

    結果像

    cp=%CLASSPATH%;E:/jars/mysql-connector.jar

    #cp=%CLASSPATH%

    也就是說如果有5個jar包,需要在一行里一個一個寫進,而不能用寫一個5個jar包所在的總目錄來省事。(網上有人說把那5個Jar包解壓到weka路徑下,然后修改CLASSPATH也能解決,我試過很多次,都不可行。)

    至於數據類型映射,參見上面Linux環境下的配置。

    最后將DatabaseUtils.props文件放在跟Weka.jar同一目錄下即可。

 

    3)Windows下若上述方法失敗,仍然報JDBC錯誤。

    解決方法有以下幾種:

    一、設置系統里環境變量,因為你在RunWeka.ini內設置了classpath,結果仍然不識別,我想應該為系統里的classpath添加路徑。

    二、使用java命令行,利用臨時權限添加classpath的方法

    上面兩方法,第一種我試了很多遍沒有成功,第二種java命令會有問題,但最終成功。

    第二種方法方法細節:

    一、使用如下命令格式,但是中途出現一些問題

    java -classpath E:/Weka-3-6/jars/RmiJdbc.jar -jar weka.jar

    依然報5個錯誤,如下圖。

    查詢相關知識,java的此classpath命令會被-jar屏蔽掉,意思就是說如果java命令里有-jar參數,則會自動屏蔽-classpath添加參數

    二、我們嘗試修改指令

    java -Xbootclasspath/a:/E:/Weka-3-6/jars/Rmijdbc.jar: -jar Weka.jar

    運行后發現少了一個錯誤,我們添加的是Rmijdbc驅動包,減少的錯誤恰好是RJDriverError,說明我們的目前指令成功,如果需要添加多個jar包,則在路徑后面使用分號,逐步添加五個jar包。不要企圖使用目錄,使用目錄仍然不識別。所以添加五個jar包的java指令確實很長。唯一欣慰的是,指令最后成功了,沒有錯誤。不過請注意一點:由上述的分析我們知道它只是不報驅動錯誤了,以后我們使用數據庫,它無法讀取識別數據庫的類型又是另外一碼事。(下面有兩張圖,第一個圖第一個指令是展示企圖使用目錄添加classpath是不正確的,第一個圖第二個指令表明添加具體某一個jar包,成功減少了錯誤。第二個圖第二個指令,一口氣寫了五個jar包,最后是成功的零錯誤,正確通過!。)

(三)WEKA使用的准備工作

WEKA數據格式中的數據屬性

  • 數據格式中的數據屬性

    1)二元屬性(定性的)

    二元屬性的英文為"binary attribute",比較好理解。可以認為它是一種特殊的標稱屬性,只有兩個類別或狀態:0或1,其中0通常表示該屬性不出現,而1表示出現。二元屬性又稱布爾屬性,如果兩種狀態對應於true和false的話。

    2)標稱屬性(定性的)

    標稱屬性的英文為"nominal attribute",有時候感到不好理解。標稱屬性值既不是具有有意義的順序,也不是定量的。每個值代表某種類別、編碼或狀態,因此標稱屬性又被看做是分類的(categorical),所以有時候看到顧客的ID或者鞋碼尺寸SIZE等,總會誤認為是序號屬性。所以標稱屬性要抓住核心概念:標稱只是一種分類,即標稱數值的數學運算毫無意義。顧客ID有時候會是數值屬性,但注意應用的時候,如果顧客ID之間的數值加減乘除沒有任何意義,則我們不認為它是數值屬性,認為其為標稱屬性更為恰當。以年齡為例,年齡之間常常涉及到加減,所以年齡總認為是數值屬性,鞋碼大小不太涉及到加減意義,故認為標稱的。

    3)數值屬性(定量的)

    數值屬性的英文為"numericattribute",它是定量的,它是可度量的量。抓住這個概念,也就是說它的數值是有意義的,可以參與加減乘除運算的,其值進行數學運算的結果常常是有意義的。

    4)序數屬性(定性的)

    序數屬性的英文單詞為"ordinal attribute",也是不太好理解的一個屬性。但是切記序數屬性可以通過數值屬性離散化得到,並且序數屬性往往攜帶一種重要意義,就是具有有意義的先后次序。以快餐店售賣可樂大小杯為例,0表示小杯,1表示中杯,2表示大杯。它們既可以認為是序數屬性,也可以認為是間接的標稱屬性。或以顧客滿意度調查為例,0是很不滿意,1是不太滿意,2是一般,3是滿意,4是非常滿意。

WEKA數據集格式屬性

  • 數據集格式問題

    WEKA可識別ARFF格式,也可識別CSV電子數據表格式。雖然我們知道WEKA-Explorer對於兩者格式都能夠讀取,但對於CSV格式與ARFF格式比較感興趣。

    1)CSV和ARFF格式

    這里寫出CSV的格式,以weather.csv為例,其內容如下表所示。至於其arff格式的規范,我們通過看后續部分,就發現兩者實際上通過手動改寫。ARFF格式實際上就是統計了CSV格式的屬性分類,並加強規范。

outlook,temperature,humidity,windy,play

sunny,85,85,FALSE,no

sunny,80,90,TRUE,no

overcast,83,86,FALSE,yes

rainy,70,96,FALSE,yes

rainy,68,80,FALSE,yes

rainy,65,70,TRUE,no

overcast,64,65,TRUE,yes

sunny,72,95,FALSE,no

sunny,69,70,FALSE,yes

rainy,75,80,FALSE,yes

sunny,75,70,TRUE,yes

overcast,72,90,TRUE,yes

overcast,81,75,FALSE,yes

rainy,71,91,TRUE,no

    2)兩者轉化

    一、我們知道WEKA讀取時會自動轉化

    二、如果需要人為干預,需要處理兩個格式的內容,那么這就是比較有意思的事情了

    ARFF文件是Weka默認的儲存數據集文件。每個ARFF文件對應一個二維表格。表格的各行是數據集的各實例,各列是數據集的各個屬性。

    下面是Weka自帶的"weather.arff"文件,在Weka安裝目錄的"data"子目錄下可以找到。 需要注意的是,在Windows記事本打開這個文件時,可能會因為回車符定義不一致而導致分行不正常。推薦使用UltraEdit這樣的字符編輯軟件察看ARFF文件的內容。

%ARFF file for the weather data with some numric features

%

@relation weather

@attribute outlook {sunny, overcast, rainy}

@attribute temperature real

@attribute humidity real @attribute windy {TRUE, FALSE} @attribute play {yes, no}

@data

%

% 14 instances

%

sunny,85,85,FALSE,no

sunny,80,90,TRUE,no

overcast,83,86,FALSE,yes

rainy,70,96,FALSE,yes

rainy,68,80,FALSE,yes

rainy,65,70,TRUE,no

overcast,64,65,TRUE,yes

sunny,72,95,FALSE,no

sunny,69,70,FALSE,yes

rainy,75,80,FALSE,yes

sunny,75,70,TRUE,yes

overcast,72,90,TRUE,yes

overcast,81,75,FALSE,yes

rainy,71,91,TRUE,no

    識別ARFF文件的重要依據是分行,因此不能在這種文件里隨意的斷行。空行(或全是空格的行)將被忽略。

    以"%"開始的行是注釋,WEKA將忽略這些行。如果你看到的"weather.arff"文件多了或少了些"%"開始的行,是沒有影響的。

    除去注釋后,整個ARFF文件可以分為兩個部分。第一部分給出了頭信息(Head information),包括了對關系的聲明和對屬性的聲明。第二部分給出了數據信息(Data information),即數據集中給出的數據。雖然Weka也支持其他一些格式的文件,但是ARFF格式是支持的最好的。因此有必要在數據處理之前把CSV數據集的格式轉換成ARFF。

    將CSV轉換為ARFF最迅捷的辦法是使用WEKA所帶的命令行工具。運行WEKA的主程序,在菜單中找到"Simple CLI"模塊,它可提供命令行功能。在新窗口的最下方(上方是不能寫字的)的輸入框寫上

java weka.core.converters.CSVLoader filename.csv > filename.arff

    即可完成轉換。

    在WEKA 3.6中提供了一個"Arff Viewer"模塊(在Tools里),我們可以用它打開一個CSV文件將進行瀏覽,然后另存為ARFF文件。 進入"Exploer"模塊,從上方的按鈕中打開CSV文件然后另存為ARFF文件亦可。

    Excel的XLS文件可以讓多個二維表格放到不同的工作表(Sheet)中,我們只能把每個工作表存成不同的CSV文件。打開一個XLS文件並切換到需要轉換的工作表,另存為CSV類型,點"確定"、"是"忽略提示即可完成操作。接下來把得到的CSV文件按照前述步驟轉換為ARFF即可。

WEKA分類挖掘的評估標准

  • 分類挖掘算法的評估性能標准

    Confusion Matrix是混淆矩陣,提供的評估標准,如下表所示。

Predicted

 

C1

¬ C1

ALL

Actual

C1

True Positives (TP)

False Negatives (FN)

P

¬ C1

False Positives (FP)

True Negatives (TN)

N

1)TP/FN/FP/TN

    對於C1事件來說,可將其看成事件中的正面事件,或稱為真事件,或稱為陽事件。由於預測結果為C1事件,既然結果為陽事件,正事件。說明實際過程可能由正到正,由負到正,都可成為真正,假正,說明預測都是正的。故TP可稱為判正得正或真陽率或真正率,FP則為判負為正或假陽率或假正率。同理FN可推理成為判正得負或真陰率或真反率,TN順理為判負得負或真陰率或假反率。

2)各評估率

    准確率(識別率)

    召回率(靈敏度)

    精度

     3)ROC曲線

    反映的是分類器的真正例率(TPR,靈敏度)和假正例率(FPR)之間的權衡,簡而言之就說觀察"真判正"占據"判真"的比率。如下圖所示。

WEKA軟件Explorer之Classifier

  • Classifier菜單面板區域模塊介紹

1)Classifier

    點擊choose按鈕,可以選擇weka提供的分類器。常用的分類器有:

    a)bayes下的Naïve Bayes(朴素貝葉斯)和BayesNet(貝葉斯信念網絡)。

    b)functions下的LibLinear、LibSVM(這兩個需要安裝擴展包)、Logistic Regression、Linear Regression。

    c)lazy下的IB1(1-NN)和IBK(KNN)。

    d)meta下的很多boosting和bagging分類器,比如AdaBoostM1。

    e)trees下的J48(weka版的C4.5)、RandomForest。

    2)Test options

評價模型效果的方法,有四個選項。

a)Use training set:使用訓練集,即訓練集和測試集使用同一份數據,一般不使用這種方法。

b)Supplied test set:設置測試集,可以使用本地文件或者url,測試文件的格式需要跟訓練文件格式一致。

c)Cross-validation:交叉驗證,很常見的驗證方法。N-folds cross-validation是指,將訓練集分為N份,使用N-1份做訓練,使用1份做測試,如此循環N次,最后整體計算結果。

    d)Percentage split:按照一定比例,將訓練集分為兩份,一份做訓練,一份做測試。在這些驗證方法的下面,有一個More options選項,可以設置一些模型輸出,模型驗證的參數。

    3)Result list

    這個區域保存分類實驗的歷史,右鍵點擊記錄,可以看到很多選項。常用的有保存或加載模型以及可視化的一些選項。

    4)Classifier output

    分類器的輸出結果,默認的輸出選項有Run information,該項給出了特征、樣本及模型驗證的一些概要信息;Classifier model,給出的是模型的一些參數,不同的分類器給出的信息不同。最下面是模型驗證的結果,給出了 一些常用的一些驗證標准的結果,比如准確率(Precision),召回率(Recall),真陽性率(True positive rate),假陽性率(False positive rate),F值(F-Measure),Roc面積(Roc Area)等。混淆矩陣(Confusion Matrix)給出了測試樣本的分類情況,通過它,可以很方便地看出正確分類或錯誤分類的某一類樣本的數量。

一種舊版的WEKA軟件

  • WEKA舊版

1)背景

    為什么要用舊版?

    舊版由於受眾廣,新版雖然新,但API的接口修改沒有那么廣泛。此舊版WEKA增加新版WEKA所沒有的功能,受眾修改的程度也較為廣泛,如增加ID3可視化樹的功能。此版本來自於伍斯特理工學院。軟件下載網址如下:

    http://davis.wpi.edu/~xmdv/weka/

    2)安裝

    下載舊版的WEKA后,安裝包的情況如下圖所示。

    然而直接運行RunWeka是可以成功的,界面如下:

    然而有一個問題,就是點擊該舊版Explorer按鈕后,沒有任何反應或響應。沒有反應的原因,是因為沒有GL4Java,如果想運行Explorer,必須安裝它(舊版網址也強調了該環境)。

    那么在如下網址尋找一個插件GL4Java:

    http://jausoft.com/Files/Java/1.1.X/GL4Java/binpkg/

    GL4Java-Installer里面有主安裝程序,然而主安裝程序需要一些插件輔助安裝

    插件的輔助清單如下圖紅色框內標注所示:

    將輔助的插件下載並保存與舊版WEKA同一目錄的文件夾內,命名為"binpkg",文件夾內的目錄結構如下圖所示,binpkg文件夾內放置都是輔助插件:

然后點擊GL4Java內的install.bat,就可以正常安裝了,安裝界面如下:

    切記選中自己安裝JDK的位置,然后點擊Start即可。

    注意:RunWeka.bat和install.bat的點擊時不要以管理員身份運行,一旦以管理員身份運行,反而控制台出不來,所以直接雙擊就好。

    3)測試

    在打開舊版的WEKA后,點擊Explorer,可以出現Explorer界面。

    參看分類器挖掘時,我們在使用ID3分類時,可視化樹的按鈕是灰色的。而使用舊版WEKA后,可以看到可視化樹的按鈕是可以點擊的。如下圖所示。

(四)WEKA實驗1:分類挖掘

決策樹分類挖掘

  • 實驗目的

    利用weka提供的決策樹算法,對提供的bank-data.csv數據進行決策樹分類挖掘。作業需完成以下內容:

    (1)了解weka提供的數據預處理功能,去除bank-data數據中的無用屬性(如ID)結構,並對income屬性進行離散化處理;

    (2)利用weka提供的多個決策樹歸納算法,構建決策樹模型(最后一個屬性pep作為需要預測的屬性),比較不同算法所構建模型的差別;

    (3)對各種決策胡算法的預測結果進行分析,並比較分類性能。

  • 實驗過程

    1)數據預處理

    a)數據讀取

    讀取bank-data.csv格式數據,如下圖所示。

    其中bank-data數據內容呈現出來,如下圖所示。

b)刪除無用參數

    刪除無用參數ID,如下圖所示。

    另存為ARFF格式,並用UltraEdit打開,觀察數據格式,如下圖所示。

    根據ARFF數據格式,展示該數據的屬性如下。

@attribute age numeric

@attribute sex {FEMALE,MALE}

@attribute region {INNER_CITY,TOWN,RURAL,SUBURBAN}

@attribute income numeric

@attribute married {NO,YES}

@attribute children numeric

@attribute car {NO,YES}

@attribute save_act {NO,YES}

@attribute current_act {NO,YES}

@attribute mortgage {NO,YES}

@attribute pep {YES,NO}

    其中{YES,NO}是二元屬性,{INNER_CITY,TOWN,RURAL,SUBURBAN}是標稱屬性,至於{FEMALE,MALE}是標稱屬性或二元屬性,而numeric認為是數值屬性。

    說句實話,這些屬性初學時基本分不清,還要對照課件才能分清楚,甚至有時對照課件都會分不清楚。所以覺得不能脫離英文環境,必須要結合英文,加強理解。可參見WEKA的數據准備里相關知識。

c)修改部分參數的數據屬性

    我們做一些簡單修改,通過觀察WEKA中某一參數的分布,如children個數,如下圖所示。

    children的取值分布為離散,WEKA軟件顯示children的Type是Numeric。我們觀察直方圖,children取值為0,1,2,3中的一個。我們修改ARFF格式數據,將

    @attribute children numeric

    修改為(請注意區分全角半角的符號)

    @attribute children {0,1,2,3}

    重新使用WEKA讀入,再點擊children參數,發現其Type更改為Nominal。如下圖所示。

    這說明了一點,那就是標稱屬性與數值屬性在應用時可能沒有明顯的界限,只要數據屬性服務於我們的要求就行。

d)離散化income參數

    選中income,然后選擇Filter-unsupervised-attribute-Discretize,然后進行后續操作,如下圖所示。

    修改income的離散化參數,雙擊choose按鈕旁的命令欄,彈出weka.gui.GenericObjectEditor對話框,最上方兩行分別是取值區間,分成幾類。修改的參數如下圖所示。

    點擊OK,再在命令欄右邊的按鈕,為保證對照對比income的分類情況,我們先觀察不應用離散處理之前的情形,如下圖所示。

    點擊Apply,對income應用離散化后的生成數據情形,如下圖所示,顯然將income參數分成了三部分。

    我們發現一個有趣的事實,那就是age參數也被分成了三份,如下圖所示。

    我猜測可能是因為在屬性里面,由於我們剛剛修改了children,僅剩下age和income兩個參數沒有改動,且仍然保持numeric類型,應用離散化時,結果把這兩個都分成了三類。說明有兩個操作無效:一是只要符合離散化條件的,weka都做離散化,不管你選不選中income;二是對已有標稱數值的屬性作離散化處理是無效的。

2)各分類挖掘算法

a)J48分類器

    打開classifier,點擊choose,在分類算法中選擇trees-J48,如下圖所示。

    選擇完J48分類器后,返回到classifier界面,在TestOptions內勾選交叉驗證(Cross-validation),旁邊的默認值10指的是十折交叉驗證。在MoreOptions中勾選Output predictions。參數設置如下圖所示。

    點擊Start,啟動實驗,在右側的classifier ouput內我們可以看到J48分類器的分類實驗結果。如下圖所示。

3)各分類器結果分析

a)J48分類器

  • 先給出一些文字或數字的結果

本分類器的分類驗證結果,如下圖所示。

    通過右鍵點擊實驗記錄時間點,選擇點擊Visualize Tree,可以生成可視化生成樹,點擊操作如下圖所示。

    由下圖可知,這是預測結果的數據,可以看到每個樣本的實際分類,預測分類,預測概率以及是否錯分等信息。

    其中我們將分類結果文字清晰地呈現出來,進行分析。如下表所示。

=== Stratified cross-validation ===

=== Summary ===

Correctly Classified Instances 510 85%

Incorrectly Classified Instances 90 15%

Kappa statistic 0.6983

Mean absolute error 0.2209

Root mean squared error 0.3518

Relative absolute error 44.5145%

Root relative squared error 70.6212%

Total Number of Instances 600

=== Detailed Accuracy By Class ===

TP Rate FP Rate Precision Recall F-Measure ROC Area Class

0.847 0.147 0.829 0.847 0.838 0.862 YES

0.853 0.153 0.869 0.853 0.861 0.862 NO

Weighted Avg. 0.85 0.151 0.85 0.85 0.85 0.862

=== Confusion Matrix ===

a b <-- classified as

232 42 | a = YES

48 278 | b = NO

  • 可視化結果

    生成的可視化樹,由於節點或分類參數較多,部分節點無法看清,但總體脈絡可以如下圖所示。

    Explorer的Visualize面板,可以觀察兩兩特征之間的樣本點分布情形,如下圖所示。

    隨便選一個區域,例如選擇pep和age交叉對應的區域,點擊后會出現新的圖框,以展示pep特征與age特征之間的關系。如下圖所示。

    回歸到Classifier面板下的ResultLists右鍵選擇實驗記錄點,然后選擇點擊Visualize Classify Errors,如下圖所示。

    選擇Visualize Classify Errors此項參數,然后會生成可視化結果,這個結果實際上對應的是可視化的混淆矩陣圖示,也就是可以展示誤判概率點(正判反,反判正),正確判別概率點(正判正,反判反),圖像中十字點表示對判,方形點表示誤判,X軸表示實際類別,Y軸表示預測類別。如下圖所示。把鼠標放到點上,點擊后也能跳出特征點的具體值,方便觀察和研究特征點離群原因或不符合正常結論的原因。

同理我們再選取讀取Visualize threshold curve參數,選擇YES(正類),如下圖所示。

    Visualize threshold curve參數顯示的是分類置信度在不同閾值下,分類效果評價標准的對比情況,其中就包括ROC曲線(縱軸為TP,橫軸為FP),如下圖所示。

b)ID3分類器

  • 先給出一些文字或數字的結果

        下面給出ID3的分類文字結果。

=== Stratified cross-validation ===

=== Summary ===

Correctly Classified Instances 463 77.1667 %

Incorrectly Classified Instances 123 20.5 %

Kappa statistic 0.5789

Mean absolute error 0.2112

Root mean squared error 0.4536

Relative absolute error 43.5901 %

Root relative squared error 92.1819 %

UnClassified Instances 14 2.3333 %

Total Number of Instances 600

=== Detailed Accuracy By Class ===

TP Rate FP Rate Precision Recall F-Measure ROC Area Class

0.801 0.219 0.753 0.801 0.776 0.784 YES

0.781 0.199 0.825 0.781 0.803 0.79 NO

Weighted Avg. 0.79 0.208 0.792 0.79 0.79 0.787

=== Confusion Matrix ===

a b <-- classified as

213 53 | a = YES

70 250 | b = NO

    下面給出ID3的分類結果的圖片示意。

  • 可視化結果

    生成的可視化樹步驟中發現無法生成ID3分類器,說明WEKA原版軟件沒有提供ID3的可視化樹,故我使用了舊版WEKA,得到了相關的可視化樹。

    (圖片這么長的原因,一是分類樹有些寬大,二是我對筆記本電腦使用了擴展屏幕截屏所致。)

    至於特征之間的關系,如下圖所示。

    我們選擇Age和Income之間的關系進行分析,如下圖所示。可以觀察到年齡越小,收入越高的樣本基本不存在,密集程度較高的是年齡又小收入又少的情形。

    其ROC曲線可以如圖所示:

c)NBTree分類器

  • 先給出一些文字或數字的結果

        NBTree是Naive Bayesian分類樹,下面給出分類文字結果。

=== Stratified cross-validation ===

=== Summary ===

Correctly Classified Instances 502 83.6667 %

Incorrectly Classified Instances 98 16.3333 %

Kappa statistic 0.6697

Mean absolute error 0.2165

Root mean squared error 0.3592

Relative absolute error 43.6251 %

Root relative squared error 72.1096 %

Total Number of Instances 600

=== Detailed Accuracy By Class ===

TP Rate FP Rate Precision Recall F-Measure ROC Area Class

0.799 0.132 0.836 0.799 0.817 0.879 YES

0.868 0.201 0.837 0.868 0.852 0.879 NO

Weighted Avg. 0.837 0.169 0.837 0.837 0.836 0.879

=== Confusion Matrix ===

a b <-- classified as

219 55 | a = YES

43 283 | b = NO

    在運行狀態中,出現明顯的停滯和計算停留,說明該分類樹收斂慢,計算速度中等,占用一定時間,沒有J48分類快速。

  • 可視化結果

        在這里我們簡單展示NBTree的可視化樹

    其ROC曲線可以如下所示:

d)分析

    由於篇幅過長,這里不再列舉其他幾類分類樹的全部過程。我還測試了隨機樹Random Tree等等其他分類器的分類效果,測試狀態如圖所示。

    在上述三種方法中,由數據分析我可以看到J48的分類效果最好,達到85%,其次是貝葉斯分類,達到83.7%,最差是ID3分類僅有77.1%。

    而在WEKA提供的所有方法中,面對bank數據下,我將所有分類器的結果列舉成表,以供閱覽:

Method Name

Correctly

InCorrectly

TP/FN/FP/TN

BuildModelTIme

J48Graft

85.0000 %

15.0000 %

232/42/48/278

0.05 sec

J48

85.0000 %

15.0000 %

232/42/48/278

0.18 sec

LMT

84.1667 %

15.8333 %

222/52/43/283

16.19 sec

NBTree

83.6667 %

16.3333 %

219/55/43/283

7.3 sec

REPTree

83.5000 %

16.5000 %

229/45/54/272

0.02 sec

SimpleCart

82.3333 %

17.6667 %

213/61/45/281

2.04 sec

LADTree

81.5000 %

18.5000 %

201/73/38/288

0.47 sec

ADTree

80.5000 %

19.5000 %

192/82/35/291

0.19 sec

BFTree

80.3333 %

19.6667 %

217/57/61/265

2.07 sec

RandomForest

79.8333 %

20.1667 %

218/56/65/261

0.04 sec

ID3

77.1667 %

20.5000 %

213/53/70/250

0.13 sec

RandomTree

74.8333 %

25.1667 %

200/74/77/249

0.07 sec

FT

72.5000 %

27.5000 %

190/84/81/245

2.33 sec

DecisionStump

68.5000 %

31.5000 %

110/164/25/301

0.00 sec

    解釋部分算法:DecisionStump是單層決策樹算法,常被作為boosting的基本學習器。LMT是組合樹結構和Logistic回歸模型,每個葉子節點是一個Logistic回歸模型,准確性比單獨的決策樹和Logistic回歸方法要好。NBTree是貝葉斯分類法。ADTree是一種基於boosting的決策樹學習算法,其預測准確率比一般決策樹高,並可以給出預測置信度,實際中常使用它的改良BICA算法。

    由觀察可知,J48的分類性能最好。時間收斂快,分類准確。除REPTree外,其余識別率達到80%以上的算法,收斂時間均比J48要長,分類准確性略遜一籌。值得敬佩J48的繼承性與改進性,也向ID3致以敬意,沒有ID3的優點,C4.5也不會繼承,C4.5的信息增益率和剪枝策略是加強收斂與提高准確的重要手段。

垃圾郵件過濾

  • 實驗目的

    利用weka提供的文本分類器實現英文垃圾郵件的過濾,需要完成內容如下:

    (1)了解垃圾郵件過濾基本思想;

    (2)利用weka的預處理功能將文本轉化為向量;

    (3)利用weka提供的分類算法實現垃圾郵件的過濾,最少使用兩種分類器;

    (4)分析分類結果,並對分類器的性能進行比較。實驗過程

    小知識:1978年5月3日,星期六。網絡時代第一批垃圾郵件,由美國DEC公司通過互聯網的前身阿帕網發送給了393個接收者。垃圾郵件,指的是不請自來、強行塞入信箱的郵件。在若干年前,郵箱存儲空間普遍很小,垃圾郵件能夠塞爆郵箱,讓人們無法工作。

 

  • 實驗過程

    簡要分析垃圾郵件過濾的實驗過程。我的初步思路是將語料庫轉化為向量,然后分析向量模式,過濾敏感詞。最后得到垃圾樣本的分類。那么這樣文縐縐的話如何實踐是個問題,可以說完全不知道。通過查閱互聯網,明白其中含義。

    語料庫轉化為向量,無非是將語料庫的文字轉化為arff格式的可讀文本,並且要將文本分詞處理,去標點處理等等,否則不能讀入WEKA進行后續過濾。

    分析向量模式,無非是用WEKA分類器去做分析。

    1)數據預處理

a)文本數據讀入

    我們既可以自己使用Java編程或Python編程,將語料庫轉換為Arff格式,並做相關處理。也可以使用WEKA的接口,其提供了文本處理的一條龍服務,堪稱業內楷模。

    其命令行接口在Simple CLI按鈕彈出的界面內,如下圖所示:

    我們使用WEKA的兩類功能

    TextDirectoryLoader

    StringToWordVector

    上者具有的功能為將文本轉換為ARFF格式,下者具有的功能為將文本分詞,去標點處理等。

    首先將文本語料以如下目錄結構放置,否則WEKA命令行處理失敗:

    其次,值得一提bare是老師所給語料庫的文件名,以及二級目錄與末端文件都是老師所給的文件名,也可以根據自己需要隨意重命名,只要目錄關系正確就行。我的語料庫全部放在test文件夾內,所以我的執行命令語句如下,並如圖所示:

    java weka.core.converters.TextDirectoryLoader -dir test > all_text_examples.arff

    我們可以打開此生成ARFF文件,內容如下圖所示:

    觀察可知,WEKA將文本語料庫讀入后,將每個Part的文件夾內的所有文本歸為每一類class。每一類的class內的文本有多個,接下來我們要對每個class內的每將文本作切割處理分詞。

    接下來,使用WEKA讀入文本數據,每個文件夾內的文本數量狀態可以如下圖所示:

b)注意事項,避免歧途

    上面a)文本數據讀入,觀察其中的圖片,可以看到文本讀入后的類有part1至part7。請注意,這樣的part個數是不對的!這並不是說目錄結構不對,而是說文本過濾訓練的模型建模的不對!

    既然要訓練模型分為"不是垃圾郵件(NO)"和"是垃圾郵件(YES)",那么我們的訓練集當然在訓練之處,就僅分為兩個類別才對。即把垃圾郵件的文本整合在一個文件夾內,不是垃圾郵件的文本整合在另一個文件夾內,總共才兩個文件夾,而不是之前part1至part7的七個文件夾。

    只有分成兩個文件夾去訓練,這才是正確的分類結果。如果是part1至part7這樣的設置,到最后訓練出來的模型效果非常差。指標Relative absolute error大約高達98%,說明根本就沒有分類。所以在訓練集上,務必就分成兩個類,一個類全是正常郵件,另一個類全是垃圾郵件。如下圖所示:

c)文本向量化操作

    然后選擇Filter內unsupervised-attribute-StringToWordVector方法,操作如圖所示:

    這里簡要介紹一下StringToWordVector可能需要自己做調整的參數:

    -W 需要保留的單詞個數,默認為1000。這不是最終的特征維數,但是維數跟此參數是正相關的

-stopwords <file> 輸入停詞文件,文件格式為每一個詞一行。在讀文件到轉化特征時會自動去掉這些常用詞,系統自帶有一套停用詞。

    -tokenizer <spec> 自定義所要去除的符號,一般為標點符號。默認有常用的標點符號,但往往是不夠的,所以需自己添加

    其他參數只需默認值即可,可以按照圖中紅框的顯示值修改好,可如圖所示修改。

 

    在GUI當中,還有一些參數設置需要介紹:lowerCaseTokens是否區分大小寫,默認為false不區分,這里一般要設置為ture,因為同一個詞就會有大小寫的區別。至此文本便被分離成向量化格式。正確的向量化文本應當如下圖所示:


    我們注意到文本向量化的結構大致為如下,下面使用偽代碼進行示意向量化文本的內容:

    @attribute @@class@@ {msg,spam}

    @attribute WORD1 numeric

    @attribute WORD2 numeric

    @attribute WORD3 numeric

    @attribute WORD4 numeric

    {1 0.693147,2 1.098612,4 1.386294,……,}

    {4 2.70805 ,7 1.098612,8 2.639057,……,}

    {0 spam,8 4.672829,9 1.791759,13 1.791759,……,}

    {0 spam,2 3.218876,4 1.386294,8 2.397895,……,}

    這里class的標簽放在第一列。spam代表垃圾郵件,而沒有標出標簽的代表正常郵件,即msg。為什么spam標記出來而msg沒有,這說明WEKA的命令一脈相承,即使沒有出現msg標簽,也可以繼續投入使用。

 

    2)垃圾郵件分類訓練

a)IBk懶惰分類器

    懶惰分類器顧名思義,該算法不需要太操心,投入即可使用,參數使用起來方便,參數設定不需要太專業,自然效果一般。IBk本質上就是距離為k的k最近鄰分類器。IB1顯然就是距離為1的k近鄰分類,距離為1說明只有一個鄰居,也就是說IB1總共分成兩個類。選擇IBk該分類器的流程如下圖所示:

    由於該垃圾郵件的結論具有二值性,要么是垃圾郵件,要么不是垃圾郵件。故在kNN個數上設置為1,也就是說總共分成兩個類進行訓練。交叉驗證設為三份,便於訓練快速。如下圖所示:

    於是,最后一步,切記將分析對象調整至class標簽,否則分類不正確。調整對象的操作流程可下圖所示。

    最后,我們的圖片結果如下:

    文字結果摘錄如下:

=== Stratified cross-validation ===

=== Summary ===

Correctly Classified Instances 1942 88.9194 %

Incorrectly Classified Instances 242 11.0806 %

Kappa statistic 0.5221

Mean absolute error 0.1113

Root mean squared error 0.3326

Relative absolute error 42.6065 %

Root relative squared error 92.0846 %

Total Number of Instances 2184

=== Detailed Accuracy By Class ===

TP Rate FP Rate Precision Recall F-Measure ROC Area Class

0.959 0.496 0.914 0.959 0.936 0.733 msg

0.504 0.041 0.694 0.504 0.584 0.733 spam

Weighted Avg. 0.889 0.425 0.88 0.889 0.882 0.733

=== Confusion Matrix ===

a b <-- classified as

1772 75 | a = msg

167 170 | b = spam

    觀察分類效果,正確分類達到88.9%,這個結果較為不錯,可以接受,可以認為分類訓練較為合理。觀察精度precision可知,平均為88%,查全率大約為89%。總體效果良好。

b)聚類分析之SimpleKMeans

    我們使用聚類來做一次分析,請注意垃圾的過濾模型需要分類挖掘,而不是聚類。這里做聚類僅僅是觀察樣本的區分程度。

    選擇SimpleKMeans聚類,將聚類簇數設為兩個,並將聚類分析對象設定為class標簽,最后生成結果如下:

    文字結果大致如下:

Number of iterations: 11

Within cluster sum of squared errors: 43032.64174958714

=== Model and evaluation on training set ===

Clustered Instances

0 464 ( 21%)

1 1720 ( 79%)

Class attribute: @@class@@

Classes to Clusters:

0 1 <-- assigned to cluster

388 1459 | msg

76 261 | spam

Cluster 0 <-- spam

Cluster 1 <-- msg

Incorrectly clustered instances :    649.0     29.7161 %

    由此觀察可知,迭代11次后聚成兩類,聚類失敗的樣本大約為29%,即有71%的樣本可以成功聚類,在這71%的可聚在一起的樣本中又有其中的79%聚成1號類,其中的21%聚成0號類。

(五)WEKA實驗2:聚類分析

聚類分析

  • 實驗目的

    利用weka提供的k-means算法,對提供的bank-data.csv數據進行決策樹分類挖掘。作業需完成以下內容:

    (1)了解weka提供的數據預處理功能,去除bank-data數據中的無用屬性(如ID)結構,並對income屬性進行離散化處理;

    (2)利用weka提供的k-means算法,進行聚類分析,k選不同取值,查看並比較聚類結果;

    (3)選擇不同的距離函數,重新進行聚類分析,並比較聚類結果。

1)數據預處理

    數據預處理工作和之前在分類挖掘相似。

2)K-Means聚類算法

a)選擇Cluster

    我們對bank數據作聚類分析,選擇K-Means算法。操作選項如圖所示:

b)設置聚類參數

    選擇完畢后,需要對K-Means聚類設置相關參數,主要的設置區域如圖所示:

    顯然參數設置的不同,為聚類帶來的效果也不同。

    主要的參數為NumCluster和Seed。

    前者為聚類簇數,后者為種子數。

c)聚成2類的結果

    設置聚成2類之后,生成結果如下:

    文字結果大致摘錄如下:

Number of iterations: 3

Within cluster sum of squared errors: 1737.4061909284878

Time taken to build model (full training data) : 0.24 seconds

=== Model and evaluation on training set ===

Clustered Instances

0 252 ( 42%)

1 348 ( 58%)

Class attribute: pep

Classes to Clusters:

0 1 <-- assigned to cluster

121 153 | YES

131 195 | NO

Cluster 0 <-- YES

Cluster 1 <-- NO

Incorrectly clustered instances :    284.0     47.3333 %

d)聚成3類的結果

    假設我們將簇數取為3,隨機數為10。生成結果如下:

    以及主要指標結果:

    可以觀察其可視化結果,如圖所示:

    展示年齡與pep的關系

    展示收入與pep的關系

    展示地域與pep的關系

    由上述幾幅圖我們可以看出聚類效果並不好,在簇點下聚類點顏色混雜度較高,區分度不好,辨析度不強,不能有效挖掘關系。其重要判定指標文字結果如下:

Class attribute: pep

Classes to Clusters:

0 1 2 <-- assigned to cluster

114 90 70 | YES

125 125 76 | NO

Cluster 0 <-- YES

Cluster 1 <-- NO

Cluster 2 <-- No class

Incorrectly clustered instances :    361.0     60.1667 %

e)調整參數

    調整參數及生成結果如下

參數

NumCluster

Seed

squared errors

Incorrectly instances

2

2

10

1737.4

284.0     47.3333 %

2

20

1723.0

294.0     49 %

2

30

1778.3

261.0     43.5 %

2

40

1752.7

272.0     45.3333 %

2

50

1756.1

259.0     43.1667 %

2

100

1773.0

271.0     45.1667 %

3

3

10

2143.0

361.0     60.1667 %

3

20

2121.0

378.0     63 %

3

30

2168.0

340.0     56.6667 %

3

40

2166.0

341.0     56.8333 %

3

50

2159.0

319.0     53.1667 %

3

100

2151.0

295.0     49.1667 %

4

4

10

1991.0

416.0     69.3333 %

4

20

2018.0

400.0     66.6667 %

4

30

2118.0

353.0     58.8333 %

4

40

2071.0

393.0     65.5 %

4

50

2069.0

336.0     56 %

4

100

2046.0

349.0     58.1667 %

5

5

10

1916

441.0     73.5 %

5

20

1935.0

416.0     69.3333 %

5

30

1976.0

388.0     64.6667 %

5

40

1925.0

426.0     71 %

5

50

1956.0

378.0     63 %

5

100

1971.0

374.0     62.3333 %

6

6

10

1889.0

441.0     73.5 %

6

20

1864

414.0     69 %

6

30

1899.0

407.0     67.8333 %

6

40

1914.0

434.0     72.3333 %

6

50

1887.0

396.0     66 %

6

100

1864.0

401.0     66.8333 %

7

7

10

1805.0

472.0     78.6667 %

7

20

1832

419.0     69.8333 %

7

30

1854.0

411.0     68.5 %

7

40

1831.0

453.0     75.5 %

7

50

1805.0

414.0     69 %

7

100

1813.0

416.0     69.3333 %

8

8

10

1807.0

473.0     78.8333 %

8

20

1733

445.0     74.1667 %

8

30

1756.0

447.0     74.5 %

8

40

1702.0

459.0     76.5 %

8

50

1689.0

433.0     72.1667 %

8

100

1745.0

450.0     75 %

9

9

10

1750.0

482.0     80.3333 %

9

20

1688

454.0     75.6667 %

9

30

1718.0

451.0     75.1667 %

9

40

1681.0

476.0     79.3333 %

9

50

1721.0

460.0     76.6667 %

9

100

1657.0

465.0     77.5 %

10

10

10

1655.0

497.0     82.8333 %

10

20

1647.0

459.0     76.5 %

10

30

1707.0

455.0     75.8333 %

10

40

1599.0

495.0     82.5 %

10

50

1608

469.0     78.1667 %

10

100

1617.0

470.0     78.3333 %

f)數據分析

    在同一情況下,根據當前Within cluster sum of squared errors,調整"seed"參數,觀察Within cluster sum of squared errors(SSE)變化。采納SSE最小的一個結果。

    結合實際情況,對於pep僅有YES,NO的兩個結果的事實,一般聚簇調整為2個。再在2個聚簇的條件內選擇合適的方差中心。

3)層次聚類

a)選擇層次聚類HierarchicalCluster

    鑒於K-Means聚類的操作解釋足夠詳細,這里有詳有略地介紹實驗內容。如下圖展示選擇聚類方法:

    在如下圖所示的按鈕中,點擊cluster的命令行欄,彈出通知,點選choose可以更換不同的距離函數。

b)層次聚類過程及結果

    我們當前選擇歐式距離,選擇分析對象為Pep,再運行start。生成的圖片結果為:

    文字摘錄結果如下:

Time taken to build model (full training data) : 16.32 seconds

=== Model and evaluation on training set ===

Clustered Instances

0 599 (100%)

1 1 ( 0%)

Class attribute: pep

Classes to Clusters:

0 1 <-- assigned to cluster

274 0 | YES

325 1 | NO

Cluster 0 <-- NO

Cluster 1 <-- No class

Incorrectly clustered instances :    275.0     45.8333 %

    對比K-Means聚類,KMeans聚類的參數結果如下。

    KMeansIncorrectly clustered instances :    284     43.3333 %

    那么再看看層次聚類的結果

     0 1 <-- assigned to cluster

    274 0 | YES 45.7%

    325 1 | NO 54.3%

    對比KMeans聚類

    Clustered Instances

    0 252 ( 42%)

    1 348 ( 58%)

    為什么這么對比,為什么層次聚類的其中某一個樣本單獨作為一個類。原因在於層次聚類的規則算法,詳情可參見書籍或互聯網。層次聚類的最后一次操作,當然為大集合類與最后一個類的合並過程。故WEKA的層次聚類停留在最后一次上。在對比標簽的類上,兩者聚類的效果近似,沒有明顯分別。

c)距離函數更改為切比雪夫距離

    我們更換距離函數為切比雪夫距離公式,測試結果如圖所示。

    文字結果摘錄如下。

Time taken to build model (full training data) : 8.87 seconds

=== Model and evaluation on training set ===

Clustered Instances

0 1 ( 0%)

1 599 (100%)

Class attribute: pep

Classes to Clusters:

0 1 <-- assigned to cluster

1 273 | YES

0 326 | NO

Cluster 0 <-- YES

Cluster 1 <-- NO

Incorrectly clustered instances :    273.0     45.5 %

    曼哈頓距離(Manhattan Distance)可以理解為一階范數距離,歐式距離(Euclidean Distance)可以理解為二階范數距離,切比雪夫距離(Chebyshev Distance)可以理解為無窮范數距離。故在更改選擇距離范數的策略上,可能不具有左右影響力,原因在於距離函數的公式彼此之間相差無幾。

4)K-Means與層次聚類比較

    對於K-Means算法,其時間復雜度為O(tKmn),其中,t為迭代次數,K為簇的數目,m為數據記錄數,n為維數

    空間復雜度為O((m+K)n),其中,K為簇的數目,m為數據記錄數,n為維數。

    以實驗為例,在簇數為2的情形下分析bank數據,迭代次數為3次,數據記錄數大約為600條,維數為11維。時間復雜度大約為3萬,空間復雜度大約為6千。

    對於層次聚類算法,層次聚類的空間復雜度O(m2)。總時間復雜度O(m2logm),其中m是數據記錄數。對於本實驗而言,空間復雜度大約為36萬,時間復雜度上大約近乎100萬。故顯然在運行時間上,理論分析結果是層次聚類運行時間更長。

    根據WEKA分別運行兩算法的時間結果,對比可知如下:

Name

K-Means(歐氏,2類)

層次聚類

Time

0.24 sec

16.32 sec

5)文本聚類

    可參見垃圾郵件過濾中德聚類分析。

(六)WEKA實驗3:Apriori關聯挖掘

實驗目的

  • 實驗目的

    利用weka提供的關聯算法,實現給定數據對象的關聯挖掘。

WEKA關聯挖掘

1)數據預處理

    首先對bank數據進行預處理,預處理的對象包括有"ID","income"和"children"。對於ID要remove,對於income使用離散化過濾,使其在1至4之間,形成三份。children則在文本文件中修改參數,詳細操作在實驗1中應該非常詳細,故不再贅述。

    唯有處理掉所有Numeric數據,全部轉化為Nominal類型數據,才可以使用Apriori算法,否則該算法按鈕為灰色不可選狀態。

2)關聯挖掘

    選擇Apriori算法的操作如圖所示。

    對於Apriori的參數窗口,可以點擊命令行欄彈出。圖片內容如圖所示。

    詳細的參數含義如下羅列:

car 如果設為真,則會挖掘類關聯規則而不是全局關聯規則。

    classindex 類屬性索引。如果設置為-1,最后的屬性被當做類屬性。

    delta 以此數值為迭代遞減單位。不斷減小支持度直至達到最小支持度或產生了滿足數量要求的規則。

    lowerBoundMinSupport 最小支持度下界。

    metricType 度量類型。設置對規則進行排序的度量依據。可以是:置信度(類關聯規則只能用置信度挖掘),提升度(lift),杠桿率(leverage),確信度(conviction)

     Weka中設置了幾個類似置信度(confidence)的度量來衡量規則的關聯程度,它們分別是:

    a) Lift P(A,B)/(P(A)P(B)) Lift=1時表示AB獨立。這個數越大(>1),越表明AB存在於一個購物籃中不是偶然現象,有較強的關聯度。

    b)Leverage :P(A,B)-P(A)P(B) Leverage=0AB獨立,Leverage越大AB的關系越密切。

    c) Conviction:P(A)P(!B)/P(A,!B) !B表示B沒有發生) Conviction也是用來衡量AB的獨立性。從它和lift的關系(對B取反,代入Lift公式后求倒數)可以看出,這個值越大, AB越關聯。

    minMtric 度量的最小值。

    numRules 要發現的規則數。

    outputItemSets 如果設置為真,會在結果中輸出項集。

    removeAllMissingCols 移除全部為缺省值的列。

    significanceLevel 重要程度。重要性測試(僅用於置信度)。

    upperBoundMinSupport 最小支持度上界。從這個值開始迭代減小最小支持度。

    verbose 如果設置為真,則算法會以冗余模式運行。

    根據實際情況,我們將參數設置為:

% car   I - 輸出項集,若設為false則該值缺省;

% numRules  N 10 - 規則數為10;

% metricType T 0 – 度量單位選為置信度,(T1-提升度,T2杠桿率,T3確信度);

% minMetric  C 0.9 – 度量的最小值為0.9;

% delta  D 0.05 - 遞減迭代值為0.05;

% upperBoundMinSupport U 1.0 - 最小支持度上界為1.0;

% lowerBoundMinSupport  M 0.5 - 最小支持度下界為0.5;

% significanceLevel S -1.0 - 重要程度為-1.0;

% classIndex  c -1 - 類索引為-1輸出項集設為真

% (由於car, removeAllMissingCols, verbose都保持為默認值False,因此在結果的參數設置為缺省,若設為True,則會在結果的參數設置信息中分別表示為A, R,V)

    那么配置好參數,我們應用關聯挖掘算法於bank數據,結果如圖所示。

    文字結果摘錄如下:

Apriori

=======

Minimum support: 0.1 (60 instances)

Minimum metric <confidence>: 0.9

Number of cycles performed: 18

 

Generated sets of large itemsets:

Size of set of large itemsets L(1): 28

Size of set of large itemsets L(2): 232

Size of set of large itemsets L(3): 524

Size of set of large itemsets L(4): 277

Size of set of large itemsets L(5): 33

 

Best rules found:

1. income='(43758.136667-inf)' 80 ==> save_act=YES 80 conf:(1)

2. age='(50.666667-inf)' income='(43758.136667-inf)' 76 ==> save_act=YES 76 conf:(1)

3. income='(43758.136667-inf)' current_act=YES 63 ==> save_act=YES 63 conf:(1)

4. age='(50.666667-inf)' income='(43758.136667-inf)' current_act=YES 61 ==> save_act=YES 61 conf:(1)

5. children=0 save_act=YES mortgage=NO pep=NO 74 ==> married=YES 73 conf:(0.99)

6. sex=FEMALE children=0 mortgage=NO pep=NO 64 ==> married=YES 63 conf:(0.98)

7. children=0 current_act=YES mortgage=NO pep=NO 82 ==> married=YES 80 conf:(0.98)

8. children=0 mortgage=NO pep=NO 107 ==> married=YES 104 conf:(0.97)

9. income='(43758.136667-inf)' current_act=YES 63 ==> age='(50.666667-inf)' 61 conf:(0.97)

10. income='(43758.136667-inf)' save_act=YES current_act=YES 63 ==> age='(50.666667-inf)' 61 conf:(0.97)

3)調整參數

    根據任務要求,調整MetricType參數,可以得到下表:

 

Output

Lift

Minimum support: 0.25 (150 instances)

Minimum metric <lift>: 1.1

Number of cycles performed: 15

Generated sets of large itemsets:

Size of set of large itemsets L(1): 21

Size of set of large itemsets L(2): 59

Size of set of large itemsets L(3): 16

Best rules found:

1. age='(-inf-34.333333]' 195 ==> income='(-inf-24386.173333]' 174 conf:(0.89) < lift:(1.88)> lev:(0.14) [81] conv:(4.65)

2. income='(-inf-24386.173333]' 285 ==> age='(-inf-34.333333]' 174 conf:(0.61) < lift:(1.88)> lev:(0.14) [81] conv:(1.72)

3. married=YES 396 ==> mortgage=NO pep=NO 171 conf:(0.43) < lift:(1.24)> lev:(0.06) [33] conv:(1.14)

4. mortgage=NO pep=NO 209 ==> married=YES 171 conf:(0.82) < lift:(1.24)> lev:(0.06) [33] conv:(1.82)

5. married=YES mortgage=NO 261 ==> pep=NO 171 conf:(0.66) < lift:(1.21)> lev:(0.05) [29] conv:(1.31)

6. pep=NO 326 ==> married=YES mortgage=NO 171 conf:(0.52) < lift:(1.21)> lev:(0.05) [29] conv:(1.18)

7. children=0 263 ==> pep=NO 167 conf:(0.63) < lift:(1.17)> lev:(0.04) [24] conv:(1.24)

8. pep=NO 326 ==> children=0 167 conf:(0.51) < lift:(1.17)> lev:(0.04) [24] conv:(1.14)

9. married=YES save_act=YES 277 ==> pep=NO 175 conf:(0.63) < lift:(1.16)> lev:(0.04) [24] conv:(1.23)

10. pep=NO 326 ==> married=YES save_act=YES 175 conf:(0.54) < lift:(1.16)> lev:(0.04) [24] conv:(1.15)

Leverage

Minimum support: 0.1 (60 instances)

Minimum metric <leverage>: 0.1

Number of cycles performed: 18

Generated sets of large itemsets:

Size of set of large itemsets L(1): 28

Size of set of large itemsets L(2): 232

Size of set of large itemsets L(3): 524

Size of set of large itemsets L(4): 277

Size of set of large itemsets L(5): 33

Best rules found:

1. age='(-inf-34.333333]' 195 ==> income='(-inf-24386.173333]' 174 conf:(0.89) lift:(1.88) < lev:(0.14) [81]> conv:(4.65)

2. income='(-inf-24386.173333]' 285 ==> age='(-inf-34.333333]' 174 conf:(0.61) lift:(1.88) < lev:(0.14) [81]> conv:(1.72)

3. age='(-inf-34.333333]' 195 ==> income='(-inf-24386.173333]' current_act=YES 138 conf:(0.71) lift:(1.97) < lev:(0.11) [68]> conv:(2.16)

4. income='(-inf-24386.173333]' current_act=YES 215 ==> age='(-inf-34.333333]' 138 conf:(0.64) lift:(1.97) < lev:(0.11) [68]> conv:(1.86)

5. income='(-inf-24386.173333]' 285 ==> age='(-inf-34.333333]' current_act=YES 138 conf:(0.48) lift:(1.9) < lev:(0.11) [65]> conv:(1.43)

6. age='(-inf-34.333333]' current_act=YES 153 ==> income='(-inf-24386.173333]' 138 conf:(0.9) lift:(1.9) < lev:(0.11) [65]> conv:(5.02)

Conviction

Minimum support: 0.25 (150 instances)

Minimum metric <conviction>: 1.1

Number of cycles performed: 15

Generated sets of large itemsets:

Size of set of large itemsets L(1): 21

Size of set of large itemsets L(2): 59

Size of set of large itemsets L(3): 16

Best rules found:

1. age='(-inf-34.333333]' 195 ==> income='(-inf-24386.173333]' 174 conf:(0.89) lift:(1.88) lev:(0.14) [81] < conv:(4.65)>

2. mortgage=NO pep=NO 209 ==> married=YES 171 conf:(0.82) lift:(1.24) lev:(0.06) [33] < conv:(1.82)>

3. income='(-inf-24386.173333]' 285 ==> age='(-inf-34.333333]' 174 conf:(0.61) lift:(1.88) lev:(0.14) [81] < conv:(1.72)>

4. age='(50.666667-inf)' 191 ==> save_act=YES 151 conf:(0.79) lift:(1.15) lev:(0.03) [19] < conv:(1.44)>

5. save_act=YES pep=NO 235 ==> married=YES 175 conf:(0.74) lift:(1.13) lev:(0.03) [19] < conv:(1.31)>

6. married=YES mortgage=NO 261 ==> pep=NO 171 conf:(0.66) lift:(1.21) lev:(0.05) [29] < conv:(1.31)>

7. pep=NO 326 ==> married=YES 242 conf:(0.74) lift:(1.12) lev:(0.04) [26] < conv:(1.3)>

8. children=0 263 ==> pep=NO 167 conf:(0.63) lift:(1.17) lev:(0.04) [24] < conv:(1.24)>

9. married=YES save_act=YES 277 ==> pep=NO 175 conf:(0.63) lift:(1.16) lev:(0.04) [24] < conv:(1.23)>

10. current_act=YES pep=NO 244 ==> married=YES 177 conf:(0.73) lift:(1.1) lev:(0.03) [15] < conv:(1.22)>

    至此關聯挖掘的實驗結束。

(七)WEKA實驗改進:二次開發

實驗目的

  • 實驗目的

    (1)嘗試簡單的WEKA二次開發入門知識;

    (2)總結網絡及論壇的各類二次開發經驗與技巧;

    (3)實踐二次開發的小實驗。

WEKA二次開發的准備工作

1)WEKA二次開發的准備分析

    通過咨詢老師和查閱網絡論壇資料,發現WEKA二次開發過程提供的教程良莠不齊。原因歸結於幾點:

    a)WEKA版本眾多,穩定版開發版嘗試的人均不一樣,操作的順序流程也不盡相同。

    b)WEKA的二次開發涉及到Eclipse,不同版本的操作軟件集成環境也不盡相同。

    c)提供的教程有時不充分,涉及到關鍵的部分時,總是沒有詳細解釋,故需要摸索一條符合實際情況的道路。

2)WEKA二次開發的版本准備

    通過結合網絡資料,決定要么采用WEKA3-4-X的穩定版,要么采用WEKA3-6-X穩定版本,其中值得一提的是對於WEKA3-5-X網絡論壇網友也有嘗試,應該是成功的,但是局限在WEKA3-5版本內。二次開發的流程區別在3-4和3-5之間成為分界嶺,但應當是細節的不同。歷屆版本的下載網址見如下:

    http://sourceforge.net/projects/weka/files/weka-3-4-windows/

    這里我展示WEKA3-4-10和WEKA3-6-11的二次開發環境配置。

3)WEKA3-4-10二次開發的配置

    WEKA3-4-10的界面如下:

    建立Eclipse的java項目工程,該過程不贅述,然后項目工程的基本目錄結構要有,也就是說,得有src文件夾,鏈接庫等。繼而開始正式的步驟,右鍵點擊weka項目名,點擊導入,並選擇文件系統,所選的文件系統為你weka軟件目錄下的weka-src文件(如果是jar包狀態,請解壓后導入),導入操作如圖所示:

    然后選擇導入文件的路徑,在這里請注意!非常重要的是,一定要選擇正確目錄。請與java程序中package的路徑一致,不要跟網絡論壇資料人雲亦雲!網絡論壇教程資料有時不符合你的實際情況。

    我通過舉例子,下圖中有帶"勾"的目錄,和帶"叉"的目錄。"勾"是正確的目錄,"叉"是錯誤路徑,而"叉"往往都是教程提供的路徑,這是因為java程序中package的內容是weka.xxxxxx。那么目錄一定要在weka/xxxxx下。

    所以導入后,各src下的程序文件都帶有weka的"頭",如下圖細紅框標注所示。如果設置錯誤的路徑,則會提示error,關於無法找到package路徑的問題。通過設置正確的文件目錄后,基本已無error,大多出現warning,如下圖粗紅框內所示,這是正常現象。現在已經可以做開發編程,warning並不會影響。

     4)WEKA3-6-11二次開發的配置

    WEKA3-6-11導入的也是WEKA3-6-11目錄下的weka-src,如果weka-src是jar包狀態,請解壓后導入。導入的路徑選擇為下圖所示,其中紅色框為路徑節點,藍色框是點擊選擇的目錄路徑,即鼠標點擊紅色框內文件夾名字,再點擊確定即可:

導入后會出現無法解析類型的錯誤,如下圖所示。

    一般是缺少動態庫所致,此時需要鏈接相關的庫。鏈接的庫jar包全部在weka-src文件夾lib目錄下。可對Eclipse的JRE系統庫上右鍵點選"構建路徑-配置構建路徑",在彈出的窗口中,選擇"添加外部jar",進入剛才所說的路徑,把三個jar導入。此時Eclipse左側欄產生了"引用庫"內容。

    此時不再提示無法解析類型錯誤,但仍然有一些錯誤未能解決,例如"覆蓋超類的錯誤"沒有解決,此問題一般是Java編譯器版本設置問題。

    修改你的eclipse指定的編譯器版本。在選項里的java compiler中指定版本至少在5.0以上。在myEclipse中改變編譯器的方法:Project->Properties->JavaCompiler

->ConfigureWorkspace Setting,在彈出的頁面中可以進行設置。

    如果是Eclipse下,請在"窗口-首選項"下的彈出窗口內,操作如圖:

    然后點選"java-編譯器",更改一致性級別,調整至1.7,此數值對應編譯5.0版本以上,可以提供兼容覆蓋超類語法的編譯環境。

    至此,所有錯誤都消失,但Warning存在,仍然是不影響二次開發編程的。通過找到"weka gui"下的"guichooser.java",點擊運行,即可看見wekaGUI界面。如需要添加自己的程序,則添加新的java程序,編寫符合weka接口的新程序,再通過可視化界面運行即可。

    ##########點擊運行,即可看見wekaGUI界面。如需要添加自己的程序,則添加新的java程序,編寫符合weka接口的新程序,再通過可視化界面運行即可。

WEKA二次開發小實驗

1)WEKA的classpath鏈接開發

    咨詢老師和查閱互聯網,教程提供各種不對,所以有一句話說得好,要走出自己的特色道路,不要迷信於別人的道路,不要迷失自己的道路,對自己的道路要自信。這句話意味着,不要認為自己完全不明白,WEKA和java eclipse實驗做到這個份上,有些知識要自己去舉一反三,觸類旁通。以增加LibSvm的jar包為例,這里我們采用鏈接至系統路徑中方法,使WEKA的jar包調用。

    靈感在於,咨詢老師時,老師建議使用支持向量機的方法去對垃圾郵件作訓練。那么如果需要該方法訓練,需要下載與WEKA版本對應的jar庫,有兩個,一個是libsvm.jar,另一個是wlsvm.jar。下載地址可以是:

    http://www.cs.iastate.edu/~yasser/wlsvm/ 

    

  下圖展示的是WEKA3-4版本就沒有保留LibSvm的位置,故無此項。

    下圖展示的是WEKA3-6版本,它雖然保留LibSvm的位置,但如果缺少其jar包,則顯示為藍色狀態。由於我的WEKA為3-6版本,當時Classify里的Classifier-functions內Libsvm是藍色的,即未連接狀態。即使運行,也會報出錯誤。

    所報錯誤如下:

    這是由於沒有相關庫,而強行運行所致。即便下載了相關庫,不進行環境變量的添加,也不行。此時不要迷信別人的方法,也不要迷信我的方法,我的方法是我根據自己的情況摸索出來的。

    首先要配置WEKA安裝目錄下的文件RunWeka.ini配置文件,打開后,觀察其中一句語句。顯然此語句提供的了classpath的路徑。(網上的修改方法完全忽略此語句,如果不注意到這個細節,則南轅北轍。這也是因為3-6版本較高不同於以前的版本的緣故,所以參考別人教程要注意自己的環境。)

    然后根據該配置文件的案例,即

    # cp=%CLASSPATH%;D:/libraries/libsvm.jar

    我們模仿一下,在上面的這個注釋下方一行,添加一條語句,類似可如下:

    cp=%CLASSPATH%;E:/Weka-3-6/wlsvm.jar;E:/Weka-3-6/libsvm.jar;

    請注意,上面的指令具備兩個條件,一是你要把這兩個相關庫放在WEKA目錄下,二是指令要寫在注釋行下方。然后保存關閉,點擊RunWeka.bat去更新(記住用管理員權限打開一次,然后再雙擊打開一次。前者方式的打開會打不開,后者的方式會打開。個中玄妙,唯有實踐者才會體會)。此時再打開WEKA-Explorer,尋找Libsvm功能時,是黑色的,可選的,可用的,如下所示:

    說明此時可以運行Libsvm成功,至於運行是否成功還未可知。

    我們使用Libsvm庫對垃圾郵件做分類,一般來說調用Libsvm會出現兩個錯誤:

一是,start后報錯:problem evaluating classifier: rand

    該問題解決方法是,更新libsvm較高,原因是libsvm的版本低。更為重要的是,WLSvm的文件包下載下來時是ZIP壓縮文件,打開后里面會有libsvm,記住不要用WLSvm里的libsvm,這個libsvm不是WEKA軟件里鏈接命名的libsvm。需要下載libsvm.zip壓縮包里的文件,並引用這里面的文件。

    二是,報錯關於不能處理Could not handle Numeric數據

    簡單,把分類關鍵標簽離散化為二值。以郵件過濾為例,class標簽要么是msg代表正常郵件,要么是spam代表垃圾郵件。我們只要離散化class的值即可。我自己操作的時候把msg標為1,spam標為2。

    其中離散化成為Nominal類型的圖片結果如下:

    使用Libsvm生成訓練結果的圖片如下:

    文字結果摘要如下:

=== Classifier model (full training set) ===

LibSVM wrapper, original code by Yasser EL-Manzalawy (= WLSVM)

Time taken to build model: 27.18 seconds

=== Stratified cross-validation ===

=== Summary ===

Correctly Classified Instances 1938 88.7363 %

Incorrectly Classified Instances 246 11.2637 %

Kappa statistic 0.3849

Mean absolute error 0.1126

Root mean squared error 0.3356

Relative absolute error 43.1037 %

Root relative squared error 92.9061 %

Total Number of Instances 2184

=== Detailed Accuracy By Class ===

TP Rate FP Rate Precision Recall F-Measure ROC Area Class

1 0.73 0.882 1 0.938 0.635 msg

0.27 0 1 0.27 0.425 0.635 spam

Weighted Avg. 0.887 0.617 0.901 0.887 0.859 0.635

=== Confusion Matrix ===

a b <-- classified as

1847 0 | a = msg

246 91 | b = spam

    觀察相關參數,知支持向量機下的精度要比K近鄰懶惰算法還要好,召回率與K懶惰算法不相上下,對於正例判決是滿分,即百分百,足見支持向量機果然大名鼎鼎。

后記

    感謝老師的幫助,沒有兩位老師的課上教學和課下指導,我是根本不可能做出來的。對於WEKA的理解和HADOOP的理解又不斷深入。

    本科畢業的時候,我的本科導師就教導過道德經中有雲"為學日益,為道日損,損之又損,以至於無為,無為而無不為。"求學日益精進,實踐日益漸辛。等到一天,求學不斷進步,實踐不斷圓滿,以至於無為,無為也就無不為了,當然這也是理想的境界,也是不斷追求的境界。

    謝謝老師!真的是麻煩您了!

 


<<<<<<<<<  寫在頁面最底的小額打賞  >>>>>>>>>

如果讀者親願意的話,可以小額打賞我,感謝您的打賞。您的打賞是我的動力,非常感激。

必讀:如您願意打賞,打賞方式任選其一,本頁面右側的公告欄有支付寶方式打賞,微信方式打賞。

避免因打賞產生法律問題,兩種打賞方式的任一打賞金額上限均為5元,謝謝您的支持。

如有問題,請24小時內通知本人郵件。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM