안녕하세요. 호랑인 입니다.
오늘은 한 가지 좋은 소식과 한 좋은 소식을 가지고 왔는데요, 하나씩 말씀드리도록 하겠습니다.
우선, 좋은 소식부터 말씀드리자면, 야찌 게임의 AI 를 다 만들었습니다! 그냥 일반적인 유전자 알고리즘 프로그램이기 때문에 그렇게 오래걸리진 않은 것 같습니다. 제가 좀만 더 덜 멍청했더라면 어제 끝낼 수 있었을 텐데 아쉽네요... ㅠㅜ
나쁜 소식을 말씀드리자면, 실제로 학습을 시켜봤지만, 실력이 늘지 않습니다... 정말 치명적인 문제죠. 지금 원인 발견 및 해결을 위해 노력하고 있습니다. (사실 앞서 만들어 놓은 계획이 대충 만든 것이기 때문에 실제로 학습속도가 매우 느리거나 불가능할 수도 있습니다.) 하지만 그때에는 그냥 하드 코딩으로 언제든지 갈아타면 되는 것이기 때문에, 매우 마음이 아프지만 우선 만든 과정을 소개해 드릴까 합니다.
우선, 큰 그림을 그려봅시다.
YahtzeePlayer 라는 object와 YahtzeeLearner 라는 object, 두 개를 만들 예정입니다.
큰 아이디어는 아래의 github에서 얻었습니다만, 실제로 만들어보니 많이 다른 상황이었기 때문에 다 만들어야 했습니다.
http://www.obitko.com/tutorials/genetic-algorithms/ga-basic-description.php
일단 YahtzeeLearner를 한번 볼까요? 이 object는 YahtzeePlayer를 발전시킬 것입니다.
따라서 player를 input으로 받는 것으로 시작해야겠죠?
마지막에 위와 같은 코드를 사용해서 돌릴 것이기 때문에, Learner에도 run 함수를 만들어 줍시다.
제가 만든 run 함수입니다. 이걸 이해하기 위해 자료구조를 설명해드리도록 하겠습니다.
보면, self.player.Chromo라는 변수가 있습니다. 이것이 gene pool 이라고 생각하시면 되겠습니다. Chromo를 대문자로 쓴 것은 Chromo 내부에 있는 gene 하나하나가 chromosome 이라는 변수의 형태로 저장되어 있기 때문입니다. chromosome은 각각 854 개의 숫자를 갖고 있는 numpy array입니다. 이중에서 853개는 실제로 계산에 필요한 데이터이고 마지막 854번째 칸은 점수를 보관할 공간입니다. 야찌 게임은 다행이도 게임의 점수가 int로 나오기 때문에 굳이 float array를 사용하지 않아도 돼 효율에 약영향을 미치지 않을 것 같습니다.
자 그러면 위의 코드를 뜯어봅시다....
1. self.plater.fitness() 라는 것은 훗날 정의할 함수입니다. 매우 간단한 함수인데요, 각 chromosome 을 사용해서 야찌를 플레이하고, 그 점수를 각 chromosome의 마지막 index에 저장해 주는 함수입니다.
2. population = self.player.Chromo 이는 player의 gene pool을 가지고 옵니다.
그 다음 4 줄은 그냥 print 관련된 함수들이니, 넘어가겠습니다.
3. if self.player.check_stop(): break 이거는 몇 번 이상 돌지 말라는 의미입니다. 제가 프로그램을 돌려놓고 나서 끄는 걸 잊는 경우가 많아 이를 방지하기 위해 만들어놨습니다.
4. population = self.next_generation() 가장 중요한 줄입니다. 다음 population을 만들어야죠. 이 함수에 대한 설명이 바로 연결될 것입니다.
어떻게 next_generation을 만들까요?
우선 코드를 가지고 와 봤습니다. 천천히 봅시다.
우선, parents generator를 만들었습니다. 사실 저는 원래 for generator는 편리해서 자주 쓰는데에 비해 yield를 사용한 코딩은 많이 하지 않지만, 위에 올려 놓은 github 코드에서 yield를 사용했고, 저 또한 yield 없이 코딩을 해보았으나, 이 방법이 더 마음에 들어 저 부분을 따라하게 되었습니다.
next_population 이라는 빈 list를 하나 만들고,
이 list의 크기가 정해놓은 크기 이하인 동안, parents를 만들고, crossing이 일어날지, mutation이 일어날지 여부를 random.random()으로 결정해서 child chromosome을 만들고, 모두 next_population에 넣으면 됩니다.
보통의 경우 코딩을 할때 list의 최대 크기는 짝수로 지정해 놓겠지만(보통 끝자리를 0으로 맞추니까요) 혹시 모를 경우를 대비해 정해놓은 size까지만 사용하는 코드가 마지막에 추가됩니다.
여기까지가 YahtzeeLearner object에 대한 설명입니다.
그러면 이제 YahtzeePlayer를 볼까요? 위에서 보면 알 수 있듯이, 상당히 많은 함수들을 갖고 있어야 합니다.
우선 위에서 이름이 한번씩은 불려진 한수들의 신제 모습입니다.
parents는 우선 무시하고, 나머지 세개를 봅시다.
crossover는 생물시간에 배웠겠지만, 두 chromosome이 겹치면서 서로의 정보를 나눠 갖는? 형태의 변화를 의미합니다. 자세한건 검색해보시기 바랍니다.
결국 중요한 것은 A와 B의 유전자가 아래와 같이 된다는 것입니다.
AAAAAAAAA => AAAABBAAA
BBBBBBBBBB => BBBBBAABBB
따라서 랜덤 index 두개를 잡고 치환해주면, 끝입니다. 두 값이 같으면 어떻게 되는가? 운이 없게도 crossing 이 일어나지 않는 거죠. (1/853의 확률입니다)
mutation은 말 그대로 랜덤 index의 값에 랜덤 int를 더하는 것입니다.
check_stop은 Iteration 횟수가 정해 놓은 것을 넘는지 확인하는 한수입니다. 이게 True 면, 프로그램은 종료합니다.
이게 parents를 만드는 코드입니다. generator를 return 하는 함수죠. Generator에서 일어나는 귀찮은 일들을 모두 없애기 위해 while True문을 넣었습니다. 이 generator가 일을 그만두어야 하는 조건이 없기 때문이죠. 그러면 tournament가 뭐냐 하면 위를 보시면 됩니다.
간단하게 두 수컷을 싸움 붙여서 꼬리에 있는 점수 라벨로 싸우고, 이긴 사람이 아빠가 되며, 이는 암컷 둘에도 동일하게 적용됩니다.
이게 끝입니다. 남은 건 저 853개의 숫자 형태 chromosome과 야찌 게임과 무슨 상관이 있냐에 대한 문제가 남았군요.
제일 간단한 부분입니다. input이 19개 였고, hidden이 10개면, 그냥 19*10의 matrix 연산을 하면 됩니다. (bias에 대한 설명은 생략하겠습니다.)
이렇게 할때 총 필요한 숫자의 개수를 계산해 보면, 853개가 나오는 것입니다.
여기 코드는 나름 더럽기 때문에 생략하도록 하겠습니다. 정말 궁급하면 댓글 달아주시거나, 이메일 주세요.
자... 이제 코딩이 끝났습니다. 돌리면 됩니다. 근데 앞서 말씀드렸다 싶이, 얘가 점수가 안 올라요 ㅠㅜ.... 이 문제는 해결되는 데로 다시 포스팅을 하도록 하겠습니다.
많은 관심 바랍니다.
'프로젝트 > 프로젝트 [야찌(Yahtzee)]' 카테고리의 다른 글
[야찌(Yahtzee)] AI의 문제 (2) | 2018.02.04 |
---|---|
[야찌(Yahtzee)] 게임 구현 (0) | 2018.02.01 |
[야찌(Yahtzee)] 기본 아이디어 (0) | 2018.01.31 |
[야찌(Yahtzee)] 야찌에 대한 소개 (2) | 2018.01.30 |