본문 바로가기
p5.js

[p5.js/The Nature of Code] 0. Randomness(무작위성) - 0.3 Probability and Nonuniform Distributions (확률과 비균등 분포)

by the_cat_Soy 2024. 4. 13.

차례

 

Nature of Code

 

natureofcode.com

 

Probability and Nonuniform Distributions (확률과 비균등 분포)

 디자인 문제에 있어 균일한 무작위성은 종종 가장 사려 깊은 해결책이 아닐 수 있습니다. 특히 유기적이거나 자연스러운 시뮬레이션을 구축하는 문제에 있어서는 그렇습니다. 그러나 몇 가지 기교를 사용하면 random() 함수가 균일하지 않은 확률 분포를 생성할 수 있습니다. 이러한 유형의 분포는 더 흥미로운, 자연스러운 결과를 얻을 수 있습니다.

달리 말하면, 이 책에서는 자연에서 볼 수 있는 것들을 모방한 시스템을 구축하려고 합니다. 때로는 조금 비뚤어진 관점으로 문제를 바라보아야 합니다.

 

 예를 들어, 제 9장에서 유전 알고리즘을 다룰 때, 선택을 수행하는 방법이 필요합니다. 어떤 개체가 다음 세대에 그들의 DNA를 전달할 수 있는가? 이것은 생존자만이 번식할 수 있는 다윈적인 자연 선택 개념과 유사합니다. 진화하는 원숭이 모집단을 가정해 보겠습니다. 모든 원숭이가 동일한 번식 기회를 가지고 있지는 않습니다. "적합한" 원숭이들이 더 많이 선택되어야 합니다. 이것을 적합도의 확률이라고 볼 수 있습니다.

 

 여기서 확률의 기본 원칙을 살펴보고 이후에 코드 예제에 더 정확한 용어를 적용해 보겠습니다. 먼저 단일 사건 확률부터 시작해보겠습니다. 단일 사건 확률은 주어진 사건이 발생할 가능성입니다. 확률에서 결과는 무작위 과정의 모든 가능한 결과를 나타내며, 사건은 특정 결과나 결과의 조합을 나타냅니다.

 

 모든 결과가 다른 결과만큼 발생할 가능성이 있는 시나리오의 경우, 주어진 사건이 발생할 확률은 해당 사건과 일치하는 결과의 수를 모든 잠재적 결과의 총 수로 나눈 것입니다. 동전 던지기가 간단한 예입니다. 동전의 가능한 결과는 앞면이나 뒷면 뿐입니다. 앞면은 하나뿐이므로 동전이 앞면이 나올 확률은 1을 2로 나눈 것이며, 1/2 또는 50%입니다.

 

카드 덱이 52장 있다고 가정해 보겠습니다. 그 덱에서 에이스를 뽑을 확률은 다음과 같습니다:

에이스 수 / 카드 수 = 4 / 52 = 0.077 ≈ 8%

 

다이아몬드를 뽑을 확률은 다음과 같습니다:

다이아몬드 수 / 카드 수 = 13 / 52 = 0.25 = 25%

 

또한 각 사건의 개별 확률을 곱하여 여러 사건이 연속적으로 발생할 확률을 계산할 수 있습니다. 예를 들어, 연속해서 세 번의 동전 던지기에서 앞면이 세 번 나올 확률은 다음과 같습니다:

(1/2) × (1/2) × (1/2) = 1/8 = 0.125 = 12.5%

 

이는 동전이 평균적으로 여덟 번 중 한 번은 세 번 연속으로 앞면이 나올 것을 나타냅니다. 동전을 연속해서 세 번 던져 500번 반복한다면, 평균적으로 여덟 번 중 한 번은 세 번 연속으로 앞면이 나올 것으로 예상됩니다. 즉, 약 63번입니다.

 

Exercise 0.2
52장의 카드 덱에서 연속해서 두 개의 에이스를 뽑을 확률은 무엇입니까? 첫 번째 뽑은 후에 카드를 다시 섞지 않으면 그 확률은 어떻게 됩니까?

 

확률의 개념을 코드에 적용하는 방법 중 하나는 숫자가 반복되는 배열을 채우고 그 배열에서 무작위 요소를 선택한 다음 그 선택에 기반하여 이벤트를 생성하는 것입니다:

let stuff = [1, 1, 2, 3, 3];
//1과 3은 두 번씩 배열에 저장되어 있으므로, 이들이 선택될 확률이 더 높습니다.

let value = random(stuff);
//배열에서 무작위 요소를 선택합니다.

print(value);

 

 다섯 개의 멤버를 가진 배열에는 두 개의 1이 있으므로, 이 코드를 실행하면 값 1이 출력될 확률이 2/5, 즉 40%입니다. 마찬가지로, 값 2가 출력될 확률은 20%, 값 3이 출력될 확률은 40%입니다.

 또한 무작위 수 (간단하게 0에서 1까지의 무작위 부동 소수점 값만 고려합시다)를 요청하고 무작위 수가 특정 범위 내에 있는 경우에만 이벤트가 발생하도록 할 수도 있습니다. 예를 들어:

let probability = 0.1;
let probability = 0.1;
//10%의 확률

let r = random(1);
//0부터 1까지의 무작위 부동 소수점

if (r < probability) {
print("Sing!");
}
//무작위 수가 0.1보다 작으면 노래합니다!

 

0부터 1까지의 부동 소수점 중 10%는 0.1보다 작으므로, 이 코드는 노래를 10%의 확률로만 합니다.

여러 결과에 다른 가중치를 적용하려면 동일한 접근 방식을 사용할 수 있습니다. 예를 들어, 노래가 발생할 확률을 60%, 춤을 출 확률을 10%, 잠자기를 할 확률을 30%로 설정하고 무작위 수를 0부터 1까지 선택하여 어디에 해당하는지를 확인할 수 있습니다:

 

0.0에서 0.6 (60%) → 노래 0.6에서 0.7 (10%) → 춤 0.7에서 1.0 (30%) → 잠자기

 

let num = random(1);
if (num < 0.6) {
  print("Sing!");
  //만약 무작위 숫자가 0.6보다 작으면
} else if (num < 0.7) {
  print("Dance!");
  //0.6과 같거나 크고 0.7보다 작으면
  } else {
  print("Sleep!");
  //나머지 케이스들
}

 

 

이제 이 방법을 사용하여 무작위 워커가 특정 방향으로 이동하는 경향을 갖도록 만들어 보겠습니다. 다음은 다음과 같은 확률을 갖는 Walker 객체의 예입니다:

위로 이동할 확률: 20%

아래로 이동할 확률: 20%

왼쪽으로 이동할 확률: 20%

오른쪽으로 이동할 확률: 40%

 

예제 0.3: 오른쪽으로 이동하는 Walker

step() {
  let r = random(1);
  if (r < 0.4) {
    this.x++;
  }
  else if (r < 0.6) {
    this.x--;
  }
  else if (r < 0.8) {
    this.y++;
  }
  else {
    this.y--;
  }
}

 

이 기술의 또 다른 일반적인 사용법은 코드에서 이벤트가 간헐적으로 발생하는 확률을 제어하는 것입니다. 예를 들어, 일정 시간 간격 (매 100프레임마다)으로 새로운 무작위 워커를 시작하는 스케치를 만든다고 가정해 보겠습니다. random()을 사용하면 대신 1%의 새로운 워커가 시작하는 확률을 할당할 수 있습니다. 최종 결과는 동일하지만 후자는 우연성을 포함하여 더 동적이고 예측할 수 없는 느낌을 줍니다.

 

Exercise 0.3
동적 확률을 갖는 무작위 워커를 만들어 보세요. 예를 들어, 마우스 방향으로 이동할 확률을 50%로 할 수 있을까요? 기억하세요, p5.js에서 현재 마우스 위치를 얻으려면 mouseX와 mouseY를 사용할 수 있습니다!