宝くじに当たる確率は非常に低いということを一度は聞いたことがあるでしょう。確率に関連するすべてのことと同様、複数の試行によって結果が有利になる可能性があります。さて、多くの宝くじに参加した場合、さらに何回参加したかに応じて、当選の可能性は少し高くなります。これは、最終的に当選するという保証はまだありませんが、均一な分布の場合です。そして、大数の法則 (この場合は多数の抽選を意味します) に従うと、比較的可能性の高い可能性に到達できます。
新しい宝くじはそれぞれ独立しており、同じ宝くじの「チケット番号」が (大数の法則に従って) 多くの異なる宝くじに当たる可能性があることを理解することが重要です。運が悪いと、何度試しても、毎回の宝くじで間違った数字を選んでしまう可能性もあります。現在、2 つのオプションがあります:
理論的に (そして数学的に)、両方のシナリオが発生する可能性は同じです。ただし、シナリオ 2 の方がわずかに有利になります。回数が無限に近づくと、最終的にはすべての数字が選択されます。問題は、シナリオ 1 では、その時点で選んだ数字が勝ちの数字と一致することを期待して、さらに何度も試行する必要があることです。シナリオ 2 では、試行は無限に進む傾向があるため、ある時点であなたの番号が「勝つ」ことが確実です。このブログ投稿では、シナリオ 2 を使用します。
それでは、私が答えを言う前に、この質問に答えられると思いますか?
「あなたの周りのすべての宝くじにちょうど 100 万人分のスロットがあり、プレイした全員に同じチケット [x] を選択した場合、最終的に当選者になるには何回の宝くじをプレイする必要がありますか?」 (最初の答えが何であったかについてお気軽にコメントしてください)
答えは...
約1,440万回。
このブログ投稿の残りの部分では、どのようにしてその値に到達したか、シミュレーションがどのように行われたか、およびいくつかの注意点について説明します。ここからはさらに技術的な話になります。
100 万人の宝くじのチケット番号は、1 ~ 1,000,000 (または 0 ~ 999,999) の範囲になります。プレイヤーは各宝くじでその範囲内の数字のみを選択でき、当選チケットはその範囲からのみ選択できます。基本的に、100 万個の数値のセットがあると言えます。
ユーザーがその範囲内の任意の数値を選択できるという事実を考慮すると、セット内のすべての項目が少なくとも 1 回ヒットするという条件を満たす必要があります。これは、すべての番号が少なくとも 1 回コールされていれば、プレーヤーが選択できるすべてのチケット番号がカバーされるためです。これは、各数値が実行される回数を気にしないことも意味し、「セット」がシミュレーションに使用する理想的な Python データ構造になります。空のセットから開始し、セットに指定された範囲内のすべての数値が含まれるまで、反復ごとにランダムに生成された数値をそのセットに入力します。 Python セットは数値を繰り返さないため、一意性の確保について心配する必要はありません。
def calculate_lottery_chances(lottery_players_count): number_set = set() count = 0 while len(number_set) < lottery_players_count: gen_number = random.randint(1, lottery_players_count) number_set.add(gen_number) count += 1 return count
1,000,000 人の宝くじの場合、関数呼び出しは Calculate_lottery_chances(1000000) のようになり、当選するまでの宝くじの試行回数が返されます。このようにコードを配置すると、非常に拡張可能になります。
一言で言えば、問題の根本原因は「ばらつき」です。初めて関数を実行したとき、値として「1,310 万」回を取得しました。再実行したところ、1,390 万程度の値が得られました。これをさらに何度も繰り返したところ、さまざまな答えが得られ、ある時点で 1,500 万を獲得しました。これを実行して平均値を見つける必要があることは明らかでした。これまでの既存のパターンに従って、平均化する反復回数が無限に近づくにつれて、1 つ の信頼できる答えに近づくだろうと考えました。これを高速に実行できるものが必要だったので、この関数を作成することにしました。
def average_over_n_times(function, function_arg, n): """ This returns the average of the returned value of a function when it is called n times, with its (one) arg """ total = 0 for x in range(0, n): total += function(function_arg) return round(total/n)
その後、すべてが次のように修正されます:
num_of_trials = average_over_n_times(calculate_lottery_chances, lottery_players_count, n)
ここで、「n」は結果を平均化する回数を表します。ただし、これは次のセクションで説明する別の問題を引き起こします。
n の値が大きいほど、「平均的な場合」の結果に近づきます。ただし、まだ絶対や確実性がないことを考えると、この一連の作業を何度も実行すると生産性が低下します。私がこれを言うのは次の理由からです:
これらを念頭に置いて、「n」を次の値でテストしました: 10、20、30、50、100、1000、および 5000 回
この時点で、ブログ投稿のタイトルにある「PyTorch」という単語がなぜ言及されていないのか疑問に思われたかもしれません。さて、さまざまな値で n をテストすると述べましたが、それはすべてのテストに使用したのと同じコードではありませんでした。
これらは計算量の多い実験であり、私の CPU は私に連絡をくれました。以前に共有したコード スニペットは、外部パッケージの依存関係がまったくない 1 つのファイルに書かれており、そのファイルは、実行時間を追跡するために time コマンドを先頭に付けて bash シェルで実行されました。 CPU のみを使用した場合の実行時間は次のようになります:
n | Time (min and sec) |
---|---|
10 | 1m34.494s |
20 | 3m2.591s |
30 | 5m19.903s |
50 | 10m58.844s |
100 | 14m56.157s |
1000 で、プログラムを動作させることができなくなりました。途中で切れて実行停止に失敗したのかは分かりませんでしたが、4時間57分後にキャンセルしました。これにはいくつかの要因が影響していると思われますが、それについては「注意事項」セクションで説明します。とにかく、ファンの音がうるさかったので、ラップトップのそれほど強力ではない CPU を少し使いすぎたのかもしれないと思いました。私は敗北を受け入れることを拒否し、少なくとも 4 桁の反復を実行するにはどうすればよいかを考えていたとき、PyTorch を使っていた友人が私に言ったことを思い出しました。
「一般に、GPU は CPU よりも大量の計算処理において効率的です。」
PyTorch は GPU を使用するため、この作業に最適なツールです。
今回の目的では PyTorch が計算に使用されるため、既存の Calculate_lottery_chances() コードをリファクタリングすることは、CPU に依存した数値演算を変更し、適切な PyTorch データ構造に切り替えることを意味します。一言で言えば:
calculate_lottery_chances のリファクタリングは次のようになります:
def calculate_lottery_chances(lottery_players_count): number_set = set() count = 0 while len(number_set) < lottery_players_count: gen_number = random.randint(1, lottery_players_count) number_set.add(gen_number) count += 1 return count
私のコンピューターでは PyTorch がサポートするインテル グラフィックス GPU を使用しているため、デバイスを「xpu」に設定しました。
実行中に GPU が使用されていることを確認するために、実行前に Windows タスク マネージャーを開き、「パフォーマンス」セクションに移動しました。実行すると、GPU リソースの使用量が顕著に急増していることがわかりました。
コンテキストのために、前と後を次に示します:
前:
GPU 使用率が 1% であることに注目してください
後:
GPU 使用率が 49% であることに注目してください
n の値を変化させた場合のランタイムでは、GPU は数倍高速でした。 100 未満の n の値を 1 分未満で一貫して実行し、5000 (5,000!)
の n の値を計算することができました。GPU を使用したランタイムの表は次のとおりです:
n | Time (min and sec) |
---|---|
10 | 0m13.920s |
20 | 0m18.797s |
30 | 0m24.749s |
50 | 0m34.076s |
100 | 1m12.726s |
1000 | 16m9.831s |
この実験での GPU 操作と CPU 操作のパフォーマンスの差がどれほど大きかったかを視覚的に理解するために、以下のデータ視覚化を考慮してください。
CPU から現実的に「タイムリーな」出力を得ることができなくなり、GPU と比較する余地がなくなったため、X 軸の上限は 100 になりました。 1,000 ~ 5,000 の範囲の数値で実験を実行すると、結果として「1,440 万回」ほどの結果が得られることが多かったです。それが先ほどの答えです。
この実験では仮説を立て、特定の方法に依存しました。さらに、私には PyTorch の経験が浅いため、より効率的なアプローチがあった可能性があります。 結果の精度または実行時間に影響を与えた可能性があると考えられる、考慮すべきいくつかの要因を以下に示します。
最後に、私は PyTorch を初めて使用したのですが、そのパフォーマンスに非常に感銘を受けたことを指摘しておきます。
これを使ってウサギの穴に落ちたとき、これほどパフォーマンスが向上するとは予想していませんでした。私はテンソルの背後にある考え方と、さらに計算的に複雑なタスクの背後にあるサポートメカニズムについていくつか学びました。コード スニペットを自由に使用、複製、変更することができます。
お楽しみいただきありがとうございます。楽しくお読みいただければ幸いです。
次回まで
乾杯。 ?
以上が宝くじのクエストが私を PyTorch のパワーに導いた経緯の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。