Python 运筹优化9 再次拆解基本的遗传算法

2021/12/19 1:19:36

本文主要是介绍Python 运筹优化9 再次拆解基本的遗传算法,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

说明

爹有娘有,不如自个有

成熟的包稍微参考一下,或者在某些场景下对付一下即可。核心的部件还是要自己研发。本篇从0.1开始(因为以前已经写过一篇),构造一个新的可迭代版本。

目标:

  • 1 完成连通性测试。
  • 2 可以使用GPU运算。
  • 3 厘清算法的要素和要点。
  • 4 使用PM规范搭建(既能测试脚手架方法的便利程度,又能使得算法过程足以服务化)

内容

先对遗传算法进行一些梳理(和增强)

遗传算法的灵感来自与遗传学与进化论。首先,会出现一批种群(道生一,谁知道为什么有初始种群?),然后种群开始变化与繁殖(一生二),优胜的个体将获得奖励(更多的资源)输掉的个体将只剩下越来越少的资源,缩减规模,甚至灭绝。

简单来说,遗传算法应该包含以下步骤:

  • 1 初始化种群
  • 2 适应性评估
  • 3 模板(约束)淘汰
  • 4 自然选择
  • 5 交叉繁衍
  • 6 变异
  • 7 对以上过程迭代(一定的次数或者某个指标)

1 逐步分析

之前的文章,已经对基础的遗传算法做了一次介绍,当时这篇文章是参考一个国外的文章,作为入门挺不错的。

接下来我将会打乱原来的函数,按逻辑从头到尾过一遍

1.1 数据和场景

实验使用的是sklearn自带的一组数据,很容易获得

import numpy as np
import pandas as pd
import random
import matplotlib.pyplot
%matplotlib inline

from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

#import the breast cancer dataset 
from sklearn.datasets import load_breast_cancer
cancer=load_breast_cancer()
df = pd.DataFrame(cancer['data'],columns=cancer['feature_names'])
label=cancer["target"]

In [2]: df.shape
Out[2]: (569, 30)

In [5]: label
Out[5]:
array([0, 0, 0, 0, 0, 0, 0,...]

X数据是一个规整的数值型表格,可以视为数字矩阵,共569行,30列
y数据是一个一维数组,长度和X一致。

1.2 二分类任务

假设数据已经处理干净(通常二分类逻辑回归会使用woe变换,这里不管了)

# 训练
import time
#splitting the model into training and testing set
X_train, X_test, y_train, y_test = train_test_split(df, 
                                                    label, test_size=0.30, 
                                                    random_state=101)
start = time.time()
#training a logistics regression model
logmodel = LogisticRegression(max_iter=10000)
logmodel.fit(X_train,y_train)
predictions = logmodel.predict(X_test)
print("Accuracy = "+ str(accuracy_score(y_test,predictions)))
print('Takes %.2f' %(time.time()-start))

---
Accuracy = 0.9415204678362573
Takes 0.41

建模有时候看起来很简单,我们稍微再细看一些,下面是模型的系数(30列)

In [7]: logmodel.coef_
Out[7]:
array([[ 0.97941119,  0.228494  , -0.44281457,  0.04240152, -0.13129615,
        -0.23586711, -0.39840754, -0.20550894, -0.27029016, -0.03286082,
        -0.04423837,  0.70893065,  0.45604112, -0.09348297, -0.01702533,
        -0.01492817, -0.0620379 , -0.03248521, -0.07882716, -0.0019002 ,
         0.00896633, -0.48931184, -0.09643741, -0.0219997 , -0.24958195,
        -0.49180984, -0.83010242, -0.39272431, -0.7384095 , -0.07693224]])

简单总结一下问题:使用了30个变量,太多。

类似逻辑回归这样的模型,事实上不应该超过10个变量。否则可以想象一下,即使你要向客户解释模型变量就能把嘴说干。而且太多的变量容易导致过拟合,不稳定,细节就不展开了,总之不对。所以有类似AIC、BIC这样的准则来对变量的个数进行惩罚。

变量选择事实上是逻辑回归建模一个非常难的问题

实践上,基于什么前向、后向、逐步(Stepwise)之类的方法其实都不是特别有效。逻辑回归是一个可以、也必须要进行解释的模型。假设构成模型空间的变量事实上可以分为几组/几类,我们希望模式能够从每个类抽出一到两个变量来构成模型的解释变量。

所以逻辑回归建模过程的关键点通常是:

  • 1 选择不多的变量,且可以合理解释
  • 2 ks尽量高
  • 3 不要有系数冲突

1.3 遗传算法实现

先来将一些概念梳理一下,然后将这个业务问题转换为遗传算法要解决的等价问题。

几个生物学上的概念(不一定准确):

  • 1 碱基对:可以视为某个变量的取值分布
  • 2 基因:若干个碱基对构成的有遗传意义的片段。例如相邻的几个变量作为一组表达某个维度的信息。
  • 3 DNA: 完整的遗传信息。
  • 4 染色体:DNA + 蛋白质

染色体决定了了个体的内在特性,个体对于环境的适应性可以视为基因在环境下的表达(类似条件概率)。

之前在使用geatpy的时候发现,在进行初始化/迭代的过程中,基因位之间的变化都是独立的,这不是很科学。严格来说,一个基因位的变异会使得其他基因位的变化收到限制(条件概率);在现实中,基因位可能会按某种规则进行随机(链式),而不是独立随机。

毕竟我们不是搞生物的,只是借用这个概念而已。所以规范一下说法:

  • 1 个体 = 染色体 chromesome
  • 2 染色体由若干基因(gene)构成
  • 3 每个基因有若干基因位,每个基因位可以视为一个变量

染色体是通过初始化、繁殖(交换基因位)、变异(随机突变)生成, 生成/删除的规则受到:

  • 1 基因位(单变量)合法值限制。
  • 2 基因位间的钳制规则。这个可能是链式的。
  • 3 适应性淘汰。可以80%按照适应度保留,20%按照随机保留。(避免有些个体过早被淘汰)

就目前这个实验而言,可以这么做等价转换

  • 1 每个变量都是基因位,一共30个。
  • 2 30个基因位构成一个基因
  • 3 这一个基因构成了一个染色体
  • 4 染色体即为我们要找的变量组

1.4 撸代码

首先有这么几个参数

# 种群大小
size=200
# 基因位(变量)
n_feat=30
# 每次迭代的生存者
n_parents=100
# 产生后代的变异几率
mutation_rate=0.10

函数1:生成初始种群中的个体

def process_get_a_chrome():
    chromosome = np.ones(n_feat,dtype=np.bool)
    chromosome[:int(0.3*n_feat)]=False
    np.random.shuffle(chromosome)
    return chromosome

# size设定了种群大小 = n个个体 = n个染色体 
population = [process_get_a_chrome() for x in range(size)]

函数2:评估一个种群中个体(变量选择)的适应性(准确率)

# 判定每个个体的“适应性”(训练后在测试集上的表现,或者说测试准确率)
def process_score_a_chrome(chromosome, X_train=X_train, X_test=X_test, y_train=y_train, y_test= y_test,logmodel = LogisticRegression(max_iter=10000)):
    logmodel.fit(X_train.iloc[:,chromosome],y_train)
    predictions = logmodel.predict(X_test.iloc[:,chromosome])
    return accuracy_score(y_test,predictions)

# 获取每个个体的适应性评分
time1 = time.time()
scores = [process_score_a_chrome(x) for x in  population]
time2 = time.time()

这步比较花时间,因为每个个体都要评估一遍,每次评估都要拟合一次模型参数(种群规模为200时需要约40秒左右)。加速的方法是将评估过程使用矩阵计算(最好是用GPU)。
如果不能用矩阵计算,也可以使用PM方法,把评估作为任务送到流程里,然后通过多台机器联合计算。应该提升1~2个数量级没有问题。

完整的函数如下:

def iter_many_gen(gens ,population = None,size=200,n_feat=30,n_parents=100,mutation_rate=0.10):
    if population is None:
        population = [process_get_a_chrome() for x in range(size)]
    
    for i in range(gens):
        print('>>>> begin iteration %s:' % i)
        # 获取每个个体的适应性评分
        time1 = time.time()
        scores = [process_score_a_chrome(x) for x in  population]
        time2 = time.time()

        time_spent1 = time2 - time1
        print('Evaluation Fitness Spent:%.2f, Max Score: %.6f' %(time_spent1, max(scores)))

        # 对打分排名
        inds = np.argsort(scores)

        # 当代保留的优秀个体
        cur_win_inds = inds < n_parents
        cur_wins = np.array(population)[cur_win_inds]


        # 均匀分为两组
        np.random.shuffle(cur_wins)

        pair_num = int(n_parents/2)
        groupa = copy.deepcopy(cur_wins[:pair_num])
        groupb = copy.deepcopy(cur_wins[pair_num:])
        # 交换基因位的方式
        # 使用两个随机变量进行交换

        # 1 横向的基因位
        gene_vec = np.random.rand(n_feat)

        # 2 纵向的随机控制器
        cross_control_vec =  np.random.rand(pair_num)
        # 升维以便广播
        cross_control_mat =  cross_control_vec.reshape(pair_num,-1)

        cross_sel_mat = gene_vec <cross_control_mat

        # 将两步分别进行 and 再进行 or进行交换
        child = (groupa&cross_sel_mat) | (groupb & ~cross_sel_mat)


        # 变异 10%
        child1 = copy.deepcopy(child)
        mutation_sel = np.random.rand(*child1.shape) <mutation_rate
        child1[mutation_sel] = ~child1[mutation_sel]


        # 下一代的个体
        nex_gen = np.r_[cur_wins, child, child1]

        time3 = time.time()
        time_spent2 = time3 - time2
        print('Make New Generation Takes:%.2f, Num: %s' %(time_spent2, len(nex_gen)))
        population = nex_gen
    return population

一开始我用种群规模为200跑,结果还是挺有意思的。有几轮迭代没什么效果0.94xxx,然后升高到0.95xxx,维持了几代,然后又掉下来(估计是因为变异),然后我用种群规模为2000跑,慢是慢了点,但是我猜会比较稳定

In [91]: evole_gen = iter_many_gen(20,size=2000,n_parents=1000)
>>>> begin iteration 0:
Evaluation Fitness Spent:396.57, Max Score: 0.953216
Make New Generation Takes:0.00, Num: 2000
>>>> begin iteration 1:
Evaluation Fitness Spent:378.01, Max Score: 0.953216
Make New Generation Takes:0.00, Num: 2000

...


这篇关于Python 运筹优化9 再次拆解基本的遗传算法的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程