svm 交叉验证 python_SVM支持向量机实现兵王问题的分类(Python版)

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实现

# -*- 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)