Add files via upload

This commit is contained in:
Peter Norvig 2024-09-16 08:28:02 -07:00 committed by GitHub
parent 45059fc72f
commit de4b5461f7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -2247,463 +2247,6 @@
"\n",
"The conclusion is: be methodical in defining the sample space and the event(s) of interest, and be careful in counting the number of outcomes in the numerator and denominator. and you can't go wrong. Easy as 1-2-3."
]
},
{
"cell_type": "code",
"execution_count": 65,
"metadata": {
"id": "8unzVDfGjrvd"
},
"outputs": [],
"source": [
"number= random.random\n",
"\n",
"def strategy(cutoff):\n",
" \"Play the game with given cutoff, returning the first or second random number.\"\n",
" first = number()\n",
" return first if first > cutoff else number()"
]
},
{
"cell_type": "code",
"execution_count": 66,
"metadata": {
"id": "N2q4GJmbjrvd",
"outputId": "e2415943-e6b9-47ab-9e2f-9cfae69602d9"
},
"outputs": [
{
"data": {
"text/plain": [
"0.7590302612462443"
]
},
"execution_count": 66,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"strategy(.5)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "jCFHv1YMjrvd"
},
"source": [
"Now compare the numbers returned with a cutoff of *A* versus a cutoff of *B*, and repeat for a large number of trials; this gives us an estimate of the probability that cutoff *A* is better than cutoff *B*:"
]
},
{
"cell_type": "code",
"execution_count": 67,
"metadata": {
"id": "rF32bGvVjrvd"
},
"outputs": [],
"source": [
"def Pwin(A, B, trials=20000):\n",
" \"The probability that cutoff A wins against cutoff B.\"\n",
" return mean(strategy(A) > strategy(B)\n",
" for _ in range(trials))"
]
},
{
"cell_type": "code",
"execution_count": 68,
"metadata": {
"id": "HP39megCjrvd",
"outputId": "df1c11ef-bf5f-4632-e6dc-db3d24f7a648"
},
"outputs": [
{
"ename": "NameError",
"evalue": "name 'mean' is not defined",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)",
"Input \u001b[0;32mIn [68]\u001b[0m, in \u001b[0;36m<cell line: 1>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mPwin\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m0.6\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m0.9\u001b[39;49m\u001b[43m)\u001b[49m\n",
"Input \u001b[0;32mIn [67]\u001b[0m, in \u001b[0;36mPwin\u001b[0;34m(A, B, trials)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mPwin\u001b[39m(A, B, trials\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m20000\u001b[39m):\n\u001b[1;32m 2\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mThe probability that cutoff A wins against cutoff B.\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m----> 3\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mmean\u001b[49m(strategy(A) \u001b[38;5;241m>\u001b[39m strategy(B)\n\u001b[1;32m 4\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m _ \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mrange\u001b[39m(trials))\n",
"\u001b[0;31mNameError\u001b[0m: name 'mean' is not defined"
]
}
],
"source": [
"Pwin(0.6, 0.9)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "mbMpYuZ3jrvd"
},
"source": [
"Now define a function, `top`, that considers a collection of possible cutoffs, estimate the probability for each cutoff playing against each other cutoff, and returns a list with the `N` top cutoffs (the ones that defeated the most number of opponent cutoffs), and the number of opponents they defeat:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "4IEnUhjCjrvd"
},
"outputs": [],
"source": [
"def top(N, cutoffs):\n",
" \"Return the N best cutoffs and the number of opponent cutoffs they beat.\"\n",
" winners = Counter(A if Pwin(A, B) > 0.5 else B\n",
" for (A, B) in itertools.combinations(cutoffs, 2))\n",
" return winners.most_common(N)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "_auNAfb4jrvd",
"outputId": "802b1e35-b3db-40f7-fc67-7c1e41132be4"
},
"outputs": [],
"source": [
"from numpy import arange\n",
"\n",
"top(10, arange(0.5, 1.0, 0.01))"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "7me09ilZjrve"
},
"source": [
"We get a good idea of the top cutoffs, but they are close to each other, so we can't quite be sure which is best, only that the best is somewhere around 0.60. We could get a better estimate by increasing the number of trials, but that would consume more time.\n",
"\n",
"## The Hot New Game Show Problem: Exact Calculation\n",
"\n",
"More promising is the possibility of making `Pwin(A, B)` an exact calculation. But before we get to `Pwin(A, B)`, let's solve a simpler problem: assume that both players **A** and **B** have chosen a cutoff, and have each received a number above the cutoff. What is the probability that **A** gets the higher number? We'll call this `Phigher(A, B)`. We can think of this as a two-dimensional sample space of points in the (*a*, *b*) plane, where *a* ranges from the cutoff *A* to 1 and *b* ranges from the cutoff B to 1. Here is a diagram of that two-dimensional sample space, with the cutoffs *A*=0.5 and *B*=0.6:\n",
"\n",
"<img src=\"http://norvig.com/ipython/probability2da.jpg\" width=413>\n",
"\n",
"The total area of the sample space is 0.5 &times; 0.4 = 0.20, and in general it is (1 - *A*) &middot; (1 - *B*). What about the favorable cases, where **A** beats **B**? That corresponds to the shaded triangle below:\n",
"\n",
"<img src=\"http://norvig.com/ipython/probability2d.jpg\" width=413>\n",
"\n",
"The area of a triangle is 1/2 the base times the height, or in this case, 0.4<sup>2</sup> / 2 = 0.08, and in general, (1 - *B*)<sup>2</sup> / 2. So in general we have:\n",
"\n",
" Phigher(A, B) = favorable / total\n",
" favorable = ((1 - B) ** 2) / 2\n",
" total = (1 - A) * (1 - B)\n",
" Phigher(A, B) = (((1 - B) ** 2) / 2) / ((1 - A) * (1 - B))\n",
" Phigher(A, B) = (1 - B) / (2 * (1 - A))\n",
" \n",
"And in this specific case we have:\n",
"\n",
" A = 0.5; B = 0.6\n",
" favorable = 0.4 ** 2 / 2 = 0.08\n",
" total = 0.5 * 0.4 = 0.20\n",
" Phigher(0.5, 0.6) = 0.08 / 0.20 = 0.4\n",
"\n",
"But note that this only works when the cutoff *A* &le; *B*; when *A* > *B*, we need to reverse things. That gives us the code:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "WrIdpqnWjrve"
},
"outputs": [],
"source": [
"def Phigher(A, B):\n",
" \"Probability that a sample from [A..1] is higher than one from [B..1].\"\n",
" if A <= B:\n",
" return (1 - B) / (2 * (1 - A))\n",
" else:\n",
" return 1 - Phigher(B, A)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "OpHBRSZojrve",
"outputId": "360ee92b-78a9-4ce7-92c6-332e2ce751b5"
},
"outputs": [],
"source": [
"Phigher(0.5, 0.6)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "8g2T_pE8jrve"
},
"source": [
"We're now ready to tackle the full game. There are four cases to consider, depending on whether **A** and **B** gets a first number that is above or below their cutoff choices:\n",
"\n",
"| first *a* | first *b* | P(*a*, *b*) | P(A wins &vert; *a*, *b*) | Comment |\n",
"|:-----:|:-----:| ----------- | ------------- | ------------ |\n",
"| *a* > *A* | *b* > *B* | (1 - *A*) &middot; (1 - *B*) | Phigher(*A*, *B*) | Both above cutoff; both keep first numbers |\n",
"| *a* < *A* | *b* < *B* | *A* &middot; *B* | Phigher(0, 0) | Both below cutoff, both get new numbers from [0..1] |\n",
"| *a* > *A* | *b* < *B* | (1 - *A*) &middot; *B* | Phigher(*A*, 0) | **A** keeps number; **B** gets new number from [0..1] |\n",
"| *a* < *A* | *b* > *B* | *A* &middot; (1 - *B*) | Phigher(0, *B*) | **A** gets new number from [0..1]; **B** keeps number |\n",
"\n",
"For example, the first row of this table says that the event of both first numbers being above their respective cutoffs has probability (1 - *A*) &middot; (1 - *B*), and if this does occur, then the probability of **A** winning is Phigher(*A*, *B*).\n",
"We're ready to replace the old simulation-based `Pwin` with a new calculation-based version:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "uwR6v88Mjrve"
},
"outputs": [],
"source": [
"def Pwin(A, B):\n",
" \"With what probability does cutoff A win against cutoff B?\"\n",
" return ((1-A) * (1-B) * Phigher(A, B) # both above cutoff\n",
" + A * B * Phigher(0, 0) # both below cutoff\n",
" + (1-A) * B * Phigher(A, 0) # A above, B below\n",
" + A * (1-B) * Phigher(0, B)) # A below, B above"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "hyfp1paSjrve",
"outputId": "89efbe5b-4452-4e30-95c5-71151bcde9a9"
},
"outputs": [],
"source": [
"Pwin(0.5, 0.6)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "xDRxPTuejrve"
},
"source": [
"`Pwin` relies on a lot of algebra. Let's define a few tests to check for obvious errors:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "L3w-TzK9jrve",
"outputId": "df6f3314-014b-4f74-dac6-87b1fd11bf9d"
},
"outputs": [],
"source": [
"def test():\n",
" assert Phigher(0.5, 0.5) == Phigher(0.75, 0.75) == Phigher(0, 0) == 0.5\n",
" assert Pwin(0.5, 0.5) == Pwin(0.75, 0.75) == 0.5\n",
" assert Phigher(.6, .5) == 0.6\n",
" assert Phigher(.5, .6) == 0.4\n",
" return 'ok'\n",
"\n",
"test()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "C9hZRUIzjrve"
},
"source": [
"Let's repeat the calculation with our new, exact `Pwin`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "OfxAXLl-jrve",
"outputId": "032bba6b-ca9d-4b28-ca9a-0b65f8e6288f"
},
"outputs": [],
"source": [
"top(10, arange(0.5, 1.0, 0.01))"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "fDDRaiLDjrve"
},
"source": [
"It is good to see that the simulation and the exact calculation are in rough agreement; that gives me more confidence in both of them. We see here that 0.62 defeats all the other cutoffs, and 0.61 defeats all cutoffs except 0.62. The great thing about the exact calculation code is that it runs fast, regardless of how much accuracy we want. We can zero in on the range around 0.6:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "rfEjXT5ojrve",
"outputId": "9f695209-61a6-4a45-8732-65d1320a28c1"
},
"outputs": [],
"source": [
"top(10, arange(0.5, 0.7, 0.001))"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "9xRR0ymyjrve"
},
"source": [
"This says 0.618 is best, better than 0.620. We can get even more accuracy:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "desDZPZmjrve",
"outputId": "e585de4d-e163-48a3-cce7-23c6d4a94b06"
},
"outputs": [],
"source": [
"top(10, arange(0.617, 0.619, 0.000001))"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "M4CYznyijrve"
},
"source": [
"So 0.618034 is best. Does that number [look familiar](https://en.wikipedia.org/wiki/Golden_ratio)? Can we prove that it is what I think it is?\n",
"\n",
"To understand the strategic possibilities, it is helpful to draw a 3D plot of `Pwin(A, B)` for values of *A* and *B* between 0 and 1:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "laEkc5E4jrve",
"outputId": "d81f3733-3d57-4db7-aa76-49dd2497e3e1"
},
"outputs": [],
"source": [
"import numpy as np\n",
"from mpl_toolkits.mplot3d.axes3d import Axes3D\n",
"\n",
"def map2(fn, A, B):\n",
" \"Map fn to corresponding elements of 2D arrays A and B.\"\n",
" return [list(map(fn, Arow, Brow))\n",
" for (Arow, Brow) in zip(A, B)]\n",
"\n",
"cutoffs = arange(0.00, 1.00, 0.02)\n",
"A, B = np.meshgrid(cutoffs, cutoffs)\n",
"\n",
"fig = plt.figure(figsize=(10,10))\n",
"ax = fig.add_subplot(1, 1, 1, projection='3d')\n",
"ax.set_xlabel('A')\n",
"ax.set_ylabel('B')\n",
"ax.set_zlabel('Pwin(A, B)')\n",
"ax.plot_surface(A, B, map2(Pwin, A, B));"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "BFnzRWmcjrvf"
},
"source": [
"What does this [Pringle of Probability](http://fivethirtyeight.com/features/should-you-shoot-free-throws-underhand/) show us? The highest win percentage for **A**, the peak of the surface, occurs when *A* is around 0.5 and *B* is 0 or 1. We can confirm that, finding the maximum `Pwin(A, B)` for many different cutoff values of `A` and `B`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "srXycT1Ojrvf"
},
"outputs": [],
"source": [
"cutoffs = (set(arange(0.00, 1.00, 0.01)) |\n",
" set(arange(0.500, 0.700, 0.001)) |\n",
" set(arange(0.61803, 0.61804, 0.000001)))\n",
"\n",
"def Pwin_summary(A, B): return [Pwin(A, B), 'A:', A, 'B:', B]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "j6K7CT0djrvf",
"outputId": "6ebd13ae-4657-49da-9a02-b0e68242dca5"
},
"outputs": [],
"source": [
"max(Pwin_summary(A, B) for A in cutoffs for B in cutoffs)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "8DgEImtDjrvf"
},
"source": [
"So **A** could win 62.5% of the time if only **B** would chose a cutoff of 0. But, unfortunately for **A**, a rational player **B** is not going to do that. We can ask what happens if the game is changed so that player **A** has to declare a cutoff first, and then player **B** gets to respond with a cutoff, with full knowledge of **A**'s choice. In other words, what cutoff should **A** choose to maximize `Pwin(A, B)`, given that **B** is going to take that knowledge and pick a cutoff that minimizes `Pwin(A, B)`?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "1tJVzNUqjrvf",
"outputId": "ca6bdc46-fd4d-4fa4-d069-fcf173ba086a"
},
"outputs": [],
"source": [
"max(min(Pwin_summary(A, B) for B in cutoffs)\n",
" for A in cutoffs)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "Jq-yVbGIjrvf"
},
"source": [
"And what if we run it the other way around, where **B** chooses a cutoff first, and then **A** responds?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "vZWIoYUDjrvf",
"outputId": "40b29cb2-4092-4f30-c67c-8ac9468909c1"
},
"outputs": [],
"source": [
"min(max(Pwin_summary(A, B) for A in cutoffs)\n",
" for B in cutoffs)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "MwmcWGBujrvf"
},
"source": [
"In both cases, the rational choice for both players in a cutoff of 0.618034, which corresponds to the \"saddle point\" in the middle of the plot. This is a *stable equilibrium*; consider fixing *B* = 0.618034, and notice that if *A* changes to any other value, we slip off the saddle to the right or left, resulting in a worse win probability for **A**. Similarly, if we fix *A* = 0.618034, then if *B* changes to another value, we ride up the saddle to a higher win percentage for **A**, which is worse for **B**. So neither player will want to move from the saddle point.\n",
"\n",
"The moral for continuous spaces is the same as for discrete spaces: be careful about defining your sample space; measure carefully, and let your code take care of the rest."
]
}
],
"metadata": {