机器学习实战之 -- kNN
k-近邻算法
L p ( x i , x j ) = ( ∑ l = 1 n ∣ x i ( l ) − x j ( l ) ∣ p ) 1 / p L_p(x_i,x_j) = (\sum_{l=1}^{n} |x_i^{(l)} - x_j^{(l)}|^p)^{1/p} Lp(xi,xj)=(l=1∑n∣xi(l)−xj(l)∣p)1/p
- l l l:维数。
一、工作原理
存在一个样本数据集合(训练样本集),并且样本集中每个数据都存在标签,即我们知道样本集中每一行数据与所属分类对应的关系。输入没有标签的新数数据后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本集中特征最相似数据的分类标签。
-
优点:精度高,对异常值不敏感、无数据输入假定。
-
缺点:计算复杂度高、空间复杂度高。
-
适用范围:数值型和标称型。
一组样本数据:样本行 - 分类标签。 新数据:没有标签,求差值提取标签。
二、核心代码
1.创建样本数据集和标签
import numpy as np
def createDataSet():
'''
Desc:
创建样本数据集和标签列
Return:
group -- 数据集
labels -- 标签列
'''
group = np.array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
labels = ['A','A','B','B']
return group, labels
group = createDataSet()[0]
labels = createDataSet()[1]
print(group)
print(labels)
[[1. 1.1]
[1. 1. ]
[0. 0. ]
[0. 0.1]]
['A', 'A', 'B', 'B']
2.利用kNN算法返回频率最高的元素标签
- np.tile(A, n) :数组A重复n次,构成一个新的数组。
- axis = 1 :按行计算;axis = 0:按列计算。
- classCount.get(voteIlabel, 0):在这里的 ‘0’ 是自己设置的,当在classCount里找不到’voteIlabel’时返回 ‘0’。
import numpy as np
import operator
def classify0(inx, dataSet, labebls, k):
'''
Note:dataSet与labels的行数必须相同
Arguments:
inx -- 待分类向量(测试集)
dataSet -- 训练数据集
labels -- 训练集对应分类
k -- 选取最邻近数据的个数
Return:
sortedClassCount[0][0] -- 返回频率最高的元素标签
'''
### 1.计算待分类向量与训练数据集中每一个样本的欧氏距离
# 训练集的行数(样本个数)
dataSetSize = dataSet.shape[0]
# 待分类向量和训练集中每个样本对应的特征值的差值,保持行数相同
diffMat = np.tile(inx,(dataSetSize, 1)) - dataSet
# 特征值差值的平方
sqDiffMat = diffMat ** 2
# 特征值差值的平方和
sqDistances = sqDiffMat.sum(axis = 1)
# 待分类向量与训练集中每个样本的欧式距离
distances = sqDistances ** 0.5
# 排序后,返回按升序排列的数据的索引
sortedDistIndicies = distances.argsort()
# 声明字典
classCount = {}
### 2.选距离最近的k个值
for i in range(k):# 筛选k个最近的值
# k个最近距离对应的类别
voteIlabel = labebls[sortedDistIndicies[i]]
# 统计不同类别的个数,
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
### 3.排序返回频率最高的类别
# key=operator.itemgetter(1)根据字典的值进行排序
# key=operator.itemgetter(0)根据字典的键进行排序
sortedClassCount = sorted(classCount.items(),key = operator.itemgetter(1),reverse=True)
print(sortedClassCount,'--选取的k个类别--')
return sortedClassCount[0][0]
classify0([0.,0.],group,labels,3)
[('B', 2), ('A', 1)] --类别--
'B'
三、示例
1.准备数据:从文本中解析数据,输出训练样本矩阵和类标签向量。
def file2matrix(filename):
'''
Desc:
将文本记录转换为Numpy的解析程序
Arguments:
filename -- 文本路径
Return:
returnMat -- 训练样本矩阵
classLabelVector -- 类标签向量
'''
# 读取文件返回file对象
fr = open(filename)
# 按行读取返回数据行列表
arrayOLines = fr.readlines()
# 文件行数
numberOfLines = len(arrayOLines)
# 初始化样本矩阵
returnMat = np.zeros((numberOfLines,3))
# 分类标签向量
classLabelVector = []
# 行指针
index = 0
# 遍历每一个样本
for line in arrayOLines:
# 去掉回车符
line = line.strip()
# 去掉分隔符
listFromLine = line.split('\t')
# 将数据前三列提取出来,存放到returnMat的样本矩阵中
returnMat[index,:] = listFromLine[0:3]
# 提取每个样本中最后一列的类别标签
# 注意类别的数据结构
classLabelVector.append(int(listFromLine[-1]))
# 指向下一行
index += 1
return returnMat, classLabelVector
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
print(datingDataMat)
print(datingLabels[0:10])
[[4.0920000e+04 8.3269760e+00 9.5395200e-01]
[1.4488000e+04 7.1534690e+00 1.6739040e+00]
[2.6052000e+04 1.4418710e+00 8.0512400e-01]
...
[2.6575000e+04 1.0650102e+01 8.6662700e-01]
[4.8111000e+04 9.1345280e+00 7.2804500e-01]
[4.3757000e+04 7.8826010e+00 1.3324460e+00]]
[3, 2, 1, 1, 1, 1, 3, 3, 1, 3]
2.分析数据:使用Matplotlib绘制散点图
import matplotlib.pyplot as plt
%matplotlib inline
# 创建图像对象
fig = plt.figure()
# 将画布分为1行1列的格子,图像画在第一个格子上
ax = fig.add_subplot(111)
# 指定散点图中x,y轴的数据,点的大小,点的颜色
ax.scatter(datingDataMat[:,0],datingDataMat[:,1],
10.0*np.array(datingLabels),35.0*np.array(datingLabels))
plt.show()
3.归一化数值
在处理这种不同取值范围的特征值时,我们通常采用的方法是将数值归一化,如将取值范围处理为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
)
newValue = \frac{(oldValue-min)}{(max - min)}
newValue=(max−min)(oldValue−min)
min(0):返回该矩阵中每一列的最小值
min(1):返回该矩阵中每一行的最小值
def autoNorm(dataSet):
'''
Desc:
将任意取值范围的特征值转化为0到1区间内的值
Arguments:
dataSet -- 训练数据集
Return:
normDataSet -- 归一化后的训练集
ranges -- 每个特征(列)的极差
minVals -- 每个特征(列)的最小值
'''
# 数据中每个特征的最小值和最大值
minVals = dataSet.min(0)
maxVals = dataSet.max(0)
# 每个(列)特征的极差(max−min)
ranges = maxVals - minVals
# shape(dataSet):返回矩阵的行列数,构造相同行列的零矩阵
normDataSet = np.zeros(np.shape(dataSet))
# 获得dataSet的行数
m = dataSet.shape[0]
# oldValue−min
normDataSet = dataSet - np.tile(minVals, (m, 1))
# (oldValue−min)/(max−min)
normDataSet = normDataSet/np.tile(ranges, (m, 1))
return normDataSet, ranges, minVals
norm, ranges, minVals = autoNorm(datingDataMat)
norm,ranges,minVals
(array([[0.44832535, 0.39805139, 0.56233353],
[0.15873259, 0.34195467, 0.98724416],
[0.28542943, 0.06892523, 0.47449629],
...,
[0.29115949, 0.50910294, 0.51079493],
[0.52711097, 0.43665451, 0.4290048 ],
[0.47940793, 0.3768091 , 0.78571804]]),
array([9.1273000e+04, 2.0919349e+01, 1.6943610e+00]),
array([0. , 0. , 0.001156]))
4.测试算法
def datingClassTest():
"""
Desc:
对约会网站的测试方法
"""
# 设置测试数据的比例(训练数据集比例为1-hoRation)
hoRatio = 0.1
# 加载数据
datingDataMat,datingLabels = file2matrix('datingTestSet2.txt')
# 归一化数据
norm, ranges, minVals = autoNorm(datingDataMat)
# 样本个(行)数
m = norm.shape[0]
# 设置测试样本数量
numTestVecs = int(m * hoRatio)
# 记错器
errorCount = 0.0
for i in range(numTestVecs):
classifierResult = classify0(norm[i,:], norm[numTestVecs:m, :],datingLabels[numTestVecs:m], 3)
print('the classifier came back with: %d, the real answer is: %d' % (classifierResult, datingLabels[i]))
if classifierResult !=datingLabels[i]:
errorCount += 1.0
# 错误率
print("the total error rate is: %f" % (errorCount / float(numTestVecs)))
# 错误个数
print('errorCount:{}'.format(errorCount))
5.使用算法
def classifyPerson():
#输出结果
resultList = ['讨厌','有些喜欢','非常喜欢']
#三维特征用户输入
precentTats = float(input("玩视频游戏所耗时间百分比:"))
ffMiles = float(input("每年获得的飞行常客里程数:"))
iceCream = float(input("每周消费的冰激淋公升数:"))
# 打开文件处理数据
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
# 归一化
normMat, ranges, minVals = autoNorm(datingDataMat)
# 生成NumPy数组,测试集
inArr = np.array([precentTats, ffMiles, iceCream])
#测试集归一化
norminArr = (inArr - minVals) / ranges
classifierResult = classify0(norminArr, normMat,datingLabels, 3 )
print(classifierResult)
print("你可能%s这个人" % (resultList[classifierResult - 1]))
classifyPerson()
玩视频游戏所耗时间百分比:10
每年获得的飞行常客里程数:10000
每周消费的冰激淋公升数:0.5
1
你可能讨厌这个人