有监督模型-KNN(近邻算法)原理与python代码

有监督模型-KNN(近邻算法)

1. 算法原理

用一句话来解释 KNN 算法原理,那就是找到 K 个与新数据最近的样本,取样本中最多的一个类别作为新数据的类别。

实现过程
假设 X_test 待标记的数据样本,X_train 为已标记的数据集。
遍历已标记数据集中所有的样本,计算每个样本与待标记点的距离,并把距离保存在 Distance 数 组中。
对 Distance 数组进行排序,取距离最近的 k 个点,记为 X_knn.
在 X_knn 中统计每个类别的个数,即 class0 在 X_knn 中有几个样本,class1 在 X_knn 中有几个 样本等。
待标记样本的类别,就是在 X_knn 中样本个数最多的那个类别。

2.算法优缺点

优点:
简单易实现: KNN 算法把全部的数据集直接当作模型本身,当一条新数据来了之后跟数据集里面的每一条数据进行对比。
对于边界不规则的数据效果较好: KNN算法把未知数据作为中心点,然后画一个圈,使得圈里有 K 个数据,所以对于边界不规则的数据,要比线性的分类器效果更好。因为线性分类器可以理解成画一条线来分类,不规则的数据则很难找到一条线将其分成左右两边。

缺点
只适合小数据集: 正是因为这个算法太简单,每次预测新数据都需要使用全部的数据集,所以如果数据集太大,就会消耗非常长的时间,占用非常大的存储空间。

数据不平衡效果不好: 如果数据集中的数据不平衡,有的类别数据特别多,有的类别数据特别少,那么这种方法就会失效了,因为特别多的数据最后在投票的时候会更有竞争优势。

必须要做数据标准化: 由于使用距离来进行计算,如果数据量纲不同,数值较大的字段影响就会变大,所以需要对数据进行标准化,比如都转换到 0-1 的区间。

不适合特征维度太多的数据: 由于我们只能处理小数据集,如果数据的维度太多,那么样本在每个维度上的分布就很少。比如我们只有三个样本,每个样本只有一个维度,这比每个样本有三个维度特征要明显很多。

3. 关于K的选取

当K 越小的时候容易过拟合,因为结果的判断与某一个点强相关。而K 越大的时候容易欠拟合。

4. 实例应用:

以人们的收入为例, 里面有年龄, 教育等级, 每周工作时长, 岗位, 婚姻状态等

加载数据
import numpy as np 
import pandas as pd 
import seaborn as sns
import matplotlib.pyplot as plt
sns.set()
data = pd.read_csv("adult.csv")
df = pd.DataFrame(data, columns=data.columns)
df.head()

在这里插入图片描述

# 收入大于50K的为1, 小于等于50K的为0
# data.income = [1 if each=='>50K' else 0 for each in data.income]
# data.income 
df['income']=df['income'].map({'<=50K': 0, '>50K': 1})
df.head()

在这里插入图片描述

数据清洗
#将问号转化成nan
from numpy import nan
df=df.replace("?",nan)
df.head()

在这里插入图片描述

null_values=df.isnull().sum()
null_values
age                   0
workclass          2799
fnlwgt                0
education             0
educational-num       0
marital-status        0
occupation         2809
relationship          0
race                  0
gender                0
capital-gain          0
capital-loss          0
hours-per-week        0
native-country      857
income                0
dtype: int64
# 将空值替换为众数
df['native-country'].fillna(df['native-country'].mode()[0], inplace=True)
df['occupation'].fillna(df['occupation'].mode()[0], inplace=True)
df['workclass'].fillna(df['workclass'].mode()[0], inplace=True)
null_values=df.isnull().sum()
null_values
null_values=df.isnull().sum()
null_values
null_values=df.isnull().sum()
null_values
age                0
workclass          0
fnlwgt             0
education          0
educational-num    0
marital-status     0
occupation         0
relationship       0
race               0
gender             0
capital-gain       0
capital-loss       0
hours-per-week     0
native-country     0
income             0
dtype: int64
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 48842 entries, 0 to 48841
Data columns (total 15 columns):
 #   Column           Non-Null Count  Dtype 
---  ------           --------------  ----- 
 0   age              48842 non-null  int64 
 1   workclass        48842 non-null  object
 2   fnlwgt           48842 non-null  int64 
 3   education        48842 non-null  object
 4   educational-num  48842 non-null  int64 
 5   marital-status   48842 non-null  object
 6   occupation       48842 non-null  object
 7   relationship     48842 non-null  object
 8   race             48842 non-null  object
 9   gender           48842 non-null  object
 10  capital-gain     48842 non-null  int64 
 11  capital-loss     48842 non-null  int64 
 12  hours-per-week   48842 non-null  int64 
 13  native-country   48842 non-null  object
 14  income           48842 non-null  int64 
dtypes: int64(7), object(8)
memory usage: 5.6+ MB
# 看下相关性,
sns.heatmap(df.corr())

在这里插入图片描述

#教育编号和教育级别重复了, 删掉其中之一,
df=df.drop(["educational-num"],axis=1)
# fnlwgt看着也好像没啥关系,
df=df.drop(["fnlwgt"],axis=1)
# 看一下年龄与收入之间的箱线图
fig = plt.figure(figsize=(10,10)) 
sns.boxplot(x="income", y="age", data=df)
plt.show()

在这里插入图片描述

# 绝大多数在 20 -70 之间, 把大于70小于20的剔除掉
df=df[(df["age"] < 70)]
df=df[(df["age"] > 20)]
# 其实还有其他条件可以优化, 例如工作时长等. 这里只用简单的处理下
# # one-hot编码 简单粗暴 不会将AGE进行编码
# df = pd.get_dummies(df)
# df
# LabelEncoder 将不连续的数字or文本进行编号
from sklearn import preprocessing
import pandas as pd
le = preprocessing.LabelEncoder()
df[['age', 'workclass', 'education', 'marital-status', 'occupation',
       'relationship', 'race', 'gender', 'capital-gain', 'capital-loss',
       'hours-per-week', 'native-country']]=df[['age', 'workclass', 'education', 'marital-status', 'occupation',
       'relationship', 'race', 'gender', 'capital-gain', 'capital-loss',
       'hours-per-week', 'native-country']].apply(le.fit_transform)
df

在这里插入图片描述

抽取测试集和训练集
X=df.drop(["income"],axis=1)
y=df["income"]
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
使用KNN模型
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import classification_report,confusion_matrix
from sklearn.model_selection import cross_val_score
accuracy_rate = []
# 选择最优K值, 由于KNN算法简单, 但是对于多个特征,和较多的样本量,计算很慢, 这里使用了交叉验证, 有时候模型时好时坏, 因为测试集和训练集划分不平衡的原因.
for i in range(1,20): 
    knn = KNeighborsClassifier(n_neighbors=i)
    score=cross_val_score(knn,X,df['income'],cv=6)
    accuracy_rate.append(score.mean())

在这里插入图片描述

模型评估
knn = KNeighborsClassifier(n_neighbors=14)
knn.fit(X_train,y_train)
pred = knn.predict(X_test)
print(classification_report(y_test,pred))

在这里插入图片描述
准确率分数0.82分

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值