目次(まとめ)
◾️ データの特徴を学習する「訓練データ」として、手元のデータを使用する
◾️学習成果を評価する「テストデータ」として、未来のデータを使用する
◾️k最近傍法は、Rのclassパッケージのknn関数で実行できる
◾️「訓練データ」と「テストデータ」の分割は、ランダムに行うのが一般的
こんにちは、みっちゃんです。
以前の記事で、多数決でデータを振り分ける「k最近傍法」を紹介しました。
今回の記事では
「いくつかのグループに属することがわかっているデータが手元にあって、未来のデータをどのグループに分けるべきかわからない」
もしくは
「手元のデータを使って、未来のデータを適切にグループ分けする方法が知りたい」
という方向けに、Rを使ってk最近傍法を実行する方法を紹介します。
データの特徴を学習する「訓練データ」として、手元のデータを使用する
以前の記事で解説したように、k最近傍法は「未来のデータが、所属グループがわかっている手元のデータにどれぐらい近いのか」という情報をもとに、未来のデータを適切なグループに振り分けます。
これを可能にするためには、まず、手元のデータを分析して、どのグループに所属するグループがどういう特徴をもつデータなのかを調べる必要があります。
機械学習の専門用語を使うと「訓練データを使った学習」です。
ここでは、以前の記事でも使用してきたように、説明のために、Rにあらかじめ準備されているアヤメのデータ(iris)を使用します。
> head(iris)
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 5.1 3.5 1.4 0.2 setosa
2 4.9 3.0 1.4 0.2 setosa
3 4.7 3.2 1.3 0.2 setosa
4 4.6 3.1 1.5 0.2 setosa
5 5.0 3.6 1.4 0.2 setosa
6 5.4 3.9 1.7 0.4 setosa
このデータは、150行5列の行列になっており、5列目には、アヤメの種類の名前("setosa", "versicolor", "virginica")が示されていて、それぞれの種類に対して50行のデータがあります。
つまり、3つのグループに所属することがわかっているデータがあるということです。
ここでは、"dplyr" ライブラリの "filter" という関数を使って、それぞれに所属する行だけを抜き出してみたいと思います。
> library(dplyr)
> group_set <- filter(iris, iris$Species == "setosa")
> group_ver <- filter(iris, iris$Species == "versicolor")
> group_vir <- filter(iris, iris$Species == "virginica")
"filter" 関数の使い方は、「filter(全データ、取り出すデータの指定)」です。1行目では、"iris" のデータのうち、5列目の "iris$Species" が "setosa" に一致する行だけを抜き出しています。
それぞれのグループのデータは、50行ずつありますが、ここでは、先頭40行を「訓練データ」にしたいと思います。
そのために以下のように実行します。
> train_size <- 40
> train_feature <- rbind(group_set[1:train_size,-5], group_ver[1:train_size,-5], group_vir[1:train_size,-5])
> train_label <- c(group_set[1:train_size,5], group_ver[1:train_size,5], group_vir[1:train_size,5])
「訓練データ」は、「データの特徴(train_feature)」と「データが所属するグループ(train_label)」のペアの情報からなっています。
データの中身を確認すると、以下のようになっています。
> head(train_feature)
Sepal.Length Sepal.Width Petal.Length Petal.Width
1 5.1 3.5 1.4 0.2
2 4.9 3.0 1.4 0.2
3 4.7 3.2 1.3 0.2
4 4.6 3.1 1.5 0.2
5 5.0 3.6 1.4 0.2
6 5.4 3.9 1.7 0.4
> head(train_label)
[1] 1 1 1 1 1 1
"train_label" に関しては、本来、"setosa", "versicolor", "virginica" なのですが、自動的に、"setosa"→"1", "versicolor"→"2", "virginica"→"3" と置き換わっています。
学習成果を評価する「テストデータ」として、未来のデータを使用する
先ほど「訓練データ」として、50行のうち先頭40行を用いました。
ここでは、未来のデータを想定して、残り10行のデータを「テストデータ」として用いたいと思います。
ここで「テスト」の意味は、「訓練データ」を用いて、どういう特徴をもつデータがどのグループに所属するのかを学習したので、その学習成果を評価するという意味です。
先ほどと同様に、以下のように実行します。
> test_feature <- rbind(group_set[(train_size+1):nrow(group_set),-5], group_ver[(train_size+1):nrow(group_ver),-5], group_vir[(train_size+1):nrow(group_vir),-5])
> test_label <- c(group_set[(train_size+1):nrow(group_set),5], group_ver[(train_size+1):nrow(group_ver),5], group_vir[(train_size+1):nrow(group_vir),5])
k最近傍法は、Rのclassパッケージのknn関数で実行できる
上で準備した「訓練データ」である "train_feature" と "train_label"、「テストデータ」である "test_feature" と "test_label" を使用して、k最近傍法を実行・評価します。
k最近傍法は、以下のように、Rの "class" パッケージの "knn" 関数を使って実行できます。
> library(class)
> predicted_label <- knn(train_feature, test_feature, train_label, k = 6, prob = TRUE)
このように実行すると、k = 6 としたときのk最近傍法が実行され「テストデータ」である "test_feature" に対して予測されたグループが "predicted_label" に出力されます。
"knn" 関数を用いてk最近傍法を実行する場合、多数決でグループを決められない場合には、ランダムにグループが割り当てられます。
実行結果を見るために、以下のように実行します。
> predicted_label
[1] 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3
attr(,"prob")
[1] 1.0000000 1.0000000 1.0000000 1.0000000 1.0000000 1.0000000 1.0000000
[8] 1.0000000 1.0000000 1.0000000 1.0000000 1.0000000 1.0000000 1.0000000
[15] 1.0000000 1.0000000 1.0000000 1.0000000 1.0000000 1.0000000 1.0000000
[22] 0.8333333 0.8333333 1.0000000 1.0000000 1.0000000 0.6666667 0.8333333
[29] 1.0000000 0.6666667
Levels: 1 2 3
1行目に並んでいる数字が「テストデータ」の "test_feature" に対して予測されたグループ(番号)です。
また、実行の際に「prob = TRUE」としたため、それぞれのテストデータに対して、そのグループの得票率が表示されています。
例えば、30個(10個×3)のテストデータのうち、最初のデータに対しては、その近傍にある6つの点(訓練データ)がすべてグループ1("setosa")に所属していたため、グループ1の得票率が100%だったことを意味しています。
逆に、最後のデータに対しては、その近傍にある6つの点(訓練データ)のうち4つがグループ3("virginica")に所属していたため、グループ3の得票率が66.6%であり、多数決で他のグループに勝ったことを意味しています。
ちなみに、以下のように実行することで、30個のデータ全てに対する正答率を計算できます。
> sum(predicted_label == test_label)/length(test_label)
1
この場合には、"1" となるので、すべてのテストデータについて、正しいグループを予測できたということを意味しています。
「訓練データ」と「テストデータ」の分割は、ランダムに行うのが一般的
上の例では「訓練データ」と「テストデータ」を分ける際に、データの上から40個を訓練データに、、といった操作をしていました。
しかし、このような操作では、「訓練データ」と「テストデータ」との間で、データの偏りが生じたりして、正しい評価ができなくなる危険性があります。
そこで一般的には、ランダムにデータを分けることが行われます。
例えば、以下のように実行すると、50個のデータのうち、ランダムに約8割のデータを「訓練データ」に、残り約2割のデータを「テストデータ」にすることができます。
(例)
> ind <- sample(c(TRUE, FALSE), 50, replace=TRUE, prob=c(0.8, 0.2))
> train_feature < group_set[ind,]
> test_feature < group_set[!ind, ]
「replace=TRUE」とすると、「c(TRUE, FALSE)」から復元抽出するということになります。