機械学習」タグアーカイブ

[Python3] scikit-learnによる機械学習4: 学習が完了したモデルを永続化する

前提

以下の環境で実装、動作確認済み

要素 バージョン
debian 8.6
python 3.6.2

scikit-learnによる機械学習シリーズ

機械学習も数学もPythonもよくわからなくても機械学習はデキるシリーズ

概要

前回、自動車の情報から価格を予測する学習モデルを構築し、それを評価した。

しかし、予測を行おうとスクリプトを実行するたびに学習モデルを構築しているのは無駄でしかないので、一度作成した学習モデルをシリアライズし、使いまわせるようにする。以降では、シリアライズされた学習モデルをデシリアライズして予測を行うことで、学習作業を省略することができる。

シリアライズ

今回は、scikit-learnのjoblibを用いてシリアライズする。以下のimport文が必要

from sklearn.externals import joblib

学習が完了したモデル(clf)を、cars.plkのファイル名で保存する場合は以下のようにする

joblib.dump(clf, 'cars.pkl')

実行するとcars.plkが生成される。可読性のあるものではないので中身は気にしなくて良い

$ file cars.pkl
cars.pkl: 8086 relocatable (Microsoft)

デシリアライズ

シリアライズされた学習モデルは、load関数でデシリアライズすることができる。以降ではclfを用いれば従来通りpredictメソッドを使って予測を行うことができる

clf = joblib.load('cars.pkl');

ソースコード

自動車の情報と価格をランダムフォレストで学習し、シリアライズするスクリプト

import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn import preprocessing
from sklearn.externals import joblib

# CSVファイルを読み込む
df = pd.read_csv('cars.csv')

# 学習に不要な行、列を削除
del df['normalized-losses']
for col in ['num-of-doors', 'price', 'bore', 'stroke', 'horsepower', 'peak-rpm']:
 df = df[df[col] != '?']

# 説明変数列と目的変数列に分割
label = df['price']
data  = df.drop('price', axis=1)

# 文字列データを数値化
le = preprocessing.LabelEncoder()
for col in ['make', 'fuel-type', 'aspiration', 'num-of-doors', 'body-style', 'drive-wheels', 'engine-location', 'engine-type', 'num-of-cylinders', 'fuel-system']:
  data[col] = le.fit_transform(data[col])

# 学習する
clf = RandomForestClassifier()
clf.fit(data, label)

# 学習結果をシリアライズ
joblib.dump(clf, 'cars.pkl')

シリアライズされた学習モデルをデシリアライズし、予測、評価するスクリプト

import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn import preprocessing
from sklearn.externals import joblib

# CSVファイルを読み込む
df = pd.read_csv('cars.csv')

# 学習に不要な行、列を削除
del df['normalized-losses']
for col in ['num-of-doors', 'price', 'bore', 'stroke', 'horsepower', 'peak-rpm']:
 df = df[df[col] != '?']

# 説明変数列と目的変数列に分割
label = df['price']
data  = df.drop('price', axis=1)

# 文字列データを数値化
le = preprocessing.LabelEncoder()
for col in ['make', 'fuel-type', 'aspiration', 'num-of-doors', 'body-style', 'drive-wheels', 'engine-location', 'engine-type', 'num-of-cylinders', 'fuel-system']:
  data[col] = le.fit_transform(data[col])

# 学習モデルをデシリアライズ
clf = joblib.load('cars.pkl');

# 評価する
predict = clf.predict(data)
rate_sum = 0
for i in range(len(label)):
  t = int(label.iloc[i])
  p = int(predict[i])
  rate_sum += int(min(t, p) / max(t, p) * 100)
print(rate_sum / len(label))

[Python3] scikit-learnによる機械学習3: 自動車の情報から価格を予測する

前提

以下の環境で実装、動作確認済み

要素 バージョン
debian 8.6
python 3.6.2

scikit-learnによる機械学習シリーズ

機械学習も数学もPythonもよくわからなくても機械学習はデキるシリーズ

概要

scikit-learnによる機械学習がなんたるかがほんの少しわかってきたので、足し算などという実用性皆無な例でなく、もう少し実用的な使い方をしてみる。

今回は、自動車の各種情報と、その自動車の価格を学習させ、新たに情報を与えると価格を予測してくれるスクリプトを作成する。なお、学習データにはMicrosoftのクラウド機械学習サービスである、Azure Maschine Learningより、そのチュートリアルで使用されているサンプルデータを拝借する。

学習用データについて

今回使用する学習データは以下のようなCSVファイル。

画像をだとわかりづらいが、以下の列で構成されている。列の内容は列名から概ね察せるが、最終列にあるprice列の値(自動車の価格)を、他の列の情報を元に予測するということがわかれば問題ない。

  • symboling
  • normalized-losses
  • make
  • fuel-type
  • aspiration
  • num-of-doors
  • body-style
  • drive-wheels
  • engine-location
  • wheel-base
  • length
  • width
  • height
  • curb-weight
  • engine-type
  • num-of-cylinders
  • engine-size
  • fuel-system
  • bore
  • stroke
  • compression-ratio
  • horsepower
  • peak-rpm
  • city-mpg
  • highway-mpg
  • price

学習用データはいつもどおりPandasを用いてDataFrameに展開する

df = pd.read_csv('cars.csv')

無効な行/列の削除

CSVのスクリーンショットを見てもらうとわかるが、全体的に値が”?”になっているセルが散らばっている。これは情報が無いという意味を表していると思うので、それらを取り除いていく。

まず、’normalized-losses'(2列目)は、列全体で”?”が多いので、思い切ってこの列自体を削除する。列についではdel文で削除できるので削除してしまう。

del df['normalized-losses']

次に、他の”?”についてだが、それらについては一部の行のみ”?”になっているだけなので、列全体の削除はせず、”?”が一つでも含まれている行を削除するという方針にする。

“?”が存在する列は’num-of-doors’, ‘price’, ‘bore’, ‘stroke’, ‘horsepower’, ‘peak-rpm’の6列なので、これらのいずれの列にも”?”が含まれていない行のみ取り出して再代入するという処理を行う。PandasのDataFrameでは、行の抽出に、列を対象とした条件式を指定することができるので、”?”が含まれている行を排除できる。

for col in ['num-of-doors', 'price', 'bore', 'stroke', 'horsepower', 'peak-rpm']:
 df = df[df[col] != '?']

おそらくPandasを使ってもっとスマートに同じことができると思うので、良い方法があったらご教授お願いします。

文字列を数値化する

今回も前回までと同様、scikit-learnの学習モデルのfitメソッドを用いて学習を行うが、fitメソッドはfloat型の値しか受け付けない。しかし、今回のCSVには文字列も多く含んでいるので、それを数値化する必要がある。

そこで今回は、scikit-learnのLabelEncoderクラスを用いて各文字列を数値に置き換えることで、学習可能のデータに変換する。変換が必要な列は、’make’, ‘fuel-type’, ‘aspiration’, ‘num-of-doors’, ‘body-style’, ‘drive-wheels’, ‘engine-location’, ‘engine-type’, ‘num-of-cylinders’, ‘fuel-system’の10列なので、それぞれに対して、以下のようにLabelEncoderオブジェクトのfit_transformメソッドを適用することで文字列を数値に変換できる。

le = preprocessing.LabelEncoder()
for col in ['make', 'fuel-type', 'aspiration', 'num-of-doors', 'body-style', 'drive-wheels', 'engine-location', 'engine-type', 'num-of-cylinders', 'fuel-system']:
  data[col] = le.fit_transform(data[col])

例として、body-style列の、変換前後を出力すると以下のようになり、文字列が数値に変換されていることがわかる

変換前

0      convertible
1      convertible
2        hatchback
3            sedan
4            sedan
5            sedan
6            sedan
7            wagon
8            sedan
(以下略)

変換後

0      0
1      0
2      2
3      3
4      3
5      3
6      3
7      4
8      3
(以下略)

わざわざ変換する列名を配列でツラツラ書かなくてももっとスマートに書ける方法がありそうです。こちらも良い方法ありましたらご教授お願いします。

学習用データとテストデータの分割

ここは前回一緒なのでコードだけ載せて割愛

train_data, test_data, train_label, test_label = train_test_split(data, label)

ランダムフォレストで学習させる

前回までは線形回帰アルゴリズムを用いて足し算の学習を行わせたが、今回は別の学習アルゴリズムを使うことにする。どのアルゴリズムをどのような場合に使えば良いかはまだ調査中だが、とりあえず線形回帰とは異なるアルゴリズムを使う場合の例として使ってみる。

ランダムフォレスト(英: random forest, randomized trees)は、2001年に Leo Breiman によって提案された[1]機械学習のアルゴリズムであり、分類、回帰、クラスタリングに用いられる。決定木を弱学習器とする集団学習アルゴリズムであり、この名称は、ランダムサンプリングされたトレーニングデータによって学習した多数の決定木を使用することによる。対象によっては、同じく集団学習を用いるブースティングよりも有効とされる。(Wikipedia引用)

といっても、scikit-learnではどの学習アルゴリズムを使おうと、その使い方が共通化されているので、線形回帰の時とほとんど同じコードで動かすことができる。
必要なモジュールをインポートした状態で以下のようにすることランダムフォレストで学習を実行できる。

clf = RandomForestClassifier()
clf.fit(train_data, train_label)

前回までとの違いは、clf変数にどの学習アルゴリズムのオブジェクトを代入するかの違いだけである。どの学習アルゴリズムについてもfitメソッドを使うことができるので、同じように利用することができる。

評価する

学習用データとテスト用データに分割した際の、テスト用データの方を用いて学習モデルの評価を行う。これまで通り、predictメソッドを用いて各自動車の価格を予想する。

predict = clf.predict(test_data)

とりあえず予測した結果と正解を並べて出力してみる。

for i in range(len(test_label)):
  p = int(predict[i])
  t = int(test_label.iloc[i])
  print(p, t)

どうだろうか、かなり近い価格を出せているのではないだろうか。(予測値 正解値)

7898 6918
5572 5572
12764 12964
10198 11694
7299 8249
8949 9549
17450 15250
7198 8358
23875 17710
10245 8495
8238 8058
6338 6488
31600 28248
11199 8449
10295 6785
34028 32528
15985 12940
7299 6849
(以下略)

ただこれだと、どのぐらいの精度が出てるかわかりづらいので、正解に何%近いかを計算して出力するように修正する

for i in range(len(test_label)):
  p = int(predict[i])
  t = int(test_label.iloc[i])
  rate = int(min(t, p) / max(t, p) * 100)
  print(rate)

それなりの精度であることがわかる

$ python cars.py
70
93
79
87
75
97
91
84
90
97
88
89
95
90
100

さらに、個々の精度の平均値を計算し、全体の精度を出力するようにする

for i in range(len(test_label)):
  t = int(test_label.iloc[i])
  p = int(predict[i])
  rate_sum += int(min(t, p) / max(t, p) * 100)
print(rate_sum / len(test_label))

学習データとテストデータの組み合わせによってムラがあるので、何度か実行して確認してみると、概ね87%前後の精度が得られた

$ python cars.py
86.6734693877551
$ python cars.py
87.46938775510205
$ python cars.py
88.59183673469387
$ python cars.py
86.55102040816327
$ python cars.py
86.04081632653062

さらに、アルゴリズムをランダムフォレストから、線形回帰に変更して再評価してみる。変数clfに代入する学習モデルのオブジェクトを、ランダムフォレストから線形回帰に変更するだけで良い

clf = linear_model.LinearRegression()

同じように何度か実行してみると、83%程度と、ランダムフォレストより微妙に精度が下がり、アルゴリズムによって結果が変わることを実感した。

$ python cars.py
82.40816326530613
$ python cars.py
83.79591836734694
$ python cars.py
83.53061224489795
$ python cars.py
85.65306122448979
$ python cars.py
82.46938775510205

なお、学習モデルの評価方法についても、scikit-learnに様々な機能があるが、今回は意図的に自前で計算するようにして感覚を掴んだ。

ソースコード

Github

#
# 車の製品情報と価格を学習し、価格を予測させる
#
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn import preprocessing

# CSVファイルを読み込む
df = pd.read_csv('cars.csv')

# 学習に不要な行、列を削除
del df['normalized-losses']
for col in ['num-of-doors', 'price', 'bore', 'stroke', 'horsepower', 'peak-rpm']:
 df = df[df[col] != '?']

# 説明変数列と目的変数列に分割
label = df['price']
data  = df.drop('price', axis=1)

# 文字列データを数値化
le = preprocessing.LabelEncoder()
for col in ['make', 'fuel-type', 'aspiration', 'num-of-doors', 'body-style', 'drive-wheels', 'engine-location', 'engine-type', 'num-of-cylinders', 'fuel-system']:
  data[col] = le.fit_transform(data[col])

# 学習用データとテストデータにわける
train_data, test_data, train_label, test_label = train_test_split(data, label)

# 学習する
clf = RandomForestClassifier()
clf.fit(train_data, train_label)

# 評価する
predict = clf.predict(test_data)
rate_sum = 0

for i in range(len(test_label)):
  t = int(test_label.iloc[i])
  p = int(predict[i])
  rate_sum += int(min(t, p) / max(t, p) * 100)
print(rate_sum / len(test_label))

[Python3] scikit-learnによる機械学習2: 足し算学習モデルを評価する

前提

以下の環境で実装、動作確認済み

要素 バージョン
debian 8.6
python 3.6.2

scikit-learnによる機械学習シリーズ

機械学習も数学もPythonもよくわからなくても機械学習はデキるシリーズ

概要

前回実装した、足し算を線形回帰学習するモデルがどのような学習をしたかを評価する

線形回帰学習で算出した式を確認する

線形回帰モデルは、目的変数Yを求めるために、説明変数X1,X2,X3…それぞれに偏回帰係数b1,b2,b3…を掛け、最後に切片(誤差)eを付与する。式にすると以下のようになる。

Y = X1*b1 + X2*b2 + X3*b3 … + e

今回の単純な足し算モデルは、結果Yに対して、入力値が2種類なのでX1とX2が存在する。
なので理論上は以下の式が求められれば正確といえる

Y = X1*1 + X2*1 + 0

では各b及びeがどのような値になったか確認する。

学習が完了したモデルのcoef_,intercept_にそれぞれ偏回帰係数と切片が格納されているのでそれを確認する。

print("偏回帰係数1: ", float(model.coef_[0][0]))
print("偏回帰係数2: ", float(model.coef_[0][0]))
print("切片: ", float(model.intercept_))

すると以下のような結果が得られたので

偏回帰係数1:  1.0000000000000002
偏回帰係数2:  1.0000000000000002
切片:  -4.440892098500626e-16

この学習モデルが予測した線形回帰は以下の式で表される

Y = X1*1.0000000000000002 + X2*1.0000000000000002 + -4.440892098500626e-16

小数計算ならともかく、今回は結果をint関数で整数化しているので、このレベルの誤差なら数値が極大出ない限りは誤った答えが出ないことが予想できる。

精度を確認する

前回は標準入力から式を入力して、解答を標準出力していたが、膨大なデータをテストする場合に一々手入力していたらキリがないので、今回は自動で100000件のテストデータを生成して、それぞれについて正しい答えを求められているかを評価する。

まずは以下のような、1~1000のランダムな数値を二種及びその和のセットを戻す関数を実装した

def get_rand_data():
    max = 1000
    a = random.randint(1, max)
    b = random.randint(1, max)
    return [a, b, a + b]

これを、以下のように内包記法を用いて呼び出すことで10000セット、PandasのDataFrame形式で取得する

rand_data = DataFrame([get_rand_data() for i in range(100000)])

ランダムデータの1,2列目が入力になるので、1,2列目を取り出して学習させる

pre = model.predict(rand_data[[0, 1]])

変数preには、1万件分のデータセットに対する和の予測値が格納されているので、それらを順に確認して、本来の解と一致しているかを比較検証する。一致件数と総数から、正解率を算出する。このあたりを勝手に評価してくれるモジュールがあったと思うが、今回は初見なので自前で実装する。

correct_num = 0
for i in range(len(rand_data)):
  if rand_data[2][i] == int(pre[i]):
    correct_num += 1

print("正解率 = ", correct_num / len(rand_data))

すると、以下のように正解率1.0と出力されるので、最低でも1~1000の整数数二種の和は正確に求められていることがわかる。

正解率 =  1.0

備考

  • もちろん計算の桁数が大きくなればなるほど誤差が影響して正解率は落ちる
  • そもそも明確な解のある足し算を機械学習させるのはお門違いだが今回は簡易的に理解できる例として使用した

[Python3] scikit-learnによる機械学習1: 足し算を学習させる

前提

以下の環境で実装、動作確認済み

要素 バージョン
debian 8.6
python 3.6.2

scikit-learnによる機械学習シリーズ

機械学習も数学もPythonもよくわからなくても機械学習はデキるシリーズ

機械学習とは

機械学習(きかいがくしゅう、英: machine learning)とは、人工知能における研究課題の一つで、人間が自然に行っている学習能力と同様の機能をコンピュータで実現しようとする技術・手法のことである。(wikipedia引用)

機械学習とは、言語やゲームなどをはじめとした人間の様々な知的活動の中で、人間が自然と行っているパターン認識や経験則を導き出したりするような活動を、コンピュータを使って実現するための技術や理論、またはソフトウェアなどの総称である。(IT用語時点引用)

まぁ人間っぽい学習をコンピュータにもやらせましょうというお話。
本シリーズでは機械学習の定義とかにあまり拘らずに、一般的に考えられる機械学習を自分でもやってみようという緩い方向で進める。

scikit-learnとは

Pythonにおける事実上標準と言っても過言ではない、機械学習のためのライブラリ。
機械学習と言えばTensorFlowというワードの知名度が高いが、TensorFlowは学習コストも高く、ディープラーニングまで幅広くできるので、本シリーズではお手軽に機械学習ができるsckit-learnを用いる

scikit-learnのインストール

sckit-learnはPythonモジュールとして公開されているので、pipなどを用いてインストールする。pipはPythonをインストールすると標準で入ってる可能性が高い。入っていない場合は別途導入する。あるいはAnacondaなどの別な手段を適用する。

$ pip install scikit-learn

Pandas/Numpyについて

Pythonで機械学習を行う上で切っても切れないライブラリに、以下の2つがある

Pandas
データ解析を支援するためのライブラリ。汎用的なデータ構造の構築や、データ内の演算などをサポートする。

Numpy
科学計算用のライブラリ。特に行列に対する計算は普通にPythonで行うより圧倒的に早い。

以上より、これらのライブラリも初めに導入しておく。

$ pip install numpy pandas

足し算の機械学習について

本記事では、機械学習の第一歩として、sckit-learnの線形回帰アルゴリズムを用いて、足し算を学習させる。

流れとしては以下のようになる

  • 足し算の式と、その答えの組み合わせをいくつか用意する
  • sckit-learnに式と答えの組み合わせを学習させ、学習モデルを生成
  • 標準入力から二種類の整数を学習モデルに与え、予測結果を標準出力する

なお、2数の足し算とは当然演算によって求められるものなので、機械学習の利用法としては適していない。
が、ここでは雰囲気を掴むためにこのテーマにした。

教師あり学習について

教師あり学習は、データの組み合わせと、それに対する答えのセットを複数セット学習させることで、データの組み合わせを与えただけでその答えを予測してもらおうという学習モデル。

今回は足し算を学習させるので、データの組み合わせが式で、答えが和と考えれば良い。

線形回帰について

Wikipediaをそのまま掲載すると以下の通り。なるほど。

何を言ってるかよくわからないが、足し算に置き換えれば非常にシンプルになる。

目的変数Yは、足し算における和である。
説明変数X1,X2は、足し算のそれぞれの数値である。

この場合、線形回帰直線は、Y = X1 + X2の形になるので、
この式を予測することが線形回帰学習である(あってるか極めて怪しい)

実装

モジュールのimport

今回は、Pandasによる学習用データの入力と、scikit-learnの線形回帰モデルを用いるので、以下のimport文を記述する。Numpyは今回出番なし。

from pandas import DataFrame
from sklearn import linear_model

学習データの用意

学習データは、PandasのDataFrameの形式で用意する。DataFrameはデータの集合を二次元で表現する。
sckit-learnでは、学習データとその答えを別々に渡す必要があるので、それぞれformulas,answersに代入する。

formulas = DataFrame([
    [0, 0],
    [0, 1],
    [0, 2],
    [1, 0],
    [1, 1],
    [1, 2],
    [2, 0],
    [2, 1],
    [2, 2]
])
answers = DataFrame([0, 1, 2, 1, 2, 3, 2, 3, 4])

コードを見ての通り、それぞれ、0 + 0 = 0, 0 + 1 = 1, 0 + 2 = 2 … のセットを用意している。
ちなみにこの段階でformulasを標準出力すると以下のようになり、DataFrameがどのようにデータ集合を持っているかのイメージが湧く

   0  1
0  0  0
1  0  1
2  0  2
3  1  0
4  1  1
5  1  2
6  2  0
7  2  1
8  2  2

学習データを学習させる

importしたlinear_modelを用いて、先程用意した学習データを予測する。
LinearRegressionメソッドで学習モデルを取得できるので、それに対してfitメソッドで学習データを割り当て、学習を開始する。

model = linear_model.LinearRegression()
model.fit(formulas, answers)

学習結果を確認する

学習が完了したmodelに対して、predictメソッドを用いることで、新たなデータに対する答えの予測値を取得することができる。
この場合、必ず入力、出力ともに配列である必要があるので注意。

例えば以下では、10 + 20の予測結果を取得する。

predected_answer = model.predict([[10, 20]])

これを用いて、標準入力から得られた2数の和の予測結果を出力し、それを繰り返すコードを以下に示す

while True:

    print('> ', end='')
    x, y = list(map(lambda x: int(x), input().split(' ')))

    predected_answer = model.predict([[x, y]])
    print("{0} + {1} = {2}".format(x, y, int(predected_answer[0][0])))

標準入力と標準出力で小難しいことをやってるように見えるが、とりあえず入力した2数の和を予測して出力していることはわかる。

動作確認

これまでのコードを以下のように整理して実行する
コード全文 (Github)

 #
 # 2つの整数の足し算を機械学習し、任意の足し算を回答させる
 #
 from pandas import DataFrame
 from sklearn import linear_model

 # 足し算の例とその答えを用意する
 formulas = DataFrame([
     [0, 0],
     [0, 1],
     [0, 2],
     [1, 0],
     [1, 1],
     [1, 2],
     [2, 0],
     [2, 1],
     [2, 2]
 ])
 answers = DataFrame([0, 1, 2, 1, 2, 3, 2, 3, 4])

 # 式と答えを線形回帰学習させる
 model = linear_model.LinearRegression()
 model.fit(formulas, answers)
 print('学習完了')

 while True:

     # 標準入力から計算式を取得
     print('> ', end='')
     x, y = list(map(lambda x: int(x), input().split(' ')))

     # 学習モデルを用いて回答を取得し、標準出力
     predected_answer = model.predict([[x, y]])
     print("{0} + {1} = {2}".format(x, y, int(predected_answer[0][0])))

実行後、2秒程度で学習が完了する。コレに対して標準入力で適当に2数を入力すると、その予測結果が出力される。

$ python addition.py
学習完了
> 1 1
1 + 1 = 2
> 10 20
10 + 20 = 30
> 777 333
777 + 333 = 1110

良い感じに計算できているように見える。

本モデルの評価について次回行う。