有监督模型-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分