1. 程式人生 > >R語言與機器學習學習筆記(分類演算法)(1)K-近鄰演算法

R語言與機器學習學習筆記(分類演算法)(1)K-近鄰演算法

前言

     最近在學習資料探勘,對資料探勘中的演算法比較感興趣,打算整理分享一下學習情況,順便利用R來實現一下資料探勘演算法。

     資料探勘裡我打算整理的內容有:分類,聚類分析,關聯分析,異常檢測四大部分。其中分類演算法主要介紹:K-近鄰演算法,決策樹演算法,樸素貝葉斯演算法,支援向量機,神經網路,logistic迴歸。

    寫這份學習筆記主要以學校data mining課程的課件為主,會參考一堆的baidu,一堆的google,一堆的blog,一堆的book以及一堆亂七八糟的資料,由於精力有限,恕不能一一列出,如果有認為有侵權行為歡迎與我聯絡,保證及時刪除。

    這篇文章是我部落格資料探勘系列的第一篇文章,介紹分類演算法中最基本的演算法——k近鄰演算法。

演算法一:K-近鄰演算法

  原理及舉例

      工作原理:我們知道樣本集中每一個數據與所屬分類的對應關係,輸入沒有標籤的新資料後,將新資料與訓練集的資料對應特徵進行比較,找出“距離”最近的k(通常k<20)資料,選擇這k個數據中出現最多的分類作為新資料的分類。

演算法描述:

(1)      計算已知類別資料及中的點與當前點的距離;

(2)      按距離遞增次序排序

(3)      選取與當前點距離最小的k個點

(4)      確定前K個點所在類別出現的頻率

(5)      返回頻率最高的類別作為當前類別的預測

        距離計算方法有"euclidean"(歐氏距離),”minkowski”(明科夫斯基距離), "maximum"(切比雪夫距離), "manhattan"(絕對值距離),"canberra"(蘭式距離), 或 "minkowski"(馬氏距離)等.

        分析學的知識告訴我們Rn上範數之間是等價的,所以我們也沒必要太過糾結選誰,畢竟範數之間都是可以相互控制的。

         這裡我們使用最常見歐氏距離作為衡量標準,以鳶尾花資料集為例來說明K-近鄰演算法:

         鳶尾花資料集包含150個數據,測量變數為花瓣,花萼的長度與寬度,分類變數為setosa, versicolor, 和 virginica。

準備資料:


        為了瞭解資料,我們先通過作圖分析,相關分析來看看資料分類指標的合理性,這一點十分重要,有助於減少分類指標中的噪聲。

        從上圖可以看出,我們通過這2個變數大致是可以把鳶尾花分類的,也就是說分類的特徵變數選擇是合理的,(同理可以分析另外2個,分類效果不如這兩個,但大致上還是能區分的)當然我們也可以選擇計算相關係數來看特徵變數的合理性。

        我們很容易發現,數值差最大的屬性對距離的影響最大,所以在特徵值等權重的假定下,我們先得歸一化特徵值,計算公式為:

Newvalue=(oldvalue-min)/(max-min)

(注:網友@reus123指出歸一化的提法不太合適,也的確如此,我們這裡將本文的“歸一化”理解為一種“標準化”就好,感謝@

         R程式碼:

autonorm<-function(data){
	min<-min(data)
	max<-max(data)
    for(i in 1:length(data))
      data[i]<-(data[i]-min)/(max-min)
    return(data)
}
data<-apply(as.matrix(iris[,1:4]),2,autonorm)

(之前的程式有誤,已更正,感謝@

       得到了歸一化後的資料集,下面計算距離。我們在這裡取三個資料作為驗證集來看看分類的效果,首先將驗證集歸一化:

x<-iris[13,1:4]
y<-iris[79,1:4]
z<-iris[100,1:4]
x<-(x-apply(iris[c(-13,-79,-100),1:4],2,min))/(apply(iris[c(-13,-79,-100),1:4],2,max)-apply(iris[c(-13,-79,-100),1:4],2,min))
y<-(y-apply(iris[c(-13,-79,-100),1:4],2,min))/(apply(iris[c(-13,-79,-100),1:4],2,max)-apply(iris[c(-13,-79,-100),1:4],2,min))
z<-(z-apply(iris[c(-13,-79,-100),1:4],2,min))/(apply(iris[c(-13,-79,-100),1:4],2,max)-apply(iris[c(-13,-79,-100),1:4],2,min))

        計算距離,僅以Z為例,執行程式碼:(k取5)

dis<-rep(0,length(data[,1]))
for(iin 1:length(data[,1]))
dis[i]<-sqrt(sum((z-data[i,1:4])^2))
table(data[order(dis)[1:5],5])

        從x,y,z的輸出結果可以看到,分類完全正確,沒有錯誤分類。

     值得一提的是,我們用同樣的辦法計算K=3時的情形,會發現沒有出現誤分類。這也就引出了一個值得思考的問題:k應該如何選取?k過小,噪聲對分類的影響就會變得非常大,K過大,那麼包含錯誤就理所當然,誤分類也不足為奇。雖然這裡我們對K的取值並未進行討論,但在實際中,我們應該通過交叉驗證的辦法來確定k值

  R語言內建函式kknn簡介     

           R語言裡的kknn包也可以實現最鄰近演算法——使用kknn函式。

kknn(formula = formula(train),train, test, na.action = na.omit(),

  k= 7, distance = 2, kernel = "optimal", ykernel = NULL, scale=TRUE,

  contrasts= c('unordered' = "contr.dummy", ordered ="contr.ordinal"))

引數解釋:

formula      一個迴歸模型,具體為:分類變數~特徵變數             

train           訓練集

test            測試集

na.action   缺失值處理,預設為去掉缺失值

k                k值選擇,預設為7

distance    這個是明科夫斯基距離,p=2時為歐氏距離

其他引數    略

    上面的鳶尾花例子使用kknn包可以實現(k=5):

library(kknn)
 
data(iris)
m <- dim(iris)[1]
val <- sample(1:m, size =round(m/3), replace = FALSE,
  prob= rep(1/m, m))
iris.learn <- iris[-val,]
iris.valid <- iris[val,]
iris.kknn <- kknn(Species~.,iris.learn, iris.valid, distance = 5,
  kernel= "triangular")
summary(iris.kknn)
fit <- fitted(iris.kknn)
table(iris.valid$Species, fit)

      這裡我們的訓練集選取更隨機化,得到結果是:

                                fit

              setosa   versicolor   virginica

 setosa         12          0              0

 versicolor      0         22             0

 virginica        0          0             16

        分類完全正確。

 應用舉例:手寫數字識別

    下面我們來做一個規模大一些的資料處理,利用k-近鄰實現一下數字的模式識別。這個例子來自《機器學習實戰》,具體資料集已上傳至百度雲盤(點選這裡下載)。資料為了簡單起見,僅提供0~9,10個數字的識別。需要識別的數字你可以看做是被影象處理軟體處理為了32*32的黑白影象。儘管文字格式儲存圖片不能夠有效地利用儲存空間,但是為了方便理解還是提供了這個文字版的圖片資料。至於影象版本的資料,你可以找到《手寫數字的光學識別》一文(登載於2010年的UCI機器學習資料庫中)的資料集合,並下載它。

    完整的R實現:

setwd("D:/R/data/digits/trainingDigits")
names<-list.files("D:/R/data/digits/trainingDigits")
data<-paste("train",1:1934,sep="")
for(i in 1:length(names))
assign(data[i],as.matrix(read.fwf(names[i],widths=rep(1,32))))


dis<-function(datatest,datatrain,len){
distance<-rep(0,len)
for(i in 1:len)
distance[i]<-sqrt(sum((get(datatest)-get(datatrain[i]))^2))
return((distance))
}

judge<-function(test,data,names){
index<-rep(0:9,c(189,198,195,199,186,187,195,201,180,204))
di<-rep(0,1934)
di[1:1934]<-dis(test,data,length(names))
return(names(which.max(table(index[order(di)[1:5]]))))
}

setwd("D:/R/data/digits/testDigits")
name<-list.files("D:/R/data/digits/testDigits")
test<-paste("test",1:946,sep="")
for(i in 1:length(name))
assign(test[i],as.matrix(read.fwf(name[i],widths=rep(1,32))))
index1<-rep(0:9,c(87,97,92,85,114,108,87,96,91,89))

error<-0
for(i in 1:946){
if(judge(test[i],data,names)!=index1[i])
error<-error+1
}


        執行結果:

>error

[1]19

>19/946

[1]0.02008457

也就是說,使用5-近鄰演算法,誤差率為2%,屬於一個可以接受的範圍。

     這裡由於本人沒有找到較好的批量匯入資料的辦法,所以程式碼有些複雜,也出現了hardcode和magicnumber的毛病,但是泛化也不是那麼的複雜,所以也沒再做更進一步的改進。希望讀者告訴我如何解決R裡匯入批量資料的方法。

     其中有兩個函式是我在之前的部落格中沒有使用過的,現在簡單介紹如下:

     賦值函式assign:

           assign("x", c(10.4, 5.6, 3.1, 6.4, 21.7)) 與x <- c(10.4,5.6, 3.1, 6.4, 21.7)等價

    讀取賦值函式的函式get:

            a<- 1:4

            assign("a[1]",2)

            a[1]== 2          #FALSE

           get("a[1]") == 2    #TRUE

    在R中,我沒有找到求眾數的函式,簡單編寫了一個names(which.max(table(index[order(di)[1:5]]))),這個函式有兩個眾數時會輸出兩個,所以K近鄰為了保證多數投票法有用,麻煩仔細選擇合理的k值。

    這裡我在做訓練集時並沒有選擇k值得過程(因為這個演算法實在是太慢了,沒有那個耐心)

    實際使用這個演算法,執行效率相當的低下,每個距離的計算包含了1024個維度的浮點運算,總計900多次,還要為測試向量準備2M的儲存空間。所以k決策樹是你需要進一步瞭解的。

    K決策樹的種類也有不少,比如kd樹,但是他們的問題就是k的選取總是一個麻煩的過程,kd樹找最近鄰是十分高效的,但是找k近鄰,刪除結點重新建樹還是比較麻煩的。

Further reading:

預告

        本文之後,待寫的幾篇文章羅列如下:

  • 演算法二:決策樹演算法
  • 演算法三:樸素貝葉斯演算法
  • 演算法四:支援向量機
  • 演算法五:神經網路
  • 演算法六:logistic迴歸

(to be continue)