遗传学详解及Matlab算法实现

遗传学算法概述

从之前转载的博客《非常好的理解遗传算法的例子》中可以知道,遗传学算法主要有6个步骤:

1. 个体编码

2. 初始群体

3. 适应度计算

4. 选择运算

5. 交叉运算

6. 变异运算

这是一个仿生的过程,模仿生物进化和自然选择。在该算法中,个体编码就相当于生物最基本的组成——基因,初始群体就是刚开始那些个原始的生物体。

在恶劣的环境中,适者生存的自然法则将让适应能力更好的生物继续存活繁衍下去,而适应能力差的生物将会被淘汰。因此遗传算法通过计算适应度来模拟这个自然选择的过程,用于筛选优良的个体保留下来。光这么讲也许太抽象,可能读者也无法知道具体如何实现的,也不知道这个适应度是如何去评价去个体的优劣程度的。下面举例个简单的例子来说明,如下图:假如现在要求一条曲线方程的最大值,并且已知有若干个点。那么曲线的值也就是求最大值过程中的适应度,因为值越大就越接近最大值,其适应度就越好。因此在这个求最大值的过程中,曲线对应的值可以直接作为适应度。在下一步选择运算过程中,这些适应能力好的个体也有更高的概率被保留下来。

《遗传学详解及Matlab算法实现》

选择运算就是自然选择的过程。在适应度计算过程中,各个个体的适应能力已经知道,因此部分适应能力差的个体就将会被淘汰。就想自然选择一样,大自然不会对生物体适应能力进行排序,然后一刀切全部淘汰弱者。在选择运算中,算法也是根据适应度来确定一个选择概率,按照概率来确定哪些个体允许保留下来。自然地,适应度高的,被保留下来的概率也更高,而适应度低的保留下来的可能性就越低。

进行选择之后,保留下来的个体就像生物一样,需要在自己的生命周期内繁衍后代来延续自己的物种。然后,就是脸红心跳的配对阶段了,两个个体之间需要贡献各自的染色体进行交换。这一过程中新的个体经过交叉运算获得由父母双方部分基因的组合,新的个体中基因来自于父母双方。当然了,算法中交差运算没有男女之分,交叉运算后所有的个体都是交叉运算产生的新个体,原本的个体被新个体代替不进入下一次迭代。如《非常好的理解遗传算法的例子》中过程5的描述:

《遗传学详解及Matlab算法实现》

交叉的时候进行交换基因的位置也是随机产生的,交换个体同样按照各自适应度以一定概率进行选择。因此适应度高的个体进行交差运算的概率也更高,这个在动物界比较普遍,即强壮的首领拥有交配权,以便优良基因更多机会保留下去。并且交叉本身也有一定发生概率,如果不进行交叉,那么交叉的结果就是新个体与原个体相同。这个理解起来也方便,就是说,要是进行交叉了,新个体为交叉结果;那要是不孕育新个体呢,那抱歉,你们两个个体还要经过下一次迭代。

最后,就是生物进化的过程了,这是一个小概率事件,但却是造就如今这样美丽而伟大的生命世界的关键。变异是随机的,因为随机,因此绝大部分变异都是恶性的,但也正是这种不确定性才导致了生物的多样性,让我们的世界变得美妙多彩。因为有自然选择的过程,变异后恶化的基因会被抛弃,因此,整个生物界依靠这几个关键步骤保持着不断进化,不断适应的趋势。但是,同样地,我们可以预料到,在变异运算这一步中,变异出现的概率会非常小,否则整个算法也就随机了。说了那么多,归结起来,变异在算法中的体现说白了就是按照一个小概率去挑选一个个体,然后将其某个编码后的码字取个反,当然,这是在二进制编码的情况下来说啦。

本身遗传学算法也是一门很复杂的学问,研究到现在,求解过程越来越像一个生命体的进化过程,但是基本思想还是如上述所说。同样地,下面我也将上述最基础的遗传学算法用matlab进行实现,各位读者也可以参照着代码来理解算法。

Matlab算法实现

下面演示,利用遗传学算法来求解最佳分割阈值。这个方法参考论文《Infrared Image Segmentation Based on Otsu and Genetic Algorithm》,该论文基于大津法和遗传算法来分割红外图像。不说算法有多好,其实也差不多,还不一定有Otsu来得直接(抱歉,我喜欢喷一喷(*^__^*) ),但是通过这个案例能够有效地描述遗传算法的使用。这里要感谢一下我师兄将该文献翻译的成中文,让我看起来方便多了。

大津法大家应该都听说过,目的是求最优的前景和背景的分界面,在图像分割算法中,大津法也是最有名的算法,个人觉得,没有之一。这里实际是使用遗传算法来求这个最优的分界面,就如上述我举例说求曲线的最大值一样,只是这里面最适应度的计算不是那么直接。下面从总体到各部分细节一一介绍。

(一)分割算法

这里将文中的算法命名为OtsuWithGenetic,算法不需要人为添加任何参数来辅助运算。从算法的名称来看,就可以知道,算法结合了大津法和遗传算法两者的特征。因此,在进行遗传算法的迭代流程之前,需要计算图像的直方图。而进行遗传学迭代运算的过程,在本案例中,我将其封装成一个名为BestThresh的函数,它的输入同样仅需要图像的直方图就可以了。在求取最佳阈值之后,就对图像进行二值化,得到最终结果图。

所以分割算法整体分为三个步骤:

1. 计算图像直方图

2. 利用遗传学算法求最佳阈值

3. 图像分割,进行二值化

function ImOut = OtsuWithGenetic(ImOriginal)
% 大津分割算法,结合遗传算法
% 在性能方面暂未进行相应优化,只对求解方程进行了优化
% 部分代码结构比较凌乱

ImGray = double(rgb2gray(ImOriginal));  %转换为双精度灰度图

[m,n] = size(ImGray);       %计算图片的尺寸
ImHist = zeros(1,256);      %灰度直方图
ImOut = zeros(m,n);         %分割结果图像

% 统计各灰度级的像素点个数,计算灰度直方图
for i=1:m
    for j=1:n
        ImHist(ImGray(i,j)+1) = ImHist(ImGray(i,j)+1)+1;
    end
end

th = BestThresh(ImHist);

% 分割图像,高于阈值为前景255,低于阈值为背景0
for i = 1:m
    for j = 1:n
        if (ImGray(i,j)+1>th)
            ImOut(i,j) = 255;
        else
            ImOut(i,j) = 0;
        end
    end
end

ImOut=uint8(ImOut);     %读入读出变换
end

(二)遗传算法求解大津最佳阈值

这一步实际上就是BestThresh函数的描述。从整个函数的代码中我们可以很清楚地看到遗传学算法描述中的6个步骤。

function bestindividual = BestThresh(ImHist)
popsize=40;     %群体大小
chromlength=8;  %字符串长度(个体长度)
pc=0.9;         %交叉概率
pm=0.001;       %变异概率

pop = InitEntity(popsize,chromlength); %随机产生初始群体
for i = 1:50        %50为迭代次数
    [objvalue] = FitnessValue(pop, ImHist);     %计算目标函数
    fitvalue = CalFitValue(objvalue);           %计算群体中每个个体的适应度
    [newpop] = Selection(pop,fitvalue);   %选择
    [newpop] = CrossOver(newpop,pc);      %交叉
    [newpop] = Mutation(newpop,pm);       %变异
    pop = newpop;
end
[bestindividual,bestfit] = BestEntity(pop,fitvalue); %求出群体中适应值最大的个体及其适应值
bestindividual = DecodeChrom(bestindividual,1,chromlength);
end

这里在一开始定义了四个参数:popsize, chromlength, pc, pm。具体含义见注释,这里重点介绍pc和pm两个参数。pc是交叉概率,pm是变异概率,在上述描述中我们说到,交叉和变异都是按照一定概率进行的,从这里也可以看到,两者的概率之间的差别。这里我默认将两者的概率定为了0.9和0.001,同样,迭代次数和群体的大小也定死了,实际上,运算次数再多,群体再大,也是浪费运算资源。

从代码中看到,在初始化完个体后,就按顺序进入迭代运算,迭代经过适应度的计算、选择运算、交叉运算和变异运算。其中计算目标函数和个体适应度计算实际是一个内容,CalFitValue实质是进行了矫正防止出现负数,而FitnessValue是适应度计算函数。

迭代结束以后,将适应度最大的作为最终的阈值,并且经过解码之后转换为实际的灰度值。

1. 初始化群体+个体编码

在本案例中,需要计算最佳的阈值,也就是计算作为阈值的那个最优的灰度值。对灰度值进行二进制编码获得随机的40个群体代码如下:

% 1 初始化群体(编码)
% popsize表示群体的大小,chromlength表示染色体的长度(二值数的长度)
function entities = InitEntity(entitySize,chromLength)
% rand随机产生随机数矩阵,round将其二值化
entities = round(rand(entitySize, chromLength));
end

该函数生成一个40行的矩阵,代码40个不同的初始个体;每个个体进行8位二进制编码,因此像素灰度本身也仅有8位深度。

2. 计算适应度

适应度的计算参考论文当中适应度函数

《遗传学详解及Matlab算法实现》

这里这里,《遗传学详解及Matlab算法实现》是适应度函数,《遗传学详解及Matlab算法实现》是目标图像像素数目,《遗传学详解及Matlab算法实现》是背景图像像素数目;《遗传学详解及Matlab算法实现》是目标图像像素总和,《遗传学详解及Matlab算法实现》是背景图像像素总和。

% 2 计算适应函数
% 实现目标函数的计算
function [objvalue] = FitnessValue(pop, Imhist)
graysum = Imhist.*(0:1:255);    % 各灰度值像素总和
x = DecodeChrom(pop,1,8);% 将pop每行转化成十进制数
loop = size(pop,1);
objvalue = zeros(loop,1);
for i = 1 : loop
    P1 = sum(graysum(x(i)+1 : 255));
    P2 = sum(graysum(1 : uint8(x(i))));
    I = sum(Imhist(x(i)+1 : 255));
    J = sum(Imhist(1 : uint8(x(i))));
    objvalue(i) = I * J * ((P1-P2)^2) * 2 ;%计算目标函数值
end
end
function fitValue = CalFitValue(objValue)
Cmin = 0;
num = size(objValue,1);
fitValue = zeros(1,num);
for i = 1 : num
    if objValue(i) + Cmin > 0
        temp = Cmin + objValue(i);
    else
        temp = 0.0;
    end
    fitValue(i) = temp;
end
end

3. 选择运算

通过计算得到的适应度就可以知道各个体的适应能力,将这些适应度进行累加,各个体就对应于一个区间。将累加结果进行归一化,然后用随机函数来进行选择,落入哪个区间就选择区间对应的个体。

% 3 选择复制
function [selectedEntities] = Selection(entities,fitValue)
totalFit = sum(fitValue);       %求适应值之和
fitValue = fitValue / totalFit; %单个个体被选择的概率
fitValue = cumsum(fitValue);    %如 fitValue=[1 2 3 4],则 cumsum(fitValue)=[1 3 6 10]
[num,cols] = size(entities);
ms = rand(num,1);      %产生用于选择的随机数

selectedEntities = zeros(num,cols); %初始化选择后的个体大小
for i = 1 : num
    for j = 1 : num
        if ms(i) < fitValue(j)
            selectedEntities(i,:) = entities(j,:);
            break;
        end
    end
end
end

4. 交叉运算

交叉运算利用一对个体产生新个体,函数如下:

% 4 交叉
% 群体中两个个体进行一定的概率 pc 的交叉,交换基因起始位置cpoint随机
function newEntities = CrossOver(entities,pc)
[num, cols]= size(entities);
newEntities = ones(size(entities));
for i = 1 : 2 : num-1
    if(rand < pc)
        cpoint = round(rand*cols);    % 随机产生交换起始位置
        newEntities(i,:) = [entities(i,1:cpoint), entities(i+1,cpoint+1:cols)];
        newEntities(i+1,:) = [entities(i+1,1:cpoint), entities(i,cpoint+1:cols)];
    else
        newEntities(i,:) = entities(i,:);
        newEntities(i+1,:) = entities(i+1,:);
    end
end
end

5. 变异运算

在经过交叉运算以后,还要进行编译运算,由于是二进制编码,变异运算就可以直接将变异位置的二进制数取反。

% 5 变异
function [newEntities] = Mutation(entities,pm)
[num, cols] = size(entities);
newEntities = ones(size(entities));
for i = 1 : num
    if(rand < pm)
        mpoint = round(rand*cols);    % 计算变异位置
        if mpoint <= 0
            mpoint = 1;
        end
        newEntities(i) = entities(i);
        if any(newEntities(i,mpoint)) == 0    % 进行二进制取反
            newEntities(i,mpoint) = 1;
        else
            newEntities(i,mpoint) = 0;
        end
    else
        newEntities(i,:) = entities(i,:);
    end
end
end

6. 计算最优值

在迭代结束后,所有的个体基本上呈现向最优值靠拢的趋势,这时候需要通过比较来取其中的最大值,作为最优解。这一步是一个比较过程,原理很简单:

% 6 求群体中对应的最大适应值
function [bestEntity,bestFit] = BestEntity(entities, fitValue)
num = size(entities, 1);
bestEntity = entities(1,:);
bestFit = fitValue(1);
for i = 2 : num
    if fitValue(i) > bestFit
        bestEntity = entities(i,:);
        bestFit = fitValue(i);
    end
end
end

(三)二进制转换函数

在运算中,由于使用数组来表示二进制编码数据,因此在运算过程中经常需要进行二进制与十进制之间的转换,转换函数如下:

% 将二进制编码转换成十进制
function decimals = DecodeChrom(entities,spoint,length)
temp = entities(:, spoint:spoint+length-1);
decimals = DecodeBinary(temp);
end

% 将二进制转化为十进制
function decimals = DecodeBinary(pop)
[num,cols] = size(pop);      
temp = zeros(num,cols);
for i = 1 : cols
    temp(:,i) = 2.^(cols-i) .* pop(:,i);
end
decimals = sum(temp,2);    
end
    原文作者:梦游太空2000
    原文地址: https://www.cnblogs.com/sleepwalker/p/3751343.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞