# 房價預測精進交接文件
---
## 執行環境
### 系統版本
```bash
$ uname -a
Linux ED716-ESC4000-G4 5.4.0-60-generic #67~18.04.1-Ubuntu SMP Tue Jan 5 22:01:05 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
$ python --version
Python 3.9.1
$ mysql --version
mysql Ver 8.0.28-0ubuntu0.20.04.3 for Linux on x86_64 ((Ubuntu))
```
### PYTHON 套件版本
```
torch == 1.9.0
dgl == 0.7.1
networkx == 2.6.3
numpy == 1.21.2
pandas == 1.3.4
Pillow == 8.3.2
scipy == 1.7.1
tqdm == 4.62.3
PyMySQL == 1.0.2
```
- 關於 ==dgl== (Deep Graph Library) 套件安裝方式,請參考官方[專案](https://github.com/dmlc/dgl)與[文件](https://docs.dgl.ai/index.html)
## 專案目錄架構
```
- ReGram
- data
- open_data
- raw_data
grepRegionRealestate.sh
grepAllRegionsRealestate.sh
- models
__init__.py
layers.py
models.py
- preprocessing
constructDataset.py
constructGraph.py
getGraphMaterial.py
preprocessing.py
preprocessUtil.py
Readme.md
reportScore.py
train.py
train_script.sh
inference.py
```
### 檔案資料意義
| 路徑 | 意義 | 補充說明 |
| -------- | -------- | -------- |
| ./data/open_data | 存放政府公開資料,包含人口密度、稅收等 | |
| ./data/raw_data | 存放實價登錄原始資料與過濾特定縣市腳本 | 過濾縣市腳本將用於「資料前處理 步驟一」|
| ./data/raw_csv | 「資料前處理 步驟二」的產物,紀錄各 transaction 的資訊 | 分成兩種檔案。第一種後綴為 "constr_yearmon.csv",紀錄 transaction 與其建立完成年月;第二種後綴為 "trade_datetime",紀錄 transaction 與建立 edge 會用到的特徵 |
| ./data/{City}\_{BuildingType} | 存放該縣市之建物型態相關生成資料 | 路徑下,四個日期組成的 directory 為訓練與測試時會用到的 graph,以及沒有鄰居而排除的資料比例;"{City}\_{Building}.csv" 為 transaction 詳細資料,其餘檔案皆為中間產物。若空間不夠,可將目錄以外的 ".csv" 與 ".pkl" 檔刪除,這些檔案在訓練測試時不會用到 |
| ./infer_output | 測試之後的輸出存放路徑 | 其下檔案為 ".pkl" 檔,存放 testing data 中,tid 與所預測價值 |
| ./log | 存放訓練過程,各 metric 數值 | ".csv" 檔內欄位為每一 epoch 後所得到的分數 |
| ./report | 存放訓練完多次之後的統計結果 | 路徑名稱中,第一段代表「縣市」,第二段代表「建物型態」,第三段代表「模型種類」 |
| ./weight | 存放訓練過程中的模型權重 | |
---
## 資料前處理
### 1. 過濾出特定縣市資料
目的:從完整 data 中過濾出特定縣市的資料,以便加快後面處理速度
#### 方法一:針對單一縣市資料
執行下述指令,抓出特定縣市的資料
```bash
cd data/raw_data
./grepRegionRealestate.sh ${City}
```
其中 ```${City}``` 可以是下列任一值
```
'臺北市', '臺中市', '基隆市', '臺南市', '高雄市', '新北市', '宜蘭縣', '桃園市', '嘉義市', '新竹縣', '苗栗縣', '南投縣', '彰化縣', '新竹市', '雲林縣', '嘉義縣', '屏東縣', '花蓮縣', '臺東縣', '金門縣', '澎湖縣'
```
#### 方法二:針對全部縣市資料
執行下述指令,針對全台縣市,抓出該縣市資料
```bash
cd data/raw_data
./grepAllRegionsRealestate.sh
```
---
### 2. 進行初步 Feature Engineering 與得到 Transaction Records
目的:將特定縣市資料進行初步前處理,包含合併外部公開資料 (e.g. 稅務資料、人口密度資料等);並將建立 graph 之資訊匯出成 csv 檔,以便後續利用 SQL 處理資料
#### 方法一:針對單一縣市資料
執行下述指令,對特定縣市進行前處理
```bash
cd preprocessing
python3 preprocessing.py ${City} ${BuildingType}
```
其中 ```${City}``` 可參考步驟一所列縣市,```${BuildingType}``` 可為下列任一值
```
'大樓', '公寓', '透天厝'
```
#### 方法二:針對全部縣市資料
執行下述指令,針對全台縣市,對該縣市進行前處理
```bash
cd preprocessing
python3 preprocessing.py ALL ${BuildingType}
```
其中 ```${BuildingType}``` 請參考步驟二方法一所列值
- 注: 由於方法二讀取的是所有資料,在讀取檔案上可能會需要比較多時間
---
### 3: 利用 MySQL 建立交易房地產鄰居組合
目的:將各縣市房地產交易紀錄兩兩配對,篩選出符合條件的組合並輸出。為求處以效率,這邊我們將使用 MySQL 來進行篩選。
執行之前請先編輯 "preprocessing/constructGraph.py" 開頭的 db_setting
#### 方法一:針對單一縣市資料
執行下述指令,對特定縣市進行處理
```bash
cd preprocessing
python3 constructGraph.py ${City} ${BuildingType}
```
#### 方法二:針對全部縣市資料
執行下述指令,針對全台縣市,對該縣市進行處理
```bash
cd preprocessing
python3 constructGraph.py ALL ${BuildingType}
```
#### ==注意事項==
1. 由於會需要匯入本地資料,可能會需要事先進入 MySQL 界面執行 "SET GLOBAL local_infile=1"
2. 此步驟相關操作都是會額外產生一 database,程式完成後會自動刪除該 database。若執行期間 database 名稱已存在,或是先前執行發生錯誤導致該 database 沒有正常刪除,請先手動進入 MySQL 刪除該 database,或是將程式開頭的 "dbName" 修改成其他值之後,再執行此步驟
3. 若需要調整 graph 的 edge 條件,可自行在程式開頭更改目前預設參數。參數解釋如下
- 鄰居物件條件
| feature 名稱 | 代表意思 |
| -------- | -------- |
| mon_diff_min | 最短交易時間差(含) |
| mon_diff_max | 最長交易時間差(含) |
| dist_diff_max | 最遠房地產距離 |
| build_type | 建物型態是否需相同 |
| small_object | 物件之「建物移轉總面積」是否需同時 小於/大於等於 13 坪 |
| main_target | 物件主要用途是否需相同 |
| shop_ind | 店家指標是否需相同 |
| firstfloor_ind | 一樓指標是否需相同 |
| house_age_diff_max | 最大屋齡差(含) |
- 同社區之物件條件
| feature 名稱 | 代表意思 |
| -------- | -------- |
| dist_diff_max | 房地產最遠距離 |
---
### 4. 產生建立 Graph 所需相關檔案
目的:根據房地產的特性與鄰居關係,產生建立 "community-to-transaction", "transaction-to-community", "community-to-community" 以及刪除過多在同一個社區的 "transaction-to-transaction" 檔案
#### 方法一:針對單一縣市資料
執行下述指令,對特定縣市進行處理
```bash
cd preprocessing
python3 getGraphMaterial.py ${City} ${BuildingType}
```
#### 方法二:針對全部縣市資料
執行下述指令,針對全台縣市,對該縣市進行處理
```bash
cd preprocessing
python3 getGraphMaterial.py ALL ${BuildingType}
```
#### ==注意事項==
1. 在此步驟可在程式最上方手動調整一些 graph construction 的規則,條件如下
| Feature Name | Meaining |
| -------- | -------- |
| PoIDist | 在得到 community-to-community 時,會用到該 community 下 transactions 的平均 PoI feature。 此參數的意思即為要取多遠的距離 (e.g. 方圓100公尺以內的某 PoI 個數) (預設為 1000 公尺) |
| CommunityEdgeRatio | 在得到 community-to-community 時,會計算兩兩 community 的距離,並取前面一定比例的 community pair 建立 edges。此參數的意思即為所取的比例 (預設為 0.1%) |
| AtmostNumber | 在精鍊 transaction-to-transaction 時,同樣 community 下的 neighbor transaction 最多只會依照交易時間由近到遠取一定數量作為鄰居。該參數意思即為同一個 community 下的 neighbors 要取多少個 (預設為 5) |
---
### 5. 建立 Graph
目的:建立訓練與測試使用的 graph
#### 方法一:針對單一縣市資料
執行下述指令,對特定縣市進行處理
```bash
cd preprocessing
python3 constructDataset.py ${City} ${BuildingType}
```
#### 方法二:針對全部縣市資料
執行下述指令,針對全台縣市,對該縣市進行處理
```bash
cd preprocessing
python3 constructDataset.py ALL ${BuildingType}
```
#### ==注意事項==
1. 可再多下四個參數,作為切割 training, validation 與 testing 的時間,見下方例子 (預設為 '2015-7-1', '2021-1-1', '2021-4-1', '2021-7-1')
```bash
python3 constructDataset.py 臺北市 公寓 2017-5-1 2019-4-1 2020-9-1 2021-5-1
## Training Data: 2017/5/1 ~ 2019/3/31
## Validation Data: 2019/4/1 ~ 2020/8/31
## Testing Data: 2020/9/1 ~ 2021/4/30
```
2. 完成此步驟之後,會在 ```data/${City}_${BuildingType}/{Time}``` 下產生資料 (.pkl檔) 與統計數值 (non_neighbors_ratio)。其中對於統計數值,分母為==在該時間中,有多少筆 transactions 發生==,分子則為 ==有多少筆 transactions 沒有鄰居==,這些沒有鄰居的 transactions ==不會出現==在訓練與測試資料當中
3. 注意我們的資料前處理有做一些 normalization。首先對於「面積類型的數值」,我們會對其值取 log (92行~100行);再來對於數值型資料(包含取完 log 後的面積資料),我們會對其做 standard normalization (129行~130行)
---
## 模型訓練與測試
### 6. 訓練模型
#### 模式一:多次訓練
目的:對於同一種資料,以不同的 random seed 訓練五次,並輸出各自的成效與模型
```bash
./train_script.sh ${各種參數}
# ./train_script.sh --city 臺北市 --building_type 公寓 --cuda_id 0
```
#### 模式二:訓練一次模型
目的:對於某一種資料,只訓練一次
```bash
python3 train.py ${各種參數}
```
#### 參數列表
| 參數名稱 | 參數意義 | 預設值 |
| -------- | -------- | --- |
| city | 縣市名稱 | 臺北市 |
| building_type | 建物型態 | 大樓 |
| model_version | 模型版本,用於儲存結果名稱 | ReGram |
| cuda_id | 使用的 GPU 編號,不指定則使用 CPU 進行訓練與測試(若無 GPU 可忽略) | None |
| iter_idx | 用以標記訓練梯次 | None |
| epochs | 單一梯次訓練 epoch 數 | 50 |
| lr | Learning Rate | 0.001 |
| bs | Batch Size | 64 |
| kernel_num | Dynamic Predictor 中的 kernel 數量 | 8 |
| attn_heads_num | Neighbor Aggregator 與 Community Aggregator 中的 attention heads 數量 | 8 |
| object_dim | Object Embedding 的維度 | 256 |
| env_dim | Environment Embedding 的維度 | 256 |
| attn_dim | Neighbor Aggregator 中所使用的 embedding 維度 | 64 |
| t1 | Training data 的開始時間(意義同步驟五的第 1 個參數) | 2015-7-1 |
| t2 | Validation data 的開始時間(意義同步驟五的第 2 個參數) | 2021-1-1 |
| t3 | Testing data 的開始時間(意義同步驟五的第 3 個參數) | 2021-4-1 |
| t4 | Testing data 的結束時間(意義同步驟五的第 4 個參數) | 2021-7-1 |
| data_path | Data 存放路徑 | ./data |
| log_path | Log 存放路徑 | ./log |
| weight_path | Weight 存放路徑 | ./weight |
#### ==注意事項==
1. 因 GPU 資源的分配問題,這邊只支援單一縣市的訓練。如需同時訓練多個縣市的模型,建議可以開啟 tmux 跑多個 process 同時訓練
---
### 7. 統計實驗成果
目的:針對實驗結果輸出 "MAPE", "Hit Rate 10%", "Hit Rate 20%" 三種分數,路徑預設為 "./report" 路徑下對應之縣市與模型版本
#### 方法一:針對單一縣市資料
執行下述指令,對特定縣市進行處理
```bash
python3 reportScore.py ${City} ${BuildingType}
```
#### 方法二:針對全部縣市資料
執行下述指令,針對全台縣市,對該縣市進行處理
```bash
python3 reportScore.py ALL ${BuildingType}
```
---
### 8. 實際執行
目的:根據訓練好的模型,將需測試的資料轉化成預測價值
#### 方法
```bash
python3 inference.py --data_path ${完整測試資料路徑} --weight_path ${完整 model weight 路徑} --save_path ${完整儲存檔案} --cuda_id ${欲使用 GPU ID}
## python3 inference.py --data_path ./data/Kaohsiung_Apartment/2015-7-1-2021-1-1-2021-4-1-2021-7-1/Kaohsiung_Apartment_testGraph.pkl --weight_path ./weight/Kaohsiung_Apartment/ReGram_1/epoch7.pkl --save_path ./infer_output/Kaohsiung_Apartment_20210401-20210701.pkl --cuda_id 1
```
#### ==注意事項==
1. 儲存檔案格式請用 ".pkl",檔案為 python dict 格式
| Key | Value |
| -------- | -------- |
| tid | 房地產 tid(通常僅作為分析用) |
| pred_price | 預測房價 |
| gold_price | 實際房價(若資料中沒有的話,該項會是 None 值) |
2. 請注意,這步驟所提供的路徑皆為「完整路徑」,請參考上面範例