Misaku
@Misaku (Misaku suzuki)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

データサイエンス タクシーの値段予測?について

解決したいこと

Ridge回帰モデルを作成して予測を実行したいのですが、
予想結果が ほぼ同じような数字、(平均ぽい?)数字になってしまいます。

モデルは、kaggleのNYタクシーの値段の予測のデータセットの
テストデータを10000件のみにしてそれらを train_test_split しています。

使用しているcsvはKaggleの test_data のうちの上から10000行
https://www.kaggle.com/competitions/new-york-city-taxi-fare-prediction/data

jupyter lab を使用しています

コードです

#ライブラリ読込
import numpy as np
import pandas as pd
from scipy import stats


import statsmodels.formula.api as smf
import statsmodels.api as sm

from sklearn.linear_model import RidgeCV
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split

from geopy.distance import geodesic

import matplotlib.pyplot as plt
import seaborn as sns

%matplotlib inline
sns.set()

#csv読み込み
data = pd.read_csv('NewYork.csv', nrows=10000)

緯度経度でerrorがあったので90以上の行を削除
data = data.query('pickup_latitude <= 90')

#降りた地点の緯度経度からそれぞれ距離を求める,新しい列 adistance を作成~

data['adistance'] = data.apply(lambda x:geodesic((x["pickup_latitude"], x["pickup_longitude"]), (x["dropoff_latitude"], x["dropoff_longitude"])), axis=1)


#adistance のindex が特殊なデータなので float型へ 変換
data['adistance'] = data['adistance'].astype(str).str[:-3].astype(float)

#散布図にするとこのようになりました
data.plot.scatter(x='adistance',y='fare_amount')

スクリーンショット 2022-08-18 19.25.09.png

#adistance 大きく外れている値が多いので 2000より小さいものに絞って表示

data.query('adistance < 2000').plot.scatter(x='adistance',y='fare_amount')

スクリーンショット 2022-08-18 19.25.54.png

発生している問題です

#fare_amount = x  ,adistance = y に代入

X = data['adistance'].values
y = data['fare_amount'].values

#下でエラーが出たので2次元に変換
X = X.reshape(-1,1)
y = y.reshape(-1,1)

#train_test_split する

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3,random_state = 2)
X_train, X_valid, y_train, y_valid = train_test_split(X_train, y_train, test_size=0.3,random_state = 2)

# Ridge回帰モデルを作成して予測を実行する
# ambdaを50個つくる
n_lambda = 50
ridge_lambdas = np.logspace(-10, 1, n_lambda)

#  RidgeCVを使って最適なlambdaでモデルを構築する
model = RidgeCV(cv = 10, alphas = ridge_lambdas, fit_intercept = True)
model.fit(X_train, y_train)

# テストデータから予測を行います。
y_pred = model.predict(X_valid)


#省略せずに表示する
np.set_printoptions(threshold=np.inf)

print(y_pred)

[[11.31607674]
[11.31211052]
[11.31268757]
[11.31513604]
[11.31296162]
[11.3127759 ]
[11.31233691]
[11.32064725]
[11.31375034]
[11.3171895 ]
[11.31421966]
[11.3144373 ]
[11.31351692]
[11.31230689]
[11.3150098 ]
[11.31379239]
[11.31641956]
[11.31818041]
[11.31181638]
[11.31372906]
[11.31411797]
[11.40207334]
[11.32004773]
[11.33141205]
[11.3188236 ]
[11.31397801]
[11.31233667]
[11.32087634]
[11.31453726]
[11.32510593]
[11.31613114]
[11.3130242 ]
[11.31426498]
[11.31474028]
[11.3157681 ]
[11.3134119 ]
[11.31304438]
[11.31293331]....

というようにずっと 11.いくつが 延々と続きます。

ambdaを50個つくる 以降からは 丸々引用してきたのですが、

切片を求める? fit_intercept の引数を False にし
その後y_pred 予測を行うと

[[1.31234801e-02]
[9.06065791e-04]
[2.68360775e-03]
[1.02257774e-02]
[3.52775870e-03]
[2.95568818e-03]
[1.60344036e-03]
[2.72023395e-02]
[5.95730867e-03]
[1.65511852e-02]
[7.40298417e-03]
[8.07341403e-03]
[5.23830659e-03]
[1.51097296e-03]
[9.83692222e-03]
[6.08684107e-03]
[1.41795102e-02]
[1.96035560e-02]
[0.00000000e+00]
[5.89178040e-03]
[7.08976219e-03]
[2.78024514e-01]
[2.53555827e-02]
[6.03618518e-02]
[2.15848168e-02]
[6.65861851e-03]
[1.60270512e-03]
[2.79080064e-02]
[8.38132722e-03]
[4.09367027e-02]
[1.32910664e-02]
[3.72055651e-03]
[7.54261161e-03]
[9.00669260e-03]
[1.21727460e-02]
[4.91479327e-03]...

このようになりもはやなんの数字かわかりません。
もう少しのような気もするのですが 理解度がきっと足りてない所がたくさんあるような気もします、
根本的に何か間違っているのか、、、?

しかしどのようなことをすれば良いのかわかりません、、どなたかご教授よろしくお願いします。

0

2Answer

こちらでも同様に実験を行ってみました.上の問題ももちろん再現いたしました.
そのうえで,原因の考察を次に示します.

runzou_suzukiさんは考察に当たって,y_predしか見ていないようだったので,他に得られる情報は無いかとRidgeCVのドキュメントを当たってみました.

今回の議論で役立ちそうなパラメータは特にalpha_intercept_ですかね.ついでに傾きcoef_も出力させます.

実際に次のコードを追加して確認してみました.

  y_pred = model.predict(X_valid)
+ print('alpha:', model.alpha_)
+ print('coef:', model.coef_)
+ print('intercept:', model.intercept_)

出てきた値はそれぞれ,

alpha: 10.0
coef: [[0.00092048]]
interecept: [11.31181638]

でした.結果として,α値はnp.logspace(-10, 1, n_lambda)で生成した最高値の10が使われていることがわかりました.これは,最高値を使うほどこのモデルがより大きい値を渇望していることがわかります.もっと大きい値を扱えるように変更できるのではないかという改善案が思いつきますね.

次に切片(intercept)です.線形回帰をやる中で,タクシーの問題を解くなら特に,初乗り価格というものがあると思いますので必要な項目だと考えます.fit_intercept = Falseにすると${\rm adisntance} = 0[{\rm km}]$の地点における初乗り価格が0であるということになってしまいます.この対策は悪手だと考えます.実際,今回のモデルではintercept = 11.31という,runzou_suzukiさんの言う「平均的な値」の出力と同様のものが得られていますので切片を必要としていることが分かります.2つ目に示していただいた結果では,切片をなくすように変更したものなので,ほとんど0が出力されています.

また,最初にデータの分布を示していただいたように,データの多くはこの切片の値周辺に固まっているのはわかっているはずです.

次のコードを追加して予測の分布と正しい分布を比較してみました.

plt.scatter(X_valid, y_pred, label = 'predict')
plt.scatter(X_valid, y_valid, label = 'correct')
plt.legend()
plt.grid()

srcshot.png

見づらいのでadistance < 2000のみ表示させました.

cutoff = X_valid < 2000
plt.scatter(X_valid[cutoff], y_pred[cutoff], label = 'predict')
plt.scatter(X_valid[cutoff], y_valid[cutoff], label = 'correct')

srcshot2.png

データ自体は$20[{\rm km}]$周辺までは距離に比例しているようになっているものの,このモデルは長距離のサンプルに引きつられてあまり傾きのないグラフとなってしまったことがわかります.

実際,出てきた傾きはcoef = 0.00092048です.ほとんど同じ値しか出さないモデルができました.

結論・改善案

このようなデータに対して単回帰分析をやったときに,よく見る当たり前の結果が得られただけのことです.プログラムやモデルは正常な動作をしていますし,RidgeCVではこれ以上どうすることもできないタスクを与えられている状態です.

機械学習はモデルの選定よりも先にデータの整形に時間をかけるべきです.その一手として緯度経度情報を直線距離情報に変換したのは非常に良いことだと思いますが,まだ求めるべき特徴量があるということです.

まずやるべきことは,料金が負の値であったりなど,あり得ない値の扱いを決めることだと思います.実際,Discussionには負の料金についての議論もあります.距離が数千$[{\rm km}]$を超えるものは料金に見合っていない移動なのでこれも異常値ですね.

Discussionを一通り見て良いなと思ったのは,Google Map APIを使って実際の発着地点の意味を見出そうとするものです.実際異常値として海中座標があるようです.地図と比較しないとわからないデータもあります.また,初乗り料金や特別料金,市外料金などのまとめが書いてあるのも良いですね.

したがって,Discussionでよく取り組まれているような特徴量エンジニアリングを頑張る.というのがrunzou_suzukiさんのやることです.学習モデルがどうこうでハイパーパラメータの調整がどうのっていうのはまだ先のように思えます.ひとまず,DiscussionにあったQuick look into NYC: starter kernelを真似てみるのも良さそうですね.GeoPyなんて使わずに地球表面上での距離を求めているのは勉強になる上,他にも空港の特別料金に対応するための座標加工だけにとどまらず時間データも整形しており,正規化も取り入れております.

こちらでそのstarterを実行してみたところ,当時の提出人数約1500人中429位となるスコア${\rm RMSE}=3.19044$を達成することができました.starterで使われているRandomForest+GridSearchをRidgeCVに置き換えたら1364位となるスコア${\rm RMSE}=6.72935$になったので,重回帰分析の線形分離不可能である欠点やデータに多重共線性があってしまったなどの問題が生じたのだと考えられます.データの整形をある程度頑張ってもモデルの限界にブチ当たってしまうこともあるということです.こうなってはじめてモデル選定やハイパーパラメータチューニングの議論が始まるのです.

1Like

Comments

  1. @Misaku

    Questioner

    すごいです、ありがとうございます。
    もう少し、データの加工について考えます!!

単純なデータを用いて

このモデルは長距離のサンプルに引きつられてあまり傾きのないグラフとなってしまった

という件と,

このようなデータに対して単回帰分析をやったときに,よく見る当たり前の結果が得られた

と言った件について再現しておきます.

次のコードで実現させます.

$0\leq x<20$の範囲で$y = x + \epsilon$,$\epsilon\sim\mathcal{N}\left(0, 1\right)$として,$100\leq x<110$の範囲で$y=10$としました.

import numpy as np
from sklearn.linear_model import RidgeCV
from matplotlib import pyplot as plt

x = np.linspace(0, 20, 100)
y = np.linspace(0, 20, 100) + np.random.normal(0, 1, 100)

x = np.append(x, np.linspace(100, 110, 10)).reshape(-1 , 1)
y = np.append(y, np.array([10] * 10)).reshape(-1, 1)

n_lambda = 50
ridge_lambdas = np.logspace(-10, 1, n_lambda)

model = RidgeCV(cv = 10, alphas = ridge_lambdas, fit_intercept = True)
model.fit(x, y)

x_pred = np.linspace(0, 110, 100).reshape(-1, 1)
y_pred = model.predict(x_pred)

plt.scatter(x, y, label = "correct")
plt.scatter(x_pred, y_pred, marker = "x", label = "predict")
plt.grid()
plt.legend()
plt.show()

得られたグラフは

recall1.png

です.Kaggleのタクシーデータセットには数千キロを超える範囲もあったので,それも再現するとより傾きの小さいグラフが得られることでしょう.

結論,$0\leq x<20$の範囲で予測がうまくいかないのはこれら外れ値を含む教師データの問題です.

1Like

Comments

  1. @Misaku

    Questioner

    ありがとうございます!
    やはり、元のデータについてもう少しどう加工するか考える必要があるのですね、!

Your answer might help someone💌