AI_ML_DL’s diary

人工知能、機械学習、ディープラーニングの日記

Titanic: Machine Learning from Disaster

Titanic: Machine Learning from Disaster

Kaggleを、一からやりなおそう!

やりなおす理由:

1.地固めせずにいろいろ手を出し、背伸びしすぎて、現在地がわからなくなった。

2.コンペに参加しても、結果を提出できるところまでたどり着けない。

3.pandas, scipy, numpyなどの基本が理解できておらず、読めないコードが多い。

スケジュール:3日間(5月8日~10日):延長(5月11日~13日)

5月8日は、tutorialに従って、練習し、コード例を覚えよう。

あとは、Kaggleのnotebookによる学習と、A. Geron氏のテキストの該当箇所の勉強など

 

5月8日:

f:id:AI_ML_DL:20200508103900p:plain

まずは、上図の左端にある"Overview"をクリックします。
初めてKaggleに参加する人のために用意されたコースなので、
英語を正しく理解できる人であれば、読んで、実行するだけです。
私が付け加えることは。皆無だと思います。
 
以下は、自分の作業メモです。
自動翻訳を使いながらの作業になります。

 

コースの目的:

機械学習を使用して、タイタニック号の難破のときに、生き残った乗客を予測するモデルを作成すること。

 

*実は、過去にこの課題で勉強しようとしたことがあるのだが、この課題では、正確に予測できることが、海難事故を防ぐこと等に生かせるわけでもなく、どうせ、裕福な女性の生存率が高いという結果を知ることになるだけだろうと思い、途中でやめたことがある。今回は、そのことは考えず、むしろ、ここで学んだことを自分のため、社会のために使えるようになるんだ、という思いで取り組むことにしよう。

 

お薦めのチュートリアル、Alexis Cook's Titanic Tutrialをクリックし、出てきたページを自動翻訳しようとしたがだめだったので、元のページをよく読むことにする。

以下、ところどころで、自動翻訳をコピペする。

この課題では、「どのような人々が生き残る可能性が高かったのか」という質問に答えることができる予測モデルを構築するために、乗客データ(名前、年齢、性別、社会経済クラスなど)を使用する。

 

Alexis Cook's Titanic Tutrialでは、

最初に概略が説明され、

次に、データについての説明がある。

データファイルは、"train.csv"と"test.csv"と"gender_submission.csv"の3種類があり、それぞれ、訓練用とテスト用と提出用である。

訓練用は、"答え:求める結果"すなわち生死の情報が入ったもの、テスト用は、訓練データで学んだモデルを用いて、生死を予測するためのデータ(”生死”の情報は入っていない)であり、予測結果は提出用ファイルに書き込んで提出する。

 

このコンペは、初心者に向けた入門コースならではの、配慮がある。

それは、実際に、予測結果を提出して、スコアをつけてもらい、その結果がリーダーボードに掲載される、ということを体験できるようになっているのである。

 

そのやりかたは、Alexis Cook's Titanic Tutrialの、(3) gender_submission.csvの説明の後に、

Your first submissionとして紹介されている。

その手順は、

1.gender_submission.csvを自分のパソコンにダウンロードする。

2.Submit Predictionsをクリックする。

3.Upload Filesのアイコンをクリックする。

4.1.で保存したgender_submission.csvを選択する(アップロードする)。

5.Make Submissionをクリックする。

以上の操作で、計算結果(gender_submission.csv)が提出され、スコアが0.76555と計算され、リーダーボードに掲載されるのである。

 

通常のコンペでは、用意されている提出用のファイルは、最初のセルに記入例(記入フォーマット)が示されているだけなので、それを今回の手順で提出すると、スコアは、通常、最低点となり、最下位付近に並んで表示されるだけである。

 

この初心者用コンペでは、初心者に臨場感を味わってもらおうということで、そこそこのスコアが出る予測結果が、あらかじめ入力されている。(すべての女性の欄を"1", the passenger survivedとしている:実際に、女性の生存率が高かった)

 

このように、実際に、ある程度のスコアが出る予測値が入力された予測結果ファイルを提出することで、コンペに参加した気分を味わうことができて、基本操作も覚えられるのだから、いうことなしだろう。

 

このような機能は、昨年の夏ごろには、なかったように思う。

最近、通常のコンペのリーダーボードに、最低スコアがたくさん並んでいるのを見かけることが多くなったような気がする。これは、予測結果の提出操作のみを覚えた参加者が、とりあえず、空データでもいいから提出してみようと思ってやっているのかなと推察(邪推かもしれないが)される。コンペのスタートラインに立った、ということを示すための手段になったのかもしれない。

 

さて、Alexis Cook's Titanic Tutrialの目的は、機械学習の体験である。

次のステップは、計算環境を手に入れることである。

 

この初心者用コンペのように、予測結果を提出するだけのコンペであれば、訓練データとテストデータと提出用ファイルをダウンロードし、自分のパソコン、あるいは、クラウド環境を利用して機械学習モデルを構築し、予測結果を提出用ファイルに上書きして提出すればよい。しかしながら、Kaggleで用意されている計算環境を使わなければならないコンペもある(コードコンペ、時間制限など種々の制約条件付きコンペなど)。

 

したがって、Alexis Cook's Titanic TutrialのPart 2: Your coding environmentに示されている手順に従って、Kaggle notebookを使えるようにする必要がある。

 

自分のパソコンで、Python等を使ってプログラミングしようと思ったら、Anaconda等を利用して計算環境を整えなければならないが、Kaggle notebookは、その計算環境が整っているうえに、GPUやTPUが使えるようになっている。

 

さて、Alexis Cook's Titanic Tutrialを参考にしながらTitanicコンペのKaggle notebookを立ち上げて、先に進もう。

ここから、機械学習のプログラムを作成することになる。

 

Kaggle notebookを立ち上げると、次のcellが現れる。

 

# This Python 3 environment comes with many helpful analytics libraries installed
# It is defined by the kaggle/python docker image: https://github.com/kaggle/docker-python
# For example, here's several helpful packages to load in 

import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)

# Input data files are available in the "../input/" directory.
# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory

import os
for dirname, _, filenames in os.walk('/kaggle/input'):
    for filename in filenames:
        print(os.path.join(dirname, filename))

# Any results you write to the current directory are saved as output.

ほんとに初めて見ると、なんのことやらさっぱりわからない。

Python 3と、関連するライブラリーがインストール済みです。

#どんなライブラリーがインストールされているかは、GitHubを見ればわかります。

#このセルを実行すれば、役に立つパッケージ(NumPyとPandas)がロードされて、使えるようになります。

#データファイルは、inputディレクトリーにあります。

#セルを実行すれば、inputディレクトリーに格納されているデータファイル(train.csv, test.csv, gender_submission.csv)が表示されます。

さらに、コードの後ろにも注釈が書かれています。

#numpyは、線形代数のパッケージです。

#pandasは、データ処理のパッケージで、例えば、pd.read_csvは、csv形式のファイルを読み込む命令になります。

import os以下の4行は、inputディレクトリーにあるファイル名を表示するコードだが、1つ1つのコードの意味がよくわからない。

でも、今は、ここで立ち止まらない方が良い。

調べるのは、非常に面倒で、時間がかかり、理解できるまで、納得できるまで、やっていたら、たいてい、挫折する。 

 

次のセルで、train.csvのファイルからデータを読み込んで、train_dataに格納する。

 

train_data = pd.read_csv("/kaggle/input/titanic/train.csv")
train_data.head()
 

機械学習で、おそらく、最初に覚えるのが、おそらくこれらのコードでしょう。

train_data.head( )で、最初の5行が表示され、カッコ内に数字を入れるとその行数だけ表示することができる。

ちなみに、train_data.tail( )とすれば、最後の5行が表示される。

テストデータtest.csvの読み込みと表示も同様である。

 

ここで、訓練データとテストデータの違いをはっきりと認識することが重要である。

train_dataとtest_dataの根本的な違いは、生存者かどうかの情報の有無である。

 

train_dataには、"PassengerId”と”Pclass”の列の間に、”Survived"が入っていて、生存者かどうかが明示されている。ここで行う機械学習は、教師あり学習に分類される。すなわち、訓練データを用い、正解情報(正しい生存情報)を頼りに、学習させるのである。

 

これに対してtest_dataには、”Survived"の列がない、すなわち生存者かどうかの情報は無い。当然である。学習したモデルが、どれだけの予測能力を獲得したかを、テストデータを用いて調べるのが最終目的だからである。

 

スパムメールの検出モデルを例にとれば、

まず、スパムメールとスパムでないメールを集めて、それぞれ、ヒトが正確に判定して、スパムか否かの判定済みのデータを作る。これが訓練データである。

これに対して、Titanicにおけるテストデータに相当するのは、現実にポストに入ってきたメールの集まりである。

訓練データを用いて訓練したモデルを用いて、実際にポストに入ってきたメールがスパムかどうかを分類するのである。

 

ここで、投稿手順を学ぶために用いられたgender_submission.csvについて調べてみよう。この投稿用ファイルは、全ての女性が生存すると予測した結果である。そのような単純な予測結果が、テストデータに対して、75%以上というかなり高い確率で予測できたのである。

women = train_data.loc[train_data.Sex == 'female']["Survived"]
rate_women = sum(women)/len(women)

print("% of women who survived:", rate_women)

指標"Survived"が”female"と一致する人数を数えて全女性の人数で割り算している。

その結果は、0.742となっていることから、全女性に対して”生存”と判定した投稿用データの予測精度がこれに近い値になった理由であることがわかる。

 

men = train_data.loc[train_data.Sex == 'male']["Survived"]
rate_men = sum(men)/len(men)

print("% of men who survived:", rate_men)

同様にして、訓練データにおける男性の生存率を計算すると、0.189となる。

このように、生存率においては、性別が生存率に対する強い指標になっていることが確認できた。

テストデータに対して、全女性を生存と判定したデータが、比較的高い確率で生存を予測できた理由がここにある。

ということは、予測精度を向上させるためには、訓練データをよく調べることが重要であることを示唆している。

 

ようやく機械学習にたどり着いた。

Alexis Cook's Titanic Tutrialの、Your first machine learning modelである。

ランダムフォレストと呼ばれている機械学習モデルの1つを使って、生存率と各人の種々の特徴との相関を調べる。

Scikit-Learnには、種々の機械学習モデルが用意されている。

以下に示されているように、いとも簡単に計算できてしまう。

計算結果を提出用ファイルに格納するところまで含まれている。

上から順番にみていくと、

1.ランダムフォレストという分類モデルを呼び出す。

2、訓練データの正解値をyに代入する。

3.訓練データについて、性別を含む4種類の特徴量をXに代入する。

4.テストデータについて、性別を含む4種類の特徴量をX_testに代入する。

5.機械学習モデルを構築する。カッコの中はモデルの構造を定義している。

6.訓練データから構成したXとyを用いて予測モデルを作る。

7.訓練データを用いて作ったモデルによって、テストデータの4種類の特徴量によって、生死を予測する。

8.予測結果からpanda形式の表を作成する。

9.作成した表を、投稿ファイルとして保存する。

10.エラーなく完了すれば、Your submission was successfully saved!と表示する。

from sklearn.ensemble import RandomForestClassifier

y = train_data["Survived"]

features = ["Pclass", "Sex", "SibSp", "Parch"]
X = pd.get_dummies(train_data[features])
X_test = pd.get_dummies(test_data[features])

model = RandomForestClassifier(n_estimators=100, max_depth=5, random_state=1)
model.fit(X, y)
predictions = model.predict(X_test)

output = pd.DataFrame({'PassengerId': test_data.PassengerId, 'Survived': predictions})
output.to_csv('my_submission.csv', index=False)
print("Your submission was successfully saved!")

これをKaggle kernelで実行した後、

Alexis Cook's Titanic Tutrialに従って、submittする。

そうすると、0.77511というスコアで、リーダーボードのxxx番に表示される。

同じ順位、スコアで、数十件並ぶ。

Alexis Cook's Titanic Tutrialによるトレーニングはここまでである。

 

 

さて、ようやく、スタートラインに立ったということかもしれない。

ここまでは、チュートリアルを、単に、なぞっただけだ。

 

次に進む前に、特徴量が、4つに絞られているのが気になった。

features = ["Pclass", "Sex", "SibSp", "Parch"]としているのである。

訓練データには、これら4つの因子の他に、ticket, fare, cabin, embarkedなどがある。

これらの因子を外した理由は何なのか。

試しに、特徴量としてこれら4種対の因子を追加してプログラムを実行した。

その結果、データに欠損がある、というようなメッセージが現れ、計算が停止した。

追加した4つの因子から1つずつ減らして計算したが、いずれの場合もエラーで停止した。

ということは、データの不完全性を理由に追加の4種類の因子を外したのかもしれない。

 

このあたりも含め、追求していきたいのだが、どのように進めるのが良いのだろうか。

1つは、機械学習のテキストに学ぶこと。

1つは、Kaggleから学ぶこと。

 前者は、必須である。ただし、良いテキスト、自分に合ったテキストに出会うことが重要である。問題は、扱う題材が標準的で、目の前にあるものとはその内容が異なり、新たな課題に取り組むことになるうえに、この課題に戻った時に、学んだ内容がそのままのかたちで使えるわけではないことがよくある。

後者を有効に活用するためには、自分のレベルにあったdiscussionやnotebookに出会うことが必要である。この課題は歴史が長いこともあり、良い教材になりそうなnotebookがたくさんありそう。

今回は、A. Geronのテキストと、投票の多いnotebookを参考にしようと思う。

 

まずは、A. Geron氏のテキストHands-On Machine Learning with Scikit-Learn, Keras & TensorFlow 2nd Editionに学ぼう。

第2章に取り組もうと思うのだが、50ページある。予定の5月10日まで、1日半を切っている。どこまで進めるやら。

 

Chapter 2  End-to End Machine Learning Project

不動産業の話題のようだ。

全プロセスを次の8つに分けて説明している。

APPENDIX Bには、Machine Learning Project Checklistがあり、以下の8つの項目について、さらに、箇条書きで、要点がまとめられている。

1. Look at the big picture.全体像を見る

2. Get the Data.

3. Discover and visualize the data to gain insights.データを発見し視覚化し洞察を得る

4. Prepare the data for Machine Learning algorithms.

5. Select an model and train it.

6. Fine-tune your model.

7. Present your solution.

8. Launch, monitor, and maintain your system.

 

Working with Real Data

機械学習を学ぶには、人工ではなく、現実世界のデータを用いるのが良い。

そのようなデータが得られるサイトとして、9ヶ所紹介している。

Kaggle datasetもそのうちの1つである。

これから扱うのは、California Housing Prices dataset from the StatLib repositoryである。このデータセットは1990年のカリフォルニアの国勢調査に基づいており、ちょっと古いが(当時はBay Areaの良い家でもまだ手頃な価格だった)、学習に向いているとのこと。1つのcategorical sttributeを追加し、いくつかの特徴を削除してあるとのこと。

 

Look at the Big Picture

カリフォルニア州国勢調査データを用いて、住宅価格を予測するプログラムを構築しよう。州のブロックごとの人口、収入の中央値、住宅価格の中央値などが、指標metricsとして含まれている。ブロックは地理学的最小単位で、人口は600人から3000人となっている。この最小単位を地区districtと呼ぶ。

このデータから学んで、いかなる地区における住宅価格も、指標を与えれば予測できるようにすることが、目的となる。

 

Frame the Problem:問題を組み立てる

ビジネスの目的は何か。

モデルを組み立てることが最終目的ではないであろう。

会社は、モデルをどのように使い、どれだけ利益を得ることが期待されるのか。

目的を明確にすることによって、どのようなモデルを、どれだけの精度で、どれだけの資源を投入して実施するかを適切に選択できる。

あなたのボスは、モデルのアウトプット(地区ごとの住宅単価の予測値)を、他の情報とともに、次の機械学習システムに投入すると答えるだろう。

この下流システムによって、どこにどれだけ投資すればよいのかを決めることができるだろう。

このような権利を得ることは重要で、収益に直結する。

などなど、機械学習を使うことによって何が可能になるかを明確かつ定量的に説明することができなければ、会社が機械学習にどれだけ投資すればよいのかを判断することができない。

従来の価格設定の方法や根拠についても調べて、機械学習を用いることの長所短所を明らかにしておくことも重要であろう。

評価すべき件数が多ければ多いほど、また、周辺の情報が多ければ多いほど、利用できるデータが正確であるほど、機械学習を用いることのメリットは増すであろう。

(テキスト原文の和訳になっていないかもしれない:要点は外れていないことを願う)

 

機械学習モデルを組み立てるにあたって検討しておくべき事項がある。

supervised, unsupervised, or reinforcement learning?

a classification task, a regression task, or something else?

batch learning or online learning technique?

 

価格情報があるので、supervised learning taskである。

価格を予測するのだから、典型的なregression taskである。

価格を予測するために区画の人口や収入などの複数の特徴量を用いるので、multiple regression problemである。

区画ごとに1つの価格を予測するので、univariate regression problemである。

もし、区画ごとに複数の価格を予測するのであれば、multivariate regression problemとなる。

データが連続的に流れてくるわけではないので、データ変化への素早い対応は不要である。

 データ量は膨大と言うわけではないので、plain batch learningで良い。

 

Pipelines

データ処理の一連の手続きを、pipelineと称す。

機械学習では、非常に多くのデータを操作し変換するので、pipelineの設計と構築は非常に重要である。

 

Select a Performance Measure 

回帰問題regression problemの性能は、ルート2乗平均誤差root mean square error(RMSE)が用いられることが多い。

はずれ値が多い場合は、平均絶対誤差mean absolute errorを用いることも検討するのもよいかもしれない。そうでなければ、RMSEの方が良い結果を与える。

RMSEもMAEも目標値と予測値殿距離である。

 

Check the Assumptions

予測結果を下流機械学習システムに流す場合には、あらかじめ話し合っておくべきである。たとえば、何か月もたってから、下流では、価格を"cheap", "medium", "expensive"のようにカテゴリーに変換して使っていることがわかった、ということが起きないように。

 

準備は整った。

さあ、コーディングを始めよう。

 

Get the Data

 

Create the Workspace 

もしまだなら、Pythonをインストールしよう。

次に、機械学習コードとデータセットに対して、workspace directryを作らなければならない。(理解できない)

いくつかのPythonモジュールが必要である。Jupyter, NumPy, pandas, MatPlotlib, Scikit-Learnなど。

Anacondaをインストール済みなので、以下省略

 

Download the data

(ようやく、Titanicコンペで練習した内容に近づいてきた。)

"housing.csv"を読み込む。

import pandas as pd

def load_housing_data(housing_path=HOUSING_PATH):

      csv_path = os.path.join(housing_path, "housing.csv")

      return pd.read_csv(csv_path)

この関数を呼び出せば、housing.csvのデータがpandas DataFrameに取り込まれる。

 

Take a Quick Look at the Data Structure

DataFrameのhead( ) methodで、5行ぶんのデータが表示される。

  longitude latitude housing_median_age total_rooms total_bedrooms population households median_income median_house_value ocean_proximity
0 -122.23 37.88 41.0 880.0 129.0 322.0 126.0 8.3252 452600.0 NEAR BAY
1 -122.22 37.86 21.0 7099.0 1106.0 2401.0 1138.0 8.3014 358500.0 NEAR BAY
2 -122.24 37.85 52.0 1467.0 190.0 496.0 177.0 7.2574 352100.0 NEAR BAY
3 -122.25 37.85 52.0 1274.0 235.0 558.0 219.0 5.6431 341300.0 NEAR BAY
4 -122.25 37.85 52.0 1627.0 280.0 565.0 259.0 3.8462 342200.0 NEAR BAY

info( ) methodによって、データの属性等がわかる。

housing.info( )

 <class 'pandas.core.frame.DataFrame'>
RangeIndex: 20640 entries, 0 to 20639
Data columns (total 10 columns):
longitude                     20640 non-null float64
latitude                        20640 non-null float64
housing_median_age  20640 non-null float64
total_rooms                 20640 non-null float64
total_bedrooms           20433 non-null float64
population                  20640 non-null float64
households                 20640 non-null float64
median_income          20640 non-null float64
median_house_value  20640 non-null float64
ocean_proximity         20640 non-null object
dtypes: float64(9), object(1)
memory usage: 1.6+ MB

データ数(区画数)は、20640で、機械学習で処理するデータ数としては、かなり少ない方に属する。

total_bedroomsのnon-nullが20433となっており、これは、207件に、欠損があるということで、注意しなければならない。

もしこのままにしておいて、total_bedroomsを特徴量として、機械学習モデルを学習させると、データ欠損のために、エラーが生じることになる。

 

ocean_proximity field以外は、数値である。

ocean_proximityはcategorical attributeである。

どのカテゴリーがどれだけあるかを、value_counts( ) methodで調べることができる。

>>> housing["ocean_proximity"].value.counts( )

<1H OCEAN     9136

INLAND            6551

NEAR OCEAN   2658

NEAR BAY         2290 

ISLAND                   5

Name: ocean_proximity, dtype: int64

 

次に、describe( ) methodを使うと、numerical attributeのサマリーが得られる。

  longitude latitude housing_median_age total_rooms total_bedrooms population households median_income median_house_value
count 20640.000000 20640.000000 20640.000000 20640.000000 20433.000000 20640.000000 20640.000000 20640.000000 20640.000000
mean -119.569704 35.631861 28.639486 2635.763081 537.870553 1425.476744 499.539680 3.870671 206855.816909
std 2.003532 2.135952 12.585558 2181.615252 421.385070 1132.462122 382.329753 1.899822 115395.615874
min -124.350000 32.540000 1.000000 2.000000 1.000000 3.000000 1.000000 0.499900 14999.000000
25% -121.800000 33.930000 18.000000 1447.750000 296.000000 787.000000 280.000000 2.563400 119600.000000
50% -118.490000 34.260000 29.000000 2127.000000 435.000000 1166.000000 409.000000 3.534800 179700.000000
75% -118.010000 37.710000 37.000000 3148.000000 647.000000 1725.000000 605.000000 4.743250 264725.000000
max -114.310000 41.950000 52.000000 39320.000000 6445.000000 35682.000000 6082.000000 15.000100 500001.000000

 

次に示すように、hist( ) methodを使うことによって、ヒストグラムを表示することができる。

%matplotlib inline
import matplotlib.pyplot as plt
housing.hist(bins=50, figsize=(20,15))
save_fig("attribute_histogram_plots")
plt.show()

f:id:AI_ML_DL:20200509215018p:plain

 これらのヒストグラムを見て、気付いてほしいことがある。

1.(各区画内の)収入の中央値:

 金額の単位が1単位当たり約10,000米ドルになっているようだ。

 最大値が15、最小値が0.5という値で打ち切られている。

 数値がどのように導出されたのか、疑問点は確認すべきだ。

2.築年数と価格の中央値:

 どちらも最大値に大きなピークがあることから、上限で打ち切られている。

 特に、価格は評価目的値なので、重要な問題なので、次のいずれかの処置をする。

 a. 頭打ちのデータを調べなおして修正

 b. 頭打ちになっている区域を削除する

3.横軸のスケールの違いが大きい(最大で5桁くらい)。

 学習時に問題となる可能性があり、後で議論する。

4.ぷんプ形状が、総じて右側に大きなテールを持っている。

 いくつかの機械学習アルゴリズムでは、パターンを認識し難くなる。

 分布がベル型になるような変換を試みる。

このように、データをプロットし、課題を抽出し、対処方法をリストアップすることが重要である。

 

Create a Test Set

この段階でテストセット(テストデータ)を分けることには違和感がある。

初心者からすると、テストデータは、目的値を予測するデータだが、すでに価格の知れているデータを使うとはどういうことか、と思う。

すでに、データを概観している。その中からテストデータを分けるということは、テストデータも概観していることになる。このことは、overfittingの原因になり得る。

現段階では、ヒトが見ているだけで、機械学習モデルがテストデータを見たわけではないので原則としては問題ないということだが、ヒトが機械学習モデルを構築するときに、すでに見たデータの特徴を何らかの形で反映したものになってしまうのは致し方ないことである。これをdata snooping biasと呼ぶ。

(メモ:データセットは、ディープラーニングでは、通常、3種類に分けられる。train dataとevaluation dataとtest dataである。train dataで学習中、途中でevaluation dataを呼び出して、予測の正確さを測るのである。その結果を見て、学習を終了させたり、学習率を変更したりする。これに対して、ディープラーニング以外の機械学習では、evaluation dataを用いることはないので、train dataとtest dataの2種類のみである)

 

データをtrain_setとtest_setに分ける方法。

import numpy as np

# For illustration only. Sklearn has train_test_split()
def split_train_test(data, test_ratio):
      shuffled_indices = np.random.permutation(len(data))
      test_set_size = int(len(data) * test_ratio)
      test_indices = shuffled_indices[:test_set_size]
      train_indices = shuffled_indices[test_set_size:]
      return data.iloc[train_indices], data.iloc[test_indices]

 

データを分割するための関数split_train_test(data, test_ratio)を定義している。

 

20640件のデータ配列をランダムに並び替えて、先頭から80%のデータをtrain_set、それ以降の20%をtest_setに格納している。

定義した関数を呼び出して、分割されたかどうかを確かめてみる。

train_set, test_set = split_train_test(housing, 0.2)
print(len(train_set), "train +", len(test_set), "test")

16512 train + 4128 test

これで、80%のtrain_setと20%のtest_setに分割されたことが確認できた。

ここでの注意点は、並び替えがランダムなので、このセルを実行するたびに、分けたデータセットが違うものになることである。これを避けるには、1回実行したら、saveする。もしくは、np.random.permutation( )を呼び出す前に、no.random.seed(42)を実行しておくことである。

データセットを更新(新たなデータを追加・削除)した場合には、上記2つの方法は使えない。ということで、特に重要なtest_setの適切な管理・更新方法がいくつか紹介されている。

(細かい話だが、実務においては重要であり、コードを学ぶ・データ構造を理解するためにもここはスキップしてはいけない、と自分に言い聞かせる。)

 

一旦決めたtest_setは、そのまま保持し、新たに追加されたデータの20%のみが、test_setに追加されるのがよい。その方法として次のコードがある。

from zlib import crc32

def test_set_check(identifier, test_ratio):
      return crc32(np.int64(identifier)) & 0xffffffff < test_ratio * 2**32

def split_train_test_by_id(data, test_ratio, id_column):
      ids = data[id_column]
      in_test_set = ids.apply(lambda id_: test_set_check(id_, test_ratio))
      return data.loc[~in_test_set], data.loc[in_test_set]

1~3行目:zlibとcrc32については、次のような説明があったが、理解できない。

zlib.crc32(data[, value]) 

data の CRC (Cyclic Redundancy Check, 巡回冗長検査) チェックサムを計算します。

 

6行目:applyとlambdaがわからない。1行の中に使ったことのないものが2つある。

 PandasのDataFrameのapplyメソッド:

 行全体や列全体に対して、同じ操作をしたいときに用いる。

 lambda:以下は、note.nkmk.meからの引用

def文による関数定義とそれに相当するラムダ式での無名関数の対応関係は以下のようになる。

def 名前(引数, 引数, ...):

return

名前 = lambda 引数, 引数, ...:

具体的には以下のようになる。この例のようにデフォルト引数を指定することもできる。

def add_def(a, b=1):

  return a + b

add_lambda = lambda a, b=1: a + b

 

housing datasetには、identifier columがないので、row indexをIDとして用いる方法が示されている。

housing_with_id = housing.reset_index() # adds an `index` column
train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, "index")

このやり方だと、データを入れ替えると、IDが変動する。新たなデータを常に最後尾に追加するとか、既存データは入れ替えないという制限がつく。

housing datasetには、場所を特定している"longitude"と"latitude"があるので、これらを組み合わせて、ユニークなIDを作ることができる。

housing_with_id["id"] = housing["longitude"] * 1000 + housing["latitude"]
train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, "id")

 

以上、split_train_test( )という関数を定義してきたが、

Scikit-Learnには、同様の機能を有する簡単な関数train_test_split( )というのがある。

from sklearn.model_selection import train_test_split

train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)

データセットの量とランダムサンプリングの課題が書かれている。

サンプル数が1000件の場合にランダムダンプリングした場合、期待値からどのくらい外れる可能性があるかを評価している。

 

ここで話が収入の中央値の分布に変わる、

住宅価格の中央値の予測において、収入の中央値は重要であると、専門家がつぶやいたと仮定する。(以下、中央値を省略して単に収入、住宅価格とする)

これから、全データにおける収入の分布が、分割したテストデータセットにおいて保存されているかどうかを確認する。

数値をカテゴリーに変える。各カテゴリーにはある程度以上の件数が入るようにするとともに、カテゴリー数は多すぎず少なすぎず設定する。

結論として0から6まで1.5毎に5段階に分ける。

そのために、関数pd.cut( )を用いる。

housing["income_cat"] = pd.cut(housing["median_income"],
              bins=[0., 1.5, 3.0, 4.5, 6., np.inf],
              labels=[1, 2, 3, 4, 5])

カテゴリー分けした結果をプロットする。

housing["income_cat"].hist()

f:id:AI_ML_DL:20200510192445p:plain

stratified sampling:この棒グラフの分布比率に合うように、test_setを選び出す方法。

これをするために、元の分布を5つのカテゴリーに分類しなおしたということ。

Scikit-Learnに、StratifiedShuffleSplit classが用意されていて、次のコードで実行できる。

from sklearn.model_selection import StratifiedShuffleSplit

split = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42)
for train_index, test_index in split.split(housing, housing["income_cat"]):
      strat_train_set = housing.loc[train_index]
      strat_test_set = housing.loc[test_index]

意図したとおりにtest_setに割り振られたかどうかの確認。

strat_test_set["income_cat"].value_counts() / len(strat_test_set)

3 0.350533
2 0.318798
4 0.176357
5 0.114583
1 0.039729
Name: income_cat, dtype: float64

目標とした収入の分布の数値化

housing["income_cat"].value_counts() / len(housing)

3 0.350581
2 0.318847
4 0.176308
5 0.114438
1 0.039826
Name: income_cat, dtype: float64 

収入のカテゴリー分布と同様の分布を持つ、test_setが、StratifiedShuffleSplit でサンプリングできていることが確認できる。

全データの分布と、Stratifiedによるtest_setとランダムによるtest_setを比較すると、ランダムサンプリングでは、割合が小さい場合に、元の分布との差異が大きくなっていることがわかる。

  Overall Stratified Random Rand. %error Strat. %error
1 0.039826 0.039729 0.040213 0.973236 -0.243309
2 0.318847 0.318798 0.324370 1.732260 -0.015195
3 0.350581 0.350533 0.358527 2.266446 -0.013820
4 0.176308 0.176357 0.167393 -5.056334 0.027480
5 0.114438 0.114583 0.109496 -4.318374 0.127011

 

ここからは、より詳細に、データを眺めていくことになる。

Discover and Visualize the Data to Gain Insight:視覚化したデータにより、洞察する。

最初に、test_setは横に置いといて、ここからは、train_setだけを、より深く見ていく。

もしも、train_setのサイズが非常に大きければ、扱いやすいように開発用にサンプリングする必要があるが、今回のデータは小さいので、そのまま進める。

まずは、データを壊さないように、コピーを作って進める。

housing = strat_train_set.copy( )

 

Visualize Geographical Data

housingデータには、地理情報(緯度と経度)があるので、散布図で表示する。

housing.plot(kind="scatter", x="longitude", y="latitude")

f:id:AI_ML_DL:20200510230021p:plain

カリフォルニアが現れたが、他に特段のパターンがみられない。

alpha optionを0.1にすれば、密度の違いが見えてくる。

housing.plot(kind="scatter", x="longtitude", y="latitude", alpha=0.1)

f:id:AI_ML_DL:20200510230738p:plain

少し良くなった。ベイエリアに集中していることや、ロサンゼルス、サンディエゴなどがよくわかる。あとはCentral ValleyのSacramentやFrescoなども見えている。

 

我々の脳は、パターンを見つけるのが上手だ。作図のパラメータを調整し、もっと他の情報も把握できるようにしよう。

住宅価格を追加、半径の大きさで人口を表現、住宅価格を色で表現、・・・。

housing.plot(kind="scatter", x="longitude", y="latitude", alpha=0.4,
s=housing["population"]/100, label="population", figsize=(10,7),
c="median_house_value", cmap=plt.get_cmap("jet"), colorbar=True,
sharex=False)
plt.legend()
save_fig("housing_prices_scatterplot")

f:id:AI_ML_DL:20200510231515p:plain

既にみてきたことだが、住宅価格が場所と人口に対して良く相関している。クラスターとその中心からの距離なども良い指標になるが、詳細に眺めると、海岸との距離と住宅価格との相関において、そう単純ではない地域もある。

 

Looking for Correlations

データセットが小さいので、全ての属性attributeのペアについて、corr( )法を用い、標準相関係数を簡単に計算することができる。 

corr_matrix = housing.corr( )

住宅価格との相関係数は、次のような値になっている。

corr_matrix["median_house_value"].sort_valus(ascending=False)

median_house_value     1.00000

median_income             0.68717

total_rooms                   0.13523 

housing_median_age    0.11422 

households                   0.06470 

totalbedrooms              0.04786

population                   -0.02667

longitude                     -0.04728

latitude                        -0.14283

Name: median_house_value, dtype: float64

相関係数は-1から1の値をとり、絶対値が1に近いほど相関は大きく、負は逆相関である。ゼロに近いと相関無しということになる。

相関係数は、線形関係を見ている。傾きは無関係。散布図と相関係数の関係を自分の目で確認しておくとよい。(自分は当初、少し勘違い・誤解していた)

pandas scatter_matrics( )関数を使えば、全ての数値指標numetrical attributeについて、互いの相関プロットができる。全部は表示できないので相関性がありそうな一部の指標

についてプロットした結果が示されている。

from pandas.plotting import scatter_matrix

attributes = ["median_house_value", "mediun_income", "total_rooms",                                                     "housing_median_age"]

acatter_matrix(housing[attributes],figsize=(12, 8))

f:id:AI_ML_DL:20200511085355p:plain

直交する場所では、相関係数1.0の直線が表示されるはずだが、それは無意味だということで、pandasでは代わりに他の情報を表示することができる。ここでは、ヒストグラムが表示されている。

最も相関が大きい、median_incomeに着目する。

housing.plot(kind="scatter", x="median_income", y="median_house_value", alpha=0.1)

f:id:AI_ML_DL:20200511090302p:plain

相関係数は0.69と計算されていたもので、図からも読み取れる。

注目したいのは、水平方向の線状分布で、$500,000はすでに指摘されていたが、それ以外にも、$450,000、$350,000など、他にも低いところに線状分布が認められる。

こういう不自然なデータ群は、機械学習にかける際には、除去するのがよい。

 

Experimenting with Attribute Combinations

ここでは、attributeの組合せを検討する。

housingデータセットは、地区単位でまとめられている。部屋数や寝室数、人数なども地区単位の数値のみである。

そこで、住宅価格と相関が大きくなるようなattributeの候補を、attributeを組合わせて、作ってみよう。

housing["rooms_per_household"] = housing["total_rooms"]/housing["households"]
housing["bedrooms_per_room"] = housing["total_bedrooms"]/housing["total_rooms"]
housing["population_per_household"]=housing["population"]/housing["households"]

これらを加えて相関係数を計算する。

median_house_value             1.000000
median_income                     0.687160
rooms_per_household          0.146285
total_rooms                           0.135097
housing_median_age            0.114110
households                           0.064506
total_bedrooms                    0.047689
population_per_household  -0.021985
population                           -0.026920
longitude                             -0.047432
latitude                                -0.142724
bedrooms_per_room          -0.259984
Name: median_house_value, dtype: float64

こんな感じで、1軒あたりの部屋数をとれば相関係数が高くなり、寝室の割合を高くすると負の相関が大きくなる:寝室の割合が高いと価格は低い、ということがデータで明確に示された。これらは比較的容易に気づくことかもしれないが、実務では、より良い特徴量を求めて、もっと多くの組合せを探索することになる。

このプロセスがしっかりやれないと、Kaggleで上位にいけないし、実務でも良いモデルを提案することができないということになる。

(余談だが、ここで、序文に書かれていたこと、「元のデータベースに追加や削除などの変更を加えている」の意味がわかる。教育的配慮から、普通なら含まれているはずの、敷地や家のサイズ、一軒あたりの部屋数などの情報を意図的に削除したのだろう、ということが推察される)

 

Prepare the Data for Machine Learning Algorithms

ここでは、機械学習モデルを作るためのデータを準備する。

手作業でやるのではなく、後から使えるように、関数をつくりましょう、ということが書かれている。

訓練用データセットを作る。

ベースになるのは、55ページで学んだ方法である。

手続きとしては、Scikit-Learnの、StratifiedShuffleSplitクラスを使い、54ページでカテゴリー分けした収入のカテゴリー分布に一致するように、元データをランダム化して、train_setとtest_setに分けた。

このとき作成したtrain_setが、strat_train_setである。

このデータを、2つに分ける必要がある。 

housing = strat_train_set.drop("median_house_value", axis=1) # drop labels for training set
housing_labels = strat_train_set["median_house_value"].copy()

ここで、housingは、建物の価格を消去した訓練データ:別の表現では機械学習モデルの入力データ "X" である。

housing_labelsは、建物の価格データで、labelsもしくはtarget valuesで、機械学習モデルの出力データ "y" である。

 

Data Cleaning

殆どの機械学習では、データに欠損があるとエラーになるので、対処方法を知っておかねばならない。

housingデータでは、48ページで見たように、total_bedroomに207個のデータ欠損があった。

次のコードで欠損を表示できる。

sample_incomplete_rows = housing[housing.isnull().any(axis=1)].head()
sample_incomplete_rows

  longitude latitude housing_median_age total_rooms total_bedrooms population households median_income ocean_proximity
4629 -118.30 34.07 18.0 3759.0 NaN 3296.0 1462.0 2.2708 <1H OCEAN
6068 -117.86 34.01 16.0 4632.0 NaN 3038.0 727.0 5.1762 <1H OCEAN
17923 -121.97 37.35 30.0 1955.0 NaN 999.0 386.0 4.6328 <1H OCEAN
13656 -117.30 34.05 6.0 2155.0 NaN 1039.0 391.0 1.6675 INLAND
19252 -122.79 38.48 7.0 6837.0 NaN 3468.0 1405.0 3.1662 <1H OCEAN

total_bedroomsの数値が配位邸内行が表示されている。

1.その地区のデータを消去する。 ⇒ データ件数が1つ減る。
対処方法は3つある。

2.attributeを消去する。 ⇒ total_bedroomというattributeがなくなる。

3.何らかの数値(ゼロ、平均、中央値など)を与える。

それぞれ次のコードで対応できる。

1.housing.dropna(subset"["total_bedrooms"])

2.housing.drop("total_bedrooms", axis=1)

3.median = housing["total_bedrooms"].median( )

        housing["total_bedrooms"].fillna(median, inplace=True)

  longitude latitude housing_median_age total_rooms total_bedrooms population households median_income ocean_proximity
4629 -118.30 34.07 18.0 3759.0 433.0 3296.0 1462.0 2.2708 <1H OCEAN
6068 -117.86 34.01 16.0 4632.0 433.0 3038.0 727.0 5.1762 <1H OCEAN
17923 -121.97 37.35 30.0 1955.0 433.0 999.0 386.0 4.6328 <1H OCEAN
13656 -117.30 34.05 6.0 2155.0 433.0 1039.0 391.0 1.6675 INLAND
19252 -122.79 38.48 7.0 6837.0 433.0 3468.0 1405.0 3.1662 <1H OCEAN

total_bedroomsとして、中央値が入力されているのがわかる。


test_setでも同じ欠損があるので、原則として、同じ処理をすることが必要。3番目は、medianを使う例を示した。

 

上記3つの方法は、pandaの持つ機能である。

Scikit-Learnにおいても、欠損値への対応として、SimpleImputerというものが、用意されている。

fron sklearn.impute import SimpleImputer

imputer = SimpleImputer(strategy="median")

ocean_proximityはtext attributeであって、numerical attributeでないので、ocean_proximityを除いてコピーするように、となっている。

housing_num = housing.drop("ocean_proximity", axis=1)

64ページのテキストの1行目に次のように書かれている。

Now you can fit the imputer instance to the training data using the fit( ) method:

imputer.fit(housing_num)

このfit( )が、ものすごく違和感を感じる。F. Chollet氏のテキストではmodel.fit( )は、trainを開始する命令で、何度も繰り返し出てくるのだ!

imputer.fit(housing_num)

これは、housing_numのnumerical attribute全体に作用するようなので、複数のattributeに欠陥があってもこの命令1つで作業できるならば非常に便利だ。

次のコードで、NumpyArrayに変換される。

X = imputer.transform(housing_num)

 

Scikit-Learn Desighn

Consistency

      Estimators

      Transformers

      Predictors

Inspection

Nonproliferation of classes

Composition

Sensible defaults

 

Handling Text and Categorical Attributes

housingデータで唯一のcategorical attribute、ocean_proximityを扱えるようにする。

48ページでチェックしていて、5つのカテゴリーに分けて記述されている。

train_setの最初の10件を表示してみよう。

housing_cat = housing"ocean_proximity"

housing.cat.head(10)

ocean_proximity

17606     <1H OCEAN

18632     <1H OCEAD

14650     NEAR OCEAN

3230     INLAND

3555     <1H OCEAN

19480     INLAND

8879     <1H OCEAN

13685     INLAND

4937     <1HOCEAN

4861     <1H OCEAN

殆どの機械学習アルゴリズムは数知表現を好むので、これらのカテゴリーをテキストから数値に変換しよう。

それには。Scikit-LearnのOrdinalEncoderクラスを用いよう。

from sklearn.preprocessing import Ordinal Encoder

ordinal_encoder = OrdinalEncoder( )

housing_cat_encoded = ordinal_encoder.fit_transform(housing_cat)

housing_cat_encoded[:10]

array([0.],

         [0.],

         [4.],

         [1.],

         [0.],

         [1.],

         [0.],

         [1.],

         [0.],

         [0.]])

カテゴリーが数字で置き換えられた。0から4までの数値で置き換えられたカテゴリーを表示すると、

ordinal_encoder.categories_

[array(["<1H OCEAN", "INLAND", "ISLAND", "NEAR BAY", "NEAR OCEAN"], dtype=object)]

カテゴリーを数値で置き換えたが、機械学習アルゴリズムは、数字の近い方が、数字の遠い場合よりも、類似している、と評価する。例えば、bad, average, good, excellentを、この順に、0、1、2、3と変換すれば、機械学習アルゴリズムにマッチする。ocean_proximityの場合、0と4が近い関係(1H OCEANとNEAR OCEAN)で、0と1が遠い関係(1H OCEANとINLAND)になっているので、機械学習にマッチしない。

このような場合、海岸からの郷里で表すという方法もあるが、ここでは、one-hot encodingで表現する例を示す。

from sklearn.preprocessing import OneHotEncoding

cat_encoder = OneHotEncoder( )

housing_cat_1hot = cat_encoder.fit_transform(housing_cat)

housing_cat_1hot

<16512x5 sparse matrix of type "<class "numpy.float64">"

with 16512 stored elements in Compressed Sparse Row format>

出力は、NumPy arrayではなく、SciPy sparce matrixとのこと。この表現は、データが大きい場合ほど、効果的である。

NumPy array表現に変換すると、

housing_cat_1hot.toarray( )

array([[1., 0., 0., 0., 0.],

          [1., 0., 0., 0., 0.],

          [0., 0., 0., 0., 1.],

           ...,

          [0., 1., 0., 0., 0.],

          [1., 0., 0., 0., 0.],

          [0., 1., 0., 0., 0.]])

cat_encoderをカテゴリーの戻すと、次のようになる。

cat_encoder.categories_

[array(["<1H OCEAN", "INLAND", "ISLAND", "NEAR BAY", "NEAR OCEAN"],

          dtype=object)]

 

もし、categorical attributeの数が非常に大きい場合は、どのように扱えばよいのか。

on-hot encodingを使うと、input featureが非常に大きくなる。もし、訓練時間が増え、パフォーマンスも低下するようであれば、カテゴリーとの相関が高いnumerical featureに置き換えることが望ましい。ocean_proximityであれば、海岸からの距離に置き換えることができ、国の識別コードは、人口や一人当たりのGDPに置き換えることができる。

他には、embeddingと呼ばれる、各カテゴリーを学習可能かつ低次元ベクトルに置き換えるという方法がある。各カテゴリーの表現は、訓練中に学習される。これは、representation learningの例である。詳細は13勝と17章を参照すること。

 

Custom Transformers

Scikit-Learnは、たくさんの有用なtransformersを提供してくれるが、自分のtaskに合わせて、cleanupやattributeの組合せを、自分でできるようにしておくことが重要である。

attributeの組合せは61-62ページで扱い、ある組み合わせにおいては、価格との相関が大きくなることが確認できた。

自然科学・工学の実験結果の解析において、説明変数の探索は研究の根幹をなすので、この探索作業を、自動的、かつ、効果的に、さらには、発見的に行える仕組みができないかなと思う。検討すべき要因とその組み合わせ、試してみたい化合物・材料・プロセスの種類と組合せなど無限にある中から有効なものを探す能力を強化することができるような機械学習モデルを作ってみたいものだ。

pipelinesのようなScikit-Learnの関数を用い、transformerがつなぎ目無く働くように組み立てよう。classを作って、fit( ), transform( ), fit_transform( )などを実装しよう。

Scikit-Learnの便利な道具を使って、小さなtransformer classを作ってみよう。

 

from sklearn.base import BaseEstimator, TransformerMixin

rooms_ix, bedrooms_ix, population_ix, households_ix = 3, 4, 5, 6

class CombinedAttributesAdder(BaseEstimator, TransformerMixin):

      def __init__(self, add_bedrooms_per_room = True): # no *args or **kargs

            self.add_bedrooms_per_room = add_bedrooms_per_room

      def fit(self, X, y=none):

            return self    # nothing else to do

      def transform(self, X):

            rooms_per_household = X[ : , rooms_ix] / X[ : , households_ix]

            population_per_household = X[ : , population_ix] / [X[ : , households_ix]

            if self.add_bedrooms_per_room:

                  bedrooms_per_room = X[ : , bedroom_ix] / X[ : , rooms_ix]

                  return np.c_[X, rooms_per_household, population_per_household,

                                      bedrooms_per_room]

            else:

                  return np.c_[X, room_per_household, population_per_household]

attradder = CombinedAttributesAdder(add_bedrooms_per_room=False)

housing_extra_attribs = attr_adder.transform(housing.values)

著者のnotebookでは、2行目のコードが次のように書き換えられている。

# get the right column indices: safer than hard-coding indices 3, 4, 5, 6
rooms_ix, bedrooms_ix, population_ix, household_ix = [
  list(housing.columns).index(col)
  for col in ("total_rooms", "total_bedrooms", "population", "households")]

 

別の書き方、FunctionTransformerを使う、もあるようだ。

from sklearn.preprocessing import FunctionTransformer

def add_extra_features(X, add_bedrooms_per_room=True):
      rooms_per_household = X[:, rooms_ix] / X[:, household_ix]
      population_per_household = X[:, population_ix] / X[:, household_ix]
      if add_bedrooms_per_room:
            bedrooms_per_room = X[:, bedrooms_ix] / X[:, rooms_ix]
            return np.c_[X, rooms_per_household, population_per_household,
                                bedrooms_per_room]
      else:
            return np.c_[X, rooms_per_household, population_per_household]

attr_adder = FunctionTransformer(add_extra_features, validate=False,
                                                       kw_args={"add_bedrooms_per_room": False})
housing_extra_attribs = attr_adder.fit_transform(housing.values)

この例では、transformerは、1つのhyperparameter、add_bedrooms_per_roomを持っていて、このattributeを追加することが機械学習アルゴリズムを助けるか否かを容易に判別することができる。

一般的には、データ調整作業の際に、このようにハイパーパラメータを追加してみて、有効性をチェックすることができる。

この動作を自動化していけば、様々なattributeの組み合わせを試すことができて、非常に良い組み合わせを見つけることができる可能性が高くなる。

 

Feature Scaling

特徴量featureのスケーリングは、非常に重要である。

入力するnumerical attributesの数値のスケールが大きく異なると、大半の機械学習アルゴリズムは良い性能を示せない。60ページに示されている4つのattributsの値は、ざっとみても、収入が0~15、部屋数が0~20000と、3桁以上異なっている。

通常、全てのattributesのスケールを合わせるには、min-max scalingと、standardizationがある。

min-max scalingは、最小値を差し引いたのち、最大値と最小値の差で割り算する。結果は、0~1となる。Scikit-Learnでは、MinMaxScalarというtransformerが用意されている。

standardizationは、まず、平均値を差し引き、次に、標準偏差で割り算する。平均値は0で、値の幅は、特定の値にはならないので、機械学習アルゴリズムによっては不都合な場合もある。min-max scalingは異常値に影響されやすいが、standardizationは異常値にによる影響は小さい。Scikit-LearnのStandardScalarを使えばよい。

注意書き:スケーリングは、全データに対して行うのではなく、train_setとtest_setで別々に行う、との記述がある。70ページ最上部。

 

Transformation Pipeline

既にみてきたように、多くのデータ変換作業があり、それらは、正しい順序で行わなければならない。 

それには、Scikit-LearnのPiplineクラスが適している。

たとえば、numerical attributesに対する小さなpipelineは、次のように書ける。

 

from sklearn.pipeline import Pipeline

from sklearn.preprocessing import StandardScaler

num_pipeline = Pipeline([

                ("imputer", SimpleImputer(strategy="median")),

                ("attribs_adder", CombinedAttributesAdder( )),

                ("std_scaler", StandardScaler( )),

      ])

housing_num_tr = num_pipeline.fit_transform(housing_num)

 

categorical columnとnumerical column を別々に扱ってきたが、Scikit-Learn version 0.20からColumnTransformerが導入され、pandas DataFrameにも適用できるようになった。

 

from sklearn.compose import ColumnTransformer

num_attribs = list(housing_num)

cat_attribs = ["ocean_proximity"]

full_pipeline = ColumnTransformer([

            ("num", num_pipeline, num_attribs),

            ("cat", OneHotEncoder( ), cat_attribs),

      ])

housing_prepared = full_pipeline.fit_transform(housing)

 

Select and Train a Model

・framed the problem

・got the data and explored the data

・sampled a training set and a test set

・wrote transformation pipeline to clean up and prepared the data for Machine Learning algorithms automatically

 ・now ready to select and train a Machine Learning model

 

Training and Evaluating on the Training Set

まず、線形回帰linear regressionモデルを訓練してみよう。

from sklearn.linear_model import LinearRegression

lin_reg = LinearRegression( )

lin_reg.fit(housing_prepared, housing_lables)

何もしないよりはまし、This is better than nothing.

モデルを変えてみよう。

from sklearn.tree import DecisionTreeRegressor

tree_reg = DecisionTreeRegressor( )

tree_reg.fit(housing_prepared, housing_lables)

訓練終了!訓練データで評価してみよう!

housing_predictions = tree_reg.predict(housing_prepared)

tree_mse = mean_squared_error(housing_lables, housing_predictions)

tree_rmse = np.sqrt(tree_mse)

tree_mse

0.0

学習データと評価データが同じで、モデルの性能が高ければ、しばしば起こること。

 

Better Evaluation Using Cross-Validation

train_setを、train_test_split( )で分けて、訓練と評価を行う方法がある。

Scikit-Learnには、K-fold cross validation featureというのがある。

例えば、トレーニングデータを10分割し、9/10で訓練し、残りの1/10で評価するというのを繰り返し、平均をとる。

 

from sklearn.model_selection import cross_val_score

scores = cross_val_score(tree_reg, housing_prepared, housing_lables,

                                        scoring="neg_mean_squared_error", cv=10)

tree_rmse_scores = np.sqrt(-scores)

def display_scores(scores):

      print("Scores:", scores)

      print("Mean:", scores.mean( ))

      print("standard deviation:", scores.std( ))

display_scores(tree_rmse_scores)

 

結果は、LinearRegression よりもDecitionTreeRegressorの方が悪い。DecitionTreeはoverfittingしているようである。

この後、RandomForestRegressorでCross-Validationを試した結果、これら3つのモデルの中ではrmseが最少、すなわち最も良い結果を示した。

 

Fine-Tune Your Model

Grid Search

hyperparameterをマニュアルでいじくりまわすことによって良い結果が得られる条件が見つかるかもしれない。退屈で時間のかかる作業で良い結果が得られるとは限らない。

そのかわり、Scikit-Learnに、GridSearchCVというのが用意されている。

RandomForestRegressorでグリッドサーチする方法を次に示す。

 

from sklearn.model_selection import GridSearchCV

param_grid = [

      {"n_estimators": [3, 10, 30], "max_features": [2, 4, 6, 8]},

      {"bootstrap": [False], "n_estimators": [3, 10], "max_features": [2, 3, 4]},

   ]

forest_reg = RandomForestRegressor( )

grid_search = GridSearchCV(forest_reg, param_grid, cv=5,

                                              scoring="neg_mean_squared_error",

                                              return_train_score=true)

grid_search.fit(housing_prepared, housing_lables)

コードの詳細はChapter 7で説明されているとのこと。

最適解は次のコードで表示できる。

>>> grid_search.best_params_

{"max_features": 8, "n_estimators": 30}

8も30も、調べたパラメータの最大値なので、次は、それより大きい値について調べれば、さらに性能が改善される可能性がある。

 

*この後、グリッドサーチのまとめ、ランダムサーチ、アンサンブル法、ベストモデルとエラー解析、テストセットを用いたモデルの評価、などについて学習した経過や感想を記述していたが、操作ミスで失った。

(文章作成中に、カーソルが別の場所に飛んでしまい、それを修正すると、画面が乱れてしまい、元に戻らなくなるので、その前にセーブした画面まで戻すしかなくなる:それがいやなら頻繁に更新ボタンを押せ、ということだ)

殆ど、テキストのコピペと一部の和訳なので大勢に影響なく、やり直すほどのことではないので先に進む。

 

*Launch, Monitor, and Maintain Your Systemについても、途中までしっかり書いていたつもりなのだが、かなりの部分が消えてしまって残念だ。

 

Launch, Monitou, and Maintain Your Systen

予測モデルの作成に仕方を学んで、はい終わり、というのでないところがこのテキストの良さかな。

タイトルにあるように、ここでは、作成したモデルを製品として出荷することを想定した内容になっている。作成したモデルの出荷、モニター、保守。

出荷と言うのは、モデルをウエブにアップロードするか、クラウドプラットホームにアップロードするか、ということかな。まだやったことがないのでよくわからないが。

モニターというのは、実際にクライアントが使用している状況のモニターかな。

保守というのは文字通りだが、これには、大きくは2つに分けて考えるのがよい。

1つは、アップデートである。レコメンドの例がわかりやすいと思うが、商品のレコメンドモデルが動いているときに、購入者の購入履歴が反映されないと、購入済みの商品をレコメンドしてしまうということである。時間スケールが長いものだと、生産ラインの製品の欠陥検査の場合、過去の欠陥結果を元に検出システムを設計しているのだが、登録されていない、新規の欠陥が現れ始めると、それに対応できるように、データを収集して、モデルを学習しなおす必要が生じるということである。

もう1つは、ファイルが壊れるとか、ウイルス感染とか、悪意のある入力データによるモデルの劣化とかのトラブル対応である。これには、まずは、モデルやデータベースのバックアップが必須であるが、メンテナンス履歴、更新前のモデルやデータの保管など、実務上は非常に手間のかかるものが多いということを覚悟しておかねばならないだろう。

 

Try It Out!

さあ、やってみよう! 

機械学習の全体の流れをつかむことと、3~4種類の機械学習アルゴリズムを知っていればよく、高度なアルゴリズムを時間を費やしてまで学ぶ必要はない。 

パソコンの前で、復習しよう、そしてKaggleのコンペに参加しよう。

 

*A. Geronさんのテキスト第2章をとりあえず、終えたので、Titanicに戻ってみようか。

 

Titanic: Machine Learning from Disaster-2

に、つづく

 

 

f:id:AI_ML_DL:20200507084232p:plain

style=122 iteration=1

f:id:AI_ML_DL:20200507084328p:plain

style=122 iteration=20

f:id:AI_ML_DL:20200507084407p:plain

style=122 iteration=500