가설을 세워본다. 여기엔 미신+SF 적인 상상력과 기계학습을 덧붙여 본다.
사실 로또는 완전 랜덤이다.
데이터 분석에 의해 어떤 상관관계나 패턴을 찾기는 힘들 것이라 생각한다.
그런데 만약...
로또 추첨일자와 당첨번호간에 어떤 전 우주적인 함수가 존재한다면?
그럼, 이번 주 추첨일자를 함수의 X 에 넣으면 당첨번호 Y 가 나올 것이다.
단순한 추첨일자뿐 아니라 해당 일이 가지는 동양 성리학적 수치를 가지고 함수를
구성하면 더 확률이 높아지지는 않을까?
마치 엉터리 연금술사처럼 여기저기 데이터를 끌어다가 섞어서
함수를 만들어서 각각의 당첨률을 비교해보고자 한다.
일단 입문의 의미로 가장 단순하게 구현할 수 있는 추첨회차와 당첨번호간의 함수를 찾아보자.
그럼, 일단 다음과 같이 데이터를 준비해보자.
| 추첨일자 (예:2016.03.10) |
추첨회차 (예:320회) |
당첨번호 (예: 1,6,10,21, 30, 34,44) |
총 692회의 데이터 셋을 준비하도록 한다. 크롤링을 이용해서 준비완료.
우리는 유전 알고리즘을 이용해서 추첨회차와 당첨번호간의 함수를 구할 것이다.
어떤 언어와 라이브러리를 써야 가장 쉽게 이 문제를 풀 수 있을까?
R, Python이 데이터 분석의 대세지만, Java 를 선택해서 하기로 한다.
JGap이란 것을 써보자.
<dependency>
<groupId>net.sf.jgap</groupId>
<artifactId>jgap</artifactId>
<version>3.4.4</version>
</dependency>
구글 검색하다가 2013년에 내가 다른 블로그에 썼던 글을 찾았다;;;
헐..내가 썼던 건데도 잊고 있었고...
꽤나 열심히 썼던 거 같다. 정확하게 3년 전의 일이네.
http://mrldk.blogspot.kr/2013/04/genetic-algorithm-in-java-using-jgap.html
잊었던 유전 알고리즘의 원리를 상기시켜주었다.
과거의 나에게 땡큐.
아직도 유효한지 모르겠지만 당시 내가 정리했던 샘플 코드이다.
지금 나는 마치 과거의 나와 대화를 하는 느낌이다. 드라마 시그널의 느낌이다.
int chromeSize = 6; int numEvolutions = 10; Configuration gaConf = new DefaultConfiguration(); IChromosome sampleChromosome = new Chromosome(gaConf, new BooleanGene(gaConf), chromeSize); gaConf.setSampleChromosome(sampleChromosome); gaConf.setPopulationSize(20); gaConf.setFitnessFunction(new MaxFunction()); Genotype gType = Genotype.randomInitialGenotype(gaConf); for (int i = 0; i < numEvolutions; i++) { gType.evolve(); } IChromosome fittest = gType.getFittestChromosome();
과거의 당첨기록을 바탕으로 가장 성적이 좋은 랜덤 제너레이터를 얻을 수 있겠는가?
먼저 랜덤 제너레이터는 seed를 받는다.같은 seed에 대해서는 동일한 값을 내뱉는다.
이 seed에 대한 식을 세우면 되겠다.
long seed = lotto_xth * A1 + lotto_nth * A2 + A3
유전 알고리즘 통해서 우리가 얻기 원하는 것은 [A1, A2, A3] 이다.
각 유전자에 대한 평가 함수(Fitness Function)는 저 [A1, A2, A3] 를 바탕으로 구한 seed로
과거 692회에 대하여
6자리 맞히면 100점
5자리 맞히면 80점
4자리 맞히면 50점
3자리 맞히면 25점
을 주게 하였다.
2시간 가량의 코딩 끝에 프로그램을 완성하였다.
10번의 세대 evolution 을 하게 하고 구한 랜덤제너레이터의 점수는 4240점이었다.
이것의 의미를 좋게 해석하면 692회 중 42번을 1등을 하는 예측기인 셈이다.
그러나, 3자리만 열심히 맞춰서 점수를 따온 건지도 모른다.
어쨌든 이 예측기를 따라서 꾸준히 600번 로또를 구매하면 그 중 40번 이상은 당첨을 한다고
기대할 수 있다. (5등이든 1등이든 말이다)
evolution횟수를 늘리고 중간에 세대를 추가해주는 코드를 넣으면서 반복해서 수행해보니
계속 신기록을 경신하고 있다.
100000회 evolution 하게 하고 중간 출력물을 보며 기다리고 있자니 영화 매트릭스 3의
한 장면이 떠오른다.
Neo가 갖은 고생 끝에 찾아간 매트릭스의 설계가(Architect)와 만나는 장면이다.
파이프 담배를 여유있게 피며, 설계가가 Neo에게 하는 말.
"흥미롭군. 지금까지 온 세대 중 가장 빨러"
Neo는 죽을 똥 살 똥 찾아갔는데, 아키텍트에게는 그저
수없이 반복되는 세대 속에서 가장 우수한 결과를 내놓는 염색체에 불과한 것이다.
그리고 그 가장 우수한 염색체는 매번 수행마다 서로 다른 결과를 내놓으니 흥미있지 않을 수가 없다.
나도, 반복적으로 수행을 하면서 새로운 결과를 내놓는 것이 재밌기만 할 뿐이다.
그 안에서는 교배, 도태, 진화, 돌연변이 등 몇 십억년의 역사가 흐르고 있을 것이다.
자 글을 쓰는 사이에 결과가 나왔다.
자 글을 쓰는 사이에 결과가 나왔다.
IntegerGene(0,2147483647)=1577371700
IntegerGene(0,2147483647)=116415628
IntegerGene(0,2147483647)=885721769
이걸로
long seed = lotto_xth * A1 + lotto_nth * A2 + A3
식에 적용하면
long seed = lotto_xth * (1577371700) + lotto_nth * (116415628) + 885721769
가 나온다.
그럼 오늘은 로또 693 회니까
long seed = 693 * (1577371700) + lotto_nth * (116415628) + 885721769
를 통해서 6개의 숫자에 대한 seed를 구하자.
그렇게 얻은 오늘의 예측 번호는
식에 적용하면
long seed = lotto_xth * (1577371700) + lotto_nth * (116415628) + 885721769
가 나온다.
그럼 오늘은 로또 693 회니까
long seed = 693 * (1577371700) + lotto_nth * (116415628) + 885721769
를 통해서 6개의 숫자에 대한 seed를 구하자.
그렇게 얻은 오늘의 예측 번호는
17, 7, 8, 11, 19, 21
이다. 근데 굉장히 불안한게...왜 다 숫자가 21 이하야....;;;
그래도 처음 얻은 결과니 무조건 오늘 구매를 해보자...!
이왕 사는 거 3장은 사야겠지.
몇 번 더 수행해보자.
점수 6200.0 의 26, 14, 38, 32, 13, 41
점수 6465.0 의 34, 18, 6, 1, 34, 19
코드를 담습니다.
Lotto Class : 기존 당첨번호를 저장하기 위한 클래스
class Lotto { Integer year; Integer month; Integer day; Integer nth; Integer[] nums; public Lotto(Integer year, Integer month, Integer day, Integer nth, Integer n1, Integer n2, Integer n3, Integer n4, Integer n5, Integer n6) { this.year = year; this.month = month; this.day = day; this.nth = nth; this.nums = new Integer[6]; this.nums[0] = n1; this.nums[1] = n2; this.nums[2] = n3; this.nums[3] = n4; this.nums[4] = n5; this.nums[5] = n6; } }
initLotto 함수 : 기존 당첨번호를 list 에 저장
private List<Lotto> initLotto() { List<Lotto> lottos = new ArrayList<Lotto>(); lottos.add(new Lotto(1, 2002, 12, 7, 10, 23, 29, 33, 37, 40)); lottos.add(new Lotto(2, 2002, 12, 14, 9, 13, 21, 25, 32, 42)); lottos.add(new Lotto(3, 2002, 12, 21, 11, 16, 19, 21, 27, 31)); lottos.add(new Lotto(4, 2002, 12, 28, 14, 27, 30, 31, 40, 42)); lottos.add(new Lotto(5, 2003, 1, 4, 16, 24, 29, 40, 41, 42)); lottos.add(new Lotto(7, 2003, 1, 18, 2, 9, 16, 25, 26, 40)); lottos.add(new Lotto(8, 2003, 1, 25, 8, 19, 25, 34, 37, 39)); return lottos; }
LottoFitness : 각 염색체를 평가하는 함수: 기존 당첨번호 기반으로 평가
class LottoFitness extends FitnessFunction { List<Lotto> lottos; public void setLottos(List<Lotto> lottos) { this.lottos = lottos; } @Override protected double evaluate(IChromosome a_subject) { Gene gene1 = a_subject.getGene(0); Gene gene2 = a_subject.getGene(1); Gene gene3 = a_subject.getGene(2); double score = 0.0; for (Lotto lotto : lottos) { List<Integer> randNums = new ArrayList<Integer>(); for (int j = 1; j <= 6; j++) { long seed = (lotto.nth * (Integer)gene1.getAllele() + j * (Integer)gene2.getAllele() + (Integer)gene3.getAllele()); Double randomValue = new Random(seed).nextDouble() * 45; Integer value1 = randomValue.intValue(); randNums.add(value1); } double result = checkResult(randNums, lotto.nth); score = score + result; } //System.out.println(score); return score; } private double checkResult(List<Integer> randNums, int nth) { Lotto lotto = lottos.get(nth-1); int match = 0; for (Integer randnum : randNums) { for (int i = 0; i < lotto.nums.length; i++) { if (randnum.equals(lotto.nums[i])) { match++; break; } } } if (match == 6) return 100.0; else if (match == 5) return 80.0; else if (match == 4) return 50.0; else if (match == 3) return 25.0; return 0.0; //System.out.println(randNums.get(0) +"/" + randNums.get(1) +"/" + randNums.get(2) +"/" + randNums.get(3) +"/" + randNums.get(4) +"/" + randNums.get(5)); } }
Main Function : 과거 로또당첨기록을 메모리에 올리고 3개의 숫자로 구성된 염색체를 정의한 후, 세대의 population은 20으로 하고
100000번의 진화 iteration을 돈다.
List<Lotto> lottos = initLotto(); Configuration gaConf = new DefaultConfiguration(); IntegerGene integerGene = new IntegerGene(gaConf, 0, Integer.MAX_VALUE); IChromosome sampleChromosome = new Chromosome(gaConf, integerGene, 3); gaConf.setSampleChromosome(sampleChromosome); gaConf.setPopulationSize(20); LottoFitness fit = new LottoFitness(); fit.setLottos(lottos); gaConf.setFitnessFunction(fit); Genotype gType = Genotype.randomInitialGenotype(gaConf); Double maxValue = 0.0; for (int i = 0; i < 100000; i++) { System.out.println(i+"th evolution..."); gType.evolve(); if (i % 100 == 0) { gType.fillPopulation(1); } //System.out.println(gType.getFittestChromosome().getFitnessValue()); } IChromosome fittest = gType.getFittestChromosome(); System.out.println(fittest.getFitnessValue()); System.out.println(fittest.getGene(0) + " " + fittest.getGene(1) + " " + fittest.getGene(2)); predict((Integer)fittest.getGene(0).getAllele(), (Integer)fittest.getGene(1).getAllele(), (Integer)fittest.getGene(2).getAllele());
* 4월 1일의 기록
그래서 695회 로또를 나는
7, 8, 11, 17, 19, 21 (0개 맞춤)
13, 14, 26, 32, 38, 41 (2개 맞춤)
1, 6, 18, 19, 34, 41(2개 맞춤)
3, 22, 25, 26, 29, 45(1개 맞춤) 를 구매했고
당첨번호는 4, 18, 26, 33, 34, 38 이라
2개씩 맞힌 게 2번 나왔으나
결국 다 꽝이다.
4천원 날림.
그러나 재미는 있었다.
꾸준히 하다 보면 5천원 당첨까지는 될 수도 있지 않을까.
댓글 없음:
댓글 쓰기