决策树 BP神经网络(BPNN) SVM实现iris鸢尾花数据集的分类
决策树
实现流程(详见代码)::
1. 连续值处理
iris数据集是连续数据, 因为连续属性的可取值数目不再有限,因此不能像处理离散属性枚举离散属性取值来对结点进行划分。因此需要连续属性离散化,这里对数据集的离散化策略是二分法,
具体方法:
将连续属性a的取值从小到大排序, 将相邻的属性值的中位数作为候选划分点, 这样,n个属性值可得到n-1个候选划分点, 计算样本集基于每个划分点t二分后的信息增益, 选择信息增益最大的候选划分点作为划分点(实际操作中只计算信息增益公式的减数部分)
2. 计算样本集合的信息熵
3. 计算属性的信息增益(由于信息熵相同, 每个属性实际只计算了公式的减数部分)
4. 选择信息增益最大公式(减数部分最小)的属性作为根节点
5. 对每个分支进一步划分, 使用递归计算其他节点
6. 模型评估
from sklearn import datasets
import math
import numpy as np
import sys
#获得属性的每个值的熵
def getEntropy(counter):
res = 0
denominator = np.sum(counter)
if denominator == 0:
return 0
for value in counter:
if value == 0:
continue
res += value / denominator * math.log2(value / denominator if value > 0 and denominator > 0 else 1)
return -res
#随机抽取80%的训练集和20%的测试集
def divideData():
completeData = np.c_[iris.data, iris.target.T]
np.random.shuffle(completeData)
trainData = completeData[range(int(length * 0.8)), :]
testData = completeData[range(int(length * 0.8), length), :]
return [trainData, testData]
#采用二分法对连续属性离散化
#取连续集每两个数的中位点作为候选划分点,计算每个候选划分点的信息增益(这里只计算熵),获得最佳的划分点
def discretization(index):
temp = np.array(iris.data[:, index])
temp= temp[temp[:].argsort()]
feature=[]
#计算小于划分点和大于划分点的结果数量集
for i in range(len(temp)-1):
counter1=[0,0,0]
counter2=[0,0,0]
counter1[0]=np.sum(iris.target[:i+1]==0)
counter1[1] = np.sum(iris.target[:i + 1] == 1)
counter1[2] = np.sum(iris.target[:i + 1] == 2)
counter2[0] = np.sum(iris.target[i + 1:] == 0)
counter2[1] = np.sum(iris.target[i + 1:] == 1)
counter2[2] = np.sum(iris.target[i + 1:] == 2)
feature.append([(temp[i]+temp[i+1])/2,counter1,counter2])
minEntropy=sys.maxsize
razor=feature[0][0]
#对每个划分点计算熵
for i in range(len(feature)):
leng=i+1
remain = length - leng
d1=getEntropy(feature[i][1])
d2=getEntropy(feature[i][2])
remain=length-leng
entropy=(leng/length)*d1+(remain/length)*d2
#选择熵最小(即信息增益最大)的特征作为划分点
if entropy<minEntropy:
minEntropy=entropy
razor=feature[i][0]
return razor
#连续值分割
def getRazor():
a = []
for i in range(len(iris.feature_names)):
a.append(discretization(i))
print("切割点: ",a)
return np.array(a)
#寻找最大索引
def findMaxIndex(dataSet):
maxIndex = 0
maxValue = -1
for index, value in enumerate(dataSet):
if value > maxValue:
maxIndex = index
maxValue = value
return maxIndex
#递归生成树
#featureSet: 特征集, dataSet: 数据集, counterSet: 三种花的个数
def tree(featureSet, dataSet, counterSet):
if (counterSet[0] == 0 and counterSet[1] == 0 and counterSet[2] != 0):
return iris.target_names[2]
if (counterSet[0] != 0 and counterSet[1] == 0 and counterSet[2] == 0):
return iris.target_names[0]
if (counterSet[0] == 0 and counterSet[1] != 0 and counterSet[2] == 0):
return iris.target_names[1]
if len(featureSet) == 0:
return iris.target_names[findMaxIndex(counterSet)]
if len(dataSet) == 0:
return []
res = sys.maxsize
final = 0
for feature in featureSet:
i = razors[feature]
set1 = []
set2 = []
counter1 = [0, 0, 0]
counter2 = [0, 0, 0]
for data in dataSet:
index = int(data[-1])
if data[feature] < i:
set1.append(data)
counter1[index] = counter1[index] + 1
elif data[feature] >= i:
set2.append(data)
counter2[index] = counter2[index] + 1
#计算属性的熵
a = (len(set1) * getEntropy(counter1) + len(set2) * getEntropy(counter2)) / len(dataSet)
#获得熵最小的属性作为树节点(即信息增益最大的属性)
if a < res:
res = a
final = feature
featureSet.remove(final)
child = [0, 0, 0]
child[0] = final
#递归生成其他树节点
child[1] = tree(featureSet, set1, counter1)
child[2] = tree(featureSet, set2, counter2)
return child
#模型评估
def judge(test_data, tree):
root = "unknow"
while (len(tree) > 0):
if isinstance(tree, str) and tree in iris.target_names:
return tree
root = tree[0]
if (isinstance(root, str)):
return root
if isinstance(root, int):
if test_data[root] < razors[root] and tree[1] != []:
tree = tree[1]
elif tree[2] != [] and (tree[1] == [] or (test_data[root] >= razors[root])):
tree = tree[2]
return root
if __name__ == '__main__':
iris = datasets.load_iris()
#随机获得80%的训练集和20%的测试集
length = len(iris.target)
[trainData, testData] = divideData()
num = [0, 0, 0]
for row in iris.data:
num[int(row[-1])] = num[int(row[-1])] + 1
#连续值分割
razors = getRazor()
#递归生成树
tree = tree(list(range(len(iris.feature_names))), trainData,
[np.sum(trainData[:, -1] == 0), np.sum(trainData[:, -1] == 1), np.sum(trainData[:, -1] == 2)])
print("本次选取的训练集构建出的树: ", tree)
#使用测试集进行模型评估
index = 0
right = 0
print("预测结果 \t 实际结果")
for data in testData:
predict= judge(testData[index], tree)
truth = iris.target_names[int(testData[index][-1])]
index = index + 1
if predict == truth:
right = right + 1
else:
print(predict + "\t" + truth)
print("决策树正确率: ", format(right / index*100,".2f"),"%")
BP神经网络(BPNN)
BP神经网络的大致思路:
1. 信号的前向传播
隐层神经元接收来自输入层的信号,隐层的每个神经元接收到的总输入值与神经元的阈值进行比较,然后通过“激活函数”处理以产生神经元的输出。输出层的神经元也是以同样方式产生神经元的输出。
2. 反向计算梯度
计算代价函数进而求出输出层神经元的梯度项gj和隐层神经元的梯度项eh
3. 更新权值和阈值
基于梯度下降策略, 以目标的负梯度方向对权值和阈值参数进行调整。
实现流程:
1. 初始化参数
使用伪随机数来初始化权重和偏置矩阵
2. 前向传播
根据初始化的参数计算隐层神经元的输入, 将tanh作为隐层的激活函数得到隐层的输出, 再计算输出层神经元的输入, 使用sigmoid作为第二层的激活函数得到输出
3.计算代价函数
使用交叉熵作为代价函数
4. 反向传播
对每一个变量求导, 得到权重和偏置矩阵的导数
5. 更新参数
利用梯度下降算法对权重和偏置矩阵进行更新
6. 回到第二步迭代5000次
7. 模型评估
import numpy as np
from sklearn.preprocessing import OneHotEncoder
from sklearn import datasets
'''
构建一个具有1个隐藏层的神经网络,隐层的大小为10
'''
# 1.初始化参数
def initialize_parameters(n_x, n_h, n_y):
np.random.seed(2)
# 权重和偏置矩阵
w1 = np.random.randn(n_h, n_x) * 0.01
b1 = np.zeros(shape=(n_h, 1))
w2 = np.random.randn(n_y, n_h) * 0.01
b2 = np.zeros(shape=(n_y, 1))
# 存储参数
parameters = {'w1': w1, 'b1': b1, 'w2': w2, 'b2': b2}
return parameters
# 2.前向传播
def forward_propagation(X, parameters):
w1 = parameters['w1']
b1 = parameters['b1']
w2 = parameters['w2']
b2 = parameters['b2']
# 通过前向传播来计算a2
z1 = np.dot(w1, X) + b1 # 这个地方需注意矩阵加法:虽然(w1*X)和b1的维度不同,但可以相加
a1 = np.tanh(z1) # 使用tanh作为第一层的激活函数
z2 = np.dot(w2, a1) + b2
a2 = 1 / (1 + np.exp(-z2)) # 使用sigmoid作为第二层的激活函数
# 通过字典存储参数
cache = {'z1': z1, 'a1': a1, 'z2': z2, 'a2': a2}
return a2, cache
# 3.计算代价函数
def compute_cost(a2, Y):
m = Y.shape[1] #总的样本数
# 采用交叉熵作为代价函数
logprobs = np.multiply(np.log(a2), Y) + np.multiply((1 - Y), np.log(1 - a2))
cost = - np.sum(logprobs) / m
return cost
# 4.反向传播(计算代价函数的导数)
def backward_propagation(parameters, cache, X, Y):
m = Y.shape[1]
w2 = parameters['w2']
a1 = cache['a1']
a2 = cache['a2']
# 反向传播,计算dw1、db1、dw2、db2
dz2 = a2 - Y
dw2 = (1 / m) * np.dot(dz2, a1.T)
db2 = (1 / m) * np.sum(dz2, axis=1, keepdims=True)
dz1 = np.multiply(np.dot(w2.T, dz2), 1 - np.power(a1, 2))
dw1 = (1 / m) * np.dot(dz1, X.T)
db1 = (1 / m) * np.sum(dz1, axis=1, keepdims=True)
grads = {'dw1': dw1, 'db1': db1, 'dw2': dw2, 'db2': db2}
return grads
# 5.更新参数
def update_parameters(parameters, grads, learning_rate=0.4):
w1 = parameters['w1']
b1 = parameters['b1']
w2 = parameters['w2']
b2 = parameters['b2']
dw1 = grads['dw1']
db1 = grads['db1']
dw2 = grads['dw2']
db2 = grads['db2']
# 更新参数
w1 = w1 - dw1 * learning_rate
b1 = b1 - db1 * learning_rate
w2 = w2 - dw2 * learning_rate
b2 = b2 - db2 * learning_rate
parameters = {'w1': w1, 'b1': b1, 'w2': w2, 'b2': b2}
return parameters
# 建立神经网络
def bpnn(X, Y, n_h, n_input, n_output, num_iterations=5000, print_cost=False):
np.random.seed(3)
n_x = n_input # 输入层节点数
n_y = n_output # 输出层节点数
# 1.初始化参数
parameters = initialize_parameters(n_x, n_h, n_y)
# 梯度下降循环
for i in range(0, num_iterations):
# 2.前向传播
a2, cache = forward_propagation(X, parameters)
# 3.计算代价函数
cost = compute_cost(a2, Y)
# 4.反向传播
grads = backward_propagation(parameters, cache, X, Y)
# 5.更新参数
parameters = update_parameters(parameters, grads)
# 每500次迭代,输出一次代价函数
if print_cost and i % 500 == 0:
print('迭代第%i次,代价函数为:%f' % (i, cost))
return parameters
# 6.模型评估
def judge(parameters, x_test, y_test):
w1 = parameters['w1']
b1 = parameters['b1']
w2 = parameters['w2']
b2 = parameters['b2']
z1 = np.dot(w1, x_test) + b1
a1 = np.tanh(z1)
z2 = np.dot(w2, a1) + b2
a2 = 1 / (1 + np.exp(-z2))
# 结果的维度
n_rows = a2.shape[0]
n_cols = a2.shape[1]
# 预测值结果存储
output = np.empty(shape=(n_rows, n_cols), dtype=int)
for i in range(n_rows):
for j in range(n_cols):
if a2[i][j] > 0.5:
output[i][j] = 1
else:
output[i][j] = 0
# 将one-hot编码反转为标签
output = encoder.inverse_transform(output.T)
output = output.reshape(1, output.shape[0])
output = output.flatten()
print('预测结果:', output)
print('真实结果:', y_test)
count = 0
for k in range(0, n_cols):
if output[k] == y_test[k]:
count = count + 1
else:
print('错误分类样本序号:', k + 1)
accuracy = count / int(a2.shape[1]) * 100
print('BP神经网络准确率:%.2f%%' % accuracy)
return output
#随机抽取80%的训练集和20%的测试集
def divideData():
completeData = np.c_[iris.data, iris.target.T]
np.random.shuffle(completeData)
trainData = completeData[range(int(length * 0.8)), :]
testData = completeData[range(int(length * 0.8), length), :]
return [trainData, testData]
if __name__ == "__main__":
#获取iris数据集
iris = datasets.load_iris()
#随机获取80%训练集和20%测试集
length = len(iris.target)
[trainData, testData] = divideData()
X=trainData[:,0:4].T
Y=trainData[:,-1].T
# 将标签转换为one-hot编码,便于计算
encoder = OneHotEncoder()
Y = encoder.fit_transform(Y.reshape(Y.shape[0], 1))
Y = Y.toarray().T
Y = Y.astype('uint8')
# 输入4个节点,隐层10个节点,输出3个节点,迭代5000次
parameters = bpnn(X, Y, n_h=10, n_input=4, n_output=3, num_iterations=5000, print_cost=True)
x_test=testData[:,0:4].T
y_test=testData[:,-1].T
result = judge(parameters, x_test, y_test)
SVM(支持向量机)
大致思路:
SVM学习的基本想法是求解能够正确划分训练数据集并且几何间隔最大的分离超平面。
-
该超平面要使得异类支持向量到超平面的距离最大, 即求最大间隔.
-
最大间隔式子是凸二次规划问题, 可以使用拉格朗日乘子法转化为对偶问题
-
计算对偶式的拉格朗日乘子可采用SMO算法
-
对于输入空间中的非线性分类问题,可以通过非线性变换将它转化为某个维特征空间中的线性分类问题,在高维特征空间中学习线性支持向量机。由于在线性支持向量机学习的对偶问题里,目标函数和分类决策函数都只涉及实例和实例之间的内积,所以不需要显式地指定非线性变换,而是用核函数替换当中的内积。
实现流程:
使用skearn库svm.SVC函数训练分类器,并进行模型评估
from sklearn import svm
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_iris
from sklearn.metrics import accuracy_score
#获得iris数据集
iris = load_iris()
#划分训练集(120)和测试集(30)
X = iris.data
Y = iris.target
train_data, test_data, train_target, test_target = train_test_split(X, Y, random_state=1, train_size=0.8, test_size=0.2)
#训练svm分类器
#SVC的参数:
# C: 惩罚参数
# kernel: 核函数
# gamma: 核函数参数
# decision_function_shape: 策略,ovo: 一对一
classifier = svm.SVC(C=2, kernel='rbf', gamma=10, decision_function_shape='ovo')
classifier.fit(train_data, train_target.ravel()) # ravel函数在降维时默认是行序优先
#计算分类器的准确率
print("测试集准确率:", classifier.score(test_data, test_target))
#直接调用accuracy_score计算准确率
tes_target = classifier.predict(test_data) # 测试集的预测结果
print("测试集准确率:", accuracy_score(test_target, tes_target))