• Aucun résultat trouvé

Grammatical Evolution

Dans le document Clever Algorithms (Page 138-146)

Evolutionary Algorithms

3.7 Grammatical Evolution

Grammatical Evolution, GE.

3.7.1 Taxonomy

Grammatical Evolution is a Global Optimization technique and an instance of an Evolutionary Algorithm from the field of Evolutionary Computation.

It may also be considered an algorithm for Automatic Programming. Gram-matical Evolution is related to other Evolutionary Algorithms for evolving programs such as Genetic Programming (Section3.3) and Gene Expression Programming (Section3.8), as well as the classical Genetic Algorithm that uses binary strings (Section3.2).

3.7.2 Inspiration

The Grammatical Evolution algorithm is inspired by the biological process used for generating a protein from genetic material as well as the broader genetic evolutionary process. The genome is comprised of DNA as a string of building blocks that are transcribed to RNA. RNA codons are in turn translated into sequences of amino acids and used in the protein. The resulting protein in its environment is the phenotype.

3.7.3 Metaphor

The phenotype is a computer program that is created from a binary string-based genome. The genome is decoded into a sequence of integers that are in turn mapped onto pre-defined rules that makeup the program. The mapping from genotype to the phenotype is a one-to-many process that uses a wrapping feature. This is like the biological process observed in many bacteria, viruses, and mitochondria, where the same genetic material is used in the expression of different genes. The mapping adds robustness to the process both in the ability to adopt structure-agnostic genetic operators used during the evolutionary process on the sub-symbolic representation and the transcription of well-formed executable programs from the representation.

3.7.4 Strategy

The objective of Grammatical Evolution is to adapt an executable program to a problem specific objective function. This is achieved through an iterative process with surrogates of evolutionary mechanisms such as descent with variation, genetic mutation and recombination, and genetic transcription and gene expression. A population of programs are evolved in a sub-symbolic form as variable length binary strings and mapped to a sub-symbolic and well-structured form as a context free grammar for execution.

3.7. Grammatical Evolution 127

3.7.5 Procedure

A grammar is defined in Backus Normal Form (BNF), which is a context free grammar expressed as a series of production rules comprised of terminals and non-terminals. A variable-length binary string representation is used for the optimization process. Bits are read from the a candidate solutions genome in blocks of 8 called a codon, and decoded to an integer (in the range between 0 and 28−1). If the end of the binary string is reached when reading integers, the reading process loops back to the start of the string, effectively creating a circular genome. The integers are mapped to expressions from the BNF until a complete syntactically correct expression is formed. This may not use a solutions entire genome, or use the decoded genome more than once given it’s circular nature. Algorithm3.7.1provides a pseudocode listing of the Grammatical Evolution algorithm for minimizing a cost function.

3.7.6 Heuristics

ˆ Grammatical Evolution was designed to optimize programs (such as mathematical equations) to specific cost functions.

ˆ Classical genetic operators used by the Genetic Algorithm may be used in the Grammatical Evolution algorithm, such as point mutations and one-point crossover.

ˆ Codons (groups of bits mapped to an integer) are commonly fixed at 8 bits, proving a range of integers∈[0,28−1] that is scaled to the range of rules using a modulo function.

ˆ Additional genetic operators may be used with variable-length rep-resentations such as codon segments, duplication (add to the end), number of codons selected at random, and deletion.

3.7.7 Code Listing

Listing3.6 provides an example of the Grammatical Evolution algorithm implemented in the Ruby Programming Language based on the version described by O’Neill and Ryan [5]. The demonstration problem is an instance of symbolic regressionf(x) =x4+x3+x2+x, wherex∈[1,10].

The grammar used in this problem is:

ˆ Non-terminals: N ={expr, op, pre op}

ˆ Terminals: T ={+,−,÷,×, x,1.0}

ˆ Expression (program): S=<expr>

The production rules for the grammar in BNF are:

Algorithm 3.7.1: Pseudocode for Grammatical Evolution.

Input: Grammar,Codonnumbits,P opulationsize,Pcrossover, Pmutation,Pdelete,Pduplicate

Output: Sbest

Population←InitializePopulation(P opulationsize,

1

Codonnumbits);

foreachSi ∈Populationdo

2

Siintegers ←Decode(Sibitstring,Codonnumbits);

3

Siprogram←Map(Siintegers,Grammar);

4

Sicost ←Execute(Siprogram);

5

end

6

Sbest←GetBestSolution(Population);

7

while ¬StopCondition()do

8

Parents ←SelectParents(Population,P opulationsize);

9

Sibitstring ←CodonDeletion(Sibitstring,Pdelete);

13

Sibitstring ←CodonDuplication(Sibitstring,Pduplicate);

14

Sibitstring ←Mutate(Sibitstring,Pmutation);

15

Children←Si;

16

end

17

foreachSi ∈Childrendo

18

Siintegers ←Decode(Sibitstring,Codonnumbits);

19

Siprogram←Map(Siintegers,Grammar);

20

Sicost ←Execute(Siprogram);

21

end

22

Sbest←GetBestSolution(Children);

23

Population←Replace(Population,Children);

24

end

25

returnSbest;

26

ˆ <expr>::=<expr><op><expr>,(<expr><op><expr>),<pre op>(<expr>),

<var>

ˆ <op> ::= +,−,÷,×

ˆ <var> ::= x, 1.0

The algorithm uses point mutation and a codon-respecting one-point crossover operator. Binary tournament selection is used to determine the parent population’s contribution to the subsequent generation. Binary strings are decoded to integers using an unsigned binary. Candidate solutions are then mapped directly into executable Ruby code and executed. A given

3.7. Grammatical Evolution 129 candidate solution is evaluated by comparing its output against the target function and taking the sum of the absolute errors over a number of trials.

The probabilities of point mutation, codon deletion, and codon duplication are hard coded as relative probabilities to each solution, although should be parameters of the algorithm. In this case they are heuristically defined as 1.0L, N C0.5 and N C1.0 respectively, whereLis the total number of bits, and N C is the number of codons in a given candidate solution.

Solutions are evaluated by generating a number of random samples from the domain and calculating the mean error of the program to the expected outcome. Programs that contain a single term or those that return an invalid (NaN) or infinite result are penalized with an enormous error value.

The implementation uses a maximum depth in the expression tree, whereas traditionally such deep expression trees are marked as invalid. Programs that resolve to a single expression that returns the output are penalized.

1 def binary_tournament(pop)

2 i, j = rand(pop.size), rand(pop.size)

3 j = rand(pop.size) while j==i

4 return (pop[i][:fitness] < pop[j][:fitness]) ? pop[i] : pop[j]

5 end

6

7 def point_mutation(bitstring, rate=1.0/bitstring.size.to_f)

8 child = ""

9 bitstring.size.times do |i|

10 bit = bitstring[i].chr

11 child << ((rand()<rate) ? ((bit=='1') ? "0" : "1") : bit)

12 end

13 return child

14 end

15

16 def one_point_crossover(parent1, parent2, codon_bits, p_cross=0.30)

17 return ""+parent1[:bitstring] if rand()>=p_cross

18 cut = rand([parent1.size, parent2.size].min/codon_bits)

19 cut *= codon_bits

20 p2size = parent2[:bitstring].size

21 return parent1[:bitstring][0...cut]+parent2[:bitstring][cut...p2size]

22 end

23

24 def codon_duplication(bitstring, codon_bits, rate=1.0/codon_bits.to_f)

25 return bitstring if rand() >= rate

26 codons = bitstring.size/codon_bits

27 return bitstring + bitstring[rand(codons)*codon_bits, codon_bits]

28 end

29

30 def codon_deletion(bitstring, codon_bits, rate=0.5/codon_bits.to_f)

31 return bitstring if rand() >= rate

32 codons = bitstring.size/codon_bits

33 off = rand(codons)*codon_bits

34 return bitstring[0...off] + bitstring[off+codon_bits...bitstring.size]

35 end

36

37 def reproduce(selected, pop_size, p_cross, codon_bits)

38 children = []

39 selected.each_with_index do |p1, i|

40 p2 = (i.modulo(2)==0) ? selected[i+1] : selected[i-1]

41 p2 = selected[0] if i == selected.size-1

42 child = {}

43 child[:bitstring] = one_point_crossover(p1, p2, codon_bits, p_cross)

44 child[:bitstring] = codon_deletion(child[:bitstring], codon_bits)

45 child[:bitstring] = codon_duplication(child[:bitstring], codon_bits)

46 child[:bitstring] = point_mutation(child[:bitstring])

47 children << child

48 break if children.size == pop_size

49 end

50 return children

51 end

52

53 def random_bitstring(num_bits)

54 return (0...num_bits).inject(""){|s,i| s<<((rand<0.5) ? "1" : "0")}

55 end

56

57 def decode_integers(bitstring, codon_bits)

58 ints = []

59 (bitstring.size/codon_bits).times do |off|

60 codon = bitstring[off*codon_bits, codon_bits]

61 sum = 0

62 codon.size.times do |i|

63 sum += ((codon[i].chr=='1') ? 1 : 0) * (2 ** i);

64 end

65 ints << sum

66 end

67 return ints

68 end

69

70 def map(grammar, integers, max_depth)

71 done, offset, depth = false, 0, 0

72 symbolic_string = grammar["S"]

73 begin

74 done = true

75 grammar.keys.each do |key|

76 symbolic_string = symbolic_string.gsub(key) do |k|

77 done = false

78 set = (k=="EXP" && depth>=max_depth-1) ? grammar["VAR"] : grammar[k]

79 integer = integers[offset].modulo(set.size)

80 offset = (offset==integers.size-1) ? 0 : offset+1

81 set[integer]

82 end

83 end

84 depth += 1

85 end until done

86 return symbolic_string

94 return bounds[0] + ((bounds[1] - bounds[0]) * rand())

3.7. Grammatical Evolution 131

95 end

96

97 def cost(program, bounds, num_trials=30)

98 return 9999999 if program.strip == "INPUT"

99 sum_error = 0.0

100 num_trials.times do

101 x = sample_from_bounds(bounds)

102 expression = program.gsub("INPUT", x.to_s)

103 begin score = eval(expression) rescue score = 0.0/0.0 end

104 return 9999999 if score.nan?or score.infinite?

105 sum_error += (score - target_function(x)).abs

106 end

107 return sum_error / num_trials.to_f

108 end

109

110 def evaluate(candidate, codon_bits, grammar, max_depth, bounds)

111 candidate[:integers] = decode_integers(candidate[:bitstring], codon_bits)

112 candidate[:program] = map(grammar, candidate[:integers], max_depth)

113 candidate[:fitness] = cost(candidate[:program], bounds)

114 end

115

116 def search(max_gens, pop_size, codon_bits, num_bits, p_cross, grammar, max_depth, bounds)

117 pop = Array.new(pop_size) {|i| {:bitstring=>random_bitstring(num_bits)}}

118 pop.each{|c| evaluate(c,codon_bits, grammar, max_depth, bounds)}

119 best = pop.sort{|x,y| x[:fitness] <=> y[:fitness]}.first

120 max_gens.times do |gen|

121 selected = Array.new(pop_size){|i| binary_tournament(pop)}

122 children = reproduce(selected, pop_size, p_cross,codon_bits)

123 children.each{|c| evaluate(c, codon_bits, grammar, max_depth, bounds)}

124 children.sort!{|x,y| x[:fitness] <=> y[:fitness]}

125 best = children.first if children.first[:fitness] <= best[:fitness]

126 pop=(children+pop).sort{|x,y| x[:fitness]<=>y[:fitness]}.first(pop_size)

127 puts " > gen=#{gen}, f=#{best[:fitness]}, s=#{best[:bitstring]}"

128 break if best[:fitness] == 0.0

129 end

130 return best

131 end

132

133 if __FILE__ == $0

134 # problem configuration

135 grammar = {"S"=>"EXP",

136 "EXP"=>[" EXP BINARY EXP ", " (EXP BINARY EXP) ", " VAR "],

137 "BINARY"=>["+", "-", "/", "*" ],

138 "VAR"=>["INPUT", "1.0"]}

139 bounds = [1, 10]

140 # algorithm configuration

141 max_depth = 7

142 max_gens = 50

143 pop_size = 100

144 codon_bits = 4

145 num_bits = 10*codon_bits

146 p_cross = 0.30

147 # execute the algorithm

148 best = search(max_gens, pop_size, codon_bits, num_bits, p_cross, grammar, max_depth, bounds)

149 puts "done! Solution: f=#{best[:fitness]}, s=#{best[:program]}"

150 end

Listing 3.6: Grammatical Evolution in Ruby

3.7.8 References

Primary Sources

Grammatical Evolution was proposed by Ryan, Collins and O’Neill in a seminal conference paper that applied the approach to a symbolic regression problem [7]. The approach was born out of the desire for syntax preservation while evolving programs using the Genetic Programming algorithm. This seminal work was followed by application papers for a symbolic integration problem [2,3] and solving trigonometric identities [8].

Learn More

O’Neill and Ryan provide a high-level introduction to Grammatical Evolu-tion and early demonstraEvolu-tion applicaEvolu-tions [4]. The same authors provide a thorough introduction to the technique and overview of the state of the field [5]. O’Neill and Ryan present a seminal reference for Grammatical Evolution in their book [6]. A second more recent book considers extensions to the approach improving its capability on dynamic problems [1].

3.7.9 Bibliography

[1] I. Dempsey, M. O’Neill, and A. Brabazon. Foundations in Grammatical Evolution for Dynamic Environments. Springer, 2009.

[2] M. O’Neill and C. Ryan. Grammatical evolution: A steady state ap-proach. InProceedings of the Second International Workshop on Fron-tiers in Evolutionary Algorithms, pages 419–423, 1998.

[3] M. O’Neill and C. Ryan. Grammatical evolution: A steady state ap-proach. In Late Breaking Papers at the Genetic Programming 1998 Conference, 1998.

[4] M. O’Neill and C. Ryan. Under the hood of grammatical evolution. In Proceedings of the Genetic and Evolutionary Computation Conference, 1999.

[5] M. O’Neill and C. Ryan. Grammatical evolution. IEEE Transactions on Evolutionary Computation, 5(4):349–358, 2001.

[6] M. O’Neill and C. Ryan. Grammatical Evolution: Evolutionary Auto-matic Programming in an Arbitrary Language. Springer, 2003.

3.7. Grammatical Evolution 133 [7] C. Ryan, J. J. Collins, and M. O’Neill. Grammatical evolution: Evolving programs for an arbitrary language. In Lecture Notes in Computer Science 1391. First European Workshop on Genetic Programming, 1998.

[8] C. Ryan, J. J. Collins, and M. O’Neill. Grammatical evolution: Solving trigonometric identities. InProceedings of Mendel 1998: 4th Interna-tional Mendel Conference on Genetic Algorithms, Optimisation Problems, Fuzzy Logic, Neural Networks, Rough Sets., pages 111–119, 1998.

Dans le document Clever Algorithms (Page 138-146)