当前位置:   article > 正文

中国大学MOOC胡浩基的机器学习第二章(支持向量机)兵王问题python版本——在python上初步使用libsvm_胡浩基机器学习笔记第二章

胡浩基机器学习笔记第二章

      在中国大学MOOC上看了胡浩基的机器学习课程,完全面向入门人群感觉挺好。其中有关原理的部分讲的很细。(虽然有几个细节我还是没懂.......)其中在第二章的例题兵王问题中课程只给了MATLAB的版本。但是,我相信也有极小部分人和我一样对MATLAB不太熟悉的。所以,我自己就按照我对老师的MATLAB的代码的理解写了python版本。先放源码大多数的内容我都会写在注释(中文)上,还有几个我认为大家可能会有疑惑的地方会在之后列出。

  1. # -*- coding: utf-8 -*-
  2. import numpy as np
  3. from libsvm.python.svm import *
  4. from libsvm.python.svmutil import *
  5. def data_read_mat(file_name):
  6. '''
  7. 从文件中取出数据
  8. :param file_name: 文件名称
  9. :return: 返回一个n*7的矩阵,前6项是三个坐标,第七项是标签
  10. '''
  11. num_list = []
  12. '''
  13. 一下是对数据进行读入并且处理,其中open的参数中encoding之所以设置成UTF-8-sig
  14. 是因为如果我们把这个参数设置为UTF-8或者不设置,在读入的开头多出\ufeff这么一串
  15. 东西,有时候会以中文字的形式出现。
  16. '''
  17. with open(file_name,"r",encoding='UTF-8-sig') as file:
  18. for l in file:
  19. l = l.split(',')
  20. list_k = []
  21. for j in range(3):
  22. list_k.append(ord(l[j*2]) - ord('a'))
  23. list_k.append(ord(l[j*2 + 1]) - ord('0'))
  24. if(l[6][0] == 'd'):
  25. list_k.append(-1)
  26. else:
  27. list_k.append(1)
  28. num_list.append(list_k)
  29. num_mat = np.array(num_list,dtype="float")
  30. '''
  31. 在此处是以numpy的二维数据矩阵的形式存储的,本以为使用numpy的数据进行运算可以使得
  32. 训练的速度快一些。结果发现如果要往libsvm中的函数传入参数只能传入list型不能传入numpy
  33. 的数据类型。所以,后面又把数据类型转回了list型。但是,我猜应该是有方法可以把numpy
  34. 的数据类型传入使用的。于是我在读取数据后任然返回的是numpy的形式。
  35. '''
  36. return num_mat
  37. def data_deal(mat,len_train,len1,len_test,len2):
  38. '''
  39. 将数据进行处理,分出训练数据和测试数据
  40. :param mat: 大矩阵,其中包括训练数据和测试数据
  41. :param len_train:训练数据
  42. :param len1: 输入坐标
  43. :param len_test: 测试数据
  44. :param len2: 标签
  45. :return: 返回的依次是训练输入数据,测试输入数据,训练输入数据的标签,测试输入数据的标签
  46. '''
  47. np.random.shuffle(mat) #先将矩阵按行打乱。然后根据要求对矩阵进行分割,第一部分就是训练集,第二部分就是测试集
  48. x_part1 = mat[0:len_train,0:len1]
  49. x_part2 = mat[len_train:,0:len1]
  50. y_part1 = mat[0:len_train,len1]
  51. y_part2 = mat[len_train:,len1]
  52. # 标准化
  53. # 根据训练集求出均值和方差
  54. avgX = np.mean(x_part1)
  55. stdX = np.std(x_part1)
  56. # print(avgX,stdX)
  57. #将训练集和测试集都进行归一化处理
  58. for data in x_part1:
  59. for j in range(len(data)):
  60. data[j] = (data[j] - avgX) / stdX
  61. for data in x_part2:
  62. for j in range(len(data)):
  63. data[j] = (data[j] - avgX) / stdX
  64. return x_part1,y_part1,x_part2,y_part2
  65. def TrainModel(CScale,gammaScale,prob):
  66. '''
  67. :param CScale: 参数C的取值序列
  68. :param gammaScale: 参数γ的取值序列
  69. :param prob: 训练集合对应的标签
  70. :return: maxACC(最高正确率),maxACC_C(最优参数C),maxACC_gamma(最优参数γ)
  71. '''
  72. maxACC = 0
  73. maxACC_C = 0
  74. maxACC_gamma = 0
  75. for C in CScale:
  76. C_ = pow(2, C)
  77. for gamma in gammaScale:
  78. gamma_ = pow(2, gamma)
  79. # 设置训练的参数
  80. # 其中-v 5表示的是2折交叉验证
  81. # “-q”可以去掉这样也就可以看到训练过程
  82. param = svm_parameter('-t 2 -c ' + str(C_) + ' -g ' + str(gamma_) + ' -v 5 -q')
  83. ACC = svm_train(prob, param) # 进行训练,但是传回的不是训练模型而是5折交叉验证的准确率
  84. #更新数据
  85. if (ACC > maxACC):
  86. maxACC = ACC
  87. maxACC_C = C
  88. maxACC_gamma = gamma
  89. return maxACC,maxACC_C,maxACC_gamma
  90. def getNewList(L,U,step):
  91. l = []
  92. while(L < U):
  93. l.append(L)
  94. L += step
  95. return l
  96. def TrainModelSVM(data,label,iter,model_file):
  97. '''
  98. 模型训练并保存
  99. :param data: 数据
  100. :param label: 标签
  101. :param iter:训练次数,在原先的MATLAB代码中的次数是两次
  102. :param model_file:模型的保存位置
  103. :return: 返回最优参数
  104. '''
  105. #将数据转换成list型的数据。因为,在svm的函数中好像只能传入list型的数据进行训练使用
  106. X = data.tolist()
  107. Y = label.tolist()
  108. CScale = [-5, -3, -1, 1, 3, 5,7,9,11,13,15] #参数C的2^C
  109. gammaScale = [-15,-13,-11,-9,-7,-5,-3,-1,1,3] #参数γ的取值2^γ
  110. cnt = iter
  111. step = 2 #用于重新生成CScale和gammaScale序列
  112. maxACC = 0 #训练过程中的最大正确率
  113. bestACC_C = 0 #训练过程中的最优参数C
  114. bestACC_gamma = 0 #训练过程中的最优参数γ
  115. prob = svm_problem(Y, X) # 传入数据
  116. while(cnt):
  117. #用传入的参数序列进行训练,返回的是此次训练的最高正确率,最优参数C,最优参数γ
  118. maxACC_train,maxACC_C_train,maxACC_gamma_train = TrainModel(CScale,gammaScale,prob)
  119. #数据更新
  120. if(maxACC_train > maxACC):
  121. maxACC = maxACC_train
  122. bestACC_C = maxACC_C_train
  123. bestACC_gamma = maxACC_gamma_train
  124. #根据返回的参数重新生成CScale序列和gammaScale序列用于再次训练,下一次训练的C参数和γ参数的精度会比之前更高
  125. #step就是CScale序列和gammaScale序列的相邻两个数之间的间隔
  126. new_step = step*2/10
  127. CScale = getNewList(maxACC_C_train - step,maxACC_C_train + step + new_step,new_step)
  128. gammaScale = getNewList(maxACC_gamma_train - step,maxACC_gamma_train + step + new_step,new_step)
  129. cnt -= 1
  130. #获得最优参数后计算出对应的C和γ,并且训练获得“最优模型”
  131. C = pow(2,bestACC_C)
  132. gamma = pow(2,bestACC_gamma)
  133. param = svm_parameter('-t 2 -c ' + str(C) + ' -g ' + str(gamma))
  134. model = svm_train(prob, param) # 交叉验证准确率
  135. svm_save_model(model_file, model) #保存模型
  136. return model
  137. def main():
  138. data_file = r"E:\python_study\KingSVM\krkopt.data" #数据存放的位置(需要修改)
  139. mode_file = r"E:\python_study\KingSVM\model_file" #训练模型保存的位置(需要修改)
  140. data_mat = data_read_mat(data_file) #从文件中读取数据并处理
  141. #以下是对数据训练进行分配,可以根据你的需要进行调整
  142. train = 5000 #5000组数据作为训练数据
  143. test = len(data_mat) - 5000 #剩下的数据作为测试数据
  144. #————————————————————————————————————————————————————————————#
  145. x_len = 6 #输入数据的维度是6维,即三个棋子的坐标
  146. y_len = len(data_mat[0]) - x_len #输出的数据时1维,即两种结果
  147. iter = 2# 训练的次数,训练的次数越多参数就调整的精度就越高
  148. x_train,y_train,x_test,y_test = data_deal(data_mat,train,x_len,test,y_len) #对数据进行分割
  149. if (input("是否需要进行训练?") == 'y'): #如果输入y就会进行训练,否则就可以直接使用之前训练的完成的模型
  150. model = TrainModelSVM(x_train,y_train,iter,mode_file) #传入输入数据,标签进行模型的训练
  151. else:
  152. model = svm_load_model(mode_file) #直接加载现有模型
  153. X = x_test.tolist() #将测试集的输入集转换成list
  154. Y = y_test.tolist() #将测试集的输出集转换成list
  155. print(Y[:10])
  156. p_labs,p_acc,p_vals = svm_predict(Y,X,model)
  157. if __name__ == "__main__":
  158. main()

        最后,我在尝试后预测正确率比课程视频中放出来的低0.2%。但是,总体来说应该是没有问题。接下来是列举出我认为大家可能会碰到的问题。

1.在python中怎么安装libsvm?

       我用的pycharm我猜用这种方式应该是可以安装的,具体细节百度一下。(虽然我没试过)

 而我使用的安装方式是参照这个博客:python libsvm安装_jessica218的博客-CSDN博客

2.大家在百度libsvm的使用时,可能会看到这个函数svm.SCV。我在刚开始的时候也碰到了不少文章中使用这个的。但是,用了这个函数死活不行。我猜可能在libsvm的老版本中使用的就是这个,对应现在这个版本中svm.svm_train。除此之外的还有svmutil.svm_read_problem在现在的版本中好像也没有了。

3.为什么libsvm中参数传入不支持numpy的一些数据类型?

         在svm.py文件中我们可以看到这样一段:

  1. class svm_problem(Structure):
  2. _names = ["l", "y", "x"]
  3. _types = [c_int, POINTER(c_double), POINTER(POINTER(svm_node))]
  4. _fields_ = genFields(_names, _types)
  5. def __init__(self, y, x, isKernel=False):
  6. if (not isinstance(y, (list, tuple))) and (not (scipy and isinstance(y, scipy.ndarray))):
  7. raise TypeError("type of y: {0} is not supported!".format(type(y)))
  8. if isinstance(x, (list, tuple)):
  9. if len(y) != len(x):
  10. raise ValueError("len(y) != len(x)")
  11. elif scipy != None and isinstance(x, (scipy.ndarray, sparse.spmatrix)):
  12. if len(y) != x.shape[0]:
  13. raise ValueError("len(y) != len(x)")
  14. if isinstance(x, scipy.ndarray):
  15. x = scipy.ascontiguousarray(x) # enforce row-major
  16. if isinstance(x, sparse.spmatrix):
  17. x = x.tocsr()
  18. pass
  19. else:
  20. raise TypeError("type of x: {0} is not supported!".format(type(x)))
  21. self.l = l = len(y)
  22. max_idx = 0
  23. x_space = self.x_space = []
  24. if scipy != None and isinstance(x, sparse.csr_matrix):
  25. csr_to_problem(x, self, isKernel)
  26. max_idx = x.shape[1]
  27. else:
  28. for i, xi in enumerate(x):
  29. tmp_xi, tmp_idx = gen_svm_nodearray(xi,isKernel=isKernel)
  30. x_space += [tmp_xi]
  31. max_idx = max(max_idx, tmp_idx)
  32. self.n = max_idx
  33. self.y = (c_double * l)()
  34. if scipy != None and isinstance(y, scipy.ndarray):
  35. scipy.ctypeslib.as_array(self.y, (self.l,))[:] = y
  36. else:
  37. for i, yi in enumerate(y): self.y[i] = yi
  38. self.x = (POINTER(svm_node) * l)()
  39. if scipy != None and isinstance(x, sparse.csr_matrix):
  40. base = addressof(self.x_space.ctypes.data_as(POINTER(svm_node))[0])
  41. x_ptr = cast(self.x, POINTER(c_uint64))
  42. x_ptr = scipy.ctypeslib.as_array(x_ptr,(self.l,))
  43. x_ptr[:] = self.rowptr[:-1]*sizeof(svm_node)+base
  44. else:
  45. for i, xi in enumerate(self.x_space): self.x[i] = xi

其中注意这部分:

其中isinstance(a,type)是判断a是否是type类型的,我们看上图的第一行和第三行可以看到传入的参数x,y必须是list或者typle类型的,否则就会异常退出。

4.标准化这个步骤很重要法,如果没有标准化正确率就会变成大约90%。(瞎猜的正确率也是90%)

——————————————————————END————————————————————————

回答评论区river_ocean111的提问,因为来看这篇的基本上都是初学者,所以我想别人应该也会有这个问题。

在看完MOOC胡浩基的课后应该能明白下面这几点:

1.gamma是高斯核中所需要设置的超参数(在这个例子中使用的就是高斯核函数)(第二章第11个视频5:02)

至于具体细节我也不知道这个高斯核是怎么推导出来的,这个还需要看一些相关的资料。(就连李航的《统计学方法》中都没有详细阐述这个高斯核的由来,可见这个东西要么很基础要么很古老。)

2.C是SVM算法中所需要设置的一个超参数(第二章第4个视频2:50)

而这个的话直接看视频应该就能很好的理解为什么会有C这个超参数。

3.C,gamma由于人工经验所以选取的范围。C = 2^(-5)~2^(15),gamma=2^(-15)~2^(3)(第二章第12个视频6:59)

明白了之后在回到coding这,你可以这样理解。我们的目的是需要找到一个最优的C和gamma。当然在这个问题中我们都当做C和gamma都大致符合下面这个图,就只是一座山的形状,而我们要找的是那个山尖。

(没有3D的绘图工具只能画个2D的来凑合一下)

而事实是大致符合下面这个图,像一个丘陵一样。我们想要找到的是最好全局的最高点,但是这是非常困难的所以我们只需要找到一个全局的较高点即可。

如果你到此为止都看懂了,那你应该也就能理解了其实也不一定非要用pow(2,C),pow(2,gamma),完全可以把111行和112行CScale,gammaScale之后的列表改成具体的数值。这样一来直接用C和gamma就行。(我没有试过,我估计单次训练的速度会变快。但是,每次训练的效果会变差。)

回答第二个问题:我试了一下什么也不用改。至于具体原因的话,还是琢磨一下SVM的源码吧,这个我也没有仔细研究过。从SVM的原理上来看的话,咋们也可以知道SVM就是单纯的解决2分类问题与分类标签为1,0还是标签为1,-1并没有关系。

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号