初めに
今までBigQueryに触れたことがなかった(それどころかGCP自体も触れていなかった)ので勉強を兼ねて触ってみることにしました。
この記事はそのアウトプットになります。
流れ
GCPのVMインスタンス上にjupyter notebookの環境を構築し、クラウド上でsuumoの賃貸物件情報をスクレイピングしました。そのデータをGCSのバケットに転送しBigQueryで価格を予測するモデルの作成を行いました。
スクレイピングは以前の記事で行なったものとほぼ同じです。
なお、この記事ではGCPの設定方法や環境構築については記載いたしません。
クローリング&スクレイピング
jupyter notebookでpythonを用いてクローリング&スクレイピングを行いました。
import requests
from bs4 import BeautifulSoup
from tqdm import tqdm as tqdm
import pandas as pd
import time
import re
from google.cloud import storage as gcs
from sklearn.model_selection import train_test_split
name = []
station = []
price = []
room = []
age = []
address = []
user_agent = "ユーザーエージェント名"
header = {'user-agent':user_agent}
# クローリング
for p in tqdm(range(1,51)):
p=str(p)
url="https://suumo.jp/jj/chintai/ichiran/FR301FC005/?ar=030&bs=040&ra=013&rn=0045&ek=004506820&cb=0.0&ct=9999999&mb=0&mt=9999999&et=9999999&cn=9999999&shkr1=03&shkr2=03&shkr3=03&shkr4=03&sngz=&po1=09&po2=99&pc=100" + "&page=" + p
urls=requests.get(url,headers=header)
time.sleep(3)
urls.encoding = urls.apparent_encoding
soup=BeautifulSoup()
soup=BeautifulSoup(urls.content,"html.parser")
house_name = soup.find_all("a",class_="js-cassetLinkHref")
station_name = soup.find_all("div",style="font-weight:bold")
table_data = pd.read_html(urls.content)
for h in house_name:
name.append(h.text)
for s in station_name:
station.append(s.text)
for i in range(100):
table = table_data[i]
price.append(table.iloc[0,0])
room.append(table.iloc[0,2])
age.append(table.iloc[0,3])
address.append(table.iloc[0,4])
# 前処理
time_move_list_pre = []
time_move_list = []
bus_list = []
walk_list =[]
time_list = []
for i in station:
time_move_list_pre.append(i.split(" ")[1])
for i in time_move_list_pre:
if "歩" in i:
bus_list.append(0)
walk_list.append(1)
time_list.append(re.sub(r"\D", "", i))
else:
bus_list.append(1)
walk_list.append(0)
time_list.append(re.sub(r"\D", "", i))
price_list = []
for i in price:
price_list.append(i.split(" ")[0].replace("万円",""))
room_type_list = []
area_list = []
for i in room:
room_type_list.append(i.split(" ")[0])
area_list.append(i.split(" ")[2].replace("m2",""))
age_pre = []
for i in age:
age_pre.append(i.split(" ")[2])
age_list = []
for i in age_pre:
if i == "新築":
age_list.append(0)
else:
age_list.append(re.sub(r"\D", "", i))
name_s=pd.Series(name)
time_s=pd.Series(time_list)
bus_s=pd.Series(bus_list)
walk_s =pd.Series(walk_list)
price_s=pd.Series(price_list)
room_s=pd.Series(room_type_list)
area_s=pd.Series(area_list)
age_s = pd.Series(age_list)
address_s=pd.Series(address)
df=pd.concat([name_s,time_s,bus_s,walk_s,price_s,room_s,area_s,age_s,address_s],axis=1)
df.columns=["name","time_to","bus","walk","price","room","area","age","address"]
# 対数変換
df["time_to"] = df["time_to"].astype(float)
df["price"] = df["price"].astype(float)
df["area"] = df["area"].astype(float)
df["age"] = df["age"].astype(float)
df["time_to_log"] = np.log1p(df["time_to"])
df["price_log"] = np.log1p(df["price"])
df["area_log"] = np.log1p(df["area"])
df["age_log"] = np.log1p(df["age"])
# 学習用にトレーニングデータとテストデータに分ける
train, test = train_test_split(df, random_state=10, test_size=0.30)
#GCSのバケットに転送
project_id = "プロジェクト名"
bucket_name = "バケット名"
gcs_path_1 = "data/scraping_data.csv"
gcs_path_2 = "data/train.csv"
gcs_path_3 = "data/test.csv"
client = gcs.Client(project_id)
bucket = client.get_bucket(bucket_name)
blob_gcs_1 = bucket.blob(gcs_path_1)
blob_gcs_2 = bucket.blob(gcs_path_2)
blob_gcs_3 = bucket.blob(gcs_path_3)
blob_gcs_1.upload_from_string(
data=df.to_csv(index=False)
)
blob_gcs_2.upload_from_string(
data=train.to_csv(index=False)
)
blob_gcs_3.upload_from_string(
data=test.to_csv(index=False)
)
大体の流れはこの記事と同じです。データは、物件を新着順に5000件取得しました。
このデータのカラムは"name","time","bus","walk","price","room","area","age","address"となっており、内容はそれぞれ以下のようになっています。
カラム名 | 説明 |
---|---|
name | 物件名 |
time_to | 駅までの時間 |
walk | 駅までの移動手段が徒歩かどうか(0,1表記) |
bus | 駅までの移動手段がバスかどうか(0,1表記) |
price | 賃貸価格(万円) |
room | 部屋の間取り(1LDKなど) |
area | 部屋の面積($m^2$) |
age | 築年数 |
address | 住所 |
time_to_log | time_toのデータを対数変換したもの |
price_log | priceのデータを対数変換したもの※目的変数 |
area_log | areaのデータを対数変換したもの |
age_log | ageのデータを対数変換したもの |
また、上記のコードでは前処理も同時に行なっています。特に"time_to"、"price"、"area"、"age"のデータは右裾が長い分布になっていたので対数変換しました。
得られたデータは、トレーニングデータ:テストデータ=7:3に分割し(トレーニングデータ3500件、テストデータ1500件)、元のデータと合わせて3つのCSVファイルをGCSのバケットに移しました。
BigQueryでの線形回帰
まずはトレーニングデータを用いてモデルを実装します。今回は説明変数として"time_to_log","area_log","age_log","walk"の4つを使用していきたいと思います。"name"や"address"といった文字型のデータは使用しませんでした。また、移動手段を表すのカラムである"walk"と"bus"ですが、どちらのデータもone-hot表現であり、片方のカラムだけで移動手段が徒歩かバスかがわかるので今回は"walk"のみを使用しました。
以下のクエリでBigqueryで線形回帰ができます。
CREATE OR REPLACE MODEL `suumo_dataset.suumo_reg_model`
OPTIONS
(model_type='linear_reg',
enable_global_explain=TRUE) AS
SELECT
price_log AS LABEL,
time_to_log,
age_log,
area_log,
walk
FROM `バケット名.suumo_dataset.train_data`
;
CREATE OR REPLACE MODEL 'データセット名.モデル名' でモデルの名前を決めます。OPTIONSのmodel_typeには実装したいモデル名を渡します。enable_global_explainはTrueとしておくことで、あとで説明変数の寄与率を確認することができるようになります。
SELECT以下はよくあるSQL構文です。SELECTでは予測に使う説明変数と目的変数を選びます。目的変数は名前をLABELに変更する必要があります。
クエリを実行すると結果からモデルの詳細を見ることができます。
線形回帰を行った場合、次のような結果を見ることができます。
この結果から平均二乗誤差や決定係数を知ることができます。
次に、テストデータを用いてモデルの評価を行います。
SELECT
*
FROM
ML.EVALUATE(MODEL `suumo_dataset.suumo_reg_model`,
(
SELECT
price_log AS LABEL,
time_to_log,
age_log,
area_log,
walk
FROM
`バケット名.suumo_dataset.test_data`
))
モデルの評価の際はFROM句にML.EVALUATE(MODEL データセット名.モデル名
)を入れる必要があります。
このクエリを実行すると次のような結果が表示されます。
これにより、テストデータを用いた時の平均二乗誤差や決定係数を知ることができます。
決定係数を見ると、トレーニングデータでは0.8035、テストデータでは0.7913なので、ほんの少し過学習していることがわかります。
モデルを用いて値を予測する際は次のクエリを実行します。
SELECT
*
FROM
ML.PREDICT(MODEL `suumo_dataset.suumo_reg_model`,
(
SELECT
time_to_log,
age_log,
area_log,
walk
FROM
`バケット名.suumo_dataset.test_data`
))
先ほどのクエリのEVALUATEをPREDICTに変え、SELECT句に説明変数を渡すことで予測が行えます。
predicted_labelが予測結果になります。
また、以下のクエリを実行すると、モデルの予測だけでなく、その予測に対する説明変数の寄与率を確認することができます。
SELECT
*
FROM
ML.EXPLAIN_PREDICT(MODEL `suumo_dataset.suumo_reg_model`,
(
SELECT
area_log,
time_to_log,
walk,
age_log
FROM
`バケット名.suumo_dataset.test_data`
))
上の結果では、行1の予測は"age_log"の数値が最も予測に使われていることがわかります。
モデル全体の寄与率を見るには次のクエリを実行します。
SELECT
*
FROM
ML.GLOBAL_EXPLAIN(MODEL `suumo_dataset.suumo_reg_model`)
;
この結果から、モデル全体では"area_log"が最も予測に寄与しており、次いで"age_log","time_to_log","walk"であることがわかりました。
まとめ
はじめてBigQueryに触れてみましたが、一番苦労したのはGCPの設定回りでした。GCPでの作業に慣れるためにももう少し勉強する必要があると感じました。
Bigqueryの利点として、クエリの処理が早いのとSQL(のような構文)で機械学習を行えるといったことが挙げられると思います。しかし、私の場合、普段pythonを中心に使っていてSQLをあまり使っていなかったので、少しやりにくい部分はありました。
大規模なデータを扱うときはBigQueryを使う必要が出てくると思うので、SQLでの操作にも慣れていきたいと思います。
参考