【机器学习】二分类算法实现及算法精度比较

一.数据集选择

鸢尾花数据集-iris.data
Iris数据集是常用的分类实验数据集,由Fisher, 1936收集整理。Iris也称鸢尾花卉数据集,是一类多重变量分析的数据集。数据集包含150个数据样本,分为3类,每类50个数据,每个数据包含4个属性(分别是:花萼长度,花萼宽度,花瓣长度,花瓣宽度)。可通过这4个属性预测鸢尾花卉属于(Setosa,Versicolour,Virginica)三个种类的鸢尾花中的哪一类。
Iris里有两个属性iris.data,iris.target。data是一个矩阵,每一列代表了萼片或花瓣的长宽,一共4列,每一列代表某个被测量的鸢尾植物,一共有150条记录。

数据集链接
数据集预处理:

由于数据集较小,易于划分,对测试集加入噪声后测试精确率

1.感知机

特点:
(1)二类分类线性模型。
(2)输入为实例的特征向量,输出为实例的类别。
(3)导入基于误分类的损失函数。
(4)利用梯度下降法对损失函数进行极小化。
算法实现:

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd  # 读取数据集,读为表格数据
from sklearn import metrics

# 引进数据集
df = pd.read_csv('../data/iris.data', header=None)  # 读取数据集的数据
df.tail()  # 对数据进行矩阵形式的显示
# 抽取出前100条样本,这正好是Setosa和Versicolor对应的样本,我们将Versicolor对应的数据作为类别1,Setosa对应的作为-1。
# 对于特征,我们抽取出sepal length和petal length两维度特征,然后用散点图对数据进行可视化
# We extract the first 100 class labels that correspond to 50 Iris-Setosa and 50 Iris-Versicolor flowers.
y = df.iloc[0:25, 4].values  # 抽取0--25行和75-100行,第4列数据
y1 = df.iloc[75:100, 4].values
y = np.concatenate([y, y1])
y = np.where(y == 'Iris-setosa', -1, 1)  # 满足条件(condition),输出x,不满足输出y。#向量推导
X = df.iloc[0:25, [0, 2]].values
X1 = df.iloc[75:100, [0, 2]].values
X = np.row_stack((X, X1))

print("数据集规模:" + str(X.shape[0]))  # (100,2) (100,3)  读取矩阵的形态

p_x = np.array(X)  # 创建数据集     x值
y = np.array(y)  # 标记数据集     1:正类;-1:负类    y值
plt.figure()  # 创建图像

for i in range(len(p_x)):  # 遍历数据集  在图中标记数据集各点
    if y[i] == 1:
        plt.plot(p_x[i][0], p_x[i][1], 'ro')  # 将数据集中正类点标记为红色实心点
    else:
        plt.plot(p_x[i][0], p_x[i][1], 'bo')  # 负类标记为蓝色实心点

w = np.array([0, 0])  # 初始化权重系数w
b = 0  # 初始化偏移常数b
delta = 1  # 步长 1
count = 0

while 1:
    choice = -1  # 初始化标记参数choice
    for j in range(len(p_x)):
        if y[j] != np.sign(np.dot(w, p_x[j]) + b):  # w与p_x内积 + b > 0 则sign=1;< 0 则sign=-1   (sign == y[i] 正确分类)
            choice = j
            w = w + delta * y[choice] * p_x[choice]  # 更新参数
            b = b + delta * y[choice]
            break
    if choice == -1:
        break
    for i in range(len(p_x)):
        if y[i] == np.sign(np.dot(w, p_x[i]) + b):
            count += 1
        else:
            count = 0
            break
    if count == len(p_x):
        break

print("输出模型:")
print("y=" + str(round(w[0], 1)) + "x1+" + str(w[1]) + "x2" + str(b))

line_x = [0, 10]  # 绘制x坐标系
line_y = [0, 0]

for i in range(len(line_x)):
    line_y[i] = (-w[0] * line_x[i] - b) / w[1]  # 绘制超平面

plt.plot(line_x, line_y)
plt.savefig("picture.png")

# 抽取0--100行,第4列数据
y = df.iloc[25:50, 4].values
y1 = df.iloc[50:75, 4].values
y = np.concatenate([y, y1])
y = np.where(y == 'Iris-setosa', -1, 1)  # 满足条件(condition),输出x,不满足输出y。#向量推导
X = df.iloc[25:50, [0, 2]].values
X1 = df.iloc[50:75, [0, 2]].values
X = np.row_stack((X, X1))
# 抽取第0列,第2列数据
print("测试集规模:" + str(X.shape[0]))  # (100,2) (100,3)  读取矩阵的形态
print()
p_x = np.array(X)  # 创建数据集     x值
y = np.array(y)  # 标记数据集     1:正类;-1:负类    y值

zhen = 0
fu = 0
y_predict = []
for i in range(len(p_x)):
    print("测试数据:" + str([p_x[i][0], p_x[i][1]]))
    print("真实结果:" + str(y[i]))
    y_predict.append(int(np.sign(np.dot(w, p_x[i]) + b)))
    print("预测结果:" + str(y_predict[i]))
    print()

M = metrics.confusion_matrix(y_predict, y)
print('混淆矩阵:\n', M)

n = len(M)
for i in range(n):
    rowsum, colsum = sum(M[i]), sum(M[r][i] for r in range(n))
    precision = M[i][i] / float(colsum)
    recall = M[i][i] / float(rowsum)
    F1 = precision * recall * 2 / (precision + recall)
    print('y=%d 精确率: %s' % (i, precision), '召回率: %s' % recall, 'F1值: %s' % F1)
print("感知机模型精度:{:.5f}".format(np.mean(y_predict == y)))

分类图像展示:
在这里插入图片描述
运行结果:
在这里插入图片描述
在这里插入图片描述

类别精确率召回率F1
00.960.960.96
10.960.960.96

模型精度(准确率):0.96000

2.K近邻(knn)

特点:
优点:
(1)简单好用,容易理解,精度高,理论成熟,既可以用来做分类也可以用来做回归;
(2)可用于数值型数据和离散型数据;
(3)训练时间复杂度为O(n);无数据输入假定;
(4)对异常值不敏感。
缺点:
(1)计算复杂性高;空间复杂性高;
(2)样本不平衡问题(即有些类别的样本数量很多,而其它样本的数量很少);
(3)一般数值很大的时候不用这个,计算量太大。但是单个样本又不能太少,否则容易          发生误分。
(4)最大的缺点是无法给出数据的内在含义。
算法实现:

import pandas as pd
import numpy as np # 导入科学计算包
import operator  # 导入运算符模块
import json
from numpy import *

def createDataSet():
    # 引进数据集
    df = pd.read_csv('../data/iris.data',
                     header=None)  # 读取数据集的数据
    df.tail()  # 对数据进行矩阵形式的显示
    # 抽取出前100条样本,这正好是Setosa和Versicolor对应的样本,我们将Versicolor对应的数据作为类别1,Setosa对应的作为-1。
    # 对于特征,我们抽取出sepal length和petal length两维度特征,然后用散点图对数据进行可视化
    # We extract the first 100 class labels that correspond to 50 Iris-Setosa and 50 Iris-Versicolor flowers.
    y = df.iloc[0:25, 4].values  # 抽取不同的数据行,第4列数据进行训练
    y1 = df.iloc[75:100, 4].values
    y = np.concatenate([y, y1])

    y = np.where(y == 'Iris-setosa', -1, 1)  # 满足条件(condition),输出x,不满足输出y。#向量推导
    X = df.iloc[0:25, [0, 2]].values
    X1 = df.iloc[75:100, [0, 2]].values
    X = np.row_stack((X, X1))
    # 抽取第0列,第2列数据
    # print(X.shape, y.shape)  # (100,2) (100,3)  读取矩阵的形态
    data = np.array(X)  # 创建数据集     x值
    labels = np.array(y)  # 标记数据集     1:正类;-1:负类    y值
    labels1 = []
    for i in labels:
        if labels[i] == 1:
            labels1.append(1)
        else:
            labels1.append(-1)
    
   
    return data, labels1


def classify(inX, data, labels, k):
    dataSetSize = data.shape[0]  # 计算共有多少条训练数据
    # print(dataSetSize)
    # print('复制输入向量 用于和样本中的每条数据进行计算 [矩阵的加减乘除]')
    # print(tile(inX, (dataSetSize, 1)))

    # 矩阵的减法 结果:每一项为输入向量和各个样本对应特征点的差值构成的新矩阵
    diffmat = tile(inX, (dataSetSize, 1)) - data
    # print('\n相减后:')
    # print(diffmat)

    sqDiffMat = diffmat ** 2  # 平方 矩阵中每一项都平方
    # print('\n平方后:')
    # print(sqDiffMat)
    sqDistances = sqDiffMat.sum(axis=1)  # axis=1 行向量相加 / axis=0 列向量相加
    # print('\n各个特征点差值相加[即坐标差值相加]:')
    # print(sqDistances)

    distances = sqDistances ** 0.5  # 开方
    # print('\n距离:')
    # print(distances)
    sortedDistIndexes = distances.argsort()  # 从小到大将距离向量排序并返回其索引值
    # 参数:
    # a为要排序的数组
    # axis:按哪一维进行排序

    classCount = {}  # dict 保存对应标签出现的次数
    for i in range(k):
        # print("循环次数:" + i.__str__())
        voteLabel = labels[sortedDistIndexes[i]]  # 获得类别标签
        classCount[voteLabel] = classCount.get(voteLabel, 0) + 1
    print('标签出现的次数:')
    print(json.dumps(classCount, ensure_ascii=False))
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)

    print('排序后:')
    print(json.dumps(sortedClassCount, ensure_ascii=False))
    # 如: print sortedClassCount ———— [('A', 2), ('B', 1)]

    return sortedClassCount[0][0]  # 返回次数出现次数最多的标签

测试

import pandas as pd
from sklearn import metrics

import knn
import numpy as np

data, labels = knn.createDataSet()

df = pd.read_csv('../data/iris.data', header=None)  # 读取数据集的数据
df.tail()  # 对数据进行矩阵形式的显示
# 抽取0--100行,第4列数据
y = df.iloc[25:50, 4].values
y1 = df.iloc[50:75, 4].values
y = np.concatenate([y, y1])
# print(y)
y = np.where(y == 'Iris-setosa', 1, -1)  # 满足条件(condition),输出x,不满足输出y。#向量推导
# print(y)
X = df.iloc[25:50, [0, 2]].values
X1 = df.iloc[50:75, [0, 2]].values
X = np.row_stack((X, X1))
# 抽取第0列,第2列数据
# print(X)
# print(X.shape, y.shape)  # (100,2) (100,3)  读取矩阵的形态
p_x = np.array(X)  # 创建数据集     x值
y = np.array(y)  # 标记数据集     1:正类;-1:负类    y值
train_data, train_lable = knn.createDataSet()

# print('测试模型分类样本数据,结果是否和样本中的分类一致')
y_predict = []
for i in range(len(p_x)):
    print("测试数据:"+str([p_x[i][0], p_x[i][1]]))
    print("真实结果:" + str(y[i]))
    y_predict.append(knn.classify([p_x[i][0], p_x[i][1]], train_data, train_lable, 3))
    print('预测结果:' + str(y_predict[i])+"\n")

M = metrics.confusion_matrix(y_predict, y)
print('混淆矩阵:\n', M)

n = len(M)
for i in range(n):
    rowsum, colsum = sum(M[i]), sum(M[r][i] for r in range(n))
    precision = M[i][i] / float(colsum)
    recall = M[i][i] / float(rowsum)
    F1 = precision * recall * 2 / (precision + recall)
    print('y=%d 精确率: %s' % (i, precision), '召回率: %s' % recall, 'F1值: %s' % F1)
print("KNN模型精度:{:.5f}".format(np.mean(y_predict == y)))

运行结果
在这里插入图片描述

类别精确率召回率F1
00.960.960.96
10.960.960.96

模型精度(准确率):0.96000

3.朴素贝叶斯

特点:
朴素贝叶斯的主要优势有:
1)朴素贝叶斯模型有稳定的分类效率。
2)对小规模的数据表现很好,能处理多分类任务,适合增量式训练,尤为是数据量超出内存时,能够一批批的去增量训练。
3)对缺失数据不太敏感,算法也比较简单,经常使用于文本分类。
朴素贝叶斯的主要缺点有:   
1) 理论上,朴素贝叶斯模型与其余分类方法相比具备最小的偏差率。可是实际上并不是老是如此,这是由于朴素贝叶斯模型给定输出类别的状况下,假设属性之间相互独立,这个假设在实际应用中每每是不成立的,在属性个数比较多或者属性之间相关性较大时,分类效果很差。而在属性相关性较小时,朴素贝叶斯性能最为良好。对于这一点,有半朴素贝叶斯之类的算法经过考虑部分关联性适度改进。
2)须要知道先验几率,且先验几率不少时候取决于假设,假设的模型能够有不少种,所以在某些时候会因为假设的先验模型的缘由致使预测效果不佳。
3)因为咱们是经过先验和数据来决定后验的几率从而决定分类,因此分类决策存在必定的错误率。
4)对输入数据的表达形式很敏感。
代码展示:

import numpy as np
import pandas as pd
from sklearn import metrics


def createDataSet():
    # 引进训练数据集
    df = pd.read_csv('../data/iris.data',
                     header=None)  # 读取数据集的数据
    df.tail()  # 对数据进行矩阵形式的显示
    # 抽取出前100条样本,这正好是Setosa和Versicolor对应的样本,我们将Versicolor对应的数据作为类别1,Setosa对应的作为-1。
    # 对于特征,我们抽取出sepal length和petal length两维度特征,然后用散点图对数据进行可视化
    # We extract the first 100 class labels that correspond to 50 Iris-Setosa and 50 Iris-Versicolor flowers.
    y = df.iloc[0:25, 4].values  # 抽取0--100行,第4列数据
    y1 = df.iloc[75:100, 4].values
    y = np.concatenate([y, y1])
    y = np.where(y == 'Iris-setosa', -1, 1)  # 满足条件(condition),输出x,不满足输出y。#向量推导
    X = df.iloc[0:25, [0, 2]].values
    X1 = df.iloc[75:100, [0, 2]].values
    X = np.row_stack((X, X1))
    dataSet = np.column_stack((X, y))
    labels = ['x1', 'x2', 'y']
    return dataSet, labels


# 统计yi的个数
def typeCount(typeList, t):
    cnt = 0
    for tL in typeList:
        if tL == t:
            cnt += 1
    return cnt


# 计算Y=-1或1的条件下,X等于某值 个数
def featCount(dataSet, i, feat, y):
    cnt = 0
    # print(i, feat, y)
    for row in dataSet:
        if row[i] == feat and row[-1] == y:
            cnt += 1
    return cnt


def calcBayes(dataSet, X):
    # 以 x = (2, 'S') 为例

    lenDataSet = len(dataSet)
    typeList = [row[-1] for row in dataSet]
    typeSet = set(typeList)  # 类别集合
    print(typeList, typeSet)
    typeLen = len(typeSet)
    # 遍历一类 t=1; t=-1
    pList = []  # 记录预计 各类类别 概率
    for t in typeSet:
        yNum = typeCount(typeList, t)  # 计算yi的个数
        print(f'{t} num =', yNum)
        py = yNum / lenDataSet
        print(f'P(Y = {t}) =', py)
        pSum = py
        # 对每个特征分量计数
        for i in range(len(X)):
            xiNum = featCount(dataSet, i, X[i], t)  # 统计Y条件下 Xi取相应特征 的数量
            print(f'特征{X[i]} num =', xiNum)
            # 条件概率P{X = xi | Y = yi}
            pxy = xiNum / yNum
            print(f'条件概率 =', pxy)
            pSum *= pxy
        pList.append(pSum)
    # print(pList)
    return pList, typeSet


# 就是找最大的概率,记录下标
def predict(pList, typeList):
    for i in range(len(pList)):
        if pList[i] == max(pList):
            print(f'预测类 为 = {typeList[i]}')
            print('*' * 50)
            return typeList[i]


if __name__ == '__main__':
    # 测试集
    df = pd.read_csv('../data/iris.data',
                     header=None)  # 读取数据集的数据
    df.tail()  # 对数据进行矩阵形式的显示
    # 抽取0--100行,第4列数据
    y = df.iloc[25:50, 4].values
    y1 = df.iloc[50:75, 4].values
    y = np.concatenate([y, y1])
    # print(y)
    y = np.where(y == 'Iris-setosa', -1, 1)  # 满足条件(condition),输出x,不满足输出y。#向量推导
    # print(y)
    X = df.iloc[25:50, [0, 2]].values
    X1 = df.iloc[50:75, [0, 2]].values
    X = np.row_stack((X, X1))
    # 抽取第0列,第2列数据
    # print(X)
    # print(X.shape, y.shape)  # (100,2) (100,3)  读取矩阵的形态
    p_x = np.array(X)  # 创建数据集     x值
    y = np.array(y)  # 标记数据集     1:正类;-1:负类    y值
    data = np.column_stack((p_x, y))
    data = np.array(data)
    dataSet, labels = createDataSet()
    predict_result = []

    for i in data:
        pList, typeSet = calcBayes(dataSet, [i[0], i[1]])
        predict_result.append(predict(pList, list(typeSet)))

    M = metrics.confusion_matrix(predict_result, y)
    print('混淆矩阵:\n', M)

    n = len(M)
    for i in range(n):
        rowsum, colsum = sum(M[i]), sum(M[r][i] for r in range(n))
        precision = M[i][i] / float(colsum)
        recall = M[i][i] / float(rowsum)
        F1 = precision * recall * 2 / (precision + recall)
        print('y=%d 精确率: %s' % (i, precision), '召回率: %s' % recall, 'F1值: %s' % F1)
    print("朴素贝叶斯模型精度:{:.5f}".format(np.mean(predict_result == y)))

运行结果:
在这里插入图片描述

类别精确率召回率F1
00.680.940.79
10.960.750.84

模型精度(准确率):0.82000

4.决策树id3

特点:
决策树的核心思想是:相似的输入必然产生相似的输出。决策树通过把数据样本分配到树状结构的某个叶子节点来确定数据集中样本所属的分类。决策树可用于回归和分类。当用于回归时,预测结果为叶子节点所有样本的均值。
①优点
• 简单易懂,容易解释,可视化,适用性广。
• 可用于分类、回归问题。
②缺点
• 容易过拟合。
• 数据中的小变化会影响结果,不稳定。
• 每一个节点的选择都是贪婪算法,不能保证全局最优解。
使用场景:
适合于标称型(在有限目标集中取值)属性较多的样本数据。
具有较广的适用性,当对模型不确定时可以使用决策树进行验证。

代码实现

# -*- coding: UTF-8 -*-
import math
from math import log
import operator

import numpy as np
import pandas as pd
from sklearn import metrics

import 决策树可视化 as treePlot

"""
函数说明:创建测试数据集
"""


def createDataSet():
    # 引进数据集
    df = pd.read_csv('../data/iris.data',
                     header=None)  # 读取数据集的数据
    df.tail()  # 对数据进行矩阵形式的显示
    # 抽取出前100条样本,这正好是Setosa和Versicolor对应的样本,我们将Versicolor对应的数据作为类别1,Setosa对应的作为-1。
    # 对于特征,我们抽取出sepal length和petal length两维度特征,然后用散点图对数据进行可视化
    # We extract the first 100 class labels that correspond to 50 Iris-Setosa and 50 Iris-Versicolor flowers.
    y = df.iloc[0:25, 4].values  # 抽取0--100行,第4列数据
    y1 = df.iloc[75:100, 4].values
    y = np.concatenate([y, y1])
    y = np.where(y == 'Iris-setosa', 1, -1)  # 满足条件(condition),输出x,不满足输出y。#向量推导
    X = df.iloc[0:25, 0:4].values
    X1 = df.iloc[75:100, 0:4].values
    X = np.row_stack((X, X1))
    print(X.shape[0])
    for i in range(X.shape[0]):
        for j in range(X.shape[1]):
            X[i][j] = math.ceil(X[i][j])
    dataSet = np.column_stack([X, y])
    dataSet = np.array(dataSet).tolist()

    labels = ['A1', 'A2', 'A3', 'A4']  # 分类属性
    return dataSet, labels  # 返回数据集和分类属性


"""
函数说明:计算给定数据集的经验熵(香农熵)
Parameters:
    dataSet - 数据集
Returns:
    shannonEnt - 经验熵(香农熵)
"""


def calcShannonEnt(dataSet):
    numEntires = len(dataSet)  # 返回数据集的行数
    labelCounts = {}  # 保存每个标签(Label)出现次数的字典
    for featVec in dataSet:  # 对每组特征向量进行统计
        currentLabel = featVec[-1]  # 提取标签(Label)信息
        if currentLabel not in labelCounts.keys():  # 如果标签(Label)没有放入统计次数的字典,添加进去
            labelCounts[currentLabel] = 0
        labelCounts[currentLabel] += 1  # Label计数
    shannonEnt = 0.0  # 经验熵(香农熵)
    for key in labelCounts:  # 计算香农熵
        prob = float(labelCounts[key]) / numEntires  # 选择该标签(Label)的概率
        shannonEnt -= prob * log(prob, 2)  # 利用公式计算
    return shannonEnt  # 返回经验熵(香农熵)


"""
函数说明:按照给定特征划分数据集
Parameters:
    dataSet - 待划分的数据集
    axis - 划分数据集的特征
    value - 需要返回的特征的值
"""


def splitDataSet(dataSet, axis, value):
    retDataSet = []  # 创建返回的数据集列表
    for featVec in dataSet:  # 遍历数据集
        if featVec[axis] == value:
            reducedFeatVec = featVec[:axis]  # 去掉axis特征
            reducedFeatVec.extend(featVec[axis + 1:])  # 将符合条件的添加到返回的数据集
            retDataSet.append(reducedFeatVec)
    return retDataSet  # 返回划分后的数据集


"""
函数说明:选择最优特征
Parameters:
    dataSet - 数据集
Returns:
    bestFeature - 信息增益最大的(最优)特征的索引值
"""


def chooseBestFeatureToSplit(dataSet):
    numFeatures = len(dataSet[0]) - 1  # 特征数量
    baseEntropy = calcShannonEnt(dataSet)  # 计算数据集的香农熵
    bestInfoGain = 0.0  # 信息增益
    bestFeature = -1  # 最优特征的索引值
    for i in range(numFeatures):  # 遍历所有特征
        # 获取dataSet的第i个所有特征
        featList = [example[i] for example in dataSet]
        uniqueVals = set(featList)  # 创建set集合{},元素不可重复
        newEntropy = 0.0  # 经验条件熵
        for value in uniqueVals:  # 计算信息增益
            subDataSet = splitDataSet(dataSet, i, value)  # subDataSet划分后的子集
            prob = len(subDataSet) / float(len(dataSet))  # 计算子集的概率
            newEntropy += prob * calcShannonEnt(subDataSet)  # 根据公式计算经验条件熵
        infoGain = baseEntropy - newEntropy  # 信息增益
        print("第%d个特征的增益为%.3f" % (i, infoGain))  # 打印每个特征的信息增益
        if (infoGain > bestInfoGain):  # 计算信息增益
            bestInfoGain = infoGain  # 更新信息增益,找到最大的信息增益
            bestFeature = i  # 记录信息增益最大的特征的索引值
    return bestFeature  # 返回信息增益最大的特征的索引值


"""
函数说明:统计classList中出现此处最多的元素(类标签)
Parameters:
    classList - 类标签列表
Returns:
    sortedClassCount[0][0] - 出现此处最多的元素(类标签)
"""


def majorityCnt(classList):
    classCount = {}
    for vote in classList:  # 统计classList中每个元素出现的次数
        if vote not in classCount.keys():
            classCount[vote] = 0
        classCount[vote] += 1
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)  # 根据字典的值降序排序
    return sortedClassCount[0][0]  # 返回classList中出现次数最多的元素


"""
函数说明:递归构建决策树
Parameters:
    dataSet - 训练数据集
    labels - 分类属性标签
    featLabels - 存储选择的最优特征标签
Returns:
    myTree - 决策树
"""


def createTree(dataSet, labels, featLabels):
    classList = [example[-1] for example in dataSet]  # 取分类标签(是否放贷:yes or no)
    if classList.count(classList[0]) == len(classList):  # 如果类别完全相同则停止继续划分
        return classList[0]
    if len(dataSet[0]) == 1:  # 遍历完所有特征时返回出现次数最多的类标签
        return majorityCnt(classList)
    bestFeat = chooseBestFeatureToSplit(dataSet)  # 选择最优特征
    bestFeatLabel = labels[bestFeat]  # 最优特征的标签
    featLabels.append(bestFeatLabel)
    myTree = {bestFeatLabel: {}}  # 根据最优特征的标签生成树
    del (labels[bestFeat])  # 删除已经使用特征标签
    featValues = [example[bestFeat] for example in dataSet]  # 得到训练集中所有最优特征的属性值
    uniqueVals = set(featValues)  # 去掉重复的属性值
    for value in uniqueVals:
        subLabels = labels[:]
        # 递归调用函数createTree(),遍历特征,创建决策树。
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels, featLabels)
    return myTree


"""
函数说明:使用决策树执行分类
Parameters:
    inputTree - 已经生成的决策树
    featLabels - 存储选择的最优特征标签
    testVec - 测试数据列表,顺序对应最优特征标签
Returns:
    classLabel - 分类结果
"""


def classify(inputTree, featLabels, testVec):
    global classLabel
    firstStr = next(iter(inputTree))  # 获取决策树结点
    secondDict = inputTree[firstStr]  # 下一个字典
    featIndex = featLabels.index(firstStr)
    for key in secondDict.keys():
        if testVec[featIndex] == key:
            if type(secondDict[key]).__name__ == 'dict':
                classLabel = classify(secondDict[key], featLabels, testVec)
            else:
                classLabel = secondDict[key]
    return classLabel


if __name__ == '__main__':
    dataSet, labels = createDataSet()
    featLabels = []
    myTree = createTree(dataSet, labels, featLabels)
    treePlot.createPlot(myTree)
    print(myTree)

    df = pd.read_csv('../data/iris.data',
                     header=None)  # 读取数据集的数据
    df.tail()  # 对数据进行矩阵形式的显示
    # 抽取出前100条样本,这正好是Setosa和Versicolor对应的样本,我们将Versicolor对应的数据作为类别1,Setosa对应的作为-1。
    # 对于特征,我们抽取出sepal length和petal length两维度特征,然后用散点图对数据进行可视化
    # We extract the first 100 class labels that correspond to 50 Iris-Setosa and 50 Iris-Versicolor flowers.
    y = df.iloc[25:50, 4].values  # 抽取0--100行,第4列数据
    y1 = df.iloc[50:75, 4].values
    y = np.concatenate((y, y1))
    y = np.where(y == 'Iris-setosa', 1, -1)  # 满足条件(condition),输出x,不满足输出y。#向量推导
    X = df.iloc[25:50, 2].values
    X1 = df.iloc[50:75, 2].values
    X = np.concatenate([X, X1])
    for i in range(X.shape[0]):
        X[i] = math.ceil(X[i])


    predict_result = []
    for i in X:
        i = [i]
        result = classify(myTree, featLabels, i)
        print(result)
        if result == 1.0:
            predict_result.append(1.0)
        else:
            predict_result.append(-1.0)
    M = metrics.confusion_matrix(predict_result, y)
    print('混淆矩阵:\n', M)

    n = len(M)
    for i in range(n):
        rowsum, colsum = sum(M[i]), sum(M[r][i] for r in range(n))
        precision = M[i][i] / float(colsum)
        recall = M[i][i] / float(rowsum)
        F1 = precision * recall * 2 / (precision + recall)
        print('y=%d 精确率: %s' % (i, precision), '召回率: %s' % recall, 'F1值: %s' % F1)
    print("决策树Id3模型精度:{:.5f}".format(np.mean(predict_result == y)))

决策树可视化

import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
from matplotlib.font_manager import FontProperties
import matplotlib.pyplot as plt

# 定义文本框和箭头格式
decisionNode = dict(boxstyle='sawtooth', fc='0.8')
leafNode = dict(boxstyle='round4', fc='0.8')
arrow_args = dict(arrowstyle='<-')
# 设置中文字体
font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)

"""
函数说明:获取决策树叶子结点的数目
Parameters:
    myTree - 决策树
Returns:
    numLeafs - 决策树的叶子结点的数目
"""


def getNumLeafs(myTree):
    numLeafs = 0  # 初始化叶子
    # python3中myTree.keys()返回的是dict_keys,不在是list,所以不能使用myTree.keys()[0]的方法获取结点属性,
    # 可以使用list(myTree.keys())[0]
    firstStr = next(iter(myTree))
    secondDict = myTree[firstStr]  # 获取下一组字典
    for key in secondDict.keys():
        if type(secondDict[key]).__name__ == 'dict':  # 测试该结点是否为字典,如果不是字典,代表此结点为叶子结点
            numLeafs += getNumLeafs(secondDict[key])
        else:
            numLeafs += 1
    return numLeafs


"""
函数说明:获取决策树的层数
Parameters:
    myTree - 决策树
Returns:
    maxDepth - 决策树的层数
"""


def getTreeDepth(myTree):
    maxDepth = 0  # 初始化决策树深度
    # python3中myTree.keys()返回的是dict_keys,不在是list,所以不能使用myTree.keys()[0]的方法获取结点属性,
    # 可以使用list(myTree.keys())[0]
    firstStr = next(iter(myTree))
    secondDict = myTree[firstStr]  # 获取下一个字典
    for key in secondDict.keys():
        if type(secondDict[key]).__name__ == 'dict':  # 测试该结点是否为字典,如果不是字典,代表此结点为叶子结点
            thisDepth = 1 + getTreeDepth(secondDict[key])
        else:
            thisDepth = 1
        if thisDepth > maxDepth:
            maxDepth = thisDepth  # 更新层数
    return maxDepth


"""
函数说明:绘制结点
Parameters:
    nodeTxt - 结点名
    centerPt - 文本位置
    parentPt - 标注的箭头位置
    nodeType - 结点格式
"""


def plotNode(nodeTxt, centerPt, parentPt, nodeType):
    arrow_args = dict(arrowstyle="<-")  # 定义箭头格式
    font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)  # 设置中文字体
    createPlot.ax1.annotate(nodeTxt, xy=parentPt, xycoords='axes fraction',  # 绘制结点
                            xytext=centerPt, textcoords='axes fraction',
                            va="center", ha="center", bbox=nodeType, arrowprops=arrow_args, fontproperties=font)


"""
函数说明:标注有向边属性值
Parameters:
    cntrPt、parentPt - 用于计算标注位置
    txtString - 标注的内容
"""


def plotMidText(cntrPt, parentPt, txtString):
    xMid = (parentPt[0] - cntrPt[0]) / 2.0 + cntrPt[0]  # 计算标注位置
    yMid = (parentPt[1] - cntrPt[1]) / 2.0 + cntrPt[1]
    createPlot.ax1.text(xMid, yMid, txtString, va="center", ha="center", rotation=30)


"""
函数说明:绘制决策树
Parameters:
    myTree - 决策树(字典)
    parentPt - 标注的内容
    nodeTxt - 结点名
"""


def plotTree(myTree, parentPt, nodeTxt):
    decisionNode = dict(boxstyle="sawtooth", fc="0.8")  # 设置结点格式
    leafNode = dict(boxstyle="round4", fc="0.8")  # 设置叶结点格式
    numLeafs = getNumLeafs(myTree)  # 获取决策树叶结点数目,决定了树的宽度
    depth = getTreeDepth(myTree)  # 获取决策树层数
    firstStr = next(iter(myTree))  # 下个字典
    cntrPt = (plotTree.xOff + (1.0 + float(numLeafs)) / 2.0 / plotTree.totalW, plotTree.yOff)  # 中心位置
    plotMidText(cntrPt, parentPt, nodeTxt)  # 标注有向边属性值
    plotNode(firstStr, cntrPt, parentPt, decisionNode)  # 绘制结点
    secondDict = myTree[firstStr]  # 下一个字典,也就是继续绘制子结点
    plotTree.yOff = plotTree.yOff - 1.0 / plotTree.totalD  # y偏移
    for key in secondDict.keys():
        if type(secondDict[key]).__name__ == 'dict':  # 测试该结点是否为字典,如果不是字典,代表此结点为叶子结点
            plotTree(secondDict[key], cntrPt, str(key))  # 不是叶结点,递归调用继续绘制
        else:  # 如果是叶结点,绘制叶结点,并标注有向边属性值
            plotTree.xOff = plotTree.xOff + 1.0 / plotTree.totalW
            plotNode(secondDict[key], (plotTree.xOff, plotTree.yOff), cntrPt, leafNode)
            plotMidText((plotTree.xOff, plotTree.yOff), cntrPt, str(key))
    plotTree.yOff = plotTree.yOff + 1.0 / plotTree.totalD


"""
函数说明:创建绘制面板
Parameters:
    inTree - 决策树(字典)
"""


def createPlot(inTree):
    fig = plt.figure(1, facecolor='white')  # 创建fig
    fig.clf()  # 清空fig
    axprops = dict(xticks=[], yticks=[])
    createPlot.ax1 = plt.subplot(111, frameon=False, **axprops)  # 去掉x、y轴
    plotTree.totalW = float(getNumLeafs(inTree))  # 获取决策树叶结点数目
    plotTree.totalD = float(getTreeDepth(inTree))  # 获取决策树层数
    plotTree.xOff = -0.5 / plotTree.totalW;
    plotTree.yOff = 1.0;  # x偏移
    plotTree(inTree, (0.5, 1.0), '')  # 绘制决策树
    plt.show()

生成决策树:
在这里插入图片描述

因为数据集仅对A3特征存在可分,所以此数据集对于决策树只生成一个节点

运行结果:
在这里插入图片描述

类别精确率召回率F1
00.960.960.96
10.960.960.96

模型精度(准确率):0.96000

5.逻辑斯蒂回归

特点:
逻辑斯谛回归是经典的分类方法,它属于对数线性模型,原理是根据现有的数据对分类边界线建立回归公式,以此进行分类。(主要思想)

定义:
在线性回归模型的基础上,使用Sigmoid函数,将线性模型的结果压缩到[0,1]之间,使其拥有概率意义,它可以将任意输入映射到[0,1]区间,实现值到概率转换。

  • 属于概率性判别式模型
  • 线性分类算法

在学习逻辑回归模型之前,先来看一下逻辑斯谛分布,因为我们的逻辑斯蒂模型就是根据逻辑斯蒂分布得到的;通过参数估计方法直接估计出参数,从而得到P(Y|X)。

import pandas as pd
from sklearn import metrics
from sklearn.linear_model import LogisticRegression
from math import exp
from math import *
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from numpy import *
import numpy as np


class LogisticRegressionClassifier(object):
    def __init__(self, eta=0.1, loop=30):
        self.eta = eta
        self.loop = loop

    def sigmoid(self, x):
        return 1.0 / (1 + exp(-x))

    def data_tranforce(self, x_train):
        data = []
        d = []
        for x in x_train:  # x因为是数组类型
            data.append([1.0, *x])  # 将每一行数组增加一个1.0数值,*x是去掉[]符号,形成一行数值
            # c=list(x)  #将一维数组变成列表
            # c.insert(0, 1.0)  #这种insert()方法必须是列表
            # print(list(x).insert(0,1.0))
            # d.append(c)  #类似d=[[1.0,2,3],[1.0,3,4.8]]

        return data

    def fit(self, x_train, y_train):
        data_mat = self.data_tranforce(x_train)  # 处理每个样本的特征值
        n = shape(data_mat)[1]  # 求出data_mat对应参数的个数
        self.weight = ones((n, 1))  # 初始化参数w数组
        cls = self.loop
        for k in range(cls):  # 循环多少次
            for i in range(len(x_train)):  # 遍历每一个样本
                h = self.sigmoid(np.dot(data_mat[i], self.weight))
                err = (y_train[i] - h)
                # 随着每次更新err下面就会更新self.weight的向量
                self.weight += self.eta * err * np.transpose([data_mat[i]])  # [data_mat[i]]变成1*4的数组,一维数组转置必须加一个[]

    # 测试训练模型的准确性
    def test(self, x_test, y_test):
        numbers = 0
        x_test = self.data_tranforce(x_test)  # 处理每个样本的特征值加一个1.0
        y_predict = []
        for x, y in zip(x_test, y_test):
            result = np.dot(x, self.weight)
            if result > 0 and y == 1 or result > 0 and y == 0:
                y_predict.append(1)
            if result < 0 and y == 0 or result < 0 and y == 1:
                y_predict.append(0)
        return np.array(y_predict)


def main():
    df = pd.read_csv('../data/iris.data',
                     header=None)  # 读取数据集的数据
    df.tail()  # 对数据进行矩阵形式的显示
    x = df.iloc[:100, :2].values
    y = df.iloc[:100, 4].values
    y = np.where(y == 'Iris-setosa', 1, 0)
    x_train = x[0:25]
    x_train = np.concatenate((x_train, x[75:100]))
    x_test = x[25:75]
    y_train = y[0:25]
    y_train = np.concatenate((y_train, y[75:100]))
    y_test = np.array(y[25:75])
    print('train', y_train)
    print('test', y_test)
    my_l = LogisticRegressionClassifier()
    my_l.fit(x_train, y_train)

    y_predict = my_l.test(x_test, y_test)
    print('ptedict', y_predict)
    M = metrics.confusion_matrix(y_predict, y_test)
    print('混淆矩阵:\n', M)

    n = len(M)
    for i in range(n):
        rowsum, colsum = sum(M[i]), sum(M[r][i] for r in range(n))
        precision = M[i][i] / float(colsum)
        recall = M[i][i] / float(rowsum)
        F1 = precision * recall * 2 / (precision + recall)
        print('y=%d 精确率: %s' % (i, precision), '召回率: %s' % recall, 'F1值: %s' % F1)
    print("逻辑斯蒂回归模型精度:{:.5f}".format(np.mean(y_predict == y_test)))


if __name__ == "__main__":
    main()

运行结果:在这里插入图片描述

类别精确率召回率F1
010.610.76
10.3610.52

模型精度(准确率):0.68000

总结

机器学习算法有很多,本学期及本实验以分类算法为主,讲解了经典的分类算法,如感知机,knn,朴素贝叶斯,决策树,逻辑斯蒂回归,最大熵模型,SVM(支持向量机),AdaBoost ,k-means(聚类算法)等,通过对于分类算法的学习,我学习到了各算法的原理以及实现,还有与其他算法的比较,及一些相关知识点:

随机梯度下降(SGD):
是一种简单但又非常高效的方法,主要用于凸损失函数下线性分类器的判别式学习,例如(线性) 支持向量机和逻辑斯蒂回归。
优势:
• 高效。
• 易于实现 (有大量优化代码的机会)。
劣势:
• SGD 需要一些超参数,例如正则化参数和迭代次数。
• SGD 对特征缩放敏感。

最小二乘法与梯度下降法比较:
最小二乘法跟梯度下降法都是通过求导来求损失函数的最小值, 首先它们都是机器学习中,计算问题最优解的优化方法,但它们采用的方式不同,前者采用暴力的解方程组方式,直接,简单,粗暴,在条件允许下,求得最优解;而后者采用步进迭代的方式,一步一步的逼近最优解。实际应用中,大多问题是不能直接解方程求得最优解的,所以梯度下降法应用广泛。

KNN最近邻:
原理是从训练样本中找到与新点在距离上最近的预定数量的几个点,然后从这些点中预测标签。 这些点的数量可以是用户自定义的常量(K-最近邻学习), 也可以根据不同的点的局部密度(基于半径的最近邻学习)确定。距离通常可以通过任何度量来衡量: 标准欧式距离是最常见的选择。尽管它简单,但最近邻算法已经成功地适用于很多的分类和回归问题,例如手写数字或卫星图像的场景。 作为一个非参数化方法,它经常成功地应用于决策边界非常不规则的分类情景下。最近邻分类属于 基于实例的学习 或 非泛化学习 :它不会去构造一个泛化的内部模型,而是简单地存储训练数据的实例。 分类是由每个点的最近邻的简单多数投票中计算得到的:一个查询点的数据类型是由它最近邻点中最具代表性的数据类型来决定的。

朴素贝叶斯:
朴素贝叶斯方法是基于贝叶斯定理的一组有监督学习算法,即“简单”地假设每对特征之间相互独立。基于训练集所求得概率,根据贝叶斯概率模型求得测试集各类别概率大小,取概率最大的类别为预测类别;朴素贝叶斯算法假设了数据集属性之间是相互独⽴的,因此算法的逻辑性⼗分简单,并且算法较为稳定,当数据呈现不同的特点时,朴素贝叶斯的分类性能不会有太⼤的差异。换句话说就是朴素贝叶斯算法的健壮性⽐较好,对于不同类型的数据集不会呈现出太⼤的差异性。当数据集属性之间的关系相对⽐较独⽴时,朴素贝叶斯分类算法会有较好的效果。
缺点:性独⽴性的条件同时也是朴素贝叶斯分类器的不⾜之处。数据集属性的独⽴性在很多情况下是很难满⾜的,因为数据集的属性之间往往都存在着相互关联,如果在分类过程中出现这种问题,会导致分类的效果⼤⼤降低。

决策树:
信息熵(info entropy)
首先介绍一下信息熵的概念. 我们把样本抽取过程当做一次随机试验A, 那么A有k个可能的输出A1,A2,…,Ak . 对应于k个分类. 那么A的信息熵定义为:

信息增益:决策树根据各特征的信息增益大小选择决策树节点

基尼指数:不管是信息熵还是基尼指数, 他们都是不纯函数的一种表达, 不纯度变化的计算没有任何变化. 我们也可以自己撰写不纯度函数.
一般来说, 机器学习都需要特征归一化, 目的是让特征之间的比较可以在同一个量纲上进行. 但是从数据构建过程来看, 不纯函数的计算和比较都是单特征的. 所有决策树不需要数据的归一化.
决策树的主要问题是容易形成过拟合. 如果我们通过各种剪枝和条件限制, 虽然可以避免过拟合, 但是会牺牲特征的有效性.

算法精确度比较:

算法感知机朴素贝叶斯Knn决策树逻辑斯蒂回归
精确度0.960.820.960.960.68

根据结果可以看出朴素贝叶斯与逻辑斯蒂回归算法精度相对来说其他算法较低;其他算法由于数据集并不是很大,并且同一数据集有可能逻辑斯蒂回归是一种对数线性模型。
由此总结出逻辑斯蒂回归的缺点:

1)容易欠拟合,分类精度不高。
2)数据特征有缺失或者特征空间很大时表现效果并不好。

经典的逻辑斯蒂回归模型(LR)可以用来解决二分类问题,但是它输出的并不是确切类别,而是一个概率。 在分析LR原理之前,先分析一下线性回归。线性回归能将输入数据通过对各个维度的特征分配不同的权重来进行表征,使得所有特征协同作出最后的决策。但是,这种表征方式是对模型的一个拟合结果,不能直接用于分类。在LR中,将线性回归的结果通过sigmod函数映射到0到1之间,映射的结果刚好可以看做是数据样本点属于某一类的概率,如果结果越接近0或者1,说明分类结果的可信度越高。这样做不仅应用了线性回归的优势来完成分类任务,而且分类的结果是0~1之间的概率,可以据此对数据分类的结果进行打分。对于线性不可分的数据,可以对非线性函数进行线性加权,得到一个不是超平面的分割面。 因此对于逻辑斯蒂回归分类一些数据可能并不是很准确。

  • 10
    点赞
  • 101
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
机器学习的手势识别算法通常分为两个步骤: 1. 特征提取:从输入的图像或视频序列中提取手势所需的特征。这些特征可以是手的形状、位置、方向、运动轨迹等等。 2. 分类器训练:使用提取出来的特征来训练一个分类器,例如支持向量机(SVM)、人工神经网络(ANN)或决策树等等。这个分类器可以用来预测输入的手势属于哪一类。 下面是一个基于机器学习的手势识别算法的简单流程: 1. 收集手势数据集:手势数据集应该包含不同种类的手势,例如数字、字母、手势动作等等。每个手势应该有多个不同的实例,以便训练算法。 2. 图像预处理:将收集到的手势图像进行预处理,例如图像增强、去噪、值化等等。预处理可以提高算法的识别精度。 3. 特征提取:从预处理后的图像中提取手势所需的特征。常用的特征提取算法包括边缘检测、角点检测、轮廓提取等等。 4. 特征选择:选择最能代表手势的特征,去除冗余或不相关的特征。这可以减少算法的计算量和提高算法的准确性。 5. 训练分类器:使用选择好的特征来训练一个分类器。训练过程包括输入手势的标签和特征,让算法通过学习来识别手势。训练的分类器可以根据需要进行调整和优化。 6. 测试算法:使用测试集来评估算法的准确性和性能。测试集包含了算法未见过的手势实例,用于验证算法的泛化能力。 7. 应用算法:将训练好的算法应用于实际应用中,例如手势控制、手势识别等等。 总的来说,机器学习的手势识别算法需要收集大量的手势数据集,并通过特征提取和分类器训练来训练算法算法的准确性和性能取决于数据集的质量和特征提取方法的有效性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Data艺术家

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值