GPSで取得した位置情報のログを学習し、数分後に自分がどこにいるのかを予測する

# はじめに

GPSで位置情報を取得することがとても簡単にできるようになったので、これを使って機械学習を適用する方法を検討してみました。

# GPSロガー

RaspberryPIとGPSモジュール、ソラコムSIMを組み合わせてGPSロガーを作り、車のエンジンがかかると現在位置を取得できる環境を用意します。

収集するデータの形式は以下のとおりです。

| 項目 | 名称 | 型 | 備考 |
|---|---|---|---|
| 日時 | datetime | date | 年月日時分秒 |
| 経度 | lon | float | |
| 緯度 | lat | float | |

これをSQLiteで「gpslog.db」というファイルに以下のスキーマにて保存します。
また今回使用したデータはおよそ1週間分の記録です。

```sql
CREATE TABLE 'log' (
  datetime DATE,
  lon REAL,
  lat REAL
);
```

ちなみに、GPSロガーの作り方は以下をご参照ください。

[Raspberry PIとGPSを使って自分の位置をさくっと収集する方法](https://qiita.com/mix_dvd/items/7bf804010acb9081f682)

# 開発環境

- MacBook Air
- Python 3系
- Scikit-learn

上記環境で、Jupyter Notebook上にて以下のコードを実行します。

# ライブラリ読込

```py3:
import sqlite3
import pandas as pd
import numpy as np
import datetime

%matplotlib inline
```

# データ読込

以下のコードでSQLiteからデータフレームを作成します。

```py3:
dbname = "gpslog.db"

conn = sqlite3.connect(dbname)
table = "log"
sql = "select * from {}".format(table)
df = pd.read_sql(sql, conn)
conn.close()
```

# データ加工

日時をtimestamp型に変換して、インデックスとし、日時順に並べ替え、もし重複するデータがあれば削除します。

```py3:
df["datetime"] = df["datetime"].map(lambda _: pd.to_datetime(_))
df = df.sort_values(by="datetime")
df.index = df.datetime
df = df.drop_duplicates()
```

ここで今日以前のデータを教師データ、今日のデータをテストデータとして以下のとおり分割します。

```py3:
t = datetime.datetime.today()
df_train = df[:str(t)[:10]]
df_test = df[str(t)[:10]:]
```

# 入力データと出力データ

入力データは、ある時点の位置から19個分の位置情報とそれらの位置情報とある時点との時間差（秒）とします。
出力データは、ある時点での位置とします。

- 入力

| 項目 | 型 |
|---|---|
| 時間差 | float |
| 経度 | float |
| 緯度 | float |

上記を19組作成し、57列のデータとする。

- 出力

| 項目 | 型 |
|---|---|
| 経度または緯度を1,000,000倍したもの | int |

上記データを以下のコードを実行して作成する。

```py3:
span = 20
k = 1000000.0

rows = []
y_lon = []
y_lat = []

for i in range(len(df_train)-span):
    
    df_tmp = df_train.iloc[i:i+span]
    t = df_tmp.datetime.tolist()
    
    row =[]
    flg = True
    
    for j in range(span-1):
        s = (t[span-1] - t[j]).seconds
        row.append(s)
        if s > 120:
            flg = False
    
    if flg:
        rows.append(
            row +
            df_tmp.iloc[:-1].lon.tolist() +
            df_tmp.iloc[:-1].lat.tolist()
        )
        y_lon.append(df_tmp.iloc[-1].lon * k)
        y_lat.append(df_tmp.iloc[-1].lat * k)

X = np.array(rows).astype("float")
y_lon = np.array(y_lon).astype("int")
y_lat = np.array(y_lat).astype("int")
```

ここでは、データに120秒以上時間差があるものについては、車が停止していたと判断して学習データに含めないことにします。

# 機械学習

教師データを学習し、精度を検証します。

```py3:
# データ分割
num = int(len(X) * 0.9)

X_train = X[:num]
y_lon_train = y_lon[:num]
y_lat_train = y_lat[:num]

X_test = X[num+1:]
y_lon_test = y_lon[num+1:]
y_lat_test = y_lat[num+1:]

# 正規化
from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
scaler.fit(X_train)

X_train = scaler.transform(X_train)
X_test = scaler.transform(X_test)

# モデル作成(緯度用、経度用で2つ作成)
from sklearn.ensemble import RandomForestRegressor
model_lon = RandomForestRegressor(random_state=42)
model_lat = RandomForestRegressor(random_state=42)

# 学習
model_lon.fit(X_train, y_lon_train)
model_lat.fit(X_train, y_lat_train)

# 予測精度確認
print(model_lon.score(X_test,y_lon_test),model_lat.score(X_test,y_lat_test))
```

実行してみると、それぞれの精度は0.990709307095と0.995966615963になりました。

# 学習結果の可視化

```py3:
# 予測値の取得
result_lon = model_lon.predict(X_test)
result_lat = model_lat.predict(X_test)

# 可視化
pd.DataFrame({"pred":result_lon, "act":y_lon_test}).plot(figsize=(15,4))
pd.DataFrame({"pred":result_lat, "act":y_lat_test}).plot(figsize=(15,4))
```

![Unknown-lng.png](https://qiita-image-store.s3.amazonaws.com/0/114240/68268a71-c4d7-bf14-9c35-cc00f026ed9a.png)
![Unknown-lat.png](https://qiita-image-store.s3.amazonaws.com/0/114240/66777fa0-17ed-844b-6044-f77f023d4f9f.png)

重なり過ぎ...

# 予測

学習結果から、自分の位置を予測します。
とりあえず、3分先まで予測できるか確認。

```py3:
df_wk = df_test["2018-02-04 18:25":"2018-02-04 18:28"]

y_lon = []
y_lat = []

pred_lon = []
pred_lat = []

lon = 0
lat = 0

times = []

for i in range(len(df_wk)-span):
    
    rows = []

    df_tmp = df_wk.iloc[i:i+span]
    t = df_tmp.datetime.tolist()
    
    row =[]
    flg = True
    
    for j in range(span-1):
        s = (t[span-1] - t[j]).seconds
        row.append(s)
        if s > 120:
            flg = False
    
    if flg:
        
        if i == 0:
            lons = df_tmp.iloc[:-1].lon.tolist()
            lats = df_tmp.iloc[:-1].lat.tolist()
        else:
            lons = lons[1:] + [lon / k]
            lats = lats[1:] + [lat / k]
        
        rows.append(
            row +
            lons +
            lats
        )
        
        times.append(df_tmp.index[-1])
        
        y_lon.append(df_tmp.iloc[-1].lon * k)
        y_lat.append(df_tmp.iloc[-1].lat * k)
        
        X = np.array(rows).astype("float")
        
        lon = model_lon.predict(scaler.transform(X))[0]
        lat = model_lat.predict(scaler.transform(X))[0]
        
        pred_lon.append(lon)
        pred_lat.append(lat)
        
        if i == 0:
            print(rows)
            print(lon, lat)

    else:
        break
```

実行すると、y_lon, y_latに実際にいた場所、pred_lon, pred_latに予測した場所が入ります。

# 可視化

経度と緯度をグラフで可視化してみます。

```py3:
pd.DataFrame({"pred":pred_lon, "act":y_lon},index=times).plot(figsize=(15,4))
pd.DataFrame({"pred":pred_lat, "act":y_lat},index=times).plot(figsize=(15,4))
```

![Unknown-lon.png](https://qiita-image-store.s3.amazonaws.com/0/114240/39fb595b-9336-f891-f058-495379df5677.png)
![Unknown-.png](https://qiita-image-store.s3.amazonaws.com/0/114240/398e128c-dd85-b319-d022-3320895a118d.png)

途中まであっていて、少しずれたり正しくなったりする感じですかね...

Leaflet.jsを使って地図上にマッピングしてみます。

![スクリーンショット 2018-02-05 3.41.04.png](https://qiita-image-store.s3.amazonaws.com/0/114240/033547dc-7cd2-8de3-0eaf-3f183167165e.png)

赤線が実際の位置で青線が予測した位置です。

ここで、予測の緯度が少し早く変化したのは、この日に大雪が降ってゆっくり運転していたためかなと考えられ、より正確な予測をするためには、気象条件や走行している日時も学習データに含める必要があるかと思います。

でも、30秒くらいはかなり正確に予測ができていると言えるのかな...(^_^;)

定時運行のバスなどは、1年程度のログを集めることでかなり正確に予測ができるのかもしれません。

...とりあえず、未来を手にしたような感じがしました。