12/9(月) 応用科学学会シンポジウムで自動運転に関する講演を担当します☆彡(試乗会もあります!来て!)

1-9. XGboostとランダムフォレストの練習(訓練データ生成、One-Hotエンコーディング、回帰学習)

やること

機械学習による回帰の備忘録です。

  1. 正解モデルを定義
  2. 訓練データを生成
  3. 回帰学習(XGboost / ランダムフォレスト)
  4. 推定精度の確認

一連の流れを残しておきます。訓練データ生成のときにランダムなノイズを乗せることで回帰の効果を確認できます。今回はノイズは乗せず、正解モデルにどれだけ近い推定結果が得られるかだけ確認します。

学習対象として「ゆうパック」「ヤマト宅急便」「佐川急便」の東京から東京へのサイズ別運賃を用います。たて、よこ、高さ、配送方法の4つを入力(説明変数)、運賃を出力(目的変数)として学習します。

参考文献

ゆうパックの運賃

基本運賃表(東京) - 日本郵便
ゆうパック都道府県別基本運賃表、差出地の都道府県から、ゆうパックの基本運賃を一覧でご覧いただけます。

ヤマトの運賃

宅急便運賃一覧表 全国一覧 | ヤマト運輸
クロネコヤマトでおなじみ、ヤマト運輸のウェブサイトの「宅急便運賃一覧表:全国一覧」ページです。

佐川の運賃

【佐川急便】関東発 宅配(陸・航空)料金表:通常配達|輸送料金|荷物を送る・受け取る
関東発 宅配(陸・航空)料金表ページです。関東から荷物を送る場合の料金(通常配達)はこちらのページを参考にしてください。

XGBoostの使い方

PythonでXGBoostを実装する方法が2つあって混乱した話+全体的なメモ - Qiita
#はじめにXGBoostは,GBDTの一手法であり,pythonでも実装することが出来ます.しかし,実装例を調べてみると,同じライブラリを使っているにも関わらずその記述方法が複数あり,混乱に陥り…

エンコーディングのお勉強

機械学習の入力に離散値(離散ラベル)が含まれる場合、これをどうやって入れるかが問題になります。専門用語でエンコーディング方法と呼ばれます。

One-Hotエンコーディングについて(+多重共線性への対応)

エンコーディング - Qiita
この記事の狙い・目的機械学習を取り入れたAIシステムの構築は、①データセット作成(前処理)→ ②モデルの構築 → ③モデルの適用というプロセスで行っていきます。その際「データセット作成(前処…
カテゴリ変数のラベル・ワンホットエンコーディング|Pythonによる機械学習用データ前処理入門 | DXCEL WAVE
こんな方におすすめ カテゴリー変数のエンコーディングについて詳しく知りたい。ワンホットエンコーディングとラベルエンコーディングをPythonで実行する方法が知りたい。 【機械学習基礎】カテゴリー変数とは カテゴリー変数とは、身長や年齢のよう

決定木モデルにおけるラベルエンコーディングとOne-Hotエンコーディングの違いについて

【機械学習】label encodingの数字の付け方を変えると精度はどうなる??実験してみた
実験の目的機械学習におけるlabelencoding(ordinalencoding)とは、カテゴリ変数を数値に変換する手法の1つで、各水準を単純に整数に置き換えるものです。例えば、「日本」「アメリカ」「イギリス」といったラベル(水準)をそ
https://yolo-kiyoshi.com/2018/12/23/post-1016/

離散ラベルに0, 1, 2, …と番号を当てて1列として入れるのがラベルエンコーディング。簡単でメモリの節約になるが、数量的な差を持たないラベルたち(例:犬、猿、雉)に数量関係を持たせてしまうため、できればOne-Hotエンコーディングしたほうが良い。ただし例外が2つある。離散ラベルではあるが数量的な意味を持っていると解釈できる場合(例:子供、大人、高齢者)はその順番でラベルを当てても良いだろう。また、決定木ベースの学習モデルを使用する場合(例:ランダムフォレスト、LightGBM)はラベルエンコーディングでも良い。だいたいこんな感じらしい。

実行環境

WinPython3.6をおすすめしています。

WinPython - Browse /WinPython_3.6/3.6.7.0 at SourceForge.net
Portable Scientific Python 2/3 32/64bit Distribution for Windows

Google Colaboratoryが利用可能です。

Google Colab

正解モデルの定義

3つの配送方法の料金体系を書いてグラフにプロットします。サイズは10~170まで10刻みです。

import itertools
import numpy as np
import numpy.random as nr
import matplotlib.pyplot as plt

from sklearn.model_selection import train_test_split
import xgboost as xgb

#正解モデルを定義
#マトリックスは1次元目:サービス名(ゆうパック、ヤマト、佐川) 2次元目:サイズ(10~170サイズまで10刻み)
service_names = ['youpack', 'yamato', 'sagawa']
price_range = range(10, 171, 10)
matrix = {'youpack':[810, 810, 810, 810, 810, 810, 1030, 1030, 1280, 1280, 1530, 1530, 1780, 1780, 2010, 2010, 2340],
          'yamato':[930, 930, 930, 930, 930, 930, 1150, 1150, 1390, 1390, 1610, 1610, 1850, 1850, 2070, 2070, 2400],
          'sagawa':[770, 770, 770, 770, 770, 770, 1045, 1045, 1386, 1386, 1848, 1848, 1848, 1848, 2068, 2068, 2420]}

#確認
plt.plot(price_range, matrix['youpack'], '--o', label='youpack')
plt.plot(price_range, matrix['yamato'], '--o', label='yamato')
plt.plot(price_range, matrix['sagawa'], '--o', label='sagawa')
plt.xlabel('size')
plt.ylabel('price')
plt.legend()
plt.show()

見やすいように点線で繋いでいますが、実際には階段状にガクガク上がります。佐川の大胆な一段飛ばしを覚えておいてください。

訓練データの生成

訓練データを1000個(1000行)生成します。

入力(x_train)は荷物の「たて」「よこ」「高さ」「配送方法」の4情報を持ちます。配送方法は多重共線性対応ということで3列ではなく2列のOne-Hotとし、したがって入力配列は5列です。

出力(y_train)は「料金」で、たて+よこ+高さ=サイズの概念を用いて算出されます。サイズ自体は列として入力しないため、機械学習によってサイズの概念を学習できるかどうかが本質的な焦点です。

#訓練データ作成
#入力(説明変数)のデータは 1列目:たて 2列目:よこ 3列目:高さ 4~6列目:サービスのワンホット([1, 0]='youpack', [0, 1]='yamato', [0, 0]='sagawa')
#出力(目的変数)のデータは 1列目:料金
x_train = []
y_train = []
num = 1000

while 1:    
    #たて、よこ、高さ生成
    tate = nr.randint(1, 81)
    yoko = nr.randint(1, 81)
    height = nr.randint(1, 81)
    
    #サイズ
    size = tate + yoko + height
    
    #サイズが170を超えたらやり直し
    if size > 170:
        continue
    
    #サービス生成
    service_name = nr.choice(service_names)
    
    #料金
    size_id = (size - 1) // 10
    price = matrix[service_name][size_id]
    
    #格納
    if service_name == 'youpack':
        x_train.append([tate, yoko, height, 1, 0])
    if service_name == 'yamato':
        x_train.append([tate, yoko, height, 0, 1])
    if service_name == 'sagawa':
        x_train.append([tate, yoko, height, 0, 0])
    y_train.append(price)
    
    if len(x_train) >= num:
        break

x_train = np.array(x_train)
y_train = np.array(y_train)
print(x_train[:5])
print(y_train[:5])

#確認(ただしサイズの概念を使って)
plt.plot(np.sum(x_train[x_train[:, 3]==1, :3], axis=1), y_train[x_train[:, 3]==1], 'o', label='youpack')
plt.plot(np.sum(x_train[x_train[:, 4]==1, :3], axis=1), y_train[x_train[:, 4]==1], 'o', label='yamato')
plt.plot(np.sum(x_train[(x_train[:, 3]==0) & (x_train[:, 4]==0), :3], axis=1), y_train[(x_train[:, 3]==0) & (x_train[:, 4]==0)], 'o', label='sagawa')
plt.xlabel('size')
plt.ylabel('price')
plt.legend()
plt.show()
[[57 78 13  0  0]
 [42 38 16  1  0]
 [64 32  5  1  0]
 [53 68 23  0  1]
 [62 72 19  0  1]]
[2068 1280 1530 2070 2070]

階段状です。

回帰学習(XGboost)

ここまでに用意したデータを7:3に分けて改めて「訓練データ」「テストデータ」と呼ぶことにします。

XGboostはほぼデフォルト設定ですが、max_depth=6→10に増やし、学習イテレーションを9999に増やしてearly_stoppingを加えました。

#訓練データ分割
x_train, x_test, y_train, y_test = train_test_split(x_train, y_train, test_size=0.3)
print(x_train.shape, x_test.shape, y_train.shape, y_test.shape)

#XGboostで回帰学習
model = xgb.XGBRegressor(n_estimators=9999, max_depth=10)
model.fit(x_train, y_train, eval_set=[(x_test, y_test)], early_stopping_rounds=10)
(700, 5) (300, 5) (700,) (300,)
[0]     validation_0-rmse:1185.65784
[1]     validation_0-rmse:835.15094
[2]     validation_0-rmse:596.64056
[3]     validation_0-rmse:424.44379
[4]     validation_0-rmse:307.71317
[5]     validation_0-rmse:232.84102
[6]     validation_0-rmse:184.68457
[7]     validation_0-rmse:155.06062
[8]     validation_0-rmse:138.36301
[9]     validation_0-rmse:130.39771
[10]    validation_0-rmse:126.12363
[11]    validation_0-rmse:125.97745
[12]    validation_0-rmse:126.16894
[13]    validation_0-rmse:126.42770
[14]    validation_0-rmse:127.37331
[15]    validation_0-rmse:127.98996
[16]    validation_0-rmse:128.67091
[17]    validation_0-rmse:129.10474
[18]    validation_0-rmse:129.12907
[19]    validation_0-rmse:129.30972
[20]    validation_0-rmse:129.33725
[21]    validation_0-rmse:129.36194

推定精度の確認

テストデータの正解と推定をプロット。相関係数も出します。

#相関確認
print('相関係数')
print(np.corrcoef(y_test, y_pred))

plt.plot(y_test, y_pred, 'o')
plt.xlabel('y_test')
plt.ylabel('y_pred')
plt.show()
相関係数
[[1.         0.94975516]
 [0.94975516 1.        ]]

相関係数は0.95程度。

網羅的なデータを生成して推定精度を確認。訓練データ数を変えた結果を並べます。横軸は「サイズ」であることに注意。推定した点が上下にブレているのは、同じサイズでも元となるたて・よこ・高さが異なり推定結果が異なるため。訓練データ数を増やすほどそのブレが小さくなり、佐川の一段飛ばしもはっきりと表現できるようになりました。

#網羅的なデータで推定してみる
for b1, b2, service_name in [[1, 0, 'youpack'], [0, 1, 'yamato'], [0, 0, 'sagawa']]:
    #x生成
    x = itertools.product(np.arange(1, 81), np.arange(1, 81), np.arange(1, 81), [b1], [b2])
    x = np.array(list(x))
    x = x[np.sum(x[:, :3], axis=1) <= 170]
    #料金推定
    price_pred = model.predict(x)
    #グラフ
    plt.plot(np.sum(x[:, :3], axis=1), price_pred, 'o', label=service_name)
plt.xlabel('size')
plt.ylabel('price')
plt.legend()
plt.show()

ランダムフォレストの場合

途中を少し変えるだけです。ランダムフォレストにはイテレーションの概念がなく、テストデータを使用して学習経過を監視する必要がありません。勝手に停止します。学習時間はXGboostよりかなり短かったです。

from sklearn.ensemble import RandomForestRegressor

#ランダムフォレストで回帰学習
model = RandomForestRegressor()
model.fit(x_train, y_train)

訓練データ数を変えた学習結果がこちら。

おわりに

今回、訓練データの理論上の組み合わせ数は80×80×80×3=約150万通りです。訓練データ数が多いほど学習精度が良くなったのは、精度評価に使用したデータが訓練データに含まれていた影響が大きいと思います。きちんとやるなら、訓練データに含まれないデータをテストデータに用いて学習を監視し、評価にも用いた方が良いです。

今回はあまり回帰向きではない正解モデルを使用しました。階段状のモデルをどれだけ学習できるか興味があったからです。もっと回帰の効果を見るには、何か数式を用いたモデルにランダムノイズを乗せて学習するのが良いでしょう。

タイトルとURLをコピーしました