k近邻详解及其Python实现

一 模型概述

1 定义
给定一个训练数据集,对新的输入实例,在训练数据集中找到与该实例最邻近的 k 个实例,这 k 个实例的多数属于某个类,就把该输入实例分为这个类。

2 距离度量

k 近邻法常用的距离度量是欧氏距离,公式如下:
在这里插入图片描述

3 k 值的选择
如果选择比较小的k值,预测结果会对近邻的实例点比较敏感,如果近邻的实例点是噪声点,会导致预测出错,也就是容易发生过拟合。 如果选择比较大的k值,距离待预测实例点较远的实例点(不相似的)也会对预测起作用,使预测失误。在实际应用中k值一般取一个比较小的数值,通常采用交叉验证来选取最优的k值。

二 k近邻方法的实现:kd树

关于kd树的讲解 推荐看知乎上的一篇文章 讲解很详细

【数学】kd 树算法之详细篇
作者 宏观经济算命椰
https://zhuanlan.zhihu.com/p/23966698

下面是代码实现

数据来源于和鲸社区
在这里插入图片描述在这里插入图片描述因为数据中大多数特征值是离散的 并且是以自然语言的形式呈现的 比如
OnlineSecurity有三种取值No,Yes,No internet service,可以用LabelEncoder来编码 结果是以数字0,1,2分别代表这三种类型 对所有值是离散类型的特征都采用这种方法来处理

def datapreprocessing(data):#数据预处理
    le = LabelEncoder()
    data['gender'] = le.fit_transform(data['gender'].values)
    data['Partner'] = le.fit_transform(data['Partner'].values)
    data['Dependents'] = le.fit_transform(data['Dependents'].values)
    data['PhoneService'] = le.fit_transform(data['PhoneService'].values)
    data['MultipleLines'] = le.fit_transform(data['MultipleLines'].values)
    data['InternetService'] = le.fit_transform(data['InternetService'].values)
    data['OnlineSecurity'] = le.fit_transform(data['OnlineSecurity'].values)
    data['OnlineBackup'] = le.fit_transform(data['OnlineBackup'].values)
    data['DeviceProtection'] = le.fit_transform(data['DeviceProtection'].values)
    data['TechSupport'] = le.fit_transform(data['TechSupport'].values)
    data['StreamingTV'] = le.fit_transform(data['StreamingTV'].values)
    data['StreamingMovies'] = le.fit_transform(data['StreamingMovies'].values)
    data['Contract'] = le.fit_transform(data['Contract'].values)
    data['PaperlessBilling'] = le.fit_transform(data['PaperlessBilling'].values)
    data['PaymentMethod'] = le.fit_transform(data['PaymentMethod'].values)
    data['Churn'] = le.fit_transform(data['Churn'].values)
    print(data)
    data.to_csv('Customer-Churn.csv')#

TotalCharges这一列有空白值 首先用0填充缺失值,以把这一列的数据类型由字符串转换为浮点型 之后求出这一列的平均值来取代为0的值

df=read_data("Customer-Churn.csv")#读数据
df['TotalCharges'] = df['TotalCharges'].replace(" ", "0")#TotalCharges中有空格
df['TotalCharges'] = pd.to_numeric(df['TotalCharges'])
TotalCharges = []
for i in range(df.shape[0]):
    TotalCharges.append(float(df.loc[i, 'TotalCharges']))  # 将字符串数据转化为浮点型加入到数组之中
avg=np.var(TotalCharges)
df['TotalCharges'] = df['TotalCharges'].replace(0, avg)#用均值填充缺失值

划分特征和标签 并对数据进行标准化处理

def prepare_data(df):#数据预处理
       ndarray_data = df.values
       X=df.iloc[:,2:21]#数据切片
       Y=df.iloc[:,21]
       print(X)
       #特征值标准化\n"
       minmax_scale = preprocessing.MinMaxScaler(feature_range=(0, 1))
       X = minmax_scale.fit_transform(X)
       Y = Y.replace(0, -1)
       return X , Y

KNN模型

class Node:
    def __init__(self, isvisit, left,right,X,split,label):#是否访问过,左子树,右子树,特征坐标,切分轴,标签
        self.isvisit = isvisit
        self.left = left
        self.right = right
        self.X=X
        self.split=split
        self.label=label
class KNN:#基于kd树进行k近邻搜索的KNN算法
    def __init__(self,k,data,label):
        self.k=k # 找多少个近邻
        #self.data=data
        self.stack=[]
        self.root=self.create_kdtree(data,0,label) #kd树根结点
    def create_kdtree(self,data,split,label):
        #print("split="+str(split))
        len = data.shape[0]  # 求出datas长度方便找中位数
        if len==0:
            return None
        data = data[np.argsort(data[:, split])]#按照划分的那一维度进行排序
       # print(data)
       # print("******************")
        dimension=data.shape[1]#求出数据维度
        #print("dimension=" +str(dimension))
        print()
        mid=int(len/2) #中位数
        if len%2==0:
            mid=mid-1
        left=data[0:mid,:] #左孩子
        llabel=label[0:mid,:]
        now=data[mid:mid+1,:]#当前结点
        nlabel = label[mid:mid+1,:]
        #print(nlabel[0][0])
        right=data[mid+1:,:] #右孩子
        rlabel= label[mid+1:,:]
        #print("now: ")
       # print(now)
        node=Node(False,None,None,now[0],split,nlabel[0][0])
       # print(node.X)
        node.left=self.create_kdtree(left,(split+1)%dimension,llabel)
        node.right=self.create_kdtree(right,(split+1)%dimension,rlabel)
        return node
    def pre_order(self,node):#先序遍历
        if node!=None:
            print(node.X)
            self.pre_order(node.left)
            self.pre_order(node.right)

    def in_order(self, node):  # 中序遍历
        if node != None:
            self.in_order(node.left)
            print(node.X)
            self.in_order(node.right)
    def search_k_neighbor(self,X): #寻找某个点的k个邻居结点
        L=[]#存放k个邻居结点的列表
        node=self.root #从根节点开始遍历
        Lmax=0
        maxi=0
        node = self.search_leaf_node(node, X)  # 首先寻找叶子节点
        node.isvisit = True #设置为已访问
        Lmax= self.EuclideanDistance(X, node.X)
        L.append(node)
        while len(self.stack)!=0 :
            node=self.stack.pop()
            if node.isvisit==False: #如果这个结点没有被访问过
                node.isvisit=True
                dist=self.EuclideanDistance(X,node.X)
                if len(L)<self.k:#如果L中不足k个结点直接放入
                    if Lmax<dist: #有需要的话调整最大距离
                        Lmax=dist
                        maxi=len(L)
                    L.append(node)
                elif dist<Lmax:#当前点到 X的距离 比L中结点到X的最大距离 要小 说明需要更新L
                     L.pop(maxi)
                     L.append(node)
                     Lmax,maxi= self.max_dist(L,X)
                split=node.split
                if abs(X[split]-node.X[split]) < Lmax:#从另一支出发
                    left=node.left
                    right=node.right
                    if left!=None and left.isvisit==False:
                           node=left
                           node = self.search_leaf_node(node, X)  # 首先寻找叶子节点
                           node.isvisit = True
                           dist=self.EuclideanDistance(X,node.X)
                           if len(L) < self.k:  # 如果L中不足k个结点直接放入
                               if Lmax < dist:
                                   Lmax = dist
                                   maxi = len(L)
                               L.append(node)
                           elif dist < Lmax:  # 当前点到 X的距离 比L中结点到X的最大距离 要小 说明需要更新L
                               L.pop(maxi)
                               L.append(node)
                               Lmax, maxi = self.max_dist(L, X)
                    if right!=None and right.isvisit==False:
                           node=right
                           node = self.search_leaf_node(node, X)  # 首先寻找叶子节点
                           node.isvisit = True
                           dist = self.EuclideanDistance(X, node.X)
                           if len(L) < self.k:  # 如果L中不足k个结点直接放入
                               if Lmax < dist:
                                   Lmax = dist
                                   maxi = len(L)
                               L.append(node)
                           elif dist < Lmax:  # 当前点到 X的距离 比L中结点到X的最大距离 要小 说明需要更新L
                               L.pop(maxi)
                               L.append(node)
                               Lmax, maxi = self.max_dist(L, X)

        return  L
    def max_dist(self,L,X):
        Lmax=0
        maxi=0
        for i in range(len(L)):
            dist=self.EuclideanDistance(L[i].X,X)
            if dist>Lmax:
                Lmax=dist
                maxi=i
        return Lmax,maxi
    def EuclideanDistance(self,X1,X2):#欧氏距离计算公式
        dist=0
        for (x1,x2) in zip(X1,X2):
            dist+=(x1-x2)**2
        return dist**0.5
    def search_leaf_node(self,node,X):
        while (node.left != None) or (node.right != None):  # 不是叶子节点
            self.stack.append(node)
            if node.left == None:  # 单分支结点
                node = node.right
            elif node.right == None:  # 单分支结点
                node = node.left
            else:
                split = node.split  # 以哪个轴做分割的
                nodeX = node.X
                if X[split] > nodeX[split]:
                    node = node.right
                else:
                    node = node.left
        return node
    def predict(self,X_test,Y_test):
        n = X_test.shape[0]  # 共有多少条数据
        num = 0  # 正确预测的数据有多少
        for i in range(X_test.shape[0]):
            X = X_test[i]
            Y = Y_test[i]
            L=self.search_k_neighbor(X)
            positive =0
            negative =0
            for node in L:
               if node.label==1:
                   positive+=1
               else:
                   negative+=1
            if positive>=negative:
                label=1
            else:
                label=-1
            if label==Y[0]:
                num+=1
        return num / n

执行过程

df=read_data("Customer-Churn.csv")#读数据
df['TotalCharges'] = df['TotalCharges'].replace(" ", "0")#TotalCharges中有空格
df['TotalCharges'] = pd.to_numeric(df['TotalCharges'])
TotalCharges = []
for i in range(df.shape[0]):
    TotalCharges.append(float(df.loc[i, 'TotalCharges']))  # 将字符串数据转化为浮点型加入到数组之中
avg=np.var(TotalCharges)
df['TotalCharges'] = df['TotalCharges'].replace(0, avg)#用均值填充缺失值
print(df.shape)#(7043, 21)
#print(df.dtypes)#查看各列数据的数据类型
X,Y=prepare_data(df)
train_size = int(len(X) * 0.7)#划分训练集与测试集
X_train = np.array(X[:train_size])
print(X_train.shape)
Y_train=np.array(Y[:train_size])
Y_train.resize([Y_train.shape[0],1])
X_test = np.array(X[train_size:])
Y_test =np.array( Y[train_size:])
Y_test.resize([Y_test.shape[0],1])
knn=KNN(10,X_train,Y_train)
print(knn.predict(X_test,Y_test))
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值