学生の備忘録なブログ

日々のことを忘れないためのブログです。一日一成果物も目標。技術系はQiitaにあげるように変更しました。

機械学習入門_第1章

機械学習の世界へ入門

www.oreilly.co.jp

各所でおすすめされている本.scikit-learnの作者の書いた本で,信頼がおけそう.理論的な部分より,scikit-learnの使ってできる解析としての機械学習を説明している.

データ分析の世界,というよりライブラリの進歩は著しく,基礎的説明より実践を重視する本は2,3年で通用しなくなる.(というより学習効率が下がる.)

www.oreilly.co.jp

こちらの本は2013年の本で,2017年現在はいささか古い.

この次にこの本で数式を踏まえた理論的な部分を勉強したい.

book.impress.co.jp

上記サイトに学習の手引がある.必読.

この分野では,PLMLがバイブルとして有名だが,少し見てめまいがするほど数式で埋め尽くされていた.よって,上記の2つの本で勉強していきたい.

python/numpy - 機械学習の「朱鷺の杜Wiki」

DeepLearning

www.oreilly.co.jp

ざっくりとした知識を付けた最大の要因は,KerasのMNISTチュートリアルをやってみること.怖がらずに四苦八苦しながら写経を恐れず書いてみた.わからない概念は,上記の本で分かりやすく解説されている.Kerasはラッパーとしてとても優秀なのでドキュメントをみれば,参考書内の概念が理解できる.

1.4.2

NumPy

import numpy as np
x = np.array([[1,2,3],[4,5,6]])#括弧は大外にも必要
print("x:\n{}".format(x))
x:
[[1 2 3]
 [4 5 6]]
print(x)
[[1 2 3]
 [4 5 6]]

SciPy

scikit-learnはアルゴリズムの実装をscipyの関数を用いている.scipy.sparseで疎行列を表現する.疎行列はone-hotの2次元配列を格納するのに使う.

from scipy import sparse

# 対角成分が1でそれ以外が0の2次元NumPy配列を作る
eye = np.eye(4)
print("NumPy array:\n{}".format(eye))
NumPy array:
[[ 1.  0.  0.  0.]
 [ 0.  1.  0.  0.]
 [ 0.  0.  1.  0.]
 [ 0.  0.  0.  1.]]
# NumPy配列をSciPyのCSR形式の疎行列に変換する
# 非ゼロ要素のみが格納される
sparse_matrix = sparse.csr_matrix(eye)
print("\nSciPy sparse CSR matrix:\n{}".format(sparse_matrix))
SciPy sparse CSR matrix:
  (0, 0)    1.0
  (1, 1)    1.0
  (2, 2)    1.0
  (3, 3)    1.0
# 疎なデータ表現を直接作る必要がある.ここでは,上のものと同じ疎行列をCOO形式で作っている.
data = np.ones(4)
row_indices = np.arange(4)
col_indices = np.arange(4)
eye_coo = sparse.coo_matrix((data, (row_indices, col_indices)))
print("COO representation:\n{}".format(eye_coo))
COO representation:
  (0, 0)    1.0
  (1, 1)    1.0
  (2, 2)    1.0
  (3, 3)    1.0
%matplotlib inline

import matplotlib.pyplot as plt
# -10から10までを100ステップに区切った列を配列として生成
# Generate a sequence numbers from -10 to 10 with 100 steps in between
x = np.linspace(-10, 10, 100)
print(x)
# create a second array using sinus
y = np.sin(x)
print(y)
# The plot function makes a line chart of one array against another
plt.plot(x, y, marker="x")

[f:id:forhighlow:20170927002441p:plain]
[-10.          -9.7979798   -9.5959596   -9.39393939  -9.19191919
  -8.98989899  -8.78787879  -8.58585859  -8.38383838  -8.18181818
  -7.97979798  -7.77777778  -7.57575758  -7.37373737  -7.17171717
  -6.96969697  -6.76767677  -6.56565657  -6.36363636  -6.16161616
  -5.95959596  -5.75757576  -5.55555556  -5.35353535  -5.15151515
  -4.94949495  -4.74747475  -4.54545455  -4.34343434  -4.14141414
  -3.93939394  -3.73737374  -3.53535354  -3.33333333  -3.13131313
  -2.92929293  -2.72727273  -2.52525253  -2.32323232  -2.12121212
  -1.91919192  -1.71717172  -1.51515152  -1.31313131  -1.11111111
  -0.90909091  -0.70707071  -0.50505051  -0.3030303   -0.1010101
   0.1010101    0.3030303    0.50505051   0.70707071   0.90909091
   1.11111111   1.31313131   1.51515152   1.71717172   1.91919192
   2.12121212   2.32323232   2.52525253   2.72727273   2.92929293
   3.13131313   3.33333333   3.53535354   3.73737374   3.93939394
   4.14141414   4.34343434   4.54545455   4.74747475   4.94949495
   5.15151515   5.35353535   5.55555556   5.75757576   5.95959596
   6.16161616   6.36363636   6.56565657   6.76767677   6.96969697
   7.17171717   7.37373737   7.57575758   7.77777778   7.97979798
   8.18181818   8.38383838   8.58585859   8.78787879   8.98989899
   9.19191919   9.39393939   9.5959596    9.7979798   10.        ]
[ 0.54402111  0.36459873  0.17034683 -0.03083368 -0.23076008 -0.42130064
 -0.59470541 -0.74392141 -0.86287948 -0.94674118 -0.99209556 -0.99709789
 -0.96154471 -0.8868821  -0.77614685 -0.63384295 -0.46575841 -0.27872982
 -0.0803643   0.12126992  0.31797166  0.50174037  0.66510151  0.80141062
  0.90512352  0.97202182  0.99938456  0.98609877  0.93270486  0.84137452
  0.7158225   0.56115544  0.38366419  0.19056796 -0.01027934 -0.21070855
 -0.40256749 -0.57805259 -0.73002623 -0.85230712 -0.93992165 -0.98930624
 -0.99845223 -0.96698762 -0.8961922  -0.78894546 -0.64960951 -0.48385164
 -0.2984138  -0.10083842  0.10083842  0.2984138   0.48385164  0.64960951
  0.78894546  0.8961922   0.96698762  0.99845223  0.98930624  0.93992165
  0.85230712  0.73002623  0.57805259  0.40256749  0.21070855  0.01027934
 -0.19056796 -0.38366419 -0.56115544 -0.7158225  -0.84137452 -0.93270486
 -0.98609877 -0.99938456 -0.97202182 -0.90512352 -0.80141062 -0.66510151
 -0.50174037 -0.31797166 -0.12126992  0.0803643   0.27872982  0.46575841
  0.63384295  0.77614685  0.8868821   0.96154471  0.99709789  0.99209556
  0.94674118  0.86287948  0.74392141  0.59470541  0.42130064  0.23076008
  0.03083368 -0.17034683 -0.36459873 -0.54402111]





[<matplotlib.lines.Line2D at 0x10d96bb38>]

png

pandas

import pandas as pd
from IPython.display import display

# create asimple dataset of people
data = {'Name': ["Jone", "Anna","Peter", "Linda"],
        'Location': ["Nes York", "Paris", "Berlin", "London"],
        'Age': [24, 13, 53, 33]
       }
data_pandas = pd.DataFrame(data)
# Ipython.displayを用いると,きれいに表示できる.
display(data_pandas)
Age Location Name
0 24 Nes York Jone
1 13 Paris Anna
2 53 Berlin Peter
3 33 London Linda
# One of many possible ways to query the table:
# selecting all rows that have an age column greate than 30
display(data_pandas[data_pandas.Age > 30])
Age Location Name
2 53 Berlin Peter
3 33 London Linda

mglearn

import sys
print("Python version: {}".format(sys.version))

import pandas as pd
print("pandas version: {}".format(pd.__version__))

import matplotlib
print("matplotlib version: {}".format(matplotlib.__version__))

import numpy as np
print("NumPy version: {}".format(np.__version__))

import scipy as sp
print("SciPy version: {}".format(sp.__version__))

import IPython
print("IPython version: {}".format(IPython.__version__))

import sklearn
print("scikit-learn version: {}".format(sklearn.__version__))
Python version: 3.6.1 |Continuum Analytics, Inc.| (default, May 11 2017, 13:04:09) 
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.57)]
pandas version: 0.20.3
matplotlib version: 2.0.2
NumPy version: 1.13.1
SciPy version: 0.19.1
IPython version: 6.1.0
scikit-learn version: 0.19.0

A First Application: Classifying iris species¶

アイリスのクラス分析

irisデータセットはscikit-learnのload_iris関数で読み込むことができる.

from sklearn.datasets import load_iris
iris_dataset = load_iris()
# load_irisが返すirisオブジェクトは,ディクショナリによく似たBunchクラスのオブジェクトで,キーと値を持つ.
print("Keys of iris_dataset: {}".format(iris_dataset.keys()))
Keys of iris_dataset: dict_keys(['data', 'target', 'target_names', 'DESCR', 'feature_names'])
print(iris_dataset['DESCR'][:193] + "\n...")
Iris Plants Database
====================

Notes
-----
Data Set Characteristics:
    :Number of Instances: 150 (50 in each of three classes)
    :Number of Attributes: 4 numeric, predictive att
...

キーDESCRの値は,データセットの簡単な説明である.(description)ここでは,説明の最初だけ見る.

Iris Plants Database

Notes

Data Set Characteristics:特性 :Number of Instances: 150 (50 in each of three classes)インスタンスの数:150(3クラスにそれぞれ50ずつ) :Number of Attributes: 4 numeric, predictive att属性の数:4つの数値属性,予測に利用可能 ...

キーtarget_namesに対応する値は文字列の配列で, 予測しようとしている花の種類が格納されている

print("Target names:{}".format(iris_dataset['target_names']))
Target names:['setosa' 'versicolor' 'virginica']

キーfeature_namesに対応する値は文字列のリストで.それぞれの特徴量の説明が格納されている.

print("Feature names: \n{}".format(iris_dataset['feature_names']))
Feature names: 
['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']

Feature names: 特徴量の名前 ガクの長さ 幅 花弁の長さ ['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']花弁の幅

ダータ本題は,targetとdataフィールドに格納されている.dataには,ガクの長さ,ガクの幅などが,NumPy配列とし格納されている.

print("Type of data:{}".format(type(iris_dataset['data'])))
Type of data:<class 'numpy.ndarray'>

配列dataの行は個々の花に対応し,列は個々の花に対して行われた4つの測定に対応する.

print("Shape of data: {}".format(iris_dataset['data'].shape))
Shape of data: (150, 4)

配列には170の花の測定結果が格納されている.機械学習では個々のアイテムをサンプルといい,その特徴を特徴量と呼ぶことを思い出そう.data配列のshapeはサンプルの個数かける特徴量の数である.これはscikit-learnの慣習的表現である.

print("First five columns of data:\n{}".format(iris_dataset['data'][:5]))
First five columns of 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つの花は花弁の幅が全て0.2cmで,5つの中では最初の花が最も長い5.1cmのガクを持っていることがわかる.

配列targetには,測定された個々の花の種類が,やはりNumPy配列として格納されている.

print("Type of target: {}".format(type(iris_dataset['target'])))
Type of target: <class 'numpy.ndarray'>

targetは1次元の配列で,個々の花に1つのエントリが対応する.

print("Shape of target: {}".format(iris_dataset['target'].shape))
Shape of target: (150,)

種類は0から2までの整数としてエンコードされている.

print("Target:\n{}".format(iris_dataset['target']))
Target:
[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]

これらの数値の意味は,配列iris['target_names']で与えられる.0はsetosaを1はversicolorを2はvirginicaを意味する.

データセットを並べ替えて,分割するtrain_test_splitという関数がscikit-learnには用意されている.データのテストに用いる.

1.7.2

成功度合いの測定:訓練データとテストデータ

過学習した状態を換言すると,モデルがうまく汎化(generalize)できていない状態である.

scikit-learnでは,データを大文字のXで,ラベルを小文字のyで表すのが,一般的である.これは数式のf(x)=yに習っている.さらに数学での寒冷に従い,2次元配列(行列)であるデータには,大文字のXを,1次元配列(べクトル)であるラベルには,小文字yを用いる.

https://github.com/amueller/introduction_to_ml_with_python/raw/cccbbca86d00f9d5ffb643c740de4489de80436d/images/iris_petal_sepal.png

train_test_split関数を呼び出し結果を,この命名規則に従った変数に代入しよう.

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
    iris_dataset['data'],iris_dataset['target'],random_state=0)

train_test_split関数は,分割を行う前に擬似乱数を用いてデータをシャッフルする.データポイントはラベルでソートされているので,単純に最後に25%をデータセットにすると,すべてのデータポイントがラベル2になってしまう.3クラスの内,1つしか含まれていないようなデータセットでは,モデルの汎化がうまく行っているか判断できない.だから,先にデータをシャッフルし,テストデータに全てのクラスが含まれるようにする.

同じ関数を何度か呼び出した際に,確実に同じ結果が得られるよう,ramdom_stateパラメータを用いて,擬似乱数生成器に同じシードを渡している.これによって出力が決定的になり,常に同じ結果が得られるようになる.本書では,乱数を用いる際には,常にこのようにrandom_stateパラメータを固定し用いる.

関数train_test_splitの出力は,X_train,X_test,y_test,y_train,y_testとなる.これらは全てNumpy配列で,X_trainにはデータセットの75%の行が,X_testには残りの25%が含まれている.

print("X_train shape:{}".format(X_train.shape))
print("y_train shape:{}".format(y_train.shape))
X_train shape:(112, 4)
y_train shape:(112,)
print("X_train shape: {}".format(X_train.shape))
print("y_train shape: {}".format(y_train.shape))
X_train shape: (112, 4)
y_train shape: (112,)

1.7.3 最初にすべきこと:データを良く観察する

データは精査すべきである.

データを検査する最良の方法は可視化である. その1つは,散布図である. 2次元の画面で散布図を効果的にプロットするのがペアプロットである. 全ての組合せ可能な特徴量の組み合わせをプロットするものだ. 特徴量の数が少ない場合にはうまくいく.しかし相関を同時にみることができないため,この方法で可視化してもデータの興味深い側面を見ることができない場合がある.

以下のグラフを作成するには,まずnumpy配列をpandasのdataframeに変換する.pandasはscatter_matrixと呼ばれるペアプロットを作成する関数を持つ.グラフマトリックスの対角部分にはここの特徴量のヒストグラムが描画される.

# X_trainのデータからDataFrameを作る.
# iris_dataset.feature_namesの文字列を使ってカラムに名前をつける
iris_datagrame = pd.DataFrame(X_train, columns=iris_dataset.feature_names)
# データフレームからscatter matrixを作成し,y_trainに従って色をつける
pd.plotting.scatter_matrix(iris_dataframe, c=y_train, figsize=(15, 15), marker='o',
                           hist_kwds={'bins': 20}, s=60, alpha=.8, cmap=mglearn.cm3)
----------------------------------------------------------------------

NameError                            Traceback (most recent call last)

<ipython-input-31-f2a828888675> in <module>()
      3 iris_datagrame = pd.DataFrame(X_train, columns=iris_dataset.feature_names)
      4 # データフレームからscatter matrixを作成し,y_trainに従って色をつける
----> 5 pd.plotting.scatter_matrix(iris_dataframe, c=y_train, figsize=(15, 15), marker='o',
      6                            hist_kwds={'bins': 20}, s=60, alpha=.8, cmap=mglearn.cm3)


NameError: name 'iris_dataframe' is not defined
# create dataframe from data in X_train
# label the columns using the strings in iris_dataset.feature_names
iris_dataframe = pd.DataFrame(X_train, columns=iris_dataset.feature_names)
# create a scatter matrix from the dataframe, color by y_train
grr = pd.plotting.scatter_matrix(iris_dataframe, c=y_train, figsize=(15, 15), marker='o',
                           hist_kwds={'bins': 20}, s=60, alpha=.8, cmap=mglearn.cm3)
----------------------------------------------------------------------

NameError                            Traceback (most recent call last)

<ipython-input-32-5c8cdb97d244> in <module>()
      4 # create a scatter matrix from the dataframe, color by y_train
      5 grr = pd.plotting.scatter_matrix(iris_dataframe, c=y_train, figsize=(15, 15), marker='o',
----> 6                            hist_kwds={'bins': 20}, s=60, alpha=.8, cmap=mglearn.cm3)


NameError: name 'mglearn' is not defined
# create dataframe from data in X_train
# label the columns using the strings in iris_dataset.feature_names
iris_dataframe = pd.DataFrame(X_train, columns=iris_dataset.feature_names)
# create a scatter matrix from the dataframe, color by y_train
pd.plotting.scatter_matrix(iris_dataframe, c=y_train, figsize=(15, 15), marker='o',
                           hist_kwds={'bins': 20}, s=60, alpha=.8, cmap=mglearn.cm3)
----------------------------------------------------------------------

NameError                            Traceback (most recent call last)

<ipython-input-33-0c4391853194> in <module>()
      4 # create a scatter matrix from the dataframe, color by y_train
      5 pd.plotting.scatter_matrix(iris_dataframe, c=y_train, figsize=(15, 15), marker='o',
----> 6                            hist_kwds={'bins': 20}, s=60, alpha=.8, cmap=mglearn.cm3)


NameError: name 'mglearn' is not defined
# create dataframe from data in X_train
# label the columns using the strings in iris_dataset.feature_names
iris_dataframe = pd.DataFrame(X_train, columns=iris_dataset.feature_names)
# create a scatter matrix from the dataframe, color by y_train
pd.plotting.scatter_matrix(iris_dataframe, c=y_train, figsize=(15, 15), marker='o',
                           hist_kwds={'bins': 20}, s=60, alpha=.8, cmap=mglearn.cm3)
----------------------------------------------------------------------

NameError                            Traceback (most recent call last)

<ipython-input-34-0c4391853194> in <module>()
      4 # create a scatter matrix from the dataframe, color by y_train
      5 pd.plotting.scatter_matrix(iris_dataframe, c=y_train, figsize=(15, 15), marker='o',
----> 6                            hist_kwds={'bins': 20}, s=60, alpha=.8, cmap=mglearn.cm3)


NameError: name 'mglearn' is not defined

1.7.4 最初のモデル:k-最近傍法

scikit-learnにはさまざまなクラス分類アルゴリズムが用意されている.ここでは,わかりやすい,k-最近傍法(k-Nearest Neighbors)によるクラス分類を用いる.このモデルを構築するには,単に訓練セットを格納するだけでよい.

このアルゴリズムは,新しいデータポイントに対して予測する際に,新しい点に最も近い点を訓練セットから探し,新しい点に最も近かった点のラベルを新しいデータポイントに与える.

k-最近傍法のkは,新しい点に最も近い1点だけを用いたりするのではなく,訓練セットの中の固定されたk個の近傍点(例えば3,5)を用いることができることを意味する.予測には,これらの近傍店の中の多数を占めるクラスを採用する.今回は,1つの近傍点しか使わない.

すべてのscikit-learnの機械学習モデルには,Estimatorと総称される個別のクラスに実装される.k-最近傍法クラス分類アルゴリズムはneighborsモジュールのKNeiborsClassifierクラスに実装されている.モデルを使う前に,クラスのインスタンスを生成してオブジェクトを作らなけれあならない.この際にパラメータを渡すことができる.KNeighborsClassfierの最も重要なパラメータは近傍点の数だが,ここでは1つとする.

from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier(n_neighbors=1)

knnオブジェクトは,訓練データからモデルを構築する際に用いられるアルゴリズムと新しいデータポイントに対して予測するためのアルゴリズムとをカプセル化している.さらに,訓練データからアルゴリズムがが抽出した情報も保持する.KNeighborsClassifierの場合には,単純に訓練データその斧を保持している.

訓練セットからモデルを構築するにはknnオブのfitメソッドを呼び出す.このメソッドはは,訓練データを含むNumpy配列X_trainとそれに対応する訓練ラベルを含むNumpu配列y_trainを引数に取る.

knn.fit(X_train, y_train)
KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
           metric_params=None, n_jobs=1, n_neighbors=1, p=2,
           weights='uniform')

このfitメソッドはknnオブエクとそのものを返す(同時にknnを書き換える)ので,出力にこのクラス分類オブジェクトの文字列表現が表示されている.この文字列表現から,モデルを構築する際に用いられたパラメータが分かる.ほとんどのパラメータがデフォルトだが,n_neighbors=1だけは,我々が与えたものだ.scigit-learnのモデルの多くは多数のパラメータがあるが,ほとんどは速度の最適化のためや,まれにしか使われないものだ.

1.7.5 予測を行う.

さて,ラベルが分かっていない新しいデータに対して予測をしてみよう.野生のアイリスを見つけたとして,ガク長5cm,ガク幅2.9,花弁の長さが1cm花弁の幅が0.2cm,である.このアイリスの品種はなんだろうか.このデータをnumpy配列に格納し,さの形を計算してみる

X_new = np.array([[5,2.9,1,0.2]])
print("X_new.shape:{}".format(X_new.shape))
X_new.shape:(1, 4)

ここで,1つの花の測定結果を2次元のnumpy配列にしていることに注意しよう.これは,scikit-learnが常に入力が2次元numpy配列であることを前提にするためだ. 予測を行うにはknnオブジェクトのpredickメソッドを呼ぶ.

prediction = knn.predict(X_new)
print("Prediction: {}".format(prediction))
print("Predicted target name: {}".format(
       iris_dataset['target_names'][prediction]))
Prediction: [0]
Predicted target name: ['setosa']

我々のモデルは,新しいアイリスをクラス0すなわち,setosaだと判断した.

しかし,これは正しいかどうかわからない.

1.7.6 モデルの評価

ここで先程作ったテストセットを用いる.モデルがどれくらい上手く機能していかどうか.精度(accuracy)を計算して測定できる. 精度は正しく品種を予測できたアイリスの割合である.

y_pred = knn.predict(X_test)
print("Test set predictions:\n {}".format(y_pred))
Test set predictions:
 [2 1 0 2 0 2 0 1 1 1 2 1 1 1 1 0 1 1 0 0 2 1 0 0 2 0 0 1 1 0 2 1 0 2 2 1 0
 2]
print("Test set score: {:.2f}".format(np.mean(y_pred == y_test)))
Test set score: 0.97

knnオブジェクトのscoreメソッドを用いても良い.このメソッドはテストセットに対応する精度を計算してくれる.

print("Test set score: {:.2f}".format(knn.score(X_test,y_test)))
Test set score: 0.97

このモデルでは,97%の精度で予測できた.

1.8 まとめと今後の展望

クラス分類問題では,分類結果となる品種はクラスと呼ばれ,個々のアイリスの品種はラベルと呼ばれる.

今回はk-最近傍法クラス分類アルゴリズムを用いた.これは,新しいデータポイントのラベルをそれと最も近い訓練データによって予測するアルゴリズムだ.このクラスはKNeighborsClassifierッックラスに実装される.

以下必要最小限の実装

X_train, X_test, y_train, y_test = train_test_split(
    iris_dataset['data'], iris_dataset['target'], random_state=0)

knn = KNeighborsClassifier(n_neighbors=1)
knn.fit(X_train, y_train)

print("Test set score: {:.2f}".format(knn.score(X_test, y_test)))
Test set score: 0.97

fit,predict,scoreメソッドは教師あり学習モデルに共通のインターフェースである.