本文為你介紹,如何從 Waze 交通事件開放數據中,利用序列模型找到規律,進行分類預測。以便相關部門可以未雨綢繆,提前有效干預可能發生的嚴重擁堵。
尋找
之前在《文科生如何理解循環神經網絡(RNN)?》一文中,我為你講解過循環神經網絡的含義。《如何用 Python 和循環神經網絡做中文文本分類?》一文,我又為你介紹了如何用循環神經網絡對文本做分類。
我不希望給你一種錯誤的簡單關聯,即“循環神經網絡只能用來處理文本數據”。
事實上,只要是序列數據,你都可以考慮一下循環神經網絡。
我一直打算找個其他序列數據的樣例,給你展示循環神經網絡的更多應用場景。
但是這個數據不太好選擇。
目前一個熱門的應用場景,就是金融產品的價格預測。
每時每秒,金融產品的價格都在變動。把它匯集起來,是個典型的序列數據。
但是我一直不看好這種應用。因為金融產品的定價,應該是面向未來的。基于歷史價格信息尋找波動規律,并對未來價格進行預測,實際上如同看著后視鏡開車一般危險。
但是,還有很多人依然樂此不疲地嘗試。很多時候,他們也能嘗到成功的甜頭。
這是為什么?
原因在于,金融市場的參與者,并非理性的機器,而是由人組成的群體。從行為金融學的角度來看,進化給人類思考與行為帶來了一些“快捷方式”,你可以利用它們從中漁利。
陸蓉教授的《行為金融學》欄目,對此有詳細介紹。
例如,人們追漲殺跌,認為歷史會重演;
例如,吸引大眾關注到事件,總會帶來買入;
例如,人們會傾向于投資于自己熟悉的標的;
例如,人們會購買下跌的已持倉標的,來攤薄成本。
……
如果沒有大風浪,這種對市場參與者行為規律的洞察,確實可以幫你賺錢。你可以從價格的歷史波動中,挖掘出這些規律的影響。但是這對沒有模型可用的人來說,不公平。教你建模,就如同教你考試作弊。
如果遇到黑天鵝事件,其影響大概率會超過市場參與者行為偏誤帶來的歷史價格波動規律。那么你,可能會因為應用模型,而遭遇虧損。你大約不會認為這是自己的錯誤,而直接把我當做騙子,朝我扔雞蛋。
理性權衡后,我決定不用金融產品價格趨勢分析,作為循環神經網絡的應用樣例。
其他開放的序列數據,當然也有很多。例如共享單車租用數據、氣溫變化數據等。
不過這些應用,一來別人都寫過了,不新鮮。二來,氣溫變化,你看天氣預報就好了。共享單車租用數量……你真的關心這里的規律嗎?
正在我猶豫的時候,一次偶然的機會,我接觸到了一個新的序列數據樣例——交通事件數據。我覺得,把它作為應用案例分享給你,可能更合適一些。
比賽
拿到這個數據,是因為我參與了一次編程馬拉松(hackathon)比賽。
比賽在 Frisco 的 UNT Inspire Park 舉辦。從早上8點開始,一直到晚上9點多才結束。中間可以自由吃免費提供的點心和水果,也可以到院子里曬曬太陽放放風。大家還可以自由交流和組隊。
主辦方為參賽者提供了若干種開放數據,也提了一些問題供大家參考解答。當然,實際參賽的時候,你也可以自己擬定新的題目。
這其中,就包括了 Waze 數據。
我在中國開車,平時用的都是高德導航,對于 Waze 這款 App 不大熟悉。
簡而言之,這個 Waze 應用除了提供一般的導航功能之外,還有一個類似于眾包的功能——讓司機們自由提交路況信息。
這樣一來,Waze 就利用群體智慧形成了一個眼觀六路耳聽八方的巨大網絡,隨時依據用戶提供的情況,匯總成實時交通參考。并且匯報給用戶,以便于大家調整自己的行車路線。
我覺得最有用的特點是,在堵車的時候,你可以了解到前面究竟發生了什么。其他導航也有實時交通狀況提示,但是你對前面的情況一無所知。道路半幅施工?交通事故?
信息的對稱,可以在很大程度上,讓司機避免焦慮。
Waze 從幾年前開始,就和政府部門合作,進行數據開放共享。這樣一來,政府可以通過 Waze 的數據了解交通實時狀況,對于問題進行快速的響應處理;與此同時, Waze 用戶也因為可以獲取整合其他相關類型的政府開放數據(例如道路規劃等),更加有效合理安排出行。
這次比賽,主辦方提供的數據,是 DFW (達拉斯-沃斯堡都會區)區域,11月1日到29日的 Waze 交通事件(Incidents)開放數據,這是政府開放數據的一部分。這些數據基本都是來自于 Waze 用戶的提交。
原始的數據,接近 300 MB。每一條事件信息,都包含了提交的經緯度,以及時間。因此在探索性數據分析階段,我做了幾個可視化圖形。
這是我當天跟新認識的編程高手 Jesse 學的 QGIS 分析結果。
看看圖上的點,每一個都對應一次事件匯報。這叫一個密密麻麻啊。
因為 QGIS 初學,用得不熟,我還是用 Python 進行了分類繪圖。
這只是前 3000 條數據中部分類型的可視化。其中紅色代表交通擁堵,黃色代表事故發生,藍色代表有車停在了路肩上。
可以看到,紅色的數據量最大。這說明交通擁堵是個大問題。
我把全部的數據都拿了出來,提煉出包含的事件類型,包括以下這些類:
我看到,其中單是交通阻塞,也是分為若干級別的。其中最嚴重的,分別是“大型交通擁堵”(large traffic jam)和“超大型交通擁堵”(huge traffic jam)。
于是,我把所有這兩種嚴重交通擁堵事件,合并成一個集合;其他剩余事件,作為另一個集合。
對于每一個嚴重擁堵事件,我追溯30分鐘,把之前同一條道路上,發生的事件,按照順序存成一個列表。這樣的列表,有987個;但是,其中有一些,是驟然發生的,30分鐘的區間里面,沒有任何其他事件作為先兆。這樣的空列表,我進行了清除。剩下了861個有效序列。
同樣,從剩余事件集合中,我們隨機找到了861個非空有效序列。這些序列,后續緊隨事件,都不是嚴重擁堵。
我們對嚴重擁堵之前30分鐘的事件序列,標記為1;對于非嚴重擁堵之前30分鐘的事件序列,標記為0。
于是,我們就把問題轉換成了,能否利用事件序列,進行分類,預測后續是否會發生嚴重擁堵。
靠著這個模型,我們團隊(UNT IIA lab代表隊,其實不過就是我和春迎倆人,團隊昵稱 watch-dumpling )在這次比賽中,獲得第一名。
這是 HackNTX 官網的報道(http://t.cn/EUbS9m5) 。
UNT 網站也正式發布了這則新聞(http://t.cn/EUbS127),于是我周圍盡人皆知。我才剛拿到手的獎金,立即就因為請客被掃蕩一空了。
奪冠純屬是個意外,幸運占得比重很大。但是我覺得我們做的這個模型,還是有些應用價值的。
下面,我就以這組 Waze 交通事件數據,詳細給你講解一下,如何用 Python, Keras 和循環神經網絡,來實現這個序列數據分類模型。
環境
要運行深度學習,你需要有 GPU 或者 TPU 的支持,否則會累壞你的筆記本電腦的。Google Colab 是個不錯的實驗平臺,可以讓你免費使用 TPU 來進行深度學習訓練。你可以閱讀《如何免費云端運行Python深度學習框架?》一文,查詢更為詳細的介紹。
這里,請你使用 Chrome 瀏覽器,點擊這個鏈接,安裝一個插件 Colaboratory 。
把它添加到 Google Chrome 之后,你會在瀏覽器的擴展工具欄里面,看見下圖中間的圖標:
然后,請到本范例的github repo 主頁面。
打開其中的demo.ipynb文件。
點擊 Colaboratory 擴展圖標。Google Chrome 會自動幫你開啟 Google Colab,并且裝載這個 ipynb 文件。
點擊上圖中紅色標出的“復制到云端硬盤”按鈕。Google 會為你新建一個屬于你自己的副本。
點擊菜單欄里面的“代碼執行程序”,選擇“更改運行時類型”。
在出現的對話框中,確認選項如下圖所示。
點擊“保存”即可。
下面,你就可以依次執行每一個代碼段落了。
注意第一次執行的時候,可能會有警告提示。
出現上面這個警告的時候,點擊“仍然運行”就可以繼續了。
如果再次出現警告提示,反勾選“在運行前充值所有代碼執行程序”選項,再次點擊“仍然運行”即可。
環境準備好了,下面我們來一步步運行代碼。
代碼
首先,我們讀入 Pandas 軟件包,以便進行結構化數據的處理。
importpandasaspd
這次還要讀入的一個軟件包,是 Python 中間進行數據存取的利器,叫做 pickle 。
importpickle
它可以把 Python 數據,甚至是許多組數據,一起存儲到指定文件。然后讀出的時候,可以完全恢復原先數據的格式。這一點上,它比用 csv 進行數據存儲和交換的效果更好,效率也更高。
下面我們從本文配套的 github 項目中,把數據傳遞過來。
!gitclonehttps://github.com/wshuyi/demo_traffic_jam_prediction.git
數據的下載,很快就可以完成。
Cloninginto'demo_traffic_jam_prediction'...remote:Enumeratingobjects:6,done.[Kremote:Countingobjects:100%(6/6),done.[Kremote:Compressingobjects:100%(4/4),done.[Kremote:Total6(delta0),reused3(delta0),pack-reused0[KUnpackingobjects:100%(6/6),done.
我們告訴 Jupyter Notebook ,數據文件夾的位置。
frompathlibimportPathdata_dir=Path('demo_traffic_jam_prediction')
打開數據文件,利用 pickle 把兩組數據分別取出。
withopen(data_dir/'data.pickle','rb')asf:[event_dict,df]=pickle.load(f)
先看其中的事件詞典event_dict:
event_dict
以下就是全部的事件類型。
{1:'roadclosedduetoconstruction',2:'trafficjam',3:'stoppedcarontheshoulder',4:'roadclosed',5:'other',6:'objectonroadway',7:'majorevent',8:'pothole',9:'trafficheavierthannormal',10:'roadconstruction',11:'fog',12:'accident',13:'slowdown',14:'stoppedcar',15:'smalltrafficjam',16:'stoppedtraffic',17:'heavytraffic',18:'minoraccident',19:'mediumtrafficjam',20:'malfunctioningtrafficlight',21:'missingsignontheshoulder',22:'animalontheshoulder',23:'animalstruck',24:'largetrafficjam',25:'hazardontheshoulder',26:'hazardonroad',27:'iceonroadway',28:'weatherhazard',29:'flooding',30:'roadclosedduetohazard',31:'hail',32:'hugetrafficjam'}
同樣,我們來看看存儲事件序列的數據框。
先看前10個:
df.head(10)
注意,每一行,都包含了標記。
再看結尾部分:
df.tail(10)
讀取無誤。
下面我們來看看,最長的一個序列,編號是多少。
這里,我們利用的是 Pandas 的一個函數,叫做idxmax(),它可以幫助我們,把最大值對應的索引編號,傳遞回來。
max_len_event_id=df.events.apply(len).idxmax()max_len_event_id
結果為:
105
我們來看看,這個編號對應的事件序列,是什么樣子的:
max_len_event=df.iloc[max_len_event_id]max_len_event.events
下面是長長的反饋結果:
['stoppedcarontheshoulder','heavytraffic','heavytraffic','heavytraffic','slowdown','stoppedtraffic','heavytraffic','heavytraffic','heavytraffic','heavytraffic','trafficheavierthannormal','stoppedcarontheshoulder','trafficjam','heavytraffic','stoppedtraffic','stoppedtraffic','stoppedtraffic','heavytraffic','trafficjam','stoppedcarontheshoulder','stoppedtraffic','stoppedtraffic','stoppedtraffic','heavytraffic','trafficheavierthannormal','trafficheavierthannormal','trafficheavierthannormal','trafficheavierthannormal','heavytraffic','stoppedtraffic','trafficheavierthannormal','pothole','stoppedcarontheshoulder','trafficjam','slowdown','stoppedtraffic','heavytraffic','trafficheavierthannormal','trafficjam','trafficjam','stoppedcarontheshoulder','majorevent','trafficjam','trafficjam','stoppedtraffic','heavytraffic','trafficheavierthannormal','stoppedcarontheshoulder','slowdown','heavytraffic','heavytraffic','stoppedcarontheshoulder','trafficjam','slowdown','slowdown','heavytraffic','stoppedcarontheshoulder','heavytraffic','minoraccident','stoppedcarontheshoulder','heavytraffic','stoppedcarontheshoulder','heavytraffic','stoppedtraffic','heavytraffic','trafficheavierthannormal','heavytraffic','stoppedcarontheshoulder','trafficheavierthannormal','stoppedtraffic','heavytraffic','heavytraffic','heavytraffic','stoppedcarontheshoulder','slowdown','stoppedtraffic','heavytraffic','stoppedcarontheshoulder','trafficheavierthannormal','heavytraffic','minoraccident','majorevent','stoppedcarontheshoulder','stoppedcarontheshoulder']
讀一遍,你就會發現,在超級擁堵發生之前,確實還是有一些先兆的。當然,這是由人來閱讀后,獲得的觀感。我們下面需要做的,是讓機器自動把握這些列表的特征,并且做出區別分類。
我們看看,這個最長列表的長度。
maxlen=len(max_len_event.events)maxlen
結果為:
84
這里的前導事件,還真是不少啊。
下面我們要做的,是把事件轉換成數字編號,這樣后面更容易處理。
我們使用以下的一個小技巧,把原先的事件詞典倒置,即變“序號:事件名稱”,為“事件名稱:序號”。這樣,以事件名稱查詢起來,效率會高很多。
reversed_dict={}fork,vinevent_dict.items():reversed_dict[v]=k
我們看看倒置的結果詞典:
reversed_dict
這是反饋結果:
{'accident':12,'animalontheshoulder':22,'animalstruck':23,'flooding':29,'fog':11,'hail':31,'hazardonroad':26,'hazardontheshoulder':25,'heavytraffic':17,'hugetrafficjam':32,'iceonroadway':27,'largetrafficjam':24,'majorevent':7,'malfunctioningtrafficlight':20,'mediumtrafficjam':19,'minoraccident':18,'missingsignontheshoulder':21,'objectonroadway':6,'other':5,'pothole':8,'roadclosed':4,'roadclosedduetoconstruction':1,'roadclosedduetohazard':30,'roadconstruction':10,'slowdown':13,'smalltrafficjam':15,'stoppedcar':14,'stoppedcarontheshoulder':3,'stoppedtraffic':16,'trafficheavierthannormal':9,'trafficjam':2,'weatherhazard':28}
成功了。
下面我們編寫一個函數,輸入一個事件列表,返回對應的事件編號列表。
defmap_event_list_to_idxs(event_list):list_idxs=[]foreventin(event_list):idx=reversed_dict[event]list_idxs.append(idx)returnlist_idxs
然后,我們在剛才是找到的最長列表上,實驗一下:
map_event_list_to_idxs(max_len_event.events)
結果是這樣的:
[3,17,17,17,13,16,17,17,17,17,9,3,2,17,16,16,16,17,2,3,16,16,16,17,9,9,9,9,17,16,9,8,3,2,13,16,17,9,2,2,3,7,2,2,16,17,9,3,13,17,17,3,2,13,13,17,3,17,18,3,17,3,17,16,17,9,17,3,9,16,17,17,17,3,13,16,17,3,9,17,18,7,3,3]
看來功能實現上,沒問題。
讀入 numpy 和 Keras 的一些工具。
importnumpyasnpfromkeras.utilsimportto_categoricalfromkeras.preprocessing.sequenceimportpad_sequences
系統自動提示我們,Keras 使用了 Tensorflow 作為后端框架。
UsingTensorFlowbackend.
我們需要弄清楚,一共有多少種事件類型。
len(event_dict)
結果是:
32
因此,我們需要對32種不同的事件類型,進行轉換和處理。
我們把整個數據集里面的事件類型,都變成事件編號。
df.events.apply(map_event_list_to_idxs)
結果如下:
0[9,17,18,14,13,17,3,13,16,3,17,17,...1[2,10,3]2[2]3[2]4[2,2,2,2,2,2,2,9]5[3,2,17]6[3,2,17]7[2,15,2,17,2,2,13,17,2]8[17,2,2,16,17,2]9[17,2,2,16,17,2]10[17,16,17,2,17,3,17,17,16,17,16,18,...11[17]12[17]13[24,24]14[24,2,24,24,2]15[24,2,24,24,2]16[2,10,2,2,2,18,16,16,7,2,16,2,2,9...17[2,10,2,2,2,18,16,16,7,2,16,2,2,9...18[24,24,24,16,2,16]19[24,24,24,16,2,16]20[2,2]21[2,16,2]22[2,16,2]23[2,2]24[2,2]25[24,24]26[2,2]27[2,2,2,17]28[2,19,2]29[24]...831[9,9,9,2,9,9,17,2,9,17]832[3,3,3]833[2,9,2,17,17,2]834[3,3,17,3,13,3,3,23,9,3,3,25,3,3]835[3,17,9,14,9,17,14,9,2,9,3,2,2,17]836[2]837[17,2,16,3,9,17,17,17,13,17,9,17]838[13,17,17,3,3,16,17,16,17,16,3,9,1...839[2]840[3]841[2]842[17,17,17,3,17,23,16,17,17,3,2,13,...843[3,3]844[2]845[2,17,2,2,2,2,2,17,2,2]846[7,17,3,18,17]847[3,3,3]848[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,...849[2,2]850[2,2,2,2,2,2,2,2,2,2,2,13,3,2]851[2,2,2]852[16,2,16]853[3,16,5,3,17,3,16,9,3,2,17]854[16]855[3,3,3,3,3,3,3,3,2,13,3,6,3,6,3,...856[17,17,17,2,3,2,2,2,2,2]857[2,2]858[2,2,9,17,2,2]859[17,3,2,2,2,2,2,2]860[17,3,3,17,3,17,2,3,18,14,3,3,16,...Name:events,Length:1722,dtype:object
現在,作為人類,我們確實是看不清楚,列表里面的事件都是什么了。好在計算機對于數字,更加喜聞樂見。
我們把該列表,起名為 sequences ,并且顯示前5項內容。
sequences=df.events.apply(map_event_list_to_idxs).tolist()sequences[:5]
下面是結果:
[[9,17,18,14,13,17,3,13,16,3,17,17,16,3,16,17,9,17,2,17,2,7,16,17,17,17,17,13,5,17,9,9,16,16,3],[2,10,3],[2],[2],[2,2,2,2,2,2,2,9]]
注意,第一行,明顯比后幾行都要長。
對于輸入序列,我們希望它的長度都是一樣的。因此,下面我們就用最長的序列長度作為標準,用 0 來填充其他短序列。
data=pad_sequences(sequences,maxlen=maxlen)data
這是結果:
array([[0,0,0,...,16,16,3],[0,0,0,...,2,10,3],[0,0,0,...,0,0,2],...,[0,0,0,...,17,2,2],[0,0,0,...,2,2,2],[0,0,0,...,3,3,2]],dtype=int32)
注意,所有的0,都補充到了序列的最前端。序列都一樣長了。
下面,我們把全部的分類標記,存儲到 labels 變量里面。
labels=np.array(df.label)
后面,我們有好幾個函數,需要用到隨機變量。
為了咱們運行結果的一致性。我這里指定隨機種子數值。你第一次嘗試運行的時候,不要動它。但是后面自己動手操作的時候,可以任意修改它。
np.random.seed(12)
好了,下面我們“洗牌”。打亂數據的順序,但是注意序列和對應標記之間,要保持一致性。
indices=np.arange(data.shape[0])np.random.shuffle(indices)data=data[indices]labels=labels[indices]
然后,我們取 80% 的數據,作為訓練;另外 20% 的數據,作為驗證。
training_samples=int(len(indices)*.8)validation_samples=len(indices)-training_samples
我們正式劃分訓練集和驗證集。
X_train=data[:training_samples]y_train=labels[:training_samples]X_valid=data[training_samples:training_samples+validation_samples]y_valid=labels[training_samples:training_samples+validation_samples]
看看訓練集的內容。
X_train
結果為:
array([[0,0,0,...,15,15,3],[0,0,0,...,0,2,2],[0,0,0,...,0,0,16],...,[0,0,0,...,2,15,16],[0,0,0,...,2,2,2],[0,0,0,...,0,0,2]],dtype=int32)
注意由于我們補充了“0”,作為填充,因此原先的32種事件類型的基礎上,又加了一種。
這就是我們新的事件類型數量:
num_events=len(event_dict)+1
我們使用嵌入層,把事件標號,轉換成一系列數字組成的向量。這樣,可以避免模型把事件序號,當成數值型數據來處理。
這里,我們指定每一個標號,轉換成 20 個數字組成的向量。
embedding_dim=20
利用事件類型數量,和事件向量長度,我們隨機構造初始的嵌入矩陣。
embedding_matrix=np.random.rand(num_events,embedding_dim)
下面我們搭建一個循環神經網絡模型。其中的 LSTM 層,包含了32位輸出數字。
fromkeras.modelsimportSequentialfromkeras.layersimportEmbedding,Flatten,Dense,LSTMunits=32model=Sequential()model.add(Embedding(num_events,embedding_dim))model.add(LSTM(units))model.add(Dense(1,activation='sigmoid'))
這里,我假設你已經看過了《如何用 Python 和循環神經網絡做中文文本分類?》一文,所以就不對細節進行講述了。如果你沒有看過,或者已經遺忘,可以點擊這個鏈接復習一下。
如果你對 Keras 的使用方法還不熟悉,我再次向你推薦 Fran?ois Chollet 的《Deep Learning with Python》。
下面,是處理其中的嵌入層參數。我們直接把剛才隨機生成的嵌入矩陣挪進來。而且,不讓模型在訓練中對嵌入層參數進行修改。
model.layers[0].set_weights([embedding_matrix])model.layers[0].trainable=False
下面,我們開始訓練。并且把模型運行結果保存起來。
model.compile(optimizer='rmsprop',loss='binary_crossentropy',metrics=['acc'])history=model.fit(X_train,y_train,epochs=50,batch_size=32,validation_data=(X_valid,y_valid))model.save("mymodel_embedding_untrainable.h5")
可以看到,因為有 TPU 的強力支持,程序在歡快地運行中。
訓練過程結束之后,我們利用 matplotlib 繪圖功能,看一下訓練中,準確率和損失值的變化。
importmatplotlib.pyplotaspltacc=history.history['acc']val_acc=history.history['val_acc']loss=history.history['loss']val_loss=history.history['val_loss']epochs=range(1,len(acc)+1)plt.plot(epochs,acc,'bo',label='Trainingacc')plt.plot(epochs,val_acc,'b',label='Validationacc')plt.title('Trainingandvalidationaccuracy')plt.legend()plt.figure()plt.plot(epochs,loss,'bo',label='Trainingloss')plt.plot(epochs,val_loss,'b',label='Validationloss')plt.title('Trainingandvalidationloss')plt.legend()plt.show()
這是準確率變化曲線。
可以看到,效果還是不錯的。因為我們數據中,不同標記各占一半。因此如果構建一個 dummy model 作為標準線的話,對所有的輸入都猜測0或者1,準確率應該只有50%。
這里的準確率,已經達到了65%-75%之間,證明我們的模型是有意義的。只不過,抖動比較厲害,穩定性差。
這是損失值變化曲線。
這個圖看起來,就不是很美妙了。因為雖然訓練集上面的損失值一路下降,但是驗證集上,這個效果并不是很明顯,一直劇烈波動。
看到結果,不是最重要的。關鍵是我們得分析出目前遇到問題,原因是什么。
注意我們前面使用了嵌入矩陣。它隨機生成,卻又沒有真正進行訓練調整,這可能是個問題。
因此,我們這里再次構建和跑一下模型。唯一改動的地方,在于讓嵌入矩陣的參數也可以隨著訓練進行自動調整。
fromkeras.modelsimportSequentialfromkeras.layersimportEmbedding,Flatten,Dense,LSTMunits=32model=Sequential()model.add(Embedding(num_events,embedding_dim))model.add(LSTM(units))model.add(Dense(1,activation='sigmoid'))
注意這里的差別,就是trainable設置為真值。
model.layers[0].set_weights([embedding_matrix])model.layers[0].trainable=True
構建模型,再次運行。
model.compile(optimizer='rmsprop',loss='binary_crossentropy',metrics=['acc'])history=model.fit(X_train,y_train,epochs=50,batch_size=32,validation_data=(X_valid,y_valid))model.save("mymodel_embedding_trainable.h5")
繪圖看看。
importmatplotlib.pyplotaspltacc=history.history['acc']val_acc=history.history['val_acc']loss=history.history['loss']val_loss=history.history['val_loss']epochs=range(1,len(acc)+1)plt.plot(epochs,acc,'bo',label='Trainingacc')plt.plot(epochs,val_acc,'b',label='Validationacc')plt.title('Trainingandvalidationaccuracy')plt.legend()plt.figure()plt.plot(epochs,loss,'bo',label='Trainingloss')plt.plot(epochs,val_loss,'b',label='Validationloss')plt.title('Trainingandvalidationloss')plt.legend()plt.show()
這次的準確率曲線,看起來好多了。驗證集波動沒有這么劇烈,模型穩定性好了許多。而且,準確率的取值,也獲得了提升。后半程穩定在了75%以上。這樣的模型,就有應用價值了。
但是我們看看損失值曲線,可能就不這么樂觀了。
注意從半程之后,訓練集和驗證集的損失值變化,就發生了分叉。
這是典型的過擬合(over-fitting)。
發生過擬合,主要原因就是相對于復雜的模型,訓練數據不夠用。
這時候,要么增加訓練數據,要么降低模型復雜度。
立即增加數據,不太現實。因為我們手中,目前只有那29天里積攢的數據。但是降低模型復雜度,是可以利用 Dropout 來嘗試完成的。
Dropout 的實現機理,是在訓練的時候,每次隨機把一定比例的模型中神經元對應權重參數,設置為0,讓它不起作用。這樣,模型的復雜度,就會降低。
下面,我們輕微修改一下,在 LSTM 層上,加入dropout=0.2, recurrent_dropout=0.2這兩個參數。
fromkeras.modelsimportSequentialfromkeras.layersimportEmbedding,Flatten,Dense,LSTMunits=32model=Sequential()model.add(Embedding(num_events,embedding_dim))model.add(LSTM(units,dropout=0.2,recurrent_dropout=0.2))model.add(Dense(1,activation='sigmoid'))
依然保持嵌入層可以被訓練。
model.layers[0].set_weights([embedding_matrix])model.layers[0].trainable=True
再次運行。
model.compile(optimizer='rmsprop',loss='binary_crossentropy',metrics=['acc'])history=model.fit(X_train,y_train,epochs=50,batch_size=32,validation_data=(X_valid,y_valid))model.save("mymodel_embedding_trainable_with_dropout.h5")
繪制圖形的函數跟之前兩次完全一致。
importmatplotlib.pyplotaspltacc=history.history['acc']val_acc=history.history['val_acc']loss=history.history['loss']val_loss=history.history['val_loss']epochs=range(1,len(acc)+1)plt.plot(epochs,acc,'bo',label='Trainingacc')plt.plot(epochs,val_acc,'b',label='Validationacc')plt.title('Trainingandvalidationaccuracy')plt.legend()plt.figure()plt.plot(epochs,loss,'bo',label='Trainingloss')plt.plot(epochs,val_loss,'b',label='Validationloss')plt.title('Trainingandvalidationloss')plt.legend()plt.show()
這次的準確率曲線,看起來達到的數值,跟沒有加入 Dropout 的差不多。
然而,我們可以感受到訓練集和驗證集達到的準確率更加貼近。曲線更加平滑。
下面我們看看損失值曲線的變化。
這個曲線上,過擬合的去除效果就更為明顯了。可以看到訓練集和驗證集兩條曲線的波動基本保持了一致。這樣我們更可以確信,模型預測能力是穩定的,對外界新的輸入信息,適應性更好。
如果把咱們的模型放在交通管理部門那里,可以期望它根據 Waze 獲得的新序列數據,能以大約 75% 的準確率,預測嚴重交通擁堵的發生。這樣,交管部門就可以未雨綢繆,提前做出干預了。
用序列模型,欺負金融市場的散戶,屬于零和博弈。然而這種在交通管理上的應用,大概更能造福社會,體現科技的價值吧。
小結
通過本文的學習和實際上手操作,希望你已了解了以下知識點:
不只是文本,其他序列數據,也可以利用循環神經網絡來進行分類預測。
對定類數據(categorical data)進行嵌入表示,如果用隨機數初始,那么在建模過程中把嵌入層一起訓練,效果會更好。
數據量不夠的情況下,深度學習很可能會發生過擬合。使用 Dropout ,可以降低過擬合的影響,讓模型具有更好的穩定性和可擴展性。
希望這篇文章,可以幫助你了解循環神經網絡的更多應用場景。在實際的工作和學習中,靈活運用它來處理序列數據的分類等任務。
-
神經網絡
+關注
關注
42文章
4806瀏覽量
102734 -
數據
+關注
關注
8文章
7239瀏覽量
90984 -
python
+關注
關注
56文章
4823瀏覽量
86156
原文標題:如何用Python和循環神經網絡預測嚴重交通擁堵?
文章出處:【微信號:rgznai100,微信公眾號:rgznai100】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
用matlab編程進行BP神經網絡預測時如何確定最合適的,BP模型
如何構建神經網絡?
改進人工蜂群算法優化RBF神經網絡的短時交通流預測模型

結合小波變換的LSTM循環神經網絡的稅收預測

評論