人工智能課要求使用樸素貝葉斯算法,決策樹算法,人工神經網絡,支持向量機算法對數據進行分類python
文件:算法
文件內容數組
標籤 | 種類 |
---|---|
buying | low, med, high, vhigh |
maint | low, med, high, vhigh |
doors | 2, 3, 4, 5more |
persons | 2, 4, more |
lugBoot | small, med, big |
safety | low, med, high |
classValue | unacc, acc, good, vgood |
import numpy as np
trans = {
0: {'low': 0, 'med': 1, 'high': 2, 'vhigh': 3}, # buying
1: {'low': 0, 'med': 1, 'high': 2, 'vhigh': 3}, # maint
2: {'2': 0, '3': 1, '4': 2, '5more': 3}, # doors
3: {'2': 0, '4': 1, 'more': 2}, # persons
4: {'small': 0, 'med': 1, 'big': 2}, # lugBoots
5: {'low': 0, 'med': 1, 'high': 2}, # safety
6: {'unacc': 0, 'acc': 1, 'good': 2, 'vgood': 3} # classValue
}
def make_one_hot(data: list) -> np.array:
return (np.arange(4)==data[:,None]).astype(np.integer)
def getData(filename: str, onehot: bool=False) -> list:
""" 若是非one-hot編碼,返回的形狀爲(x, 7); 不然返回(x, 7, 4) """
data = []
with open(filename) as f:
f.readline() # 讀取第一行
for line in f.readlines():
tmp = line.strip().split(',')
for i, t in enumerate(tmp):
tmp[i] = trans[i][t]
tmp = np.array(tmp)
if onehot:
tmp = make_one_hot(tmp)
data.append(tmp)
return np.array(data)
# 訓練集
train_data = getData("test.csv")
train_data_1hot = getData("test.csv", True)
# 訓練集參數&標籤
train_paras, train_tags = train_data[:,:6], train_data[:,6]
# 訓練集參數(one-hot),標籤不須要one-hot
train_1hot_paras = train_data_1hot[:, :6, :]
# 測試集
test_data = getData("predict.csv")
test_data_1hot = getData("predict.csv", True)
# 測試集參數&標籤
test_paras, test_tags = test_data[:, :6], test_data[:, 6]
# 測試集參數(one-hot),標籤不須要one-hot
test_1hot_paras = test_data_1hot[:, :6, :]
# 給個樣例
print("非one-hot參數")
print(test_paras[0])
print("非one-hot標籤")
print(test_tags[0])
print("\none-hot參數")
print(test_1hot_paras[0])
複製代碼
非one-hot參數
[0 3 2 0 0 0]
非one-hot標籤
0
one-hot參數
[[1 0 0 0]
[0 0 0 1]
[0 0 1 0]
[1 0 0 0]
[1 0 0 0]
[1 0 0 0]]
複製代碼
定義好了輸入,在定義一個統一的評估方式吧。最簡單的方式是計算其正確率。其定義爲:bash
其中T表示預測正確的樣例數量,F表示預測錯誤的樣例數量。網絡
因爲預測種類和本來標籤的種類都有4種,因能獲得16種組合,這裏集合T包含那些預測和輸出相等的組合。能夠將這16種組合顯示出來,左斜對角線上的值越大,說明分類效果越好。app
def predict_matrix(predicted, tags) -> np.ndarray:
""" 返回預測矩陣 """
mtx = np.zeros((4,4)) # 4*4矩陣
for i, p in enumerate(predicted):
mtx[p][tags[i]] += 1
return mtx
def Accuracy(predict, tags, show_mtx=False) -> float:
""" 返回準確率 """
mtx = predict_matrix(predict, tags)
if show_mtx:
print(mtx)
T = mtx[0][0] + mtx[1][1] + mtx[2][2] + mtx[3][3]
return T / len(predict)
# 舉個例子:
pred = np.random.randint(0, 4, 100)
tags = np.random.randint(0, 4, 100)
print("隨機數:")
print("accuracy: %.4f" % Accuracy(pred, tags, True))
複製代碼
隨機數:
[[ 6. 4. 3. 8.]
[ 2. 3. 7. 10.]
[ 3. 4. 4. 8.]
[10. 7. 10. 11.]]
accuracy: 0.2400
複製代碼
樸素貝葉斯就一個公式框架
若是加入獨立性假設的話,能夠將全部的分離dom
因爲對於全部的y,老是相同的,所以能夠化簡爲:ide
那麼對於給定的,求最可能的y,就是枚舉全部的y,讓機率最大便可:函數
sklearn提供了多種樸素貝葉斯分類器:高斯樸素貝葉斯GaussianNB、多項式樸素貝葉斯MultinomialNB、伯努利樸素貝葉斯BernoulliNB、補充樸素貝葉斯ComplementNB。不一樣貝葉斯分類器雖然使用的權值計算方式不一樣,也就是對分佈的假設不一樣,可是核心原理都大同小異。
這裏使用高斯樸素貝葉斯分類器,其主要參數有兩種:
每一個類的先驗機率,能夠不提供。若是提供則程序不會根據輸入自動計算
爲計算穩定性而添加的全部要素的最大方差的一部分。
from sklearn.naive_bayes import GaussianNB
NB_clf = GaussianNB()
NB_clf.fit(train_paras, train_tags)
# 樸素貝葉斯預測
# 準確率
acc_on_train = Accuracy(NB_clf.predict(train_paras), train_tags, True)
print("Accuracy of Naive Bayes working on training set is %.4f" % acc_on_train)
acc_on_test = Accuracy(NB_clf.predict(test_paras), test_tags, True)
print("Accuracy of Naive Bayes working on testing set is %.4f" % acc_on_test)
複製代碼
[[852. 109. 0. 0.]
[ 41. 136. 0. 0.]
[ 58. 10. 23. 2.]
[ 39. 56. 0. 24.]]
Accuracy of Naive Bayes working on training set is 0.7667
[[217. 38. 9. 0.]
[ 3. 35. 37. 39.]
[ 0. 0. 0. 0.]
[ 0. 0. 0. 0.]]
Accuracy of Naive Bayes working on testing set is 0.6667
複製代碼
上面的結果不是很好。所以須要設置參數讓樸素貝葉斯算法發揮更大的做用。若是須要更加精確的priors,那麼首先須要知道總樣本的機率分配,也就是從「data.csv」中知道全部的信息,可是這樣就失去了「訓練」的意義,不能正確反映出模型的 泛化能力 。所以選擇不設置priors。
另外一個參數的意義不太明確,不知道是否是限制最大的方差?可是爲了求解能夠在必定範圍內離散枚舉,選擇讓正確率最大的var_smoothing。
# Setting the var—smoothing
best_smooths = 0
accuracy_NB = 0
for smooth in np.linspace(1e-2, 1, 500):
# Set and fit
NB_clf.set_params(var_smoothing=smooth)
NB_clf.fit(test_paras, test_tags)
# Test
pred = NB_clf.predict(test_paras)
acc = Accuracy(pred, test_tags)
if acc > accuracy_NB:
accuracy_NB = acc
best_smooths = smooth
print(
"Var_smoothing = %f leads to best accuracy %.4f" % (
best_smooths,
accuracy_NB
)
)
複製代碼
Var_smoothing = 0.053647 leads to best accuracy 0.8254
複製代碼
看來使用高斯樸素貝葉斯所能達到的最好的結果是正確率爲82.54%,這個結果並非特別優秀。下面再試試看其餘的分類器。
決策樹是一棵遞歸生成的樹,具備較強的泛化能力。
構建決策樹的關鍵在於對於每一個維度的劃分,每一次劃分都會產生一些樣本純度更高的子結點,也就是子結點中樣本的類別儘量統一。
評價一些樣本「純度」經常使用的指標是信息熵
和基尼係數
其中是X中類別i的比例。這兩個指標的特色都是當樣本的類別越統一,指標給出的值越小,當樣本徹底屬於同一類別時,指標爲0。
有了衡量樣本「純度」的標準,就能夠定義屬性分類訓練數據的效力的度量標準——信息增益。一個屬性的信息增益就是因爲使用該屬性劃分樣本致使的指望的熵下降程度。一個屬性A相對於樣本集合S的信息增益定義爲:
實際上這個公式就是將原來的總信息熵減去各子結點信息熵的加權平均數。
剪枝(pruning)是決策樹學習算法對付「過擬合」的主要手段。在決策樹學習中,爲了儘量正確分類訓練樣本,節點劃分過程不斷重複,有時會形成決策樹分支過多,這時就可能因訓練樣本學得「太好」了,以致於把訓練樣本自身的一些特色看成全部數據都具備的通常性質而致使過擬合。所以,可經過主動去掉一些分支來下降過擬合的風險。
訓練、測試決策樹均使用非one-hot編碼的數據集和測試集。
這裏使用sklearn中的DecisionTreeClassifier類,訓練函數fit
通常只須要傳入測試集和標籤便可。
DecisionTreeClassifier在實例化的時候能夠接受幾個參數,用於定製不一樣的分類器。其中參數criterion選擇評估分類的函數,有基尼係數gini和信息熵entropy兩種。
上面提到了決策樹算法容易過擬合,所以須要剪枝,在實例化分類器的時候能夠選擇設置以下參數:
這個參數定義了決策樹的最大深度,若是沒有設置這個參數,那麼決策樹結點會一直分類直到該結點中樣本的類別相同或者樣本數小於min_samples_split
分裂一個結點至少須要的樣本數
成爲一個結點至少須要的樣本數
成爲一個結點至少須要的權重和(該結點樣本的權重佔全部樣本的權重)
上面的這些參數不可能一拍腦殼就能想出最優的,這個須要結合實際訓練結果慢慢嘗試。
from sklearn import tree
tree_clf = tree.DecisionTreeClassifier(
criterion="entropy",
max_depth=10,
min_samples_split=3,
min_samples_leaf=2
) # 實例化決策樹分類器
tree_clf.fit(train_paras, train_tags) # 這樣就設置好了一個分類器
# 決策樹預測
# 準確率
acc_on_train = Accuracy(tree_clf.predict(train_paras), train_tags, True)
print("Accuracy of Naive Bayes working on training set is %.4f" % acc_on_train)
acc_on_test = Accuracy(tree_clf.predict(test_paras), test_tags, True)
print("Accuracy of Naive Bayes working on testing set is %.4f" % acc_on_test)
複製代碼
[[990. 8. 1. 0.]
[ 0. 303. 1. 1.]
[ 0. 0. 21. 1.]
[ 0. 0. 0. 24.]]
Accuracy of Naive Bayes working on training set is 0.9911
[[220. 13. 2. 0.]
[ 0. 60. 23. 14.]
[ 0. 0. 21. 1.]
[ 0. 0. 0. 24.]]
Accuracy of Naive Bayes working on testing set is 0.8598
複製代碼
上面的預測結果還不錯,僅隨意設置的參數就能超越上面樸素貝葉斯分類器的結果,可是隻憑一次預測並不能知道決策樹的最佳結果。爲了尋找最合適的參數,能夠在參數空間進行枚舉,記錄最佳的參數搭配。
criterion = ""
best_deep = 0
best_split = 0
best_leaf = 0
accuracy_tree = 0.0
for cri in ["entropy", "gini"]:
for deep in range(15, 5, -1):
for sp in range(2, 5):
for leaf in range(1, sp):
tree_clf.set_params( # 設置參數
criterion = cri,
max_depth=deep,
min_samples_split=sp,
min_samples_leaf=leaf
)
tree_clf.fit(train_paras, train_tags) # 訓練
# 測試
pred = tree_clf.predict(test_paras)
acc = Accuracy(pred, test_tags)
if acc >= accuracy_tree:
accuracy_tree = acc
criterion = cri
best_deep = deep
best_split = sp
best_leaf = leaf
print(
"Best accuracy is %.4f when\ncriterion is %s\nmax_depth = %d\nmin_samples_split = %d\nmin_samples_leaf = %d" % (
accuracy_tree,
criterion,
best_deep,
best_split,
best_leaf
)
)
複製代碼
Best accuracy is 0.8783 when
criterion is gini
max_depth = 11
min_samples_split = 2
min_samples_leaf = 1
複製代碼
決策樹最終的分類結果比樸素貝葉斯分類器要好。
須要用到的第三方庫graphviz。安裝方法爲:
conda install python-graphviz
複製代碼
使用方法以下,最後會在當前目錄下生成一個PDF文件。
import os
if os.name == 'posix':
print("不支持")
else:
import graphviz
tree_clf.set_params(
criterion = criterion,
max_depth=best_deep,
min_samples_split=best_split,
min_samples_leaf=best_leaf
)
dot_data = tree.export_graphviz(tree_clf, out_file=None)
graph = graphviz.Source(dot_data)
graph.render("House")
print("生成成功")
複製代碼
生成成功
複製代碼
支持向量機的思想是在一個多維空間裏尋找一個超平面,而且讓離這個超平面最近的點到超平面的距離最大。
給定的訓練樣本形如:
SVM尋找的最佳超平面能夠用以下方程描述:
表示法向量,表示超平面距離原點的距離。
通常狀況下SVM作的事二分類任務,所以 經過縮放 ,可讓正樣例距離超平面的距離和負樣例相同,且都爲1,而且正樣例分佈在超平面之上,負樣例在超平面之下。距離超平面最近的正負樣例能讓這個不等式取等號,這些樣例表明的向量就稱爲 「支持向量」 。
超平面和超平面之間的距離記做,SVM作得就是讓這個最大化。的距離很好計算:
這個表達式說明求最大距離只和有關。求知足要求的和就是SVM的工做,求解方法是用拉格朗日方法。
支持向量機的優勢有不少:
決策樹所作的事就是讓全部的類別儘量分開,可是SVM不只讓這些點分開,還儘量讓分類作得容錯能力大,所以 指望SVM的效果會好於決策樹。
可是一樣它也具備一些缺點:若是特徵的數量遠大於樣本的數量,則容易過擬合,此時正則化項是相當重要的。
sklearn庫提供了多種SVM,有SVC、NuSVC、LinearSVC等。不過決策樹的參數不少,有以下這些:
先來嘗試一下隨意設置的參數的效果:
from sklearn import svm
SVM_clf = svm.SVC(
gamma='auto',
decision_function_shape='ovr'
)
SVM_clf.fit(train_paras, train_tags)
# SVM預測
# 準確率
acc_on_train = Accuracy(SVM_clf.predict(train_paras), train_tags, True)
print("Accuracy of SVM working on training set is %.4f" % acc_on_train)
acc_on_test = Accuracy(SVM_clf.predict(test_paras), test_tags, True)
print("Accuracy of SVM working on testing set is %.4f" % acc_on_test)
複製代碼
[[975. 8. 0. 0.]
[ 15. 303. 4. 2.]
[ 0. 0. 19. 0.]
[ 0. 0. 0. 24.]]
Accuracy of SVM working on training set is 0.9785
[[217. 8. 1. 0.]
[ 2. 62. 22. 13.]
[ 1. 3. 22. 0.]
[ 0. 0. 1. 26.]]
Accuracy of SVM working on testing set is 0.8651
複製代碼
上述隨意配置的SVM已經得到了較好的結果。
使用多邊形核函數poly,嘗試最佳的參數組合。這裏改動空間比較大的選項是degree,而且可視化訓練出來的SVM在訓練集和測試集上的性能。
import matplotlib.pyplot as plt
degrees = [i for i in range(1, 8)]
train_acc = []
test_acc = []
best_degree = 0
best_acc = 0
best_mtx = None
for degree in degrees:
# 配置參數
SVM_clf.set_params(
kernel='poly',
degree=degree,
gamma='auto',
decision_function_shape='ovo'
)
# 訓練
SVM_clf.fit(train_paras, train_tags)
# 測試結果
acc1 = Accuracy(SVM_clf.predict(train_paras), train_tags)
acc2 = Accuracy(SVM_clf.predict(test_paras), test_tags)
# print(acc1, acc2)
train_acc.append(acc1)
test_acc.append(acc2)
if acc2 > best_acc:
best_degree = degree
best_acc = acc2
plt.plot(degrees, train_acc, 'c')
plt.plot(degrees, test_acc, 'r')
plt.show()
print("Best accuracy is %.4f when degree is %d." % (best_acc, best_degree))
複製代碼
Best accuracy is 0.8995 when degree is 2.
複製代碼
如上配置的SVM在訓練集(青色線條)和測試集(紅色線條)上的準確率變化如圖所示,並最終獲得的準確率爲89.95%,是前幾個中效果最好的。
人工神經網絡已經用了不少次了,這裏使用PyTorch框架來實現一個全鏈接神經網絡。而且這個數據集相對來講是比較簡單的,每一個樣本僅有6個維度,所以不須要定義太多的隱含層,可是能夠嘗試使用線性和非線性兩種激活函數。
import torch
from torch import nn
import torch.optim as optim
from torch.autograd import Variable
# 定義神經網絡
# 三層全鏈接
class Net1(nn.Module):
def __init__(self, in_dim, hidden1, hidden2, out_dim):
super(Net1, self).__init__()
self.layers = nn.Sequential(
nn.Linear(in_dim, hidden1),
nn.Linear(hidden1, hidden2),
nn.Linear(hidden2, out_dim)
)
def forward(self, x):
return self.layers(x)
# 三層全鏈接+ReLU激活
class Net2(nn.Module):
def __init__(self, in_dim, hidden1, hidden2, out_dim):
super(Net2, self).__init__()
self.layers = nn.Sequential(
nn.Linear(in_dim, hidden1),
nn.ReLU(inplace=True),
nn.Linear(hidden1, hidden2),
nn.ReLU(inplace=True),
nn.Linear(hidden2, out_dim)
)
def forward(self, x):
return self.layers(x)
# 三層全鏈接+Sigmoid
class Net3(nn.Module):
def __init__(self, in_dim, hidden1, hidden2, out_dim):
super(Net3, self).__init__()
self.layers = nn.Sequential(
nn.Linear(in_dim, hidden1),
nn.Sigmoid(),
nn.Linear(hidden1, hidden2),
nn.Sigmoid(),
nn.Linear(hidden2, out_dim)
)
def forward(self, x):
return self.layers(x)
複製代碼
上面定義了三個神經網絡結構:
不過這些網絡還處於定義階段,沒有被實例化,實例化的時候須要考慮輸入數據的類型和形狀。輸入數據的類型和形狀以下:
paras: 6 * 4
[[1 0 0 0]
[0 0 0 1]
[0 0 1 0]
[1 0 0 0]
[1 0 0 0]
[1 0 0 0]]
tags: 1 * 4
[1 0 0 0]
複製代碼
因爲每一個神經元能夠輸入一個數值,所以首先須要將6*4
的tensor展平爲1*24
,所以輸入層須要24個神經元,也就是in_dim=24
。一樣的輸出包含四個維度,所以out_dim=4
。中間層的輸出沒有太大的規定,可是不能包含太多的神經元——防止神經元將輸入數據所有學習致使過擬合。
而後接下來就要開始訓練了,輸入輸出的數據首先轉化爲可保存梯度的Variable類型。訓練的時候將train_1hot_paras輸入,將網絡輸出的結果和train_1hot_tags進行對比,得出的偏差反傳,將這樣的步驟反覆幾回就能提升準確率。
輸入的時候通常會將樣本組織爲batch * dim
的形式,經過增長tensor的維度能夠同時輸入多個樣本一塊兒訓練,而後反傳偏差。這樣作的好處不只能夠抵消單樣本帶來的偏差,當計算髮生在GPU上的時候還能夠加速訓練。
因爲使用one-hot編碼,使用CrossEntropy損失函數,形式以下:
該損失函數的接受兩個參數,第一個參數x是one-hot類型的模型輸出向量,第二個參數class是標籤中對該樣本的分類。
使用反向傳播方式訓練參數,參數的優化器使用Adam,這是一種自動調整學習率的優化器。爲了看到模型訓練過程當中的性能變化,在每次訓練完一個epoch以後同時檢驗模型在訓練集和測試集上的準確率,最終繪製偏差曲線圖。
def Train_Test(net, epoch:int, batch_size:int, lr:float):
epochs = [i for i in range(epoch)]
train_acc, test_acc = [], []
best_acc = 0
criterion = nn.CrossEntropyLoss() # Loss function
optimizer = optim.Adam(net.parameters(), lr=lr)
for epoch in epochs:
# 訓練
for i in range(0, len(train_1hot_paras), batch_size):
optimizer.zero_grad()
inputs = Variable(torch.FloatTensor(train_1hot_paras[i: i+batch_size])).view(-1, 24)
targets = Variable(torch.LongTensor(train_tags[i: i+batch_size]))
outputs = net(inputs)
loss = criterion(outputs, targets)
loss.backward() # 梯度反傳
optimizer.step() # 學習
# 檢驗訓練集
inputs = Variable(torch.FloatTensor(train_1hot_paras)).view(-1, 24)
outputs = net(inputs)
predict = outputs.topk(1)[1].view(-1)
acc = Accuracy(predict, train_tags)
train_acc.append(acc)
# 檢驗測試集
inputs = Variable(torch.FloatTensor(test_1hot_paras)).view(-1, 24)
outputs = net(inputs) # 須要將輸出的one-hot分類轉化爲普通的分類
predict = outputs.topk(1)[1].view(-1)
acc = Accuracy(predict, test_tags)
test_acc.append(acc)
best_acc = max(acc, best_acc)
print("Best accuracy on testing set is %.4f" % best_acc)
plt.plot(epochs, train_acc, 'c')
plt.plot(epochs, test_acc, 'r')
plt.show()
複製代碼
線性全鏈接網絡使用較小的epoch,並增長隱藏層的結點數量。
net1 = Net1(in_dim=24, hidden1=8, hidden2=8, out_dim=4)
Train_Test(net1, epoch=200, batch_size=8, lr=1e-2)
複製代碼
Best accuracy on testing set is 0.9286
複製代碼
使用ReLU激活的網絡設置較少的隱藏層結點,並增長batch的大小,以免過擬合。
net2 = Net2(in_dim=24, hidden1=8, hidden2=6, out_dim=4)
Train_Test(net2, epoch=200, batch_size=16, lr=1e-2)
複製代碼
Best accuracy on testing set is 0.8942
複製代碼
使用Sigmoid激活的網絡,一樣設置較少的隱藏層結點,並增長batch的大小,以免過擬合。
net3 = Net2(in_dim=24, hidden1=6, hidden2=5, out_dim=4)
Train_Test(net3, epoch=200, batch_size=16, lr=1e-2)
複製代碼
Best accuracy on testing set is 0.9153
複製代碼
神經網絡的訓練結果存在不肯定性,而且其最終的性能不只和模型結構有關,甚至和訓練中使用的超參數有很大的關係。可是可以看出神經網絡的最佳性能至少比前三種分類器的性能好。
最後上述4中分類方法在訓練集上的效果以下:
分類器 | 準確率 |
---|---|
樸素貝葉斯 | 82.54% |
決策樹 | 87.83% |
SVM | 89.95% |
人工神經網絡 | 89%-93% |
其實在不少分類任務中,不一樣參數的權重是不同的,若是能提早知道這些參數各自的權重,那麼性能上會有很大的提高。上面用到的4中分類方法不少函數都提供了權重參數,只不過沒有用到。
若是有一個分類任務須要得到更優秀的分類性能,那麼不妨在使用分類任務前,對樣本預先分析,好比查看各個樣本不一樣參數的分佈狀況等。
在使用神經網絡的時候,樣本的輸入順序是一直不變(偷懶),若是爲了獲得更好的分類結果,應該每一次都將訓練數據集打亂。
還有一種比較可行的方式就是同時使用多種分類器,當不一樣分類器輸出結果後,綜合各分類器的建議,而後選擇合適的結果。實際上有一種分類算法: 隨機森林 的核心思想就是這樣的。