1. 程式人生 > >機器學習C10筆記: kNN推薦系統

機器學習C10筆記: kNN推薦系統

KNN簡介

來自百度百科
鄰近演算法,或者說K最近鄰(kNN,k-NearestNeighbor)分類演算法是資料探勘分類技術中最簡單的方法之一。所謂K最近鄰,就是k個最近的鄰居的意思,說的是每個樣本都可以用它最接近的k個鄰居來代表。
kNN演算法的核心思想是如果一個樣本在特徵空間中的k個最相鄰的樣本中的大多數屬於某一個類別,則該樣本也屬於這個類別,並具有這個類別上樣本的特性。該方法在確定分類決策上只依據最鄰近的一個或者幾個樣本的類別來決定待分樣本所屬的類別。 kNN方法在類別決策時,只與極少量的相鄰樣本有關。由於kNN方法主要靠周圍有限的鄰近的樣本,而不是靠判別類域的方法來確定所屬類別的,因此對於類域的交叉或重疊較多的待分樣本集來說,kNN方法較其他方法更為適合。

優點

  1. 簡單,易於理解,易於實現,無需估計引數,無需訓練;
  2. 適合對稀有事件進行分類;
  3. 特別適合於多分類問題(multi-modal,物件具有多個類別標籤), kNN比SVM的表現要好。

缺點

  1. 該演算法在分類時有個主要的不足是,當樣本不平衡時,如一個類的樣本容量很大,而其他類樣本容量很小時,有可能導致當輸入一個新樣本時,該樣本的K個鄰居中大容量類的樣本佔多數。 該演算法只計算“最近的”鄰居樣本,某一類的樣本數量很大,那麼或者這類樣本並不接近目標樣本,或者這類樣本很靠近目標樣本。無論怎樣,數量並不能影響執行結果。
  2. 該方法的另一個不足之處是計算量較大,因為對每一個待分類的文字都要計算它到全體已知樣本的距離,才能求得它的K個最近鄰點。
    可理解性差,無法給出像決策樹那樣的規則。

算例

讀取資料

# First code snippet
df <- read.csv(file.path('data', 'example_data.csv'))

head(df)
# X Y Label
#1 2.373546 5.398106 0
#2 3.183643 4.387974 0
#3 2.164371 5.341120 0
#4 4.595281 3.870637 0
#5 3.329508 6.433024 0
#6 2.179532 6.980400 0

計算每個樣本間的歐氏距離

# Second code snippet
distance.matrix <- function(df)
{
  distance
<- matrix(rep(NA, nrow(df) ^ 2), nrow = nrow(df)) for (i in 1:nrow(df)) { for (j in 1:nrow(df)) { distance[i, j] <- sqrt((df[i, 'X'] - df[j, 'X']) ^ 2 + (df[i, 'Y'] - df[j, 'Y']) ^ 2) } } return(distance) }

按照距離排序

# Third code snippet
k.nearest.neighbors <- function(i, distance, k = 5)
{
  return(order(distance[i, ])[2:(k + 1)])#返回與i最近的5個樣本距離
}

knn

# Fourth code snippet
knn <- function(df, k = 5)
{
  distance <- distance.matrix(df)

  predictions <- rep(NA, nrow(df))

  for (i in 1:nrow(df))
  {
    indices <- k.nearest.neighbors(i, distance, k = k)
    predictions[i] <- ifelse(mean(df[indices, 'Label']) > 0.5, 1, 0)#按照label數多少分類
  }

  return(predictions)
}

分類效果比較

# Fifth code snippet
df <- transform(df, kNNPredictions = knn(df))

sum(with(df, Label != kNNPredictions))
#[1] 7

nrow(df)
#[1] 100

用glm構建線性判別模型

library('class')

df <- read.csv(file.path('data', 'example_data.csv'))

n <- nrow(df)

set.seed(1)

indices <- sort(sample(1:n, n * (1 / 2)))

training.x <- df[indices, 1:2]
test.x <- df[-indices, 1:2]

training.y <- df[indices, 3]
test.y <- df[-indices, 3]

# There's a bug here!
predicted.y <- knn(training.x, test.x, training.y, k = 5)

sum(predicted.y != test.y)
#[1] 7

length(test.y)
#[1] 50

# Seventh code snippet
logit.model <- glm(Label ~ X + Y, data = df[indices, ])

predictions <- as.numeric(predict(logit.model, newdata = df[-indices, ]) > 0)

sum(predictions != test.y)

誤差16大於knn的7

sum(predictions != test.y)
[1] 16

構建簡單的推薦系統

利用kNN進行推薦的方法分為:
1. 基於物品(item-based)的方法: 為目標使用者推薦那些與他已經喜歡的物品相似的物品, 這種方法為基於物品(item-based)方法.
2. 基於使用者(user-based)的方法: 先利用kNN演算法找到與目標使用者品味比較相同的使用者,然後根據這些品味相近的使用者的喜好來為目標使用者進行推薦.
兩種方法適用於不同的場景, 當用戶比物品多時,使用基於物品的推薦方法會節省許多的計算時間與儲存空間. 反之,使用另外一種方法.  

若想要構建大型的推薦系統需要將kNN,矩陣分解和其他分類器結合起來. 即整合方法(ensemble method)
可參考:矩陣分解在推薦系統中的應用

接下來的例子是,r程式包推薦.有50個使用者安裝程式包的資訊,利用此資訊進行推薦.

dataclean

# Eighth code snippet
installations <- read.csv(file.path('data', 'installations.csv'))

head(installations)
# Package User Installed
#1 abind 1 1
#2 AcceptanceSampling 1 0
#3 ACCLMA 1 0
#4 accuracy 1 1
#5 acepack 1 0
#6 aCGH.Spline 1 0

# Ninth code snippet
library('reshape')
# 利用cast對矩陣reshape, user為行package為列
user.package.matrix <- cast(installations, User ~ Package, value = 'Installed')

user.package.matrix[, 1]# 第一列是user
# [1] 1 3 4 5 6 7 8 9 11 13 14 15 16 19 21 23 25 26 27 28 29 30 31 33 34
#[26] 35 36 37 40 41 42 43 44 45 46 47 48 49 50 51 54 55 56 57 58 59 60 61 62 63
#[51] 64 65

user.package.matrix[, 2]#其他列是不同的包是否安裝
# [1] 1 1 0 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 0 0 1 1 1 1 1 1 1 0 1 0 1 0 1 1 1 1 1 1
#[39] 1 1 1 1 1 1 1 1 0 1 1 1 1 1
#將user列程式設計行名並去掉此列
row.names(user.package.matrix) <- user.package.matrix[, 1]

user.package.matrix <- user.package.matrix[, -1]

reshape後的資料:

> head(user.package.matrix[,1:5])
  User abind AcceptanceSampling ACCLMA accuracy
1    1     1                  0      0        1
2    3     1                  1      0        1
3    4     0                  1      1        1
4    5     1                  1      1        0
5    6     1                  1      1        0
6    7     1                  1      1        0

用列之間的相似度衡量包之間的相似度cor

similarities <- cor(user.package.matrix)

nrow(similarities)
#[1] 2487
ncol(similarities)
#[1] 2487
similarities[1, 1]
#[1] 1
similarities[1, 2]
#[1] -0.04822428

我們得到了不同包之間的相似度,但是我們需要把相似度轉換成距離,利用以下公式轉換:

distances <- -log((similarities / 2) + 0.5)```

knn推薦

找到與i包最相似的前25個包

# Twelfth code snippet
k.nearest.neighbors <- function(i, distances, k = 25)
{
  return(order(distances[i, ])[2:(k + 1)])
}

計算一個使用者安裝一個包的概率

# Thirteenth code snippet
installation.probability <- function(user, package, user.package.matrix, distances, k = 25)
{
  neighbors <- k.nearest.neighbors(package, distances, k = k)

  return(mean(sapply(neighbors, function (neighbor) {user.package.matrix[user, neighbor]})))#計算一個使用者安裝一個包的概率
}
installation.probability(1, 1, user.package.matrix, distances)
#[1] 0.76

有0.76的概率安裝包1.

按照概率最大值為使用者推薦包

most.probable.packages <- function(user, user.package.matrix, distances, k = 25)
{
  return(order(sapply(1:ncol(user.package.matrix),
               function (package)
               {
                 installation.probability(user,
                                          package,
                                          user.package.matrix,
                                          distances,
                                          k = k)
               }),
         decreasing = TRUE))
}

user <- 1

listing <- most.probable.packages(user, user.package.matrix, distances)

colnames(user.package.matrix)[listing[1:10]]
> colnames(user.package.matrix)[listing[1:10]]
 [1] "adegenet"            "AIGIS"               "ConvergenceConcepts"
 [4] "corcounts"           "DBI"                 "DSpat"              
 [7] "ecodist"             "eiPack"              "envelope"           
[10] "fBasics"  

概率最大的是adegenet包。