선택한 무게가 무겁다
44833 단어 elixirpythonalgorithmsprobability
겸손하고 포부가 있는 초보 개발자의 확률적 사고 지침일련의 포효와 이상한 생각.
가능성이 얼마나 큽니까?나는 게임의 무고해 보이는 기능을 실현하는 데 상당히 많은 시간을 들였다. 이것은 내가 Elixir 함수 프로그래밍 분야에서 배운 일부분이다.나는 너로 하여금 이런 세부 사항을 귀찮게 하지 않을 것이다. 나는 심지어 게임을 완성하지 못했는데, 이 문제의 서막이 무고하다는 것을 발견했다.이것은 궁극의 감옥 기어오르기 게임으로 어둠의 방에 들어가 보물을 찾고 결국(희망을 품고) 떠날 수 있다.
내 일을 알고 있다면?저는 Looovvveee 게임과 RPG, 특히 JRPG를 좋아합니다. 저는 지옥기어오르기와 수수께끼를 원망해서 I write about it까지 좋아합니다. [NSFW!]그래서 저는 이 일을 하면서 매우 기뻤습니다. 모든 톱니바퀴가 어떻게 배합되는지 궁금했습니다. 특히 재미있는 것은 이것은 objective functions의 모델링과 시뮬레이션과 관련이 있다는 것을 알아차렸습니다. 이것은 제가 매우 흥미를 느낀 것입니다!!!잠수 탐험을 합시다!
방을 선택할 확률은 Enum.random(iterable)
에 대한 호출 제어입니다. 이 호출은Erlang의 내부 위조 랜덤 생성 알고리즘을 사용하여iterable에서 랜덤 요소를 선택합니다.이때 방이 세 개밖에 없다(출구로 통하는 방 하나, 괴물로 가득 찬 방 하나, 괴물로 가득 찬 방 하나)
따라서 어떤 방을 찾을 수 있는 수학적 확률이 P(1/3)라고 안전하게 가정할 수 있다.이거 안 좋죠?너는 게임을 시작할 수 있다. 3분의 1의 시간 동안 심지어는 전투를 경험한 적이 없다. (오, 작가의 조언을 알고 있다.... 우리는 방에 다른 확률을 추가했다. 이렇게 하면 전투가 있는 방이 더 가능해 보이고 출구가 있는 방은 불가능해 보인다. 그래. 그래서 나는 첫 시간쯤 후에 만족스러운 해결 방안을 생각해 냈다.그렇습니다.나는 우선 확률 키를 추가했는데, 그 값은 구조 중의 원자이다.
defmodule DungeonCrawl.Room do
@moduledoc """
data structure representing a "room"
a room has "actions" borrowed
"""
alias DungeonCrawl.Room, as: Room
alias DungeonCrawl.Room.Triggers, as: Triggers
import DungeonCrawl.Room.Action
defstruct name: nil, description: nil, actions: [], trigger: nil, probability: nil
def all,
do: [
%Room{
name: :exit,
description: "You can see a small light from a crack in the walls",
actions: [forward()],
trigger: Triggers.Exit,
probability: [:exit]
},
%Room{
name: :goblin,
description: "You can see an enemy blocking your path",
actions: [forward()],
trigger: Triggers.Enemy,
probability: [:goblin, :goblin, :goblin, :goblin, :goblin]
},
%Room{
name: :ogre,
description: "Something moves around in the dark, what do you do?",
actions: [forward()],
trigger: Triggers.Enemy,
probability: [:ogre, :ogre, :ogre]
},
]
end
너는 아마도 이 해결 방안의 유치함을 곧 알아차릴 수 있을 것이다.계산법이 뭐예요?
defp bias_probability(rooms) do
rooms
|> Enum.map(fn room -> room.probability end)
|> Enum.reduce(fn room, acc -> room ++ acc end)
|> Enum.random()
end
마지막으로, 이제 편차가 있는 랜덤 결과를 알았어-
rooms
|> Enum.find(fn room -> room.name == bias_probability(rooms) end)
기술적으로?이것은 수학적으로 정확한 해결 방안이다.그것은 P(0.1, 0.5, 0.3)
의 편차 확률로 무작위 값을 계산할 것이다.나는 내 자신에 대해 매우 만족해서 노트북을 끄고 잠이 들었다.그렇지!적어도 나는 이렇게 생각한다.
이러한 접근 방식에는 다음과 같은 두 가지 기본적인 결함이 있습니다.
defmodule DungeonCrawl.Room do
@moduledoc """
data structure representing a "room"
a room has "actions" borrowed
"""
alias DungeonCrawl.Room, as: Room
alias DungeonCrawl.Room.Triggers, as: Triggers
import DungeonCrawl.Room.Action
defstruct name: nil, description: nil, actions: [], trigger: nil, probability: nil
def all,
do: [
%Room{
name: :exit,
description: "You can see a small light from a crack in the walls",
actions: [forward()],
trigger: Triggers.Exit,
probability: [:exit]
},
%Room{
name: :goblin,
description: "You can see an enemy blocking your path",
actions: [forward()],
trigger: Triggers.Enemy,
probability: [:goblin, :goblin, :goblin, :goblin, :goblin]
},
%Room{
name: :ogre,
description: "Something moves around in the dark, what do you do?",
actions: [forward()],
trigger: Triggers.Enemy,
probability: [:ogre, :ogre, :ogre]
},
]
end
defp bias_probability(rooms) do
rooms
|> Enum.map(fn room -> room.probability end)
|> Enum.reduce(fn room, acc -> room ++ acc end)
|> Enum.random()
end
rooms
|> Enum.find(fn room -> room.name == bias_probability(rooms) end)
확률은 불공평해!
선형 스캐닝 O(kay)법
O(n) 런타임
좋은 생각은 가능한 결과에 따라 체인 테이블에서 색인 값을 검색하는 것이다. 나는 이 점을 생각할 수 있기를 바란다!!이것은
0 - 1
사이에 있는 무작위 수를 생성하고 이를 총 확률 분포(내 용례에 대해 나는 목록이 정렬되었다고 가정)를 곱한 다음에 확률 분포를 두루 훑어보고 무작위 확률에서 모든 확률을 뺀다. 만약 그것이 0보다 낮다면?우리는 이미 확률 분포를 궁리하고 우리가 필요로 하는 지수를 찾았다.좋은 해결 방안.이것은 우리가 수학적으로 random.random()
이 완전히 무작위 숫자를 생성할 것이라고 가정하기 때문이다. (비록 그것은 아니지만 우리는 개의치 않는다.)만약 확률이 정말 낮다면?확률이 높으면?그것은 그 지수에 더욱 빈번하게 닿을 것이다.만약 네가 좋아한다면, 아래의 코드를 실행해서 그것을 이해할 수 있다.import random
def weighted_random(weights):
"""
INPUT probabilities - [0.2, 0.3, 0.5]
OUTPUT index of random_weight - [0, 1, 2]
"""
remaining_distance = random.random() * sum(weights)
# probability distribution sample size * random integer
for (i, weight) in enumerate(weights):
# [{0, 0.2}, {1, 0.3}, {2, 0.5}]
remaining_distance -= weights[i]
#print("debug", remaining_distance)
if remaining_distance < 0:
return i
# Repeat the experiment to observe bias factor
for e in range(10):
print(f"exp trial{e}",weighted_random([0.2, 0.3, 0.5]))
이거 멋있어 보이지 않아요?나는 게임의 P(n)를 부동으로 표시하고 동적 업데이트를 해서 함수의 순결성을 유지할 수 있다.빠르고 더러운 불로장생의 약 버전이 보기에defmodule Prob do
def bias_probability(weights) do
# [i[0] P=0.2, i[1] P=0.5 , i[2] P=0.3] s.s = 3
# P(n) = distribution sample len bias * random() - P[i] < 0
len = Enum.count(weights)
# could increase range for greater float precision as you like
distance = Enum.random(1..99)/100 * Enum.sum(weights)
# bias factor
enumerated_list = Enum.zip(0..len, weights)
Enum.reduce_while(enumerated_list, distance, fn (_weight = {i, w}, acc) ->
if (acc - w) > 0, do: {:cont, acc - w}, else: {:halt, i}
end)
end
end
IO.inspect Prob.bias_probability([0.2, 0.3, 0.5])
그리고 편향지수를 찾아라. rooms
|> Enum.map(fn room -> room.probability end)
|> Enum.fetch(bias_probability(rooms))
하지만 지금은 가중 확률을 연구하는 번거로움을 겪었기 때문에 선형 검색에 만족하고 싶지 않다.가장 좋은 것은 가장 빠른 속도로 게임을 완성하는 것이다.역사를 돌이켜 보면 alias algorithms 중 하나를 볼 수 있다.Aliasing(Vose) the O yes! method
O(n)별명표 준비 + O(1)찾기
이것은 처음에는 좀 이해하기 어려웠다.만약 당신이 이 문제를 해결하는 각종 방법에 대해 더욱 깊은 기술적 연구에 관심이 있다면, 이 article by Keith Schwarz을 보십시오. 이것은 밀집적이고 전면적이며 기술적입니다.별명에 대해서!
솔직히 이 생각은 매우 똑똑하다!내가 보기에 이것은 직감에 어긋나는 것이다. 이것이 바로 왜 그것이 정해진 시간에 운행할 때 이렇게 인상적인 것을 찾는지 하는 것이다.우리는 먼저 한 걸음 물러선 후에 다시 뛰어들었다.별명은 우리가 물건에 지어준 별명과 같지?너의 친구는 세무르라고 하는데, 너는 그를 샘이라고 부른다. 그의 이름은 샘이 아니다. 그는 이 별명을 싫어한다. 그러나, 아이고, 그것은 끊겼다.불쌍한 세무르. 하지만 어느 곳에서든 그의 얼굴을 보면 세무르가 세무르라는 것을 알 수 있다. 네가 이렇게 좋은 친구이기 때문이다.alias 방법은 원칙적으로 이와 유사하다.너는 편향된 확률로 권중분포를 가지고 있다. 이것은 특정한 극한에 가깝다. 이 극한은 사용자 정의 분포를 생성하는 데 사용할 수 있다.우리 샘에 대해 이야기합시다.
샘의 닉네임은
alias table
입니다. 당신의 우정(샘이 세무르라는 것을 어떻게 알았는가)이 알고리즘입니다.지금 우리는 우리가 하고 있는 일에 대해 약간의 힌트를 얻었다. 아래는python 실현과 내가 Bruce Hill 블로그에 올린 평론이다.한번 훑어보고 모험심이 있다면 운행해 보세요.다음 단계에서 우리는 한 걸음 한 걸음 그것을 분해할 것이다.(Java에 익숙한 경우 Keith Schwarz's implementation instead을 참조하십시오.)def prepare_aliased_randomizer(weights):
N = len(weights) # limit
avg = sum(weights)/N # alias partition
aliases = [(1, None)] * N
# [(1, None), (1, None), (1, None)] --> alias table
# Bucketting (pseudo quick sort like behaviour*)
# weight/avg < | > avg
# smalls are partial alias fits
# bigs fits and remainder thrown to smalls
smalls = ((i, w/avg) for (i, w) in enumerate(weights) if w < avg)
bigs = ((i, w/avg) for (i, w) in enumerate(weights) if w >= avg)
# [(0, 0.6), (1, 0.9)] --> small weight avgs
# [(2, 1.5)] --> big weight avgs
#cycle through small and big partition generator
small, big = next(smalls, None), next(bigs, None)
# if elems are not exhauted kindly loop
while big and small:
# put the partial elem in aliases
aliases[small[0]] = (small[1], big[0])
print("alias lookup table transformation", aliases)
# big = i of big , weight_b - (1 - weight_s)
# big = (0, (1.5 - (1 - 0.6))
big = (big[0], big[1] - (1-small[1]))
if big[1] < 1:
print("large weight is < 1 skip", aliases)
small = big
big = next(bigs, None)
else:
small = next(smalls, None)
print("alias table generated", aliases)
# SELECTION STAGE
def weighted_random():
r = random.random() * N
i = int(r)
# choose a random probability
# from the alias table
(odds, alias) = aliases[i]
print("what are the odds of", r)
# return the larger probability if
# it's odds are higher else
# return the smaller probability index
return alias if (r-i) > odds else i
return weighted_random()
# single trial selection
print(f"experiment trial", prepare_aliased_randomizer([ 0.2, 0.3, 0.]))
# Repeat the experiment to observe bias factor
for e in range(10):
print(f"experiment trial", prepare_aliased_randomizer([0.2, 0.3, 0.5]))
이 단락은 매우 재미있지만, 결국은 주제에서 벗어났다. 너는 그것을 뛰어넘을 수 있다.
컴퓨터는 어떻게 샘이 누구인지 정확하게 기억합니까?컴퓨터는 사람의 뇌와 약간 비슷하다. 사람들은 복잡한 신경원 유기회로를 가지고 감각관의 관찰을 공고히 하며 그 중에서 모델을 식별하고'사상'을 형성할 수 있다. 유일한 차이점은 컴퓨터가 어떤 모델을 따라야 하는지 알려줘야 한다는 것이다. 컴퓨터는 하나의 사상을 형성할 필요가 없고 단지 하나의 결과일 뿐이다. 우리는 이런 지령이 필요하지 않다는 것이다.(dna? 아니, 이것은 철학적인 문제로 지능뿐만 아니라 은근한 의지도 관련된다.) 본질보다 먼저 존재하는가?나는 이렇게 생각하는 것을 좋아한다.뻔뻔한 플러그인 @me on, 이 화제에 관심이 있다면.
표 생성
표를 만들기 위해 원시적인 권중 분포는 몇 개의 블록(구역)으로 분할됩니다!정말!가령 우리가 얼마나 많은 무게를 필요로 하는지 알고 있다면, 이 예에서 3이다. 이것은 극한이다.
P(0.2, 0.3, 0.5)
을 찾기 위해 우리는 데이터 구조를 가진 빈 별명을 만들었는데 그 길이는 [partition, partition, partition]
과 완전히 같고 확률은 [partition, partition, partition]
이다.각 구역은 마침 P(1/3)
을 포함한다.흥미로운 부분입니다. 그리고 확률을 극한인자로 축소하여 less than 1
개와 greater than or equal to 1
개로 나눈다.왜?이유는 다음과 같습니다.
defmodule Probability do
def bias_probability(weights) do
# Initialization
n = Enum.count(weights)
prepare_alias_table(weights, n)
end
defp prepare_alias_table(weights, n) do
alias_table = Enum.map(1..n, fn _ -> {0, nil} end)
prob = Enum.map(1..n, fn _ -> nil end)
# create work lists
scaled_weight = scale_probability(weights, n)
small =
scaled_weight
|> Enum.filter(fn {_, w} -> w < 1 end)
large =
scaled_weight
|> Enum.filter(fn {_, w} -> w >= 1 end)
# recursively create table (TCO optimized)
transform_alias(small, large, alias_table, prob)
end
# Base case when small and large are empty
defp transform_alias([], [], _, prob), do: prob
defp transform_alias(small = [], [_g = {i, _} | tail], alias_table, prob) do
# Remove the first element from large call it g, Set prob[g] = 1
transform_alias(
small,
tail,
alias_table,
List.replace_at(prob, i, 1)
)
end
defp transform_alias([_l = {i, _} | tail], large = [], alias_table, prob) do
# (clause will trigger due to numerical instability)
# Remove the first element from Small, call it l
transform_alias(
tail,
large,
alias_table,
List.replace_at(prob, i, 1)
)
end
defp transform_alias(
[{index_l, weight_l} | tail_s],
[_g = {index_g, weight_g} | tail_l],
alias_table,
prob
) do
# Remove the first element from small call it l
# Remove the first element from large call it g
# Pg := (pg + pl) - 1 (numerical stability :) )
new_weight_g = (weight_g + weight_l) - 1
# if Pg < 1 add g to small
if new_weight_g < 1 do
transform_alias(
[{index_g, new_weight_g} | tail_s],
tail_l,
List.replace_at(alias_table, index_l, weight_g),
List.replace_at(prob, index_l, weight_l)
)
# else Pg >= 1 add g to large
else
transform_alias(
tail_s,
[{index_g, new_weight_g} | tail_l],
List.replace_at(alias_table, index_l, weight_g),
List.replace_at(prob, index_l, weight_l)
)
end
end
# HELPER
defp scale_probability(probs, n) do
0..n
|> Enum.zip(probs)
|> Stream.map(fn {i, w} -> {i, w * n} end)
end
end
이제 우리는 다른 이름표를 만들 수 있습니다. 이 실현은 로컬 캐시도 포함합니다. Erlang Term Storage을 사용하여 다른 이름표를 다른 표에 저장합니다.프로그램이 실행되기 시작하면, lol은 전체 생명 주기를 관통할 것이다.나는 일단 난이도 등급을 선택하면 무작위 방을 채워야 한다고 가정하지만 이 방이 나타날 확률은 한 번 계산해야 한다. def bias_probability(weights) do
# Initialization
# does the probability exist in memory?
current_probability = try do
[weights: cached_probs] = :ets.lookup(:weight_probability, :weights)
cached_probs
rescue
ArgumentError -> cache(weights)
end
current_probability
#|> weighted_random # to be implemented next
end
defp cache(weights) do
n = Enum.count(weights)
:ets.new(:weight_probability, [:duplicate_bag, :private, :named_table])
:ets.insert(:weight_probability, {:weights, prepare_alias_table(weights, n)})
[weights: cached_probs] = :ets.lookup(:weight_probability, :weights)
cached_probs
end
우러러보다
마지막으로 우리는 무작위 확률을 생성하고 지수를 선택하여 되돌아갈 수 있습니다!
# GENERATION
defp weighted_random(aliased_table, n) do
# Generate a fair random distro in a range
# from n and call it i.
r = Enum.random(0..1000)/1000 * n
# random choice P(1/3)
i = floor(r) # 0, 1 , 2
prob = aliased_table
{:ok, odd} = Enum.fetch(prob, i)
# partial fit
if (r - i) > (odd) do
# which piece of what goes where
bias = prob
|> Enum.with_index()
|> Stream.filter(fn {p, _} -> p == 1 end)
|> Enum.random()
{_, i} = bias
i
else
i
end
end
다음은 Github gist의 전체 실현고차스
이것을 복사하거나 붙여넣기를 원한다면, 많은 기다림이 있습니다. 예를 들어, 부동 정밀도 (일부) 를 무시하고, 반대로 피드 값을 선택할 수 있습니다. 그리고 나의 확률은 항상 권중분수가 1이거나, 스텔스 가설은 영원히 0으로 되돌아오지 않거나, 선형 검색의 권중은 1입니다.더 깊은 블로그를 읽어 주십시오. 그것들은 값이 없습니다.그러나, 내 용례에서, 그것은 수천 개의 방까지 쉽게 확장할 수 있을 것이다😄 읽어주셔서 감사합니다!!!!나는 줄곧 피드백을 찾고 있는데, 어떤 건의가 있습니까?벌레 한 마리를 발견했습니까?재미있는 알고리즘 하나 알아요?어쩌면 당신은 단지 우호적인 말을 남기고 싶을 뿐, 나는 항상 즐겁게 이야기를 나눈다.)
Reference
이 문제에 관하여(선택한 무게가 무겁다), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/haile/the-weight-of-choice-is-heavy-55o텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)