레이블이 로또인 게시물을 표시합니다. 모든 게시물 표시
레이블이 로또인 게시물을 표시합니다. 모든 게시물 표시

2016년 4월 16일 토요일

로또 관련 통계



위 그래프는 각 회차의 가장 작은 로또 당첨 번호의 분포이다.
5이하가 가장 많은 것을 알 수 있다.

로또를 뽑을 때는 일단 시작은 5이하의 숫자로 시작하라.
1이 가장 확률이 높긴하다. 


그럼 각 당첨 숫자간의 간격의 분포를 알아보자.

각 당첨 번호간의 간격은 다음과 같은 형태를 띄고 있다.




 1에서 5사이가 가장 많이 나타났다. 

그럼, 시작은 1-5사이로 시작하고,  간격은 1~5로 하는게 좋다.

그럼 궁금한 것은, 각 숫자 사이의 간격이 5이하로만 나타난 비중은 얼마나 되는가?

단 6번이다. 총 697 회중 6번만 5이하의 간격으로만 나타난다.

5이하의 간격이 전체적으로 많긴 하지만,

5이하의 간격으로만 번호를 구성하면 

당첨될 가능성이 매우 적어진다. 


그럼 적절한 번호간의 간격은 얼마 이하로 하는게 좋을까?





그래프에서 보듯이, 13정도가 좋다.

즉, 각 번호간의 간격은 최대 13으로 하라.

1~5 간격의 비중을 높이되, 최대 13까지를 꼭 포함시켜서

당첨번호를 구성한다.




그럼 지금까지의 통계정보를 바탕으로

바람직한 로또 번호의 예를 만들어 보겠다.

1. 시작은 1~5 

그 중에서도 1이 가장 비중이 높았으므로, 1을 선택.

2. 간격은 1~5로 하되 중간에 13의 간격을 넣는다. 

그럼, 간격을 [1, 2, 5, 8, 13] 로 해보자.

그럼 최종적인 로또 번호는

[1, 2, 4, 9, 17, 30]

이 된다.

마지막으로 이전 회차의 당첨번호와의 상관성을 살펴보자.

이전 회차와 동일한 당첨번호가 나온 경우의 수를 살펴보겠다.



이전 회차에서 나온 번호가 0번 혹은 1번 나온 경우가 가장 많으며 2개가 나오기도 한다.

그럼, 이번 주 로또를 사기 전에 지난 회차에서 나온 번호 중 하나를 고를지 말지 부터

선택하자. 고른다면 어떤 번호를 고르는게 가장 나을까?

여기까지 조사하고, 나머지 더 궁금한 것은 다음에 조사하기로 한다.

위와 같이 고르더라도, 확률이 높아져도 여전히 굉장히 낮은 확률임을 명심.
















2016년 3월 25일 금요일

데이터 사이언티스트 로또를 논하다.

지금까지 총 692회 추첨한 로또.

가설을 세워본다. 여기엔 미신+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를 구하자.

그렇게 얻은 오늘의 예측 번호는

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천원 당첨까지는 될 수도 있지 않을까.