# 【5分でわかりやすく解説】ｋ近傍法とは？理論とRでの実装方法！

> 当サイト【スタビジ】の本記事では、アルゴリズムがシンプルで分かりやすいk近傍法について理解を深めていきます。最終的にはRでの実装を行うことで理論と実装の両輪を理解していきましょう！

- URL: https://toukei-lab.com/%ef%bd%8b%e8%bf%91%e5%82%8d%e6%b3%95
- 公開: 2022.11.29 / 更新: 2025.01.04
- サイト: スタビジ（https://toukei-lab.com）

:::talk{who="umatan"}
本記事では、アルゴリズムがシンプルで分かりやすいk近傍法について理解を深めていきます。最終的にはRでの実装を行うことで理論と実装の両輪を理解していきましょう！
:::

こんにちは！

データサイエンティストのウマたん([@statistics1012](https://twitter.com/statistics1012?lang=ja))です！

アルゴリズムが分かりやすくて使いやすい手法であるk近傍法！

:::talk{who="robotan"}
強力な手法とは言えないけど、感覚的に分かりやすくていいよねー！
:::

:::talk{who="umatan"}
そうそう！説明もしやすいしね！
:::

この記事ではそんなk近傍法というクラス判別の手法をご説明します！

アルゴリズムも非常に分かりやすく使いやすい**k近傍法**についてみていきましょう！

最後には**Rでの実装方法**も見ていきますよー！

:::talk{who="umatan"}
k近傍法は使いやすい機械学習手法！
:::

:::box{title="この記事で分かるコト"}
・[k近傍法のアルゴリズム](#k-2)\
・[k近傍法のR実装法](#kR)
:::

他の手法についてはこちらも合わせてご参照ください！

<https://toukei-lab.com/pattern-recognition>

<https://toukei-lab.com/ai-term>

## k近傍法とは

![](/wp-content/uploads/2019/04/52ceff2ccf51b6465679ab4c976fc8b0_s.jpg)

それではk近傍法の解説からはじめましょう！

以下の動画でも解説しているのでぜひチェックしてみてください！

::youtube{#7HEQy4BoBiQ}

k近傍法とは<mark>データを分類する時に活躍する手法</mark>です。

ある未知データが与えられたとき、周りの学習データのクラスからその未知データの分類を決定するもの。

そしてkとは手法に与えるパラメーターで、近くに存在する学習データのクラス数を示しています。

たとえば、このような状況では、

![](/wp-content/uploads/2018/02/66c19942ab4ba346fdb64ccc04cde373-3.png)

k＝3なので未知データの周りの3つのデータを判断材料とします。

実際に見てみると、<mark>青〇のクラスが2つ、緑△のクラスが1つなので、未知データは青〇と判断されます。</mark>

続いてこのkを5にするとどうなるでしょう？

![](/wp-content/uploads/2018/02/44d29d2792be3bad9a9ed98c7bbb20d6.png)

k＝5なので周りの5個を判断材料とします。

実際に見てみると、青〇のクラスが2つ、緑△のクラスが3つなので、未知データは緑△と判断されます。

<mark>kの値を変えることで分類されるクラスが変わりましたね！</mark>

今回挙げた例は非常に分類が難しい例であり、このような場合はそれほど多くありませんがkの値によって分類されるクラスが変わる可能性はあるよ！というお話でした。

ちなみに<mark>Rで解析する時はclassパッケージのknnという関数を使うと良いでしょう！</mark>

この時デフォルトではk＝1になっています。すなわちこれは、近くの1つしか判断材料に入れないということ。

これは<mark>最近傍法というk近傍法の特殊な例になってしまい局所最適に陥りやすい</mark>です。

あまりにも大きすぎるkの値は不均衡データに対して上手く分類できなくなってしまう可能性もありますが、ある程度大きい値にした方が良いでしょう！

個人的には5が推奨です。解析の時は大抵5を選択していました。

実はこのk近傍法の考え方を不均衡データに応用したSMOTEという方法があります。

<https://toukei-lab.com/imbalance-data-smote>

## k近傍法の識別規則

![Check](/wp-content/uploads/2019/09/b8bc8c6db6cbd74d3cfddb22f7d9a699_s.jpg)

それでは、具体的にk近傍法の識別規則について数式で見ていきましょう！

\begin{eqnarray}\
\left\\{\
\begin{array}{l}\
j&…&((k_j)=max(k_1,…,k_K)の時)\\\\\
reject&…&((k_1,…,k_j)=max(k_1,…,k_K)の時)\\\\\
\end{array}\
\right.\
\end{eqnarray}

非常に簡単な数式になりました！

この時$k_i$は近傍kのうち、クラスiに属するデータ数を表しています。

近傍kの中でも最もクラスjに属するデータが多いときそのデータをjと識別します。

<mark>最もデータ数が多いクラスが多数存在する場合は識別不能となります。</mark>

k近傍法は非常に分かりやすいアルゴリズムなのでとっかかりやすいです！

Rのパッケージで一発で出来ちゃうんで使ってみましょう！

ちなみにk近傍法と名前の似ている手法にk-means法という機械学習手法があります。

名前は似ていても手法は全く違います！k-means法は教師なし学習でありクラスター分析の一種！

k-means法については以下の記事でまとめているのでよければご覧ください！

<https://toukei-lab.com/k-means>

## k近傍法をRで実装！

![](/wp-content/uploads/2018/08/PAK85_MBAdesagyou20140312_TP_V-e1557018085482.jpg)

k近傍法はRやPythonで簡単に実装することができます。実際にRを用いてk近傍法を実装してみましょう！

今回は、[Kaggleの公式サイト](https://www.kaggle.com/c/titanic/data)で提供されているTitanicのデータを使います。

kaggleに関しては以下の記事で詳しく解説しているのであわせてチェックしてみてください。

<https://toukei-lab.com/kaggle>

データのクレンジングを行い、[欠損値](/missing-value)は削除、不要と考えられる項目は削除します。

生死（Survive）を目的変数としそれ以外を説明変数とします。

サンプル数714のうちランダムに400個のデータを取り出し学習データとし、残りの314を予測データとします。

学習データで予測モデルを作り、予測データにあてはめ真値と予測値の判別率を精度として比較します。

シミュレーション回数は10回とし、上記の手順を10回繰り返し、結果を平均したものを最終アウトプットとします。

k近傍法を[XGboost](/xgboost)、[ランダムフォレスト](/%e3%83%a9%e3%83%b3%e3%83%80%e3%83%a0%e3%83%95%e3%82%a9%e3%83%ac%e3%82%b9%e3%83%88)、[サポートベクターマシン](/svm)、[ナイーブベイズ](/naivebaise)、[ニューラルネットワーク](/nn)の5手法と比較します！他の手法については他の記事をご覧ください。

```r title="knn-comparison.R"
##様々な手法で予測精度の比較を行う##
##randomForest,SVM,ナイーブベイズ，ニューラルネット
library(xgboost)
library(caret)
library(Matrix)
library(kernlab)
library(randomForest)
library(e1071)
library(nnet)
library(ggplot2)
library(class)
titanic<-read.csv("タイタニック乗船者リスト.csv")##データの読み込み
titanic<-na.omit(titanic)##欠損値削除
titanic$Survived<-as.factor(titanic$Survived)##質的変数に変換
sim<-10##シミュレーション回数
result<-matrix(0,sim,6)##結果の箱
titanic[,4]<-as.numeric(titanic[,4])
titanic[,9]<-as.numeric(titanic[,9])

for(i in 1:sim){
  ####学習データとテストデータに分ける####
  train.id<-sample(nrow(titanic),400)
  train.data<-titanic[train.id,-1]
  test.data<-titanic[-train.id,-1]
  
  
  ##randomForest##
  rf<-randomForest(Survived~.,train.data)
  test.data.rf<-cbind(test.data,"predict"=predict(rf,test.data))
  result[i,1]<-sum(test.data.rf$predict==test.data.rf$Survived)/nrow(test.data.rf)
  
  
  ##SVM##
  svm<-ksvm(Survived~.,train.data)
  test.data.svm<-cbind(test.data,"predict"=predict(svm,test.data))
  result[i,2]<-sum(test.data.svm$predict==test.data.svm$Survived)/nrow(test.data.svm)
  
  ##ナイーブベイズ##
  nb<-naiveBayes(Survived~.,train.data)
  test.data.nb<-cbind(test.data,"predict"=predict(nb,test.data))
  result[i,3]<-sum(test.data.nb$predict==test.data.nb$Survived)/nrow(test.data.nb)
  
  ##ニューラルネット##
  nn<-nnet(Survived~.,train.data,size=5)
  test.data.nn<-cbind(test.data,"predict"=round(predict(nn,test.data),0))
  result[i,4]<-sum(test.data.nn$predict==test.data.nn$Survived)/nrow(test.data.nn)
  
  ##XGboost##
  label.data.train<-as.integer(train.data$Survived)-1##目的変数を01に
  label.data.predict<-as.integer(test.data$Survived)-1
  
  train.data.xg<-train.data[,-1]##目的変数取り除く
  test.data.xg<-test.data[,-1]
  
  xgb.data<-xgb.DMatrix(data.matrix(train.data.xg),label=label.data.train)##Xgb型へ成型
  xgb.data.predict<-xgb.DMatrix(data.matrix(test.data.xg))
  
  param <- list(
    "objective" = "binary:logistic"
    ,"eta" = 0.01
    # ,"max_depth" = 10
    ,"min_child_weight" = 5
    # ,"subsample" = 1
    # ,"colsample_bytree" = 1
  )##パラメータ適当
  
  model <- xgboost(
    param=param
    ,data=xgb.data
    ,nrounds=1000
  )##実際の学習
  
  predict_xgb<-predict(model,xgb.data.predict)
  
  a<-c()
  m<-length(predict_xgb)#行数
  
  for(l in 1:m){
    if(predict_xgb[l]>0.5){a[l]<-1}
    else{a[l]<-0}
  }
  result[i,5]<-sum(a==label.data.predict)/length(a)
  
  #k-nn
  knn<-knn(train.data,test.data,train.data[,"Survived"] ,prob=T,k=3)
  test.data.knn<-cbind(test.data,"predict"=knn)
  result[i,6]<-sum(test.data.knn$predict==test.data.knn$Survived)/nrow(test.data.knn)
  
}

result.c<-apply(result,2,mean)##simの平均値
result.c<-t(as.matrix(result.c))
result.c<-rbind(result.c,result.c)
result.c<-round(result.c,3)
##グラフ描画
colnames(result.c) <- c("RF","SVM","Bayes","NN","xgb","knn")
df<-data.frame(method=colnames(result.c),result=colMeans(result.c))
gp<-ggplot(df,aes(x=method,y=result,fill=method))
gp<-gp+geom_bar(width=0.8,stat="identity")
gp<-gp+ylab("result")
gp<-gp+geom_text(label=colMeans(result.c),vjust=1)
plot(gp)
```

classというパッケージに入っているknnという関数を用います。

他の手法では大丈夫なんですが、knnは文字列として入っている質的変数を数値に直さなくてはいけないのでこのような処理を施しています。

```python
titanic[,4]<-as.numeric(titanic[,4])
titanic[,9]<-as.numeric(titanic[,9])
```

関数knnでは学習データと未知データと目的変数を与えます。データによっては、パラメータkで精度が大きく変わる可能性があります。

```python
#k-nn
knn<-knn(train.data,test.data,train.data[,"Survived"] ,prob=T,k=3)
test.data.knn<-cbind(test.data,"predict"=knn)
result[i,6]<-sum(test.data.knn$predict==test.data.knn$Survived)/nrow(test.data.knn)
```

結果は以下のようになりました。やはりk近傍法が最も精度が低い・・・

![](/wp-content/uploads/2018/02/ml-comparison.png)

他の機械学習手法に比べるとアルゴリズムが非常にシンプルなのでしょうがない！

:::talk{who="umatan"}
それでも解釈性は高いから、ビジネスシーンで用いられることは多いんだよ！
:::

## k近傍法　まとめ

本記事では、**k近傍法**について見てきました。

k近傍法はアルゴリズムがシンプルで分かりやすく、<mark>精度も比較的高いのでビジネスシーンでも用いられることが多いです。</mark>

是非k近傍法を使ってみてください。

ちなみにk近傍法に関しては、機械学習の王道本「はじめてのパターン認識」で取り上げていますのでそちらを読んで理解を深めると良いでしょう！

::product[はじめてのパターン認識]{url="https://www.amazon.co.jp/dp/4627849710?tag=statistic08-22&linkCode=ogi&th=1&psc=1" image="https://m.media-amazon.com/images/I/51YFB3Y0EYL._SL160_.jpg" amazon="https://www.amazon.co.jp/dp/4627849710?tag=statistic08-22&linkCode=ogi&th=1&psc=1" rakuten="https://af.moshimo.com/af/c/click?a_id=858168&p_id=54&pc_id=54&pl_id=616&url=https%3A%2F%2Fsearch.rakuten.co.jp%2Fsearch%2Fmall%2F%25E3%2581%25AF%25E3%2581%2598%25E3%2582%2581%25E3%2581%25A6%25E3%2581%25AE%25E3%2583%2591%25E3%2582%25BF%25E3%2583%25BC%25E3%2583%25B3%2F%3Ff%3D1%26grp%3Dproduct"}

もし機械学習手法について理解を深めたいのであれば以下の記事を参考にしてみてください！

<https://toukei-lab.com/machine-learning-fresh>

また、機械学習の勉強やデータサイエンスの勉強については以下の記事で詳しく解説しています！

<https://toukei-lab.com/machine-learning-study>

<https://toukei-lab.com/data-scientist>