2020/06/07
今回は「
15. k近傍法(kNN)」について、PythonとRで計算していきたいと思います。教材は今回も『データサイエンス教本』を参考にしました。
k近傍法(k-Nearest Neighbor: kNN)とは、Wikipediaの
解説にもあるように、あるオブジェクト(データ)の「分類」を、その近傍の「k個」のオブジェクト群の投票によって決定するアルゴリズムで、近傍を選ぶにあたっては、各オブジェクト間の「ユークリッド距離」を使う手法です。PythonでもRでもkNNの関数を持っているので、簡単に実行できます。
まずは、教材にあるPythonのコードです。kNNを実行するには、scikit-learnのKNeighborsClassifierパッケージを利用しました。breast_cancerというデータセットを用い、30個の説明変数から癌であるか否かを分類しました。
from sklearn.datasets import load_breast_cancer
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
# data read
cancer = load_breast_cancer()
# data split
X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, stratify = cancer.target, random_state=0)
# list for graph
training_accuracy = []
test_accuracy = []
# training
for n_neighbors in range(1, 21):
model = KNeighborsClassifier(n_neighbors=n_neighbors)
model.fit(X_train, y_train)
training_accuracy.append(model.score(X_train, y_train))
test_accuracy.append(model.score(X_test, y_test))
# graph
plt.plot(range(1, 21), training_accuracy, label='Training')
plt.plot(range(1, 21), test_accuracy, label='Test')
plt.ylabel('Accuracy')
plt.xlabel('n_neighbors')
plt.legend()
plt.show()
10行目のtrain_test_split関数の中で、stratifyを指定して「層別サンプリング」を実行しています。trainとtestとでcancer.targetの癌であるか否かの「割合」を合わせるための処理です。詳細は後ほどのRのコードで紹介します。
17行目以降で近傍の数kを1〜20まで変化させた時のtrainとtestの分類精度を計算し、24行目以降でグラフ化しています。グラフ化の結果は以下の通りです。

近傍数kが17、18あたりがtrainとtestの精度が同程度で安定しているように見えました。
次に、Rのコードです。RにPythonのような「層別サンプリング」を一発で実行できる関数のパッケージを探したのですが、見つけることができなかったので、少し面倒な処理をしています・・。層別サンプリングを用いた場合/用いない場合の比較もしました。最終のグラフ化は層別サンプリングを用いたデータで行いました。
library(dplyr)
# data read
url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/wdbc.data'
cancer_temp = read.csv(url, header = FALSE)
print(head(cancer_temp))
cancer = cancer_temp[,3:32]
cancer.target = as.data.frame(ifelse(cancer_temp[,2] == 'B', 1, 0))
df = data.frame(cancer, cancer.target)
names(df)[31] = "cancer.target"
set.seed(1)
# Normal sampling
train.rate = 0.7 # training data rate
train.index1 = sample(nrow(df),nrow(df) * train.rate)
df_Train1 = df[train.index1 ,]
df_Test1 = df[-train.index1 ,]
ratio_Train1 = sum(df_Train1['cancer.target']) / nrow(df_Train1['cancer.target'])
ratio_Test1 = sum(df_Test1['cancer.target']) / nrow(df_Test1['cancer.target'])
cat("train=", nrow(df_Train1), "test=", nrow(df_Test1), "\n")
cat("ratio train=", ratio_Train1, "ratio test=", ratio_Test1, "\n")
# stratified sampling
df1 = cbind(1:nrow(df), df)
names(df1)[1] = "samp.no"
df2 = df1 %>%
group_by(cancer.target) %>%
sample_frac(0.7) %>%
ungroup()
train.index2 = df2$samp.no
test.index2 = as.integer(setdiff(as.list(1:nrow(df1)), as.list(train.index2)))
df_Train2 = df1[train.index2, 2:32]
df_Test2 = df1[test.index2, 2:32]
ratio_Train2 = sum(df_Train2['cancer.target']) / nrow(df_Train2['cancer.target'])
ratio_Test2 = sum(df_Test2['cancer.target']) / nrow(df_Test2['cancer.target'])
cat("train=", nrow(df_Train2), "test=", nrow(df_Test2), "\n")
cat("ratio train=", ratio_Train2, "ratio test=", ratio_Test2, "\n")
# kNN
library(class)
accurary.train = array(0, dim = c(20))
accurary.test = array(0, dim = c(20))
for (i in 1:20) {
res.train = knn(df_Train2[,1:30], df_Train2[,1:30], df_Train2$cancer.target, k=i)
res.test = knn(df_Train2[,1:30], df_Test2[,1:30], df_Train2$cancer.target, k=i)
accurary.train[i] = sum(res.train == df_Train2$cancer.target) / nrow(df_Train2)
accurary.test[i] = sum(res.test == df_Test2$cancer.target) / nrow(df_Test2)
}
plot(c(1:20), accurary.train[1:20], ylim=c(0.88, 1.00), xlab='', ylab='', type = 'l',col='blue')
par(new=T)
plot(c(1:20), accurary.test[1:20], ylim=c(0.88, 1.00), xlab='n_neighbors', ylab='Accuracy', type = 'l',col='red')
legend("topright",
legend=c("training", "test"),
lty=c(1,1),
col=c("blue", "red")
)
Rコードの実行結果は以下の通りです。
# 24行目
train= 398 test= 171
# 25行目
ratio train= 0.6407035 ratio test= 0.5964912
# 45行目
train= 398 test= 171
# 46行目
ratio train= 0.6281407 ratio test= 0.625731
16〜25行目の層別サンプリングを行わない場合は、25行目の結果にあるように、trainとtestとでcancer.targetの癌であるか否かの「割合」が異なります。ちなみに当然のことながら13行目のようにseedを固定しておかないと、この割合はプログラムを実行毎に変わります。28〜46行目の層別サンプリングを行った場合は、trainとtestとで癌であるか否かの「割合」がほぼ同じになりました(46行目)。
RでkNNを実行するのにclassパッケージのknn関数を用いました。Pythonコードで実行したように、kを1〜20に変化させた時の精度のグラフは以下の通りです。データサンプリングも異なるため、完全にはPythonの結果と同じにはなりませんが、類似のグラフが得られました。

次回はサポートベクターマシン(SVM)について、勉強しようと思います。
『RからPythonへの道』バックナンバー(1)
はじめに(2)
0. 実行環境(作業環境)(3)
1. PythonからRを使う方法 2. RからPythonを使う方法(4)
3. データフレーム(5)
4. ggplot(6)
5.行列(7)
6.基本統計量(8)
7. 回帰分析(単回帰)(9)
8. 回帰分析(重回帰)(10)
9. 回帰分析(ロジスティック回帰1)(11)
10. 回帰分析(ロジスティック回帰2)(12)
11. 回帰分析(リッジ、ラッソ回帰)(13)
12. 回帰分析(多項式回帰)(14)
13. 決定木(分類)(1)(15)
14. 決定木(分類)(2)