• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
機械学習のPythonとの出会い(1):単純ベイズ基礎編
 

機械学習のPythonとの出会い(1):単純ベイズ基礎編

on

  • 7,911 views

機械学習の Python との出会い: http://www.kamishima.net/mlmpyja/

機械学習の Python との出会い: http://www.kamishima.net/mlmpyja/
通読される場合は,このスライドよりも,フルバージョンのチュートリアルの方読みやすいです..

Statistics

Views

Total Views
7,911
Views on SlideShare
7,851
Embed Views
60

Actions

Likes
32
Downloads
81
Comments
0

4 Embeds 60

https://twitter.com 33
https://cybozulive.com 24
http://us-w1.rockmelt.com 2
http://s.deeeki.com 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    機械学習のPythonとの出会い(1):単純ベイズ基礎編 機械学習のPythonとの出会い(1):単純ベイズ基礎編 Presentation Transcript

    • 機械学習のPythonとの出会い (1) 単純ベイズ:入門編 神嶌 敏弘 ( http://www.kamishima.net/ ) Tokyo.Scipy #4 (2012.06.18) 1
    • 自己紹介• 専門について • 機械学習やデータマイニングが専門と名乗ってます • PRML本とか翻訳しましたが,変分ベイズとか,MCMC とか複雑 なことは全然してません • 手法を深掘りすることよりも,新しい問題設定を考えて,できるだ け簡単な方法で解くようにしたいと思ってます• NumPy / SciPy への道 • 数値計算系のプログラムを C で書くのに疲れて Perl でしばらく書 いてましたがあまり数値計算向けのではなく挫折 • 2009年は R を書き始めたのですが,メモリ関係や,オブジェクト 指向の問題で挫折 • 2010年に,NumPy / SciPy に手をだしたけど,脳のオッサン化 のため習得に1年ぐらい費やしたが,どうにか使えるように 2
    • 機械学習のPythonとの出会い 通読される場合は以下のオリジナル版の方が読みやすいです機械学習のPythonとの出会い (Machine Learning Meets Python) http://www.kamishima.net/mlmpyja/• このチュートリアルは,機械学習とPythonの基礎を知っている人 が,NumPy / SciPy を使って数値計算プログラムが書けるようにな ることを目標としたチュートリアルです• パッケージ機能の網羅的な解説ではなく,具体的なアルゴリズムを実 装する過程を通じて,パッケージをどう使うかを知ることができるよ うに工夫しました• Sphinx を用いて執筆しており,HTML版の他PDF版とePub版も配布• ソースはGitHubで公開 https://github.com/tkamishima/mlmpy 3
    • NumPy配列の基礎 4
    • NumPy 配列の基礎np.ndarray:N-d Array すなわち,N次元配列を扱うためのクラスnp.ndarray と多重リストによる配列の違い (1)1.メモリ上での保持 • 多重リスト:リンクでセルを結合した形式でメモリ上に保持 • 多重リストは動的に変更可能 • np.ndarray:メモリの連続領域上に保持 • 形状変更には全体の削除・再生成が必要.2.要素の型 • 多重リスト:リスト内でその要素の型が異なることが許される • np.ndarray:基本的に全て同じ型の要素 5
    • 3.配列の形状 • 多重リスト:次元ごとに要素数が違ってもよい • np.ndarray:各次元ごとの要素数が等しい • 行ごとに列数が異なるような2次元配列などは扱えない4.高度で高速な演算操作 • np.ndarray:行や列を対象とした多くの高度な数学的操作を,多 重リストより容易かつ高速に適用可能 (fancy indexing) • 配列中の全要素,もしくは一部の要素に対してまとめて演算や関 数を適用することで,高速な処理が可能 (ブロードキャスト,ユ ニバーサル関数) 6
    • NumPy 配列の生成• np.array を使った生成 np.array(object, dtype=None)• object には,配列の内容を,array_like という型で与える • array_like型:配列を np.ndarray の他,(多重)リスト や(多重)タプルで表現したもの• 要素が 1, 2, 3 である長さ 3 のベクトルの例 In [10]: a = np.array([1, 2, 3]) In [11]: a Out[11]: array([1, 2, 3])• タプルを使った表現も可能です: In [12]: a = np.array((10, 20, 30)) In [13]: a Out[13]: array([10, 20, 30]) 7
    • NumPy 配列の生成• 2重にネストしたリストで表した配列の例 In [14]: a = np.array([[1.5, 0], [0, 3.0]]) In [15]: a Out[15]: array([[ 1.5, 0. ], [ 0. , 3. ]])• リストの要素に np.ndarray やタプルを含むことも可能 In [16]: a = np.array([1.0, 2.0, 3.0]) In [17]: b = np.array([a, (10, 20, 30)]) In [18]: b Out[18]: array([[ 1., 2., 3.], [ 10., 20., 30.]]) 8
    • 0行列と1行列• 0行列(要素が全て 0 の配列)の生成 np.zeros(shape, dtype=None)• 1行列(要素が全て 1 の配列)の生成 np.ones(shape, dtype=None)• shape:スカラーや,タプルによって配列の各次元の長さを表現• 長さが 3 の 0 ベクトルの例 In [20]: np.zeros(3) Out[20]: array([ 0., 0., 0.])• 3 4の1行列の例(引数をタプルにすることを忘れないように) In [21]: np.ones((3, 4)) Out[21]: array([[ 1., 1., 1., 1.], [ 1., 1., 1., 1.], [ 1., 1., 1., 1.]]) 9
    • 初期化なし配列• 要素の初期化なしで指定した大きさの配列を生成np.empty(shape, dtype=None)• 配列の生成後,その内容をすぐ後で書き換える場合には,配列の要素を 0 や 1 で初期化するのは無駄 → メモリだけ確保・初期化しない 10
    • 単位行列• 単位行列を生成np.identity(n, dtype=None)• n は行列の大きさを表す• 例:4 と指定=大きさ4 4の行列(単位行列は正方行列なので) In [30]: np.identity(4) Out[30]: array([[ 1., 0., 0., 0.], [ 0., 1., 0., 0.], [ 0., 0., 1., 0.], [ 0., 0., 0., 1.]]) 11
    • NumPy 配列の属性と要素の参照• np.ndarray クラスの主な属性• class np.ndarray • dtype:配列要素の型 • 後ほど,詳細を述べる • ndim:配列の次元数 • ベクトルでは 1 に,配列では 2 • shape:配列の各次元の大きさ=配列の形状 • 各次元ごとの配列の大きさをまとめたタプルで指定 • 例:長さが 5 のベクトルは (5,) ※ Python のタプルは1要素のときは , が必要 • 例:2 3行列では (2, 3) 12
    • dtype 属性• Python のビルトイン型に対応する型• 基本は np. を前に付けるだけ(str と unicode はのちほど) • 真理値型= np.bool,整数型= np.int,浮動小数点型= np.float,複素数型= np.complex• 厳密には _ を最後につけた np.int_ などが,Pure Pythonと互換だ けど,あまり気にしない• メモリのビット数を明示的に表す np.int32 や np.float64 なども • 利用目的 • メモリを特に節約したい場合(10までなら8bitで十分) • C や Fortran で書いた関数とリンクする(32bit・64bitのどち らの環境で実行しても暴走しない) 13
    • 文字列のdtype• ビルトイン型の str / unicode と NumPy のdtype の相違点 • np.ndarray の要素の大きさが同じである必要→文字列は固定長• NumPy での文字列型:NumPy の型を返す関数 np.dtype() を利用 • np.dtype(S<the length of string>)• 例:最大長が16である文字列 np.dtype("S16")• Unicode文字列の場合は,この S が U に置き換わります np.dtype("U16") 14
    • 配列のdtypeの指定方法(1) np.array() などの配列生成関数の dtype 引数で指定する方法 In [41]: a = np.array([1, 2, 3]) In [42]: a.dtype Out[42]: dtype(int64) In [43]: a = np.array([1, 2, 3], dtype=np.float) In [44]: a.dtype Out[44]: dtype(float64)(2) np.ndarray の np.ndarray.astype() メソッドを使う方法 In [50]: a = np.array([1, 2, 3]) In [51]: a.dtype Out[51]: dtype(int64) In [52]: a = a.astype(np.float) In [53]: a.dtype Out[53]: dtype(float64) In [54]: a Out[54]: array([ 1., 2., 3.]) 15
    • np.ndarrayの要素の参照• 多様な要素の参照方法がありますが,ここでは基本的な方法のみ• 各次元ごとに何番目の要素を参照するかを指定する方法• 例:1次元配列であるベクトル a の要素 3 を a[3] 参照 In [60]: a = np.array([1, 2, 3, 4, 5], dtype=float) In [61]: a[3] Out[61]: 4.0• 注意:添え字の範囲は,1 からではなく 0 から数える• 注意:a.shape[0] で,第1次元の要素の長さとして 5 が得られたとき添え字の範囲はそれより一つ前の 4 まで In [62]: a = np.array([[11, 12, 13], [21, 22, 23]]) In [63]: a.shape Out[63]: (2, 3) In [64]: a[1,2] Out[64]: 23 16
    • np.ndarrayと数学の行列• 1次元の np.ndarray 配列には,線形代数でいう縦ベクトルや横ベクトルという区別はなく,1次元配列は転置できません• 縦ベクトルや横ベクトルを区別して表現するには,それぞれ列数が1である2次元の配列と,行数が1である2次元配列を利用• 縦ベクトルの例: In [65]: np.array([[1], [2], [3]]) Out[65]: array([[1], [2], [3]])• 横ベクトルの例: In [66]: np.array([[1, 2, 3]]) Out[66]: array([[1, 2, 3]]) 17
    • 単純ベイズ:カテゴリ特徴の場合 18
    • 単純ベイズ:カテゴリ特徴の場合• 単純ベイズ法の詳しいことは他の資料を調べて下さい• 簡単な単純ベイズ法の実装 • クラス Y は二値で,{0, 1} の値をとる • 特徴数は K 個 • j 番目特徴の特徴を Xj で表す • 全ての特徴は二値で,{0, 1} の値をとる 19
    • 単純ベイズ:学習 学習すべきパラメータは二種類: Pr[y] と Pr[xj ¦ y] Pr[y]:クラス Y の分布のパラメータy {0, 1} について次の量を計算 クラスの値が y である事例数 N [yi = y] Pr[y] = N 事例数の総数Pr[xj ¦ y]:Y が与えられたときの Xj の条件付き分布のパラメータy {0, 1},xj {0, 1},j {0,…,K-1} について次の量を計算 クラスの値が y かつ j 番目の特徴が xj である事例数 N [xij = xj , yi = y] Pr[xj |y] = N [yi = y] 20
    • 単純ベイズ:予測 入力ベクトル xnew が与えられたときのクラス事後確率を y=0 と 1 の場合について計算して,大きな方を予測クラスにする 学習したパラメータを使うと計算できる new y = arg max log Pr[y] + ˆ log Pr[xj |y] y j y {0, 1} のうち大きな方を採用※小さなここでは対数をとって計算するのは,浮動小数点の数値計算 の不安定さを避けるため 21
    • 入力データとクラスの仕様 22
    • 入力データの仕様• 入力データ:特徴ベクトル xi とクラス yi の対を N 個集めたもの• 特徴ベクトルの配列 X • i 番目の行が,i 番目の特徴ベクトル xi となる • X.ndim は 2 に,X.shape は (N, K) になる • 配列の要素は {0, 1} なので,X.dtype は np.int• クラス配列 y • i 番目の要素が,i番目のクラス yi となる • y.ndim は 1 に,y.shape は (N,) になる • 配列の要素は {0, 1} なので,X.dtype は np.int 23
    • アルゴリズムをクラスで実装• 機械学習のアルゴリズムは,関数を用いるだけでも実装できますが,クラスを定義して実装しましょう• クラスを用いて実装する利点 • Python はオブジェクト指向型言語なのに,クラスを使わないのは 悲しい • クラスの継承を利用して,モデルと予測メソッドだけを共有し,学 習アルゴリズムだけを変えて部分的に改良するといったことが可能 • cPickle などの,オブジェクトのシリアライズを利用して,学習し たモデルのオブジェクトを,ファイルに保存しておくことで再利用 可能に 24
    • scikit-learnのAPI仕様• scikit-learn のクラス設計の基本仕様を踏襲 • あとで,scikit-learn の便利な機能を利用できて便利• コンストラクタ:クラスの初期化 • 引数:データに依存しないアルゴリズムのパラメータ• fit()メソッド:あてはめ・学習 • 引数:訓練データと,データに依存したパラメータ• predict()メソッド:学習済みデータを用いた予測 • 引数:予測対象の新規の入力データ• score()メソッド:モデルのデータへのあてはめの良さの評価 • 評価対象のデータを,このメソッドの引数で指定する.• transform()メソッド:次元削減などのデータ変換 25
    • NaiveBayes1クラスとコンストラクタ• 単純ベイズクラス:NaiveBayes1 • 親クラスは object• コンストラクタ • データに依存しないパラメータはないので,引数は self だけ • pY_ と pXgY_ は,P[y] と P[xj ¦ y] のパラメータ class NaiveBayes1(object): """ Naive Bayes class (1) """ def __init__(self): """ Constructor """ self.pY_ = None self.pXgY_ = None 26
    • 学習メソッドの実装(1) 27
    • 学習メソッドの実装(1)• fit() メソッド • 訓練事例からモデルのパラメータを学習するメソッド • 引数は特徴量行列 X とクラスベクトル y def fit(self, X, y):• 定数の処理 • 特徴数と事例数を,特徴量行列の大きさから取り出す n_samples = X.shape[0] n_features = X.shape[1] • ついでに,クラス数と特徴値数も定義 n_classes = 2 n_fvalues = 2• NumPy の本領は発揮できないけど,単純な方法での実装する 28
    • P[y] の計算• N[yi = y] の計算 • 大きさ n_classes の配列 nY を作り,0 で初期化 • サンプル y[i] の値に応じて nY を一つづつ数え上げる nY = np.zeros(n_classes, dtype=np.int) for i in xrange(n_samples): nY[y[i]] += 1• pY_[y] の計算 • パラメータの計算式より,nY[y] を事例の総数 n_samples で割る • 最初にメモリを確保してから,割り算の結果を代入 • 注意:整数型で割ると整数になるので型変換を忘れないように self.pY_ = np.empty(n_classes, dtype=np.float) for i in xrange(n_classes): self.pY_[i] = nY[i] / np.float(n_samples) 29
    • P[xj ¦ y] の計算• N[xij=xj, yi=y] の計算 • j=1…K について (xj, y) の個数を考えるので,(n_features, n_fvalues, n_classes) の大きさの3次元配列を確保 • あとは初期化して,数え上げるだけ nXY = np.zeros((n_features, n_fvalues, n_classes), dtype=np.int) for i in xrange(n_samples): for j in xrange(n_features): nXY[j, X[i, j], y[i]] += 1• pXgY_ の計算 • パラメータの計算式に従って割り算するだけ self.pXgY_ = np.empty((n_features, n_fvalues, n_classes), dtype=np.float) for j in xrange(n_features): for x in xrange(n_fvalues): for y in xrange(n_classes): self.pXgY_[j, x, y] = nXY[j, x, y] / np.float(nY[y]) 30
    • 予測メソッドの実装 31
    • 予測メソッドの実装• predict() メソッド • 学習したモデルパラメータを使って,未知の事例のクラスを予測 • 引数は,各行が,未知の特徴ベクトルに対応する2次元配列 X def predict(self, X):• 今度は NumPy の実力が発揮できるような方法で実装• fit()メソッドと同様に, n_samples や n_features などの定数 を設定 32
    • 1次元配列のスライス• スライス • リストや文字列などのスライスと同様の方法により,配列の一部分 をまとめて参照する方法• 1次元配列:リストのスライス表記と同様の 開始:終了:増分 の形式 In [10]: x = np.array([0, 1, 2, 3, 4]) In [11]: x[1:3] Out[11]: array([1, 2]) In [12]: x[0:5:2] Out[12]: array([0, 2, 4]) In [13]: x[::-1] Out[13]: array([4, 3, 2, 1, 0]) In [14]: x[-3:-1] Out[14]: array([2, 3]) 33
    • 複数要素の同時指定• 配列やリストを使って複数の要素を指定し,それらをまとめた配列を作る• 21行目の例:リストを使って,0番目,2番目,1番目,2番目の要素を繋げた配列• 22行目の例:配列を使って要素を取り出して,まとめた例 In [20]: x = np.array([10, 20, 30, 40, 50]) In [21]: x[[0, 2, 1, 2]] Out[21]: array([10, 30, 20, 30]) In [22]: x[np.array([3, 3, 1, 1, 0, 0])] Out[22]: array([40, 40, 20, 20, 10, 10]) 34
    • 2次元配列のスライス• スライスは,2次元以上の配列でも同様の操作が可能• 特に, : のみを使って,行や列全体を取り出す操作は頻繁に利用 In [30]: x = np.array([[11, 12, 13], [21, 22, 23]]) In [31]: x Out[31]: array([[11, 12, 13], [21, 22, 23]]) In [32]: x[0, :] Out[32]: array([11, 12, 13]) In [33]: x[:, 1] Out[33]: array([12, 22]) In [34]: x[:, 1:3] Out[34]: array([[12, 13], [22, 23]]) 35
    • 未知ベクトルの抽出• 入力未知ベクトル集合 X から,各行の未知ベクトル xi を抽出方法(1) • forループで,行をスライスで抽出する for i in xrange(n_samples): xi = X[i, :]• 方法 (2) • np.ndarray のイテレータは,最初の次元を順に走査する • 1次元配列なら要素を,2次元配列なら行を,3次元配列なら2次元 目と3次元目で構成される配列を順に返す for i, xi in enumerate(X): # process 36
    • 対数同時確率の計算• 対数同時確率を,y が 0 と 1 それぞれの場合について計算 new new log Pr[x , y] = log Pr[y] + log Pr[xj |y] j• 第1項は self.pY_ を用いて計算• 第2項は self.pXgY_ と未知入力ベクトル xi を用いて計算 37
    • log Pr[y] の計算• log Pr[y] を,y が 0 と 1 の場合を個別に計算する代わりに,ユニ バーサル関数を用いてまとめて計算• ユニバーサル関数 • 入力した配列の各要素に関数を適用し,その結果を入力と同じ形の 配列して返す • ユニバーサル関数の機能を利用するには,mathパッケージの math.log() などではなく,NumPy の np.log() を用いる• 例:self.pY_[0] と self.pY_[1] の値それぞれに対し対数を計算し, それぞれを logpXY[0] と logpXY[1] に格納する logpXY = np.log(self.pY_) 38
    • Σ log Pr[xj new ¦ y] の計算• log Pr[ xnew, y] の第2項 Σj log Pr[Pr[ xjnew ¦ y] の計算 • Σについての和は,全ての特徴 j に付いての和で,forループを使う • 新規ベクトルの特徴量の j 番目の値は xi[j] で取り出せる • y=0 と 1 の両方について値を求め,ユニバーサル関数でまとめて 計算する • 以上の条件から,j 番目の特徴の,入力特徴 xi[j] に対する分布 のベクトルは self.pXgY_[j, xi[j], :] で得られる • これの対数をとって,j についての総和を計算すればよい for j in xrange(n_features): logpXY = logpXY + np.log(self.pXgY_[j, xi[j], :]) 39
    • logpXY の計算の改良• NmyPy は,for ループを使わずにベクトルのまま計算すると速い • 現在は事例のループと特徴のループの2重ループでかっこ悪い • せめて1重分は for ループをやめたい→特徴 j のループをやめる• self.pXgY_[j, xi[j], :] のコードに対する検討 • 第要素 j は j=0,…, n_features-1 で変化させると 0,…, n_features - 1の系列になる • このような要素を作るには,数列を作る np.arange(n_featues) 使い方は,リスト用の range() 関数の使い方と同じ 40
    • logpXY の計算の改良• self.pXgY_[j, xi[j], :] のコードに対する検討 (続き) • 第2項は j=0,…, n_featues - 1 と変化させると xi[0],…, xi[n_features - 1] の系列 • これはすなわち xi そのものになる• これらの系列をまとめて引数にとると self.pXgY_[np.arange(n_features), xi, :]• これは,y=0 と 1 の場合に対応する長さ2のベクトルを,j=0, …,n_features-1 のである場合について集めたものとなる• すなわち,n_features n_classes の行列が得られる 41
    • logpXY の計算の改良• 得られた行列の対数をとって,j の方向,すなわち行列の列和をとる• それには,np.sum()関数を利用するnp.sum(a, axis=None, dtype=None) • a は総和をとる配列 • axis は,指定しなければ配列 a の全ての要素についての総和 • 指定した場合は shape の各次元方向の和となる 二次元配列=行列の場合は,axis=0 は列和,axis=1 は行和• これを利用して列和を求めるとforループなしで logpXY は計算可能 logpXY = np.log(self.pY_) + np.sum(np.log(self.pXgY_[np.arange(n_features), xi, :]), axis=0) 42
    • 予測クラスの決定• 以上で y=0 と 1 に対応する対数同時確率 logpXY が得られた• logpXY が最大になる要素の添え字が予測クラスになる• これには,配列の最大要素の添え字を返すnp.argmax()関数を利用• np.argmax(a, axis=None) • a は,最大要素を探す配列 • axis は指定がなければ,配列全体から,指定されていればその軸 での最大値を探す• この関数を使うと,予測クラスは次のコードで計算できる y[i] = np.argmax(logpXY)• argmax()メソッドもや,最小値用の np.argmin()関数も存在 43
    • 実行 44
    • np.genfromtxt()• カンマ区切り形式や,タブ区切り形式のテキストファイルを読み込み,それを NumPy 配列に格納する np.genfromtxt()関数np.genfromtxt(fname, dtype=<type float>,comments=#, delimiter=None) • fname:ファイル名か,open()関数でえたファイルオブジェクト • dtype:関数が返す配列の dtype 属性 • comments:コメント行を示す先頭文字 • delimiter:列の区切りを指定します.デフォルトは,タブを含 むホワイトスペース.csv なら , を指定.数値や数値のタプルに より,区切り文字ではなく,文字数で区切ることもできます. • 欠損値処理など他にも多数の引数があります • npy形式,matlab形式,Weka arff形式などに対応する関数も 45
    • 学習の実行• テキストファイルからデータを読み込む • 最後の列はクラス,それ以外が特徴量を表すファイル data = np.genfromtxt(vote_filled.tsv, dtype=np.int)• 行列を,特徴量部分と,クラス部分に分離 • -1 は,リストの場合と同様に最後から数える指定 X = data[:, :-1] y = data[:, -1]• コンストラクタで分類器を作り, fit()メソッドに訓練データを与えてモデルパラメータを学習 clr = NaiveBayes1() clr.fit(X, y) 46
    • 予測の実行• テスト用のデータは, X の最初の10個分を再利用• 予測クラスは,分類器の predict() メソッドで取得• 元のクラスと予測クラスを表示して,結果を確認 predict_y = clr.predict(X[:10, :]) for i in xrange(10): print i, y[i], predict_y[i] 47
    • 実行結果In [1]: %run run_nbayes1.py0 1 11 1 12 0 03 0 04 0 05 0 06 0 17 1 18 1 19 0 0 48
    • まとめ 49
    • まとめ• NumPy 配列の基礎 • NumPy 配列 np.ndarray の特徴 • np.array() による NumPy 配列の生成 • np.zeros() など,その他の関数による NumPy 配列の生成 • NumPy 配列 np.ndarray クラスの属性 • NumPy 値の型 np.dtype • NumPy 配列の値の参照方法 • NumPy 配列と,数学のベクトルや行列との対応• 単純ベイズ:カテゴリ特徴の場合 • 特徴がカテゴリ変数である場合の単純ベイズ法 50
    • まとめ• 入力データとクラスの仕様 • 入力データの仕様例 • 機械学習アルゴリズムをクラスとして実装する利点 • scikit-learn モジュールのAPI基本仕様 • 機械学習アルゴリズムのクラスの仕様例• 学習メソッドの実装(1) • NumPy 配列の基本的な参照を用いたアルゴリズムの実装 51
    • まとめ• 予測メソッドの実装 • NumPy 配列のスライスを使った参照 • ユニバーサル関数によるベクトル化演算 • for ループを用いない実装の例 • np.sum() の紹介.特に, axis 引数について • np.argmax() , np.argmin()• 実行 • np.genfromtxt() を用いたテキスト形式ファイルの読み込み • scikit-learn API基本仕様に基づくクラスの利用 52
    • 機械学習のPythonとの出会いhttp://www.kamishima.net/mlmpyja/ 53