机器学习-Dry Beans 分类实现过程

1.写在前面

前段时间收到进入导师团队的考核,考核内容是一个多元分类问题,具体内容是Dry Bean,前几天刚刚学习了机器学习的一点点内容,感觉对自己来说还是很有难度的,今日写此博客,记录下自己的实验过程(本想过段时间在写,想想还是写了吧,有些事情拖着拖着就忘了= . =),希望能够帮到你,写的不好,请多多指教!

拿到题目以后,先去看了看相关的文献,名为《Multiclass classification of dry beans using computer vision and machine learing techniques》,文中讲道使用MLP、KNN、SVM、DT实现,给出了实现结果等等。读完之后,先去学了KNN,发现还是比较简单的,然后看了MLP,一头雾水(不会调参,准确率在40%左右,博客内就不写了),然后学了DT,SVM未学,以后学了会补上。

2.数据预处理

数据集中一共10000多条数据,每个数据包含16个特征,1个标签(该条数据对应的种子类别),一共有7类种子。
每个特征都为定距数据,即:取值范围为连续取值的数值数据
部分特征是通过其他特征计算出来。(这让我想到线性相关
各类种子的个数如下:
Seker(2027), Barbunya(1322), Bombay(522), Cali(1630), Dermosan(3546), Horoz(1928) ,Sira(2636)
下面是各特征的最小值、最大值、平均值、标准差:
在这里插入图片描述
从表中可以看出,不同特征之间,数据的量级差别较大,数据范围跨度差别很大,面积的最大值达到了254616,而一些特征的最大值还不到1.0,数据值域很小。(埋个伏笔,后边算法改进会用到)

3.KNN算法实现

一个样本在特征空间中的k个最近邻的样本中的大多数都属于某一个类别,则该样本也属于这个类别。其中k表示最近邻居的个数(距离计算使用欧氏距离)。
在这里插入图片描述
(该图片来源于他人博客,参考链接附在文末)
欧氏距离公式:在这里插入图片描述

1.算法实现思路

1.将数据随机划分训练集和测试集

2.计算测试集中单条记录与训练集数据之间的欧式距离

3.将计算的距离进行由小到大排序

4.找出距离最小的前k个值

5.计算找出的值中每类种频次,少数服从多数原则,返回频次最高的类别,即为该种子的类别

2.代码实现
import openpyxl
import random
import numpy as np
import operator
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler  # 均值归一化
from sklearn.metrics import confusion_matrix  # 生成混淆矩阵
from sklearn.metrics import classification_report  # 分类报告


# 使用KNN算法实现对干豆种子的分类

def openfile(filename):
    """
    打开数据集,进行数据处理
    :param filename:文件名
    :return:特征集数据、标签集数据
    """
    # 打开excel
    readbook = openpyxl.load_workbook(filename)
    # 获取sheet
    sheet = readbook['Dry_Beans_Dataset']
    # 数据集中数据的总数量
    n_samples = sheet.max_row - 1
    # 数据集中特征的种类个数
    n_features = sheet.max_column - 1
    # empty()函数构造一个未初始化的矩阵,行数为数据集数量,列数为特征值的种类个数
    data = np.empty((n_samples, n_features))

    # empty()函数构造一个未初始化的矩阵,行数为数据集数量,1列
    target = np.empty((n_samples,), dtype=np.object)

    index = 0
    for i in sheet.values:
        if (index != 0):
            data[index - 1] = np.asarray(i[0:-1], dtype=np.object)
            target[index - 1] = np.asarray(i[-1], dtype=np.object)
            index += 1
        else:
            index += 1

    return data, target


def random_number(data_size):
    """
    该函数使用shuffle()打乱一个包含从0到数据集大小的整数列表。因此每次运行程序划分不同,导致结果不同

    改进:
    可使用random设置随机种子,随机一个包含从0到数据集大小的整数列表,保证每次的划分结果相同。

    :param data_size: 数据集大小
    :return: 返回一个列表
    """

    number_set = []
    for i in range(data_size):
        number_set.append(i)

    random.shuffle(number_set)

    return number_set


def split_data_set(data_set, target_set, rate=0.1):
    """
    说明:分割数据集,默认数据集的10%是测试集

    :param data_set: 数据集
    :param target_set: 标签集
    :param rate: 测试集所占的比率
    :return: 返回训练集数据、测试集数据、训练集标签、测试集标签
    """
    # 计算训练集的数据个数
    train_size = int((1 - rate) * len(data_set))

    # 随机获得数据的下标
    data_index = random_number(len(data_set))

    # print(data_index)

    # 分割数据集(X表示数据,y表示标签),以返回的index为下标
    # 训练集数据
    x_train = data_set[data_index[:train_size]]
    # 测试集数据
    x_test = data_set[data_index[train_size:]]
    # 训练集标签
    y_train = target_set[data_index[:train_size]]
    # 测试集标签
    y_test = target_set[data_index[train_size:]]
    return x_train, x_test, y_train, y_test


def data_diatance(x_test, x_train):
    """
    :param x_test: 测试集
    :param x_train: 训练集
    :return: 返回计算的距离
    """

    distances = np.sqrt(sum((x_test - x_train) ** 2))
    return distances


# 只传入一个k值
def knn(x_test, x_train, y_train, k):
    """
    :param x_test: 测试集数据
    :param x_train: 训练集数据
    :param y_train: 训练集标签
    :param k: 邻居数
    :return: 返回一个列表包含预测结果
    """

    # 预测结果列表,用于存储测试集预测出来的结果
    predict_result_set = []

    # 训练集的长度
    train_set_size = len(x_train)

    # 创建一个全零的矩阵,长度为训练集的长度
    distances = np.array(np.zeros(train_set_size))

    # 计算每一个测试集与每一个训练集的距离
    # i 代表测试集记录、indx代表训练集记录
    for i in x_test:
        for indx in range(train_set_size):
            # 计算数据之间的距离
            # 传入测试集、训练集
            distances[indx] = data_diatance(i, x_train[indx])

        # 此时计算完了 测试集第i条记录 和 所有训练集的 欧氏距离
        # y = x.argsort()
        # argsort()函数是将x中的元素从小到大排列,提取其对应的index(索引),然后输出到y
        # 排序后的距离的下标,从小到大,下标代表在训练集中的下标
        sorted_dist = np.argsort(distances)

        class_count = {}

        # 取出k个最短距离,并且计算每一个标签的数量
        for i in range(k):
            # 获得下标所对应的标签值 y_train 代表 训练集标签
            sort_label = y_train[sorted_dist[i]]
            sort_label = (str)(sort_label)
            # 将标签存入字典之中并存入个数
            count = class_count.get(sort_label, 0) + 1
            class_count[sort_label] = count

        # 对标签的个数 进行排序,从大到小排序
        sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True)

        # 将出现频次最高的放入预测结果列表
        predict_result_set.append(sorted_class_count[0][0])

    # 返回预测结果列表
    return predict_result_set


def knnChangeK(x_test_item, x_train, y_train, min_k, max_k):
    """
    传入k的范围 [min_k,max_k)
    :param x_test_item: 单个测试记录
    :param x_train: 训练集数据
    :param y_train: 训练接标签
    :param min_k: 最小k值
    :param max_k: 最大k值
    :return: 该单个测试记录对应不同k值下的预测结果
    """
    # 预测结果列表,用于存储不同k值下测试集预测出来的结果
    predict_result_set = []

    # 训练集的长度
    train_set_size = len(x_train)

    # 创建一个全零的矩阵,长度为训练集的长度
    distances = np.array(np.zeros(train_set_size))

    # 计算该测试记录与每一个训练集的距离
    for indx in range(train_set_size):
        # 计算数据之间的距离
        # 传入测试集、训练集
        distances[indx] = data_diatance(x_test_item, x_train[indx])

    # 此时计算完了 该条记录 和 所有训练集的 欧氏距离

    # 排序后的距离的下标,从小到大,下标代表在训练集中的下标
    sorted_dist = np.argsort(distances)

    class_count = {}

    # 枚举k的取值范围
    # 取出k个最短距离,并且计算每一个标签的数量
    for k in range(min_k, max_k):
        for index in range(k):
            # 获得下标所对应的标签值 y_train 代表 训练集标签
            sort_label = y_train[sorted_dist[index]]
            sort_label = (str)(sort_label)
            # 将标签存入字典之中并存入个数
            count = class_count.get(sort_label, 0) + 1
            class_count[sort_label] = count

        # 对标签的个数 进行排序,从大到小排序
        sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True)

        # 将出现频次最高的放入预测结果列表
        predict_result_set.append((int)(sorted_class_count[0][0]))

    # 返回预测结果列表
    return predict_result_set


def score(predict_result_set, y_test):
    """
    :param predict_result_set: 预测结果列表
    :param y_test: 测试集标签
    :return: 返回测试集精度
    """
    count = 0
    for i in range(0, len(predict_result_set)):
        if predict_result_set[i] == y_test[i]:
            count += 1

    # 预测对的结果 / 总预测数
    score = count / len(predict_result_set)

    return score


def convertNameToCode(name):
    """
    根据名字转换成相应的代码
    :param name: 姓名
    :return: 编码
    """
    if name == "SEKER":
        return 0
    if name == "BARBUNYA":
        return 1
    if name == "BOMBAY":
        return 2
    if name == "CALI":
        return 3
    if name == "DERMASON":
        return 4
    if name == "HOROZ":
        return 5
    if name == "SIRA":
        return 6


if __name__ == "__main__":
    filename = r'Dry_Bean_Dataset.xlsx'
    bean_dataset = openfile(filename)
    # 特征集
    feature = bean_dataset[0]
    # 标签集
    target = bean_dataset[1]

    # 将标签集从字符串转成对应的编码(int类型 便于计算)
    for i in range(len(target)):
        target[i] = convertNameToCode(target[i])

    # 数据划分
    x_train, x_test, y_train, y_test = split_data_set(feature, target)

   	x = []
    y = []
    result = []
    min_k = 20
    max_k = 21
    # 初始化result
    for i in range(max_k - min_k):
        result.append([])

    for i in x_test:
        # 遍历测试集的每一条数据 列表大小为 max_k-min_k
        x_test_item_result_list = knnChangeK(i, x_train, y_train, min_k, max_k)
        for j in range(len(x_test_item_result_list)):
            result[j].append(x_test_item_result_list[j])

    for i in range(len(result)):
        accuracy = score(result[i], y_test)
        x.append(i + min_k)
        y.append(accuracy)

    print(x)
    print(y)
    plt.plot(x, y)
    plt.xlabel('k-value')
    plt.ylabel('accuracy-value')
    plt.title(u'result map')
    plt.show()

    # 混淆矩阵
    print("输出混淆矩阵")
    conf_mat = confusion_matrix(y_test.astype('int'), result[0])
    print(conf_mat)
    # 精确度,召回率,F1值-F1值是精确度和召回率的调和平均值:
    target_names = ['SEKER', 'BARBUNYA', 'BOMBAY', 'CALI', 'DERMASON', 'HOROZ', 'SIRA']
    report = classification_report(y_test.astype('int'), result[0], target_names=target_names)
    print(report)

代码终于调通了,输入了k=10,准确率在73%左右,显然准确率不高!
试了试k取值从1~1000,绘制出来准确率曲线,结果是这样的:
在这里插入图片描述
整体趋势是下降的,而且准确率也不高,然后有测了测k取1~20,结果如下图所示:
在这里插入图片描述
预测率的整体变化趋势为逐渐下降,降到64%左右,K=1时,取值虽然最高,但是预测准确率不高!
显然这样是不行的!这样交上去不就凉了。。,下面针对数据集的特点,进行优化

3.对算法的优化

因为不同的特征之间数据的量级差距比较大,而且一些特征的值域范围非常大(面积、周长等特征),很可能导致在算法计算的过程中,将数据取值较小、值域范围较小的特征给忽略掉!
基于上述情况,决定采用均值方差归一化进行优化。(使用StandardScaler)
我用了现成的库。
具体何时均值方差归一化有 两种方式:

1:划分训练集和测试集之前,进行均值方差归一化
2:划分训练集和测试集之后,在分别进行均值方差归一化
(我测了测,第一种方式准确率要高一些,但并不意味着这样做更好!!)

加了这段代码

	# 对数据进行均值归一化处理
    scaler = StandardScaler()
    # 在数据集划分前对训练集和测试集统一进行处理
    feature = scaler.fit_transform(feature)

归一化完成后,取K=10,预测准确率达到了92.43%,明显提高!

4.实验结果

测试k从1~100,准确率变化情况,如图所示:
在这里插入图片描述
图中显示出在K取10~25之间,取到最大值约为92.8%
测试K从1~40之间,结果如图所示:
在这里插入图片描述
最值时K的取值在10~15之间
由于训练集、测试集不同的原因、图中显示出准确率比刚刚的高,其实每次的测量结果,准确率取最大值时,K的取值都不一定一样,甚至差的比较大,那么如何选取最优的K值,了解到需要用到 交叉验证。(本人尚未实现)

然后我取了K是12,结果是这样的:
混淆矩阵
在这里插入图片描述
从混淆矩阵中看出,Dermason和Sira在进行分类时容易分类错误,Bombay预测较准确
在这里插入图片描述
准确率为92.21%,效果还行 ^ - ^

4.决策树(DT)实现

本数据集中所包含的特征全为连续数据(定距数据),在构造决策树过程中,需要对特征进行离散化处理(了解到可以用二分法),选择最优的划分属性,离散化后,根据信息熵、信息增益 ,构建决策二叉树。
连续数据离散化处理,参考链接:https://blog.csdn.net/u012328159/article/details/79396893
代码如下:

import numpy as np
import random
from sklearn.tree import DecisionTreeClassifier
from sklearn import tree
import openpyxl
from sklearn.metrics import accuracy_score  # 对算法进行评分
from sklearn.metrics import confusion_matrix  # 生成混淆矩阵
from sklearn.metrics import classification_report  # 分类报告

def bean_type(s):
    it = {'SEKER': 0, 'BARBUNYA': 1, 'BOMBAY': 2, 'CALI': 3, 'DERMASON': 4, 'HOROZ': 5, 'SIRA': 6}
    return it[s]


iris_feature = 'Area', 'Perimeter', 'MajorAxisLength', 'MinorAxisLength', 'AspectRation', 'Eccentricity', 'ConvexArea', 'EquivDiameter', 'Extent', 'Solidity', 'roundness', 'Compactness', 'ShapeFactor1', 'ShapeFactor2', 'ShapeFactor3', 'ShapeFactor4'

if __name__ == "__main__":
    iris_dataset = openfile('Dry_Bean_Dataset.xlsx')
    # 特征集
    x = iris_dataset[0]
    # 标签集
    y = iris_dataset[1]
    for i in range(len(y)):
        temp_label = (str)(y[i])
        y[i] = bean_type(temp_label)

    feature_names = ['Area', 'Perimeter', 'MajorAxisLength', 'MinorAxisLength', 'AspectRation', 'Eccentricity',
                     'ConvexArea', 'EquivDiameter', 'Extent', 'Solidity', 'roundness', 'Compactness', 'ShapeFactor1',
                     'ShapeFactor2', 'ShapeFactor3', 'ShapeFactor4']

    feature_name = ['Perimeter', 'MajorAxisLength', 'MinorAxisLength', 'Compactness', 'ShapeFactor1']

    choice = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

    # 数据划分
    x_train, x_test, y_train, y_test = split_data_set(x, y, 0.2)

    # 决策树学习
    # min_samples_split:设置结点的最小样本数量,当样本数量可能小于此值时,结点将不会在划分。
    # entropy : 使用信息增益进行划分。
    clf = DecisionTreeClassifier(criterion='entropy', splitter='random', min_samples_split=50)
    # 在训练之前,可以对特征训练集进行拆分,选取部分特征进行训练

    x_train_features = x_train[:, choice]
    x_test_features = x_test[:, choice]
    # 传入训练集特征(可选)和响应的结果
    dt_clf = clf.fit(x_train_features, y_train.astype('int'))

    # 返回预测结果
    predict_result = dt_clf.predict(x_test_features)
    # 不分行列,改成一串
    # 目标结果
    y_test = y_test.reshape(-1)

    c = np.count_nonzero(predict_result == y_test)  # 统计预测正确的个数
    print('\t总数目数目:', len(x_test))
    print('\t预测正确数目:', c)
    print('\t准确率: %.2f%%' % (100 * float(c) / float(len(y_test))))

    # 混淆矩阵
    print("输出混淆矩阵")
    conf_mat = confusion_matrix(y_test.astype('int'), predict_result)
    print(conf_mat)
    # 精确度,召回率,F1值-F1值是精确度和召回率的调和平均值:
    target_names = ['SEKER', 'BARBUNYA', 'BOMBAY', 'CALI', 'DERMASON', 'HOROZ', 'SIRA']
    report = classification_report(y_test.astype('int'), predict_result, target_names=target_names)
    print(report)

    # 输出决策树到文件
    f = open('dry_bean_tree_16_features_2.dot', 'w')
    tree.export_graphviz(dt_clf, feature_names=feature_names, out_file=f)

(部分函数在KNN算法里有,唉,其实可以把公共的方法,单独写在一个类里的,太菜了,回头学学吧 =.=)
DecisionTreeClassifier,参考链接:https://blog.csdn.net/qq_41577045/article/details/79844709
所有特征一股脑全弄进去,准确率到了90.38%,但是我对其内的调参、具体实现过程,还是非常模糊的。有待加强学习。

初学机器学习,很多地方都还不知道,写的不对的地方,欢迎评论!!

参考链接:
KNN:
https://www.jb51.net/article/172682.htm
https://zhuanlan.zhihu.com/p/76682561
https://blog.csdn.net/wzyaiwl/article/details/90549391
决策树:
https://blog.csdn.net/u012328159/article/details/70184415
https://blog.csdn.net/qq_41577045/article/details/79844709

  • 14
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值