核心思想:如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。(近朱者赤,近墨者黑)
有监督学习、多分类算法
(1)计算已知类别数据集中的样本与当前样本的距离。
(2)按顺序递增排序。
(3)选取距离最小的k个点。
(4)统计这k个样本类别出现的频率最高的类别。
(5)出现的频率最高的类别即为预测分类。
优
简单有效
重新训练代价低
适合类域交叉样本
适合大样本自动分类(不适合类域较小的样本)
缺
惰性学习
类别评分不是规格化
输出可解释性不强
对不均衡的样本不擅长(一般若达到4:1,就说不均衡)
计算量较大
在m维空间中两个点之间的真实距离
欧氏距离体现数值上的绝对差异
缺点:它将样品的不同量纲之间的差别等同看待
闵可夫斯基距离不是一种距离,而是一组距离的定义,是对多个距离度量公式的概括性的表述。
p=1时为曼哈顿距离
p=2时为欧氏距离
p=无穷大时为切比雪夫距离
注意:
(1)闵氏距离与特征参数的量纲(单位)有关,有不同量纲的特征参数的闵氏距离常常是无意义的。
(2)闵氏距离没有考虑特征参数间的相关性,而马哈拉诺比斯距离解决了这个问题。
解决了欧氏距离将样品的不同量纲之间的差别等同看待的缺点
Sk:当前分量标准差
eg:求(0,5)(1,6)(2,7)中(0,5)(1,6)的距离,Sk1=0,1,2标准差,Sk2=5,6,7标准差。
ps:若将1/Sk^2看作一个权重,也可称为加权欧氏距离。
余弦距离体现方向上的相对差异。将数据映射为高维度的向量,余弦值接近1,夹角趋于0,表明两个向量越相似,余弦值接近于0,夹角趋于90度,表明两个向量越不相似。
两个不同字符串,将其中一个变为另一个所需要做的最小字符替换数。
对两个字符串进行异或运算,并统计结果为1的个数,那么这个数就是汉明距离。
ps:汉明重量是字符串相对于同样长度的零字符串的汉明距离,也就是说,它是字符串中非零的元素个数:对于二进制字符串来说,就是 1 的个数,所以 11101 的汉明重量是 4。
汉明距离更多的用于信号处理,表明一个信号变成另一个信号需要的最小操作。
杰卡德距离(Jaccard Distance) 是用来衡量两个集合差异性的一种指标,它是杰卡德 相似系数的 补集,被定义为1减去Jaccard相似系数。而杰卡德相似系数(Jaccard similarity coefficient),也称杰卡德指数(Jaccard Index),是用来衡量两个集合相似度的一种指标。
表示点与一个分布之间的距离。它是一种有效的计算两个未知样本集的相似度的方法。与欧氏距离不同的是,它考虑到各种特性之间的联系(例如:一条关于身高的信息会带来一条关于体重的信息,因为两者是有关联的),并且是尺度无关的(scale-invariant),即独立于测量尺度。
马氏距离也可以定义为两个服从同一分布并且其协方差矩阵为Σ的随机变量之间的差异程度。
如果协方差矩阵为单位矩阵,那么马氏距离就简化为欧氏距离,如果协方差矩阵为对角阵,则其也可称为正规化的欧氏距离。
μ:均值
Σ:协方差矩阵
协方差矩阵:
设
为n维随机变量,称矩阵
为n维随机变量 的协方差矩阵(covariance matrix),也记为D(X)
,其中
为 X的分量Xi 和 Xj的协方差(设它们都存在)。
例如,二维随机变量 的协方差矩阵为
所以协方差矩阵为对称非负定矩阵。
针对Python 编程语言的免费软件机器学习库 。它具有各种分类,回归和聚类算法,包括支持向量机,随机森林,梯度提升,k均值和DBSCAN,并且旨在与Python数值科学库NumPy和SciPy联合使用。(依赖NumPy和SciPy)
. 分类、聚类、回归
. 特征工程
. 模型选择、调优
sklearn中文社区
sklearn官方文档中文版
在实际应用中,K值一般取一个比较小的数值,例如采用交叉验证法(简单来说,就是一部分样本做训练集,一部分做测试集)来选择最优的K值。
过大:减小估计误差,受到样本均衡的问题,模型简单,增大近似误差。
过小:减小近似误差,容易受异常点的影响,模型复杂,过拟合,增大估计误差。
误差分析:
k近邻法最简单的实现是线性扫描(穷举搜索),即要计算输入实例与每一个训练实例的距离。计算并存储好以后,再查找K近邻。当训练集很大时,计算非常耗时。
为了避免每次都重新计算一遍距离,算法会把距离信息保存在一棵树里,这样在计算之前从树里查询距离信息,尽量避免重新计算。其基本原理是,如果A和B距离很远,B和C距离很近,那么A和C的距离也很远。有了这个信息,就可以在合适的时候跳过距离远的点。(减少距离值的计算)
构造kd树相当于不断地用垂直于坐标轴的超平面将K维空间切分,构成一系列的K维超矩形区域。kd树的每个结点对应于一个k维超矩形区域。利用kd树可以省去对大部分数据点的搜索,从而减少搜索的计算量。
其原理有点类似于“二分查找”:给出一组数据:[9 1 4 7 2 5 0 3 8],要查找8。如果挨个查找(线性扫描),那么将会把数据集都遍历一遍。而如果排一下序那数据集就变成了:[0 1 2 3 4 5 6 7 8 9],按前一种方式我们进行了很多没有必要的查找,现在如果我们以5为分界点,那么数据集就被划分为了左右两个“簇” [0 1 2 3 4]和[6 7 8 9]。因此,根本就没有必要进入第一个簇,可以直接进入第二个簇进行查找。把二分查找中的数据点换成k维数据点,这样的划分就变成了用超平面对k维空间的划分。空间划分就是对数据点进行分类,“挨得近”的数据点就在一个空间里面。
(1)构建根节点,使根节点对应K维空间中包含所有实例的超矩形区域。
(2)构建子节点,使用递归的方法对K维空间进行切分,生成子节点。
(3)重复上述过程,直到子区域内没有实例。
(4)通常循环选择坐标轴对空间进行切分,选择坐标轴上的中位数为切分点,这样得出的kd树是平衡的。(左子树和右子树深度之差绝对值不超过一)
主要问题:
(1)选择向量的哪一维进行划分:最好在数据比较分散的那一维进行划分(方差较大的)
(2)如何划分:一般选择中位数划分
从root节点开始,DFS搜索直到叶子节点,同时创建回溯队列,按顺序存储已经访问的节点。
如果搜索到叶子节点,当前的叶子节点被设为最近邻节点。
然后通过队列回溯:
如果当前点的距离比最近邻点距离近,更新最近邻节点.
然后检查以最近距离为半径的圆是否和父节点的超平面相交.
如果相交,则必须到父节点的另外一侧,用同样的DFS搜索法,开始检查最近邻节点。
如果不相交,则继续往上回溯,而父节点的另一侧子节点都被淘汰,不再考虑的范围中.
当搜索回到root节点时,搜索完成,得到最近邻节点。
DFS:
深度优先搜索算法(Depth First Search,简称DFS):一种用于遍历或搜索树或图的算法。 沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所在边都己被探寻过或者在搜寻时结点不满足条件,搜索将回溯到发现节点v的那条边的起始节点。整个进程反复进行直到所有节点都被访问为止。
import numpy as np
class TreeNode:
def __init__(self, s, d):
self.vec = s # 特征向量
self.Dimension = d # 即划分空间时的特征维度,这里选取方差最大的维度
self.left = None # 左子节点
self.right = None # 右子节点
self.father = None # 父节点(搜索时需要往回退)
def __str__(self):
return str(self.vec) # print 一个 Node 类时会打印其特征向量
#求欧式距离
def distance(arr1, arr2):
res = 0
for a, b in zip(arr1, arr2):
res += (a - b) ** 2
return res ** 0.5
#求出最大方差对应下标
def myvar(data):
data=np.array(data).T
maxvar=0
varindex=0
index=0
for i in data:
if np.var(i)>maxvar:
maxvar=np.var(i)
varindex=index
index+=1
return varindex
#构建kd树
def build(arr, father):
if len(arr) == 0: # 样本空间为空则返回
return None
#确认分割维度
l=myvar(data=arr)
# 找x^l的中位数和对应特征向量,即arr[:][l]的中位数及arr[x][:]
#取长度
size = len(arr)
# 直接对arr进行排序,因为要得到特征向量和划分子空间,由此直接对arr排序最便捷
# 对l列进行排序
arr.sort(key=(lambda x: x[l]))
# 中位数的下标值
mid = int((size - 1) / 2)
# 创建节点
root = TreeNode(arr[mid], l)
root.father = father
# 递归创建左右节点
root.left = build(arr[0:mid] , root) # 0:mid不包括mid,即[0,mid)
root.right = build(arr[mid + 1:] , root)
print(root.left, root.right,root.vec,root.Dimension)
return root
#dfs寻找当前最近节点
#root:kd树节点、father:父节点,stack:回溯队列、depth:深度
def dfs(depth, root, father, stack,target):
if root == None:
return father
stack.append(root)
if target[root.Dimension]<root.vec[root.Dimension]:
return dfs(depth + 1, root.left, root, stack,target)
else:
return dfs(depth + 1, root.right, root, stack,target)
#获得回溯队列与当前最近节点
def mykd(root,target):
depth=0
father=None
mystack=[]
dfs(depth,root,father,mystack,target)
return mystack,root
#获得最近邻
def mynearest(root, target):
# 获取当前最近邻与回溯队列
stack, nearest = mykd(root, target)
# nearest_dis为当前最近邻离target的距离,即最小距离,也是超球体的半径
nearest_dis = distance(nearest.vec, target)
visited = {} # 用来判断兄弟节点是否已经讨论过
# 利用stack进行回溯,而非递归
while stack[-1] != root:
# 取出当前节点
cur = stack[-1]
# 将当前节点移出队列
stack.pop()
# 定义父亲节点father
father = cur.father
# 定义兄弟节点bro
bro = father.left
if father.left == cur:
bro = father.right
# 如果当前节点与target的距离小于最近距离,则更新最近结点和最近距离
if distance(cur.vec, target) < nearest_dis:
nearest = cur
nearest_dis = distance(cur.vec, target)
# 若当前节点没有递归过
if visited.get(hash(cur)) == None:
# 若超球体和父节点的超平面相交,相交则父节点的另一侧,即兄弟节点所在划分域可能存在更近的节点
if father.vec[father.Dimension] - target[father.Dimension] < nearest_dis:
visited.update({hash(bro): 'yes'})
dfs(father.Dimension, bro, father, stack, target)
return nearest_dis,nearest
Featureset = [[1, 6, 2],
[2, 9, 3],
[5, 1, 4],
[9, 4, 7],
[4, 2, 6],
[6, 3, 5],
[7, 2, 5],
[9, 1, 4]]
target = [1, 4, 5]
# 递归构造kd树
root = build(Featureset, None)
#获取最近距离及最近邻
nearest_dis,nearest=mynearest(root,target)
print("最近距离:", nearest_dis)
print("最近邻:", nearest)
有时特征的数值或单位相差较大,容易影响预测结果,使得算法无法学习到其他的特征需要进行无量纲化,使其转移到统一规格(归一化,标准化)
对数据进行处理,使其同意映射在某一区间内(默认0~1)
鲁棒性较差,易受到异常点的影响,适合传统精确小数据。
API:sklearn.preprocessing.MinMaxScaler(feature_range=[0,1])
feature_range:范围
#实例化转换器类(归一化)
transfer=sklearn.preprocessing.MinMaxScaler(feature_range=[2,3])
#导入数据
iris_data=transfer.fit_transform(iris.data)
# 把数据转换为dataframe格式
iris_data=pd.DataFrame(data=iris_data,columns=iris.feature_names)
iris_data['Specials']=iris.target
把数据变换到均值为0,标准差为一的范围内
API:sklearn.preprocessing.StandardScaler()
#实例化转换器类(归一化)
transfer=sklearn.preprocessing.StandardScaler()
#导入数据
iris_data=transfer.fit_transform(iris.data)
print(iris_data)
# 把数据转换为dataframe格式
iris_data=pd.DataFrame(data=iris_data,columns=iris.feature_names)
iris_data['Specials']=iris.target
可通过
transfer.var_
transfer.mean_`
查看每一列的方差及均值
将训练集再分为n份,每一份都分别作为验证集,
分成几份就叫几折交叉验证。
为了使模型更加准确可信(并不能提高准确率)
超参数:需要手动输入的参数eg:KNN算法的k值
可设置多组超参数,用交叉验证来寻找最优参数组合来建立模型。
API: sklearn.model_selection.GridSearchCV(estimator,param_grid,cv)
estimator:估计器对象
param_grid:估计器对象所需参数(字典)eg:{‘n_neighbors’:[1,2,3]}
cv:几折交叉验证
n_jobs:运行的cpu个数
from sklearn.neighbors import KNeighborsClassifier as KNN
sklearn.neighbors.KNeighborsClassifier(self, n_neighbors=5, *,
weights=‘uniform’, algorithm=‘auto’, leaf_size=30,
p=2, metric=‘minkowski’, metric_params=None, n_jobs=None,
**kwargs)
常用参数: n_neighbors=5 k-近邻算法中k值
algorithm=‘auto’ 选择搜索算法
auto:自动选择
brute:暴力检索
kd tree:kd树,20维以下效率较高
ball tree:克服kd树高位失效问题,每一个节点都是一个超球体
index0=[ '电影'+str(i) for i in range(1,8)]
columns0=[ chr(ord('A')+i)+'镜头' for i in range(8)]+['电影类型']
data=pd.DataFrame(data=np.random.randint(0,100,(7,9)),index=index0,columns=columns0)
data['电影类型']=['动作类','动作类','喜剧类','喜剧类','喜剧类','爱情类','动作类']
print(data)
>>>
A镜头 B镜头 C镜头 D镜头 E镜头 F镜头 G镜头 H镜头 电影类型
电影1 99 1 95 47 15 62 45 5 动作类
电影2 94 9 40 18 84 46 84 2 动作类
电影3 20 87 77 9 34 41 56 2 喜剧类
电影4 65 77 25 51 54 81 51 97 喜剧类
电影5 26 12 19 76 27 89 87 59 喜剧类
电影6 23 21 86 70 5 50 24 10 爱情类
电影7 75 20 3 23 65 34 34 96 动作类
#实例化API
estimator=KNN(n_neighbors=2)
#使用fit(x,y)方法进行训练:x为特征值,y为目标值
estimator.fit(X=data.iloc[:,:-1].values,y=data['电影类型'])
#使用模型预测分类
data2=np.random.randint(0,100,(3,8))
print(estimator.predict(data2))
>>>
['动作类' '喜剧类' '动作类']
sklearn自带一些小数据集,可直接导出
sklearn.datasets.load_[name]
也可下载一些sklearn较大的数据集
sklearn.datasets.fetch_[name]
他们的返回值为sklearn.utils.Bunch,类字典型,主要包含
data:特征数据数组,numpy.ndarry二维数组
target:标签数组,numpy.ndarry一维数组
DESCR:数据描述
feature_names:特征数据名
target_names:标签名()新闻数据、手写数字、回归数据集没有
此次我们使用鸢尾花数据集
from sklearn.datasets import load_iris
iris=load_iris()
查看
def data_descr():
print(iris.feature_names)
print(iris.data)
print(iris.target_names)
print(iris.target)
print(iris.DESCR)
data_descr()
使用sepal length (cm), sepal width (cm), petal length (cm),petal width (cm)四个特征值对鸢尾花的种类(Specials)进行预测
**Seaborn:**基于matplotlib的图形可视化python包。它提供了一种高度交互式界面,便于用户能够做出各种有吸引力的统计图表。
Seaborn是在matplotlib的基础上进行了更高级的API封装,从而使得作图更加容易,在大多数情况下使用seaborn能做出很具有吸引力的图,而使用matplotlib就能制作具有更多特色的图。应该把Seaborn视为matplotlib的补充,而不是替代物。同时它能高度兼容numpy与pandas数据结构以及scipy与statsmodels等统计模式。
导入所需包
ps:seaborn绘图时使用DataFrame,所以需要pandas
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
使用seaborn.lmplot绘图
lmplot(
*,
x=None, y=None,
data=None,
hue=None, col=None, row=None, # TODO move before data once * is enforced
palette=None, col_wrap=None, height=5, aspect=1, markers=“o”,
sharex=None, sharey=None, hue_order=None, col_order=None, row_order=None,
legend=True, legend_out=None, x_estimator=None, x_bins=None,
x_ci=“ci”, scatter=True, fit_reg=True, ci=95, n_boot=1000,
units=None, seed=None, order=1, logistic=False, lowess=False,
robust=False, logx=False, x_partial=None, y_partial=None,
truncate=True, x_jitter=None, y_jitter=None, scatter_kws=None,
line_kws=None, facet_kws=None, size=None,
)
data:关联到数据集
x、y:对应坐标轴列名
fit_reg=True:是否进行线性回归
col_wrap:指定每行的列数,最多等于col参数所对应的不同类别的数量
aspect:控制图的长宽比
sharex:共享x轴刻度(默认为True)
sharey:共享y轴刻度(默认为True)
hue:用于分类
ci:控制回归的置信区间
x_jitter:给x轴随机增加噪音点
y_jitter:给y轴随机增加噪音点
order:控制进行回归的幂次
# 把数据转换为dataframe格式
iris_data=pd.DataFrame(data=iris.data,columns=iris.feature_names)
iris_data['Specials']=iris.target
def show_dataset(data,col1,col2):
sns.lmplot(x=col1,y=col2,data=data,hue=data.columns[-1],fit_reg=False)
plt.xlabel=col1
plt.ylabel=col2
plt.title('鸢尾花种类分布图')
plt.show()
show_dataset(iris_data,iris_data.columns[1],iris_data.columns[2])
API:
sklearn.model_selection.train_test_split()
train_test_split(*arrays,test_size=None,train_size=None,random_state=None,shuffle=True,stratify=None)
return:x_train,x_test,y_train,y_test
arrays:分割对象同样长度的列表或者numpy 的ndarray。
test_size:两种指定方法。1:指定小数。小数范围在0.0~0.1之间,它代表test集占据的比例。2:指定整数。整数的大小必须在这个数据集个数范围内,要是test_size在没有指定的场合,可以通过train_size来指定。(两个是对应关系),默认25%
train_size:和test_size相似。
random_state:随机种子
from sklearn.datasets import load_iris
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.model_selection import GridSearchCV
#导入数据集
iris=load_iris()
#数据集划分
x_train,x_test,y_train,y_test=train_test_split(iris['data'],iris['target'],test_size=0.2,random_state=2)
#标准化
transfer=StandardScaler()
x_train=transfer.fit_transform(x_train)
x_test=transfer.fit_transform(x_test)
#实例化估计器
esitmator=KNeighborsClassifier(algorithm='auto')
#参数调优
param_grid={'n_neighbors':[1,2,3]}
esitmator=GridSearchCV(estimator=esitmator,param_grid=param_grid,cv=3)
#训练模型
esitmator.fit(x_train,y_train)
#模型评估
#1、直接比较
pre=esitmator.predict(x_test)
print(pre)
print(pre==y_test)
#2、模型评分(准确率)
esitmator.score(x_test,y_test)
#查看参数调优结果
print('交叉验证最好结果',esitmator.best_score_)
print('交叉验证最好估计器',esitmator.best_estimator_)
print('每次的结果',esitmator.cv_results_)