美文网首页
SVM支持向量机实现兵王问题的分类(Python版)

SVM支持向量机实现兵王问题的分类(Python版)

作者: 正能量y先生 | 来源:发表于2020-07-04 12:36 被阅读0次

    1.说明

    最近在B站看了浙江大学胡浩基老师的机器学习课程,完全面向入门人群感觉挺好。其中有关原理的部分讲的很细。其中在第六章-支持向量机的例题兵王问题中课程只给了MATLAB的版本,没有Python语言的优势。所以本文首先根据胡老师的MATLAB版的思路改写成Python版,然后使用Python的优势重新编写一版。

    2.问题分析

    在国际象棋中,存在着一种残局的现象。剩余三子,分别是黑方的王,白方的王和兵,那么无论这三子在棋盘的布局如何,只有两种结果,白方胜利和逼和。这就是一个二分类问题。

    3. 数据集

    关于这个问题的数据集krkopt.DATA可以在老师给的代码里面找到,然后老师也推荐了一个网址UCI Machine Learning
    数据形式:前面六个就是棋子的位置,draw就是逼和,后面的数字eight就代表,白棋最少用8步就能将死对方。

    a,1,b,3,c,2,draw
    a,1,c,1,c,2,draw
    c,2,c,6,e,1,eight
    ...
    

    4.代码实现

    4.1 MATLAB思想的Python实现

    使用了LIBSVM -- A Library for Support Vector Machines

    # -*- coding: utf-8 -*-
    
    import numpy as np
    from libsvm.svm import *
    from libsvm.svmutil import *
    
    def data_read_mat(file_name):
        '''
        从文件中取出数据
        :param file_name: 文件名称
        :return: 返回一个n*7的矩阵,前6项是三个坐标,第七项是标签
        '''
        num_list = []
        '''
        一下是对数据进行读入并且处理,其中open的参数中encoding之所以设置成UTF-8-sig
        是因为如果我们把这个参数设置为UTF-8或者不设置,在读入的开头多出\ufeff这么一串
        东西,有时候会以中文字的形式出现。
        '''
        with open(file_name, "r", encoding='UTF-8-sig') as file:
            for l in file:
                l = l.split(',')
                list_k = []
                for j in range(3):
                    list_k.append(ord(l[j * 2]) - ord('a'))
                    list_k.append(ord(l[j * 2 + 1]) - ord('0'))
                if (l[6][0] == 'd'):
                    list_k.append(0)
                else:
                    list_k.append(1)
                num_list.append(list_k)
        num_mat = np.array(num_list, dtype="float")
        '''
        在此处是以numpy的二维数据矩阵的形式存储的,本以为使用numpy的数据进行运算可以使得
        训练的速度快一些。结果发现如果要往libsvm中的函数传入参数只能传入list型不能传入numpy
        的数据类型。所以,后面又把数据类型转回了list型。但是,我猜应该是有方法可以把numpy
        的数据类型传入使用的。于是我在读取数据后任然返回的是numpy的形式。
        '''
        return num_mat
    
    
    def data_deal(mat, len_train, len1, len_test, len2):
        '''
        将数据进行处理,分出训练数据和测试数据
        :param mat: 大矩阵,其中包括训练数据和测试数据
        :param len_train:训练数据
        :param len1: 输入坐标
        :param len_test: 测试数据
        :param len2: 标签
        :return: 返回的依次是训练输入数据,测试输入数据,训练输入数据的标签,测试输入数据的标签
        '''
        np.random.shuffle(mat)  # 先将矩阵按行打乱。然后根据要求对矩阵进行分割,第一部分就是训练集,第二部分就是测试集
        x_part1 = mat[0:len_train, 0:len1]
        x_part2 = mat[len_train:, 0:len1]
        y_part1 = mat[0:len_train, len1]
        y_part2 = mat[len_train:, len1]
        # 标准化
        # 根据训练集求出均值和方差
        avgX = np.mean(x_part1)
        stdX = np.std(x_part1)
        # print(avgX,stdX)
        # 将训练集和测试集都进行标准化处理
        for data in x_part1:
            for j in range(len(data)):
                data[j] = (data[j] - avgX) / stdX
        for data in x_part2:
            for j in range(len(data)):
                data[j] = (data[j] - avgX) / stdX
        return x_part1, y_part1, x_part2, y_part2
    
    
    def TrainModel(CScale, gammaScale, prob):
        '''
        :param CScale: 参数C的取值序列
        :param gammaScale:  参数γ的取值序列
        :param prob: 训练集合对应的标签
        :return: maxACC(最高正确率),maxACC_C(最优参数C),maxACC_gamma(最优参数γ)
        '''
        maxACC = 0
        maxACC_C = 0
        maxACC_gamma = 0
    
        for C in CScale:
            C_ = pow(2, C)
            for gamma in gammaScale:
                gamma_ = pow(2, gamma)
                # 设置训练的参数
                # 其中-v 5表示的是2折交叉验证
                # “-q”可以去掉这样也就可以看到训练过程
                param = svm_parameter('-t 2 -c ' + str(C_) + ' -g ' + str(gamma_) + ' -v 5 -q')
                ACC = svm_train(prob, param)  # 进行训练,但是传回的不是训练模型而是5折交叉验证的准确率
                # 更新数据
                if (ACC > maxACC):
                    maxACC = ACC
                    maxACC_C = C
                    maxACC_gamma = gamma
        return maxACC, maxACC_C, maxACC_gamma
    
    
    def getNewList(L, U, step):
        l = []
        while (L < U):
            l.append(L)
            L += step
        return l
    
    
    def TrainModelSVM(data, label, iter, model_file):
        '''
        模型训练并保存
        :param data: 数据
        :param label: 标签
        :param iter:训练次数
        :param model_file:模型的保存位置
        :return: 返回最优参数
        '''
        # 将数据转换成list型的数据。因为,在svm的函数中好像只能传入list型的数据进行训练使用
        X = data.tolist()
        Y = label.tolist()
        CScale = [-5, -3, -1, 1, 3, 5, 7, 9, 11, 13, 15]  # 参数C的2^C
        gammaScale = [-15, -13, -11, -9, -7, -5, -3, -1, 1, 3]  # 参数γ的取值2^γ
        cnt = iter
        step = 2  # 用于重新生成CScale和gammaScale序列
        maxACC = 0  # 训练过程中的最大正确率
        bestACC_C = 0  # 训练过程中的最优参数C
        bestACC_gamma = 0  # 训练过程中的最优参数γ
        prob = svm_problem(Y, X)  # 传入数据
        while (cnt):
            # 用传入的参数序列进行训练,返回的是此次训练的最高正确率,最优参数C,最优参数γ
            maxACC_train, maxACC_C_train, maxACC_gamma_train = TrainModel(CScale, gammaScale, prob)
            # 数据更新
            if (maxACC_train > maxACC):
                maxACC = maxACC_train
                bestACC_C = maxACC_C_train
                bestACC_gamma = maxACC_gamma_train
            # 根据返回的参数重新生成CScale序列和gammaScale序列用于再次训练,下一次训练的C参数和γ参数的精度会比之前更高
            # step就是CScale序列和gammaScale序列的相邻两个数之间的间隔
            new_step = step * 2 / 10
            CScale = getNewList(maxACC_C_train - step, maxACC_C_train + step + new_step, new_step)
            gammaScale = getNewList(maxACC_gamma_train - step, maxACC_gamma_train + step + new_step, new_step)
            cnt -= 1
        # 获得最优参数后计算出对应的C和γ,并且训练获得“最优模型”
        C = pow(2, bestACC_C)
        gamma = pow(2, bestACC_gamma)
        param = svm_parameter('-t 2 -c ' + str(C) + ' -g ' + str(gamma))
        model = svm_train(prob, param)  # 交叉验证准确率
        svm_save_model(model_file, model)  # 保存模型
        return model
    
    
    def main():
        data_file = r"/Users/apple/Documents/PycharmPro/svmtest2/krkopt.data"  # 数据存放的位置(需要修改)
        mode_file = r"/Users/apple/Documents/PycharmPro/svmtest2/model_file"  # 训练模型保存的位置(需要修改)
        data_mat = data_read_mat(data_file)  # 从文件中读取数据并处理
        # 以下是对数据训练进行分配,可以根据你的需要进行调整
        train = 5000  # 5000组数据作为训练数据
        test = len(data_mat) - 5000  # 剩下的数据作为测试数据
        # ————————————————————————————————————————————————————————————#
        x_len = 6  # 输入数据的维度是6维,即三个棋子的坐标
        y_len = len(data_mat[0] - x_len)  # 输出的数据时1维,即两种结果
        iter = 2  # 训练的次数,训练的次数越多参数就调整的精度就越高
        x_train, y_train, x_test, y_test = data_deal(data_mat, train, x_len, test, y_len)  # 对数据进行分割
        if (input("是否需要进行训练?") == 'y'):  # 如果输入y就会进行训练,否则就可以直接使用之前训练的完成的模型
            model = TrainModelSVM(x_train, y_train, iter, mode_file)  # 传入输入数据,标签进行模型的训练
        else:
            model = svm_load_model(mode_file)  # 直接加载现有模型
        X = x_test.tolist()  # 将测试集的输入集转换成list
        Y = y_test.tolist()  # 将测试集的输出集转换成list
        p_labs, p_acc, p_vals = svm_predict(Y, X, model)
    
    
    if __name__ == "__main__":
        main()
    

    4.2 Python库简化后代码实现

    (1) 使用了Pandas处理数据集
    (2) 使用了sklearn.model_selection数据集分割,自动调参
    (3) 使用了sklearn.svm线性支持向量分类

    import pandas as pd
    from sklearn.model_selection import train_test_split,cross_val_score,GridSearchCV
    from sklearn.svm import SVC
    import numpy as np
    
    def svm_c(x_train, x_test, y_train, y_test):
        # rbf核函数,设置数据权重
        svc = SVC(kernel='rbf', class_weight='balanced',)
        c_range = np.logspace(-5, 15, 11, base=2)
        gamma_range = np.logspace(-9, 3, 13, base=2)
        # 网格搜索交叉验证的参数范围,cv=3,3折交叉
        param_grid = [{'kernel': ['rbf'], 'C': c_range, 'gamma': gamma_range}]
        grid = GridSearchCV(svc, param_grid, cv=3, n_jobs=-1)
        # 训练模型
        clf = grid.fit(x_train, y_train)
        # 计算测试集精度
        score = grid.score(x_test, y_test)
        print('精度为%s' % score)
    
    
    # 读取数据
    data = pd.read_csv('/Users/apple/Documents/PycharmPro/svmtest/krkopt.data', header=None)
    data.dropna(inplace=True)  # 不创建新的对象,直接对原始对象进行修改
    # 样本数值化 a,b,c...h 转化为 1,2,3...8
    for i in [0, 2, 4]:
        data.loc[data[i] == 'a', i] = 1
        data.loc[data[i] == 'b', i] = 2
        data.loc[data[i] == 'c', i] = 3
        data.loc[data[i] == 'd', i] = 4
        data.loc[data[i] == 'e', i] = 5
        data.loc[data[i] == 'f', i] = 6
        data.loc[data[i] == 'g', i] = 7
        data.loc[data[i] == 'h', i] = 8
    # 将标签数值化 -1表示将死,1表示和棋
    data.loc[data[6] != 'draw', 6] = -1
    data.loc[data[6] == 'draw', 6] = 1
    # 归一化处理 Z-score标准化方法 (经过处理的数据符合标准正态分布,即均值为0,标准差为1)
    # from sklearn.preprocessing import StandardScaler
    for i in range(6):
        data[i] = (data[i]-data[i].mean())/data[i].std()
    
    # 拆分训练集和测试集    train_teat_split() https://www.cnblogs.com/bonelee/p/8036024.html 省略random_state=0
    X_train, X_test, y_train, y_test = train_test_split(data.iloc[:, :6], data[6].astype(int), test_size=0.82178500142572)
    svm_c(X_train, X_test, y_train, y_test)
    

    4.3 实验结果

    老师用Matlab得到的结果是99.61%,那么上述程序的多次实验,平均正确率为99.53%,可以看到差距不是很大,而且呢就算是同一个程序得到的结果可能也会有差距,这是因为划分测试集和训练集的不同造成的。

    AUC和EER曲线:

    AUC是指黄色曲线和x轴的面积,EER是指蓝色曲线与黄色曲线的交点的横坐标,衡量一个系统的好坏就在于auc越大,性能越好,eer越小性能越好。


    auc_err.png

    5.SVM支持向量机总结

    5.1 SVM的步骤

    (1) 将原始数据转化为SVM算法软件或包所能识别的数据格式;
    (2) 将数据标准化;(防止样本中不同特征数值大小相差较大影响分类器性能)
    (3) 不知使用什么核函数,考虑使用RBF;
    (4) 利用交叉验证网格搜索寻找最优参数(C, γ);(交叉验证防止过拟合,网格搜索在指定范围内寻找最优参数)
    (5) 使用最优参数来训练模型;
    (6) 测试。

    5.2 网格搜索参数小技巧

    网格搜索法中寻找最优参数中为寻找最优参数,网格大小如果设置范围大且步长密集的话难免耗时,但是不这样的话又可能找到的参数不是很好,针对这解决方法是,先在大范围,大步长的粗糙网格内寻找参数。在找到的参数左右在设置精细步长找寻最优参数比如:

    一开始寻找范围是 C = 2^−5 , 2^−3 , . . . , 2^15 and γ = 2^−15 , 2^−13 , . . . , 2^3 .由此找到的最优参数是(2^3 , 2^−5 );
    然后设置更小一点的步长,参数范围变为2^1 , 2^1.25 , . . . , 2^5 and γ = 2^−7 , 2^−6.75 , . . . , 2^−3 在这个参数范围再寻找最优参数。
      这样既可以避免一开始就使用大范围,小步长而导致分类器进行过于多的计算而导致计算时间的增加。

    5.3 线性核和RBF的选择

    如果训练样本的特征数量过于巨大,也许就不需要通过RBF等非线性核函数将其映射到更高的维度空间上,利用非线性核函数也并不能提高分类器的性能。利用linear核函数也可以获得足够好的结果,此外,也只需寻找一个合适参数C,但是利用RBF核函数取得与线性核函数一样的效果的话需要寻找两个合适参数(C, γ)。

    分三种情况讨论:
    (1) 样本数量远小于特征数量:这种情况,利用情况利用linear核效果会高于RBF核。
    (2) 样本数量和特征数量一样大:线性核合适,且速度也更快。liblinear更适合
    (3) 样本数量远大于特征数量: 非线性核RBF等合适。

    相关文章

      网友评论

          本文标题:SVM支持向量机实现兵王问题的分类(Python版)

          本文链接:https://www.haomeiwen.com/subject/bvxafktx.html