机器学习系列一之 kNN(k近邻算法理解和实现)

最近我也入坑机器学习了呢

推荐一本好书(机器学习实战),别问我问什么,那么好的内容,为什么不一起入坑呢。欢迎fold 代码哦,python 代码:

https://github.com/unnunique/MachineLearning/tree/master/Ch02

https://github.com/unnunique/unnunique.github.io/tree/master/ScalaDemo/src/main/scala/com/sydney/dream/classdefine

需要书籍: 请留言,留下邮箱:

转载请注明出处哦,

kNN 近邻算法

大体上机器学习分为分类和回归,有监督学习和无监督学习。kNN 属于监督学习和分类算法模块。

kNN 概念(分类算法)

在一大堆数据集合中,每一数据都存在自己的标签,即,每一数据都属于一个分类。当我们输入没有分类的新数据的时候,将新数据和数据集合中的每一数据进行比较,然后计算新数据离哪些数据最相近(把原始数据分布在坐标轴上,计算新数据和原始数据的距离)。距离计算好后,我们选取k个最相近的数据,这个就是kNN 中k 的来源。一般上k不大于20,。k个数据中出现最多的分类,即为kNN 算出来的新数据的分类。

先来看一个简单电影分类的例子。

我们以kiss 次数和打斗次数来区分是动作电影还是动作电影。

来接一波图和数据。


假如我们有一个位置的电影,问号脸?我们如何来区分他是爱情电影还是动作电影。

我们来计算他距离其他电影的距离。

例如:(18-98)**2 +(90-2)**2, 然后开方。

比如算的的距离最近的三个是Robo Slaye 3000, Kavin Longblode, Califonia Ma, 则起是动作片。(没有计算过哈哈。)

接下来我们用scala 以及python 来各自实现一波:

kNN 电影分类python 实现

写代码嘛,先来一段注释,然后请佛祖开光,这个还是很皮很开心的

'''
reated on Mar 03, 2018
kNN: k Nearest Neighbors

Input:      inX: vector to compare to existing dataset (1xN)
            dataSet: size m data set of known vectors (NxM)
            labels: data set labels (1xM vector)
            k: number of neighbors to use for comparison (should be an odd number)
            
Output:     the most popular class label

@author: lidiliang
'''
from numpy import *
import operator
## movie dataSets
def createMovieDataSets():                 # 函数定义
    dataSets = array([[3, 104], [2, 100], [1, 81], [101, 10], [99, 5], [98, 2]])  # python 自带的array 产生一个一维数组
    lables = ['A', 'A', 'A', 'B', 'B', 'B']   # 以为数组
    return group, lables                      # 数据返回

def classify0(inX, dataSet, lables, k):            # 函数定义
    dataSetSize = dataSet.shape[0]                 # dataSet 行数
    diffMatV0 = tile(inX, (dataSetSize, 1)) - dataSet  # 两个M*N 的数组相减
    diffMatV1 = diffMat**2                        # 平方
    distancesV0 = diffMatV1.sum(axis=1)             # 平方后相加
    distancesV1 = distancesV0**0.5              # 开方
    sortedDistancesIndexes = distancesV1.argsort()  # 距离排序后,返回其索引值
    classCount={}   # 字典
    for i in range(k):
        voteLable = lables[sortedDistancesIndexes[i]]   # 统计类别
        classCount[voteLable] = classCount.get(voteLable, 0) + 1
    sortedClassCount = sorted(classCount.iteritems(), key = operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]

然后秀一波实验,感觉python 写起来完全像是在写脚本,然后,一个最简单的例子,就这么华丽丽的实现了。

[root@steve-lidiliang /user/MLiA_SourceCode/Ch02]# clear
[root@steve-lidiliang /user/MLiA_SourceCode/Ch02]# python 
Python 2.7.5 (default, Nov  6 2016, 00:28:07) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-11)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import kNNMovie
>>> dataSets, labels = kNNMovie.createMovieDataSets()
>>> dataSets
array([[  3, 104],
       [  2, 100],
       [  1,  81],
       [101,  10],
       [ 99,   5],
       [ 98,   2]])
>>> labels
['A', 'A', 'A', 'B', 'B', 'B']
>>> kNNMovie.classify0([0,1000], dataSets, labels, 3)
'A'
>>>                                                                                                 

kNN 电影分类Scala 实现:

直接上代码实现吧,具体流程和上面python 大致一样,写Scala 好烧脑,哎。

package com.sydney.dream.classdefine

import util.control.Breaks._
// 首先顶一个Movie 类来实现数据的封装
class Movie(var name : String,var action : Int,var  kiss : Int,var typeMovie : Int ) {
    def this(name : String, action : Int, kiss : Int) {
        this(name, action, kiss, 0)
    }
    override def toString = s"movieName: ${name}, actinNum: ${action}, kissNum: ${kiss}, typeMovie: ${typeMovie}"
}
// 数据模型好了,那当然先再来一个类似java main 的东东了,no bb ,just do it
object Movie {
    def main(args : Array[String]): Unit = {
        // 准备数据
        val movies = Array(new Movie("两小无猜", 1, 100, 0), new Movie("泰坦尼克号",  20, 100, 0), new Movie("初恋那件小事", 0, 500, 0), 
                           new Movie("功夫足球", 100, 3, 1), new Movie("神棍",  300,  1,  1), new Movie("逃学威龙", 200, 3, 1))
        // 计算new Movie("test", 50, 50) 到各个已知movie 的距离
        val distances = getDistances(new Movie("test", 50, 50), movies)
        println("排序前的数据")
        for((k, v) <- distances) println(k, v)
        // 对计算出来的距离进行排序
        val sortedDistances = distances.toSeq.sortWith(_._2>_._2) 
        println
        println("排序后的数据:")
        for((k, v) <- sortedDistances) println(k, v)   
        // 返回分类标签
        val lable = getClassLable(movies, sortedDistances, 3)
        // 展示最终这个新电影属于哪个分类      
        println
        showLable(lable)
    }

    def showLable(lable : Int) {
        if (lable == 1) {
            println("这个片子是动作片......")
        } else {
            println("这个片子是爱情片......")
        }
    }

    // 返回分类标签
    def getClassLable(movies : Array[Movie], sortedDistances : Seq[(Int, Double)], k : Int) : Int = {
        var tmp : Map[Int, Int] = Map()
        var count = 0
        for((key, v) <- sortedDistances) {
            if (count >= k) { 
            } else {
               tmp +=  (movies(key).typeMovie -> (tmp.getOrElse(movies(key).typeMovie, 0) + 1))   
            }
            count = count + 1
        }
        //for((key, v) <- tmp) {println(key, v)} 
        val seqTmp = tmp.toSeq.sortWith(_._2 > _._2)
        println
        println("最相邻的K个数据的分类统计...")
        for((key, v) <- seqTmp) {println(key, v)}
        seqTmp(0)._1
    }
    // 获取距离列表
    def getDistances(movie : Movie, movies : Array[Movie]) : Map[Int, Double] = {
        var distances : Map[Int, Double] = Map()
        for (i <- 0 until movies.length) {
            val distance2 = Math.pow((movies(i).action - movie.action), 2) + Math.pow((movies(i).kiss - movie.kiss), 2)
            val distance = Math.sqrt(distance2)
            distances += (i -> distance)
        }
        distances
    }
}

接下来看一个相对来说,稍微复杂一点点的例子,利用kNN 优化相亲网站

先来简单过一下需求背景。

某都市杭州大龄单身妹纸小百合在某百合相亲网站上进行相亲,寻求高富帅。小百合发现自己相过的对象可以分为三类:

1,十分喜欢的人

2,一般般喜欢的人

3,不喜欢的屌丝

这三类人,可以用下面的特征数据进行衡量:

1,每年飞灰机的里程数。

2,玩游戏的次数。

3,每周吃掉的冰淇淋的公升数。

这些数据保存在txt 文件中类似如下:(数据可以在附件中获取哦,亲,)

32404 3.513921    0.9918541
27268 4.398172    0.9750242
5477 4.276823     1.174874 3

从左到右分别表示:

飞行的里程数, 玩游戏次数 冰淇淋公升数  喜欢程度(1,非常喜欢, 2, 一般般, 3,不喜欢)

来,让我们借鉴电影的例子,来实现相关的程序。

kNN python 实现约会网站分类优化

首先啦,需要从文件中读取数据。

然后把读取到的数据转化为矩阵。

函数如下:(包含kNN 近邻算法的实现)

from numpy import *
import operator
# kNN suan fa
def classify0(inX, dataSet, lables, k):
    dataSetSize = dataSet.shape[0]
    diffMat = tile(inX, (dataSetSize, 1)) - dataSet
    sqDiffMat = diffMat**2
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances**0.5
    sortedDistIndicies = distances.argssort()
    calssCount = {}
    for i in range(k):
        vateIlable = lables[sortedDistIndicies[i]]
        calssCount[vateIlable] = classCount.get(vateIlable, 1) + 1
    sortedClassCount = sorted(classCount.iteritems, key = operator.itemgetter(1), reverse = True)
    return sortedClassCount[0][0]

# Read data from files
def file2matrix(filename):
    fr = open(filename)
    arrayLines = fr.readlines()
    numberOfLines = len(arrayLines)
    returnMat = zeros((numberOfLines, 3))
    classLableVector = []
    index = 0
    for line in arrayLines:
        line = line.strip()
        listFromLine = line.split('\t')
        returnMat[index, :] = listFromLine[0:3]
        classLableVector.append(int(listFromLine[-1]))
        index += 1
    return returnMat, classLableVector

接下来考虑一个问题,如下是4组数据:


我们来计算第三组和第四组的距离:

(0-67)**2 + (20000-32000)**2 + (0.1-1.1)**2,然后开方。

Oh my good(夸张脸), 有没有发现什么。 每年飞行常客里程数对最终结果的影响远远大于其他两个特征。所以计算出来的结果,可能只与每年 飞行常客的里程数有关。

为了解决这个问题: 

我们使用一些技巧,把这些数据都转换为0到1之间的数据。

专业上叫做归一化数据:

我们利用如下的公式,把数据转换到0到1之间:

newValue = (oldValue - min)/(max - min)

在上一步,我们已经把数据从文件中读取,并且赋给了矩阵datingDataMat。接下来,我们就用上面这个公式,把这个矩阵中的数据进行归一化(皈依佛门)

# Shu zhi gui yi hua
def autoNorm(dataSet):
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals
    normDataSet = zeros(shape(dataSet))
    m = dataSet.shape[0]
    normDataSet = dataSet - tile(minVals, (m, 1))
    normDataSet = normDataSet/tile(ranges, (m, 1))
    return normDataSet, ranges, minVals

好了,到现在为止,我们改准备的都准备了,数据已有,数据已经皈依佛门,算法已有,接下来我们进行测试:

测试代码如下:

def datingClassTest():
    testRate = 0.10
    datingDataMat, datingLables = file2matrix('datingTestSet2.txt')
    normMat, ranges, minVals = autoNorm(datingDataMat)
    m = normMat.shape[0]
    numTest = int(m*testRate)
    errorCount = 0.0
    for i in range(numTest):
        classifierResult = classify0(normMat[i, :], normMat[numTest:m, :], datingLables[numTest:m], 3)
        print "the classifier came back with: %d, the real answer is : %d" % (classifierResult, datingLables[i]) 
        if (classifierResult != datingLables[i]): errorCount += 1.0
    print "the total error rate is: %f" % (errorCount/float(numTest))

测试结果:

the classifier came back with: 3, the real answer is : 3
the classifier came back with: 2, the real answer is : 3
the classifier came back with: 1, the real answer is : 1
the classifier came back with: 2, the real answer is : 2
the classifier came back with: 1, the real answer is : 1
the classifier came back with: 3, the real answer is : 3
the classifier came back with: 3, the real answer is : 3
the classifier came back with: 2, the real answer is : 2
the classifier came back with: 1, the real answer is : 1
the classifier came back with: 3, the real answer is : 1

the total error rate is: 0.050000

错误率为百分之五,效果还行。

最后,来一段python 代码模拟约会网站优化:

交互式模拟相亲网站过滤过程:

def classifyPerson():
    resultList = ['not at all', 'in small doses', 'in large doses']
    gameTime = float(raw_input(\
               "please input the time to playing video games: "))
    flyMiles = float(raw_input(\
               "please input the miles to flying: "))
    icecream = float(raw_input(\
               "please input the consumed icecream: "))
    datingDataMat, datingLables = file2matrix('datingTestSet2.txt')
    normMat, ranges, minVals = autoNorm(datingDataMat)
    inArr = array([flyMiles, gameTime, icecream])
    classifierResult = classify0((inArr-minVals)/ranges, normMat, datingLables, 3)
    print "You will probably like this person: ", resultList[classifierResult -1]

kNN 约会网站优化 Scala 实现

我比较倾向于直接进行上代码,所以nobb 了。

package com.sydney.dream.classdefine

import scala.io.Source
import scala.collection.mutable.ArrayBuffer

// 用来封装约会数据
class DatingData(var fly : Float, var playGame : Float, var iceCream : Float, var typeLike : Int) {
    // 重载构造函数
    def this(fly : Float, playGame : Float, iceCream : Float) {
        this(fly, playGame, iceCream, 0)
    }
    // 重写toString 方法
    override def toString = s"fly: ${fly}, playGame: ${playGame}, iceCream: ${iceCream}, typeLike: ${typeLike} "
}
// dating 分类程序
/**
  * 1, 加载数据
  * 2,计算一个未知person 和其他person 的距离
  * 3,把数据从大到小排序
  * 4, 按照传入的K, 统计并且返回标签
  */
object Dating {
    def main(args : Array[String]) : Unit = {
        println("starting...")
        // 参数验证:
        if(args.length != 1) {
            println("请传入datingDataSet2.txt 的路径")
        }
        // 数据处理
        // 文件路径
        val datingDataSetFilePath = args(0)
        // 获取文件对象
        val datingDataSetFile = Source.fromFile(datingDataSetFilePath)
        // 数据存放在ArrayBuffer 中
        var datingDataArrBuf = new ArrayBuffer[DatingData]()
        // 封装DatingData, 并且添加到datingDataArrBuf 中
        for(line <- datingDataSetFile.getLines) {
            val arr = line.split("\t")
            val datingData = new DatingData(arr(0).toFloat, arr(1).toFloat, arr(2).toFloat, arr(3).toInt)
            datingDataArrBuf += datingData        
        }
        println("datingDataSet 的总行数是:" + datingDataArrBuf.length)
        // 计算距离
        println("计算距离...")
        val datingData = new DatingData(12345.0f, 15.09f , 1.23f) 
        val distances = getDistance(datingData, datingDataArrBuf)
        // 对距离从大到小排序
        // 距离排序
        println("距离排序...")
        val seqDistances = sortDistances(distances)
        println("获取最终分类标签...")
        val classLable = getClassLable(datingDataArrBuf, seqDistances, 3)
        println("最终的分类标签是: " + classLable)
    }

    // 计算距离
    def getDistance(datingData : DatingData, datingDataArrBuf : ArrayBuffer[DatingData]) : Map[Int, Double] = {
        //用于结果封装返回
        var distances : Map [Int, Double] = Map()
        for(i <- 0 until datingDataArrBuf.length) {
            // 求取欧式距离的平方
            val distance2 = Math.pow((datingDataArrBuf(i).fly - datingData.fly), 2) + 
                            Math.pow((datingDataArrBuf(i).playGame - datingData.playGame), 2) + 
			    Math.pow((datingDataArrBuf(i).iceCream - datingData.iceCream), 2)
            // 求取欧氏距离
            val distance = Math.sqrt(distance2)
            // 把距离添加到返回结果中
            distances += (i -> distance)
        }
        distances
    }

    // 把计算出来的距离按照从大到小的顺序排序
    def sortDistances(distances : Map[Int, Double]) : Seq[(Int, Double)] = {
        distances.toSeq.sortWith(_._2 > _._2)
    }

    // 按照K,统计排序后的距离,返回分类标签
    def getClassLable(datingDataArrBuf : ArrayBuffer[DatingData], sortedDistances : Seq[(Int, Double)], k : Int) : Int = {
        var tmp : Map[Int, Int] = Map()
        var count = 0 
        for((key, value) <- sortedDistances) {
            if (count >= k) {} else {
                tmp += (datingDataArrBuf(key).typeLike -> (tmp.getOrElse(datingDataArrBuf(key).typeLike, 0) +1))
            }
            count = count + 1
        }
        val seqTmp = tmp.toSeq.sortWith(_._2 > _._2)
        seqTmp(0)._1
    }

    
}
写得有些累,scala 和python 都是新学的语言。暂时更新到这里。后面会继续更新,不好意思了,各位大佬们。

























猜你喜欢

转载自blog.csdn.net/eases_stone/article/details/80174970
今日推荐