레이블이 교차인 게시물을 표시합니다. 모든 게시물 표시
레이블이 교차인 게시물을 표시합니다. 모든 게시물 표시

GA - 자원운송계획 - 재생산

4. 재생산
8193개의 개체들 중 최고적응도의 개체를 골라 엘리트로 복사하고, 나머지 8192개의 개체는 유전자알고리즘을 사용해 재생산했습니다.
T=0.99의 Tournament법을 사용하여 우수개체를 골라 교차 및 돌연변이를 일으켰습니다.

5. 교차
일반적인 유전자알고리즘의 경우와 같이 여기서도 재생산 대상을 두개 선택한 후 그들을 교차시킵니다. 그러나 유전자가 예전의 1차원 유전자가 아니라 2차원 유전자라는 문제가 있습니다.
일반적으로 2차원 유전자의 교차는 (아니 1차원부터 2차원, 3차원 이상의 고차원 유전자의 교차는) 구획나누기 이후 구획 재배치로 정의될 수 있습니다. 문제는 구획을 어떻게 나누느냐가 됩니다.
다음의 보기는 2차원의 경우이지만 n차원으로 확장시킬 수 있습니다.

① 다음 그림과 같이 x, y축을 몇개의 구획으로 나눕니다. 그후 아래 그림과 같이 순서대로 교차시키는 것입니다. 이것은 1차원교차에서 주로 사용했던 n점교차를 확장한 형태입니다.

② 또다른 방법은 구획을 다음 그림과 같이 도형으로 나누는 경우도 있습니다. 이 경우에는 구획 재배치가 쉽지 않아 보이지만, 간단히 짝수(0 포함) 또는 홀수개의 도형에 포함되는 구획만 교환하면 생각보다 쉽게 됩니다.

③ 마지막으로 임의로 선을 그어 구획을 나눌 수 있습니다.
①, ②번은 각각 하나의 축만, 그리고 각 차원에서의 원/구를 생각하면 되므로 4차원 이상의 유전자에도 비교적 쉽게 적용할 수 있습니다. 그러나 ③번은, 개념적으로는 3차원에서는 3각형평면의 연속, 4차원에서는 삼각뿔 도형의 연속, ... 등으로 할 수 있으나 직관적으로 다가오지 않아 상당히 어려운 작업이 될 수 있습니다.
여기서는 3개의 임의의 원을 그려 ②번과 같은 식의 교차를 시행했습니다.

procedure CrossOver(MoonBase a, MoonBase b)
.. // 세개의 임의의 원을 만듦
.. for k := 0, 3 do
..... circle[k].f := Random(0, 10) // 유전자크기가 10X10이므로
..... circle[k].t := Random(0, 10)
..... circle[k].radius := Random(0, 10)
.. end
.. for f := 0, 10 do
..... for t := 0, 10 do
........ covered := 0
........ // 3개 원중 몇개에 겹쳐지는지 계산
........... for k := 0, 3 do
.............. // (f,t)와 k번째 원 중심간 거리 계산
.............. df := f - circle[k].f
.............. dt := t - circle[k].t
.............. dist := sqrt(df * df + dt * dt)
.............. // 만약 거리가 반지름보다 작다면(원에 포함된다면)
.............. if dist < circle[k].radius then
................. covered := covered + 1
.............. end
........... end
........ if covered % 2 then // 홀수개에 겹쳐져 있다면 교환
........... temp := a[f][t]
........... a[f][t] := b[f][t]
........... b[f][t] := temp
........ end
..... end
.. end
end


6. 돌연변이
10×10개의 칸 하나하나에 대해 돌연변이를 적용시킵니다.

procedure Mutantation(Gene gene)
.. for f := 1, 10 do
..... for t := 1, 10 do
........ if gene[f][t] == 0 then
........... if MutantationRate then
............. gene[f][t] := Random(0, 10)
........... end
........ else // f->t로의 운송량 있을 경우
........... if MutantationRate then // 운송을 제거
............. gene[f][t] := 0
........... end
........... else if MutantationRate then // 운송량 변경
............. gene[f][t] := gene[f][t] + Random(-1, 1)
........... end
........... else if MutantationRate then // 운송을 나눔
............. T := Random(1, 3)
............. split := Random(0, gene[f][t])
............. gene[f][t] := gene[f][t] - split
............. gene[f][T] := gene[f][t] + split
........... end
........ end

..... end
.. end
end

GA - CNNC를 실은 꼬마자동차[2] - 입력과 출력, 교차

5. 꼬마자동차의 신경망 입력
꼬마자동차가 알아야 할 정보는, 바로 앞의 블럭정보, 그리고 연료가 어디 있는지에 대한 정보입니다.
꼬마자동차 CNNC의 입력노드 중 3개는 앞쪽에 어떤 장애물이 있는지(있으면 1, 없으면 -1), 마지막 하나는 연료의 위치를 -1~+1 사이의 float값으로 입력합니다.

procedure Detect(Car car, StoneRoad road)
.. cl := car.Locate.Len
.. cw := car.Locate.Wid

.. // 앞쪽 3칸 블럭정보
.. if road[cl - 1][cw - 1] == ROCK then
..... SetCharge(
car.CNNC, 0, 1) // 0번셀에 입력
.. else
.....
SetCharge(car.CNNC, 0, -1)
.. end
.. if road[cl - 1][cw + 0] == ROCK then
.....
SetCharge(car.CNNC, 1, 1) // 1번셀에 입력
.. else
.....
SetCharge(car.CNNC, 1, -1)
.. end
.. if road[cl - 1][cw + 1] == ROCK then
.....
SetCharge(car.CNNC, 2, 1) // 2번셀에 입력
.. else
.....
SetCharge(car.CNNC, 2, -1)
.. end

.. // 기름통 위치정보(차에서 가장 가까운)
.. fl := road.Fuel(car).Locate.Len
.. fw := road.Fuel(car).Locate.Wid
..
SetCharge(car.CNNC, 3, (fw - cw) / (fl - cl)) // 3번셀에 입력
end

6. 꼬마자동차의 신경망 출력
신경망 출력을 꼬마자동차의 움직임으로 바꾸기 위해 3개의 출력노드를 사용합니다. 그리고 각 출력노드의 출력양에 따라 직진할지 왼쪽이나 오른쪽으로 움직일지 결정하는 것입니다.
그러나 신경망의 특성상 가능하면 노드수를 줄이는 것이 최적화시키는데 도움이 됩니다. 여기서는 출력노드를 두개로 만들어, 더 강한 출력을 보이는 쪽으로 이동시키도록 하겠습니다. 즉,

procedure Action(Car car, StoneRoad road)
.. left = 0
.. right = 0
.. l := GetCharge(car.CNNC, 0);
.. r := GetCharge(car.CNNC, 1);
.. if l > 0 then // 왼쪽으로 움직이려는 경향
..... left := left + l
.. else // -왼쪽(즉 오른쪽)으로 움직이려는 경향
..... right := right - l
.. end .. if r > 0 then // 오른쪽으로 움직이려는 경향
..... right := right + r
.. else // -오른쪽(즉 왼쪽)으로 움직이려는 경향
..... left := left - r
.. end
.. // 출력이 강한 쪽으로 이동
.. rnd := Random(0, 2)
.. if left > rnd then
..... if right > rnd then
........ MoveForwar(car)
..... else
........ MoveLeft(car)
..... end
... else
..... if right > rnd then
........ MoveRight(car)
..... else
........ MoveForwar(car)
..... end
.. end
end


출력노드를 하나로 해 놓고, +1에 가까울수록 왼쪽으로, -1에 가까울수록 오른쪽으로 움직이도록 할 수도 있지만, 생각만큼 수렴이 잘 되지 않더군요. 우선은 출력노드 두개짜리로 실행해 봤습니다.

7. 적응도 계산
위와 같은 식으로 256개의 꼬마자동차와 64개의 돌길 개체를 만듧니다. 이 256개의 꼬마자동차 각각은 64개의 돌길을 통과하고 각 꼬마자동차돌길의 적응도를 결정합니다.
꼬마자동차의 적응도 : 평지로 진입할 때마다 10점추가, 장애물을 통과할때 10점감점
돌길의 적응도 : 자동차가 기름을 먹으면 5점감점, 장애물로 진입하면 10점추가

8. 교차
꼬마자동차의 경우는 앞의 XOR회로와 같은 일반적인 CNNC식 교차를 사용했습니다.
돌길의 경우는 길 전체를 길이 100의 선형유전자로 간주하고 일반적인 일점교차를 사용했습니다.

procedure CrossOver(StoneRoad a, StoneRoad b)
.. cut := Random(1, 99)
.. for l := 0, cut do
..... for w := 0, 11 do
........ tmp := a[l][w]
........ a[l][w] := b[l][w]
........ b[l][w] := tmp
..... end
.. end
end

9. 돌연변이
돌연변이 역시 꼬마자동차의 경우에는 CNNC의 돌연변이 루틴을 사용합니다.
돌길의 돌연변이는 두 단계로 이루어집니다. 첫째, 일정한 확률로 장애물을 만들기(또는 장애물을 없애기), 둘째, 기름통 위치 바꾸기입니다.

procedure Mutantation(StoneRoad gene)
.. for l := 0, 99 do
..... for w := 1, 10 do // 가장자리는 장애물로 유지
........ if MutantRate then
........... if gene[l][w] == ROCK then
.............. gene[l][w] := ROAD
........... else if gene[l][w] == ROAD then
.............. gene[l][w] := ROCK
........... end
..... end
.. end

.. for l := 0, 99 do
..... for w := 1, 10 do // 가장자리는 장애물로 유지
........ if gene[l][w] == FUEL then
........... if MutantRate then
.............. gene[l][w] := ROAD
.............. gene[l][Random(1, 10)] := FUEL
.............. break // w 루프 탈춮
........... end
........ end
..... end
.. end
end

10. 재생산
256개 꼬마자동차들이 모두 64개 돌길에서 달려 본 후 꼬마자동차돌길을 재생산합니다. 역시 꼬마자동차의 경우는 CNNC의 재생산 루틴을 사용했으며(이 경우 상위 8개의 꼬마자동차를 다음 세대에 그대로 복사하는 Elitism을 사용했습니다.)
돌길의 경우에는 T==0.95, Round==4인 토너먼트법(임의로 24=16개의 돌길을 고른 후 95%의 확률로 적응도 높은 쪽이 이기는 토너먼트를 4회 반복)으로 재생산시켰습니다.

GA - CNNC를 이용한 XOR 회로[3] - 교차

5. 교차
재생산에 대해 말하기 전에 먼저 교차를 어떻게 해야 할지 생각해 봅시다.
다음 두 유전자를 보죠. 보기이므로 간단하게 만들어 보았습니다.

+1.1/1/2(0000000000000000)(0010000100000000)
-0.1/1/2(0010000000000000)(0000000000000000)
-0.2/1/2(0000000100000000)(0000000000000000)

+1.1/3/4(0000000000000000)(0000001000000100)
-0.1/3/4(0000000000000100)(0000000000000000)
-0.2/3/4(0000001000000000)(0000000000000000)

둘 다 기본적인 OR회로와 같은 형태를 가지고 있습니다. 이 둘을 교차시키면 최소한 동일한 OR회로가 나와야 할 것 같습니다.
① 먼저 저 유전자를 단순한 1차원 유전자라 간주하고 1점교차를 시도해 보겠습니다.

+1.1/1/2(0000000000000000)(0010000100000000)
-0.1/1/2(0010000
000000100)(0000000000000000)
-0.2/3/4(0000001000000000)(0000000000000000)


+1.1/3/4(0000000000000000)(0000001000000100)
-0.1/3/4(0000000
000000000)(0000000000000000)
-0.2/1/2(0000000100000000)(0000000000000000)


이것으로 신경망을 만들어보면 알 수 있겠지만, 앞쪽것은 입력노드와 출력노드를 연결하던 연결 하나가 사라지고 말았습니다. 뒷쪽것은 아예 연결이 모두 사라졌네요. 결국 다음과 같이 망가진 두개의 신경망이 생기고 말았습니다.

② 그렇다면 저 3개의 노드를 한묶음으로 생각하고 교차를 해 봅시다.

+1.1/1/2(0000000000000000)(0010001000000100)
-0.1/1/2(0010000000000000)(000
0000000000000)
-0.2/1/2(0000000100000000)(000
0000000000000)

+1.1/3/4(0000000000000000)(000
0000100000000)
-0.1/3/4(0000000000000100)(000
0000000000000)
-0.2/3/4(0000001000000000)(000
0000000000000)

마찬가지 결과입니다. 윗그림과 똑같이 망가진 신경망 두개가 생기는군요.

③ 잘 생각해 보면 하나의 연결에 영향을 미치는 것은 송신채널과 수신채널에 나뉘어져 있습니다. 그렇다면 송신채널과 수신채널을 겹쳐놓고 교차시키는 것이 나을 수도 있겠군요.

+1.1/1/2(0000000000000000)
........(0010000100000100)
-0.1/1/2(0010000000000100)
........(0000000000000000)
-0.2/1/2(0000000100000000)
........(0000000000000000)

+1.1/3/4(0000000000000000)
........(0000001000000000)
-0.1/3/4(0000000000000000)
........(0000000000000000)
-0.2/3/4(0000001000000000)
........(0000000000000000)

이 경우에는 두번째 것은 연결 하나가 끊어졌지만 첫번째 것은 온전하게 남아 있습니다. 다른 교차법에 비해 그나마 낫군요.
하지만 또한가지 문제가 있습니다. 다음 두 유전자는 어떻게 교차를 시킬 수 있을까요?

+1.1/1/2(0000000000000000)(0011000100000000)
+0.7/1/2(0001000000000000)(0000000000000100)
-0.1/1/2(0010000000000100)(0000000000000000)
-0.2/1/2(0000000100000000)(0000000000000000)

+1.1/3/4(0000000000000000)(0000001000000100)
-0.1/3/4(0000000000000100)(0000000000000000)
-0.2/3/4(0000001000000000)(0000000000000000)

이 둘을 교차시키기는 쉽지 않습니다. 그러므로 CNNC에서의 교차는 '노드 수가 같을 경우에만' 하는 것으로 규정합니다.

지금까지 만들었던 유전자 알고리즘에서, 교차는 다음과 같은 방식으로 해 왔습니다.

① 두 부모 유전자의 복제를 만든다.
② 만들어진 복제의 유전자를 교차시켜 자식유전자로 한다.

즉 두 부모 유전자로부터 두 자녀유전자가 생겼습니다.
그러나 CNNC에서는 교차를 하더라도 하나는 망가지는 경우가 종종 생깁니다. 그러므로 CNNC에서는 방법을 바꾸어 두 부모유전자를 섞어 하나의 자식유전자를 만들게 됩니다.
이때는 각종 인수들(Bias, SigmoidFacter 등) 및 채널정보를 두 부모유전자의 적응도비율에 의해 부모 양쪽에서 가져옵니다. 이때 1점교차가 아니라 랜덤교차를 사용합니다.
만약 한쪽 적응도가 100, 다른쪽이 50이라면 대략 다음과 같은 자식이 나옵니다.

+1.1/3/2(0000000000000000)
........(0000001000000100)
-0.1/1/2(0000000000000100)
........(0000000000000000)
-0.2/1/2(0000001000000000)
........(0000000000000000)

물론 이 방식도 랜덤함수에 의해 잘못된 자식이 나올 가능성도 배제할 수는 없습니다만, 정상적인(그리고 보다 우수한) 자손이 나올 가능성이 더 크며 잘못된 자식은 다음 세대에 도태되므로 큰 문제를 일으키지는 않습니다(물론 알고리즘의 효율성이 조금 떨어질 수는 있죠).

procedure CrossOver(Gene male, Gene female, Gene child)
.. if male.NodeSize != female.NodeSize then // 궁합이 안맞음(노드수가 다름)
..... child.Valid := false
..... return
.. end

.. // 각 부모를 layer순으로 정렬(입력노드와 출력노드가 교차되지 않도록)
.. sort(male)
.. sort(female)

.. TotalFitness := male.Fitness + female.Fitness
.. MalePriority := male.Fitness / TotalFitness

.. // 각종 파라미터 나누기
.. for k := 0, male.NodeSize do
..... if MalePriority > Random(0, 1) then
........ child.Node[k].Layer = male.Node[k].Layer
..... else
........ child.Node[k].Layer = female.Node[k].Layer
..... end
..... // 생략, Bias와 SigmoidFactor도 동일하게 복사
.. end

.. // 채널 복사
.. for c := 0, 16 do
..... rnd := Random(0, 1)
..... for k := 0, male.NodeSize do
........ if MalePriority > rnd then
........... child.Node[k].SendChannel[c] := male.Node[k].SendChannel[c]
........... child.Node[k].RecvChannel[c] := male.Node[k].RecvChannel[c]
........ else
........... child.Node[k].SendChannel[c] := female.Node[k].SendChannel[c]
........... child.Node[k].RecvChannel[c] := female.Node[k].RecvChannel[c]
........ end
..... end
.. end
.. child.Valid := true
end

GA - 여덟여왕문제(8-Queen's Problem)[2] - 재생산

3. 적응도 계산
역시 자세한 알고리즘은 생략하겠습니다. 각각의 하렘들의 유전자에 따라 여왕들을 배치한 후 각각의 여왕들에 대해 진로를 가로막고 있는 다른 여왕이 있는지 살핍니다. 자신의 진로 위에 다른 여왕이 있을 때마다 ConflictNumber를 하나씩 더합니다. 만약 다른 여왕이 같은 자리를 차지하고 있을 경우, 그 여왕은 가로, 세로, 양쪽 대각선(2~7시방향, 11~5시방향) 네 진로에 걸리므로 ConflictNumber가 4 증가됩니다.
이 하렘의 전체 적응도는 -ConflictNumber가 됩니다(결국 이 문제도 -적응도를 가지게 되었군요)

4. 재생산 대상 선택
앞에서와 같이 Tournament법을 사용했습니다. 다만 여기서는 4차 Tournament법을 사용했습니다.
난수를 통해 16개의 재생산 후보를 골라냅니다. 그리고 이 16개의 후보들을 토너먼트 방식(일반적인 Tournament법과 같이, 0~1 사이의 난수를 발생시켜, 상수 T보다 작으면 적응도가 큰쪽, 상수 T보다 크면 적응도가 작은 쪽이 승자)으로 계속 경쟁시켜 최후의 승자를 재생산 후보로 결정합니다.

5. 교차
여기서는 유전자에 대한 특정한 제한이 없으므로 일반적인 교차를 사용할 수 있습니다. 이 문제에서는 랜덤교차를 사용했습니다.
다만 이경우에, '유전자의 최소단위'가 어떻게 될지 생각해 보는 것이 좋겠군요.

㉠:[(-2/ 1)( 1/ 2)(-3/ 2)(-3/-2)(-3/ 1)( 2/ 3)(-1/ 3)(-2/-1)]
㉡:[(-2/ 1)( 1/ 2)(-2/ 3)( 3/ 2)( 2/-1)(-1/-3)( 3/-2)(-1/ 3)]

위와 같은 두 유전자를 교차시킨다고 생각해 봅시다. 만약

㉠:[(-2/ 1)( 1/ 2)(-3/ 3)( 3/ 2)( 2/-1)(-1/-3)( 3/ 3)(-2/-1)]
㉡:[(-2/ 1)( 1/ 2)(-2/ 2)(-3/-2)(-3/ 1)( 2/ 3)(-1/-2)(-1/ 3)]

와 같이 교차를 시켰다면, ㉠의 (-3/ 3), ( 3/ 3), ㉡의 (-2/ 2)는 (재생산 전에는 앞의 퀸과 부딫치지 않는 위치였음에도) 앞의 퀸과 충돌하는 위치가 되어 버립니다. 그러므로 이 경우에 유전자의 최소단위(bit)는 () 안의 요소가 되어야 합니다.
이 문제에서는 랜덤교차를 사용했으므로 교차 후의 결과는

㉠:[(-2/ 1)( 1/ 2)(-2/ 3)(-3/-2)( 2/-1)( 2/ 3)(-1/ 3)(-2/-1)]
㉡:[(-2/ 1)( 1/ 2)(-3/ 2)( 3/ 2)(-3/ 1)(-1/-3)( 3/-2)(-1/ 3)]

가 될 수 있습니다.

procedure CrossOver(Harem A, Harem B)
.. for k := 0, 8 do
..... if Random(0, 1) > 0.5 then
........ dx := A.gene[k].dx
........ dy := A.gene[k].dy
........ A.gene[k].dx := B.gene[k].dx
........ A.gene[k].dy := B.gene[k].dy
........ B.gene[k].dx := dx
........ B.gene[k].dy := dy
..... end
.. end
end


6. 돌연변이
여기서는 0.0001 확률로 돌연변이시킵니다. 단 이경우에는 dx와 dy에 대해 따로 돌연변이를 시킵니다.

procedure Mutantation(Harem A)
.. for k := 0, 8 do
..... if 0.0001 >
Random(0, 1) then
........ A.gene[k].dx := Random(-4, 5) // -4~4 사이의 랜덤값
..... end
..... if 0.0001 > Random(0, 1) then
........ A.gene[k].dy := Random(-4, 5) // -4~4 사이의 랜덤값
..... end
.. end
end

GA - 장사꾼여행문제(TSP) [3] - 교차

6. 필요한 프로시져
앞에서 말했듯 1234567890과 4567890123, 5432109876 같은 유전자들은 모두 동일한 유전자입니다. 이렇게 유전자를 변형시키는 프로시저가 필요합니다. 자세한 알고리즘은 생략하겠습니다.
Rolling(Gene gene, CityID city)
gene에서 도시들의 순서는 변화시키지 않고 특정 도시를 가장 앞으로 끌어오는 프로시져입니다.
즉 gene이 0123456789일때 Rolling(gene, 7)을 하면 gene이 7890123456으로 바뀝니다.
Flip(Gene gene)
gene에서 첫째 도시는 건드리지 않고 전체 도시 순서를 역순으로 바꿉니다. 즉 gene이 7890123456일때 을 Flip(gene)하면 gene은 7654321098이 됩니다.

7. 교차
잘 아시다시피 교차는 특정한 유전자를 교환하는 작업입니다. 그런데 이 문제에서와 같이 유전자에 제약이 있는 경우에는 함부로 유전자를 교환할 수 없습니다.
한가지 보기로 다음과 같은 두 유전자가 있습니다.

A : 0219746835
B : 7150382946
이 유전자를 1점교배로 교환하면

A : 0219382946
B :
7150746835

이런 유전자가 생깁니다. A는 2, 9번 도시를 두번씩, B는 7, 5번 도시를 두번씩 방문하는군요. 즉 일반적인 교차를 사용하면 제약을 만족하는 유전자를 얻을 수가 없습니다. 그러므로 이 문제에서는 다음과 같은 방식으로 교차를 시킵니다.

㉠ 먼저 교차영역을 정해야 합니다. 임의의 도시를 정해 양쪽 모두 Rolling프로시저를 호출합니다. 만약 4번도시가 선택되었다면

A : 4683502197
B :
4671503829

와 같이 변환시킵니다.
이후에는 앞쪽부터 살펴가면서 경로가 나뉘어졌다가 다시 합류하는 부분을 찾습니다.

A : 4683502197
B :
4671503829

두 유전자의 경로는 6번 도시 이후부터 나뉘기 시작하고 5번 도시에서 다시 일치합니다. 그러므로 이 경우에는 4번도시부터 5번도시까지의 경로를 교차영역으로 삼습니다.
만약 교차영역을 찾을 수가 없다면 이 두 유전자는 교차를 포기하게 됩니다.

㉡ A'와 B'를 만들어 일단 상대방의 교차영역을 복사합니다.

A' :
46715
B' : 46835

㉢ 다음에는 유전자의 나머지 영역을 완성할 차례입니다.
A와 B는 교차영역의 끝(5)을 첫머리로 돌립니다.

A :
5021974683
B : 5038294671

다음에는 A'와 B'에 A와 B를 연결하는데, A'와 B'에 이미 들어가 있는 도시는 제외하면서 연결합니다.

A' :
4671502983
B' : 4683502971

procedure CrossOver(Salesman A, Salesman B)
.. if Random(0, 100) > 50 then // 일정확률로 유전자 하나를 뒤집음
..... Flip(A.gene)
.. end

.. crosspoint := Random(0, 150) // 교차 시작할 도시 정해서 첫머리로
.. Rolling(A.gene, crosspoint)
.. Rolling(B.gene, crosspoint)

.. // 달라지기 시작하는 위치 찾기
.. sub := 0
.. while A.gene[sub] == B.gene[sub] do
..... sub := sub + 1
.. end
.. if sub >= 150 then // 두 유전자가 동일한 유전자
..... return // 교차 포기
.. end
.. // 교차 끝날 위치 찾기
.. while A.gene[sub] == B.gene[sub] do
..... sub := sub + 1
.. end
.. if sub >= 150 then // 두 유전자가 전혀 다른 유전자
..... return // 교차 포기
.. end
.. crossend := sub

.. Salesman AA, BB // 새로운 유전자를 만들 틀
.. // 상대방의 앞쪽부분을 복사
.. for sub := 0, crossend do
..... AA.gene[sub] := b.gene[sub]
..... BB.gene[sub] := a.gene[sub]
.. end
.. // 나머지 부분을 정리하기 위해
.. Rolling(A.gene, A.gene[crossend]) // 교차의 끝부분을 첫째도시로 재설정

.. // AA에 A를 추가
.. insertloc := crossend
.. for sub := 0, 150 do
..... // A.gene[sub]가 AA.gene에 포함되어 있는지를 확인하기 위해
..... for ct := 0, insertloc do
........ if AA.gene[ct] == A.gene[sub] then
........... break for loop
........ end
..... end
..... if ct >= insertloc then // 정상적 루프 탈출 - 중복되는 것이 없었음
........ AA.gene[insertloc] := A.gene[sub]
........
insertloc := insertloc + 1
..... end
.. end

.. // BB에 B를 추가
.. // 생략.. AA건과 동일

.. A := AA
.. B := BB
end


TSP 문제를 해결하기 위한 유전자 설계법이 여러가지인 것처럼, 여기서 사용한 경로표현법 유전자를 교차하는 방법도 여러가지입니다. 여기서 사용한 교차법은 OX법(어떤 말의 약자인지는 모르겠습니다. Order Xover가 아닐까 추측할 뿐입니다)을 약간 수정한 것입니다. OX법은 원리는 같지만, 교차 시작점과 끝점을 찾는 것이 아니라 그냥 랜덤하게 결정해서 교차시키는 방법입니다. 하지만 제가 테스트한 결과로는 전혀 수렴이 안되더군요. 물론 제가 OX법을 제대로 이해 못해서일 수도 있습니다만...^^

GA - 자동차[2] 재생산

3. 가상환경에서의 적응도 테스트
이부분은 자세한 알고리즘은 생략하겠습니다. 단지 먼저 1000점을 주고, 임의로 만들어진 도로를 앞에서 설계한 유전자에 따라 일정거리(100칸) 이동시키면서 장애물을 들이받을 때마다 10점씩 감점시키는 것입니다. 다만 만약 앞 3칸이 다 장애물로 채워진 경우, 자동차는 장애물을 들이받을 수밖에 없기에 이런 장애물은 미리 제거하였습니다.

4. 재생산할 꼬마자동차 찾기
높은 점수를 받은 꼬마자동차를 찾아 재생산해야 '더 우수한 꼬마자동차'가 만들어질 수 있습니다. 재생산할 꼬마자동차를 찾는 가장 기본적인 방법은 룰렛법입니다. 룰렛법에 의하면 각자의 꼬마자동차가 선택될 확률은 그 꼬마자동차의 점수와 비례합니다.

function SelectParent()
.. // 모든 꼬마자동차의 점수 합 계산
.. totalscore := 0
.. for i := 0, 128 do
..... totalscore := totalscore + car[i].score
.. end

.. // 점수비율로 선택
.. rnd := Random(0:totalscore - 1)
.. for i := 0, 128 do
..... if car[i].score > rnd then
........ return car[i]
..... end
..... rnd := rnd -
car[i].score
.. end
.. error Not Selected
end

5. 교차
선택된 두 꼬마자동차를 복제하는 것만으로는 더 우수한 꼬마자동차를 얻을 수 없습니다. 꼬마자동차의 유전자를 섞어야 선택된 두 부모의 장점을 이어받은 꼬마자동차를 얻을수 있죠.
실제 생물계에서도 오른쪽 그림과 같이 DNA가닥의 교차가 일어납니다.
여기서는 가장 간단한 1점교차를 사용해 유전자를 혼합하기로 하겠습니다. 즉, 임의의 한 점을 골라 그 점을 중심으로 유전자를 바꾸는 것입니다.
이를테면

A : 02101001
B : 11022120
였던 유전자가 3번 위치에서 교차가 일어났을 경우
A : 11001001
B : 02122120
가 되는 것입니다.

procedure CrossOver(Car A, car B)
.. // 교차가 일어날 위치 결정
.. crosspoint = Random(1, 7)

.. // 실제 교차
.. for k = 0, crosspoint do
..... tmp = A.gene[k]
.....
A.gene[k] = B.gene[k]
..... B.gene[k] = tmp
.. end
end

한편 교차에는 위와 같은 일점교차뿐 아니라 2점교차 및 n점교차도 존재합니다. n점교차란 n개의 점을 선택해 자른 후 각각의 조각을 서로 바꾸는 교차입니다.
다음 유전자를
A : 02101001
B : 11022120
다음 유전 2, 5, 7점에서 3점교차를 한다면
A : 11101121
B :
02022000
와 같은 두 유전자가 만들어집니다.

마지막으로 랜덤교차는, 각각의 유전자 하나하나마다 일정확률을 적용시켜 교환할지 말지를 결정하는 교차방법입니다.
A : 02101001
B : 11022120
이것을 랜덤교차시킨다면
A : 11121021
B :
02002100
같이 보다 많이 뒤섞이게 됩니다.

6. 돌연변이
돌연변이는 임의의 비트를 전혀 다른 값으로 바꾸어버림으로써 새로운 정보를 만들고 유전자의 다양성을 증가시키기 위한 기본적인 방법입니다.
돌연변이 없이는 완전히 새로운 정보를 만들어낼 수도 없습니다(이 꼬마자동차는 처음 만들었을때 모든 유전자가 '직진'으로만 채워져 있기에 돌연변이 없이는 옆으로 이동이 불가능하죠). 그러나 돌연변이는 자칫하면 이미 잘 만들어진 유전자를 흐트러뜨릴 수도 있습니다(장애물을 잘 피해가던 꼬마자동차가 돌연변이에 의해 장애물을 들이받을 수도 있습니다). 그러므로 유전자알고리즘에서는, 돌연변이를 적용하더라도그 비율은 매우 낮게 적용합니다.
꼬마자동차의 경우에는 각 유전자 비트가 0.1%확률로 돌연변이를 일으키도록 설정했습니다.

procedure Mutantation(Car a)

.. for k = 0, 8 do
..... if Random(0, 999) == 0 then
........ a.gene[k] := Random(0, 2)
..... end
.. end
end

이렇게 새롭게 만든 128개의 꼬마자동차들을 원래의 배열로 옮기면 한 세대가 끝납니다. 이 새로운 세대를 대상으로 적응도테스트를 반복하면 점차 적응성이 높아지는 모습을 볼 수 있습니다.
다음은 실제로 유전자알고리즘을 돌린 결과입니다. 각 세대당 최고, 최저, 평균점수를 표시했습니다.

Generation 0 MaxScore:920 MinScore:690 AverageScore:810
Generation 1 MaxScore:890 MinScore:710 AverageScore:809
Generation 2 MaxScore:900 MinScore:720 AverageScore:802
Generation 3 MaxScore:930 MinScore:710 AverageScore:801
Generation 4 MaxScore:910 MinScore:720 AverageScore:809
Generation 5 MaxScore:910 MinScore:680 AverageScore:807
Generation 6 MaxScore:900 MinScore:710 AverageScore:804
............
Generation 999 MaxScore:1000 MinScore:830 AverageScore:935
Generation 1000 MaxScore:1000 MinScore:790 AverageScore:930
Generation 1001 MaxScore:980 MinScore:830 AverageScore:934
Generation 1002 MaxScore:990 MinScore:870 AverageScore:935
Generation 1003 MaxScore:990 MinScore:850 AverageScore:939
Generation 1004 MaxScore:1000 MinScore:840 AverageScore:935
Generation 1005 MaxScore:990 MinScore:840 AverageScore:934
Generation 1006 MaxScore:990 MinScore:840 AverageScore:938
Generation 1007 MaxScore:990 MinScore:850 AverageScore:935
Generation 1008 MaxScore:990 MinScore:860 AverageScore:937
Generation 1009 MaxScore:990 MinScore:810 AverageScore:938
Generation 1010 MaxScore:980 MinScore:870 AverageScore:933
Generation 1011 MaxScore:1000 MinScore:860 AverageScore:935
Generation 1012 MaxScore:990 MinScore:850 AverageScore:935
Generation 1013 MaxScore:980 MinScore:860 AverageScore:929
......

1000세대 이상 지난 상태입니다. 1000점짜리(모든 장애물을 완전하게 피한) 꼬마자동차가 나타났군요. 하지만 만점 꼬마자동차가 계속 유지되지 않습니다. 만점 꼬마자동차가 도태되었다가 다시 만들어지는 현상의 반복이군요.
다음번엔 이것을 한번 고쳐봅시다.