본문 바로가기
수업 공유 자료/AI, 딥러닝 주제

유전 알고리즘 3.유전 알고리즘으로 이미지 훈련하기

by Majestyblue 2024. 1. 29.

이전 시간에는 적합도의 개념을 이용하여 3개의 숫자들의 합이 20이 되게 훈련하였다. 이번에는 유전 알고리즘을 이용하여 이미지를 원하는 대로 만들어 보자!

 

1. 특정 이미지가 만들어지게 하기

카드의 숫자를 유전정보라고 할 때 유전 객체를 [3, 5, 7] 이런식으로 표현했다. [3, 5, 7]1차원 배열이라고 한다. 일반적으로 컴퓨터에서 그려지는 이미지는 2차원 배열인데 2차원 배열을 이용하여 특정 이미지가 그려지도록 훈련할 수 있다.

 

3차원 배열도 있다. 여러분이 생각하는 가로, 세로, 높이로 생각해도 좋다.

 

 

 

2. 생성, 자연선택, 돌연변이 코드 구현하기

1) 목표 설정

import numpy as np # 넘파이 모듈 임포트
np.random.seed(220329) # 랜덤 시드 생성

# 학습 목표(target) 설정
target = np.array([[1, 1, 1, 1],
                   [1, 0, 0, 1],
                   [1, 0, 0, 1],
                   [1, 1, 1, 1]])
print(target.shape) # (4, 4)

이번 유전알고리즘의 목표는 target이 되도록 객체를 만드는 것이다.

 

 

2) 유전 객체 생성

def create_genome():
  return np.random.choice((0, 1), (4, 4))

print(create_genome()) # shape은 (4, 4)

2 np.random.choice((0, 1), (4, 4)) : 01로 이루어진 행렬을 44열로 랜덤하게 만든다 라는 뜻이다.

 

 

 

3) 1세대 유전자 객체 4개 생성

#1세대 유전자 객체 4개 생성
gen_list = create_genome()
for i in range(3):
  value = create_genome()
  gen_list = np.vstack((gen_list, value)) #(16, 4), 2차원 배열을 그냥 이어붙임

print(gen_list) # (4, 4, 4) # 2차원 배열을 3차원 배열로 변환
gen_list = np.reshape(gen_list, (-1, 4, 4)) 

print(gen_list)

코드설명

2 gen_list : (4, 4)01로 이루어진 객체를 하나 생성한다.

3 3번 반복한다. (왜 이렇게 밖에서 한번 생성하냐면 vstackshape이 다르면 결합X)

4 gen_list와 결합할 새로운 객체 value를 생성

5 vstack을 이용하여 객체를 쌓는다. stack은 쌓는다 라는 뜻이고 vvertical(세로)줄임말이다. i = 0

gen_list(4, 4), value(4, 4) 이므로 세로로 쌓게 되면 (8, 4)가 된다. 이를 3번 반복하면 (16, 4)가 된다.

 

 

4) 적합도 정의해 보기

적합도를 아래와 같이 정의해 보자.

# 적합도 계산해 보기
appro = np.abs(target-gen_list[0])
print(appro)
appro = np.sum(appro)
print(appro)

print(np.sum(np.abs(target-target)))

코드설명

2 목표 객체와 gen_list의 첫 번째 객체(gen_list[0])의 차이를 절대값으로 씌운다.

4 각 요소들 끼리 합한다. 배열이 아닌 숫자가 출력

7 target에 대한 적합도를 확인해 본다.

 

 

 

5) 객체 4개에 대한 적합도 계산

# 적합도 계산
def Appropriate(list_gen):
  appro_list = np.array([])
  for gen in list_gen:
    appro = np.array([np.sum(np.abs(target-gen))])
    appro_list = np.concatenate([appro_list, appro], axis=0)
  return appro_list

print(Appropriate(gen_list))

이전과 같은 원리로 적합도를 계산 한다.

 

 

 

6) 객체 4개에 대한 적합도 계산

# 개체 중 적합도가 가장 작은 2개의 객체 선택하기
def Select_appropriate(list_gen):
  list_appro = Appropriate(list_gen) # 적합도 배열 
  argsort = list_appro.argsort() # 적합도 배열 인덱스 오름차순 정렬
  select_one = list_gen[argsort[0]] # 적합도가 가장 작은 객체
  select_two = list_gen[argsort[1]] # 적합도가 두 번째로 작은 객체
  two_genlist = np.vstack((select_one, select_two)) # 적합도 객체 쌓기 (8, 4)
  two_genlist = np.reshape(two_genlist, (-1, 4, 4)) # shape 재정의 (2, 4, 4)
  return two_genlist

print("유전 객체 4개")
print(gen_list)
print("각 적합도 계산")
print(Appropriate(gen_list))
genlist_two_origin = Select_appropriate(gen_list)
print("적합도가 가장 작은 2개 선택")
print(genlist_two_origin)

 

 

 

7) 감수 분열을 이용한 교차 구현

감수 분열을 하면 행 1-2(인덱스 0, 1) , 3-4(인덱스 2, 3)로 분열된다. 서로 교차할 물질은 행 3-4번 끼리 서로 교체함.

# 유전자 객체 2개 교차, 밑의 2개 행을 서로 교차한다.
def intersect_genorm(list_gen):
  temp_list = np.vstack((list_gen[0][2:4], list_gen[1][2:4]))
  copy_list = genlist_two_origin.copy()
  copy_list[0][2:4] = temp_list[2:4]
  copy_list[1][2:4] = temp_list[0:2]
  return copy_list

print("두번째 자리 교차 전")
print(genlist_two_origin)

intersect_gen = intersect_genorm(genlist_two_origin) # 두 번째 자리 교차
print("두번째 자리 교차 후")
print(intersect_gen)

코드설명

3 temp_list : 감수 분열된 객체가 vstack으로 쌓임 (2, 2) , (2, 2) vstack (4, 4)

temp_list 1~2: select_one3~4, / temp_list 3~4: select_two3~4

4 부모 객체를 복사하여 자식 개체로 사용하자.

5 copy_list[0][2:4]은 원래 select_one3~4행임. 이를 temp_list3~4행 즉 selct_two3~4 행으로 교환

5 copy_list[1][2:4]은 원래 select_two3~4행임. 이를 temp_list1~2행 즉 selct_one3~4 행으로 교환

 

 

 

8) 확률 구현하기

파이썬에서는 확률을 어떻게 표현할까?

# 확률 구현, 0만 뜨면 여러번 실행해 보자
prob = 0.3
for i in range(10):
  event = np.random.choice((0, 1), p=[1-prob, prob])
  print(event)

코드설명

prob = 0.3으로 정의하면

4- (0, 1), p=[1-prob, prob]에서 코드를 실행했을 때 0을 선택할 확률은 1-0.3 = 0.7, 1을 선택할 확률은 0.3 이다.

p는 각 수를 선택할 확률을 의미하며 p를 구성하는 배열의 합은 1이 되어야 한다.

 

 

 

9) 돌연변이 구현하기

조금 더 복잡한 돌연변이를 구현해 보자.

# 확률요소와 조금 더 복잡한 돌연변이가 가능하다.
def Mutation(list_gen, prob=0.5):
  event = np.random.choice((0, 1), p=[1-prob, prob])
  if event == True:
    mutant_list = np.random.choice((0, 1), (4, 4))
    copy_list = genlist_two_origin.copy()
    high_low = np.random.choice((0, 1), p=[0.5, 0.5])
    if high_low == True:
      copy_list[0][0:2] = mutant_list[0:2]
      copy_list[1][0:2] = mutant_list[2:4]
    else:
      copy_list[0][2:4] = mutant_list[0:2]
      copy_list[1][2:4] = mutant_list[2:4]
    return copy_list
  else:
    return list_gen

print('before Mutation : 정상적인 교차로 인한 자식 생성')
print(intersect_gen)
print('after Mutaion : 자식 생성 중 돌연변이 발생')
print(Mutation(genlist_two_origin))

 

코드설명

3 1이 선택될 때 event = 1

4 event = 1일 때 조건이 성립되어 if문으로 진입

5 mutant_list = 돌연변이 리스트 01로 이루어진 (4, 4) 생성

7 돌연변이 다양성을 위해 위의 2행을 변화시킬지, 아래 2행을 변화시킬지 정하는 랜덤 함수. hight_low = 1이면 위 2행을 변화시키고, high_low = 0 이면 아래 2행을 변화시킨다. 위와 아래는 동전 뒤집기 확률로 0.5씩 각각 고정해두었다.

 

 

10) 객체 2+ 객체 2개 구현하기

파이썬에서는 확률을 어떻게 표현할까?

# 4개의 객체 중 적합도가 가장 작은 유전 객체 2개 + 유전 객체 2개 교차 구현한 것 합치기
def combine_genome(list_1, list_2):
  return np.concatenate((list_1, list_2))

combine_gen = combine_genome(genlist_two_origin, intersect_gen)
print("1세대 두번째 자리 교차 전 + 교차 후")
print(combine_gen)

 

 

 

 

11) 반복 훈련을 위한 객체 4개 생성하기

# 1세대 유전체 객체 4개 생성
gen_list = create_genome()
for i in range(3):
  value = create_genome()
  gen_list = np.vstack((gen_list, value))

genlist_four = np.reshape(gen_list, (-1, 4, 4))
genlist_two_origin= Select_appropriate(genlist_four)
print('genlist_two_origin')
print(genlist_two_origin)

 

 

 

 

12) (최종) 반복 훈련하기

epochs = 3000
for epoch in range(epochs):
  # 부모 세대가지 합쳐 4개 중 적합도가 가장 큰 2개 선택
  genlist_two= Select_appropriate(genlist_four) 
  # 유전자 교차(자손 번식)
  intersect_gen = intersect_genorm(genlist_two) 
  # 돌연변이 진입, 조건이 해당하면 돌연변이 발생
  intersect_gen = Mutation(intersect_gen, prob=0.2) 
  # 부모세대와 자식세대를 합쳐 4개로 만듦
  genlist_four = combine_genome(genlist_two, intersect_gen) 

genlist_last_two= Select_appropriate(genlist_four)
print('genlist_last_two')
print(genlist_last_two)

 

 

 

원하는 모양에 거의 가깝게 출력된 것을 알 수 있다.

 

 

 

2. 전체 코드

GeneSimulator-2.ipynb
0.02MB