如何用 Python 和循環(huán)神經(jīng)網(wǎng)絡(luò)預(yù)測(cè)嚴(yán)重交通擁堵?

本文為你介紹盾舌,如何從 Waze 交通事件開放數(shù)據(jù)中墓臭,利用序列模型找到規(guī)律,進(jìn)行分類預(yù)測(cè)妖谴。以便相關(guān)部門可以未雨綢繆窿锉,提前有效干預(yù)可能發(fā)生的嚴(yán)重?fù)矶隆?/p>

尋找

之前在《文科生如何理解循環(huán)神經(jīng)網(wǎng)絡(luò)(RNN)?》一文中膝舅,我為你講解過(guò)循環(huán)神經(jīng)網(wǎng)絡(luò)的含義嗡载。《如何用 Python 和循環(huán)神經(jīng)網(wǎng)絡(luò)做中文文本分類仍稀?》一文鼻疮,我又為你介紹了如何用循環(huán)神經(jīng)網(wǎng)絡(luò)對(duì)文本做分類。

我不希望給你一種錯(cuò)誤的簡(jiǎn)單關(guān)聯(lián)琳轿,即“循環(huán)神經(jīng)網(wǎng)絡(luò)只能用來(lái)處理文本數(shù)據(jù)”判沟。

事實(shí)上耿芹,只要是序列數(shù)據(jù),你都可以考慮一下循環(huán)神經(jīng)網(wǎng)絡(luò)挪哄。

我一直打算找個(gè)其他序列數(shù)據(jù)的樣例吧秕,給你展示循環(huán)神經(jīng)網(wǎng)絡(luò)的更多應(yīng)用場(chǎng)景朗鸠。

但是這個(gè)數(shù)據(jù)不太好選擇护姆。

目前一個(gè)熱門的應(yīng)用場(chǎng)景刀闷,就是金融產(chǎn)品的價(jià)格預(yù)測(cè)井厌。

每時(shí)每秒,金融產(chǎn)品的價(jià)格都在變動(dòng)弓熏。把它匯集起來(lái)五辽,是個(gè)典型的序列數(shù)據(jù)特碳。

但是我一直不看好這種應(yīng)用刻两。因?yàn)榻鹑诋a(chǎn)品的定價(jià)增蹭,應(yīng)該是面向未來(lái)的“跄。基于歷史價(jià)格信息尋找波動(dòng)規(guī)律滋迈,并對(duì)未來(lái)價(jià)格進(jìn)行預(yù)測(cè),實(shí)際上如同看著后視鏡開車一般危險(xiǎn)户誓。

但是饼灿,還有很多人依然樂此不疲地嘗試。很多時(shí)候帝美,他們也能嘗到成功的甜頭碍彭。

這是為什么?

原因在于悼潭,金融市場(chǎng)的參與者庇忌,并非理性的機(jī)器,而是由人組成的群體女责。從行為金融學(xué)的角度來(lái)看漆枚,進(jìn)化給人類思考與行為帶來(lái)了一些“快捷方式”创译,你可以利用它們從中漁利抵知。

陸蓉教授的《行為金融學(xué)》欄目,對(duì)此有詳細(xì)介紹软族。

例如刷喜,人們追漲殺跌,認(rèn)為歷史會(huì)重演立砸;

例如掖疮,吸引大眾關(guān)注到事件,總會(huì)帶來(lái)買入颗祝;

例如浊闪,人們會(huì)傾向于投資于自己熟悉的標(biāo)的恼布;

例如,人們會(huì)購(gòu)買下跌的已持倉(cāng)標(biāo)的搁宾,來(lái)攤薄成本折汞。

……

如果沒有大風(fēng)浪,這種對(duì)市場(chǎng)參與者行為規(guī)律的洞察盖腿,確實(shí)可以幫你賺錢爽待。你可以從價(jià)格的歷史波動(dòng)中,挖掘出這些規(guī)律的影響翩腐。但是這對(duì)沒有模型可用的人來(lái)說(shuō)鸟款,不公平。教你建模茂卦,就如同教你考試作弊何什。

如果遇到黑天鵝事件,其影響大概率會(huì)超過(guò)市場(chǎng)參與者行為偏誤帶來(lái)的歷史價(jià)格波動(dòng)規(guī)律疙筹。那么你富俄,可能會(huì)因?yàn)閼?yīng)用模型,而遭遇虧損而咆。你大約不會(huì)認(rèn)為這是自己的錯(cuò)誤霍比,而直接把我當(dāng)做騙子,朝我扔雞蛋暴备。

理性權(quán)衡后悠瞬,我決定不用金融產(chǎn)品價(jià)格趨勢(shì)分析,作為循環(huán)神經(jīng)網(wǎng)絡(luò)的應(yīng)用樣例涯捻。

其他開放的序列數(shù)據(jù)浅妆,當(dāng)然也有很多。例如共享單車租用數(shù)據(jù)障癌、氣溫變化數(shù)據(jù)等凌外。

不過(guò)這些應(yīng)用,一來(lái)別人都寫過(guò)了涛浙,不新鮮康辑。二來(lái),氣溫變化轿亮,你看天氣預(yù)報(bào)就好了疮薇。共享單車租用數(shù)量……你真的關(guān)心這里的規(guī)律嗎?

正在我猶豫的時(shí)候我注,一次偶然的機(jī)會(huì)按咒,我接觸到了一個(gè)新的序列數(shù)據(jù)樣例——交通事件數(shù)據(jù)。我覺得但骨,把它作為應(yīng)用案例分享給你励七,可能更合適一些智袭。

比賽

拿到這個(gè)數(shù)據(jù),是因?yàn)槲覅⑴c了一次編程馬拉松(hackathon)比賽掠抬。

比賽在 Frisco 的 UNT Inspire Park 舉辦补履。從早上8點(diǎn)開始,一直到晚上9點(diǎn)多才結(jié)束剿另。中間可以自由吃免費(fèi)提供的點(diǎn)心和水果箫锤,也可以到院子里曬曬太陽(yáng)放放風(fēng)。大家還可以自由交流和組隊(duì)雨女。

主辦方為參賽者提供了若干種開放數(shù)據(jù)谚攒,也提了一些問題供大家參考解答。當(dāng)然氛堕,實(shí)際參賽的時(shí)候馏臭,你也可以自己擬定新的題目。

這其中讼稚,就包括了 Waze 數(shù)據(jù)括儒。

我在中國(guó)開車,平時(shí)用的都是高德導(dǎo)航锐想,對(duì)于 Waze 這款 App 不大熟悉帮寻。

簡(jiǎn)而言之,這個(gè) Waze 應(yīng)用除了提供一般的導(dǎo)航功能之外赠摇,還有一個(gè)類似于眾包的功能——讓司機(jī)們自由提交路況信息固逗。

這樣一來(lái),Waze 就利用群體智慧形成了一個(gè)眼觀六路耳聽八方的巨大網(wǎng)絡(luò)藕帜,隨時(shí)依據(jù)用戶提供的情況烫罩,匯總成實(shí)時(shí)交通參考。并且匯報(bào)給用戶洽故,以便于大家調(diào)整自己的行車路線贝攒。

我覺得最有用的特點(diǎn)是,在堵車的時(shí)候时甚,你可以了解到前面究竟發(fā)生了什么隘弊。其他導(dǎo)航也有實(shí)時(shí)交通狀況提示,但是你對(duì)前面的情況一無(wú)所知撞秋。道路半幅施工长捧?交通事故嚣鄙?

信息的對(duì)稱吻贿,可以在很大程度上,讓司機(jī)避免焦慮哑子。

Waze 從幾年前開始舅列,就和政府部門合作肌割,進(jìn)行數(shù)據(jù)開放共享。這樣一來(lái)帐要,政府可以通過(guò) Waze 的數(shù)據(jù)了解交通實(shí)時(shí)狀況把敞,對(duì)于問題進(jìn)行快速的響應(yīng)處理;與此同時(shí)榨惠, Waze 用戶也因?yàn)榭梢垣@取整合其他相關(guān)類型的政府開放數(shù)據(jù)(例如道路規(guī)劃等)奋早,更加有效合理安排出行。

這次比賽赠橙,主辦方提供的數(shù)據(jù)耽装,是 DFW (達(dá)拉斯-沃斯堡都會(huì)區(qū))區(qū)域,11月1日到29日的 Waze 交通事件(Incidents)開放數(shù)據(jù)期揪,這是政府開放數(shù)據(jù)的一部分掉奄。這些數(shù)據(jù)基本都是來(lái)自于 Waze 用戶的提交。

原始的數(shù)據(jù)凤薛,接近 300 MB姓建。每一條事件信息,都包含了提交的經(jīng)緯度缤苫,以及時(shí)間速兔。因此在探索性數(shù)據(jù)分析階段,我做了幾個(gè)可視化圖形活玲。

這是我當(dāng)天跟新認(rèn)識(shí)的編程高手 Jesse 學(xué)的 QGIS 分析結(jié)果憨栽。

看看圖上的點(diǎn),每一個(gè)都對(duì)應(yīng)一次事件匯報(bào)翼虫。這叫一個(gè)密密麻麻啊屑柔。

因?yàn)?QGIS 初學(xué),用得不熟珍剑,我還是用 Python 進(jìn)行了分類繪圖掸宛。

這只是前 3000 條數(shù)據(jù)中部分類型的可視化。其中紅色代表交通擁堵招拙,黃色代表事故發(fā)生唧瘾,藍(lán)色代表有車停在了路肩上。

可以看到别凤,紅色的數(shù)據(jù)量最大饰序。這說(shuō)明交通擁堵是個(gè)大問題。

我把全部的數(shù)據(jù)都拿了出來(lái)规哪,提煉出包含的事件類型求豫,包括以下這些類:

我看到,其中單是交通阻塞,也是分為若干級(jí)別的蝠嘉。其中最嚴(yán)重的最疆,分別是“大型交通擁堵”(large traffic jam)和“超大型交通擁堵”(huge traffic jam)。

于是蚤告,我把所有這兩種嚴(yán)重交通擁堵事件努酸,合并成一個(gè)集合;其他剩余事件杜恰,作為另一個(gè)集合获诈。

對(duì)于每一個(gè)嚴(yán)重?fù)矶率录易匪?0分鐘心褐,把之前同一條道路上烙荷,發(fā)生的事件,按照順序存成一個(gè)列表檬寂。這樣的列表终抽,有987個(gè);但是桶至,其中有一些昼伴,是驟然發(fā)生的,30分鐘的區(qū)間里面镣屹,沒有任何其他事件作為先兆圃郊。這樣的空列表,我進(jìn)行了清除女蜈。剩下了861個(gè)有效序列持舆。

同樣,從剩余事件集合中伪窖,我們隨機(jī)找到了861個(gè)非空有效序列逸寓。這些序列,后續(xù)緊隨事件覆山,都不是嚴(yán)重?fù)矶隆?/p>

我們對(duì)嚴(yán)重?fù)矶轮?0分鐘的事件序列竹伸,標(biāo)記為1;對(duì)于嚴(yán)重?fù)矶轮?0分鐘的事件序列簇宽,標(biāo)記為0勋篓。

于是,我們就把問題轉(zhuǎn)換成了魏割,能否利用事件序列譬嚣,進(jìn)行分類,預(yù)測(cè)后續(xù)是否會(huì)發(fā)生嚴(yán)重?fù)矶隆?/p>

靠著這個(gè)模型钞它,我們團(tuán)隊(duì)(UNT IIA lab代表隊(duì)拜银,昵稱 watch-dumpling )在這次比賽中殊鞭,獲得第一名。

這是 HackNTX 官網(wǎng)的報(bào)道(http://t.cn/EUbS9m5) 盐股。

UNT 網(wǎng)站也正式發(fā)布了這則新聞http://t.cn/EUbS127),于是我周圍盡人皆知耻卡。我才剛拿到手的獎(jiǎng)金疯汁,立即就因?yàn)檎?qǐng)客被掃蕩一空了。

奪冠純屬是個(gè)意外卵酪,幸運(yùn)占得比重很大幌蚊。但是我覺得我們做的這個(gè)模型,還是有些應(yīng)用價(jià)值的溃卡。

下面溢豆,我就以這組 Waze 交通事件數(shù)據(jù),詳細(xì)給你講解一下瘸羡,如何用 Python, Keras 和循環(huán)神經(jīng)網(wǎng)絡(luò)漩仙,來(lái)實(shí)現(xiàn)這個(gè)序列數(shù)據(jù)分類模型。

環(huán)境

要運(yùn)行深度學(xué)習(xí)犹赖,你需要有 GPU 或者 TPU 的支持队他,否則會(huì)累壞你的筆記本電腦的。Google Colab 是個(gè)不錯(cuò)的實(shí)驗(yàn)平臺(tái)峻村,可以讓你免費(fèi)使用 TPU 來(lái)進(jìn)行深度學(xué)習(xí)訓(xùn)練麸折。你可以閱讀《如何免費(fèi)云端運(yùn)行Python深度學(xué)習(xí)框架?》一文粘昨,查詢更為詳細(xì)的介紹垢啼。

這里,請(qǐng)你使用 Chrome 瀏覽器张肾,點(diǎn)擊這個(gè)鏈接芭析,安裝一個(gè)插件 Colaboratory 。

把它添加到 Google Chrome 之后吞瞪,你會(huì)在瀏覽器的擴(kuò)展工具欄里面放刨,看見下圖中間的圖標(biāo):

然后,請(qǐng)到本范例的github repo 主頁(yè)面尸饺。

打開其中的 demo.ipynb 文件进统。

點(diǎn)擊 Colaboratory 擴(kuò)展圖標(biāo)。Google Chrome 會(huì)自動(dòng)幫你開啟 Google Colab浪听,并且裝載這個(gè) ipynb 文件螟碎。

點(diǎn)擊上圖中紅色標(biāo)出的“復(fù)制到云端硬盤”按鈕。Google 會(huì)為你新建一個(gè)屬于你自己的副本迹栓。

點(diǎn)擊菜單欄里面的“代碼執(zhí)行程序”掉分,選擇“更改運(yùn)行時(shí)類型”。

在出現(xiàn)的對(duì)話框中,確認(rèn)選項(xiàng)如下圖所示酥郭。

點(diǎn)擊“保存”即可华坦。

下面,你就可以依次執(zhí)行每一個(gè)代碼段落了不从。

注意第一次執(zhí)行的時(shí)候惜姐,可能會(huì)有警告提示。

出現(xiàn)上面這個(gè)警告的時(shí)候椿息,點(diǎn)擊“仍然運(yùn)行”就可以繼續(xù)了歹袁。

如果再次出現(xiàn)警告提示,反勾選“在運(yùn)行前充值所有代碼執(zhí)行程序”選項(xiàng)寝优,再次點(diǎn)擊“仍然運(yùn)行”即可条舔。

環(huán)境準(zhǔn)備好了,下面我們來(lái)一步步運(yùn)行代碼乏矾。

代碼

首先孟抗,我們讀入 Pandas 軟件包,以便進(jìn)行結(jié)構(gòu)化數(shù)據(jù)的處理钻心。

import pandas as pd

這次還要讀入的一個(gè)軟件包夸浅,是 Python 中間進(jìn)行數(shù)據(jù)存取的利器,叫做 pickle 扔役。

import pickle

它可以把 Python 數(shù)據(jù)帆喇,甚至是許多組數(shù)據(jù),一起存儲(chǔ)到指定文件亿胸。然后讀出的時(shí)候坯钦,可以完全恢復(fù)原先數(shù)據(jù)的格式。這一點(diǎn)上侈玄,它比用 csv 進(jìn)行數(shù)據(jù)存儲(chǔ)和交換的效果更好婉刀,效率也更高。

下面我們從本文配套的 github 項(xiàng)目中序仙,把數(shù)據(jù)傳遞過(guò)來(lái)突颊。

!git clone https://github.com/wshuyi/demo_traffic_jam_prediction.git

數(shù)據(jù)的下載,很快就可以完成潘悼。

Cloning into 'demo_traffic_jam_prediction'...
remote: Enumerating objects: 6, done.[K
remote: Counting objects: 100% (6/6), done.[K
remote: Compressing objects: 100% (4/4), done.[K
remote: Total 6 (delta 0), reused 3 (delta 0), pack-reused 0[K
Unpacking objects: 100% (6/6), done.

我們告訴 Jupyter Notebook 律秃,數(shù)據(jù)文件夾的位置。

from pathlib import Path
data_dir = Path('demo_traffic_jam_prediction')

打開數(shù)據(jù)文件治唤,利用 pickle 把兩組數(shù)據(jù)分別取出棒动。

with open(data_dir / 'data.pickle', 'rb') as f:
    [event_dict, df] = pickle.load(f)

先看其中的事件詞典 event_dict

event_dict

以下就是全部的事件類型。

{1: 'road closed due to construction',
 2: 'traffic jam',
 3: 'stopped car on the shoulder',
 4: 'road closed',
 5: 'other',
 6: 'object on roadway',
 7: 'major event',
 8: 'pothole',
 9: 'traffic heavier than normal',
 10: 'road construction',
 11: 'fog',
 12: 'accident',
 13: 'slowdown',
 14: 'stopped car',
 15: 'small traffic jam',
 16: 'stopped traffic',
 17: 'heavy traffic',
 18: 'minor accident',
 19: 'medium traffic jam',
 20: 'malfunctioning traffic light',
 21: 'missing sign on the shoulder',
 22: 'animal on the shoulder',
 23: 'animal struck',
 24: 'large traffic jam',
 25: 'hazard on the shoulder',
 26: 'hazard on road',
 27: 'ice on roadway',
 28: 'weather hazard',
 29: 'flooding',
 30: 'road closed due to hazard',
 31: 'hail',
 32: 'huge traffic jam'}

同樣宾添,我們來(lái)看看存儲(chǔ)事件序列的數(shù)據(jù)框船惨。

先看前10個(gè):

df.head(10)

注意柜裸,每一行,都包含了標(biāo)記粱锐。

再看結(jié)尾部分:

df.tail(10)

讀取無(wú)誤疙挺。

下面我們來(lái)看看,最長(zhǎng)的一個(gè)序列怜浅,編號(hào)是多少铐然。

這里,我們利用的是 Pandas 的一個(gè)函數(shù)海雪,叫做 idxmax() 锦爵,它可以幫助我們舱殿,把最大值對(duì)應(yīng)的索引編號(hào)奥裸,傳遞回來(lái)。

max_len_event_id = df.events.apply(len).idxmax()
max_len_event_id

結(jié)果為:

105

我們來(lái)看看沪袭,這個(gè)編號(hào)對(duì)應(yīng)的事件序列湾宙,是什么樣子的:

max_len_event = df.iloc[max_len_event_id]
max_len_event.events

下面是長(zhǎng)長(zhǎng)的反饋結(jié)果:

['stopped car on the shoulder',
 'heavy traffic',
 'heavy traffic',
 'heavy traffic',
 'slowdown',
 'stopped traffic',
 'heavy traffic',
 'heavy traffic',
 'heavy traffic',
 'heavy traffic',
 'traffic heavier than normal',
 'stopped car on the shoulder',
 'traffic jam',
 'heavy traffic',
 'stopped traffic',
 'stopped traffic',
 'stopped traffic',
 'heavy traffic',
 'traffic jam',
 'stopped car on the shoulder',
 'stopped traffic',
 'stopped traffic',
 'stopped traffic',
 'heavy traffic',
 'traffic heavier than normal',
 'traffic heavier than normal',
 'traffic heavier than normal',
 'traffic heavier than normal',
 'heavy traffic',
 'stopped traffic',
 'traffic heavier than normal',
 'pothole',
 'stopped car on the shoulder',
 'traffic jam',
 'slowdown',
 'stopped traffic',
 'heavy traffic',
 'traffic heavier than normal',
 'traffic jam',
 'traffic jam',
 'stopped car on the shoulder',
 'major event',
 'traffic jam',
 'traffic jam',
 'stopped traffic',
 'heavy traffic',
 'traffic heavier than normal',
 'stopped car on the shoulder',
 'slowdown',
 'heavy traffic',
 'heavy traffic',
 'stopped car on the shoulder',
 'traffic jam',
 'slowdown',
 'slowdown',
 'heavy traffic',
 'stopped car on the shoulder',
 'heavy traffic',
 'minor accident',
 'stopped car on the shoulder',
 'heavy traffic',
 'stopped car on the shoulder',
 'heavy traffic',
 'stopped traffic',
 'heavy traffic',
 'traffic heavier than normal',
 'heavy traffic',
 'stopped car on the shoulder',
 'traffic heavier than normal',
 'stopped traffic',
 'heavy traffic',
 'heavy traffic',
 'heavy traffic',
 'stopped car on the shoulder',
 'slowdown',
 'stopped traffic',
 'heavy traffic',
 'stopped car on the shoulder',
 'traffic heavier than normal',
 'heavy traffic',
 'minor accident',
 'major event',
 'stopped car on the shoulder',
 'stopped car on the shoulder']

讀一遍,你就會(huì)發(fā)現(xiàn)冈绊,在超級(jí)擁堵發(fā)生之前侠鳄,確實(shí)還是有一些先兆的。當(dāng)然死宣,這是由人來(lái)閱讀后伟恶,獲得的觀感。我們下面需要做的毅该,是讓機(jī)器自動(dòng)把握這些列表的特征博秫,并且做出區(qū)別分類。

我們看看眶掌,這個(gè)最長(zhǎng)列表的長(zhǎng)度挡育。

maxlen = len(max_len_event.events)
maxlen

結(jié)果為:

84

這里的前導(dǎo)事件,還真是不少啊朴爬。

下面我們要做的即寒,是把事件轉(zhuǎn)換成數(shù)字編號(hào),這樣后面更容易處理召噩。

我們使用以下的一個(gè)小技巧母赵,把原先的事件詞典倒置,即變“序號(hào):事件名稱”具滴,為“事件名稱:序號(hào)”市咽。這樣,以事件名稱查詢起來(lái)抵蚊,效率會(huì)高很多施绎。

reversed_dict = {}
for k, v in event_dict.items():
  reversed_dict[v] = k

我們看看倒置的結(jié)果詞典:

reversed_dict

這是反饋結(jié)果:

{'accident': 12,
 'animal on the shoulder': 22,
 'animal struck': 23,
 'flooding': 29,
 'fog': 11,
 'hail': 31,
 'hazard on road': 26,
 'hazard on the shoulder': 25,
 'heavy traffic': 17,
 'huge traffic jam': 32,
 'ice on roadway': 27,
 'large traffic jam': 24,
 'major event': 7,
 'malfunctioning traffic light': 20,
 'medium traffic jam': 19,
 'minor accident': 18,
 'missing sign on the shoulder': 21,
 'object on roadway': 6,
 'other': 5,
 'pothole': 8,
 'road closed': 4,
 'road closed due to construction': 1,
 'road closed due to hazard': 30,
 'road construction': 10,
 'slowdown': 13,
 'small traffic jam': 15,
 'stopped car': 14,
 'stopped car on the shoulder': 3,
 'stopped traffic': 16,
 'traffic heavier than normal': 9,
 'traffic jam': 2,
 'weather hazard': 28}

成功了溯革。

下面我們編寫一個(gè)函數(shù),輸入一個(gè)事件列表谷醉,返回對(duì)應(yīng)的事件編號(hào)列表致稀。

def map_event_list_to_idxs(event_list):
  list_idxs = []
  for event in (event_list):
    idx = reversed_dict[event]
    list_idxs.append(idx)
  return list_idxs

然后,我們?cè)趧偛攀钦业降淖铋L(zhǎng)列表上俱尼,實(shí)驗(yàn)一下:

map_event_list_to_idxs(max_len_event.events)

結(jié)果是這樣的:

[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]

看來(lái)功能實(shí)現(xiàn)上抖单,沒問題。

讀入 numpy 和 Keras 的一些工具遇八。

import numpy as np
from keras.utils import to_categorical
from keras.preprocessing.sequence import pad_sequences

系統(tǒng)自動(dòng)提示我們矛绘,Keras 使用了 Tensorflow 作為后端框架。

Using TensorFlow backend.

我們需要弄清楚刃永,一共有多少種事件類型货矮。

len(event_dict)

結(jié)果是:

32

因此,我們需要對(duì)32種不同的事件類型斯够,進(jìn)行轉(zhuǎn)換和處理囚玫。

我們把整個(gè)數(shù)據(jù)集里面的事件類型,都變成事件編號(hào)读规。

df.events.apply(map_event_list_to_idxs)

結(jié)果如下:

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

現(xiàn)在抓督,作為人類,我們確實(shí)是看不清楚束亏,列表里面的事件都是什么了铃在。好在計(jì)算機(jī)對(duì)于數(shù)字,更加喜聞樂見碍遍。

我們把該列表定铜,起名為 sequences ,并且顯示前5項(xiàng)內(nèi)容雀久。

sequences = df.events.apply(map_event_list_to_idxs).tolist()
sequences[:5]

下面是結(jié)果:

[[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]]

注意宿稀,第一行,明顯比后幾行都要長(zhǎng)赖捌。

對(duì)于輸入序列祝沸,我們希望它的長(zhǎng)度都是一樣的。因此越庇,下面我們就用最長(zhǎng)的序列長(zhǎng)度作為標(biāo)準(zhǔn)罩锐,用 0 來(lái)填充其他短序列。

data = pad_sequences(sequences, maxlen=maxlen)
data

這是結(jié)果:

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涩惑,都補(bǔ)充到了序列的最前端。序列都一樣長(zhǎng)了桑驱。

下面竭恬,我們把全部的分類標(biāo)記跛蛋,存儲(chǔ)到 labels 變量里面。

labels = np.array(df.label)

后面痊硕,我們有好幾個(gè)函數(shù)赊级,需要用到隨機(jī)變量。

為了咱們運(yùn)行結(jié)果的一致性岔绸。我這里指定隨機(jī)種子數(shù)值理逊。你第一次嘗試運(yùn)行的時(shí)候,不要?jiǎng)铀腥唷5呛竺孀约簞?dòng)手操作的時(shí)候晋被,可以任意修改它。

np.random.seed(12)

好了刚盈,下面我們“洗牌”羡洛。打亂數(shù)據(jù)的順序,但是注意序列和對(duì)應(yīng)標(biāo)記之間扁掸,要保持一致性翘县。

indices = np.arange(data.shape[0])
np.random.shuffle(indices)
data = data[indices]
labels = labels[indices]

然后最域,我們?nèi)?80% 的數(shù)據(jù)谴分,作為訓(xùn)練;另外 20% 的數(shù)據(jù)镀脂,作為驗(yàn)證牺蹄。

training_samples = int(len(indices) * .8)
validation_samples = len(indices) - training_samples

我們正式劃分訓(xùn)練集和驗(yàn)證集。

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ùn)練集的內(nèi)容薄翅。

X_train

結(jié)果為:

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)

注意由于我們補(bǔ)充了“0”沙兰,作為填充,因此原先的32種事件類型的基礎(chǔ)上翘魄,又加了一種鼎天。

這就是我們新的事件類型數(shù)量:

num_events = len(event_dict) + 1

我們使用嵌入層,把事件標(biāo)號(hào)暑竟,轉(zhuǎn)換成一系列數(shù)字組成的向量斋射。這樣,可以避免模型把事件序號(hào)但荤,當(dāng)成數(shù)值型數(shù)據(jù)來(lái)處理罗岖。

這里,我們指定每一個(gè)標(biāo)號(hào)腹躁,轉(zhuǎn)換成 20 個(gè)數(shù)字組成的向量桑包。

embedding_dim = 20

利用事件類型數(shù)量,和事件向量長(zhǎng)度纺非,我們隨機(jī)構(gòu)造初始的嵌入矩陣哑了。

embedding_matrix = np.random.rand(num_events, embedding_dim)

下面我們搭建一個(gè)循環(huán)神經(jīng)網(wǎng)絡(luò)模型赘方。其中的 LSTM 層,包含了32位輸出數(shù)字弱左。

from keras.models import Sequential
from keras.layers import Embedding, Flatten, Dense, LSTM

units = 32

model = Sequential()
model.add(Embedding(num_events, embedding_dim))
model.add(LSTM(units))
model.add(Dense(1, activation='sigmoid'))

這里蒜焊,我假設(shè)你已經(jīng)看過(guò)了《如何用 Python 和循環(huán)神經(jīng)網(wǎng)絡(luò)做中文文本分類?》一文科贬,所以就不對(duì)細(xì)節(jié)進(jìn)行講述了泳梆。如果你沒有看過(guò),或者已經(jīng)遺忘榜掌,可以點(diǎn)擊這個(gè)鏈接復(fù)習(xí)一下优妙。

如果你對(duì) Keras 的使用方法還不熟悉,我再次向你推薦 Fran?ois Chollet 的《Deep Learning with Python》憎账。

下面套硼,是處理其中的嵌入層參數(shù)。我們直接把剛才隨機(jī)生成的嵌入矩陣挪進(jìn)來(lái)胞皱。而且邪意,不讓模型在訓(xùn)練中對(duì)嵌入層參數(shù)進(jìn)行修改。

model.layers[0].set_weights([embedding_matrix])
model.layers[0].trainable = False

下面反砌,我們開始訓(xùn)練雾鬼。并且把模型運(yùn)行結(jié)果保存起來(lái)。

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")

可以看到宴树,因?yàn)橛?TPU 的強(qiáng)力支持策菜,程序在歡快地運(yùn)行中。

訓(xùn)練過(guò)程結(jié)束之后酒贬,我們利用 matplotlib 繪圖功能又憨,看一下訓(xùn)練中,準(zhǔn)確率和損失值的變化锭吨。

import matplotlib.pyplot as plt

acc = 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='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()

plt.figure()

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()

這是準(zhǔn)確率變化曲線蠢莺。

可以看到,效果還是不錯(cuò)的零如。因?yàn)槲覀償?shù)據(jù)中躏将,不同標(biāo)記各占一半。因此如果構(gòu)建一個(gè) dummy model 作為標(biāo)準(zhǔn)線的話埠况,對(duì)所有的輸入都猜測(cè)0或者1耸携,準(zhǔn)確率應(yīng)該只有50%。

這里的準(zhǔn)確率辕翰,已經(jīng)達(dá)到了65%-75%之間夺衍,證明我們的模型是有意義的。只不過(guò)喜命,抖動(dòng)比較厲害沟沙,穩(wěn)定性差河劝。

這是損失值變化曲線。

這個(gè)圖看起來(lái)矛紫,就不是很美妙了赎瞎。因?yàn)殡m然訓(xùn)練集上面的損失值一路下降,但是驗(yàn)證集上颊咬,這個(gè)效果并不是很明顯务甥,一直劇烈波動(dòng)筛峭。

看到結(jié)果僧界,不是最重要的。關(guān)鍵是我們得分析出目前遇到問題外盯,原因是什么麸澜。

注意我們前面使用了嵌入矩陣挺尿。它隨機(jī)生成,卻又沒有真正進(jìn)行訓(xùn)練調(diào)整炊邦,這可能是個(gè)問題编矾。

因此,我們這里再次構(gòu)建和跑一下模型馁害。唯一改動(dòng)的地方窄俏,在于讓嵌入矩陣的參數(shù)也可以隨著訓(xùn)練進(jìn)行自動(dòng)調(diào)整。

from keras.models import Sequential
from keras.layers import Embedding, Flatten, Dense, LSTM

units = 32

model = Sequential()
model.add(Embedding(num_events, embedding_dim))
model.add(LSTM(units))
model.add(Dense(1, activation='sigmoid'))

注意這里的差別蜗细,就是 trainable 設(shè)置為真值裆操。

model.layers[0].set_weights([embedding_matrix])
model.layers[0].trainable = True

構(gòu)建模型怒详,再次運(yùn)行炉媒。

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")

繪圖看看。

import matplotlib.pyplot as plt

acc = 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='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()

plt.figure()

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()

這次的準(zhǔn)確率曲線昆烁,看起來(lái)好多了吊骤。驗(yàn)證集波動(dòng)沒有這么劇烈,模型穩(wěn)定性好了許多静尼。而且白粉,準(zhǔn)確率的取值,也獲得了提升鼠渺。后半程穩(wěn)定在了75%以上鸭巴。這樣的模型,就有應(yīng)用價(jià)值了拦盹。

但是我們看看損失值曲線鹃祖,可能就不這么樂觀了。

注意從半程之后普舆,訓(xùn)練集和驗(yàn)證集的損失值變化恬口,就發(fā)生了分叉校读。

這是典型的過(guò)擬合(over-fitting)。

發(fā)生過(guò)擬合祖能,主要原因就是相對(duì)于復(fù)雜的模型歉秫,訓(xùn)練數(shù)據(jù)不夠用。

這時(shí)候养铸,要么增加訓(xùn)練數(shù)據(jù)雁芙,要么降低模型復(fù)雜度。

立即增加數(shù)據(jù)钞螟,不太現(xiàn)實(shí)却特。因?yàn)槲覀兪种校壳爸挥心?9天里積攢的數(shù)據(jù)筛圆。

但是降低模型復(fù)雜度裂明,是可以利用 Dropout 來(lái)嘗試完成的。

Dropout 的實(shí)現(xiàn)機(jī)理太援,是在訓(xùn)練的時(shí)候闽晦,每次隨機(jī)把一定比例的模型中神經(jīng)元對(duì)應(yīng)權(quán)重參數(shù),設(shè)置為0提岔,讓它不起作用仙蛉。這樣,模型的復(fù)雜度碱蒙,就會(huì)降低荠瘪。

下面,我們輕微修改一下赛惩,在 LSTM 層上哀墓,加入 dropout=0.2, recurrent_dropout=0.2 這兩個(gè)參數(shù)。

from keras.models import Sequential
from keras.layers import Embedding, Flatten, Dense, LSTM

units = 32

model = 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'))

依然保持嵌入層可以被訓(xùn)練喷兼。

model.layers[0].set_weights([embedding_matrix])
model.layers[0].trainable = True

再次運(yùn)行篮绰。

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")

繪制圖形的函數(shù)跟之前兩次完全一致。

import matplotlib.pyplot as plt

acc = 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='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()

plt.figure()

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()

這次的準(zhǔn)確率曲線季惯,看起來(lái)達(dá)到的數(shù)值吠各,跟沒有加入 Dropout 的差不多。

然而勉抓,我們可以感受到訓(xùn)練集和驗(yàn)證集達(dá)到的準(zhǔn)確率更加貼近贾漏。曲線更加平滑。

下面我們看看損失值曲線的變化藕筋。

這個(gè)曲線上纵散,過(guò)擬合的去除效果就更為明顯了。可以看到訓(xùn)練集和驗(yàn)證集兩條曲線的波動(dòng)基本保持了一致困食。這樣我們更可以確信边翁,模型預(yù)測(cè)能力是穩(wěn)定的,對(duì)外界新的輸入信息硕盹,適應(yīng)性更好符匾。

如果把咱們的模型放在交通管理部門那里,可以期望它根據(jù) Waze 獲得的新序列數(shù)據(jù)瘩例,能以大約 75% 的準(zhǔn)確率啊胶,預(yù)測(cè)嚴(yán)重交通擁堵的發(fā)生。這樣垛贤,交管部門就可以未雨綢繆焰坪,提前做出干預(yù)了。

用序列模型聘惦,欺負(fù)金融市場(chǎng)的散戶某饰,屬于零和博弈。然而這種在交通管理上的應(yīng)用善绎,大概更能造福社會(huì)黔漂,體現(xiàn)科技的價(jià)值吧。

小結(jié)

通過(guò)本文的學(xué)習(xí)和實(shí)際上手操作禀酱,希望你已了解了以下知識(shí)點(diǎn):

  • 不只是文本炬守,其他序列數(shù)據(jù),也可以利用循環(huán)神經(jīng)網(wǎng)絡(luò)來(lái)進(jìn)行分類預(yù)測(cè)剂跟。
  • 對(duì)定類數(shù)據(jù)(categorical data)進(jìn)行嵌入表示减途,如果用隨機(jī)數(shù)初始,那么在建模過(guò)程中把嵌入層一起訓(xùn)練曹洽,效果會(huì)更好鳍置。
  • 數(shù)據(jù)量不夠的情況下,深度學(xué)習(xí)很可能會(huì)發(fā)生過(guò)擬合衣洁。使用 Dropout 墓捻,可以降低過(guò)擬合的影響,讓模型具有更好的穩(wěn)定性和可擴(kuò)展性坊夫。

希望這篇文章,可以幫助你了解循環(huán)神經(jīng)網(wǎng)絡(luò)的更多應(yīng)用場(chǎng)景撤卢。在實(shí)際的工作和學(xué)習(xí)中环凿,靈活運(yùn)用它來(lái)處理序列數(shù)據(jù)的分類等任務(wù)。

祝(深度)學(xué)習(xí)愉快放吩!

喜歡請(qǐng)點(diǎn)贊和打賞智听。還可以微信關(guān)注和置頂我的公眾號(hào)“玉樹芝蘭”(nkwangshuyi)

如果你對(duì) Python 與數(shù)據(jù)科學(xué)感興趣,不妨閱讀我的系列教程索引貼《如何高效入門數(shù)據(jù)科學(xué)到推?》考赛,里面還有更多的有趣問題及解法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末莉测,一起剝皮案震驚了整個(gè)濱河市颜骤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌捣卤,老刑警劉巖忍抽,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異董朝,居然都是意外死亡鸠项,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門子姜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)祟绊,“玉大人,你說(shuō)我怎么就攤上這事哥捕【妹猓” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵扭弧,是天一觀的道長(zhǎng)阎姥。 經(jīng)常有香客問我,道長(zhǎng)鸽捻,這世上最難降的妖魔是什么呼巴? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮御蒲,結(jié)果婚禮上衣赶,老公的妹妹穿的比我還像新娘。我一直安慰自己厚满,他們只是感情好府瞄,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著碘箍,像睡著了一般遵馆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上丰榴,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天货邓,我揣著相機(jī)與錄音,去河邊找鬼四濒。 笑死换况,一個(gè)胖子當(dāng)著我的面吹牛职辨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播戈二,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼舒裤,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了觉吭?” 一聲冷哼從身側(cè)響起腾供,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎亏栈,沒想到半個(gè)月后台腥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡绒北,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年黎侈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片闷游。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡峻汉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出脐往,到底是詐尸還是另有隱情休吠,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布业簿,位于F島的核電站瘤礁,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏梅尤。R本人自食惡果不足惜柜思,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望巷燥。 院中可真熱鬧赡盘,春花似錦、人聲如沸缰揪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)钝腺。三九已至抛姑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拍屑,已是汗流浹背途戒。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留僵驰,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蒜茴,于是被迫代替她去往敵國(guó)和親星爪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345