AIでお買い得な神奈川県の中古マンションをあぶり出す1
08
- 10月
2018
Posted By : boomin
AIでお買い得な神奈川県の中古マンションをあぶり出す1
Advertisements

0. AIでお得な中古マンションをあぶり出す その1

これまでやってみた分析までの流れを整理してみます。

  • スクレイピング
    • suumoから販売中の中古マンションの情報を取得しました。
  • 前処理
    • 取得したマンション情報を、分析しやすいように加工などしました。
  • 分析その1
    • 取得したデータを可視化して、データの特性を把握してみました。
  • 分析その2
    • 物件の位置情報に注目して、データ可視化をしてみました。
  • 分析その3
    • 市区町村ごとに、物件の特性を可視化してみました。

今回は、これらの情報を基にして説明変数を準備して、機械学習にかけていきます。

 

1. 機械学習手法

はっきり言って、今回は採用する手法の選択肢はほとんどありませんでした。
というのも、めちゃめちゃ精度を上げるのに頑張ります!という気力もないので、楽に使えて適度に精度が出るものというと。。。。あまり残りません。

で、選択肢は以下の3つ。

手法メリットデメリット
Random Forest王道ですよね。使ってみて間違いない。scikit-learnに入っているし、環境構築も不要古典とまでいえないものの、他の2つの手法に比べて速度や精度が(一般的に)劣る
Gradient boostingRandom Forestより、高精度で高速とkaggleでもっぱらの噂使用のために環境構築が必要
LightGBMGradient boostingよりもさらに高速で高精度ともっぱらの噂使用のために環境構築が必要。特にGPUを使おうとするとちょっと面倒

で、結局LightGBMを使用することにしました。

理由・・・・?そんなん、使ってみたかったからです(キリッ

 

2. LightGBM使用のポイント

個人的に考えるポイントは以下でした。

  • scikit-learn風のinterfaceを備えている
  • 学習時のGPU使用/不使用を選択しやすい
  • パラメタに指定すれば、パラメタの修正だけでRandom Forestも使用できる
  • カテゴリ変数(Categorical Features)をone-hot表現にしなくても良い
    • ここによると、performanceも良いらしい。メモリも節約できるらしい。
    • 行列のサイズが小さくなるしね!
    • これは結構大きなポイント!
  • カテゴリ変数のサイズが大きくなると、GPUが使えない
    • でもCPUで学習すればいけた

 

3. LightGBMの準備

 

3.1 LightGBM使用のポイント

前処理済みのデータを読み込んで、カテゴリ変数に対する処理部分だけを以下に示します。
ポイントは以下の通りです。

  • DataFrameの型をcategoryにする32
  • データの中身はint32型とする
  • 後で処理が楽なように、カラム名のprefixにcat_を付けておく

 

Advertisements

3.2 LightGBM使用のポイント

こんな感じで、LightGBMに食わせるデータを作ります。

#必要なライブラリをインポート
import pandas as pd
import numpy as np
import os

from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import mean_squared_error

import json,codecs
class NumpyEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, np.ndarray):
            return obj.tolist()
        return json.JSONEncoder.default(self, obj)

import pickle
def readpkl(path):
  with open(path, 'rb') as f2:
    return pickle.load(f2)

# データ読み込み
suumo_df = readpkl('data'+os.sep+'suumo_df.pkl')

# カテゴリ変数とするカラムをint型へ変換し、DataFrameの型をcategory型とする
suumo_df.fillna("-",inplace=True)
ccol = ["構造","向き","管理タイプ","管理形態","管理費サイクル","敷地権利","駐車場場所","交通0_交通手段","交通1_交通手段","交通2_交通手段","市区町村","交通0_最寄駅"]
cdict = {}
for d in ccol:
  cdict[d] = suumo_df[d].value_counts().reset_index()["index"].to_dict()
  revdic = { v:k for k,v in cdict[d].items() }
  suumo_df["cat_{}".format(d)] = suumo_df[d].apply(lambda x: revdic[x])
  suumo_df["cat_{}".format(d)] = suumo_df["cat_{}".format(d)].astype('category')

# "cat_を付ける前のカラムを削除"
suumo_df.drop(ccol, axis=1, inplace=True)
# カテゴリ変数の変換をファイル保存
json.dump(cdict, codecs.open("{}_categorical.json".format("カテゴリ変数"), 'w', encoding='utf-8'), separators=(',', ':'), sort_keys=True, indent=2, cls=NumpyEncoder, ensure_ascii=False, encoding='utf8')
# カテゴリ変数カラムnのlist
categorical_features = [c for c, col in enumerate(suumo_df.columns) if 'cat' in col]

#################################################

# 説明変数と目的変数に分離
X, y = suumo_df.drop('価格', axis=1), suumo_df["価格"]

import lightgbm as lgb
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.metrics import mean_squared_error

# 学習用と評価用にデータを分割
X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=908 )

# create dataset for lightgbm
lgb_train = lgb.Dataset(X_train, y_train, categorical_feature=categorical_features)
lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train)

作ってみたデータを見てみると、以下のような感じです。

In [80]: X_train[0:5]
Out[80]: 
        専有面積  所在階  総戸数     ...       cat_交通2_交通手段  cat_市区町村  cat_交通0_最寄駅
4629   98.73    1   -1     ...                  1        17           85
10843  81.13    4   73     ...                  0        14          155
2783   41.32    2  126     ...                  0        27           48
3663   69.55    3  101     ...                  2         2            2
841    65.48    5   53     ...                  0        32           20

カテゴリ変数がint32型に変換されているのがわかりますね。

 

3.3 学習データと評価用データ

上記codeを見てもらえればわかりますが、データは学習用と評価用データに分けています。
ポイントは、学習データとして 使用していないデータを用いて評価する という点です。


こちらのサイトから引用

上記のcodeでは、図中の「Test」にあたるデータとして、全体の20%をランダムに抽出しました。
残る80%を学習データとして学習し、この学習結果を評価用データで評価することにします。

なお、後述しますが「validation」のために、Trainデータを10分割した交差検証を行うことで、汎化性能を担保するようにしています。

 

3.4 交差検証

最適な学習パラメータを探るためにGridSearchを行います。
この時、一緒に交差検証も実施することで、意味のあるパラメタを選択しようと思います。

以下、gridsearch部分のcodeです。

gridParams = {
  'n_estimators': [100, 250, 300, 500],
  'num_leaves': [64, 96, 128, 256],
  'max_depth': [20, 30, 40],
  'subsample': [0.9, 0.95],
  'colsample_bytree': [0.8, 1.0],
}
gridsearch = GridSearchCV(
  estimator  = lgb.LGBMRegressor(
    boosting_type='gbdt',
    objective  = "regression",
    metric='rmse',
    learning_rate=0.04,
    random_state=614,
    silent=True,
    device='gpu',
  ),
  param_grid = gridParams,
  verbose=1,
  n_jobs=-1,
  cv=10,
  refit =True
)

最後から2行目、 cv=10 と指定している部分が、交差検証を10分割して行うためのパラメタ指定です。lightgbmのパラメタは、 gridParams で指定しています。この範囲でパラメタの検索を行います。

 

3.5 交差検証とgrid searchの効率化

パラメタサーチの時は、最適なパラメタを探すことが目的なので、cvの値は小さめに2などを指定しておくといいと思います。パラメタを絞り込めたら、その時はcv=10などとして、汎化性能を担保したモデルを作るようにすると、計算時間が大幅に節約できます。

 

4. 結果

さぁ、さっそく学習結果を紹介します。

おお、これはいい感じで予測できていると思いませんか?
この予測した価格が、販売価格より低ければ、それはお買い得ということになる(?)はずです。

lightgbmは、random forestと同様、説明変数の重要度を求めることができます。
こちらも確認してみましょう。

ほほう、、、、
専有面積、築年数がかなり価格を左右する要素のようです。
意外にも、総戸数や階割合(最上階=1)がかなり効いています。
おそらくこれは、今回使用した乱数のseedが、たまたま高層マンションを取り入れたからなのでは?と思っています。それはぞれで一つの真実ですので、そういうこともあるだろうと思います。

マンションは立地がすべて、とか最重要といわれることも多いです。が、価格を決めるファクターとしては、専有面積のほうがはるかに大きいようです。そうですよね。所有権の土地面積と直結するので、間違いなく重要なファクターではあるんです。

これを最寄り駅ごとにモデルにしたら、また様子が違うのでしょうね。
大変だしデータ数がそろわないし、なによりそれを説明変数に入れているのだから、やらなくてもいいですよね。

 

4. 終わりに

次回は、実際に予測された物件の価格のうち、お得度が高い物件に注目していきたいと思います。

その後、国交省で公開されているデータ(例えばe-Stat)などを使って、何かやりたいと思っています。

Advertisements

コメントを残す