2016年8月9日 星期二

[機器學習練習] [Machine Learning Practice] 用 Scikit 預測河川水位資料(四)


這是系列文最後一篇,

目前只剩下最後一個步驟也是最有成就感的步驟,

我們將用前面 training 好的機器來預測今年的兩次大雨事件!

開始!


------------------------------------------------------------------------------------------------------------

Step 7 : FORECASTING!

這次我們到防汛中心下載今年兩次雨量的資料。

為了加快速度這邊直接給下載連結 :)



下載完成後,讓我們繼續開始編碼。



因為一樣是政府的資料,記得也先看看有沒有'缺測'。

這次也一樣要把 Data 轉換成我們要的格式,

因為每次要預測 Data 時,都要經過一樣的轉換模式,

我這次決定寫一個方法來讀取 Data,程式碼如下:

def read_predict_file(file_name, start, stop_condition):

  df = pd.read_csv(file_name)
  df.columns = ['time2','waterlevel','rainfall']
  df.drop(['time2'], 1, inplace=True)
  df[df < 0] = 0
  df['daysvalue'] = df['rainfall'].rolling(window = stop_condition).mean()
  df.fillna(0.00000001, inplace=True)

  df = df[df['daysvalue']>0]
  #print (df)
  time_index = []
  startpoint = datetime(start[0],start[1],start[2],start[3],0,0)
  for i in range(len(df.index)):
     time_index.append(startpoint)
     startpoint += interval
  df['time'] = pd.Series(time_index, index = df.index)
  df.set_index('time', inplace=True)
  df[df < 0] = 0

  for i in range(2,8):
     df['rainfall'+str(i)] = df['rainfall'].shift(i)

  #df['waterlevel1'] = df['waterlevel'].shift(1)
  df['waterlevel2'] = df['waterlevel'].shift(2)
  #df['waterlevel3'] = df['waterlevel'].shift(3)

  df.dropna(inplace=True)

  return df

df_0610 = read_predict_file('0610rain.csv', [2016, 6, 10, 0], 48)
df_0706 = read_predict_file('0706rain.csv', [2016, 7, 6, 0], 48)

方法裡面,先讀取檔案,然後一樣整理 columns。

因為原始檔案的 time column 都會有 ‘上午/下午’ 讓我們無法使用資料

所以要丟掉 time columns 自己重新設置。

我每次抓的日子就是以大雨開始那天往後抓兩週,一共 14 天資料。

再利用紅色那行的條件,在超過兩天沒下雨後 drop data。

就這樣我們拿到可以用來預測資料的兩個 DataFrame, df_0610 & df_0706



接下來我們就要預測水位,並且把預測資料圖像化跟現實資料做比對。

在程式下方加上預測的方法,

def predict_df(df):
  # set up the feature set
  df_feature_predict_me = df[['waterlevel2','rainfall2','rainfall3','rainfall4','rainfall5','rainfall6','rainfall7']]

  X_predict_me = np.array(df_feature_predict_me)
  #X_predict_me = preprocessing.scale(X_predict_me)

  forecast_set = clf.predict(X_predict_me)

  df['predict_wl'] = pd.Series(forecast_set, index = df.index)

predict_df(df_0610)
df_0610[['waterlevel','predict_wl']].plot(ax= ax3, linewidth = 1)
ax3.set_ylabel('0610_Waterlevel')

predict_df(df_0706)
df_0706[['waterlevel','predict_wl']].plot(ax= ax4, linewidth = 1)
ax4.set_ylabel('0706_Waterlevel')

plt.show()

就這樣,我們拿到 df_0610['predict_wl'] 和 df_0706['predict_wl']

這兩欄就是我們小機器學出來的資料,我們把他們分別放進 ax3 和 ax4 和 waterlevel 做比較。

一樣在終端機目標資料夾中輸入

    python DBriver.py

應該會得到下面這張可愛的圖,

























到這邊勉勉強強算大功告成了!

有些波峰拉近看後,是馬後砲波峰,但絕大部分都確實有達到預測能力!

------------------------------------------------------------------------------------------------------------

Step 8 : 誤差值與結果討論

這邊我們要分析一下到底這樣的結果有沒有意義,有意義的原因在哪,

以及要如何把誤差數值化。

在 matplotlib 中,我們試著把每個波峰拉開看看,到底我們預測了什麼。

以下取 6/10 號大雨的前兩個波峰放大來討論一下結果。















點 1 與紅線交點為我們在 22:00 時預測的 00:00 點水位高度,

點 1 與藍線交點為我們在 00:00 點 W 水位站的真實水位高度。

這邊肥了,誤差很小,意思是說晚上十點我們就會知道 12 點水位是 2.2 公尺高。

點 2 這邊就居掉了,誤差高達一公尺!

回頭看了一下 Data 發現凌晨一點的瞬時雨量是 27 毫米

這已經是超大豪雨的級別了,但我們在晚上 12 點的時候,

並不會知道接下來一小時,會有這樣大的雨量,所以錯估了兩小時後的水位。

接下來大家可以自己看,基本上預測都算准,但只要有瞬間超大豪雨,

因為 input feature 都沒有未來預測資料,所以會容易有大誤差。



誤差值方面,參考湄公河的文章,我們採用 root mean square error 來計算,

在 predict_df 方法中,再加入以下程式碼,計算 RMSE,

  error_square_sum = 0
  for i in range(len(df.index)):

     error_square = ((df['predict_wl'][i] - df['waterlevel'][i]) ** 2)
     error_square_sum += error_square

  rmse = sqrt(error_square_sum / len(df.index))
  print ('root mean square error', rmse)

在終端機目標資料夾中輸入

    python DBriver.py

得出結果如下:









大概可以解釋成, 6 / 10 號整個大雨區間,我們平均預測誤差是 15 公分

7 / 6 號尼伯特颱風則是 25 公分左右,其實算是很精準了。

------------------------------------------------------------------------------------------------------------

心得:

這邊紀錄一下可以讓 model 改良的方法,

在結果與討論時我們有發現,瞬間超大豪雨的問題,

因為我們沒有辦法預先知道未來降雨量

所以在改良方面,有以下兩個可以做:

1. 取氣象局的未來雨量預測當做 feature 去 predict,當然 training 時用真實 Data 就可以。

2. training step 盡量不要匯入沒有下雨時的資料,因為我們只在乎水位高漲的狀態。

第二步,我已經做過,但效果似乎不大... ...

下面再放上預測一小時的資料,算是非常非常精準。


















右邊兩張小圖,分別是 6 / 10 號大雨事件的兩次大雨區間。

############ Github link ############

------------------------------------------------------------------------------------------------------------

我覺得機器學習是非常好用的一項知識、技能、工具

希望台灣可以每一個人都會機器學習,然後成為超強小國!

------------------------------------------------------------------------------------------------------------

Reference

[1] " Forecasting Time Series Water Levels on Mekong River Using Machine Learning Models ", 10.1109/KSE.2015.53

[2] '' Application of Support Vector Machine in Lake Water Level Prediction " , http://ascelibrary.org/doi/abs/10.1061/(ASCE)1084-0699(2006)11%3A3(199)

[3] " Integrating Support Vector Regression and a geomorphologic Artificial Neural Network for daily rainfall-runoff modelling", http://www.sciencedirect.com/science/article/pii/S1568494615006304

[4] Scikit-Learn, http://machine-learning-python.kspax.io

[5] Scikit-Learn, http://scikit-learn.org/stable/

[6] 水文資訊網, http://gweb.wra.gov.tw/hyis/index.aspx

[7] 水利署防災資訊服務網, http://fhy.wra.gov.tw/fhy/

[8] 典寶溪排水治理計畫 - 經濟部水利署


2 則留言:

  1. 你好,我在read_predict_file裡
    df = pd.read_csv(file_name)
    df.columns = ['time2','waterlevel','rainfall']
    這裡會有錯誤:
    ValueError: Length mismatch: Expected axis has 285 elements, new values have 3 elements

    這和python版本有關嗎? 我是 v3.6,請教該如何修改比較好!!!

    回覆刪除
    回覆
    1. 哭哭,這太久之前寫的,東西都亂七八糟...
      我剛也有試出這個問題,主要是我寫法不好才讓 read_csv 格式跑掉,
      你改成這樣,
      df = pd.read_csv(file_name)
      cols = []
      for c in df.columns[:3]:
      cols.append(c)
      df = df[cols]
      df.columns = ['time2','waterlevel','rainfall']

      刪除