天天看點

Python進化算法工具箱的使用(三)用進化算法優化SVM參數前言正文

前言

自從上兩篇部落格詳細講解了Python遺傳和進化算法工具箱及其在帶限制的單目标函數值優化中的應用以及利用遺傳算法求解有向圖的最短路徑之後,我經過不斷學習工具箱的官方文檔以及對源碼的研究,更加掌握如何利用遺傳算法求解更多有趣的問題了。

與前面的文章不同,本篇采用差分進化算法來優化SVM中的參數C和Gamma。(用遺傳算法也可以,下面會給出效果比較)

首先簡單回顧一下Python高性能實用型遺傳和進化算法工具箱的用法。對于一個優化問題,需要做兩個步驟便可進行求解:Step1:自定義問題類;Step2:編寫執行腳本調用Geatpy進化算法模闆對問題進行求解。在上一篇部落格曾“詳細”介紹過具體的用法:https://blog.csdn.net/weixin_37790882/article/details/84034956,但完整的中文教程可以參考官方文檔。

下面切入主題:

首先簡單描述一下SVM的使用。本文采用Python的sklearn庫來跑SVM算法。sklearn中SVM的算法庫分為兩類,一類是分類的算法庫,包括:SVC、NuSVC和LinearSVC 3個類。另一類是回歸算法庫,包括:SVR、NuSVR和LinearSVR 3個類。相關的類都包裹在sklearn.svm子產品之中。

正文

本文重點講解利用SVC這個類來對iris資料集的資料進行分類。iris資料集的資料格式如下:

Python進化算法工具箱的使用(三)用進化算法優化SVM參數前言正文

前4列是特征資料,第5列是标簽資料。整個資料集一共有3種标簽:Iris-setosa、Iris-versicolor、Iris-virginica。

本文采用的是将iris資料集拆分成訓練集(iris_train.data)和測試集(iris_test.data)兩部分的資料,已經拆分好的資料檔案詳見https://github.com/geatpy-dev/geatpy/tree/master/geatpy/demo/soea_demo/soea_demo6

采用SVC對資料進行分類的一般步驟為:

Step1:讀取訓練集的特征資料,并儲存在一個Numpy array類型“矩陣”中,使得每一列代表一個特征,每一行代表一組資料,并對資料進行标準化處理。

Step2:讀取訓練集的标簽資料,儲存在一個Numpy array類型的行向量中。

Step3:尋找最優參數C和Gamma。

Step4:使用最優參數執行個體化SVC類的對象(即建立分類器對象),并調用它的成員函數fit()利用訓練集的資料來拟合分類器模型。

Step5:用與Step1和Step2同樣的方法讀取測試集的特征資料和标簽資料,并對特征資料進行标準化處理。

Step6:用Step4中訓練好的分類器對标準化後的特征資料進行預測,預測出每組特征資料對應的标簽。

Step7:把預測出的标簽資料和讀取到的測試集标簽資料進行對比,計算正确率。

在上面的步驟中,尋找最優參數C和Gamma是一個關鍵步驟,一般采用固定步長的網格搜尋政策以及交叉驗證來尋找最優參數。而本文采用差分進化算法來尋找該最優參數,把交叉驗證的平均得分作為待優化的目标,把C和Gamma參數作為決策變量。差分進化算法基本流程和一般的進化算法相差無幾,這裡就不贅述了,權威又易懂的參考文獻如下:

Price, K.V., Storn, R.N. and Lampinen, J.A.. Differential Evolution:  A Practical Approach to Global Optimization. : Springer, 2005.

代碼實作(摘自Geatpy網站中的單目标優化案例6)

1)首先把模型寫到自定義問題類中,代碼如下:

# -*- coding: utf-8 -*-
"""MyProblem.py"""
import numpy as np
import geatpy as ea
from sklearn import svm
from sklearn import preprocessing
from sklearn.model_selection import cross_val_score
from multiprocessing.dummy import Pool as ThreadPool
class MyProblem(ea.Problem): # 繼承Problem父類
    def __init__(self):
        name = 'MyProblem' # 初始化name(函數名稱,可以随意設定)
        M = 1 # 初始化M(目标維數)
        maxormins = [-1] # 初始化maxormins(目标最小最大化标記清單,1:最小化該目标;-1:最大化該目标)
        Dim = 2 # 初始化Dim(決策變量維數)
        varTypes = [0, 0] # 初始化varTypes(決策變量的類型,元素為0表示對應的變量是連續的;1表示是離散的)
        lb = [2**(-8)] * Dim # 決策變量下界
        ub = [2**8] * Dim # 決策變量上界
        lbin = [1] * Dim # 決策變量下邊界(0表示不包含該變量的下邊界,1表示包含)
        ubin = [1] * Dim # 決策變量上邊界(0表示不包含該變量的上邊界,1表示包含)
        # 調用父類構造方法完成執行個體化
        ea.Problem.__init__(self, name, M, maxormins, Dim, varTypes, lb, ub, lbin, ubin)
        # 目标函數計算中用到的一些資料
        fp = open('iris_train.data')
        datas = []
        data_targets = []
        for line in fp.readlines():
            line_data = line.strip('\n').split(',')
            data = []
            for i in line_data[0:4]:
                data.append(float(i))
            datas.append(data)
            data_targets.append(line_data[4])
        fp.close()
        self.data = preprocessing.scale(np.array(datas)) # 訓練集的特征資料(歸一化)
        self.dataTarget = np.array(data_targets)
    
    def aimFunc(self, pop): # 目标函數,采用多線程加速計算
        Vars = pop.Phen # 得到決策變量矩陣
        pop.ObjV = np.zeros((pop.sizes, 1)) # 初始化種群個體目标函數值列向量
        def subAimFunc(i):
            C = Vars[i, 0]
            G = Vars[i, 1]
            svc = svm.SVC(C=C, kernel='rbf', gamma=G).fit(self.data, self.dataTarget) # 建立分類器對象并用訓練集的資料拟合分類器模型
            scores = cross_val_score(svc, self.data, self.dataTarget, cv=10) # 計算交叉驗證的得分
            pop.ObjV[i] = scores.mean() # 把交叉驗證的平均得分作為目标函數值
        pool = ThreadPool(2) # 設定池的大小
        pool.map(subAimFunc, list(range(pop.sizes)))
    
    def test(self, C, G): # 代入優化後的C、Gamma對測試集進行檢驗
        # 讀取測試集資料
        fp = open('iris_test.data')
        datas = []
        data_targets = []
        for line in fp.readlines():
            line_data = line.strip('\n').split(',')
            data = []
            for i in line_data[0:4]:
                data.append(float(i))
            datas.append(data)
            data_targets.append(line_data[4])
        fp.close()
        data_test = preprocessing.scale(np.array(datas)) # 測試集的特征資料(歸一化)
        dataTarget_test = np.array(data_targets) # 測試集的标簽資料
        svc = svm.SVC(C=C, kernel='rbf', gamma=G).fit(self.data, self.dataTarget) # 建立分類器對象并用訓練集的資料拟合分類器模型
        dataTarget_predict = svc.predict(data_test) # 采用訓練好的分類器對象對測試集資料進行預測
        print("測試集資料分類正确率 = %s%%"%(len(np.where(dataTarget_predict == dataTarget_test)[0]) / len(dataTarget_test) * 100))
           

上面的代碼裡利用交叉驗證得到的平均得分作為待優化的目标函數值,因為分數越高表示參數越好,是以上面的maxormins設定為[-1],表示待優化的目标是個最大化的目标。設定C和Gamma的搜尋區間均為2的負8次方到2的8次方。

2)然後建立執行腳本,調用差分進化算法模闆soea_DE_rand_1_bin進行利用DE/rand/1/bin差分進化算法優化來優化上面定義好的待優化模型,代碼如下:

# -*- coding: utf-8 -*-
"""main.py"""
import geatpy as ea # import geatpy
from MyProblem import MyProblem # 導入自定義問題接口
if __name__ == '__main__':
    """===============================執行個體化問題對象==========================="""
    problem = MyProblem() # 生成問題對象
    """=================================種群設定==============================="""
    Encoding = 'RI'       # 編碼方式
    NIND = 20             # 種群規模
    Field = ea.crtfld(Encoding, problem.varTypes, problem.ranges, problem.borders) # 建立區域描述器
    population = ea.Population(Encoding, Field, NIND) # 執行個體化種群對象(此時種群還沒被初始化,僅僅是完成種群對象的執行個體化)
    """===============================算法參數設定============================="""
    myAlgorithm = ea.soea_DE_rand_1_bin_templet(problem, population) # 執行個體化一個算法模闆對象
    myAlgorithm.MAXGEN = 30 # 最大進化代數
    myAlgorithm.trappedValue = 1e-6 # “進化停滞”判斷門檻值
    myAlgorithm.maxTrappedCount = 10 # 進化停滞計數器最大上限值,如果連續maxTrappedCount代被判定進化陷入停滞,則終止進化
    myAlgorithm.logTras = 1  # 設定每隔多少代記錄日志,若設定成0則表示不記錄日志
    myAlgorithm.verbose = True  # 設定是否列印輸出日志資訊
    myAlgorithm.drawing = 1  # 設定繪圖方式(0:不繪圖;1:繪制結果圖;2:繪制目标空間過程動畫;3:繪制決策空間過程動畫)
    """===========================調用算法模闆進行種群進化======================="""
    [BestIndi, population] = myAlgorithm.run()  # 執行算法模闆,得到最優個體以及最後一代種群
    BestIndi.save()  # 把最優個體的資訊儲存到檔案中
    """==================================輸出結果============================="""
    print('用時:%f 秒' % myAlgorithm.passTime)
    print('評價次數:%d 次' % myAlgorithm.evalsNum)
    if BestIndi.sizes != 0:
        print('最優的目标函數值為:%s' % BestIndi.ObjV[0][0])
        print('最優的控制變量值為:')
        for i in range(BestIndi.Phen.shape[1]):
            print(BestIndi.Phen[0, i])
    else:
        print('沒找到可行解。')
    """=================================檢驗結果==============================="""
    problem.test(C = BestIndi.Phen[0][0], G = BestIndi.Phen[0][1])
           

運作結果如下:

Python進化算法工具箱的使用(三)用進化算法優化SVM參數前言正文

後記

上面的執行腳本中調用了DE/rand/1/bin的差分進化算法進行進化優化,實際上還可以調用其他的比如遺傳算法、遺傳政策等的算法模闆進行進化優化。例如調用最經典的遺傳算法:

# 把這一行替換上面main.py中的soea_DE_rand_1_bin_templet...那一行
myAlgorithm = ea.soea_SGA_templet(problem, population)
           

按照上面注釋中的描述修改main.py代碼後運作結果如下:

Python進化算法工具箱的使用(三)用進化算法優化SVM參數前言正文

可見采用SGA進行參數優化的效果稍微比不上采用DE/rand/1/bin差分進化算法,這意味着對于訓練集的資料而言,後者分類的準确率比前者低;但對于測試集而言,兩者分類的正确率一樣。

最後回顧一下上一篇部落格提到的”進化算法套路“(上篇寫的是“遺傳”,這篇拓展為“進化”):

Python進化算法工具箱的使用(三)用進化算法優化SVM參數前言正文

該套路實作了具體問題、使用的算法以及所調用的相關算子之間的脫耦。而Geatpy工具箱已經内置了衆多進化算法模闆類以及相關的算子,直接調用即可。對于實際問題的求解,隻需關心如何把問題寫在自定義問題類中就好了。

本文所用到的實驗代碼很好地展現了這個流程,整個過程裡面我沒有關心進化算法的具體實作,隻管怎麼把待優化的模型寫在自定義問題類MyProblem中。

更多詳細的教程可以詳見:http://geatpy.com/index.php/geatpy%E6%95%99%E7%A8%8B/

後續我将繼續學習和挖掘該工具箱的更多深入的用法。希望這篇文章在幫助自己記錄學習點滴之餘,也能幫助大家!