LoginSignup
13
12

More than 3 years have passed since last update.

Kaggleに挫折したのでスクレイピング&機械学習でお得な賃貸物件探してみた

Last updated at Posted at 2020-05-02

はじめに

機械学習の勉強始めて色々と知識のインプットも出来てきたのでいっちょkaggleでもやるか!挑戦したのですが挫折しました。
ぶっちゃけどういう風に手をつけていいか全く分からん!!状態になり学習のモチベーションも下がってしまいました。

このままじゃダメだと思いなんか面白いことないかなーと探してたところ下記記事を発見!
【超初心者向け】コピペで動かして楽しむPython環境構築&スクレイピング&機械学習&実用化【SUUMOでお得賃貸物件を探そう!】

最近賃貸物件探していたので丁度良いと思い試してみました。

記事を参考に実装。
自分なりに色々と改良したのでご紹介します。

どんな人向け?

私みたいな自称機械学習初級者向けです。
色々インプットしたけどその後どうしていいか分からんと言う方が対象です。
機械学習の基本的な用語とか、手法については解説してませんので悪しからず。

自分の環境

windous10 Home
python3.7.3
jupyter notebook(テスト用)

ソースコード
https://github.com/pattatto/scraping

改良したところ

まず概要について説明します。
ディレクトリ構成としては以下です。
image.png

一連の流れとしては
1. スクレイピングによるデータ取得(suumo_getdata.py)
2. データの前処理(Preprocessing.py)
3. 特徴量作成(Feature_value.py)
4. モデル学習(model_lightgbm.py)
5. 学習済モデルから予測結果を出力(Create_Otoku_data.py)

元々一つのコードでしたが、これらをモジュール化しました。
データ処理の結果はその都度CSVで出力して、使用するたびに呼び出してます。

スクレイピング編

suumo_getdata.py
from bs4 import BeautifulSoup
import urllib3
import re
import requests
import time
import pandas as pd
from pandas import Series, DataFrame

url = input()

result = requests.get(url)
c = result.content

soup = BeautifulSoup(c)

#全ページ数を取得したい
summary = soup.find("div",{'id':'js-bukkenList'})
body = soup.find("body")
pages = body.find_all("div",{'class':'pagination pagination_set-nav'})
pages_text = str(pages)
pages_split = pages_text.split('</a></li>\n</ol>')
pages_split0 = pages_split[0]
pages_split1 = pages_split0[-3:]
pages_split2 = pages_split1.replace('>','')#2桁の時に邪魔になる>を除去
pages_split3 = int(pages_split2)

urls = []

urls.append(url)

#2ページ以降はurlの最後に&page=2がつく
for i in range(pages_split3-1):
    pg = str(i+2)
    url_page = url + '&page=' + pg
    urls.append(url_page)

names = []
addresses = []
buildings = []
locations0 = []
locations1 = []
locations2 = []
ages = []
heights = []
floors = []
rent = []
admin = []
others = []
floor_plans = []
areas = []
detail_urls = []


for url in urls:
    result = requests.get(url)
    c = result.content
    soup = BeautifulSoup(c)
    summary = soup.find("div",{'id':'js-bukkenList'})
    apartments = summary.find_all("div",{'class':'cassetteitem'})

    for apartment in apartments:

        room_number = len(apartment.find_all('tbody'))

        name = apartment.find('div', class_='cassetteitem_content-title').text
        address = apartment.find('li', class_='cassetteitem_detail-col1').text
        building = apartment.find('span', class_='ui-pct ui-pct--util1').text
        #各賃貸で部屋数の数だけ物件名と住所をリストに追加
        for i in range(room_number):
            names.append(name)
            addresses.append(address)
            buildings.append(building)

        sublocation = apartment.find('li', class_='cassetteitem_detail-col2')
        cols = sublocation.find_all('div')
        for i in range(len(cols)):
            text = cols[i].find(text=True)
            #部屋の数だけ各リストにデータを追加
            for j in range(room_number):
                if i == 0:
                    locations0.append(text)
                elif i == 1:
                    locations1.append(text)
                elif i == 2:
                    locations2.append(text)

        age_and_height = apartment.find('li', class_='cassetteitem_detail-col3')
        age = age_and_height('div')[0].text
        height = age_and_height('div')[1].text

        for i in range(room_number):
            ages.append(age)
            heights.append(height)

        table = apartment.find('table')
        rows = []
        rows.append(table.find_all('tr'))#各部屋の情報

        data = []
        for row in rows:
            for tr in row:
                cols = tr.find_all('td')#td 詳細な部屋の情報
                if len(cols) != 0:
                    _floor = cols[2].text
                    _floor = re.sub('[\r\n\t]', '', _floor)

                    _rent_cell = cols[3].find('ul').find_all('li')
                    _rent = _rent_cell[0].find('span').text#家賃
                    _admin = _rent_cell[1].find('span').text#管理費

                    _deposit_cell = cols[4].find('ul').find_all('li')
                    _deposit = _deposit_cell[0].find('span').text
                    _reikin = _deposit_cell[1].find('span').text
                    _others = _deposit + '/' + _reikin

                    _floor_cell = cols[5].find('ul').find_all('li')
                    _floor_plan = _floor_cell[0].find('span').text
                    _area = _floor_cell[1].find('span').text

                    _detail_url = cols[8].find('a')['href']
                    _detail_url = 'https://suumo.jp' + _detail_url

                    text = [_floor, _rent, _admin, _others, _floor_plan, _area, _detail_url]
                    data.append(text)

        for row in data:
            floors.append(row[0])
            rent.append(row[1])
            admin.append(row[2])
            others.append(row[3])
            floor_plans.append(row[4])
            areas.append(row[5])
            detail_urls.append(row[6])


        time.sleep(3)

names = Series(names)
addresses = Series(addresses)
buildings = Series(buildings)
locations0 = Series(locations0)
locations1 = Series(locations1)
locations2 = Series(locations2)
ages = Series(ages)
heights = Series(heights)
floors = Series(floors)
rent = Series(rent)
admin = Series(admin)
others = Series(others)
floor_plans = Series(floor_plans)
areas = Series(areas)
detail_urls = Series(detail_urls)

suumo_df = pd.concat([names, addresses, buildings, locations0, locations1, locations2, ages, heights, floors, rent, admin, others, floor_plans, areas, detail_urls], axis=1)

suumo_df.columns=['マンション名','住所', '建物種別', '立地1','立地2','立地3','築年数','建物の高さ','階層','賃料料','管理費', '敷/礼/保証/敷引,償却','間取り','専有面積', '詳細URL']

suumo_df.to_csv('suumo.csv', sep = '\t', encoding='utf-16', header=True, index=False)

建物種別(マンションとかアパートなど)を追加で取得してます。
元のコードを実行したところ、お得物件の上位にアパートが結構入ってました。
同じ条件ならアパートの方が安いのは当たり前です。

前処理編

Preprocessing.py
import pandas as pd
import numpy as np
from sklearn.preprocessing import OrdinalEncoder
from sklearn import preprocessing
import pandas_profiling as pdp

df = pd.read_csv('otokuSearch/data/suumo.csv', sep='\t', encoding='utf-16')

splitted1 = df['立地1'].str.split(' 歩', expand=True)
splitted1.columns = ['立地11', '立地12']
splitted2 = df['立地2'].str.split(' 歩', expand=True)
splitted2.columns = ['立地21', '立地22']
splitted3 = df['立地3'].str.split(' 歩', expand=True)
splitted3.columns = ['立地31', '立地32']

splitted4 = df['敷/礼/保証/敷引,償却'].str.split('/', expand=True)
splitted4.columns = ['敷金', '礼金']

df = pd.concat([df, splitted1, splitted2, splitted3, splitted4], axis=1)

df.drop(['立地1','立地2','立地3','敷/礼/保証/敷引,償却'], axis=1, inplace=True)

df = df.dropna(subset=['賃料料'])

df['賃料料'] = df['賃料料'].str.replace(u'万円', u'')
df['敷金'] = df['敷金'].str.replace(u'万円', u'')
df['礼金'] = df['礼金'].str.replace(u'万円', u'')
df['管理費'] = df['管理費'].str.replace(u'円', u'')
df['築年数'] = df['築年数'].str.replace(u'新築', u'0')
df['築年数'] = df['築年数'].str.replace(u'99年以上', u'0') #
df['築年数'] = df['築年数'].str.replace(u'築', u'')
df['築年数'] = df['築年数'].str.replace(u'年', u'')
df['専有面積'] = df['専有面積'].str.replace(u'm', u'')
df['立地12'] = df['立地12'].str.replace(u'分', u'')
df['立地22'] = df['立地22'].str.replace(u'分', u'')
df['立地32'] = df['立地32'].str.replace(u'分', u'')

df['管理費'] = df['管理費'].replace('-',0)
df['敷金'] = df['敷金'].replace('-',0)
df['礼金'] = df['礼金'].replace('-',0)

splitted5 = df['立地11'].str.split('/', expand=True)
splitted5.columns = ['路線1', '駅1']
splitted5['駅徒歩1'] = df['立地12']
splitted6 = df['立地21'].str.split('/', expand=True)
splitted6.columns = ['路線2', '駅2']
splitted6['駅徒歩2'] = df['立地22']
splitted7 = df['立地31'].str.split('/', expand=True)
splitted7.columns = ['路線3', '駅3']
splitted7['駅徒歩3'] = df['立地32']

df = pd.concat([df, splitted5, splitted6, splitted7], axis=1)

df.drop(['立地11','立地12','立地21','立地22','立地31','立地32'], axis=1, inplace=True)

df['賃料料'] = pd.to_numeric(df['賃料料'])
df['管理費'] = pd.to_numeric(df['管理費'])
df['敷金'] = pd.to_numeric(df['敷金'])
df['礼金'] = pd.to_numeric(df['礼金'])
df['築年数'] = pd.to_numeric(df['築年数'])
df['専有面積'] = pd.to_numeric(df['専有面積'])

df['賃料料'] = df['賃料料'] * 10000
df['敷金'] = df['敷金'] * 10000
df['礼金'] = df['礼金'] * 10000

df['駅徒歩1'] = pd.to_numeric(df['駅徒歩1'])
df['駅徒歩2'] = pd.to_numeric(df['駅徒歩2'])
df['駅徒歩3'] = pd.to_numeric(df['駅徒歩3'])

splitted8 = df['階層'].str.split('-', expand=True)
splitted8.columns = ['階1', '階2']
splitted8['階1'].str.encode('cp932')
splitted8['階1'] = splitted8['階1'].str.replace(u'階', u'')
splitted8['階1'] = splitted8['階1'].str.replace(u'B', u'-')
splitted8['階1'] = splitted8['階1'].str.replace(u'M', u'')
splitted8['階1'] = pd.to_numeric(splitted8['階1'])
df = pd.concat([df, splitted8], axis=1)

df['建物の高さ'] = df['建物の高さ'].str.replace(u'地下1地上', u'')
df['建物の高さ'] = df['建物の高さ'].str.replace(u'地下2地上', u'')
df['建物の高さ'] = df['建物の高さ'].str.replace(u'地下3地上', u'')
df['建物の高さ'] = df['建物の高さ'].str.replace(u'地下4地上', u'')
df['建物の高さ'] = df['建物の高さ'].str.replace(u'地下5地上', u'')
df['建物の高さ'] = df['建物の高さ'].str.replace(u'地下6地上', u'')
df['建物の高さ'] = df['建物の高さ'].str.replace(u'地下7地上', u'')
df['建物の高さ'] = df['建物の高さ'].str.replace(u'地下8地上', u'')
df['建物の高さ'] = df['建物の高さ'].str.replace(u'地下9地上', u'')
df['建物の高さ'] = df['建物の高さ'].str.replace(u'平屋', u'1')
df['建物の高さ'] = df['建物の高さ'].str.replace(u'階建', u'')
df['建物の高さ'] = pd.to_numeric(df['建物の高さ'])

df = df.reset_index(drop=True)
df['間取りDK'] = 0
df['間取りK'] = 0
df['間取りL'] = 0
df['間取りS'] = 0
df['間取り'] = df['間取り'].str.replace(u'ワンルーム', u'1')

for x in range(len(df)):
    if 'DK' in df['間取り'][x]:
        df.loc[x,'間取りDK'] = 1
df['間取り'] = df['間取り'].str.replace(u'DK',u'')

for x in range(len(df)):
    if 'K' in df['間取り'][x]:
        df.loc[x,'間取りK'] = 1
df['間取り'] = df['間取り'].str.replace(u'K',u'')

for x in range(len(df)):
    if 'L' in df['間取り'][x]:
        df.loc[x,'間取りL'] = 1
df['間取り'] = df['間取り'].str.replace(u'L',u'')

for x in range(len(df)):
    if 'S' in df['間取り'][x]:
        df.loc[x,'間取りS'] = 1
df['間取り'] = df['間取り'].str.replace(u'S',u'')

df['間取り'] = pd.to_numeric(df['間取り'])

splitted9 = df['住所'].str.split('区', expand=True)
splitted9.columns = ['市町村']
#splitted9['区'] = splitted9['区'] + '区'
#splitted9['区'] = splitted9['区'].str.replace('東京都','')
df = pd.concat([df, splitted9], axis=1)

splitted10 = df['駅1'].str.split(' バス', expand=True)
splitted10.columns = ['駅1', 'バス1']
splitted11 = df['駅2'].str.split(' バス', expand=True)
splitted11.columns = ['駅2', 'バス2']
splitted12 = df['駅3'].str.split(' バス', expand=True)
splitted12.columns = ['駅3', 'バス3']

splitted13 = splitted10['バス1'].str.split('分 \(バス停\)', expand=True)
splitted13.columns = ['バス時間1', 'バス停1']
splitted14 = splitted11['バス2'].str.split('分 \(バス停\)', expand=True)
splitted14.columns = ['バス時間2', 'バス停2']
splitted15 = splitted12['バス3'].str.split('分 \(バス停\)', expand=True)
splitted15.columns = ['バス時間3', 'バス停3']

splitted16 = pd.concat([splitted10, splitted11, splitted12, splitted13, splitted14, splitted15], axis=1)
splitted16.drop(['バス1','バス2','バス3'], axis=1, inplace=True)

df.drop(['駅1','駅2','駅3'], axis=1, inplace=True)
df = pd.concat([df, splitted16], axis=1)

splitted17 = df['駅1'].str.split(' 車', expand=True)
splitted17.columns = ['駅1', '車1']
splitted18 = df['駅2'].str.split(' 車', expand=True)
splitted18.columns = ['駅2', '車2']
splitted19 = df['駅3'].str.split(' 車', expand=True)
splitted19.columns = ['駅3', '車3']

splitted20 = splitted17['車1'].str.split('分', expand=True)
splitted20.columns = ['車時間1', '車距離1']
splitted21 = splitted18['車2'].str.split('分', expand=True)
splitted21.columns = ['車時間2', '車距離2']
splitted22 = splitted19['車3'].str.split('分', expand=True)
splitted22.columns = ['車時間3', '車距離3']

splitted23 = pd.concat([splitted17, splitted18, splitted19, splitted20, splitted21, splitted22], axis=1)
splitted23.drop(['車1','車2','車3'], axis=1, inplace=True)

df.drop(['駅1','駅2','駅3'], axis=1, inplace=True)
df = pd.concat([df, splitted23], axis=1)

df['車距離1'] = df['車距離1'].str.replace(u'\(', u'')
df['車距離1'] = df['車距離1'].str.replace(u'km\)', u'')
df['車距離2'] = df['車距離2'].str.replace(u'\(', u'')
df['車距離2'] = df['車距離2'].str.replace(u'km\)', u'')
df['車距離3'] = df['車距離3'].str.replace(u'\(', u'')
df['車距離3'] = df['車距離3'].str.replace(u'km\)', u'')

df[['路線1','路線2','路線3', '駅1', '駅2','駅3','市町村', 'バス停1', 'バス停2', 'バス停3']] = df[['路線1','路線2','路線3', '駅1', '駅2','駅3','市町村', 'バス停1', 'バス停2', 'バス停3']].fillna("NAN")
df[['バス時間1','バス時間2','バス時間3',]] = df[['バス時間1','バス時間2','バス時間3']].fillna(0)#欠損値あると特徴量の計算でエラーがでるため0に置換
df['バス時間1'] = df['バス時間1'].astype(float)
df['バス時間2'] = df['バス時間2'].astype(float)
df['バス時間3'] = df['バス時間3'].astype(float)

oe = preprocessing.OrdinalEncoder()
df[['建物種別', '路線1','路線2','路線3', '駅1', '駅2','駅3','市町村', 'バス停1', 'バス停2', 'バス停3']] = oe.fit_transform(df[['建物種別', '路線1','路線2','路線3', '駅1', '駅2','駅3','市町村', 'バス停1', 'バス停2', 'バス停3']].values)

df['賃料料+管理費'] = df['賃料料'] + df['管理費']

df_for_search = df.copy()

#上限価格を設定
df = df[df['賃料料+管理費'] < 300000]

df = df[["マンション名",'建物種別', '賃料料+管理費', '築年数', '建物の高さ', '階1',
       '専有面積','路線1','路線2','路線3', '駅1', '駅2','駅3','駅徒歩1', '駅徒歩2','駅徒歩3','間取り', '間取りDK', '間取りK', '間取りL', '間取りS',
       '市町村', 'バス停1', 'バス停2', 'バス停3', 'バス時間1','バス時間2','バス時間3']]

df.columns = ['name', 'building', 'real_rent','age', 'hight', 'level','area', 'route_1','route_2','route_3','station_1','station_2','station_3','station_wolk_1','station_wolk_2','station_wolk_3','room_number','DK','K','L','S','adress', 'bus_stop1', 'bus_stop2', 'bus_stop3', 'bus_time1', 'bus_time2', 'bus_time3']


#pdp.ProfileReport(df)
df.to_csv('otokuSearch/Preprocessing/Preprocessing.csv', sep = '\t', encoding='utf-16', header=True, index=False)
df_for_search.to_csv('otokuSearch/Preprocessing/df_for_search.csv', sep = '\t', encoding='utf-16', header=True, index=False)

ここは結構処理が大変でした。
改良したのは駅の情報が入ったカラムです。
もとは駅の情報と駅からの距離だけでした。
ただ駅の情報にバス停やバスでの移動時間、最寄り駅までの車での時間が入ったままでした。

こんな感じで
川口駅 バス9分 (バス停)元郷中学校 歩1分
このデータを前処理すると

駅1      徒歩1
川口駅 バス9分 (バス停)元郷中学校 1

駅1のカラムに
川口駅 バス9分 (バス停)元郷中学校
徒歩1に
1(分)
となり駅から徒歩1分になってしまいます。
しかも駅の名前にバス情報が入るので、後でLabel encodingした時に川口駅とは別の駅情報になってしまいます。
なのでこれをバス停バスでの時間車での移動時間に分けてます。

また追加した建物種別とバス停はLabel encodingしています。

特徴量作成編

Feature_value.py
import pandas as pd
import numpy as np

df = pd.read_csv('otokuSearch/Preprocessing/Preprocessing.csv', sep='\t', encoding='utf-16')

df["per_area"] = df["area"]/df["room_number"]
df["hight_level"] = df["hight"]*df["level"]
df["area_hight_level"] = df["area"]*df["hight_level"]
df["distance_staion_1"] = df["station_1"]*df["station_wolk_1"]+df["bus_stop1"]*df["bus_time1"]
df["distance_staion_2"] = df["station_2"]*df["station_wolk_2"]+df["bus_stop2"]*df["bus_time2"]
df["distance_staion_3"] = df["station_3"]*df["station_wolk_3"]+df["bus_stop3"]*df["bus_time3"]

df.to_csv('otokuSearch/Featurevalue/Fettur_evalue.csv', sep = '\t', encoding='utf-16', header=True, index=False)

新しく作ったバスのデータ使って駅までの距離の特徴量を新たに作成しました。

あと最初はこんな特徴量作成してました。
df["per_real_rent"] = df["real_rent"]/df["area"]
ただ、よくみたら目的変数(予測する家賃の情報)が入ってるじゃんてなって消しました。
あとで学習時に各特徴量の重要度を可視化するのですが
重要度がダントツだったので良い特徴量できたとはじめは喜んでました。。。

モデル学習編

model_lightgbm.py
#データ解析用ライブラリ
import pandas as pd
import numpy as np

#データ可視化ライブラリ
import matplotlib.pyplot as plt
import seaborn as sns

#ランダムフォレストライブラリ
import lightgbm as lgb

#交差検証用に訓練データとモデル評価用データに分けるライブラリ
from sklearn.model_selection import KFold

#関数の処理で必要なライブラリ
from sklearn.metrics import mean_squared_error
from sklearn.metrics import r2_score
#モデルの保存に必要なライブラリ
import pickle

#予測値と正解値を描写する関数
def True_Pred_map(pred_df):
    RMSE = np.sqrt(mean_squared_error(pred_df['true'], pred_df['pred']))
    R2 = r2_score(pred_df['true'], pred_df['pred'])
    plt.figure(figsize=(8,8))
    ax = plt.subplot(111)
    ax.scatter('true', 'pred', data=pred_df)
    ax.set_xlabel('True Value', fontsize=15)
    ax.set_ylabel('Pred Value', fontsize=15)
    ax.set_xlim(pred_df.min().min()-0.1 , pred_df.max().max()+0.1)
    ax.set_ylim(pred_df.min().min()-0.1 , pred_df.max().max()+0.1)
    x = np.linspace(pred_df.min().min()-0.1, pred_df.max().max()+0.1, 2)
    y = x
    ax.plot(x,y,'r-')
    plt.text(0.1, 0.9, 'RMSE = {}'.format(str(round(RMSE, 5))), transform=ax.transAxes, fontsize=15)
    plt.text(0.1, 0.8, 'R^2 = {}'.format(str(round(R2, 5))), transform=ax.transAxes, fontsize=15)


df = pd.read_csv('otokuSearch/Featurevalue/Fettur_evalue.csv', sep='\t', encoding='utf-16')

#kf : データ分割の挙動を指定する箱。今回は10分割・データシャッフルあり。
kf = KFold(n_splits=10, shuffle=True, random_state=1)

#predicted_df : これから各予測値を結合していく時に、空のデータフレームを容易しておく
predicted_df = pd.DataFrame({'index':0, 'pred':0}, index=[1])

#パラメータは特に調整してない
lgbm_params = {
        'objective': 'regression',
        'metric': 'rmse',
        'num_leaves':80
}

#交差検証4分割で行うので、10回ループが繰り返される。
#kfにindexを与え、訓練データindexと評価データindexを決定してもらう。
#df,indexの中から1回目につかう訓練用のデータのインデックス番号と評価用データのインデックス番号をtrain_index, val_indexに出力する
for train_index, val_index in kf.split(df.index):

    #訓練データindexと評価データindexを使って、訓練データと評価データ&説明変数と目的変数に分割
    X_train = df.drop(['real_rent','name'], axis=1).iloc[train_index]
    y_train = df['real_rent'].iloc[train_index]
    X_test = df.drop(['real_rent','name'], axis=1).iloc[val_index]
    y_test = df['real_rent'].iloc[val_index]

    #LightGBM高速化の為のデータセットに加工する
    lgb_train = lgb.Dataset(X_train, y_train)
    lgb_eval = lgb.Dataset(X_test, y_test)

    #LightGBMのモデル構築
    gbm = lgb.train(lgbm_params,
                lgb_train,
                valid_sets=(lgb_train, lgb_eval),
                num_boost_round=10000,
                early_stopping_rounds=100,
                verbose_eval=50)

    #モデルに評価用説明変数を入れて、予測値を出力する
    predicted = gbm.predict(X_test)

    #temp_df : 予測値と正答値を照合するために、予測値と元のindexを結合
    temp_df = pd.DataFrame({'index':X_test.index, 'pred':predicted})

    #predicted_df : 空のデータフレームにtemp_dfを結合→二周目以降のループでは、predicted_df(中身アリ)にtemp_dfが結合する
    predicted_df = pd.concat([predicted_df, temp_df], axis=0)

predicted_df = predicted_df.sort_values('index').reset_index(drop=True).drop(index=[0]).set_index('index')
predicted_df = pd.concat([predicted_df, df['real_rent']], axis=1).rename(columns={'real_rent' : 'true'})

True_Pred_map(predicted_df)

print(r2_score(y_test, predicted)  )
lgb.plot_importance(gbm, figsize=(12, 6))
plt.show()

#モデルの保存
with open('otokuSearch/model/model.pickle', mode='wb') as fp:
    pickle.dump(gbm, fp)

もとの記事にも書いてありますが学習データと予測したいデータがほぼ同じなので
「カンニング」状態になってます

なので交差検証を実装しました。
ここのコードを拝借しました。とても分かりやすく解説されています。
https://rin-effort.com/2019/12/31/machine-learning-8/

交差検証について簡単に説明すると
1. 学習データを分割する(今回は10)
2. そのうち一つをバリデーション、残りを学習データとして使用して学習
3. バリデーションデータで評価
4. 分割した分だけバリデーションデータ変えて学習→評価を繰り返す
5. それらのスコアの平均でモデルを評価

あと作成したモデルを保存するようにしました。都度学習させるのは面倒なので。

元のコード同様に実行すると予測正当値マップと重要な特徴量のグラフが出力されます。
なかなかな相関関係ですね!
image.png
新しく取得してきた建物種別(building)はあまり寄与してないですね。
image.png

お得物件データ作成編

Create_Otoku_data.py
import pandas as pd
import numpy as np
import lightgbm as lgb
import pickle

#データの読み込み
df = pd.read_csv('otokuSearch/Featurevalue/Fettur_evalue.csv', sep='\t', encoding='utf-16')

#学習済モデルの読み込み
with open('otokuSearch/model/model.pickle', mode='rb') as fp:
    gbm = pickle.load(fp)


#お得物件データの作成
y = df["real_rent"]
X = df.drop(['real_rent',"name"], axis=1)
pred = list(gbm.predict(X, num_iteration=gbm.best_iteration))
pred = pd.Series(pred, name="予測値")
diff = pd.Series(df["real_rent"]-pred,name="予測値との差")
df_for_search = pd.read_csv('otokuSearch/Preprocessing/df_for_search.csv', sep='\t', encoding='utf-16')
df_for_search['賃料料+管理費'] = df_for_search['賃料料'] + df_for_search['管理費']
df_search = pd.concat([df_for_search,diff,pred], axis=1)
df_search = df_search.sort_values("予測値との差")
df_search = df_search[["マンション名",'賃料料+管理費', '予測値',  '予測値との差', '詳細URL', '間取り', '専有面積', '階層', '駅1', '徒歩1', '間取りDK', '間取りK', '間取りL']]
df_search.to_csv('otokuSearch/Otoku_data/otoku.csv', sep = '\t',encoding='utf-16')

大きな変更はありません
学習モデルの読み込みと出力データのカラムの追加しただけです。

出力ファイルから映えある第一位に輝いたのはコチラ!
image.png
駅徒歩分2分!3LDK!これで家賃8.2万円とは素晴らしい!
ただ路線がちょっとローカルなのが。。。

結果

お得物件データとしてはそれなりのものが作成出来る様になりました。
ただEDA(探索的データ解析)は全然してないので精度を上げるのにはもっと分析が必要です。
あとは、スクレイピングしてくるデータ増やすと良いですね。
例えば、都市ガスとかエアコンとか色々あります。
他にもハイパーパラメータのチューニングとかやりだしたらきりがないです。
まぁ取り敢えずやりたいことはできたのでヨシ!

あと色々なコード管理してgitの必要性がやっと理解できました。
Atom使ったりgitの使い方覚えたりと色々と収穫があり、
プログラミングは実践で覚えるのが一番の近道ということを実感しました。

今後

Djangoつかって今回作成したモデルをどうにかシステムの形にもってきたいと考えています。
ページのURL入力すると家賃予想してお得度出してくれるみたいな感じですかね。
やってみてできたら記事にします。

13
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
12