Pythonのrandom.choices関数について調べてみた話

ふと思いついたアイデアをプログラミングで実装しようとあれこれ考えてました。で、今回はアイデア自体は置いておいて、そのプログラミングの基礎としての話になります。

あーでもないこーでもないと考えた結果、いくつか条件ごとに分かれた項目を作成、その条件の発生確率に従って選択すべき条件を決めるという線で行こうと思ったわけです。大元のアイデアとは切り分けて、話を単純化するために条件を
  • a, b, c, d, e
の5つで考えます。で、それぞれの発生確率を仮に
  • 10%, 20%, 5%, 35%, 30%
としておきます。この発生確率を元に、5つの条件のどれかを選ぶという処理になるわけです。
(発生確率は固定ではなくて経過に伴い変動していくよう作る予定ですが、ここでは固定して考えます)

さて、アタマで考えるにそう難しくは無さそうと思えるのですが、いざプログラミングで処理を書こうとするとよく分からなくなりました。Pythonにはrandomモジュールに「random.choice」という関数があります。この関数は与えたシーケンス(リストとか)からランダムにシーケンス内の要素を返します。
「なるほど。この関数でいいじゃない」と思ったのですが、この関数で選ばれる要素はランダムで選ばれるだけなので確率がどうとかは考慮されません。5つの条件を与えればそれぞれ20%の確率で選ばれるだけ。これではダメです。

そして「ちょっとヒントもらうだけだからいいよね…」とここでインターネット上の知識に頼ることにしました。こういうのは自分で考えないとプログラミング能力が鍛えられないとかなんとか言いますが、使える知識を使うのもまたプログラミング能力…などと自分に言い訳をしつつ検索検索。
で、すぐに見つかったのはnumpyライブラリにある重み付けランダム選択関数。「なるほど、これを使えばすぐ出来そう」と思いましたが、ここでしばらく考えることにします。確かにこれなら使い方を把握すれば、すぐにプログラミングを組めるようになりそうです。しかし、そうは言ってもやはり少しは自分で考えた方がいいだろう、というさっきの言い訳に抗う気持ちもあるわけです。
ということで、ここでしばらく自分で考えてみることに。

まずランダムに選択する方法としては、始めに調べたrandom.choiceを使うことにします。まぁ「乱数生成して、数値範囲を決めて区切ってそれに従い条件を選ぶ」みたいなものを作ってもいいんでしょうけど、ここではそこはrandom.choiceを使うことで簡略化。random.choiceでは要素はランダムにしか選べませんが、確率に従って5つの条件をリスト内に詰めてしまえばよさそうです。
  • a, b, c, d, e
では単に同じ確率でしか選べませんが
例えば
  • a,a,a,b,b,b,b,b,b,c,d,d,d,d,d,d,d,d,d,e,e,e,e
と確率に従ってそれぞれの条件をその数だけ入れておけば、ランダム選択が確率に依存するようになるわけです。
(数字の並び方により選択に偏りが生じる可能性については考えないことにします。よくわからないし)

とまぁここまではいいのですが、後々にアイデアの実装に組もうとするとこれだと扱い難いということが判明(まだ実際に組んではいないのでホントに扱い難いかどうかは確定していない)。

さて、あれこれ考えたりしてましたが、ここで根負け。しかしnumpyを使うことにすると、numpyについての情報も調べる必要が出てくるので、なるべくPythonに標準である機能で済ませたい。そうこうしてもう少し調べてみると、random.choicesという関数があることが分かります。こちらはrandom.choiceとは異なり、引数に重み付けを指定することが出来ます。また、この関数が追加されたのはPython 3.6からだそうです。

重み付けが出来ればどうして確率を表せるのかは、まぁ説明がめんどうなのでここでは
「それぞれの要素の重み付けを重み付けの合計値で割ってパーセントで表わすことでそれぞれの要素の確率と考える」
としておきましょう。

random.choicesの書式は
random.choices(population, weights=None, *, cum_weights=None, k=1)
  • population : populationシーケンスのこと(リストのことを考えればいいのかな?)
  • weights : 重み付け
  • cum_weights : 累積的な重み付け
  • k : 返されるリスト内の要素の数(重複ありの場合で選択)
というようになるそうです。
weightsとcum_weightsで何が違うのかがよく分かりませんでしたが、cum_weightsは要素の順番に従って加算されていくもののようです。例えば
weightsが[ 5, 3, 2, 6]の場合をcum_weightsで表すと[ 5, 8, 10, 16]となるようです。cum_weightsの方はちょっとどう使えばいいのか分からないので、ここではweightsを使う方法だけ考えることにします。

さて、なにはともあれ、実際に目的の用途に使えるかどうかチェックしてみましょう。
リストとそれに対応した重み付けを以下のようにします。
  • l = ["a", "b", "c", "d", "e"]
  • w = [10, 18, 50, 21, 1]
これを
  • random.choices(l,weights=w)
とすれば、それぞれの重み付けに従った要素が得られることになるはず。そして一回ずつどの要素が出たかをカウントして、それを100回繰り返すプログラムを実行すると
  • a 9回, b 23回, c 50回, d 17回, e 1回
という出現回数が得られました。元の重み付けとは若干のズレがありますが、ランダムで要素を引いてるのでこんなものでしょう。100回ではなく、もっと回数を多くして引けば元の重み付けに近い値に収束するはず。

分かりやすくなるかなと思ってグラフも描いてみました。これは100回要素を引くのを1サイクルとして、100サイクル繰り返していったグラフです。結構バラツキがあるように見えますが、1サイクルで引く回数を1000回とか10000回にすればもっと平らなグラフになるでしょう。

ついでに重み付けなしの場合どうなるかというと、こんな感じ。確率としてはそれぞれ20%の確率なのでだいたいそんな風になっているのが現れています。

さて、実際に使ってみた結果、これなら使えそうだなと思うので、もう少し調べてみてからこれでプログラミングを組んで行こうと思います。


参考ページ



Amazon Python関連書籍など

コメント

スポンサーリンク


このブログの人気の投稿

Ubuntu Softwareが起動しないのでいろいろと調べてみる(Ubuntu 20.04.1 LTS)

gnuplotでプロットなどの色をcolornameの指定で変更する

Pythonのformat()を使って1桁の16進数でも2桁で出力する方法

gnuplot : プロット画像のサイズ指定について(set sizeとの違い)

gnuplot : グラフにグリッド線を描く方法(set grid)