[初心者向け] Bostonデータ使って Amazon SageMaker 組み込みアルゴリズムで住宅価格を推定してみる

2020.04.25

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

おはようございます、もきゅりんです。

最近は個人的な取り組みの一環として、機械学習の学習に取り組んでいます。

前回 は、LinearLearnerで Iris の分類をしました。

次は、Bostonデータを使って LinearLearner で住宅価格を推定します。

なお、自分は専門的なデータサイエンティストでも何でもないので、無駄、非効率な作業を行っているかもしれない点、ご了承下さい。

前提

  • データを格納するS3バケットがあること
  • Jupyterノートブックが作成されていること
  • IAM権限を設定・更新できること

こちらの作業については下記を参照下さい。

はじめてのSageMaker みんな大好きアイリスデータを使って組み込みアルゴリズムで分類してみる

やること

住宅価格を、13つの特徴(町の生徒/教師の比率とか)を利用して予測しようぜ! が趣旨です。

(正確に言えば、ピンポイントの価格ではなく、中央値です。)

  1. データのロード、データを探索、処理、S3アップロード
  2. モデルでの学習
  3. モデルのデプロイ
  4. モデルの検証
  5. 後片付け

1. データのロード、データを探索、処理、S3アップロード

環境変数とロールの確認

%%time

import os
import boto3
import re
import numpy as np
from sagemaker import get_execution_role

role = get_execution_role()
region = boto3.Session().region_name

bucket='YOUR_BUCKET_NAME'
prefix = 'sagemaker/DEMO-linearlearner'
# customize to your bucket where you have stored the data
bucket_path = 'https://s3-{}.amazonaws.com/{}'.format(region,bucket)

データのロード

Boston なのでsklearnのデータセットからロードします。

(実は最初、LIBSVMでやってみようとしたけど、LinearLearnerはLIBSVM形式に対応していないようだった... *1)

from sklearn import datasets
boston = datasets.load_boston()

データの分割

テストデータにデータの20%を切り分けます。

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(boston['data'],boston['target'],test_size=0.2, random_state=42)

さらに訓練データと検証データに分けます。

from sklearn.model_selection import train_test_split
X_train, X_validation, y_train, y_validation = train_test_split(X_train,y_train,random_state=42)

第1列目に教師データ(住宅価格の数値)を挿入します。

train_set = np.insert(X_train, 0, y_train, axis=1)
valid_set = np.insert(X_validation, 0, y_validation, axis=1)
test_set = np.insert(X_test, 0, y_test, axis=1)

それぞれのデータのサイズを確認します。

教師データが挿入されたので、column は14です。

test_set.shape
valid_set.shape
train_set.shape

データのアップロード

それぞれのデータをS3バケットにアップロードします。

アップロードする関数を作成します。

def convert_data(feature_dim):
    for data_partition_name, data_partition in data_partitions:
        print('{}: {} {}'.format(data_partition_name, data_partition[0].shape, data_partition[1].shape))
        labels = [t.tolist() for t in data_partition[:,0]]  # ラベルの抽出
        features = [t.tolist() for t in data_partition[:,1:feature_dim + 1]] # 特徴量の抽出

        if data_partition_name != 'test':
            examples = np.insert(features, 0, labels, axis=1)
        else:
            examples = features

        np.savetxt('data.csv', examples, delimiter=',')


        key = "{}/{}/data".format(prefix,data_partition_name)
        url = 's3://{}/{}'.format(bucket, key)
        boto3.Session().resource('s3').Bucket(bucket).Object(key).upload_file('data.csv')
        print('Done writing to {}'.format(url))

def get_data(data):
    return 's3://{}/{}/{}'.format(bucket, prefix, data)

def set_channel(data,content_type):
    return sagemaker.session.s3_input(data, content_type=content_type)
data_partitions = [('train', train_set), ('validation', valid_set), ('test', test_set)]
# 特徴量が引数
convert_data(13)

2. モデルでの学習

LinearLearnerのコンテナを取得します。

import sagemaker
from sagemaker.amazon.amazon_estimator import get_image_uri
container = get_image_uri(boto3.Session().region_name, 'linear-learner')

前ステップでアップロードしたS3から訓練データと検証データをダウンロードし、トレーニングの出力を保存する場所を設定します。

#Load the dataset from S3
train_data = get_data('train')
validation_data = get_data('validation')
s3_output_location = 's3://{}/{}/{}'.format(bucket, prefix, 'linear-learner_model_sdk')

モデルのハイパーパラメータを設定します。

特徴量は13、回帰なので regressor です。

linear_model = sagemaker.estimator.Estimator(container,
                                       role,
                                       train_instance_count=1,
                                       train_instance_type='ml.c4.xlarge',
                                       output_path=s3_output_location,
                                       sagemaker_session=sagemaker.Session())
linear_model.set_hyperparameters(feature_dim=13,
                           mini_batch_size=100,
                           predictor_type='regressor')

モデルが利用するデータチャネルを trainvalidation で作ります。

train_channel = set_channel(train_data, 'text/csv')
valid_channel = set_channel(validation_data, 'text/csv')
data_channels = {'train': train_channel, 'validation': valid_channel}

訓練を開始します。

linear_model.fit(inputs=data_channels,  logs=True)

回帰の損失関数は デフォルトで squared_loss のようです。

3. モデルのデプロイ

訓練されたモデルをエンドポイントにデプロイすることもできますが、特にエンドポイントは必要ないのでバッチ変換します。

13つの特徴量のみを与えられているテストデータを元に、住宅価格を推定します。

batch_input = 's3://{}/{}/test/data'.format(bucket, prefix)
batch_output = 's3://{}/{}/batch-inference'.format(bucket, prefix)
transformer = linear_model.transformer(instance_count=1, instance_type='ml.c4.xlarge', output_path=batch_output)
transformer.transform(data=batch_input, data_type='S3Prefix', content_type='text/csv', split_type='Line')
transformer.wait()

4. モデルの検証

モデルから予測された予測を答え合わせします。

テストデータから予測したファイルをダウンロードします。

boto3.resource('s3').Bucket(bucket).download_file(prefix + '/batch-inference/data.out',  'test_results')

ちょっとファイルを整形します。

(うまい方法を探そう...)

with open('test_results', "r") as f:
    s = f.read()
    s = s.replace("{\"score\":", "")
    s = s.replace("}", "")
with open('test_results_fixed', "w") as f:
    f.write(s)

arr = []
f=open('test_results_fixed','r')
for line in f:
    l = line.split(',')[0]
    arr.append(float(l))
f.close

results = np.array(arr)

結果です。

np.sqrt(np.square(np.subtract(results, test_set[:,0])).mean())

5.09 (*$1000)なので、まぁ特別良いモデルではないですね。

特徴量をいじったり、ハイパーパラメータを調整することでもっと改善するかと思います。

(ちなみに、多重共線性の疑いがある RAD を削除して同じように訓練してみましたが、5.06 で、大して変化しませんでした。)

特徴量を正規化しなくても特に支障ないんですね。

下図は、45度線が完全一致となる、予測と答え合わせのプロットです。

import matplotlib.pyplot as plt
%matplotlib inline
plt.scatter(test_set[:,0],results)
plt.xlabel('Actual Prices')
plt.ylabel('Predicted Prices')
plt.title('Comparison of Actual Prices and Predicted Prices')
plt.show()

linear-check

5. 後片付け

不用なインスタンスは削除しましょう。

S3バケットも忘れずサヨナラしましょう。

以上です。

引き続き学習を進めていきます。

どなたかのお役に立てば幸いです。

参考:

脚注

  1. https://forums.aws.amazon.com/thread.jspa?threadID=317158