遗传编程——java语言实现

xiaoxiao2021-02-28  38

对于遗传编程的理论请参看《集体智慧编程》一书,书中对于遗传编程的原理有详细的阐述。

遗传编程的大体执行过程如下图所示:

我们使用树形表示法来描述图中遗传编程中的程序。

下面进入到我们这篇博客的重点了,遗传编程——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

转载请注明原文地址: https://www.6miu.com/read-2150328.html

最新回复(0)