机器学习之一:K-近邻算法

K-近邻算法是一种简单粗暴的监督学习算法,其原理十分简单,对于给定的训练集和当前数据,从训练集中找出与当前数据最小的K个数据(这里的K就是K-近邻中的K),将这K个数据中出现次数最多的类别做为当前数据的预测类别。

其中,两个数据的之间的距离定义这两个数据对应特征的欧式距离之和。例如,对于数据 x_{1} 和 x_{2} ,其有 m 个特征,那么 x_{1} 和 x_{2}  之间的距离d_{ij} = \sqrt{\sum_{l=1}^{m}(x_{il}-x_{jl})^{2}}。注意到在这里,每个特征的权重都是一样的,也可以为不同的特征赋予不同的权重,那么,距离公式则变为:d_{ij} = \sqrt{\sum_{l=1}^{m}\mu_{l}(\m x_{il}-x_{jl})^{2}},其中\sum _{l=1}^{m}{\mu _{l}}=1. 本文还是按照相同权重来做的。

因此,K-近邻算法的步骤如下:

1. 计算已知类别数据集中的点与当前点之间的距离

2. 按照距离递增次序排序

3. 选取与当前点距离最小的k个点

4. 确定前k个点所在类别出现的频率

5. 返回前k个点出现频率最高的类别作为当前点的预测分类

 

接下来,会用加州大学欧文分校的乳腺癌数据集进行K-近邻算法的案例计算。

import math
import pandas as pd
import numpy as np
import sklearn.utils
from sklearn import preprocessing
from scipy import stats

features = ['class','age','menopause','tumor-size','inv-nodes','node-caps','deg-malig','breast','breast-quad','irradiat']
df = pd.read_csv('breast-cancer.data',names=features)
print(df.head(5))
print(df.info())

数据的基本情况如下:

 

可以看到,一共有286条数据,其中每条数据有10个属性。这10个属性中,第一个属性‘class’是我们要预测的分类,这个数据集中只有两大类,所以实际参与预测的属性只有后面9个。这9个属性的含义分别如下:

class:类别,分别是乳腺癌复发(recurrence-events)和未复发(no-recurrence-events)

age:年龄,有 20-29, 30-39, 40-49, 50-59, 60-69, 70-79,六个区间

menopause:绝经期,分为prememo(未绝经),ge40(40岁之后绝经),lt40(40岁之前绝经)

tumor-size:肿瘤大小

inv-nodes:淋巴结个数

node-caps:结节冒有无

deg-malig:肿瘤恶性程度,分为1、2、3三种,3恶性程度最高

breast: 分为left和right

breast-quad:所在象限

irradiat:是否有放射性治疗经历

 

 

第一步是对数据进行预处理。数据集的9个属性中,有字符串类型,有区间类型,我们要将其处理可以参与运算的数值类型。这里用到了sklearn的preprocessing方法,具体如下:

le = preprocessing.LabelEncoder()
for f in features:
    le.fit(df[f])
    df[f] = le.transform(df[f])
print(df)

可见,这个方法自动的将各种数据类型处理为了数值型,其中,no-recurrence-events对应的数值为0,recurrence-events对应的数值为1.

下面会用三种方法实现K-近邻算法

第一种是比较傻瓜式的方法,用python里最基础的方法来实现:

df = sklearn.utils.shuffle(df,random_state = 20) #random_state实现每次运行打乱后的的顺序一致
data_size = len(df)
training_ratio = math.floor(0.8*data_size)
training_set = df[:training_ratio]
test_set = df[training_ratio:]

def get_class(cur_data,data_set,k):
    dist = np.array([])
    labels = []
    data_set_size = len(data_set)
    for i in range(data_set_size):
        d = 0
        for j in range(1,10):
            d += (cur_data[j]-data_set.iloc[i,j])**2
        dist = np.append(dist,d**0.5)
    index_sort = dist.argsort()[:k]
    for i in index_sort:
        labels.append(data_set.iloc[i,0])
    return stats.mode(labels)[0][0]    #计算众数
print(get_class(df.iloc[-1,:],training_set,20))

对最后一条数据进行预测,显示其类别为0,也就是no-recurrence-events。

写完训练算法后,要用测试集对算法进行测试,判断其训练效果,代码如下:

def test(test_set):
    right_count = 0
    for i in range(len(test_set)):
        test_data = test_set.iloc[i,:]
        if get_class(test_data,training_set,20) == test_data[0]:
            right_count += 1
    return right_count/len(test_set)

print(test(test_set))

这里的结果输出结果为0.603448275862069,很明显,这种算法效果很差,正确率只有60%= = 。

 

第二种方法是通过python里的numpy实现矩阵运算,避免写循环,从而可以节省大量的运算时间,具体如下:

import math
import pandas as pd
import numpy as np
import sklearn.utils
from sklearn import preprocessing
import operator

features = ['class','age','menopause','tumor-size','inv-nodes','node-caps','deg-malig','breast','breast-quad','irradiat']
df = pd.read_csv('breast-cancer.data',names=features)
le = preprocessing.LabelEncoder()
for f in features:
    le.fit(df[f])
    df[f] = le.transform(df[f])

df = sklearn.utils.shuffle(df,random_state = 20)
data_mat = np.array(df)

data_size = len(df)
training_ratio = math.floor(0.8*data_size)
training_set = data_mat[:training_ratio]
test_set = data_mat[training_ratio:]

def classify(cur_data,data_set,labels,k):
    #labels为每条数据的类别
    data_set_size = len(data_set)
    diff_mat = np.tile(cur_data[1:],(data_set_size,1)) - data_set[:,1:]
    sq_diff_mat = diff_mat**2
    sq_dist = sq_diff_mat.sum(axis=1)
    dist = sq_dist ** 2
    sort_dist_ind = dist.argsort()
    class_count = {}
    for i in range(k):
        voteIlabel = labels[sort_dist_ind[i]]
        class_count[voteIlabel] = class_count.get(voteIlabel,0) + 1
    sort_calss_count = sorted(class_count.items(),key=operator.itemgetter(1),reverse=True)
    return sort_calss_count[0][0]
    

def classify_test(test_set):
    error_count = 0
    for i in range(len(test_set)):
        if classify(test_set[i,:],training_set,df['class'],20) != test_set[i,0]:
            error_count += 1
    return error_count / len(test_set)

print(classify_test(test_set))

 

第三种是通过调用sklearn。在这里通过sklearn自动完成了训练集、测试集的划分和K-近邻算法,相关介绍可以参见下面两篇文章:

https://www.cnblogs.com/Yanjy-OnlyOne/p/11288098.html

https://blog.csdn.net/smallcases/article/details/78236412

代码如下:

import pandas as pd
import numpy as np
from sklearn import preprocessing
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier

features = ['class','age','menopause','tumor-size','inv-nodes','node-caps','deg-malig','breast','breast-quad','irradiat']
df = pd.read_csv('breast-cancer.data',names=features)
le = preprocessing.LabelEncoder()
for f in features:
    le.fit(df[f])
    df[f] = le.transform(df[f])

x = df.iloc[:,1:]
y = df.iloc[:,0]
train_x,test_x,train_y,test_y = train_test_split(x,y,test_size=0.2,random_state=5,stratify=y)
knn = KNeighborsClassifier(n_neighbors=5,p=2,metric='minkowski')
knn.fit(train_x,train_y)
test_result = knn.predict(test_x)
compare = list(np.array(test_result) == np.array(test_y))
print(compare.count(False)/len(compare))

这篇文章使用的数据集存在着很大的偏差,两种类别的数量分别为201和85,这种情况下,K-近邻算法可能还不如随机预测。鉴于这一情况,未来会用更好的算法对这个数据集来进行预测。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值