# Python <!-- HackMDのタイトルです -->
<!-- 内容はここから -->
## はじめに <!-- 0章 はじめに に変更 1つの単元ごと章で分けていきます-->
ここでは、Pythonを使って機械学習のプログラムを作る方法の基礎について説明します。
<!--
<div class="alert alert-info">
このレッスンは、Python関連のコースの受講を検討されている方へ向けて、Pythonコースの一部内容を再構成したダイジェスト版として公開しています。
</div> --><!-- 削除 Techpitのフォーマットに合わないため-->
<!-- 以下Techpitのフォーマット使用に合わせるため追加 -->
## 0-1 教材の概要 <!-- 以降、教材のパートごとに0-1といった番号を降っていきます -->
まずは機械学習とはどういうものかについて学びましょう。機械学習に関する、さまざまな用語を紹介します。
### 学習内容
### 受講における必要条件
(例)PHPの基本的な知識(変数、配列、連想配列、if文、each文、メソッド・クラスの存在を理解している)
### この教材の対象者
(例)PHPを学んだことがある方
### 学ばないこと
(例)本教材では、大規模フレームワークの使用方法や、完全なMVCモデルの実装は行いません。
### 本教材の対応バージョン
(例)PHP8.0
### 目次
(例)
* 0章 はじめに
* 1章
### 0-2 必要となるツール
Google ColaboratoryでPython,scikit-learnのセットアップができる。
Google Colaboratoryの使用方法を画像を入れて説明する。
### AI(人工知能) <!-- 1章 AI(人工知能) に変更-->
機械学習が注目された要因のひとつが **AI(Artificial intelligence:人工知能)** です。
AIは、高速な計算処理ができ、さらに人間との対話を行えるコンピュータ(プログラム)です。ひと昔前までは、AIを目にするのはSFの映画やドラマの中だけでしたが、現在は「スマートスピーカー」をはじめ商業施設の店頭で応対するロボットなど、いたるところでAIを利用した技術を見るようになりました。
現在、人間の知能と比較するとAIはまだまだで、発展途上の技術です。それでも、人間の話す言葉を認識して適切な情報を教えてくれたり、車が自動でブレーキを踏んでくれたりと、AI技術の実用化も進んでいます。
### シンギュラリティ(技術的特異点) <!-- 1-1 シンギュラリティ(技術的特異点) に変更 -->
現時点のAI技術では実用化できる範囲は限定的ですが、技術が発達し、人間と同等の知能をAIが得たときのことを考えると不安になる人も多いのではないでしょうか。AIが人間を支配するという、SF映画で描かれてきたような世界が現実になる可能性は、ゼロとは言い切れません。
人間の知能にAIが追いつくことを **シンギュラリティ(技術的特異点)** といいます。
AIを研究するレイ・カーツワイル氏が提唱した概念です。シンギュラリティに到達したとき、人間の社会がどのようになるかは誰も予測できない現状で、研究者の間でも一種の問題として考えられています。この問題は、シンギュラリティに到達するのが2045年であると考えられていることから「2045年問題」と呼ばれることもあります。
AI技術を進化させるとともに、AIをどのように制御するかも検討されていかなければなりません。
### 機械学習 <!-- 1-2 機械学習 に変更 -->
AI技術の根幹にあるのが **機械学習(machine learning)** です。「機械」が「学習」するという言葉のとおり、コンピュータが膨大な数のデータを覚え、それを基にして、ひとつの判断を下すのです。
ここでいう「判断」は、以下のような内容を指します。
- 人間が写真を見て「これは○○の写真だ」というように「答えが決まっていること」に対してコンピュータが正確に判断すること
- 「答えがない」ことに対して「将来こうなるのではないか」とコンピュータが予測を立てること
- ペットが芸を覚えるときのように「良い行動に報酬を与える」ことで、瞬間瞬間での最良の判断を繰り返すこと
### 深層学習(ディープラーニング)とニューラルネットワーク <!-- 1-3 深層学習(ディープラーニング)とニューラルネットワーク に変更 -->
「機械学習」とは別に **深層学習(ディープラーニング:deep learning)** という言葉があります。機械学習と深層学習は何が違うのでしょうか。それを見ていく前に **ニューラルネットワーク(neural network)** という言葉について説明します。
#### ニューラルネットワーク
単に「機械学習」といっても、コンピュータの学習を実現する仕組みは各種存在します。そのひとつがニューラルネットワークです。ニューラルネットワークは簡単に言うと、人間の脳の働きをコンピュータプログラムで模倣した仕組みです。
人間の脳は多数の神経回路(ニューロン:neuron)で構成されており、ニューロンとニューロンがつながることで、大きな神経回路を形成しています。外から受けた情報は電気信号として神経回路の中を流れていく仕組みになっていて、新しいニューロンや、つながりができることで、人間は新しいことを学習できるのです。
その神経回路の仕組みを参考に、コンピュータプログラムで模倣しているので、「ニューラル」という言葉をつけて「ニューラルネットワーク」と呼んでいます。
ただし、人間の脳とニューラルネットワークは、まったく別のものです。人間の脳は未だ解明されていない部分が多く、ニューラルネットワークでは再現できません。あくまで神経回路の仕組みを参考にしているだけであって、再現したものではないことに注意しましょう。
#### 深層学習(ディープラーニング)
ニューラルネットワークを利用してコンピュータに学習させるのが「深層学習」です。
つまり、深層学習は機械学習の一部だと思ってください。

ニューラルネットワークや深層学習の仕組みを覚えるのは非常に大変です。深層学習をやってみるだけでしたら「Tensorflow」や「Keras」というPythonのライブラリを導入することで簡単に行えます。
とはいえレベルの高い話になるため、ここでは深層学習までは扱いません。このレッスン<!-- この教材に変更 Techpitのフォーマットに合わせるため-->を最後まで学習したあとで、興味の出た方は、ぜひ深層学習についても学んでください。
### 教師あり学習と教師なし学習 <!-- 1-4 教師あり学習と教師なし学習 に変更 -->
機械学習では、ひとつの判断を下すために利用する大量のデータをどのように使って学習するのでしょうか。
その方法は2つあります。**教師あり学習** と **教師なし学習** です。
2つの違いは、簡単に言えば「答えがあるかないか」です。**学習させるデータに答えがある場合「教師あり学習」、答えのない場合が「教師なし学習」となります。**
先述の事例で言えば、写真をみて「これは○○の写真だ」と判断する場合は教師あり学習を使います。何の写真かわからない状態で写真を覚えさせられても、判断の材料にならないからです。
これとは別に、将来予測では教師なし学習を使います。実測値を学習した上で傾向を分析し、その分析結果を基に「○○の値がこうなら、△△の値は、こうなるのではないか」と予測を立てるのです。
なお、同じ予測でも、たとえば「年齢・性別・職業などの情報と喫煙の有無」が記録されたデータを学習して「この年齢・性別・職業なら喫煙しているのではないか」と予測を立てるのは教師あり学習に該当します。
### 強化学習 <!-- 1-5 強化学習 に変更 -->
ペットに芸を教えるという一種のたとえを先ほど書きましたが、教師あり学習や教師なし学習とは違い、試行錯誤を繰り返す中で「より良い行動をしたら報酬を得られる」というルールで学習を続けていき、瞬間瞬間での最良な判断を繰り返していく手法が **強化学習** です。
強化学習は将棋やチェスをするAIで活用されています。最初は、インターネット上に存在する大量の棋譜を「教師あり学習」で学習させますが、学習が完了したAI同士を対局させ、その中で強化学習を行う(より良い手を打ったら報酬を与える)ことでAIの能力を高めていきます。
また、テレビゲームをAIにプレーさせる中で、より前へ進む行動に報酬を与える形で強化学習を行い、ステージクリアを目指すということに挑戦している方もいます。その様子を動画サイトで公開されていることもありますので、興味ある方は視聴してみると、おもしろいでしょう。
### 分類と回帰 <!-- 2章 分類と回帰 に変更 -->
学習したAIが教えてくれること(データを分析する方針)は **分類** と **回帰** の2つに分けられます。
#### 分類 <!-- 2-1 分類 に変更 -->
「これは○○の写真だ」と判断するのは分類に該当します。「いくつかある選択肢の中のどれになるか」はっきりと答えを言うのが分類だと思ってください。
データを解析して捉えた特徴を基にして「これは○○だ」と分類します。
#### 回帰
それに対して「施設の利用回数」や「価格」など、連続的な数値について予測するのが回帰です。
回帰では統計学の知識や計算を使います。データを数学的な関数の式やグラフに当てはめて分析し、法則を導き出します。
### scikit-learnの概要 <!-- 2-2 scikit-learnの概要 に変更 -->
このレッスンでは<!-- このパートでは に変更--><!-- Techpitのフォーマットに合わせるため-->機械学習を体験するために **scikit-learn** というパッケージライブラリを利用します。Pythonのプログラムを作りやすくするため、個人や団体・組織が作成して公開している、「特定の処理を行う命令」をひとまとめにしたものをパッケージライブラリ(もしくは単にライブラリ)といいます。
Pythonでは機械学習や深層学習を行う上で便利なライブラリが数多く存在しています。その中でもscikit-learnは機械学習のほとんどの分野をカバーしている汎用性を持ち、さらに機械学習の初心者でも使いやすい特徴があるため、非常に人気のライブラリです。
機械学習の特定の分野についてプログラムを作りたい場合は、他のライブラリの導入を検討した方が良い場合もあります。たとえば深層学習なら「Tensorflow」や「Keras」です。しかし、機械学習そのものが初体験という方には扱い方が難しいものも多いです。
最初はscikit-learnで簡単なレベルの機械学習を学んでから、他のライブラリや特定分野の学習へステップアップすることをおススメします。
<div class="alert alert-success">
<!-- 以下、必要となるツールの部分に以降 -->
Python関連のコースでは、AWS Cloud9上にPythonの環境を作成して学習を進めますが、このレッスンに記載されたコードは <a href="https://colab.research.google.com/" target="_blank">Google Colaboratory</a> やPCにインストールしたPythonでも実行可能です。<br>
<br>
なお、次のチャプターから掲載しているサンプルコードを実行するには、以下のものが必要です。(Google Colaboratoryにはデフォルトで以下のすべてが入っています)<br>
<ul>
<li>Python3</li>
<li>JupyterLab もしくは Jupyter Notebook</li>
<li>NumPy</li>
<li>SciPy</li>
<li>Matplotlib</li>
<li>Pandas</li>
<li>Pillow</li>
<li>scikit-learn</li>
</ul>
</div>
## 機械学習の流れ <!-- 3章 機械学習の流れ に変更 -->
Pythonで機械学習プログラムを作る際の、大まかな流れは、次の通りです。
1. モデルを決める
2. 学習に使うデータセットをインポートする
3. データの前処理をする
4. データを訓練データ(train data)とテストデータ(evaluate data)とに分ける
5. モデルを作って学習する
6. 期待する性能が出たかを評価する

### モデルを決める <!-- 3-1 モデルを決める に変更 -->
機械学習で処理する方法を定義したものを **モデル(model)** と言います。独自のモデルを作ることもできますが、scikit-learnには、さまざまなモデルが提供されており、最初はそのなかから選ぶとよいでしょう。
scikit-learnで提供されているチートシートを見ると、どれを選べばよいのか、大まかにわかります。
[Choosing the right estimator \| scikit-learn documentation](https://scikit-learn.org/0.21/tutorial/machine_learning_map/){:target="_blank"}

*scikit-learn algorithm cheat-sheet(引用元:上記のscikit-learn公式のページより)*
チートシートでは、大きく4つのグループに分かれています。まずは、何をしたいかによってグループを選び、それから、その分類のなかにあるモデルのなかからひとつを選びます。ときには、こうしたモデルを多段に組み合わせることもあります。
#### ① 回帰(regression) <!-- 3-2 回帰(regression) に変更 -->
連続したデータから、その傾向を出力する連続値を出力します。たとえば過去の実績から未来の数値を予測するなどです。教師あり学習です。
#### ② 分類(classification)
データを分類します。たとえばメール本文を入力として迷惑メールか否かを決めたり、手書き文字が何の文字か分類するなどです。教師あり学習です。
#### ③ クラスタリング(clustering)
データの類似性によって、グループ化します。たとえば顧客の購買データを入力して、似ている傾向の顧客を集めるなどです。そうすればリコメンド(他のオススメ商品を紹介する)に使えます。
ほかの例としては、「人物」「車」「山」「川」など、さまざまなシチュエーションの写真がたくさんあるとき、似たような写真でグループ化する、ということもできます。
これらは、教師なし学習です。
#### ④ 次元削減(dimensionality reduction)
データの特徴量を捕らえて、より小さなデータ量に削減します。多数の項目からなるデータを処理する際、それを直接使うと計算量が多くなるときなどに必要となります。次元圧縮とも呼ばれます。教師なし学習です。
### 学習に使うデータセットをインポートする <!-- 3-3 学習に使うデータセットをインポートする に変更-->
モデルを決めたら、機械学習のプログラムを作っていきます。
最初に、学習に使うデータを取り込みます。機械学習の勉強のためであれば、scikit-learnで最初から用意されているサンプルデータを使うとよいでしょう。
ここから、ひとつの事例で機械学習の実習を行います。このチャプターでは、機械学習でよく使われる「アヤメ(iris)」のサンプルデータを読み込んで、アヤメの種類を分類してみます。また、今回は「サポートベクトル」と呼ばれるアルゴリズムの分類モデル(`SVC`)を使います。
まずは、scikit-learnのパッケージライブラリを **インポート(import)** します。その際、パッケージの名前はscikit-learnではなく `sklearn` とします。以下のように記述することで、`sklearn` に含まれる `datasets`(サンプルデータのセット)のみをインポートできます。
```python
from sklearn import datasets
```
次に、アヤメ(iris)のサンプルデータを `irisdata` という変数に入れておきます。
```python
irisdata = datasets.load_iris()
```
scikit-learnに含まれるサンプルデータは、その書式やデータの意味が `DESCR` プロパティに記載されています。次のようにして確認できます。JavaScriptの `console.log` のように、指定した情報を出力する命令が `print()` です。
```python
print(irisdata.DESCR)
```
*出力結果:*
```txt
.. _iris_dataset:
Iris plants dataset
--------------------
**Data Set Characteristics:**
:Number of Instances: 150 (50 in each of three classes)
:Number of Attributes: 4 numeric, predictive attributes and the class
:Attribute Information:
- sepal length in cm
- sepal width in cm
- petal length in cm
- petal width in cm
- class:
- Iris-Setosa
- Iris-Versicolour
- Iris-Virginica
:Summary Statistics:
============== ==== ==== ======= ===== ====================
Min Max Mean SD Class Correlation
============== ==== ==== ======= ===== ====================
sepal length: 4.3 7.9 5.84 0.83 0.7826
sepal width: 2.0 4.4 3.05 0.43 -0.4194
petal length: 1.0 6.9 3.76 1.76 0.9490 (high!)
petal width: 0.1 2.5 1.20 0.76 0.9565 (high!)
============== ==== ==== ======= ===== ====================
:Missing Attribute Values: None
:Class Distribution: 33.3% for each of 3 classes.
:Creator: R.A. Fisher
:Donor: Michael Marshall (MARSHALL%PLU@io.arc.nasa.gov)
:Date: July, 1988
…略…
```
「Sepal Length(がく片の長さ)」「Sepal Width(がく片の幅)」「Petal Length(花びらの長さ)」「Petal Width(花びらの幅)」の4つのデータが `irisdata` の `data`プロパティに格納され、それらが「Setosa」「Versicolor」「Virginica」のどの品種に属するかが `irisdata` の `target` プロパティに格納されています。
`target` には `Setosa` `Versicolor` `Virginica` の文字列そのものではなく、`0` `1` `2` の整数値が入っています。これは、`Setosa = 0, Versicolor = 1, Virginica = 2` を意味しています(カテゴリ値:詳細は後述)。
「アヤメに関する4つの長さ」のように、それぞれがどういう特徴を持っているかを説明するデータを **説明変数** や **特徴量** 、また「アヤメのカテゴリ値」のように、それが何の種類に属しているかを示すデータを **目的変数** といいます。
scikit-learnに含まれるサンプルデータは `data` プロパティに説明変数(特徴量)が、`target` プロパティに目的変数が入っています。
```python
print(irisdata.data)
```
*出力結果:*
```
[[5.1 3.5 1.4 0.2]
[4.9 3. 1.4 0.2]
[4.7 3.2 1.3 0.2]
[4.6 3.1 1.5 0.2]
[5. 3.6 1.4 0.2]
[5.4 3.9 1.7 0.4]
…略…]
```
```python
print(irisdata.target)
```
*出力結果:*
```txt
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
2 2]
```
targetは、このように「0」「1」「2」のいずれかの値になっており、「Setosa」「Versicolor」「Virginica」のそれぞれに対応します。
`data` と `target` は、それぞれ、NumPyというパッケージライブラリで定義された特殊な配列(ndarray)です。`shape` プロパティを参照することで、配列の要素数がわかります。
```python
print(irisdata.data.shape)
print(irisdata.target.shape)
```
*出力結果:*
```txt
(150, 4)
(150,)
```
`data` は `縦150行 x 横4列` の配列、 `target` は `縦150行 x 横1列` の配列です。`data` と `target` は番号(添字)の同じ要素がペアとなっています。たとえば、0番目の要素の `data`(説明変数)である `[5.1 3.5 1.4 0.2]` に対応する `target`(目的変数)は、同じ0番目の要素である `0` となります。
`data` の横の列がそれぞれ何のデータか、また、`target` の値 `0,1,2` がそれぞれ何を表しているかは、以下のプロパティを参照するとわかります。
```python
print(irisdata.feature_names)
print(irisdata.target_names)
```
*出力結果:*
```txt
['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']
['setosa' 'versicolor' 'virginica']
```
### データの前処理を行う <!-- 3-4 データの前処理を行う に変更-->
インポートしたデータは、そのまま使えるとは限りません。計測データの場合、ノイズや誤差があったり、値がうまくとれていないことがあります。また正しいデータであっても、説明変数間の単位が大きく違うと、適切に学習できないことがあります。そこで必要になるのが、データの **前処理** です。
前処理は、学習しやすいようにする、さまざまな加工です。目的によって異なりますが、たとえば、次のようなものがあります。
#### カテゴリ値の数値化
機械学習では、数値を扱います。たとえば「ネコ」「イヌ」「風景」のカテゴリに分類するモデルを作る場合、この文字を扱うのではなく `ネコ = 0, イヌ = 1, 風景 = 2` のように数値化します。このように数値化されたデータを **カテゴリ値** といいます。
ここでのアヤメのサンプルの場合は、すでに `Setosa = 0, Versicolor = 1, Virginica = 2` のようになっていますが、自分でデータを作る場合は、こうした数値化をしなければなりません。
#### ダミー変数化
カテゴリ値の数値化とも関連しますが、機械学習の計算方式(アルゴリズム)によっては、テストの点数のように「数値の大小に意味がある」という認識となってしまい、適切に学習できないことがあります。今回のアヤメの例であれば、`Setosa(0)`より`Virginica(2)`のほうが優位にあるものと認識される場合があります。
そのようなときは、横軸に項目名を持ち、「0」か「1」かを持つ変数マトリックスに展開するように分解します。これを **ダミー変数** と言います。ダミー変数化が必要かどうかは、学習データの性質によります。
*ダミー変数化前*
| カテゴリの値 | 値 |
| ---------------- | --- |
| Setosaのとき | 0 |
| Versicolorのとき | 1 |
| Virginicaのとき | 2 |
*ダミー変数化後*
| 値・列名 | Setosa | Versicolor | Virginica |
| ---------------- | ------ | ---------- | --------- |
| Setosaのとき | 1 | 0 | 0 |
| Versicolorのとき | 0 | 1 | 0 |
| Virginicaのとき | 0 | 0 | 1 |
また、以下のように、列を1つ減らして「すべての列が0だったらVirginicaとする」という処理を行ったほうが、精度向上を見込める場合もあります。
| 値・列名 | Setosa | Versicolor |
| ---------------- | ------ | ---------- |
| Setosaのとき | 1 | 0 |
| Versicolorのとき | 0 | 1 |
| Virginicaのとき | 0 | 0 |
<div class="alert alert-info">
列をひとつ減らすのは必須の対応ではありませんが、ダミー変数の列どうしに強い関係性が発生し、学習精度に悪い影響を与えている場合に効果的です(このようなケースを多重共線性といいます)。
</div>
#### 欠損データの補完(穴埋め
データが一部欠損している場合、 **補完(穴埋め)** することを検討します。それを「0」として扱うのか、「前後の平均で補完するか」、そもそも、欠損したデータをすべて捨ててしまうか、などを決めます。
単純に0にすると実際のデータと大きくかけ離れるので、ほとんどの場合、そうすることはありません。欠損データをすべて捨ててしまうのはよい方法ですが、そうするとデータ数が足りなくなって、十分な学習ができないかも知れません。また前後の平均で補完する方法もよく使われますが、誤差を大きくするかもしれません。
ですから、どの方法がよいのかは、一概に言えません。データの性質や量、目的によって、選択する必要があります。
なお、ここでサンプルとして読み込んだアヤメのデータの場合、DESCRプロパティには`Missing Attribute Values: None`と記述されており、欠損値はありません。
#### スケーリング <!-- 3-5 スケーリング に変更 -->
ある列の数値の範囲(上限と下限)が、ほかの列と大きくかけ離れる場合、その列が少し変わっただけで、全体に大きく影響を与える可能性があります。
そこで必要なのが **スケーリング** です。スケーリングとは、 各列の数値データの範囲を、およそ0〜1(もしくはマイナス1〜1)の範囲に収められるように再計算することをいいます。
どのようなモデルを作るのかにもよりますが、各列の数値の範囲が、大きく違う場合は、スケーリングするのがよいでしょう。
#### 情報保護に関連する加工(マスキング)
機械学習では、個人情報を扱うこともあります。就活生の内定辞退を予測するサービスが大きな問題となったように、個人情報を「機械学習が学習して判断した結果」によって、自分の情報が公にさらされたり、知らないところで悪意のあるレッテルを貼られたりすることも考えられます。
そういうときには、個人を特定する部分を隠す **マスキング** などの処理をします。
#### 画像や音声を処理しやすくする加工
画像や音声を取り扱う場合は、学習しやすいように加工します。データの量が少ないほど学習にかかる時間が少なくて済みますが、細部が潰れて学習に影響を及ぼすこともあるので、目的によって適切に調整します。
画像の場合は、ノイズを除去したり、一部を切り出したり、傾きを補正したりしたほうが良い結果を得られることもあります。さらに、色の情報が必要ないときは、 **グレースケール(白黒のみ)にする** のも有効です。
音声についても同様です。
#### データの水増し
一般にデータをたくさん用意できたほうが、学習効果が高まります(ただし学習させ過ぎると、その学習データに特化した結果が出やすくなり、汎用的になりにくくなるという別の問題が生じます)。どうしてもデータが足りない場合、学習の際に **水増し** をすることもあります。
画像の場合は、拡大や縮小、回転、反転などの幾何学操作をしたり、ノイズを加えたりした画像を作ることで水増しします。とくに、多くの学習データが必要な深層学習のような学習では、よく使われるテクニックです。
### データを訓練データとテストデータに分ける <!-- 3-6 データを訓練データとテストデータに分ける に変更-->
機械学習ではデータを **訓練データ** と **テストデータ** とに分けます。訓練データはコンピュータに覚えさせたいデータ、テストデータは学習結果の正確さを検証するために使うデータです。
訓練データとテストデータの分け方には、単純に「○番目から△番目までのデータを訓練データ、それ以降をテストデータ」と分ける方法の他、ランダムに振り分ける方法、などがあります。インポートしたデータが規則順に並んでいるときに単純な2分割を行うと、訓練データに偏りが生じるので、極力ランダムに振り分けられると良いでしょう。
すべてのデータを訓練データとテストデータに分ける際、テストデータは全体の2割から3割程度で振り分けるのが望ましいです。
訓練データが少なすぎると判断材料が少ないため、精度が悪くなるのは想像に難くありませんが、同じ訓練データを使い回しすぎても訓練データに対する学習に偏ってしまい、テストデータに対する判断の精度が落ちてしまう可能性があります。このような状態を **過学習** といいます。
過学習を防ぐ方法は複数ありますが、テストデータの割合を考慮するだけでも充分な精度が望めます。
なおscikit-learnでプログラミングする場合は説明変数を `X` 、目的変数を`y` とし、さらに訓練データには `_train` 、テストデータに `_test` という文字列を変数名に付け加えるのが慣例です。ランダムに振り分けるなら、scikit-learnの `train_test_split()` をインポートして使うのが便利です。以下に一例を示します。
```python
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(irisdata.data, irisdata.target, test_size = 0.2, train_size = 0.8, shuffle = True)
```
`train_test_split` の1つ目の引数は説明変数、2つ目の引数には目的変数を指定します。
また、次に続く `test_size`・`train_size`・`shuffle` は **キーワード引数** というPython独自の文法要素で、記載順に関係なく引数を指定できる特長があります。`test_size` はテストデータの割合、`train_size` は訓練データの割合です。全データのうち、テストデータを2割、訓練データを8割で振り分けています。
<div class="alert alert-info">
<code>train_test_split()</code>で<code>shuffle = True</code>(分割前にランダムな並び替えを行う)というキーワード引数を指定しているため、以降の実習内容において、皆さんの実行結果がカリキュラムに掲載のものと少し異なる結果になる可能性があります。エラーが出ない限りは正常に処理が実行できていると考えて、学習を進めてください。
</div>
### モデルを作って学習する <!-- 3-7 モデルを作って学習する に変更-->
ここまでが、機械学習の前処理だと思ってください。次に、機械学習モデルを作って学習します。
scikit-learnには、代表的な機械学習モデルがあるので、インポートして簡単に作れます。ここでは、比較的高い精度を得られる「サポートベクトル」と呼ばれるアルゴリズムを利用した分類モデル(`SVC`)をインポートして使います。
たとえば、サポートベクトルの分類モデルの中でも「線形分類モデル」を作るには、次のようにします。`kernel = "linear"`のキーワード引数は、どのような線で分けるのかという線の種類を指定しています。今回は、"linear"とすることで、直線で分けるという指定をしています。
```python
from sklearn.svm import SVC
classifier = SVC(kernel = "linear")
```
作ったモデルの `fit` メソッドを実行すると、学習が実行されます。訓練データの `X` と `y` をそれぞれ第1、第2引数に指定します。
```python
classifier.fit(X_train, y_train)
```
*出力結果:*
```
SVC(kernel='linear')
```
### 期待する性能が出たかを評価する <!-- 3-8 期待する性能が出たかを評価する に変更 -->
以上で、機械学習モデルができました。分けて残して置いたテストデータを `predict` メソッドで、この学習済みモデルに入れて、モデルの評価を実行します。引数は、テストデータの `X` のみです。
```python
y_pred = classifier.predict(X_test)
```
ここで求めた `y_pred` が機械学習によって求められた結果です。
``` python
print(y_pred)
```
*出力結果の一例:*
```
[1 1 2 1 2 2 0 2 1 1 0 2 0 1 0 2 1 2 1 0 0 1 2 0 0 2 2 2 1 0]
```
テストデータの元々の結果である `y_test` とどれだけ合致しているかを見て、モデルの性能を評価したいのですが、ひとまず `y_test` をそのまま表示してみます。
``` python
print(y_test)
```
*出力結果の一例:*
```
[1 1 2 1 2 2 0 2 2 1 0 2 0 1 0 2 2 2 2 0 0 1 2 0 0 2 1 2 1 0]
```
少し横に伸びていて見にくいので、集計した結果で `y_pred` と `y_test` の合致度(モデルの性能)を評価します。
性能を測る方法は、いくつかあり、たとえば `accuracy_score`を使って、正答率を見る方法があります。
`accuracy_score` を使うには、`sklearn` が持つ `metrics` をインポートします。`accuracy_score` の第1引数がテストデータの `y`、第2引数に予測結果の `y` を指定します。
```python
from sklearn import metrics
print(metrics.accuracy_score(y_test, y_pred))
```
*出力結果の一例:*
```txt
0.8666666666666667
```
上記の例では、正答率は、86.67%のようです。
他にも **混同行列** というものを表示する方法があります。先ほどと同様、`metrics` が持つ `confusion_matrix` という命令を使います。引数の指定と順序は、`accuracy_score` と同じです。
```python
print(metrics.confusion_matrix(y_test, y_pred))
```
*出力結果:*
```
[[ 9 0 0]
[ 0 7 1]
[ 0 3 10]]
```
この出力結果が、混同行列です。これをどう見るか、ですが、縦の並び(行)が「正解」、横の並び(列)は「分類器が出力した結果」です。簡単に言うと **左上から右下に引いた対角線上の数値が正解数** になります。

上記の混同行列では、分類器が不正解だったデータは4つだけ、ということになります。
正答率を表示する、もうひとつの方法があります。`metrics` の `classification_report()` を実行してください。
``` python
print(metrics.classification_report(y_test, y_pred))
```
*出力結果:*
```
precision recall f1-score support
0 1.00 1.00 1.00 9
1 0.70 0.88 0.78 8
2 0.91 0.77 0.83 13
accuracy 0.87 30
macro avg 0.87 0.88 0.87 30
weighted avg 0.88 0.87 0.87 30
```
`recall` の列が正解率です。全体の正解率が `0.87`(87%)ということで、良い精度の分類器が作成されたと言えます。
以上が集計方法の一例です。
## 手書き数字の画像を分類する機械学習プログラムの構築 <!-- 4章 手書き数字の画像を分類する機械学習プログラムの構築 に変更 -->
他のサンプルデータを利用して、scikit-learnの使い方を学びながら、機械学習プログラムを構築します。ここでは、画像の分類プログラムを作ってみます。
scikit-learnが持っている手書き数字のサンプル画像を利用して、それが何の数字が描かれた画像かを分類します。
#### 使用するモデルについて <!-- 4-1 使用するモデルについて に変更-->
分類の機械学習を行いたいので、今回も `SVC` の線形分類モデルを使います。
#### 学習に使うデータセットをインポートする
まずはscikit-learnと手書き数字のサンプルデータセットをインポートしましょう。手書き数字のサンプルデータは `load_digits()` でインポートします。
``` python
from sklearn import datasets
```
``` python
digits = datasets.load_digits()
```
データセットの概要は `DESCR` プロパティで確認できます。
``` python
print(digits.DESCR)
```
*出力結果:*
```
Optical Recognition of Handwritten Digits Data Set
===================================================
Notes
-----
Data Set Characteristics:
:Number of Instances: 5620
:Number of Attributes: 64
:Attribute Information: 8x8 image of integer pixels in the range 0..16.
:Missing Attribute Values: None
:Creator: E. Alpaydin (alpaydin '@' boun.edu.tr)
:Date: July; 1998
This is a copy of the test set of the UCI ML hand-written digits datasets
http://archive.ics.uci.edu/ml/datasets/Optical+Recognition+of+Handwritten+Digits
The data set contains images of hand-written digits: 10 classes where
each class refers to a digit.
Preprocessing programs made available by NIST were used to extract
normalized bitmaps of handwritten digits from a preprinted form. From a
total of 43 people, 30 contributed to the training set and different 13
to the test set. 32x32 bitmaps are divided into nonoverlapping blocks of
4x4 and the number of on pixels are counted in each block. This generates
an input matrix of 8x8 where each element is an integer in the range
0..16. This reduces dimensionality and gives invariance to small
distortions.
For info on NIST preprocessing routines, see M. D. Garris, J. L. Blue, G.
T. Candela, D. L. Dimmick, J. Geist, P. J. Grother, S. A. Janet, and C.
L. Wilson, NIST Form-Based Handprint Recognition System, NISTIR 5469,
1994.
References
----------
- C. Kaynak (1995) Methods of Combining Multiple Classifiers and Their
Applications to Handwritten Digit Recognition, MSc Thesis, Institute of
Graduate Studies in Science and Engineering, Bogazici University.
- E. Alpaydin, C. Kaynak (1998) Cascading Classifiers, Kybernetika.
- Ken Tang and Ponnuthurai N. Suganthan and Xi Yao and A. Kai Qin.
Linear dimensionalityreduction using relevance weighted LDA. School of
Electrical and Electronic Engineering Nanyang Technological University.
2005.
- Claudio Gentile. A New Approximate Maximal Margin Classification
Algorithm. NIPS. 2000.
```
いったん `data` と `target` をそれぞれ `X` と `y` に分けておきます。
``` python
X = digits.data
y = digits.target
```
配列の大きさ(データの個数)を調べてみます。
``` python
print(X.shape)
print(y.shape)
```
*出力結果:*
```
(1797, 64)
(1797,)
```
1797個のデータがあります。
今回の計測データ `X` の `shape` で表示された `64` は、データが `縦8ピクセル × 横8ピクセル` の画像データ(モノクロ画像)になっていることを表します。データ自体は、縦と横(2次元)に並んでいたものを `1 x 64` のような横並び(1次元)に変換された形式となっています。
各ピクセルのデータは白黒の度合いを表していて、それぞれ、`0` から `15` までの16段階(数値が大きいほど白いピクセル)です。
<div class="alert alert-info">
なお、画像データに対して「計測」という言葉を使うのは変かもしれませんが、レッスン全体で共通した形で話を進めたいので、ここでも「計測データ」と呼びます。
</div>
データの中身を見てみましょう。
``` python
print(X[0])
print(y[0:50])
```
*出力結果:*
```txt
[ 0. 0. 5. 13. 9. 1. 0. 0. 0. 0. 13. 15. 10. 15. 5. 0. 0. 3.
1. 2. 0. 11. 8. 0. 0. 4. 12. 0. 0. 8. 8. 0. 0. 5. 8. 0.
2. 9. 8. 0. 0. 4. 11. 0. 1. 12. 7. 0. 0. 2. 14. 5. 10. 12.
3. 0. 0. 0. 6. 13. 10. 0. 0. 0.]
[0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 9 5 5 6 5 0
9 8 9 8 4 1 7 7 3 5 1 0 0]
```
<div class="alert alert-info">
配列(ndarray)の「○番目の要素」を参照する方法はJavaScriptと同様ですが、Pythonのndarrayの場合 <code>○番目から△番目までの複数要素</code> を同時に参照するという指定が可能です。<code>y[0:50]</code> は、配列yの<strong>0番目から49番目までの複数要素</strong>、という指定になります。(コロンの右側は「未満」になるため50番目は含みません)
</div>
このデータ `X` がどういう手書き画像になっているかを表示した例が、下記の画像です。

このデータは、以下のコードを実行して表示させたものとなっています。(参考例としてコードを掲載します)
``` python
import numpy as np # NumPyをインポート
import matplotlib.pyplot as plt # Matplotlibが持つ"pyplot"をインポート
fig = plt.figure() # 画像の表示領域を設定
# 最初の10個のデータについて同じ処理を繰り返す
for i, x in enumerate(X[0:10], 0):
sp = fig.add_subplot(2, 5, (i + 1)) ## 各数字を表示するための区画を設定
sp.imshow(x.reshape(8, 8), cmap = "gray") ## 数字の画像を表示
```
<div class="alert alert-info">
確認のため、表示した画像データと、X[0]の数値データを比較すると、数字が大きいセルほど白く、数字が小さいセルほど黒くなっていることが分かります。<br>
<br>
<img src="https://techacademy.s3.ap-northeast-1.amazonaws.com/bootcamp/python/machine-learning/hand_img_01.png">
</div>
#### データの前処理について <!-- 4-2 データの前処理について に変更-->
今回のサンプルは、とくに前処理の必要がないサンプルデータとなっています。前処理についてはスキップして、データの分割を行います。
#### データを訓練データとテストデータに分ける
今回の手書き数字のデータは順序よく並んではいないようなので、`train_test_split()` を使う必要なく、単純に「○○○件目で区切る」という方法で問題ありません。
このテストデータは1800件弱ほどありますので、今回は1200件目までを訓練データ、1201件目以降をテストデータとしてください。以下のように記述すればOKです。要素指定のコロンの左側を省略すると `0番目の要素から`、右側の省略は `最後の要素まで` という意味になります。
``` python
X_train = X[:1201]
X_test = X[1201:]
y_train = y[:1201]
y_test = y[1201:]
```
また、各ピクセルのデータはすべて16段階で表現したデータになっているため、スケーリングも必要ありません。
#### モデルを作って学習する
今回も線形分類器を使って分類器を作成しましょう。
``` python
from sklearn.svm import SVC
classifier = SVC(kernel = "linear")
classifier.fit(X_train, y_train)
```
*出力結果:*
```
SVC(kernel='linear')
```
#### 期待する性能が出たかを評価する
テストデータ `X_test` を分類器にかけましょう。
``` python
y_pred = classifier.predict(X_test)
```
まずは `y_pred` と `y_test` をそのまま表示してみます。
``` python
print(y_pred)
```
*出力結果:*
```txt
[7 3 5 1 0 0 2 2 7 8 2 0 1 2 6 3 3 7 3 3 4 6 6 6 4 9 1 5 0 9 5 2 8 2 0 0 1
7 6 3 2 1 7 4 6 3 1 3 9 1 7 6 8 4 3 8 4 0 5 3 6 9 6 6 7 5 4 4 7 2 8 2 2 5
7 9 5 4 8 8 4 9 0 8 9 8 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4
5 6 7 8 9 0 9 5 5 6 5 0 9 8 9 8 4 1 7 7 3 5 1 0 0 2 2 7 8 2 0 1 2 6 3 3 7
3 3 4 6 6 6 4 9 1 5 0 9 6 2 8 2 0 0 1 7 6 3 2 1 7 4 6 3 1 3 9 1 7 6 8 0 3
1 4 0 5 3 6 9 6 1 7 5 4 4 7 2 8 2 2 5 7 9 5 4 8 8 4 5 0 8 0 1 2 3 4 5 6 7
3 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 9 5 5 6 5 0 9 8 9 8 4 1 7 7
3 5 1 0 0 2 2 7 8 2 0 8 2 6 3 3 7 3 3 4 6 6 6 4 9 9 5 0 9 5 2 3 2 0 0 9 7
6 3 2 9 7 4 6 3 1 3 9 1 7 6 8 4 3 9 4 0 5 3 6 9 6 9 7 5 4 4 7 2 8 2 2 5 7
9 5 4 8 8 4 3 0 7 9 8 0 1 2 3 4 5 1 7 1 9 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
8 9 6 9 5 5 6 5 0 9 4 9 8 4 1 7 7 3 5 1 0 0 0 2 7 8 2 0 1 2 6 8 3 7 7 3 4
6 6 6 9 9 1 5 0 9 5 2 8 0 1 7 6 3 2 1 7 8 6 3 1 3 9 1 7 6 8 4 3 1 4 0 5 3
6 9 6 1 7 5 4 4 7 2 2 5 7 8 5 9 4 5 0 8 9 8 0 1 2 3 4 5 6 7 8 9 0 1 2 8 4
5 6 7 8 9 0 1 2 8 4 5 6 7 8 9 0 9 5 5 6 5 0 9 8 9 8 4 1 7 7 7 5 1 0 0 2 2
7 8 2 0 1 2 6 8 8 7 5 8 4 6 6 6 4 9 1 5 0 9 5 2 8 2 0 0 1 7 6 3 2 1 7 4 6
3 1 3 9 1 7 6 8 4 8 1 4 0 5 3 6 9 6 1 7 5 4 4 7 2 8 2 2 5 7 9 5 4 8 8 4 9
0 8 9 8]
```
``` python
print(y_test)
```
*出力結果:*
```txt
[7 3 5 1 0 0 2 2 7 8 2 0 1 2 6 3 3 7 3 3 4 6 6 6 4 9 1 5 0 9 5 2 8 2 0 0 1
7 6 3 2 1 7 4 6 3 1 3 9 1 7 6 8 4 3 1 4 0 5 3 6 9 6 1 7 5 4 4 7 2 8 2 2 5
7 9 5 4 8 8 4 9 0 8 9 8 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4
5 6 7 8 9 0 9 5 5 6 5 0 9 8 9 8 4 1 7 7 3 5 1 0 0 2 2 7 8 2 0 1 2 6 3 3 7
3 3 4 6 6 6 4 9 1 5 0 9 5 2 8 2 0 0 1 7 6 3 2 1 7 4 6 3 1 3 9 1 7 6 8 4 3
1 4 0 5 3 6 9 6 1 7 5 4 4 7 2 8 2 2 5 7 9 5 4 8 8 4 9 0 8 0 1 2 3 4 5 6 7
8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 9 5 5 6 5 0 9 8 9 8 4 1 7 7
3 5 1 0 0 2 2 7 8 2 0 1 2 6 3 3 7 3 3 4 6 6 6 4 9 1 5 0 9 5 2 8 2 0 0 1 7
6 3 2 1 7 4 6 3 1 3 9 1 7 6 8 4 3 1 4 0 5 3 6 9 6 1 7 5 4 4 7 2 8 2 2 5 7
9 5 4 8 8 4 9 0 8 9 8 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 9 0 1 2 3 4 5 6 7
8 9 0 9 5 5 6 5 0 9 8 9 8 4 1 7 7 3 5 1 0 0 2 2 7 8 2 0 1 2 6 3 3 7 3 3 4
6 6 6 4 9 1 5 0 9 5 2 8 0 1 7 6 3 2 1 7 4 6 3 1 3 9 1 7 6 8 4 3 1 4 0 5 3
6 9 6 1 7 5 4 4 7 2 2 5 7 9 5 4 4 9 0 8 9 8 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4
5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 9 5 5 6 5 0 9 8 9 8 4 1 7 7 3 5 1 0 0 2 2
7 8 2 0 1 2 6 3 3 7 3 3 4 6 6 6 4 9 1 5 0 9 5 2 8 2 0 0 1 7 6 3 2 1 7 4 6
3 1 3 9 1 7 6 8 4 3 1 4 0 5 3 6 9 6 1 7 5 4 4 7 2 8 2 2 5 7 9 5 4 8 8 4 9
0 8 9 8]
```
割と近いように思えます。混同行列を見てみましょう。
``` python
from sklearn import metrics
print(metrics.confusion_matrix(y_test, y_pred))
```
*出力結果:*
```txt
[[58 0 0 0 0 0 1 0 0 0]
[ 0 53 0 0 0 0 1 0 2 5]
[ 1 0 59 0 0 0 0 0 0 0]
[ 0 0 0 52 0 1 0 2 7 0]
[ 1 0 0 0 57 0 0 0 1 2]
[ 0 0 0 0 0 58 1 0 0 0]
[ 0 1 0 0 0 0 60 0 0 0]
[ 0 0 0 0 0 0 0 60 0 0]
[ 0 1 0 2 1 0 0 1 50 0]
[ 0 0 0 1 0 2 0 1 1 53]]
```
具体的な数値としての正答率を確認します。
``` python
print(metrics.classification_report(y_test, y_pred))
```
*出力結果:*
```
precision recall f1-score support
0 0.97 0.98 0.97 59
1 0.96 0.87 0.91 61
2 1.00 0.98 0.99 60
3 0.95 0.84 0.89 62
4 0.98 0.93 0.96 61
5 0.95 0.98 0.97 59
6 0.95 0.98 0.97 61
7 0.94 1.00 0.97 60
8 0.82 0.91 0.86 55
9 0.88 0.91 0.90 58
accuracy 0.94 596
macro avg 0.94 0.94 0.94 596
weighted avg 0.94 0.94 0.94 596
```
上記の例ではrecall(正答率)が `0.94` 、つまり94%であると出ました。かなり良い精度の分類器になったようです。
## まとめ <!-- #### 学習のまとめ に変更 -->
このレッスンでは、機械学習の概要、ならびに機械学習プログラムの作り方について紹介しました。
<!-- 以下削除
#### Python系コースと機械学習の学習内容に関する補足
先述のように、テックアカデミーのPython系コースでは、AWS Cloud9上でPythonの環境をご自身で構築いただき、学習を進めます。
また、Python/AI/データサイエンスの各コースは、モデルの作成において以下のような違いがあります。
- Pythonコース:scikit-learnを使って、さまざまな機械学習プログラム(教師あり学習)を作成する
- AIコース:教師なし学習や、ディープラーニング(Tensorflow利用)のプログラムも作成する
- データサイエンスコース:パッケージライブラリを使うのみの上記2コースとは違い、数学的な側面を理解しながらモデルを構築する
<div class="alert alert-info">
Pythonの言語に自信のない方は、Pythonコースからの受講をおススメします。
</div>
--><!-- Techpitの学習内容と異なるため -->