KNN算法实现-基于R6类

KNN分类

  • 一个电影分类问题

Alt text

通过距离衡量位置类别的电影和已知类别的电影之间的相似度
Alt text
从表中能够看出,三部Romance类型的电影与未知类别的电影之间的距离最小(即相似度最大),所以能够有“很大把握”判定未知类别的电影为Romance类型。

KNN的思想

对于训练数据集中的每一个实例,我们都拥有他们的特征以及标签。当我们拿到了一个新的不带有标签的实例,我们比较这个新实例与每一个测试集中的实例的距离。然后我们就能得到与新实例最近的那个测试实例(称为最近邻)以及它的标签。我们从测试集中计算出与新实例最接近的k个实例,这也就是KNN中K的含义。最后,我们在这k个测试实例中采取多数表决的方法,选出k个中实例个数最多的那一类作为新实例的标签。

编写KNN类

KNN <- R6Class("KNN",
  public = list(
    CreateDataSet = function(){
      group <- matrix(c(1.0,1.1,1.0,1.0,0,0,0,0.1),ncol=2,byrow=T)
      labels <- rep(c('A','B'),each=2)
      dt <- as.data.frame(group)
      dt$labels <- labels
      dt
    }
  )
)
knn <- KNN$new()
known <- knn$CreateDataSet()
plot(V2~V1,data=known,xlim=c(-0.2,1.2),ylim=c(-0.2,1.2),xlab="",ylab="")
text(known$V1,known$V2,known$labels,adj=c(1.5,0.5))

Alt text

为KNN动态增加一个函数CalDistance,计算未知数据与已知数据的距离(衡量相似度)。由于该函数无需提供外部使用,所以可以封装成为私有成员。

library(magrittr)#管道函数的包(管道操作符是通过一种流程式的书写方式来表达一系列依次进行的运算操作,最重要的就是%>%)管道操作符的实质就是将左边表达式(前一步运算)返回的结果默认地传给后续要执行的函数
CalDistance <- function(item1,item2)
{
  (item1-item2)**2 %>% sum %>% sqrt
}
KNN$set("private","CalDistance",CalDistance,overwrite = T)

为KNN动态增加一个函数Classify0,用来对未知的数据进行分类。由于该函数需要提供外部使用,所以应该封装成为公有成员。

#known是一个数据框,数据框的每一行为一个样本实例,
#最后一列为实例的类别,前面的列为实例的特征(为数值型变量)。
#unknown是一个数据框,包含着一个未知类别的实例的特征(数值型变量)
#k指明的是最近邻个数(是对未知类别数据进行投票的最近邻个数)
Classify0 <- function(known,unknown,k)
{
  #此处应该有很多关于程序健壮性的语句 
  mat <- as.matrix(known[,-ncol(known)])

  diffMat <- mat - matrix(rep(unlist(unknown),nrow(mat)),ncol=ncol(mat),byrow=T)
  distances <- diffMat**2 %>% rowSums() %>% sqrt
  sortedDistIndicies <- order(distances)
  classCount <- table(known[sortedDistIndicies[1:k],ncol(known)])
  classCount <- sort(classCount,decreasing = T)
  return(names(classCount)[1])
}

KNN$set("public","Classify0",Classify0,overwrite = T)
unknown <- data.frame(V1=0,V2=0)
knn <- KNN$new()
knn$Classify0(known,unknown,3)

一个例子:KNN用于提升相亲网站的匹配成功度

问题描述:Hellen使用一些在线约会网站来寻找不同的人耍朋友。她发现有三种人:
- 她不喜欢的;
- 有一点点喜欢的;
- 挺喜欢的。
虽然发现了这三种人但是她不晓得怎么分类。
Hellen收集了一些数据并且存储在文件datingTestSet.txt里面。每一行代表一个实例,并且Hellen意识到有这些特征她比较关注:
- 每年获得的飞行里程数;
- 玩游戏花费的时间所占的比例;
- 每周消费的冰淇淋升数。

Alt text
Hellen觉得这些数据在选人的时候挺有用的,但是怎么用呢?
1. 准备工作:读取文档
为KNN类动态增加一个读取文本文档数据的方法(考虑为公有成员)

file2Dataframe <- function(filename,sep="\t",header=F)
{
  tmp <- read.delim(filename,header=header,sep=sep)
}
KNN$set("public","file2Dataframe",file2Dataframe,overwrite = T)
knn <- KNN$new()
known <- knn$file2Dataframe('datingTestSet.txt')
names(known) <- c("ffm","vgame","icecream","label")

Alt text

  1. 分析数据:作个散点图看看
plot(known[,1:3],col = as.factor(known$label))

Alt text

  • 标准化数据:三个特征的量纲不统一将会导致量纲较大的特征在距离计算中的作用过大,而忽略了其余两个特征的情况,因此需要标准化数据。不仅仅在KNN算法中,在其他很多机器学习算法中,标准化用的都非常频繁,通常情况下都是将数值型数据标准化到(0,1)或者是(-1,1)。
    将数据归一化到(0,1),可以用到这个公式:
    n e w V a l u e = o l d V a l u e m i n m a x m i n
autoNorm <- function(mat)
{
  normCol <- function(matCol){
    minVals <- min(matCol)
    maxVals <- max(matCol)
    return((matCol-minVals)/(maxVals-minVals))
  }
  return(apply(mat,2,FUN=normCol))
}
KNN$set("public","autoNorm",autoNorm,overwrite = T)
  • 测试:测试分类器是机器学习评估一个算法准确性的共同任务。一种方法是将数据集中90%的数据作为测试集来训练这个分类器。然后用剩下的10%数据来测试分类器,看分类的准确率如何。
knn <- KNN$new()
hoRation <- 0.10#划分训练集和测试集的比率
m <- dim(known)[1]
numTestVecs <- ceiling(m*hoRation)#向上取整
unknown <- known[1:numTestVecs,-ncol(known)]#选取1-100行作为测试集,并将标签隐去
rightLabel <- known$label[1:numTestVecs]
known[,1:(ncol(known)-1)] <- knn$autoNorm(known[,1:(ncol(known)-1)])
unknown <- as.data.frame(knn$autoNorm(unknown))
predictLabel <- apply(unknown,1,FUN=knn$Classify0,known=known,k=3)
table(rightLabel == predictLabel)
## FALSE  TRUE 
##     9    91

测试集的分类正确率有91%,还阔以!

另一个例子:手写识别

KNN方法(以及其他机器学习方法)中,数据(无论是已知的数据,还是待判别的未知数据)通常都是按行存放的,一行代表着一个实例。对于图像而言,其对应的数据是矩阵!!那么该如何处理,才能让KNN等方法能够去识别图像呢?
将图像对应的矩阵转换成一个向量即可!
Alt text

为了简单起见,这里构造的系统只能识别数字0-9。如上图所见,每一个手写数字都被处理成具有相同的色彩和大小:宽高是32像素*32像素的黑白图像,然后再转化成文本格式。

  1. 为了使用KNN分类器,我们必须将图像格式化处理为一个向量。我们将把32*32的二进制图像矩阵转换为1*1024的向量。编写img2vector函数,然后动态添加到KNN类中即可。
img2vector <- function(filename)
{
  f <- file(filename)
  cont <- readLines(f)
  picmat <- matrix('0',nrow=32,ncol=32)
  for(i in 1:length(cont)){
    picmat[i,] <- stringr::str_split(cont[i],'')[[1]]
  }
  close(f)
  return(as.numeric(c(t(picmat))))
}
KNN$set("public","img2vector",img2vector,overwrite = T)

knn <- KNN$new()
testVector <- knn$img2vector("C:\Users\Administrator\Desktop\赵品勇课件\02\data\trainingDigits\0_0.txt")
testVector[1:32]
  1. 训练算法:
knn <- KNN$new()
baseDir <- "F:/MyStudy/R/Machine Learning in Action/machinelearninginaction/Ch02/digits/trainingDigits/"
knownPic <- list.files(baseDir)
knownmat <- matrix(0,nrow=length(knownPic),ncol=32^2)
for(i in 1:nrow(knownmat)){
  knownmat[i,] <- knn$img2vector(paste0(baseDir,knownPic[i]))
}
known <- as.data.frame(knownmat)
names(known) <- paste0("V",1:ncol(knownmat))
label <- stringr::str_sub(knownPic,1,1)
known$label <- label
baseDir <- "F:/MyStudy/R/Machine Learning in Action/machinelearninginaction/Ch02/digits/testDigits/"
unknownPic <- list.files(baseDir)
unknownmat <- matrix(0,nrow=length(unknownPic),ncol=32^2)
for(i in 1:nrow(unknownmat)){
  unknownmat[i,] <- knn$img2vector(paste0(baseDir,unknownPic[i]))
}
unknown <- as.data.frame(unknownmat)
names(unknown) <- paste0("V",1:ncol(unknownmat))
rightLabel <- stringr::str_sub(unknownPic,1,1)
predictLabel <- apply(unknown,1,FUN=knn$Classify0,known=known,k=3)
table(rightLabel == predictLabel)
## FALSE  TRUE 
##    12   934

猜你喜欢

转载自blog.csdn.net/weixin_40514680/article/details/80391072