{ "cells": [ { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "# R notebook の基本\n", "\n", "**R notebook**とは,markdown方式によるドキュメントで,独立してすぐに実行できるR言語のコードの塊(**セル;cell**)といっしょに記述できます.セルに記述したコードの実行結果はセルのすぐ下に表示されます.\n", "\n", "「In [整数]」はひとつのセルを表します.そして,セルにはR言語によるコードが記述されています. セルをクリックすると,クリックしたセルにカーソルが移動します.カーソルのあるセルで,Shiftキーを押しながらEnterキーを押す(**Shiftキー+Enterキー**)と,カーソルのあるセル内のR言語のコードが実行され,次のセルにカーソルが移動します.その他のセルのコードは実行されません.\n", "\n", "コードの実行を試すために,下に3つのセルを準備しました.まずは1つ目のセルを実行(**Shiftキー+Enterキー**)してみてください.\n", "\n", "これは簡単なコードで,セルの1行目で変数aに1を代入し,セルの2行目で変数bに2を代入しています.そして,セルの3行目で変数aと変数bの和(a+b)を計算して表示するコードです.\n", "\n", "うまく実行できたら,その下のセルも実行(**Shiftキー+Enterキー**)してみてください.\n", "\n", "セルの1行目で変数cに3を代入し,セルの2行目で a+b+c を計算して表示するコードです.変数aと変数bは前のセルでの処理(a=1とb=2という代入処理)がそのまま引き継がれていることがわかります.\n", "\n", "最後に,3つ目のセルを実行(**Shiftキー+Enterキー**)してみてください.3つ目のセルはグラフを表示するコードです.R notebookを使うと,このようにドキュメントを記述しながら,グラフを表示したりすることができます.\n", "\n", "ここまで完了したら,3つ目のセルの下にある次の「[タスクML-1a_\"SVM体験のためのデータ\"](#タスクML-1a_\"SVM体験のためのデータ\")」に進んでください.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "deletable": true, "editable": true }, "outputs": [], "source": [ "a = 1\n", "b = 2\n", "a + b" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "deletable": true, "editable": true }, "outputs": [], "source": [ "c = 3\n", "a + b + c" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "deletable": true, "editable": true }, "outputs": [], "source": [ "plot(c(1:10))" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "# タスクML-1a_\"SVM体験のためのデータ\"\n", "\n", "R言語[1]がもつアヤメのデータのセット(データセット)を使って,「分類問題」の1つSVMを体験します.R言語には,練習用にたくさんのデータセットが含まれています([Documentation for package 'datasets'](https://stat.ethz.ch/R-manual/R-devel/library/datasets/html/00Index.html)).アヤメのデータは,3種類類のアヤメ(setosa,versicolor,virginica)のデータを含みます.3種類のアヤメのそれぞれ50本のデータで,各データは,がく片(sepal)と花弁(petal)のそれぞれの長さと幅の数値から構成されます.\n", "\n", "アヤメのデータセットは**変数iris**に格納されています.変数irisの中を眺めたいので,データの始めの方を眺める**関数head()**を実行してみます.下のように関数head()の引数に変数irisを入力してコードを実行します.また,下の1つ目のセルでは,**関数summary()**も利用しています.関数summary()は,引数に入力された変数のまとめ(サマリ)を表示してくれます.下の1つ目のセルを実行してみてください.コードの実行はShiftキー+Enterキーです.\n", "\n", "関数head()の結果と関数summary()の結果が表示されます.\n", "\n", "関数head()の結果としてデータセットの始めの方が表示されたと思います.アヤメのデータセットは,「Sepal.Length」「Sepal.Width」「Petal.Length」「Petal.Width」「Species」の5つの項目を持っていることがわかります.「Sepal.Length」はがく片の長さの値です.「Sepal.Width」はがく片の幅の値です.「Petal.Length」は,花弁の長さの値で,「Petal.Width」は,花弁の幅の値,「Species」はアヤメの種類を表します.関数head()を使って表示するとデータセットの始めの数行だけなので,項目「Species」には「sentosa」の1種類のように見えます.アヤメの種類が3種類であることを確認できません.\n", "\n", "関数summary()の結果をみると,それぞれの項目の最小値(Min.),第1四分位(1st Qu.),中央値(Median),平均(Mean),第3四分位(3rd Qu.),最大値(Max.)が表示され,Speciesについは,「setosa」「versicolor」「virginica」の3種があることが確認でき,それぞれ50個のデータがあることを確認できます.\n", "\n", "下の2番目のセルでは,アヤメのデータセットをグラフに描画します.\n", "\n", "グラフの描画には,**関数plot()**を用います.今回は,横軸に,がく片の長さ(Sepal.Length)をとり,縦軸に,がく片の幅(Sepal.Width)をとり,種類によって点の色を変えて,データをプロットします.関数plot()は様々な引数の取り方をするので,引数の詳細については省略します.下の2つ目のセルを実行してみてください(コードの実行:Shiftキー+Enterキー).プロットされたグラフを見ると,赤色の点とそれ以外の点がくっきり分かれていることが確認できます.\n", "\n", "ここまでできた人は,次の「[タスクML-1b_\"SVMの概要\"](#タスクML-1b_\"SVMの概要\")」へ進んでください.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "deletable": true, "editable": true }, "outputs": [], "source": [ "head(iris)\n", "summary(iris)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "deletable": true, "editable": true }, "outputs": [], "source": [ "library(RColorBrewer)\n", "cols = brewer.pal(8, \"Set1\")\n", "plot(iris$Sepal.Length, iris$Sepal.Width, pch=21, bg = c(cols[1], cols[2], cols[3])[unclass(iris$Species)])\n", "legend(\"topright\", legend = levels(iris$Species), pt.bg = c(cols[1], cols[2], cols[3]), pch = 21)" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "# タスクML-1b_\"SVMの概要\"\n", "\n", "上のグラフを眺めながらSVMの仕組みについて概観します.上のグラフを見てわかるように,アヤメはどうやら**がく片の長さ**と**がく片の幅**で,大きく2つに分かれていて,赤でプロットされているsentosaは,がく片の長さと幅で区別できそうです.sentosaであるか,sentosaではないか,という2つの領域に分ける斜めの線を引けそうです.SVMの目的は,トレーニングデータを使って,最適な境界を決め,未知のデータをプロットしたときに,その境界よりも左側にあれば,その未知のデータをsentosaと判定し,その境界よりも右側にあればsentosaではないと判定(分類)することです.\n", "\n", "あなたならどのように最適な斜め線を引きますか?ただ斜め線を引くだけではなく,できることなら赤の点とそれ以外の点とのちょうど真ん中に引きたいと思いませんか.コンピュータを使って「ちょうど真ん中」を探すには,計算して「ちょうど真ん中」の線を求めなければなりません.そのために考えられたのが「**マージン最大化**」という考え方です.マージンとは,二分するための斜めの線と,その線と最も近い点との距離をいいます.境界と最も近い位置にあるトレーニングデータの点のことを**サポートベクトル**といいます.SVMの名前の由来です.トレーニングデータの中のある点と未知だがあるはずの境界線との距離が最大になるように考えます.トレーニングデータの中のどの点がサポートベクトルであるかは,境界が引かれたときに,sentosaの点がすべて左側にあり,sentosaではない点はすべて右側にあるという条件から決まります.SVMは,このような制約条件のもと最適な境界線の式を求める最適化問題になります.境界を導出するための手法として,\n", "\n", "- ハードマージン(マージン最大化)\n", "- ソフトマージン\n", "- カーネルトリック\n", "\n", "などがあります.今回は機械学習の分類問題に対して,SVMという学習手法を扱いましたが,他にも,ナイーブベイズやパーセプトロンなど様々な分類手法が検討され,研究されています.\n", "\n", "\n", "参考:\n", "- [サポートベクターマシン,金久保正明先生,静岡理工科大学](http://www.sist.ac.jp/~kanakubo/research/neuro/supportvectormachine.html)\n", "- [Deisenroth, M. P., Faisal, A. A., & Soong Ong, C. (2018). Mathematics for Machine Learning -Draft-.](https://mml-book.com)\n", "\n", "# タスクML-1c_\"SVMのためのデータの準備\"\n", "\n", "データセットirisをもとにトレーニングデータとテストデータを構成しましょう.データセットirisには150個(アヤメ3種)のデータが格納されています.このうちの半分をランダムに取り出し,トレーニングデータとし,残りの半分をテストデータとして扱います.そのため,トレーニングデータを変数TrainingDataに格納し,テストデータを変数TestDataに格納します.\n", "\n", "下のセルを実行(コードの実行:Shiftキー+Enterキー)して,変数TrainingDataと変数TestDataを確認してください.関数head()でそれぞれのデータの上部のデータを表示するコードになっています.\n", "\n", "データの準備ができた人は[タスクML-1d_\"SVMによる分類\"](#タスクML-1d_\"SVMによる分類\")に進んでください.\n", "\n", "## [コードの解説](興味がある人のみ)\n", "\n", "**関数sample()**の第1引数に,1から150までの数列を表す「c(1:150)」を入力し,第2引数に取り出す個数である75を入力して,実行すると,1から150までの整数値からランダムに75個の整数が取り出された結果が出力されます.繰り返し実行すると,それらの値は毎回異なります.\n", "\n", "変数row_TrainingDataには,ランダムに選んだ75個の整数の数列を代入しておいて,その75個の整数に対応する行のデータを,データセットirisから取り出して,変数TrainingDataに格納しています(TrainingData = iris[row_TrainingData,]).これで変数TrainingDataには,データセットirisからランダムに選んだ75個のデータが格納されます.\n", "\n", "残りの75個をテストデータとして変数TestDataに格納しています.このとき,残りの75個を取り出すために,1から150までの整数のうち変数row_TrainingDataに格納されている整数を取り除く(setdiff(c(1:150), row_TrainingData))という処理を行なっています.**関数setdiff()**は,差集合を算出するための関数です.**関数intersect()**は第1変数と第2変数の数列に重複がないか確認するために利用しています.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "deletable": true, "editable": true }, "outputs": [], "source": [ "row_TrainingData = sample(c(1:150), 75)\n", "#row_TrainingData\n", "TrainingData = iris[row_TrainingData,]\n", "print(\"変数TrainingData\")\n", "head(TrainingData)\n", "row_TestData = setdiff(c(1:150), row_TrainingData)\n", "intersect(row_TrainingData, row_TestData)\n", "#row_TestData\n", "TestData = iris[row_TestData,]\n", "print(\"変数TestData\")\n", "head(TestData)" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "# タスクML-1d_\"SVMによる分類\"\n", "\n", "いよいよSVMによる分類です.ここではR言語の**パッケージ「e1071」**を用います.細かな設定が多く複雑ですが,高度な調整が可能です.それぞれの手法を理解して利用シーンにあわせて使用しなければなりません.実践するときはマニュアルや他の参考資料を確認しながら取り組んでください.\n", "\n", "参考:https://cran.r-project.org/web/packages/e1071/e1071.pdf\n", "\n", "下のセルについて簡単に説明を加えます.まずはトレーニングデータを使って学習器を生成します.トレーニングデータを**学習する**には**関数svm()**が用いられ,第1引数には目的変数と説明変数との関係式(モデル式)を記述します.データセットirisでは,花弁の長さ(Petal.Length),花弁の幅(Petal.Width),がく片の長さ(Sepal.Length),がく片の幅(Sepal.Width),アヤメの種類(Species)を扱うことができ,今回は,アヤメの種類(項目Species)を予測するので,「Species ~ 」として書き,Species以外の変数を説明変数として予測するので,「.(ピリオド)」を書きます.\n", "\n", "> Species ~ .\n", "\n", "次に関数svm()の第2引数には,**data属性**としてトレーニングデータを指定します.今回トレーニングデータは変数TrainingDataに格納しているので,「data = TrainingData」です.その他,**type属性**で指定しているのは,どのような手法で分類するかを指定します.C-classificationはソフトマージンで,ハードマージンに対していくつかの外れを許容する手法です.type属性を指定しない場合,C-classificationになります.**kernel属性**で指定しているのは利用する境界の形です.linearは超平面(2次元であれば直線)を表しています.SVMはここを工夫することができ非線形(曲線)で囲むように境界を設定することができます.kernel属性は,linear,polynomial,radial basis,sigmoidの4つを選択できます.どのような境界になりそうか検討して選択する必要がまります.\n", "\n", "機械学習の処理で,ちょっと独特なのが,関数svm()で処理した結果を変数svmModelに代入しているところです.変数svmModelにはトレーニングデータを使って学習した結果がオブジェクト(処理結果のあれこれをまとめたもの)として格納されています.学習器を使って**分類を判定する**ときは**関数predict()**を使い,第1引数に学習結果のオブジェクト(変数svmModel),第2引数にテストデータを指定します.テストデータは,変数TestDataに含まれる花弁の長さ(Petal.Length),花弁の幅(Petal.Width),がく片の長さ(Sepal.Length),がく片の幅(Sepal.Width)なので,TestDataに含まれていたSpeciesを除いて(TestData[,-5]),関数predictの第2引数に入力しています.\n", "\n", "それでは下のセルを実行(コードの実行:Shiftキー+Enterキー)して,関数svm()による学習と関数predict()による判定結果を確認してください.\n", "\n", "関数svm()を実行した結果として,変数svmModelを表示させると,学習したときのパラメータやサポートベクトルの数が表示されます.\n", "\n", "テストデータに対する判定結果は,関数predict()の処理結果を格納した変数resultに格納しています.判定結果と正解を比較するために,変数TestDataの6番目の項目として,追加しています(TestData <- data.frame(TestData, result)).変数TestDataを表示すると,5項目「Species」が正解で,6項目「result」が判定結果です.\n", "\n", "最後に,区別するための境界は十分な境界なのでしょうか.下のセルのコードでは,学習した結果をグラフ表示しています.これはパッケージ「e1071」の機能の1つです.下に表示されるグラフは,トレーニングデータと学習によって生成された境界を色分けして示しています.その中でもバツ印はサポートベクトルを表しています.詳しくはマニュアルを参照してください.\n", "\n", "ここまでを確認したら「[タスクML-1d_\"SVMによる結果の評価と感想を共有しよう\"](#タスクML-1d_\"SVMによる結果の評価と感想を共有しよう\")」に進んでください." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "deletable": true, "editable": true }, "outputs": [], "source": [ "library(e1071)\n", "svmModel = svm(Species ~ ., data=TrainingData, type = \"C-classification\", kernel = \"linear\")\n", "svmModel\n", "TestData = iris[row_TestData,]\n", "result <- predict(svmModel, TestData[,-5])\n", "TestData <- data.frame(TestData, result)\n", "print(TestData)\n", "plot(svmModel, TrainingData, Petal.Width ~ Petal.Length,\n", " slice = list(Sepal.Width=3, Sepal.Length=5.8),\n", " symbolPalette = c(cols[1], cols[2], cols[3])\n", " )\n", "legend(\"topleft\", legend = levels(TrainingData$Species), pt.bg = c(cols[1], cols[2], cols[3]), pch = 21)" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "# タスクML-1d_\"SVMによる結果の評価と感想を共有しよう\"\n", "\n", "ここまで行ったSVMの評価について考えてみましょう.今回の予測は十分だったのでしょうか.十分だったか否かは予測が正解したか否かで判断されます.今回の演習では,途中ランダムな抽出を行なっており,同じ演習を行った人でもその結果が異なります.自分の結果を確認してみましょう.\n", "\n", "予測の評価として,Precision(適合率)とRecall(再現率)そしてF値について確認しておきましょう.\n", "\n", "今回の予測でどれくらい正解だったかを考えるのですが,Precision(適合率)とRecall(再現率)は見方がちょっと違います.そのことを考えるために,予測結果とテストデータの正解の情報を比較した**分割表(クロス集計表)**を考察します.\n", "\n", "分割表とは,2つの変数の関係を記述した表です.例えば,学年という変数(小学5年生,6年生など)を縦に,外国語テストという変数(上位群,中位群,下位群)を横にとった表を想像してみてください.これにより学年の違いとテストの群との関係性を考察することができます.\n", "\n", "| | 上位群 | 中位群 | 下位群 |\n", "| --- | --- | --- | --- |\n", "| 小学5年生 | X1 | X2 | X3 |\n", "| 小学6年生 | Y1 | Y2 | Y3 |\n", "\n", "参考:[分割表の検定と論文表現「分割表とグラフ表現」,首都大学東京 大学教育センター 情報教育担当 & 学術情報基盤センター 情報メディア教育支援部門](https://infolit.uec.tmu.ac.jp/lit/contents/jmp/04/#chapter4)\n", "\n", "いま考察したいのは,判定結果と正解の分類との比較なので,\n", "\n", "| | sentosa | versicolor | virginica |\n", "| --- | --- | --- | --- |\n", "| sentosaと判定 | X1 | X2 | X3 |\n", "| versicolorと判定 | Y1 | Y2 | Y3 |\n", "| virginicaと判定 | z1 | z2 | z3 |\n", "\n", "という分割表です.今回のSVMによる分類を評価するには,分類の判定が正しかったか否かです.\n", "\n", "sentosaと判定されたデータの数をPositiveな数(P)とし,そのPositiveのうち実際にsentosaと判定されたデータをTrue Positiveな数(TP)として,PのうちのTPの割合により比較することができます.この指標のことを**Precision(適合率)**といいます.Precisionは,sentosaと判定したときの良さ悪さを表します.この考察は,sentosaと判定されたデータだけを対象に考察しています.\n", "\n", "> Precision(適合率) = TP/(TP + FP)\n", "\n", "これに対して,もう一つの見方があります.それはsenntosaと判定されなかったデータを考察することです.Negativeと判定されたデータの中に,正解がsentosaのデータが存在するかもしれません.このようにNegativeと判定されたが,その判定が失敗であったデータを Flase Negative(FN)といいます.FNの数が大きくなるとsentosaと判定できずに見逃しているケースが多く,sentosaという種類を判定することの良さ悪さを見ることになります.そして,この指標のことを**Recall(再現率)**といいます.Recallは,正解がsentosaであるデータを正しく判定できている具合を表します.\n", "\n", "> Recall(再現率) = TP/(TP + FN)\n", "\n", "PrecisionとRecallとの両方が適度に高い値となる指標としてF値があります.F値は,PrecisionとRecallの調和平均で表されます.これも分類や予測の正確さの指標として利用されます.\n", "\n", "> F-value(F値) = 2 * Precision * Recall /(Precision + Recall)\n", "\n", "R言語では関数table()を使って分割表を生成できます.下のセルでは,判定結果が格納された変数resultと正解のデータ(TestData[,5])をそれぞれ引数に入力しています.今回の判定の正確さを考察してみましょう.判定結果がすべて正解なら,上記分割表のX1,Y2,Z3のみに数値が入り,他の要素は0になります.要素X2,X3,Y1,Y3,Z1,Z2のいずれかに数値が入るということは,判定が失敗したケースがあるということです.下のセルのコードを実行(コードの実行:Shiftキー+Enterキー)して,virginicaのPrecision(適合率),Recall(再現率),F値を計算してみてください.**結果はMoodleのフォーラム「タスクML-1f SVM体験結果を共有しよう」に投稿してください.**\n", "\n", "機械学習による判定結果が十分であることを説明するために使用される指標として,Precision(適合率),Recall(再現率),F値を紹介しましたが,機械学習でよく用いられる検証手法に,**交差検証(Cross Validation)**というものがあります.交差検証では,データ全体をNに等分割して,Nのうちの1つをテストデータとし,残りをトレーニングデータとし,すべての組み合わせのNパターン,これを繰り返し,Precision(適合率),Recall(再現率),F値を考察し,判定結果の評価を行います.分割方法に影響されない結果であることを示すために行われます.よく見かけるのが,データ全体を10に分割して,そのうちの9をトレーニングデータとして,残りの1つをテストデータとし,10通りすべてのパターンで,Precision(適合率),Recall(再現率),F値を考察する検証です.パッケージ「e1071」の関数svm()には,交差検証の結果を簡単に得る仕組みがあり,関数svm()で,属性crossにデータを分割する数を指定するだけです.下のセルでは,データセットiris全体を使い,5つに分割した交差検証の結果を表示しました.交差検証の結果には,関数summary()が必要です.結果には,Total Accuracy や Single Accuracies が表示され,5つに分割した交差検証であれば,Single Accuraciesに5つの正確度が並び,その平均が Total Accuracy に表示されます.このときのAccuracyは,正確度で,全テストデータのうち,Possitiveと判定して正解であったTrue Positive(TP)とNegativeと判定して正解であったTrue Negative(TN)の数(TP+TN)の割合を示しています.\n", "\n", "ここまで確認できたら,「[タスクML-1e_\"SVMの利用イメージを投稿しよう\"](#タスクML-1e_\"SVMの利用イメージを投稿しよう\")」へ進んでください.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false, "deletable": true, "editable": true }, "outputs": [], "source": [ "table(result, TestData[,5])\n", "svmModel.cross = svm(Species ~ ., data=iris, type = \"C-classification\", kernel = \"linear\", cross = 5)\n", "summary(svmModel.cross)" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true }, "source": [ "# タスクML-1e_\"SVMの利用イメージを投稿しよう\"\n", "\n", "ラーニングアナリティクスにとって,機械学習はただのツールです.重要なのは,機械学習で予測や分類された結果に合わせて,学習者や教授者などのステークホルダに対して教育工学的知見からフィードバックを返し,現場の問題を解決することです.\n", "\n", "SVMを教育シーンで利用している事例もありますので,自分で探したり,下記原稿を確認してみてください.いくつかの事例を確認して,**Moodleのフォーラム「タスクML-1e_\"SVMの利用イメージを投稿しよう\"」に投稿してください.**\n", "\n", "- 杉山 いおり, 渡辺 雄貴, 加藤 浩, 西原 明法, [企業内eラーニングにおける社会人の最終学習状態推定](https://www.jstage.jst.go.jp/article/jjet/40/Suppl./40_S40057/_article/-char/ja/), 日本教育工学会論文誌, 2016, 40 巻, Suppl. 号, p. 85-88\n", "- 清水 典史, 井上 寛, 松延 千春, 高露 恵理子, 椿 友梨, 白谷 智宣, [サポートベクターマシンに基づ-く試験合否予測モデルの構築](https://www.jstage.jst.go.jp/article/jjphe/2/0/2_2017-023/_html/-char/ja), 薬学教育, 2018, 2 巻\n", "- 大堀 裕一, 廣安 知之, 横内 久猛, [SVMと学習データ選択によるクラス分類アルゴリズムの検討](https://www.jstage.jst.go.jp/article/pjsai/JSAI2012/0/JSAI2012_3B1R24/_article/-char/ja/), 人工知能学会全国大会論文集, 2012, JSAI2012 巻, 第26回全国大会(2012), セッションID 3B1-R-2-4, p. 3B1R24" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true, "deletable": true, "editable": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "R", "language": "R", "name": "ir" }, "language_info": { "codemirror_mode": "r", "file_extension": ".r", "mimetype": "text/x-r-source", "name": "R", "pygments_lexer": "r", "version": "3.3.3" } }, "nbformat": 4, "nbformat_minor": 2 }