题目信息
实践课要求对糖尿病数据集使用KNN算法进行训练,对测试集进行分类,题目具体如下:
数据描述
皮马印第安人糖尿病数据集由8个医学预测变量和1个目标变量Outcome组成:
【1】Pregnancies:怀孕次数 【2】Glucose:葡萄糖 【3】BloodPressure:血压 (mm Hg) 【4】SkinThickness:皮层厚度 (mm) 【5】Insulin:胰岛素 2小时血清胰岛素(𝑚𝑢𝑈/𝑚𝑙)(muU/ml) 【6】BMI:体重指数 (体重/身高)2(体重/身高)2 【7】DiabetesPedigreeFunction:糖尿病谱系功能 【8】Age:年龄 (岁) 【9】Outcome:类标变量 (0或1)
完整的训练集PimaIndiansdiabetes.csv
和测试集PimaIndiansdiabetes-test.csv
放在当前目录下。
题目要求
请使用KNN算法对此数据集进行分类,并对测试集PimaIndiansdiabetes-test.csv
进行分类,将分类结果保存为csv文件,编码为utf_8_sig。 保存的csv文件格式要求如下:
-
header分别为“ID”和“Outcome”。
-
第一列为ID,如:“0”,第二列为标签,如:“1”。
解题过程
KNN算法
优点
在处理边界不规则数据的分类问题比线性分类器的效果好,不规则数据很难找到一条线来分割所有样本数据,但是KNN是以样本为中心画一个圈,这就不用考虑数据的边界问题。
使用jupyter,比较好用,代码块的使用相当方便,将代码的功能分割成一个个区块,方便理解和分析解题过程。
缺点
①只适合小数据集,对于大数据集,一般选择KNN的优化算法,比如kd-tree来实现。
②KNN算法对数据容错率低,因为KNN算法对数据样本质量的依赖很高,如果训练数据集中存在错误的样本数据,又离待测样本很近,就会直接导致预测数据不准确。
基本了解数据
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt # 可视化
import seaborn as sns # matplotlib的高级API
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.neighbors import KNeighborsClassifier, RadiusNeighborsClassifier
以上导入的包随着一步步使用逐渐导入
数据直观观察和统计性描述
# 先查看训练集的样子
# 导入数据
data = pd.read_csv("./PimaIndiansdiabetes.csv")
X = data.iloc[:, 0:8]
Y = data.iloc[:, -1]
print(type(Y))
# 查看数据类型以及数据数值
print(data)
# print("-------------------------")
# 对数据集进行描述性统计
print(data.describe())
# 查看是否具有数据不均衡的表现
data.groupby('Outcome').size()
结果如下所示:
<class 'pandas.core.series.Series'>
Pregnancies Glucose BloodPressure SkinThickness Insulin BMI \
0 6 148 72 35 0 33.6
1 1 85 66 29 0 26.6
2 8 183 64 0 0 23.3
3 1 89 66 23 94 28.1
4 0 137 40 35 168 43.1
.. ... ... ... ... ... ...
763 10 101 76 48 180 32.9
764 2 122 70 27 0 36.8
765 5 121 72 23 112 26.2
766 1 126 60 0 0 30.1
767 1 93 70 31 0 30.4
DiabetesPedigreeFunction Age Outcome
0 0.627 50 1
1 0.351 31 0
2 0.672 32 1
3 0.167 21 0
4 2.288 33 1
.. ... ... ...
763 0.171 63 0
764 0.340 27 0
765 0.245 30 0
766 0.349 47 1
767 0.315 23 0
[768 rows x 9 columns]
Pregnancies Glucose BloodPressure SkinThickness Insulin \
count 768.000000 768.000000 768.000000 768.000000 768.000000
mean 3.845052 120.894531 69.105469 20.536458 79.799479
std 3.369578 31.972618 19.355807 15.952218 115.244002
min 0.000000 0.000000 0.000000 0.000000 0.000000
25% 1.000000 99.000000 62.000000 0.000000 0.000000
50% 3.000000 117.000000 72.000000 23.000000 30.500000
75% 6.000000 140.250000 80.000000 32.000000 127.250000
max 17.000000 199.000000 122.000000 99.000000 846.000000
BMI DiabetesPedigreeFunction Age Outcome
count 768.000000 768.000000 768.000000 768.000000
mean 31.992578 0.471876 33.240885 0.348958
std 7.884160 0.331329 11.760232 0.476951
min 0.000000 0.078000 21.000000 0.000000
25% 27.300000 0.243750 24.000000 0.000000
50% 32.000000 0.372500 29.000000 0.000000
75% 36.600000 0.626250 41.000000 1.000000
max 67.100000 2.420000 81.000000 1.000000
Outcome
0 500
1 268
dtype: int64
数据可视化分析
然后将数据分布进行一个可视化
# 将数据的分布进行可视化
data.hist(figsize=(16, 14));
结果如下图所示
分析
数据集中有缺失值,但是也查看了测试集中的样本,其中缺失值的比例几乎相同。
主要缺失值在胰岛素水平值上,其中的有一大部分值是0,查阅资料可以知道,人的正常值是5-20。(!!!此处应该对这些异常值进行处理,比如分类均值填补数据集中的0,0的取值即不科学,对分类也会造成一定的影响,想法是对于训练集中的数据,根据是否为糖尿病人进行分类,分类后计算他们的平均值,替代训练集中的不合理的数值0,(是不合理的!!怀孕次数为0是合理的!!),而训练集我打算就使用全体的平均值替代,因为不好确定他们的所属类别)
糖尿病的数量较少,但是没有出现数据不平衡的情况。
可以看到数值之间相差比较大,并且有量纲的影响,需要进行标准化处理。
但是给出的训练集和测试集是在两个文件之中,所以需要合并之后统一进行处理
缺失值处理
这里用全体的平均值替代了不合理的为0的缺失值
再次看一看可视化效果,如下所示
数据如下所示:
<class 'pandas.core.series.Series'>
Pregnancies Glucose BloodPressure SkinThickness Insulin BMI \
0 6 148 72 35 80 33.6
1 1 85 66 29 80 26.6
2 8 183 64 21 80 23.3
3 1 89 66 23 94 28.1
4 0 137 40 35 168 43.1
.. ... ... ... ... ... ...
763 10 101 76 48 180 32.9
764 2 122 70 27 80 36.8
765 5 121 72 23 112 26.2
766 1 126 60 21 80 30.1
767 1 93 70 31 80 30.4
DiabetesPedigreeFunction Age Outcome
0 0.627 50 1
1 0.351 31 0
2 0.672 32 1
3 0.167 21 0
4 2.288 33 1
.. ... ... ...
763 0.171 63 0
764 0.340 27 0
765 0.245 30 0
766 0.349 47 1
767 0.315 23 0
[768 rows x 9 columns]
Pregnancies Glucose BloodPressure SkinThickness Insulin \
count 768.000000 768.000000 768.000000 768.000000 768.000000
mean 3.845052 121.682292 72.250000 26.743490 118.757812
std 3.369578 30.435999 12.117203 9.546733 93.039581
min 0.000000 44.000000 24.000000 7.000000 14.000000
25% 1.000000 99.750000 64.000000 21.000000 80.000000
50% 3.000000 117.000000 72.000000 23.000000 80.000000
75% 6.000000 140.250000 80.000000 32.000000 127.250000
max 17.000000 199.000000 122.000000 99.000000 846.000000
BMI DiabetesPedigreeFunction Age Outcome
count 768.000000 768.000000 768.000000 768.000000
mean 32.450911 0.471876 33.240885 0.348958
std 6.875366 0.331329 11.760232 0.476951
min 18.200000 0.078000 21.000000 0.000000
25% 27.500000 0.243750 24.000000 0.000000
50% 32.000000 0.372500 29.000000 0.000000
75% 36.600000 0.626250 41.000000 1.000000
max 67.100000 2.420000 81.000000 1.000000
Outcome
0 500
1 268
dtype: int64
标准化处理
# 导入数据
traindata = pd.read_csv("./PimaIndiansdiabetes.csv")
testdata = pd.read_csv("./PimaIndiansdiabetes-test.csv")
alldata = pd.concat([traindata,testdata])
print(alldata)
结果如下所示:
Pregnancies Glucose BloodPressure SkinThickness Insulin BMI \
0 6 148 72 35 80 33.6
1 1 85 66 29 80 26.6
2 8 183 64 21 80 23.3
3 1 89 66 23 94 28.1
4 0 137 40 35 168 43.1
.. ... ... ... ... ... ...
95 10 101 76 48 180 32.9
96 2 122 70 27 80 36.8
97 5 121 72 23 112 26.2
98 1 126 60 20 80 30.1
99 1 93 70 31 80 30.4
DiabetesPedigreeFunction Age Outcome ID
0 0.627 50 1.0 NaN
1 0.351 31 0.0 NaN
2 0.672 32 1.0 NaN
3 0.167 21 0.0 NaN
4 2.288 33 1.0 NaN
.. ... ... ... ...
95 0.171 63 NaN 95.0
96 0.340 27 NaN 96.0
97 0.245 30 NaN 97.0
98 0.349 47 NaN 98.0
99 0.315 23 NaN 99.0
[868 rows x 10 columns]
划分数据集X Y
# 划分训练数据集,统一进行处理
X = alldata.iloc[:, 0:8]
# Y是训练集的分类结果
Y = alldata.iloc[0:768, -2]
print(X)
print(Y)
print(type(Y))
结果如下:
Pregnancies Glucose BloodPressure SkinThickness Insulin BMI \
0 6 148 72 35 80 33.6
1 1 85 66 29 80 26.6
2 8 183 64 21 80 23.3
3 1 89 66 23 94 28.1
4 0 137 40 35 168 43.1
.. ... ... ... ... ... ...
95 10 101 76 48 180 32.9
96 2 122 70 27 80 36.8
97 5 121 72 23 112 26.2
98 1 126 60 20 80 30.1
99 1 93 70 31 80 30.4
DiabetesPedigreeFunction Age
0 0.627 50
1 0.351 31
2 0.672 32
3 0.167 21
4 2.288 33
.. ... ...
95 0.171 63
96 0.340 27
97 0.245 30
98 0.349 47
99 0.315 23
[868 rows x 8 columns]
0 1.0
1 0.0
2 1.0
3 0.0
4 1.0
...
763 0.0
764 0.0
765 0.0
766 1.0
767 0.0
Name: Outcome, Length: 768, dtype: float64
<class 'pandas.core.series.Series'>
标准化处理
# 数据标准化处理
from sklearn import preprocessing
X_all_scaled = preprocessing.scale(X)
print(X_all_scaled)
结果如下:
[[ 0.63202204 0.85835214 -0.03283457 ... 0.14954285 0.49306912
1.4117557 ]
[-0.85146939 -1.21892385 -0.52422086 ... -0.87046426 -0.36110387
-0.19983667]
[ 1.22541861 2.01239435 -0.68801629 ... -1.35132475 0.63233646
-0.11501602]
...
[ 0.33532375 -0.031909 -0.03283457 ... -0.92875038 -0.68915582
-0.28465732]
[-0.85146939 0.13295417 -1.01560715 ... -0.36046071 -0.36729353
1.15729374]
[-0.85146939 -0.95514277 -0.19663 ... -0.31674612 -0.47251774
-0.87840188]]
划分训练集与待测试数据
# 将待测试的数据集和待测试的测试集分开
X_train_all = X_all_scaled[0:768, 0:8]
print(X_train_all)
X_tobe_test = X_all_scaled[768:868, 0:8]
print("--------------------------------------")
print(X_tobe_test)
结果如下:
[[ 0.63202204 0.85835214 -0.03283457 ... 0.14954285 0.49306912
1.4117557 ]
[-0.85146939 -1.21892385 -0.52422086 ... -0.87046426 -0.36110387
-0.19983667]
[ 1.22541861 2.01239435 -0.68801629 ... -1.35132475 0.63233646
-0.11501602]
...
[ 0.33532375 -0.031909 -0.03283457 ... -0.92875038 -0.68915582
-0.28465732]
[-0.85146939 0.13295417 -1.01560715 ... -0.36046071 -0.36729353
1.15729374]
[-0.85146939 -0.95514277 -0.19663 ... -0.31674612 -0.47251774
-0.87840188]]
--------------------------------------
[[ 6.32022039e-01 -7.90279601e-01 -1.17940258e+00 6.65850163e-01
7.68822799e-01 2.07828966e-01 -1.16612327e-01 8.18011139e-01]
[ 1.52211690e+00 1.05618795e+00 4.58551721e-01 3.47824883e-01
-2.08135040e-01 -2.43888466e-01 -9.39837028e-01 9.87652441e-01]
[ 6.32022039e-01 1.41888693e+00 -3.60425427e-01 -7.62088228e-02
5.30010883e-01 1.49542845e-01 5.05448443e-01 1.32693505e+00]
...
[ 3.35323754e-01 -3.19090014e-02 -3.28345677e-02 -3.94234102e-01
-7.78739946e-02 -9.28750380e-01 -6.89155822e-01 -2.84657323e-01]
[-8.51469389e-01 1.32954172e-01 -1.01560715e+00 -7.12259382e-01
-4.25236782e-01 -3.60460707e-01 -3.67293533e-01 1.15729374e+00]
[-8.51469389e-01 -9.55142775e-01 -1.96629997e-01 4.53833310e-01
-4.25236782e-01 -3.16746117e-01 -4.72517743e-01 -8.78401879e-01]]
划分数据集的训练集与测试集(留出法)
# 划分训练集训练的训练集与测试集
X_train, X_test, Y_train, Y_test = train_test_split(X_train_all, Y, test_size=0.1, random_state=1024)
Y_train
124 1.0
665 0.0
538 0.0
40 0.0
261 1.0
...
208 0.0
601 0.0
613 0.0
492 0.0
609 0.0
Name: Outcome, Length: 691, dtype: float64
模型初训练
先是留出法
# 模型训练与评估
# 此处用于与之后的结果进行比对,没有确定参数K
# K-近邻算法中K的取值应该为奇数,为了能够得到当前样本对应的分类
# 以下为每个点权重相等的时候的模型
model1 = KNeighborsClassifier(n_neighbors=5)
model1.fit(X_train, Y_train)
score1 = model1.score(X_test, Y_test)
# 我们认为,两个人身体特征相同或者相近的时候,
# 这两个人应该具有较为相似的疾病表现
# 我们设置weight='distance'
model2 = KNeighborsClassifier(n_neighbors=5, weights='distance')
model2.fit(X_train, Y_train)
score2 = model2.score(X_test, Y_test)
交叉验证法并比较结果
# 使用交叉验证
res1 = cross_val_score(model1, X_train_all, Y, cv=10)
res2 = cross_val_score(model2, X_train_all, Y, cv=10)
# 打印结果
print(score1, score2)
print(res1.mean(), res2.mean())
结果为:
0.7402597402597403 0.7532467532467533
0.7395933014354068 0.7369958988380042
确定K的取值
# 确定K的值
score = []
alphas = []
for alpha in range(1,50,1):
alphas.append(alpha)
rdg = KNeighborsClassifier(alpha)
sc = np.sqrt( -cross_val_score(rdg,X_train_all,Y,scoring = "neg_mean_squared_error", cv = 10))
score.append(sc.mean())
plt.plot(alphas,score)
plt.show()
结果如图所示
由上图可以看出,K的值为29时,损失是最小的,因此选K=29
训练模型
# 模型训练与评估
# K-近邻算法中K的取值应该为奇数,为了能够得到当前样本对应的分类
# 以下为每个点权重相等的时候的模型
model1 = KNeighborsClassifier(n_neighbors=29)
model1.fit(X_train, Y_train)
score1 = model1.score(X_test, Y_test)
# 我们认为,两个人身体特征相同或者相近的时候,
# 这两个人应该具有较为相似的疾病表现
# 我们设置weight='distance'
model2 = KNeighborsClassifier(n_neighbors=29, weights='distance')
model2.fit(X_train, Y_train)
score2 = model2.score(X_test, Y_test)
# 使用交叉验证
res1 = cross_val_score(model1, X_train_all, Y, cv=10)
res2 = cross_val_score(model2, X_train_all, Y, cv=10)
# 打印结果
print(score1, score2)
print(res1.mean(), res2.mean())
结果如下:
0.8311688311688312 0.8181818181818182
0.7773581681476418 0.7708475734791524
绘制学习曲线
#绘制学习曲线
from sklearn.model_selection import learning_curve
from sklearn.model_selection import ShuffleSplit
def plot_learning_curve(estimator, title, X, y, ylim=None, cv=None,
n_jobs=1, train_sizes=np.linspace(.1, 1.0, 5)):
plt.figure()
plt.title(title)
if ylim is not None:
plt.ylim(*ylim)
plt.xlabel("Training examples")
plt.ylabel("Score")
train_sizes, train_scores, test_scores = learning_curve(
estimator, X, y, cv=cv, n_jobs=n_jobs, train_sizes=train_sizes)
train_scores_mean = np.mean(train_scores, axis=1)
train_scores_std = np.std(train_scores, axis=1)
test_scores_mean = np.mean(test_scores, axis=1)
test_scores_std = np.std(test_scores, axis=1)
plt.grid()
plt.fill_between(train_sizes, train_scores_mean - train_scores_std,
train_scores_mean + train_scores_std, alpha=0.1,
color="r")
plt.fill_between(train_sizes, test_scores_mean - test_scores_std,
test_scores_mean + test_scores_std, alpha=0.1, color="g")
plt.plot(train_sizes, train_scores_mean, 'o-', color="r",
label="Training score")
plt.plot(train_sizes, test_scores_mean, 'o-', color="g",
label="Cross-validation score")
plt.legend(loc="best")
return plt
knn = KNeighborsClassifier(n_neighbors=21)
cv = ShuffleSplit(n_splits=10, test_size=0.1, random_state=1024)
plt.figure(figsize=(10, 6))
plot_learning_curve( knn, "Learn Curve for KNN Diabetes",
X_train_all, Y, (0.5,1.01), cv=cv);
plt.show()
结果图如下:
导入待预测的数据进行预测
先前处理好了数据,在这里直接用X_tobe_test
res=pd.DataFrame(model1.predict(X_tobe_test))
outcome=pd.concat([test_id,res],axis=1)
outcome.to_csv('./outcome.csv',header=['ID','Outcome'],index=False,encoding='utf_8_sig')
尝试PCA降维
考虑特征是否有多余的,由此使用person相关分析如下:
# 导入数据
data = pd.read_csv("./PimaIndiansdiabetes.csv")
X = data.iloc[:, 0:8]
Y = data.iloc[:, -1]
# 先看一下person相关系数矩阵(这里画成了热力图,方便比较)
corr = data.corr() # 计算变量的相关系数,得到一个N * N的矩阵
plt.subplots(figsize=(14,12)) # 可以先试用plt设置画布的大小,然后在作图,修改
sns.heatmap(corr, annot = True) # 使用热度图可视化这个相关系数矩阵
plt.show()
结果如图所示:
可以看出,虽然有一些特征与Outcome的相关系数并不大,尝试降维。
划分数据集以及标准化处理与以上方法一致
代码如下:
# 进行PCA降维
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt # 可视化
from sklearn.decomposition import PCA
pca = PCA(n_components=0.90)# 保证降维后的数据保持90%的信息
pca.fit(X_all_scaled)
X_all_scaled = pca.transform(X_all_scaled)
X_all_scaled
结果如下:
[[ 1.40387844 -0.71387504 0.02562345 ... -0.44985738 -0.87652201
0.72606078]
[-1.50773494 0.25009407 -0.46039451 ... -0.3366215 0.36535497
0.92392879]
[ 0.07073991 -1.00461334 1.84145542 ... 0.06379412 -1.4691181
-0.40918533]
...
[-0.74972145 -0.64302622 0.31654953 ... -0.08207593 0.12649778
-0.03331613]
[-0.75295036 -0.45294644 0.53962514 ... -0.12035791 -0.81639477
0.88124598]
[-1.20872818 0.76484397 -0.83339878 ... -0.09029731 0.25933703
0.43746935]]
--------------------------------------
[[ 2.34879714e-01 -1.94684846e-02 1.65382999e-01 2.36213843e-01
-1.85280470e+00 4.02796837e-01 2.31006438e-01]
[ 1.17605825e+00 -1.66362332e+00 1.05546164e-01 -7.63710104e-01
-5.28956666e-01 -5.76403919e-01 3.71885086e-02]
[ 1.40627477e+00 -6.21163929e-01 1.27042072e+00 2.56720733e-01
-2.79723364e-01 -7.47499464e-01 1.24579210e-01]
...
[-7.94550283e-01 -4.90206809e-01 5.77582712e-01 -7.51469579e-02
-7.50135533e-02 -8.12100720e-01 8.28709007e-01]
[-1.20872818e+00 7.64843966e-01 -8.33398782e-01 -2.57749620e-01
-9.02973092e-02 2.59337032e-01 4.37469354e-01]]
确定k值如图所示:
于是取K=39
然后训练模型
# 可以看出,在K值取到39时具有较好的效果,这里我们取K=39
# 模型训练与评估
# K-近邻算法中K的取值应该为奇数,为了能够得到当前样本对应的分类
# 以下为每个点权重相等的时候的模型
model1 = KNeighborsClassifier(n_neighbors=39)
model1.fit(X_train, Y_train)
score1 = model1.score(X_test, Y_test)
# 我们认为,两个人身体特征相同或者相近的时候,
# 这两个人应该具有较为相似的疾病表现
# 我们设置weight='distance'
model2 = KNeighborsClassifier(n_neighbors=39, weights='distance')
model2.fit(X_train, Y_train)
score2 = model2.score(X_test, Y_test)
# 使用交叉验证
res1 = cross_val_score(model1, X_train_all, Y, cv=10)
res2 = cross_val_score(model2, X_train_all, Y, cv=10)
# 打印结果
print(score1, score2)
print(res1.mean(), res2.mean())
结果为:
0.7532467532467533 0.7922077922077922
0.7799726589200273 0.7786397812713604
可以看到,效果比没有使用PCA降维效果好一点点。