作者:陳千鶴 來源:人工智慧學習圈
本篇主要介紹樸素貝葉斯算法及應用案例。
以Tatinic數據集為例進行應用,同時介紹數據處理,數據集的劃分,算法效果評估等內容。
一、簡介
樸素貝葉斯法是基於貝葉斯定理與特徵條件獨立假設的分類方法 。
和決策樹模型相比,樸素貝葉斯分類器(Naive Bayes Classifier 或 NBC)發源於古典數學理論,有著堅實的數學基礎,以及穩定的分類效率。同時,NBC模型所需估計的參數很少,對缺失數據不太敏感,算法也比較簡單。
樸素貝葉斯算法(Naive Bayesian algorithm) 是應用最為廣泛的分類算法之一。
也就是說沒有哪個屬性變量對於決策結果來說佔有著較大的比重,也沒有哪個屬性變量對於決策結果佔有著較小的比重。
雖然這個簡化方式在一定程度上降低了貝葉斯分類算法的分類效果,但是在實際的應用場景中,極大地簡化了貝葉斯方法的複雜性。
二、原理
設有樣本數據集D = {d~1~ ,d~2~,···,d~n~ },對應樣本集的特徵屬性集為X={x~1~,x~2~,···,x~d~},類變量為Y={y~1~,y~2~,···,y~m~},即D可以分成y~m~類別。其中x~1~,x~2~,···,x~d~相互隨機獨立,則Y的先驗概率P~prior~ = P(Y),Y的後驗概率P~post~ = P(Y|X),則由樸素貝葉斯算法可得,後驗概率可以由先驗概率P~prior~ = P(Y)、證據P(X)、類條件概率P(X|Y)計算出:
由於P(X)的大小是固定不變的,因此在比較後驗概率時,只比較上式的分子部分即可。
因此可以得到一個樣本數據屬於類別y~i~的樸素貝葉斯計算公式如下:
注意,一般只比較分子部分,這一點在後面會有一個在Titanic上應用的小討論
三、原理分析(實例)
這裡用一個實際案例來說明比較抽象的公式。
以瓜樹的案例來計算說明:
目標:對下例進行分類
計算過程:
那麼,我自己的理解就是:
先在訓練集中計算標籤為0和1的先驗概率,再分別計算每個特徵在標籤為0和1的條件概率。
預測過程中,將預測數據每個特徵在標籤為1時的條件概率之積與預測為1的概率直接相乘,記為P(1)
再將預測數據每個特徵在標籤為0時的條件概率之積與預測為0的概率直接相乘,記作P(0)
比較P(1)與P(0),哪個大就預測數據的標籤為0或1.
四、在Titanic數據集上的應用
1.數據集介紹
數據有如下特徵:
繼續觀察每個特徵下的具體情況:
我們會發現:
Age,Cabin,Embarked存在空值,需要進行處理
繼續看整體生存狀況
再綜合我們的常識可以得到結論:
女性的存活率高於男性Pclass1,2,3的生存率依次降低,這有可能與不同層的乘船人的社會地位,富裕程度有關年齡上來說,小孩先走,存活率較高Fare分布差異很大。Parch 代表同船的父母或子女,SipSp代表同船的兄弟姐妹,這都是兩個表現親人的關係,顯然這類因素會對存活率產生影響
那麼結合上面的分析的,可以做出如下處理:
對於不重要的特徵捨棄掉,簡化後面的計算,同時將SibSp和Parch兩項合併。
數據中很多為連續型和字符型數據,我們需要進行數值化,離散化處理。
年齡中位數28,缺失值補充為28。並且以25和31為界限分為三類sibsp&parch按照有無分為兩類生還共計342人。其中全體票價和生還票價均值均約為32。生還者票價高於32的126人死亡共計549人。其中票價低於32的464人 票價低於32共計680人,死亡率0.68票價低於64的共計773人,死亡512人 選擇以64為分界點
附上此部分分析代碼:
# 以下是最初的數據分析
# train_set, category = loadDataset('titanic.csv')
# print(category)
# print(train_set)
# k = [0, 0, 0, 0, 0, 0]
# min = train_set[0][3]
# max = 0
# age = []
# for data in train_set:
# for i in range(len(category)):
# if data[i] is None:
# k[i] += 1
# if data[3] is not None:
# if data[3] < min:
# min = data[3]
# if data[3] > max:
# max = data[3]
# age.append(data[3])
#
# age = sorted(age)
#
# print(category)
# print(k)
# print(min)
# print(max)
# print(age)
# print((len(train_set)-k[3])/2) #357
# print(age[int((len(train_set)-k[3])/2)]) # 28
# print(len(age)/3)
# print(age[297]) # 25
# print(age[297*2]) # 31
# for data in train_set:
# if data[3] is None:
# data[3] = 28
# print(train_set)
# s = 0
# for data in train_set:
# if data[-1] < 64:
# s += 1
#
# print(s)
# 以上是最初的數據分析
2.導入相關庫
import csvimport numpy as npimport matplotlib.pyplot as plt
3.讀入數據和數據處理
此部分函數實現以下內容:
讀入數據集
刪除分析中的不重要特徵
轉換數據格式為float
數據離散化:
性別以0,1區分 #此處其實更好的方法是one—hot處理,讀者可以自行研究年齡以25,31(60)為界限進行離散 #此處再優化分析時會在此說明sibsp和parcg加和後以2為界限,離散化fare以64為界限離散化
def loadDataset(filename):
with open(filename, 'r') as f:
lines = csv.reader(f)
data_set = list(lines)
if filename != 'titanic.csv':
for i in range(len(data_set)):
del(data_set[i][0])
# 整理數據
for i in range(len(data_set)):
del(data_set[i][0])
del(data_set[i][2])
data_set[i][4] += data_set[i][5]
del(data_set[i][5])
del(data_set[i][5])
del(data_set[i][6])
del(data_set[i][-1])
category = data_set[0]
del (data_set[0])
# 轉換數據格式
for data in data_set:
data[0] = int(data[0])
data[1] = int(data[1])
if data[3] != '':
data[3] = float(data[3])
else:
data[3] = None
data[4] = float(data[4])
data[5] = float(data[5])
# 補全缺失值 轉換記錄方式 分類
for data in data_set:
if data[3] is None:
data[3] = 28
# male : 1, female : 0
if data[2] == 'male':
data[2] = 1
else:
data[2] = 0
# age <25 為0, 25<=age<31為1,age>=31為2
if data[3] < 25:
data[3] = 0
elif data[3] >= 21 and data[3] < 60: # 但是測試得60分界準確率最高???!!!
data[3] = 1
else:
data[3] = 2
# sibsp&parcg以2為界限,小於為0,大於為1
if data[4] < 2:
data[4] = 0
else:
data[4] = 1
# fare以64為界限
if data[-1] < 64:
data[-1] = 0
else:
data[-1] = 1
return data_set, category
這一部分讀入數據要根據自己手中的數據來寫,無法直接應用
4.訓練集和測試集的劃分
常有hold-out,cross validation,bootstrapping法
hold—out
Hold-Out 是基本的劃分方法,字面意思就是「留出來一部分」,即將數據集直接按照一定比例劃分。
例如常用的 「2/8」 與 「3/7」,含義為訓練集 80% 或 70%,相應的測試集佔 20% 或 30%.
Hold-Out 的缺點較為明顯,即在驗證集上計算的出來的 最後評估指標與原始數據的順序有很大關係。
import randomimport csvimport pandas as pd
def loadDataset(filename, split, trainingSet = [], testSet = []):
with open(filename, 'r') as f:
lines = csv.reader(f)
dataset = list(lines)
for x in range(len(dataset)-1):
if random.random() < split: #將數據集隨機劃分
trainingSet.append(dataset[x])
else:
testSet.append(dataset[x])
if __name__ == "__main__":
train = []
test = []
loadDataset('', 0.7, train, test)
print(train)
print(test)
train2 = pd.DataFrame(data=train)
train2.to_csv('')
test2 = pd.DataFrame(data=test)
test2.to_csv('')
cross validation
簡單交叉驗證
將原始數據隨機分為兩組,一組做為訓練集,一組做為驗證集,利用訓練集訓練分類器,然後利用驗證集驗證模型,記錄最後的分類準確率為此分類器的性能指標。
好處:處理簡單,只需隨機把原始數據分為兩組即可
壞處:沒有達到交叉的思想,由於是隨機的將原始數據分組,所以最後驗證集分類準確率的高低與原始數據的分組有很大的關係,得到的結果並不具有說服性。
2-折交叉驗證(2-fold Cross Validation,記為2-CV)
做法是將數據集分成兩個相等大小的子集,進行兩次的分類器訓練。在第一次中,一個子集作為訓練集,另一個便作為測試集;在第二次訓練中,則將訓練集與測試集對換,
其並不常用:
主要原因是訓練集樣本數太少,不足以代表母體樣本的分布,導致te測試階段辨識率容易出現明顯落差。
K-折交叉驗證(K-fold Cross Validation,記為K-CV)
將原始數據分成K組(一般是均分),將每個子集數據分別做一次驗證集,其餘的K-1組子集數據作為訓練集,這樣會得到K個模型,用這K個模型最終的驗證集的分類準確率的平均數作為此K-CV下分類器的性能指標。K一般大於等於2,實際操作時一般從3開始取,只有在原始數據集合數據量小的時候才會嘗試取2。
應用最多,K-CV可以有效的避免過擬合與欠擬合的發生,最後得到的結果也比較具有說服性。
常見為十折交叉驗證
將數據集分成十份,輪流將其中9份作為訓練數據,1份作為測試數據,進行試驗。每次試驗都會得出相應的正確率。
10次的結果的正確率的平均值作為對算法精度的估計,一般還需要進行多次10折交叉驗證(例如10次10折交叉驗證),再求其均值,作為對算法準確性的估計
import csvimport pandas as pd
with open('titanic.csv', 'r') as f:
lines = csv.reader(f)
data_set = list(lines)del(data_set[0])length = len(data_set)//10print(length)data = []for i in range(10):
data.append(data_set[i*length:(i+1)*length])data.append(data_set[10*length:])
print(data_set)print(data)
bootstrapping(自助法)
最常用的一種Bootstrap自助法,假設給定的數據集包含d個樣本。該數據集有放回地抽樣m次,產生m個樣本的訓練集。這樣原數據樣本中的某些樣本很可能在該樣本集中出現多次。沒有進入該訓練集的樣本最終形成檢驗集(測試集)。 顯然每個樣本被選中的概率是1/m,因此未被選中的概率就是(1-1/m),這樣一個樣本在訓練集中沒出現的概率就是m次都未被選中的概率,即(1-1/m)^m^。當m趨於無窮大時,這一概率就將趨近於e^-1=0.368,所以留在訓練集中的樣本大概就佔原來數據集的63.2%。
例如:人工樣本為1,2,3;只有三個樣本,則可以從隨機變量X,分布為P(X=k)=1/3, k=1,2,3; 這樣的經驗分布中用計算機根據上述分布自動產生樣本,如產生5個樣本:1 2 3 2 1;也可以是:3 3 2 1 1
#自助法import numpy as np#任意設置一個數據集X = [1, 4, 3, 23, 4, 6, 7, 8, 9, 45, 67, 89, 34, 54, 76, 98, 43, 52]
#通過產生的隨機數獲得抽取樣本的序號bootstrapping = []for i in range(len(X)):
bootstrapping.append(np.floor(np.random.random()*len(X)))#通過序號獲得原始數據集中的數據D_1 = []for i in range(len(X)):
D_1.append(X[int(bootstrapping[i])])
5.樸素貝葉斯計算
這部分沒什麼好說的,按照公式計算就好
class NaiveBayes:
def __init__(self):
pass
def train(self, data):
length = len(data)
p = []
for i in range(len(data[0])):
p.append([])
# 計算先驗概率
sur = 0
for i in data:
if i[0] == 1:
sur += 1
death = length - sur
p1 = sur/length
p0 = 1 - p1
p[0].append(p1)
p[0].append(p0)
print(p)
# 計算pclass的條件概率
a1 = 0
a0 = 0
b1 = 0
b0 = 0
c1 = 0
c0 = 0
for i in data:
if i[0] == 1 and i[1] == 1:
a1 += 1
elif i[0] == 0 and i[1] == 1:
a0 += 1
elif i[0] == 1 and i[1] == 2:
b1 += 1
elif i[0] == 0 and i[1] == 2:
b0 += 1
elif i[0] == 1 and i[1] == 3:
c1 += 1
elif i[0] == 0 and i[1] == 3:
c0 += 1
p[1].append(a1 / sur)
p[1].append(a0 / death)
p[1].append(b1 / sur)
p[1].append(b0 / death)
p[1].append(c1 / sur)
p[1].append(c0 / death)
# 計算sex的條件概率
m1 = 0
m0 = 0
f1 = 0
f0 = 0
for i in data:
if i[0] == 1 and i[2] == 1:
m1 += 1
elif i[0] == 0 and i[2] == 1:
m0 += 1
elif i[0] == 1 and i[2] == 0:
f1 += 1
elif i[0] == 0 and i[2] == 0:
f0 += 1
p[2].append(m1 / sur)
p[2].append(m0 / death)
p[2].append(f1 / sur)
p[2].append(f0 / death)
# 計算age的條件概率
a1 = 0
a0 = 0
b1 = 0
b0 = 0
c1 = 0
c0 = 0
for i in data:
if i[0] == 1 and i[3] == 1:
a1 += 1
elif i[0] == 0 and i[3] == 1:
a0 += 1
elif i[0] == 1 and i[3] == 2:
b1 += 1
elif i[0] == 0 and i[3] == 2:
b0 += 1
elif i[0] == 1 and i[3] == 3:
c1 += 1
elif i[0] == 0 and i[3] == 3:
c0 += 1
p[3].append(a1 / sur)
p[3].append(a0 / death)
p[3].append(b1 / sur)
p[3].append(b0 / death)
p[3].append(c1 / sur)
p[3].append(c0 / death)
# 計算sibsp&parch條件概率
m1 = 0
m0 = 0
f1 = 0
f0 = 0
for i in data:
if i[0] == 1 and i[4] == 1:
m1 += 1
elif i[0] == 0 and i[4] == 1:
m0 += 1
elif i[0] == 1 and i[4] == 0:
f1 += 1
elif i[0] == 0 and i[4] == 0:
f0 += 1
p[4].append(m1 / sur)
p[4].append(m0 / death)
p[4].append(f1 / sur)
p[4].append(f0 / death)
m1 = 0
m0 = 0
f1 = 0
f0 = 0
for i in data:
if i[0] == 1 and i[5] == 1:
m1 += 1
elif i[0] == 0 and i[5] == 1:
m0 += 1
elif i[0] == 1 and i[5] == 0:
f1 += 1
elif i[0] == 0 and i[5] == 0:
f0 += 1
p[5].append(m1 / sur)
p[5].append(m0 / death)
p[5].append(f1 / sur)
p[5].append(f0 / death)
return p
def predict(self, t_data, p):
result = []
pp = []
for data in t_data:
p1 = p[0][0]
p0 = p[0][1]
# pclass
if data[1] == 1:
p1 *= p[1][0]
p0 *= p[1][1]
elif data[1] == 2:
p1 *= p[1][2]
p0 *= p[1][3]
elif data[1] == 3:
p1 *= p[1][4]
p0 *= p[1][5]
# sex
if data[2] == 1:
p1 *= p[2][0]
p0 *= p[2][1]
else:
p1 *= p[2][2]
p0 *= p[2][3]
# age
if data[3] == 0:
p1 *= p[3][0]
p0 *= p[3][1]
elif data[3] == 1:
p1 *= p[3][2]
p0 *= p[3][3]
else:
p1 *= p[3][4]
p0 *= p[3][5]
#sibsp&parch
if data[4] == 1:
p1 *= p[4][0]
p0 *= p[4][1]
else:
p1 *= p[4][2]
p0 *= p[4][3]
# fare
if data[5] == 1:
p1 *= p[5][0]
p0 *= p[5][1]
else:
p1 *= p[5][2]
p0 *= p[5][3]
pp.append(p1)
if p1 >= p0:
result.append(1)
else:
result.append(0)
return pp, result
6.模型評估1
此處評估標準選取ROC_AUC
先給出混淆矩陣:
ROC 圖像:
縱軸為 TPR 真正例率,預測為正且實際為正的樣本佔所有正例樣本的比例。
橫軸為 FPR 假正例率,預測為正但實際為負的樣本佔所有負例樣本的比例。
ROC圖像分析
第一個點,(0,1),即 FPR=0, TPR=1,這意味著 FN(false negative)=0,並且FP(false positive)=0。這意味著分類器很完美,因為它將所有的樣本都正確分類。
第二個點,(1,0),即 FPR=1,TPR=0,這個分類器是最糟糕的,因為它成功避開了所有的正確答案。
第三個點,(0,0),即 FPR=TPR=0,即 FP(false positive)=TP(true positive)=0,此時分類器將所有的樣本都預測為負樣本(negative)。
第四個點(1,1),分類器將所有的樣本都預測為正樣本。
對角線上的點表示分類器將一半的樣本猜測為正樣本,另外一半的樣本猜測為負樣本。
因此,ROC 曲線越接近左上角,分類器的性能越好。
怎麼畫ROC曲線
按 Score 從大到小排列
依次將每個 Score 設定為閾值,然後這 20 個樣本的標籤會變化,當它的 score 大於或等於當前閾值時,則為正樣本,否則為負樣本。
這樣對每個閾值,可以計算一組 FPR 和 TPR,此例一共可以得到 20 組。
當閾值設置為 1 和 0 時, 可以得到 ROC 曲線上的 (0,0) 和 (1,1) 兩個點。
怎麼計算AUC
def roc_auc(p, test, result):
tp = 0
tn = 0
for i in test:
if i[0] == 1:
tp += 1
else:
tn += 1
for i in range(len(test)):
test[i] = test[i][0]
p[i] = [p[i]]
p[i].append(test[i])
p[i].append(result[i])
p = sorted(p, reverse=True)
print(p)
tpr = [0]
fpr = [0]
length = len(p)
for i in range(1, length):
if p[i][1] == 1:
fpr.append(fpr[i-1])
tpr.append(tpr[i-1]+1/tp)
elif p[i][1] == 0:
fpr.append(fpr[i-1]+1/tn)
tpr.append(tpr[i-1])
tpr.append(1)
fpr.append(1)
print(fpr)
print(tpr)
auc = 0
for i in range(len(fpr)-1):
auc += (fpr[i+1]-fpr[i])*(tpr[i]+tpr[i+1])
auc = auc/2
print(auc)
fpr = np.array(fpr)
tpr = np.array(tpr)
plt.title("ROC curve of %s (AUC = %.4f)" % ('svm', auc))
plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate")
plt.plot(fpr, tpr)
plt.show()
7.模型評估2——F1_score
tp = 0
tn = 0
for i in range(len(result)):
if test[i][0] == result[i] == 1:
tp += 1
if test[i][0] == result[i] == 0:
tn += 1
print(tp)
print(tn)
f1 = (2*tp)/(len(test)+tp-tn)
print(f1)
roc_auc(pp, test, result)
8.評估結果
準確率:74.5%
F1 : 0.678
9.完整代碼
import csvimport numpy as npimport matplotlib.pyplot as plt
# 0PassengerId:乘客的ID 不重要# 1Survived:乘客是否獲救,Key:0=沒獲救,1=已獲救# 2Pclass:乘客船艙等級(1/2/3三個等級艙位)# 3Name:乘客姓名 不重要# 4Sex:性別# 5Age:年齡# 6SibSp:乘客在船上的兄弟姐妹/配偶數量# 7Parch:乘客在船上的父母/孩子數量# 8Ticket:船票號 不重要# 9Fare:船票價# 10Cabin:客艙號碼 不重要# 11Embarked:登船的港口 不重要
# 數據分析得: 年齡中位數28,缺失值補充為28。並且以25和31為界限分為三類# sibsp&parch按照有無分為兩類# 生還共計342人。其中全體票價和生還票價均值均約為32。# 生還者票價高於32的126人# 死亡共計549人。其中票價低於32的464人# 票價低於32共計680人,死亡率0.68# 票價低於64的共計773人,死亡512人 選擇以64為分界點
def loadDataset(filename):
with open(filename, 'r') as f:
lines = csv.reader(f)
data_set = list(lines)
if filename != 'titanic.csv':
for i in range(len(data_set)):
del(data_set[i][0])
# 整理數據
for i in range(len(data_set)):
del(data_set[i][0])
del(data_set[i][2])
data_set[i][4] += data_set[i][5]
del(data_set[i][5])
del(data_set[i][5])
del(data_set[i][6])
del(data_set[i][-1])
category = data_set[0]
del (data_set[0])
# 轉換數據格式
for data in data_set:
data[0] = int(data[0])
data[1] = int(data[1])
if data[3] != '':
data[3] = float(data[3])
else:
data[3] = None
data[4] = float(data[4])
data[5] = float(data[5])
# 補全缺失值 轉換記錄方式 分類
for data in data_set:
if data[3] is None:
data[3] = 28
# male : 1, female : 0
if data[2] == 'male':
data[2] = 1
else:
data[2] = 0
# age <25 為0, 25<=age<31為1,age>=31為2
if data[3] < 25:
data[3] = 0
elif data[3] >= 21 and data[3] < 60: # 但是測試得60分界準確率最高???!!!
data[3] = 1
else:
data[3] = 2
# sibsp&parcg以2為界限,小於為0,大於為1
if data[4] < 2:
data[4] = 0
else:
data[4] = 1
# fare以64為界限
if data[-1] < 64:
data[-1] = 0
else:
data[-1] = 1
return data_set, category
class NaiveBayes:
def __init__(self):
pass
def train(self, data):
length = len(data)
p = []
for i in range(len(data[0])):
p.append([])
# 計算先驗概率
sur = 0
for i in data:
if i[0] == 1:
sur += 1
death = length - sur
p1 = sur/length
p0 = 1 - p1
p[0].append(p1)
p[0].append(p0)
print(p)
# 計算pclass的條件概率
a1 = 0
a0 = 0
b1 = 0
b0 = 0
c1 = 0
c0 = 0
for i in data:
if i[0] == 1 and i[1] == 1:
a1 += 1
elif i[0] == 0 and i[1] == 1:
a0 += 1
elif i[0] == 1 and i[1] == 2:
b1 += 1
elif i[0] == 0 and i[1] == 2:
b0 += 1
elif i[0] == 1 and i[1] == 3:
c1 += 1
elif i[0] == 0 and i[1] == 3:
c0 += 1
p[1].append(a1 / sur)
p[1].append(a0 / death)
p[1].append(b1 / sur)
p[1].append(b0 / death)
p[1].append(c1 / sur)
p[1].append(c0 / death)
# 計算sex的條件概率
m1 = 0
m0 = 0
f1 = 0
f0 = 0
for i in data:
if i[0] == 1 and i[2] == 1:
m1 += 1
elif i[0] == 0 and i[2] == 1:
m0 += 1
elif i[0] == 1 and i[2] == 0:
f1 += 1
elif i[0] == 0 and i[2] == 0:
f0 += 1
p[2].append(m1 / sur)
p[2].append(m0 / death)
p[2].append(f1 / sur)
p[2].append(f0 / death)
# 計算age的條件概率
a1 = 0
a0 = 0
b1 = 0
b0 = 0
c1 = 0
c0 = 0
for i in data:
if i[0] == 1 and i[3] == 1:
a1 += 1
elif i[0] == 0 and i[3] == 1:
a0 += 1
elif i[0] == 1 and i[3] == 2:
b1 += 1
elif i[0] == 0 and i[3] == 2:
b0 += 1
elif i[0] == 1 and i[3] == 3:
c1 += 1
elif i[0] == 0 and i[3] == 3:
c0 += 1
p[3].append(a1 / sur)
p[3].append(a0 / death)
p[3].append(b1 / sur)
p[3].append(b0 / death)
p[3].append(c1 / sur)
p[3].append(c0 / death)
# 計算sibsp&parch條件概率
m1 = 0
m0 = 0
f1 = 0
f0 = 0
for i in data:
if i[0] == 1 and i[4] == 1:
m1 += 1
elif i[0] == 0 and i[4] == 1:
m0 += 1
elif i[0] == 1 and i[4] == 0:
f1 += 1
elif i[0] == 0 and i[4] == 0:
f0 += 1
p[4].append(m1 / sur)
p[4].append(m0 / death)
p[4].append(f1 / sur)
p[4].append(f0 / death)
m1 = 0
m0 = 0
f1 = 0
f0 = 0
for i in data:
if i[0] == 1 and i[5] == 1:
m1 += 1
elif i[0] == 0 and i[5] == 1:
m0 += 1
elif i[0] == 1 and i[5] == 0:
f1 += 1
elif i[0] == 0 and i[5] == 0:
f0 += 1
p[5].append(m1 / sur)
p[5].append(m0 / death)
p[5].append(f1 / sur)
p[5].append(f0 / death)
return p
def predict(self, t_data, p):
result = []
pp = []
for data in t_data:
p1 = p[0][0]
p0 = p[0][1]
# pclass
if data[1] == 1:
p1 *= p[1][0]
p0 *= p[1][1]
elif data[1] == 2:
p1 *= p[1][2]
p0 *= p[1][3]
elif data[1] == 3:
p1 *= p[1][4]
p0 *= p[1][5]
# sex
if data[2] == 1:
p1 *= p[2][0]
p0 *= p[2][1]
else:
p1 *= p[2][2]
p0 *= p[2][3]
# age
if data[3] == 0:
p1 *= p[3][0]
p0 *= p[3][1]
elif data[3] == 1:
p1 *= p[3][2]
p0 *= p[3][3]
else:
p1 *= p[3][4]
p0 *= p[3][5]
#sibsp&parch
if data[4] == 1:
p1 *= p[4][0]
p0 *= p[4][1]
else:
p1 *= p[4][2]
p0 *= p[4][3]
# fare
if data[5] == 1:
p1 *= p[5][0]
p0 *= p[5][1]
else:
p1 *= p[5][2]
p0 *= p[5][3]
pp.append(p1)
if p1 >= p0:
result.append(1)
else:
result.append(0)
return pp, result
def roc_auc(p, test, result):
tp = 0
tn = 0
for i in test:
if i[0] == 1:
tp += 1
else:
tn += 1
for i in range(len(test)):
test[i] = test[i][0]
p[i] = [p[i]]
p[i].append(test[i])
p[i].append(result[i])
p = sorted(p, reverse=True)
print(p)
tpr = [0]
fpr = [0]
length = len(p)
for i in range(1, length):
if p[i][1] == 1:
fpr.append(fpr[i-1])
tpr.append(tpr[i-1]+1/tp)
elif p[i][1] == 0:
fpr.append(fpr[i-1]+1/tn)
tpr.append(tpr[i-1])
tpr.append(1)
fpr.append(1)
print(fpr)
print(tpr)
auc = 0
for i in range(len(fpr)-1):
auc += (fpr[i+1]-fpr[i])*(tpr[i]+tpr[i+1])
auc = auc/2
print(auc)
fpr = np.array(fpr)
tpr = np.array(tpr)
plt.title("ROC curve of %s (AUC = %.4f)" % ('svm', auc))
plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate")
plt.plot(fpr, tpr)
plt.show()
if __name__ == "__main__":
# 以下是最初的數據分析
# train_set, category = loadDataset('titanic.csv')
# print(category)
# print(train_set)
# k = [0, 0, 0, 0, 0, 0]
# min = train_set[0][3]
# max = 0
# age = []
# for data in train_set:
# for i in range(len(category)):
# if data[i] is None:
# k[i] += 1
# if data[3] is not None:
# if data[3] < min:
# min = data[3]
# if data[3] > max:
# max = data[3]
# age.append(data[3])
#
# age = sorted(age)
#
# print(category)
# print(k)
# print(min)
# print(max)
# print(age)
# print((len(train_set)-k[3])/2) #357
# print(age[int((len(train_set)-k[3])/2)]) # 28
# print(len(age)/3)
# print(age[297]) # 25
# print(age[297*2]) # 31
# for data in train_set:
# if data[3] is None:
# data[3] = 28
# print(train_set)
# s = 0
# for data in train_set:
# if data[-1] < 64:
# s += 1
#
# print(s)
# 以上是最初的數據分析
train_set, category = loadDataset('titanic_train.csv')
test_set, category = loadDataset('titanic_test.csv')
print(category)
print(train_set)
bayes = NaiveBayes()
p = bayes.train(train_set)
print(p)
pp, result = bayes.predict(test_set, p)
print(result)
count = 0
for i in range(len(result)):
if test_set[i][0] == result[i]:
count += 1
accuracy = count/len(test_set)
print(accuracy)
# # 十折交叉驗證
# data_set, category = loadDataset('titanic.csv')
#
# length = len(data_set) // 10
# print(length)
# data = []
# for i in range(10):
# data.append(data_set[i * length:(i + 1) * length])
# data.append(data_set[10 * length:])
#
# sum = 0
#
# for i in range(9):
# p = bayes.train(data[i])
# print(p)
#
# pp, result = bayes.predict(data[9], p)
# print(result)
# count = 0
# for i in range(len(result)):
# if test_set[i][0] == result[i]:
# count += 1
# sum += count/len(data[9])
#
# print(sum/10)
# 自助法
data_set, category = loadDataset('titanic.csv')
bootstrapping = []
for i in range(len(data_set)):
bootstrapping.append(np.floor(np.random.random()*len(data_set)))
test = []
for i in range(len(data_set)):
test.append(data_set[int(bootstrapping[i])])
p = bayes.train(data_set)
print(p)
pp, result = bayes.predict(test, p)
print(result)
count = 0
for i in range(len(result)):
if test[i][0] == result[i]:
count += 1
accuracy = count / len(test)
print(accuracy)
#基於自助法計算F1-score
tp = 0
tn = 0
for i in range(len(result)):
if test[i][0] == result[i] == 1:
tp += 1
if test[i][0] == result[i] == 0:
tn += 1
print(tp)
print(tn)
f1 = (2*tp)/(len(test)+tp-tn)
print(f1)
roc_auc(pp, test, result)
五、模型優化與提升
性別數值化更好的方法是one—hot處理
年齡離散化的標準。後續再測試的過程中發現,年齡的離散化標準對模型的準確率影響極大,這一點可以在探索後進行優化進而提升模型效果
對於貝葉斯的概率計算時,原方法是只計算分子進行比較。但是我們可以換一種方法,計算完整的概率值,然後與0.5進行比較,然後分類。這種方法再結合更加合適的年齡劃分標準可以是本地的準確率達到90%以上,在kaggle上也可以達到80%以上
這幾種優化思路提供給讀者自行探索嘗試。