선택한 무게가 무겁다

겸손하고 포부가 있는 초보 개발자의 확률적 사고 지침일련의 포효와 이상한 생각.


가능성이 얼마나 큽니까?나는 게임의 무고해 보이는 기능을 실현하는 데 상당히 많은 시간을 들였다. 이것은 내가 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)의 편차 확률로 무작위 값을 계산할 것이다.나는 내 자신에 대해 매우 만족해서 노트북을 끄고 잠이 들었다.그렇지!적어도 나는 이렇게 생각한다.
이러한 접근 방식에는 다음과 같은 두 가지 기본적인 결함이 있습니다.
  • 저는 확률에 대해 하드코딩을 하고 있습니다. 방을 수정할 확률은 가능하지만 보일러판 코드와 불필요한 목록이
  • 을 누비게 됩니다.
  • 이 알고리즘의 성능이 좋지 않다(공간의 복잡도에 있어) 원자는 불로장생약에서 쓰레기로 수집된 것이 아니라 마음대로 만드는 것은 현명하지 않다!가령 내가 장래에 천 칸의 방이 있다고 가정하면, 예를 들면
  • 아니나 다를까 추가적인 기능 수요가 나타나'난이도 등급'을 증가시켰는데 이것은 이런 가능성에 동태적으로 영향을 줄 것이다.그래서나의 검색을 시작하고 새로운 용기를 가지고 내가 반복적으로 시도한 수학 문제를 시도해 보았지만 운이 없었다.나는 학교에서 공학을 배우는 친구들에게 운이 나쁘다고 물었다.아니나 다를까 구글 검색 기교가 뛰어난 친구가 2017년 오랫동안 전해지지 않았던 블로그를 찾았다.이것이야말로 진정으로 나를 돕는 것이다. 그것이 없으면 나는 확률론과 조건 문제의 무의미한 낙서에 빠진다.가중 확률 알고리즘 실현에 대한 더 많은 정보를 알고 싶으시면 David Hills's blog을 보십시오. 아주 상세하고 잘하고 있습니다!나는 내가 배운 두 가지 알고리즘 방법을 어떻게 실현하고 이 게임의 환경에 적응하도록 하는지를 중점적으로 소개할 것이다.선형 검색과alias 알고리즘 중 하나입니다.

    확률은 불공평해!


    선형 스캐닝 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개로 나눈다.왜?
    이유는 다음과 같습니다.
  • 만약 권중점수가 그것보다 크거나 같으면, 일부 변경 사항으로 별명 구역을 채울 것입니다. (같으면 완전히 일치합니다.)빈 구역을 보낼 것입니다.
  • 가중치가 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입니다.더 깊은 블로그를 읽어 주십시오. 그것들은 값이 없습니다.그러나, 내 용례에서, 그것은 수천 개의 방까지 쉽게 확장할 수 있을 것이다😄 읽어주셔서 감사합니다!!!!나는 줄곧 피드백을 찾고 있는데, 어떤 건의가 있습니까?벌레 한 마리를 발견했습니까?재미있는 알고리즘 하나 알아요?어쩌면 당신은 단지 우호적인 말을 남기고 싶을 뿐, 나는 항상 즐겁게 이야기를 나눈다.)

    좋은 웹페이지 즐겨찾기