遗传编程——java语言实现
对于遗传编程的理论请参看《集体智慧编程》一书,书中对于遗传编程的原理有详细的阐述。
遗传编程的大体执行过程如下图所示:
我们使用树形表示法来描述图中遗传编程中的程序。
下面进入到我们这篇博客的重点了,遗传编程——java语言实现
一、由于是使用树形表示法来描述,所以我们首先需要构造一棵树
树节点的构造
由于有三种类型的节点,所以首先我们定义一个通用的节点接口
public interface Node extends Cloneable{
int evaluate(int[] inp);
void display(int indent);
Object clone();
}
这里继承Cloneable接口是为了后面节点的拷贝
之后构造函数节点、参数节点、常值节点
1、函数节点
public class FuncNode implements Node{
private Func function;
private String name;
private List<Node> children;
public FuncNode(FunctionWapper fw, List<Node> children) {
this.function = fw.getFunction();
this.name = fw.getName();
this.children = children;
}
public int evaluate(int[] inp) {
int n = this.children.size();
int[] results = new int[n];
for (int i = 0; i<n; i++) {
results[i] = this.children.get(i).evaluate(inp);
}
return function.cal(results);
}
@Override
public void display(int indent) {
String str = "";
for (int i=0; i<indent; i++) str += ' ';
System.out.println(str+this.name);
for (Node c: this.children) {
c.display(indent + 2);
}
}
public Func getFunction() {
return function;
}
public String getName() {
return name;
}
public List<Node> getChildren() {
return children;
}
public void setFunction(Func function) {
this.function = function;
}
public void setName(String name) {
this.name = name;
}
public void setChildren(List<Node> children) {
this.children = children;
}
@Override
public Object clone(){
FuncNode node = null;
try {
node = (FuncNode) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
node.setFunction((Func) this.getFunction().clone());
node.children = new ArrayList<>();
for (int i=0; i<this.getChildren().size(); i++) {
node.children.add(copyTree((this.getChildren().get(i))));
}
return node;
}
}
对树的深度拷贝,所以这里我们另外在定义一个对树深度拷贝的类
public class CopyTree {
public static Node copyTree(Node tree) {
if (tree instanceof FuncNode) {
FuncNode node = (FuncNode) tree.clone();
node.setFunction((Func) ((FuncNode) tree).getFunction().clone());
for (int i=0; i<((FuncNode) tree).getChildren().size(); i++)
node.getChildren().set(i, copyTree(((FuncNode) tree).getChildren().get(i))) ;
return node;
}else{
return (Node) tree.clone();
}
}
}
2、参数节点
public class ParamNode implements Node{
private int idx;
public ParamNode(int idx) {
this.idx = idx;
}
public int evaluate(int[] inp) {
return inp[this.idx];
}
@Override
public void display(int indent) {
String str = "";
for (int i=0; i<indent; i++) str += ' ';
System.out.println(String.format("%sp%d", str, this.idx));
}
public int getIdx() {
return idx;
}
public void setIdx(int idx) {
this.idx = idx;
}
@Override
public Object clone() {
Node result = null;
try {
result = (Node) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return result;
}
}
3、常值节点
public class ConstNode implements Node{
private int v;
public ConstNode(int v) {
this.v = v;
}
public int evaluate(int[] inp) {
return this.v;
}
@Override
public void display(int indent) {
String str = "";
for (int i=0; i<indent; i++) str += ' ';
System.out.println(String.format("%s%d", str, this.v));
}
public int getV() {
return v;
}
public void setV(int v) {
this.v = v;
}
@Override
public Object clone() {
Node result = null;
try {
result = (Node) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return result;
}
}
二、在FuncNode中含有Func这个属性,下面我们来定义这个属性,这个属性对应的就是节点上的操作
操作函数的构造
由于操作多种多样,所以首先我们定义一个通用的接口
public interface Func extends Cloneable {
int cal(int[] l);
Object clone();
}
这里继承Cloneable接口是为了后面节点的拷贝
之后定义具体的操作类
1、加法操作类
public class AddFunc implements Func , Cloneable{
@Override
public int cal(int[] l) {
return l[0] + l[1];
}
@Override
public Object clone() {
Func f = null;
try {
f = (Func) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return f;
}
}
2、减法操作类
public class SubFunc implements Func, Cloneable {
@Override
public int cal(int[] l) {
return l[0] - l[1];
}
@Override
public Object clone() {
Func f = null;
try {
f = (Func) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return f;
}
}
3、乘法操作类
public class MulFunc implements Func, Cloneable {
@Override
public int cal(int[] l) {
return l[0] * l[1];
}
@Override
public Object clone() {
Func f = null;
try {
f = (Func) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return f;
}
}
4、if判断操作类
public class IfFunc implements Func, Cloneable {
@Override
public int cal(int[] l) {
if (l[0] > 0) return l[1];
else return l[2];
}
@Override
public Object clone() {
Func f = null;
try {
f = (Func) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return f;
}
}
5、比较操作类
public class IsGreaterFunc implements Func, Cloneable {
@Override
public int cal(int[] l) {
if (l[0] > l[1]) return 1;
else return 0;
}
@Override
public Object clone() {
Func f = null;
try {
f = (Func) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return f;
}
}
三、定义一个包装类,对应于函数型节点上的函数
public class FunctionWapper {
private Func function;
private int childCount;
private String name;
public FunctionWapper(Func function, int childCount, String name) {
this.function = function;
this.childCount = childCount;
this.name = name;
}
public String getName() {
return name;
}
public Func getFunction() {
return function;
}
public int getChildCount() {
return childCount;
}
public void setName(String name) {
this.name = name;
}
public void setChildCount(int childCount) {
this.childCount = childCount;
}
public void setFunction(Func function) {
this.function = function;
}
}
为了后面操作的方便下面我们在定义一个ConstantWapper类用于保存一些常量
public class ConstantWapper {
public static final FunctionWapper ifw = new FunctionWapper(new IfFunc(), 3, "if");
public static final FunctionWapper gtw = new FunctionWapper(new IsGreaterFunc(), 2, "isgreater");
public static final FunctionWapper addw = new FunctionWapper(new AddFunc(), 2, "add");
public static final FunctionWapper subw = new FunctionWapper(new SubFunc(), 2, "substract");
public static final FunctionWapper mulw = new FunctionWapper(new MulFunc(), 2, "multiply");
public static final FunctionWapper[] flist = {addw, mulw, ifw, gtw, subw};
}
四、尽管为遗传编程手工构造程序是可行的,但是通常的初始种群是由一组随机程序构成的,所以下面就是构造我们的随机树了。
public class RandomTree {
public static Node makeRandomTree(int pc) {
return makeRandomTree(pc,4,0.5, 0.6);
}
public static Node makeRandomTree(int pc, int maxdepth) {
return makeRandomTree(pc,maxdepth,0.5, 0.6);
}
public static Node makeRandomTree(int pc, int maxdepth, double fpr) {
return makeRandomTree(pc,maxdepth,fpr, 0.6);
}
public static Node makeRandomTree(int pc, int maxdepth, double fpr, double ppr) {
if (Math.random() < fpr && maxdepth > 0) {
FunctionWapper f = ConstantWapper.flist[(int)(Math.random()*5)];
List<Node> children = new ArrayList<>();
for (int i=0; i<f.getChildCount(); i++) {
children.add(makeRandomTree(pc, maxdepth-1, fpr, ppr));
}
return new FuncNode(f, children);
} else if (Math.random() < ppr) {
return new ParamNode((int)(Math.random()*(pc-1)));
} else {
return new ConstNode((int)(Math.random()*10));
}
}
}
五、随机树构造好了,如何判断随机树的好坏就成了一个问题,所以接下来我们需要对随机树的好坏进行衡量。
由于衡量函数多种多样,所以这里我们定义一个通用的接口
public interface CostFunc {
long calCost(Node tree,List<Score> s);
}
下面是1范式衡量的具体实现类
public class OneNormCostFunc implements CostFunc {
@Override
public long calCost(Node tree, List<Score> s) {
long dif = 0;
long v = 0;
for (Score data: s) {
v = tree.evaluate(new int[]{data.getX(), data.getY()});
dif += Math.abs(v - data.getScore());
}
return dif;
}
}
六、下面就是遗传编程的重点了,我们采用遗传算法对随机树进行变异和交叉。
1、变异
public class Mutate {
public static Node mutate(Node t, int pc) {
return mutate(t, pc, 0.1);
}
public static Node mutate(Node t, int pc, double probchange) {
FuncNode result = null;
if (Math.random() < probchange) {
return RandomTree.makeRandomTree(pc);
} else {
if (!(t instanceof FuncNode)) {
return t;
}
result = (FuncNode) t.clone();
FuncNode temp = (FuncNode) t;
for (int i=0; i<temp.getChildren().size(); i++) {
result.getChildren().set(i, mutate(temp.getChildren().get(i), pc, probchange));
}
}
return result;
}
}
2、交叉
public class Crossover {
public static Node crossover(Node t1, Node t2) {
return crossover(t1, t2, 0.7);
}
public static Node crossover(Node t1, Node t2, double probswap) {
return crossover(t1, t2, probswap, true);
}
public static Node crossover(Node t1, Node t2, double probswap, boolean top) {
FuncNode result = null;
if (Math.random() < probswap && !top) {
return (Node) t2.clone();
} else {
if (!(t1 instanceof FuncNode && t2 instanceof FuncNode)) {
return t1;
}
result = (FuncNode) t1.clone();
FuncNode temp = (FuncNode) t1;
FuncNode temp2 = (FuncNode) t2;
for (int i=0; i<temp.getChildren().size(); i++) {
result.getChildren().set(i, crossover(temp.getChildren().get(i), temp2.getChildren().get((int)(Math.random()*temp2.getChildren().size())),probswap, false));
}
}
return result;
}
}
七、有了度量随机树优劣的方法和修改随机树的两种方法,我们接下来可以开始构筑程序进化用的竞争环境了。
首先是需要将一组程序按从优到劣的顺序进行排序,由于排序的方式多种多样,下面我们首先定义一个通用的接口
public interface RankFunction {
List<RankScore> getRankFunction(List<Node> population);
}
这里Score保存x,y以及对应的分数
public class Score {
private int x;
private int y;
private int score;
public Score() {}
public Score(int x, int y, int score) {
this.x = x;
this.y = y;
this.score = score;
}
public int getScore() {
return score;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public void setScore(int score) {
this.score = score;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
}
这里RankScore保存随机树以及对应的分数
public class RankScore{
private long score;
private Node t;
public long getScore() {
return score;
}
public void setScore(long score) {
this.score = score;
}
public Node getT() {
return t;
}
public void setT(Node t) {
this.t = t;
}
}
对应格子战争游戏的排序类如下:
public class RankFunc implements RankFunction{
public static List<Score> buildhiddenset() {
List<Score> rows = new ArrayList<>();
int x,y;
for (int i=0; i<200; i++) {
Score score = new Score();
x = i+3;
y = 3*i-1;
score.setX(x);
score.setY(y);
score.setScore(x*y);
rows.add(score);
}
return rows;
}
public List<RankScore> getRankFunction(List<Node> population) {
List<RankScore> scores = new ArrayList<>();
for (int i=0; i<population.size(); i++) {
RankScore rankScore = new RankScore();
rankScore.setScore(new OneNormCostFunc().calCost(population.get(i), buildhiddenset()));
rankScore.setT(population.get(i));
scores.add(rankScore);
}
Collections.sort(scores, new Comparator<RankScore>() {
@Override
public int compare(RankScore o1, RankScore o2) {
if (o1.getScore() > o2.getScore()) return 1;
else if (o1.getScore() == o2.getScore()) return 0;
else return -1;
}
});
return scores;
}
}
然后就是构造竞争环境了
public class Environment {
public static List<RankScore> evolve(List<Node> p,int pc, int popsize, RankFunction rankFunc) {
return evolve(p, pc, popsize, rankFunc, 500);
}
public static List<RankScore> evolve(List<Node> p,int pc, int popsize, RankFunction rankFunc, int maxgen) {
return evolve(p, pc, popsize, rankFunc, maxgen, 0.1);
}
public static List<RankScore> evolve(List<Node> p,int pc, int popsize, RankFunction rankFunc, int maxgen, double mutationrate) {
return evolve(p, pc, popsize, rankFunc, maxgen, mutationrate, 0.4);
}
public static List<RankScore> evolve(List<Node> p,int pc, int popsize, RankFunction rankFunc, int maxgen, double mutationrate, double breedingrate) {
return evolve(p, pc, popsize, rankFunc, maxgen, mutationrate, breedingrate, 0.7);
}
public static List<RankScore> evolve(List<Node> p,int pc, int popsize, RankFunction rankFunc, int maxgen, double mutationrate, double breedingrate, double pexp) {
return evolve(p, pc, popsize, rankFunc, maxgen, mutationrate, breedingrate, pexp, 0.05);
}
public static List<RankScore> evolve(List<Node> p, int pc, int popsize, RankFunction rankFunc, int maxgen, double mutationrate, double breedingrate, double pexp, double pnew) {
List<Node> population = null;
if (p == null) {
population = new ArrayList<>();
for (int i = 0; i < popsize; i++) {
population.add(RandomTree.makeRandomTree(pc));
}
} else {
population = p;
}
List<RankScore> scores = new ArrayList<>();
List<Node> newpop = null;
for (int i = 0; i < maxgen; i++) {
scores = rankFunc.getRankFunction(population);
System.out.println(scores.get(0).getScore());
if (scores.get(0).getScore() == 0) break;
newpop = new ArrayList<>();
newpop.add(scores.get(0).getT());
newpop.add(scores.get(1).getT());
while (newpop.size() < popsize) {
if (Math.random() > pnew) {
int temp1 = selectIndex(pexp);
int temp2 = selectIndex(pexp);
int index1 = temp1 <= 1 ? temp1+2: temp1;
int index2 = temp2 <= 1 ? temp2+2: temp2;
newpop.add(Mutate.mutate(Crossover.crossover(scores.get(index1).getT(), scores.get(index2).getT(), breedingrate), pc, mutationrate));
} else {
newpop.add(RandomTree.makeRandomTree(pc));
}
}
population = newpop;
}
scores.get(0).getT().display(2);
return scores;
}
private static int selectIndex(double pexp) {
return (int) (Math.log(Math.random()) / Math.log(pexp));
}
}
至此遗传编程就实现了!
下面我们来测试一下
public static List<Score> buildhiddenset() {
List<Score> rows = new ArrayList<>();
int x,y;
for (int i=0; i<200; i++) {
Score score = new Score();
x = i+3;
y = 3*i-1;
score.setX(x);
score.setY(y);
score.setScore(x*y);
rows.add(score);
}
return rows;
}
上面的程序表示我们是在x*y=3i^2+8i-3上采的数据点集,看看是否最后能够生成代表数学公式的程序
public class GeneticTest {
public static void main(String[] args) {
Environment.evolve(null, 3, 100, new RankFunc(), 100);
}
}
输出:
595020
0
multiply
p1
p0
p0为x,p1为y,从而p0*p1=x*y=3i^2+8i-3
再次执行输出:
5349179
5015604
0
substract
multiply
p0
p1
substract
p1
p1
p0为x,p1为y,从而p0*p1=x*y=3i^2+8i-3