Edited at

機械学習によるタクシー利用時間予測

More than 1 year has passed since last update.


はじめに

今回、kaggleというデータ分析関連プラットフォームを用い、機械学習の勉強を行ってみました。その備忘録となります。kaggleとは何か。簡単に言えば、機械学習の競技会です。与えられた課題にたいして各々(この各々は研究者から機械学習1日目の人まで)が取り組み、正答率を競います。期限までに予測結果を提出し正答率が高ければ報酬も出るようです。

kaggleを用いる利点は様々です。私が思う利点は次の数点です。


  • データセットが容易に手に入る。

  • 人のプログラムや思考の手順が公開されている。人のプログラムや思考の手順が公開されている。

  • kaggle上のサーバーでpython,Rなどが使用できる。

機械学習を行う上では多量のデータが必要になります。これを選定するのが意外にめんどくさいです。kaggleでは訓練用データ、テストデータが指定されており、すぐにダウンロードすることができます。さらにkernelという機能を用いると、すぐにkaggleが提供するサーバー上でプログラム構築・実行を行うことができます。プログラムやノートを公開してくれている参加者もいるので、それを眺めるだけで非常に勉強となります。

今回は、New York City Taxi Trip Durationという課題に取り組んでみます。

In this competition, Kaggle is challenging you to build a model that predicts the total ride duration of taxi trips in New York City.

つまり、ニューヨーク市でのタクシーを用いた旅行予測をするためのモデル構築を行う必要があります。


データについて

さっそく、課題に取り組んでみます。まずはデータをダウンロードしてみます。

import numpy as np

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import os
print(os.listdir("../input"))
import matplotlib.pyplot as plt

train = pd.read_csv("../input/train.csv") # Reading data
test = pd.read_csv("../input/test.csv") # Reading data

ここではpythonのライブラリ"pandas"を用いてデータを読み込んでいます。

次にデータの中身を見てみます。

train.head()

id
vendor_id
pickup_datetime
dropoff_datetime
passenger_count
pickup_longitude
pickup_latitude
dropoff_longitude
dropoff_latitude
store_and_fwd_flag
trip_duration

0
id2875421
2
2016-03-14 17:24:55
2016-03-14 17:32:30
1
-73.98
40.77
-73.96
40.77
N
455

1
id2377394
1
2016-06-12 00:43:35
2016-06-12 00:54:38
1
-73.98
40.74
-74.00
40.73
N
663

2
id3858529
2
2016-01-19 11:35:24
2016-01-19 12:10:48
1
-73.98
40.76
-74.01
40.71
N
2124

3
id3504673
2
2016-04-06 19:32:31
2016-04-06 19:39:40
1
-74.01
40.72
-74.01
40.71
N
429

4
id2181028
2
2016-03-26 13:30:55
2016-03-26 13:38:10
1
-73.97
40.79
-73.97
40.78
N
435

訓練データには11種類のデータがあることがわかります。

id - タクシー利用個別番号(一回のタクシー利用ごとに自動的に割り当てられる番号だと思います。)

vendor_id - どのタクシーを利用したか判別するための番号

passenger_count - 乗客数(ドライバーが値を入力)

pickup_datetime - 乗客を乗せた日時

dropoff_datetime - 乗客を下ろした日時

pickup_longitude - 乗客を乗せた経度

pickup_latitude - 乗客を乗せた緯度

dropoff_longitude - 乗客を下ろした経度

dropoff_latitude - 乗客を下ろした緯度

store_and_fwd_flag - 旅行記録が車内のメモリーに保存されていたかどうか(データサーバに接続していない場合があるため?)

trip_duration - 旅行時間(pickup_date_timeとdropoff_datetimeから単位はsecond(秒)だと判断できます)

訓練データの詳細を確認してみます。

pd.set_option('display.float_format', lambda x: '%.2f' % x)

train.describe()

vendor_id
passenger_count
pickup_longitude
pickup_latitude
dropoff_longitude
dropoff_latitude
trip_duration

count
1458644.00
1458644.00
1458644.00
1458644.00
1458644.00
1458644.00
1458644.00

mean
1.53
1.66
-73.97
40.75
-73.97
40.75
959.49

std
0.50
1.31
0.07
0.03
0.07
0.04
5237.43

min
1.00
0.00
-121.93
34.36
-121.93
32.18
1.00

25%
1.00
1.00
-73.99
40.74
-73.99
40.74
397.00

50%
2.00
1.00
-73.98
40.75
-73.98
40.75
662.00

75%
2.00
2.00
-73.97
40.77
-73.96
40.77
1075.00

max
2.00
9.00
-61.34
51.88
-61.34
43.92
3526282.00

すべての項目でデータ数は等しいので欠損値はなさそうです。

気になるのは、passenger_count(乗客数)についてです。最小値が0となっています。 考えれられる理由としては、ドライバーが手で入力するため、人為的なエラーが考えられます。または、ニューヨーク市のタクシーでは荷物を運ぶだけが可能なのでしょうか?

またtrip_duration(旅行時間)の最大値が3526282[s]となっています。hourになおすと約980[h]となります。一ヶ月以上タクシーを連続利用?これはおかしいです。

与えられたデータでは人為的なエラーやなんらかのバグで生じた外れ値がそのままになっています。このようなデータをそのまま機械学習に利用してしまうと、予測が困難になります。変な値は外していきます。


データの整形

store_and_fwd_flagはNが0,Yが1として扱います。

def correct_data(taxi_data):

taxi_data.store_and_fwd_flag = taxi_data.store_and_fwd_flag.replace(['N', 'Y'], [0, 1])

return taxi_data

train_data = correct_data(train)
test_data = correct_data(test)

pickup_datetimedropoff_datetimeがそのままでは扱えないので、整形します。

for df in (train_data,test_data):

df['pickup_datetime'] = pd.to_datetime(df['pickup_datetime'], format='%Y-%m-%d %H:%M:%S')
df['year'] = df['pickup_datetime'].dt.year
df['month'] = df['pickup_datetime'].dt.month
df['day'] = df['pickup_datetime'].dt.day
df['hour'] = df['pickup_datetime'].dt.hour
df['minute'] = df['pickup_datetime'].dt.minute
df['second'] = df['pickup_datetime'].dt.second
df['pickup_weekday'] = df['pickup_datetime'].dt.weekday
df['pickup_weekofyear'] = df['pickup_datetime'].dt.weekofyear
df['pickup_dayofyear'] = df['pickup_datetime'].dt.dayofyear

train_data['pickup_dt'] = (train_data['pickup_datetime'] - train_data['pickup_datetime'].min()).dt.total_seconds()
train_data['pickup_week_hour'] = train_data['pickup_weekday'] * 24 + train_data['hour']
test_data['pickup_dt'] = (test_data['pickup_datetime'] - train_data['pickup_datetime'].min()).dt.total_seconds()
test_data['pickup_week_hour'] = test_data['pickup_weekday'] * 24 + test_data['hour']

とりあえず、横軸が乗客数、縦軸が旅行時間として散布図をプロットしてみます。



train_data.plot.scatter(x='passenger_count', y='trip_duration')

taxi_1.png

やはり外れ値がいくつか見えます。このような値は、ここでは消去してみます。

train_data = train_data[train_data['trip_duration'] < 1000000]

train_data.plot.scatter(x='passenger_count', y='trip_duration')

taxi2.png

train_data['log_trip_duration'] = np.log1p(train_data['trip_duration'].values)

fig, (ax1, ax2) = plt.subplots(1, 2,figsize=(12,8))
fig.suptitle('Train trip duration and log of trip duration')
ax1.legend(loc=0)
ax1.set_ylabel('count')
ax1.set_xlabel('trip duration')
ax2.set_xlabel('log(trip duration)')
ax2.legend(loc=0)
ax1.hist(train_data.trip_duration,color='black',bins=7)
ax2.hist(train_data.log_trip_duration,bins=50,color='gold');

taxi6.png

緯度と経度の扱いを考えてみます。Haversine式を用いて距離を求めてみます。

train['lat_diff'] = train['pickup_latitude'] - train['dropoff_latitude']

test['lat_diff'] = test['pickup_latitude'] - test['dropoff_latitude']

train['lon_diff'] = train['pickup_longitude'] - train['dropoff_longitude']
test['lon_diff'] = test['pickup_longitude'] - test['dropoff_longitude']

train['haversine_distance'] = train.apply(lambda row: haversine( (row['pickup_latitude'], row['pickup_longitude']), (row['dropoff_latitude'], row['dropoff_longitude']) ), axis=1)
test['haversine_distance'] = test.apply(lambda row: haversine( (row['pickup_latitude'], row['pickup_longitude']), (row['dropoff_latitude'], row['dropoff_longitude']) ), axis=1)
train['log_haversine_distance'] = np.log1p(train['haversine_distance'])
test['log_haversine_distance'] = np.log1p(test['haversine_distance'])

train_data["passenger_count"].plot.hist()

taxi3.png

乗客は一人での利用が最も多いようです。

train_data['pickup_weekday_name'] = train_data['pickup_datetime'].dt.weekday_name

train_data.pickup_weekday_name.value_counts().plot(kind='bar',align='center',width=0.3)

taxi5.png

金曜日、土曜日の週末の利用が多いようです。 逆に週明けの月曜日はタクシーの利用が少ないです。

train_data.hour.value_counts().plot(kind='bar',align='center',width=0.3)

taxi4.png

夕方の利用が多いです。深夜は早朝はタクシーの利用が少ないです。


ランダムフォレストを用いた機械学習

今回の課題では回帰分析となります。ランダムフォレストを用いて分析を行ってみます。

手法については、

機械学習の情報を手法を中心にざっくり整理

代表的な機械学習手法一覧

が参考になると思います。

from sklearn.ensemble import RandomForestRegressor

rf_model = RandomForestRegressor(n_estimators=1000, min_samples_leaf=50, min_samples_split=75)

predictors = ["vendor_id","passenger_count", "pickup_longitude", "pickup_latitude", "dropoff_longitude", "dropoff_latitude","lat_diff","lon_diff","log_haversine_distance","month", "day","hour","minute","pickup_weekday",
"pickup_weekofyear","pickup_dayofyear","pickup_dt","pickup_week_hour"]

データ数が多いためそのままで計算するにはメモリ不足です。データ数を制限してみます。

train_data2=train_data[:1000]

test_data2=test_data[:1000]

3等分の交差検証を行ってみます。交差検証については

scikit-learn を用いた交差検証(Cross-validation)とハイパーパラメータのチューニング(grid search)

が参考になります。

from sklearn.model_selection import cross_val_score

result = cross_val_score(RandomForestRegressor(), train_data2[predictors], train_data2["log_trip_duration"], cv=3)
print(result.mean())

0.640932947719

rf_model.fit(train_data2[predictors], train_data2["log_trip_duration"])

predictions=rf_model.predict(test_data2[predictors])
predictions[:5]

array([ 6.6776906 ,  6.65590457,  6.05277367,  7.05715958,  5.8944864 ])


まとめ

今回はタクシー利用時間の予測を行いました。

もう少しいろいろと試したいですが、データ数が非常に多いため、ローカルの環境や、karnel上での計算も困難になっています。環境構築が出来次第、改めてトライしてみます。


参考

kaggle上で公開されているこちらのkernelを参考にしています。図なども豊富に公開されています。

https://www.kaggle.com/poonaml/last-cab-to-new-york-animated-heatmap-trips-folium