貝葉斯網絡python實戰(以泰坦尼克號數據集為例,pgmpy庫)
本文的相關數據集,代碼見文末百度雲
貝葉斯網絡簡介
貝葉斯網絡是一種置信網絡,一個生成模型。(判別模型,生成模型的區分可以這樣:回答p(label|x)即樣本x屬於某一類別的可能的,就是判別模型,而回答p(x,label) 和p(x|label)的,即回答在給定的類別中找樣本x及樣本分布情況的,即為生成模型。生成模型給出的聯合分布比判別網絡能給出更多的信息,對其求邊緣分布即可得p(label|x) p(x|label))同時貝葉斯網絡還是一個簡單的白盒網絡,提供了高可解釋性的可能。相比於大熱的幾乎無所不能的深度神經網絡,貝葉斯網絡仍有他的優勢和應用場景。比如在故障分析,疾病診斷里,我們不僅需要回答是不是,更重要的是回答為什么,並給出依據。這樣的場景下,以貝葉斯網絡為代表的一些可解釋好的白盒網絡更加有優勢。
貝葉斯推斷思路
與頻率派直接從數據統計分析構建模型不同,貝葉斯派引入一個先驗概率,表達對事件的已有了解,然后利用觀測數據對先驗知識進行修正,如通常把拋硬幣向上的概率認為是0.5,這是個很朴素的先驗知識,若是實驗結果拋出了500-500的結果,那么證明先驗知識是可靠的,合適的,若是出現100-900結果,那么先驗知識會被逐漸修改(越來越相信這是個作弊硬幣),當實驗數據足夠多的時候,先驗知識就幾乎不再體現,這時候得到與頻率派幾乎相同的結果。如圖
具體例子推導可見here
貝葉斯網絡
貝葉斯網絡結構如下所示,其是有特征節點和鏈接構成的有向無環圖。節點上是概率P(A),P(B)… 連接上是條件概率P(A|B) P(A|C) … 即若有A指向B的連接,則連接代表的就應為P(B|A),更多信息可參考以下內容,這里不再贅述,貝葉斯網絡結構本身不困難,其難點主要在於推理算法等數值計算問題,如為應用則無需深究。
貝葉斯網絡發展及其應用綜述
《貝葉斯網絡引論》@張連文
靜態貝葉斯網絡
貝葉斯網絡的實現
相關工具一直很豐富,matlab,R上都有成熟的工具。這里使用了python下的pgmpy,輕量好用,不像pymc那樣容易安裝困難。
安裝:
conda install -c ankurankan pgmpy
或
pip install pgmpy
應用步驟
1.先確定以那些變量(特征)為節點,這里還包括由特征工程特征選擇之類的工作。當然若有專業知識的參與會得到更合理的特征選擇。
2.確定網絡結構(拓撲)用以反應變量節點之間的依賴關系。也就是明確圖的結構。這里既可以在有專家參與的情況下手工設計,也可以自動找到高效合適的網絡,稱為結構學習。貝葉斯網絡的結構對最終網絡性能很關鍵,若是構建所謂全連接貝葉斯網(即各個變量間兩兩相連),雖沒有遺漏關聯,但會導致嚴重的過擬合,因為數據量很難支撐起全連接直接海量的條件概率。
3.明確每條邊上的條件概率。和結構一樣,參數也可由專家手工確定(先驗),亦可通過數據自動學習(即參數學習),或兩者同時進行。
下面以一個經典數據集為例展示如何利用pgmpy包進行貝葉斯網絡建模
泰坦尼克數據集背景介紹
ref:https://www.jianshu.com/p/9b6ee1fb7a60
https://www.kaggle.com/c/titanic
這是kaggle經典數據集,主要是讓參賽選手根據訓練集中的乘客數據和存活情況進行建模,進而使用模型預測測試集中的乘客是否會存活。乘客特征總共有11個,以下列出。這個數據集特征明確,數據量不大,很適合應用貝葉斯網絡之類的模型來做,目前最好的結果是正確率應該有80+%(具體多少因為答案泄露不好講了)
PassengerId => 乘客ID
Pclass => 客艙等級(1/2/3等艙位)
Name => 乘客姓名
Sex => 性別
Age => 年齡
SibSp => 兄弟姐妹數/配偶數
Parch => 父母數/子女數
Ticket => 船票編號
Fare => 船票價格
Cabin => 客艙號
Embarked => 登船港口
在開始建模之前,先進行下特征工程,處理原始數據集的缺項等。這里前面處理主要采用https://www.jianshu.com/p/9b6ee1fb7a60的方法(他應用pandas清理數據的技巧很值得一學),我在他的處理后,進一步進行了一些離散化處理,以使得數據符合貝葉斯網絡的要求(貝葉斯網絡也有支持連續變量的版本,但因為推理,學習的困難,目前還用的很少),最后保留5個特征。
''' PassengerId => 乘客ID Pclass => 客艙等級(1/2/3等艙位) Name => 乘客姓名 Sex => 性別 清洗成male=1 female=0 Age => 年齡 插補后分0,1,2 代表 幼年(0-15) 成年(15-55) 老年(55-) SibSp => 兄弟姐妹數/配偶數 Parch => 父母數/子女數 Ticket => 船票編號 Fare => 船票價格 經聚類變0 1 2 代表少 多 很多 Cabin => 客艙號 清洗成有無此項,並發現有的生存率高 Embarked => 登船港口 清洗na,填S ''' # combine train and test set. train=pd.read_csv('./train.csv') test=pd.read_csv('./test.csv') full=pd.concat([train,test],ignore_index=True) full['Embarked'].fillna('S',inplace=True) full.Fare.fillna(full[full.Pclass==3]['Fare'].median(),inplace=True) full.loc[full.Cabin.notnull(),'Cabin']=1 full.loc[full.Cabin.isnull(),'Cabin']=0 full.loc[full['Sex']=='male','Sex']=1 full.loc[full['Sex']=='female','Sex']=0 full['Title']=full['Name'].apply(lambda x: x.split(',')[1].split('.')[0].strip()) nn={'Capt':'Rareman', 'Col':'Rareman','Don':'Rareman','Dona':'Rarewoman', 'Dr':'Rareman','Jonkheer':'Rareman','Lady':'Rarewoman','Major':'Rareman', 'Master':'Master','Miss':'Miss','Mlle':'Rarewoman','Mme':'Rarewoman', 'Mr':'Mr','Mrs':'Mrs','Ms':'Rarewoman','Rev':'Mr','Sir':'Rareman', 'the Countess':'Rarewoman'} full.Title=full.Title.map(nn) # assign the female 'Dr' to 'Rarewoman' full.loc[full.PassengerId==797,'Title']='Rarewoman' full.Age.fillna(999,inplace=True) def girl(aa): if (aa.Age!=999)&(aa.Title=='Miss')&(aa