(机器学习)使用k-邻近算法实现的两个实例

目录

一.判定未知电影的类型

2 python代码实现 

2.1运行结果:

 二.改进约会网站的配对效果

1. 实例解释

2.数据处理

 3.实现代码:

 4. 运行结果:

总结 


一.判定未知电影的类型

1. 实例解释

以电影分类为例子,使用k-近邻算法分类爱情片和动作片。有人曾经统计过很多电影的打斗镜头和接吻镜头,下图显示了6部电影的打斗和接吻镜头数。 假如有一部未看过的电影,如何确定它是爱情片还是动作片呢? 

(机器学习)使用k-邻近算法实现的两个实例_第1张图片

①首先需要统计这个未知电影存在多少个打斗镜头和接吻镜头,下图中问号位置是该未知电影出现的镜头数 

(机器学习)使用k-邻近算法实现的两个实例_第2张图片

②之后计算未知电影与样本集中其他电影的距离(相似度),具体算法先忽略,结果如下表所示:

(机器学习)使用k-邻近算法实现的两个实例_第3张图片

③将相似度列表排序,选出前k个最相似的样本。此处我们假设k=3,将上表中的相似度进行排序后前3分别是:He’s Not Really into Dudes,Beautiful Woman,California Man。
④统计最相似样本的分类。此处很容易知道这3个样本均为爱情片。
⑤将分类最多的类别作为未知电影的分类。那么我们就得出结论,未知电影属于爱情片。

2 python代码实现 

from numpy import *
import operator

def createDataSet():
    '创建数据集'
    group=array([[3, 104], [2, 100], [1, 81], [101, 10], [99, 5], [98, 2]])
    labels= ['爱情片', '爱情片', '爱情片', '动作片', '动作片', '动作片']
    return group, labels

def classify(inX, dataSet, labels, k):
    # 获取维度
    dataSetSize=dataSet.shape[0]   # 训练数据集数量
    print("训练数据集数量:")
    print(dataSetSize)
    diffMat=tile(inX, (dataSetSize, 1))-dataSet  # 测试样本的各维度的差值
    sqDiffMat=diffMat**2  # 平方计算
    sqDistance=sqDiffMat.sum(axis=1)  # 输出每行的值
    distances=sqDistance**0.5   # 开方计算
    print("与未知电影的距离:")
    print(distances)
    sortedDistances=distances.argsort()  # 返回distances中元素从小到大排序后的索引值
    classCount={}
    for i in range(k):
        voteIlabel=labels[sortedDistances[i]]
        classCount[voteIlabel]=classCount.get(voteIlabel, 0)+1
    sortedClassCount=sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)

    return sortedClassCount[0][0]

if __name__ == '__main__':
    # 创建数据集
    group, labels = createDataSet()
    # 测试集
    test = [18, 90]
    # kNN分类
    test_class = classify(test, group, labels, 3)
    # 打印分类结果
    print("判定未知电影类型:")
    print(test_class)

2.1运行结果:

(机器学习)使用k-邻近算法实现的两个实例_第4张图片

 二.改进约会网站的配对效果

1. 实例解释

(机器学习)使用k-邻近算法实现的两个实例_第5张图片

2.数据处理

(机器学习)使用k-邻近算法实现的两个实例_第6张图片

 

datingTestSet.txt 文件中有1000行的约会数据,样本主要包括以下3种特征:

  • 每年获得的飞行常客里程数
  • 玩视频游戏所耗时间百分比
  • 每周消费的冰淇淋公升数

将上述特征数据输人到分类器之前,必须将待处理数据的格式改变为分类器可以接受的格式 。在kNN.py中创建名为 file2matrix 的函数,以此来处理输人格式问题。该函数的输人为文件名字符串,输出为训练样本矩阵和类标签向量。autoNorm 为数值归一化函数,将任意取值范围的特征值转化为0到1区间内的值。最后,datingClassTest 函数是测试代码。 

使用Matplotlib创建散点图(横坐标x是玩视频游戏所耗时间百分比;纵坐标y是每周消费的冰激凌公升数):

 (机器学习)使用k-邻近算法实现的两个实例_第7张图片

 散点图实现代码:

from numpy import *
import matplotlib.pyplot as plt
def classify0(inX , dataSet ,labels,k):
    dataSetSize = dataSet.shape[0] #行数
    diffMat = tile(inX,(dataSetSize,1)) - dataSet # tile(inX,(dataSetSize,1)) 生成 dataSetSize 行 1 列的 元素为 inX的 数组
    sqDiffMat = diffMat ** 2 #  ** 为 ^
    sqDistances = sqDiffMat.sum(axis=1) # axis=0是按列求和 axis=1 是按行求和
    distance = sqDistances ** 0.5
    sortedDisInd = distance.argsort()# argsort,属于numpy中的函数 返回排序后元素在原对象中的下标
    classCount = {}
    for i in range(k):
        votelabel = labels[sortedDisInd[i]]
        classCount[votelabel] = classCount.get(votelabel,0) + 1 #dict.get(key, default=None) key:key在字典中查找。 default:在key不存在的情况下返回值None。
    sortedClassCount = sorted(classCount.iteritems(),key = operator.itemgetter(1),reverse =True)
    return sortedClassCount[0][0]

def file2matrix(filename):
    fr = open(filename)
    arrayOLines = fr.readlines()  #注意需要加s
    numberOfLines = len(arrayOLines)
    returnMat = zeros((numberOfLines,3))
    classLabelVector = []
    index = 0
    for line in arrayOLines:
        line = line.strip()
        listFormLine = line.split('\t')
        for x in range(0,3):
            returnMat[index,x] = float(listFormLine[x])
        classLabelVector.append(int(listFormLine[-1])) # -1 为最后一个元素
        index += 1
    return returnMat,classLabelVector


datingDataMat,datingLabels = file2matrix('datingTestSet2.txt')
fig = plt.figure() #figure创建一个绘图对象
ax = fig.add_subplot(111)# 若参数为349,意思是:将画布分割成3行4列,图像画在从左到右从上到下的第9块,

ax.scatter(datingDataMat[:,1],datingDataMat[:,2],15.0*array(datingLabels),marker=(8,0),alpha=0.5)
plt.show()

 3.实现代码:

vimport numpy as np
import operator

def classify0(inX, dataSet, labels, k):
    # numpy函数shape[0]返回dataSet的行数
    dataSetSize = dataSet.shape[0]
    # 在列向量方向上重复inX共1次(横向),行向量方向上重复inX共dataSetSize次(纵向)
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    # 二维特征相减后平方
    sqDiffMat = diffMat ** 2
    # sum()所有元素相加,sum(0)列相加,sum(1)行相加
    sqDistance = sqDiffMat.sum(axis=1)
    # 开方,计算出距离
    distances = sqDistance ** 0.5
    # 返回distances中元素从小到大排序后的索引值
    sortedDistIndices = distances.argsort()
    # 定一个记录类别次数的字典
    classCount = {}
    for i in range(k):
        # 取出前k个元素的类别
        voteIlabel = labels[sortedDistIndices[i]]
        # dict.get(key,default=None),字典的get()方法,返回指定键的值,如果值不在字典中返回默认值。
        # 计算类别次数
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
        # python3中用items()替换python2中的iteritems()
        # key=operator.itemgetter(1)根据字典的值进行排序
        # key=operator.itemgetter(0)根据字典的键进行排序
        # reverse降序排序字典
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    # 返回次数最多的类别,即所要分类的类别
    return sortedClassCount[0][0]

def file2matrix(filename):
    # 打开文件
    fr = open(filename)
    # 读取文件所有内容
    arrayOLines = fr.readlines()
    # 得到文件行数
    numberOfLines = len(arrayOLines)
    # 返回的NumPy矩阵,解析完成的数据:numberOfLines行,3列
    returnMat = np.zeros((numberOfLines, 3))
    # 返回的分类标签向量
    classLabelVector = []
    # 行的索引值
    index = 0
    for line in arrayOLines:
        # s.strip(rm),当rm空时,默认删除空白符(包括'\n','\r','\t',' ')
        line = line.strip()
        # 使用s.split(str="",num=string,cout(str))将字符串根据'\t'分隔符进行切片。
        listFormLine = line.split('\t')
        # 将数据前三列提取出来,存放到returnMat的NumPy矩阵中,也就是特征矩阵
        returnMat[index, :] = listFormLine[0:3]
        # 根据文本中标记的喜欢的程度进行分类,1代表不喜欢,2代表魅力一般,3代表极具魅力
        if listFormLine[-1] == '1':
            classLabelVector.append(1)
        elif listFormLine[-1] == '2':
            classLabelVector.append(2)
        elif listFormLine[-1] == '3':
            classLabelVector.append(3)
        index += 1

    return returnMat, classLabelVector

def autoNorm(dataSet):
    # 获得数据的最小值
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    # 最大值和最小值的范围
    ranges = maxVals - minVals
    # shape(dataSet)返回dataSet的矩阵行列数
    normDataSet = np.zeros(np.shape(dataSet))
    # 返回dataSet的行数
    m = dataSet.shape[0]
    # 原始值减去最小值
    normDataSet = dataSet - np.tile(minVals, (m, 1))
    # 除以最大和最小值的差,得到归一化数据
    normDataSet = normDataSet / np.tile(ranges, (m, 1))
    # 返回归一化数据结果,数据范围,最小值
    return normDataSet, ranges, minVals


def datingClassTest():
    # 打开的文件名
    filename = "datingTestSet2.txt"
    # 打开并处理数据
    datingDataMat, datingLabels = file2matrix(filename)
    # 取所有数据的百分之十
    hoRatio = 0.10
    # 数据归一化,返回归一化后的矩阵,数据范围,数据最小值
    normMat, ranges, minVals = autoNorm(datingDataMat)
    # 获得normMat的行数
    m = normMat.shape[0]
    # 百分之十的测试数据的个数
    numTestVecs = int(m * hoRatio)
    # 分类错误计数
    errorCount = 0.0
    for i in range(numTestVecs):
        # 前numTestVecs个数据作为测试集,后m-numTestVecs个数据作为训练集
        classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 4)
        print("分类结果:%d\t真实类别:%d" % (classifierResult, datingLabels[i]))
        if classifierResult != datingLabels[i]:
            errorCount += 1.0
        print("错误率:%f%%" % (errorCount / float(numTestVecs) * 100))

def classifyperson():
    resultList=['讨厌', '喜欢', '非常喜欢']
    percentTats=float(input("玩视频游戏所占时间百分比?"))
    ffMiles=float(input("每年获得的飞行常客里程数?"))
    iceCream=float(input("每周消费的冰淇淋公升数?"))
    datingDatMat,datingLabels=file2matrix('datingTestSet289.txt')
    normMat,ranges,minVals=autoNorm(datingDatMat)
    inArr=np.array([ffMiles,percentTats,iceCream])
    classifierResult=classify0((inArr-minVals)/ranges,normMat,datingLabels,3)
    print("你喜欢这个人的程度可能是:",resultList[classifierResult-1])


if __name__ == '__main__':
    classifyperson()

 4. 运行结果:

(机器学习)使用k-邻近算法实现的两个实例_第8张图片

(机器学习)使用k-邻近算法实现的两个实例_第9张图片 

总结 

近似误差,更关注于“训练”。最小化近似误差,即为使估计值尽量接近真实值,但是这个接近只是对训练样本(当前问题)而言,模型本身并不是最接近真实分布。换一组样本,可能就不近似了。这种只管眼前不顾未来预测的行为,即为过拟合。估计误差,更关注于“测试”、“泛化”。最小化估计误差,即为使估计系数尽量接近真实系数,但是此时对训练样本(当前问题)得到的估计值不一定是最接近真实值的估计值;但是对模型本身来说,它能适应更多的问题(测试样本)也就是泛化能力更强

 K的取值

对查询点标签影响显著(效果拔群)。k值小的时候 近似误差小,估计误差大。 k值大 近似误差大,估计误差小。

如果选择较小的 k 值,就相当于用较小的邻域中的训练实例进行预测,“学习”的近似误差(approximation error)会减小,只有与输入实例较近的(相似的)训练实例才会对预测结果起作用。但缺点是“学习”的估计误差(estimation error)会增大,预测结果会对近邻的实例点非常敏感。如果邻近的实例点恰巧是噪声,预测就会出错。换句话说,k 值的减小就意味着整体模型变得复杂,容易发生过拟合。

如果选择较大的 k 值,就相当于用较大的邻域中的训练实例进行预测。其优点是可以减少学习的估计误差。但缺点是学习的近似误差会增大。这时与输入实例较远的(不相似的)训练实例也会对预测起作用,使预测发生错误。 k 值的增大就意味着整体的模型变得简单。

太大太小都不太好,可以用交叉验证(cross validation)来选取适合的k值。

近似误差和估计误差,请看这里:https://www.zhihu.com/question/60793482

距离度量 Metric/Distance Measure

距离度量 通常为 欧式距离(Euclidean distance),还可以是 Minkowski 距离 或者 曼哈顿距离。也可以是 地理空间中的一些距离公式。(更多细节可以参看 sklearn 中 valid_metric 部分)

分类决策 (decision rule)

分类决策 在 分类问题中 通常为通过少数服从多数 来选取票数最多的标签,在回归问题中通常为 K个最邻点的标签的平均值。

KNN算法是很基本的机器学习算法了,它非常容易学习,在维度很高的时候也有很好的分类效率,因此运用也很广泛,这里总结下KNN的优缺点。

KNN的主要优点有:

1) 理论成熟,思想简单,既可以用来做分类也可以用来做回归

2) 可用于非线性分类

3) 训练时间复杂度比支持向量机之类的算法低,仅为O(n)

4) 和朴素贝叶斯之类的算法比,对数据没有假设,准确度高,对异常点不敏感

5) 由于KNN方法主要靠周围有限的邻近的样本,而不是靠判别类域的方法来确定所属类别的,因此对于类域的交叉或重叠较多的待分样本集来说,KNN方法较其他方法更为适合

6)该算法比较适用于样本容量比较大的类域的自动分类,而那些样本容量较小的类域采用这种算法比较容易产生误分

KNN的主要缺点有:

1)计算量大,尤其是特征数非常多的时候

2)样本不平衡的时候,对稀有类别的预测准确率低

3)KD树,球树之类的模型建立需要大量的内存

4)使用懒散学习方法,基本上不学习,导致预测时速度比起逻辑回归之类的算法慢

5)相比决策树模型,KNN模型可解释性不强

你可能感兴趣的:(算法,python,机器学习)