人工智能课要求使用朴素贝叶斯算法,决策树算法,人工神经网络,支持向量机算法对数据进行分类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中分类方法不少函数都提供了权重参数,只不过没有用到。
若是有一个分类任务须要得到更优秀的分类性能,那么不妨在使用分类任务前,对样本预先分析,好比查看各个样本不一样参数的分布状况等。
在使用神经网络的时候,样本的输入顺序是一直不变(偷懒),若是为了获得更好的分类结果,应该每一次都将训练数据集打乱。
还有一种比较可行的方式就是同时使用多种分类器,当不一样分类器输出结果后,综合各分类器的建议,而后选择合适的结果。实际上有一种分类算法: 随机森林 的核心思想就是这样的。