{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# FPS Victory Bayesian Model" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "WuSHcQ1LBJQC" }, "source": [ "In this notebook, we describe a very simple Bayesian network implemented using pgmpy library, that can be used to assess victory ofan 1v1 match, given two opponents, including a penalty/bonus mechanism for situations when the two opponents’ skills are disproportionate." ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "yaJhi8VRN8sy" }, "source": [ "# Imports" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "id": "wYR_sV3bCEJe" }, "outputs": [], "source": [ "!pip install pgmpy" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "id": "hgIFY1FlB4_3" }, "outputs": [], "source": [ "from pgmpy.models import BayesianModel\n", "from pgmpy.factors.discrete import TabularCPD" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "WkTLQIt8N_Na" }, "source": [ "# Model Definition" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "r5hXU29FI9Jb" }, "source": [ "As our model, we'll use a Bayesian Network inspired by [Microsoft's TrueSkill](https://www.microsoft.com/en-us/research/project/trueskill-ranking-system/), where we'll only have discrete variables (as pgmpy doesn't properly support continuous variables at the time of writing this) with a much smaller domain, we'll also limit our model to a 1v1 situation." ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "xTlbAoTYDW8N" }, "source": [ "\n", "We define our model by first defining the nodes:\n", "\n", "* **Player’s W/L ≥ 50%**: whether the player has a Win/Lose ratio in\n", "above 50 % of matches (i.e. wins more than half of the matches).\n", "* **Player’s K/D ratio**: kill/death ratio of a player, telling us how\n", "many other players a player usually kills before dying. We consider\n", "this quantity as a discrete random variable with three possible values:\n", "Neg for a k/d ratio < 1.0, Pos for a k/d ratio between 1.0 and 2.0,\n", "and Double for a k/d ratio > 2.0.\n", "* **Player’s Skill**: this variable is used to model a player’s skill level, being conditioned on previous two variables W/L and K/D stats. In our\n", "case, we assume three skill levels, namely: bad, average and good. The\n", "conditioning on player’s stats changes the probability of that player of\n", "being of a certain skill level (i.e. a player with a W/L < 50 and K/D\n", "< 1.0 is more likely to be assigned ”Bad” skill level).\n", "* **Player’s Performance**: this variable is used to model the fact that\n", "players do not always perform according to their skill in all of their\n", "games, in fact good players can be beaten by bad players (i.e. it was\n", "not their day, or they weren’t taking the game seriously, etc.). In our\n", "case, the domain of this variable is Bad/Good, where of course we\n", "model the fact that a player is more likely to perform well if his/her\n", "skill level is higher.\n", "* **Victory A**: this variable, conditioned on performances of both players,\n", "tells us whether player A wins or not.\n", "* **Bonus**: this variable has a domain of -1/0/1, and is used to model\n", "the impact of a player’s win. Namely, we want to reward a lower skill\n", "player if he/she beats a higher skill player, and penalize a high skill player if he/she loses against a lower skill player. This variable is thus\n", "conditioned on Skill A, Skill B and Victory A variables.\n", "\n", "Players' nodes are defined in a symmetric way, so we'll have the K/D, W/L, Skill and Performance nodes defined for player A and player B.\n", "\n", "**Note**: Our choice of values for K/D ratio, very crudely approximates the values that are generally looked upon, by players’ communities in an average FPS\n", "game (i.e. players with negative k/d ratio are considered bad, while\n", "players with a ratio above 2.0 are considered expert).\n", "\n", "Finally, we need to define connections, which we'll do as in the following image:\n", "\n", "\"Model" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "id": "sOMwhHsh8Ebx" }, "outputs": [], "source": [ "victory_model = BayesianModel([\n", " (\"WL_A\", \"Skill_A\"),\n", " (\"WL_B\", \"Skill_B\"),\n", " (\"KD_A\", \"Skill_A\"),\n", " (\"KD_B\", \"Skill_B\"),\n", " (\"Skill_A\", \"Perf_A\"),\n", " (\"Skill_B\", \"Perf_B\"), \n", " (\"Perf_A\", \"Victory_A\"),\n", " (\"Perf_B\", \"Victory_A\"),\n", " (\"Skill_A\", \"Bonus\"),\n", " (\"Skill_B\", \"Bonus\"),\n", " (\"Victory_A\", \"Bonus\")\n", "])" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "7kf2JOLiC6yh" }, "source": [ "# CPT Definition" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "id": "AfPcwnpTnv75" }, "outputs": [], "source": [ "# assume W/L: Neg if w/l < 50%, Pos if w/l >= 50%\n", "cpd_WL_50_A = TabularCPD(variable='WL_A', variable_card=2, values=[[0.5], [0.5]], state_names={\"WL_A\": [\"Neg\", \"Pos\"]})\n", "cpd_WL_50_B = TabularCPD(variable='WL_B', variable_card=2, values=[[0.5], [0.5]], state_names={\"WL_B\": [\"Neg\", \"Pos\"]})\n", "\n", "# assume KD: Neg if kd < 1.0, Pos if 1<=kd<=2.0, Double if kd>2.0\n", "cpd_KD_A = TabularCPD(variable='KD_A', variable_card=3, values=[[0.4], [0.4], [0.2]], state_names={'KD_A': ['Neg', 'Pos', 'Double']})\n", "cpd_KD_B = TabularCPD(variable='KD_B', variable_card=3, values=[[0.4], [0.4], [0.2]], state_names={'KD_B': ['Neg', 'Pos', 'Double']})\n", "\n", "cpd_Skill_A = TabularCPD(variable='Skill_A', variable_card=3, \n", " values=[[0.93, 0.5, 0.1, 0.6, 0.2, 0.05],\n", " [0.05, 0.4, 0.6, 0.35, 0.7, 0.1],\n", " [0.02, 0.1, 0.3, 0.05, 0.1, 0.85]],\n", " evidence=['WL_A', 'KD_A'],\n", " evidence_card=[2, 3],\n", " state_names={\"Skill_A\": ['1', '2', '3'], 'KD_A': ['Neg', 'Pos', 'Double'], \"WL_A\": [\"Neg\", \"Pos\"]}\n", ")\n", "\n", "cpd_Skill_B = TabularCPD(variable='Skill_B', variable_card=3, \n", " values=[[0.93, 0.5, 0.1, 0.6, 0.2, 0.05],\n", " [0.05, 0.4, 0.6, 0.35, 0.7, 0.1],\n", " [0.02, 0.1, 0.3, 0.05, 0.1, 0.85]],\n", " evidence=['WL_B', 'KD_B'],\n", " evidence_card=[2, 3],\n", " state_names={\"Skill_B\": ['1', '2', '3'], 'KD_B': ['Neg', 'Pos', 'Double'], \"WL_B\": [\"Neg\", \"Pos\"]}\n", ")\n", "\n", "cpd_Perf_A = TabularCPD(variable='Perf_A', variable_card=2, \n", " values=[[0.6, 0.35, 0.15],\n", " [0.4, 0.65, 0.85]],\n", " evidence=['Skill_A'],\n", " evidence_card=[3],\n", " state_names = {'Perf_A': ['Bad', 'Good'], \"Skill_A\": ['1', '2', '3']}\n", ")\n", "\n", "cpd_Perf_B = TabularCPD(variable='Perf_B', variable_card=2, \n", " values=[[0.6, 0.35, 0.15],\n", " [0.4, 0.65, 0.85]],\n", " evidence=['Skill_B'],\n", " evidence_card=[3],\n", " state_names = {'Perf_B': ['Bad', 'Good'], \"Skill_B\": ['1', '2', '3']}\n", ")\n", "\n", "cpd_Victory_A = TabularCPD(variable='Victory_A', variable_card=2, \n", " values=[[0.5, 0.7, 0.3, 0.5],\n", " [0.5, 0.3, 0.7, 0.5]],\n", " evidence=['Perf_A', 'Perf_B'],\n", " evidence_card=[2, 2],\n", " state_names = {'Victory_A': ['No', 'Yes'], 'Perf_A': ['Bad', 'Good'], 'Perf_B': ['Bad', 'Good']}\n", ")\n", "\n", "cpd_Bonus = TabularCPD(variable='Bonus', variable_card=3, \n", " values=[[0.05, 0.0, 0.0, 0.6, 0.05, 0.0, 0.8, 0.6, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n", " [0.95, 1.0, 1.0, 0.4, 0.95, 1.0, 0.2, 0.4, 0.9, 0.5, 0.35, 0.1, 1.0, 0.5, 0.35, 1.0, 0.95, 0.8],\n", " [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.65, 0.9, 0.0, 0.5, 0.65, 0.0, 0.05, 0.2]],\n", " evidence=['Victory_A', 'Skill_A', 'Skill_B'],\n", " evidence_card=[2, 3, 3],\n", " state_names = {'Bonus': [\"-1\", \"0\", \"1\"], 'Victory_A': ['No', 'Yes'], 'Skill_A': ['1', '2', '3'], 'Skill_B': ['1', '2', '3']}\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "id": "acYvmriHtS1C" }, "outputs": [], "source": [ "victory_model.add_cpds(cpd_WL_50_A, cpd_WL_50_B, cpd_KD_A, cpd_KD_B, cpd_Skill_A, cpd_Skill_B, cpd_Perf_A, cpd_Perf_B, cpd_Victory_A, cpd_Bonus)\n", "victory_model.check_model()" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "wo1_EhQyODVd" }, "source": [ "# CPDs viewing" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "id": "gVDmw4Cot0t-" }, "outputs": [], "source": [ "print(cpd_Skill_A)\n", "print(cpd_Perf_A)\n", "print(cpd_Victory_A)\n", "print(cpd_Bonus)" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "B1U13MmvOPtX" }, "source": [ "# Independences viewing" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "id": "B5ieJbeyzPJh" }, "outputs": [], "source": [ "print(victory_model.local_independencies('Victory_A'))\n", "print(victory_model.local_independencies('Skill_A'))\n", "print(victory_model.local_independencies('Perf_A'))" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "mXhTLmkjOI9K" }, "source": [ "# Inference queries" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "id": "PM1lzmLqvTH1" }, "outputs": [], "source": [ "from pgmpy.inference import VariableElimination\n", "infer = VariableElimination(victory_model)" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "fxYv1TA7Ly7p" }, "source": [ "The distribution over Victory_A without any evidence, is in fact 50/50" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "id": "AeCW21y1v659" }, "outputs": [], "source": [ "victory_A_dist = infer.query(['Victory_A'])\n", "print(victory_A_dist)" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "XfHpNA_CL5lG" }, "source": [ "If we know that KD of A is > 2.0 (i.e. he usually kills at least 2 players before dying, then his chance to win rises)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "id": "rsJPqxPqvVwt" }, "outputs": [], "source": [ "print(infer.query(['Victory_A'], evidence={'KD_A': 'Double'}))" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "y1c8yxg7MAG2" }, "source": [ "If we also add the fact that his opponent loses more than half matches, and that is bad overall" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "id": "sKBsftMpxy9A" }, "outputs": [], "source": [ "print(infer.query(['Victory_A'], evidence={'KD_A': 'Double', \"WL_B\": 'Neg'}))\n", "print(infer.query(['Victory_A'], evidence={'KD_A': 'Double', \"WL_B\": 'Neg', 'KD_B': 'Neg'}))\n", "print(infer.query(['Victory_A'], evidence={'WL_A': 'Pos', 'KD_A': 'Double', \"WL_B\": 'Neg', 'KD_B': 'Neg'}))" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "5R0_Mj-pMClK" }, "source": [ "If we know that A loses most of the games, but has double KD, the probability of winning drops" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "id": "IiGexNjCyYtR" }, "outputs": [], "source": [ "print(infer.query(['Victory_A'], evidence={'WL_A': 'Neg', 'KD_A': 'Double', \"WL_B\": 'Neg', 'KD_B': 'Neg'}))" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "4HDrhMWSMIdx" }, "source": [ "However, if we know that B performs poorly, then A is more likely to win" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "id": "cRKnf9B1yqhU" }, "outputs": [], "source": [ "print(infer.query(['Victory_A'], evidence={'WL_A': 'Neg', 'KD_A': 'Double', \"WL_B\": 'Neg', 'KD_B': 'Neg', 'Perf_B': \"Bad\"}))\n", "# But since if we know the performance, we don't care about the stats and skill, we get the same probability,\n", "# thus: Victory_A _|_ WL_B, KD_B | Perf_B\n", "print(infer.query(['Victory_A'], evidence={'WL_A': 'Neg', 'KD_A': 'Double', 'Perf_B': \"Bad\"}))" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "olk-nkmbMLhB" }, "source": [ "What's the probability that A performed well if he won?" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "id": "TsihhM0Azb_u" }, "outputs": [], "source": [ "print(infer.query(['Perf_A'], evidence={'Victory_A': \"Yes\"}))\n", "# What's the probability that A was skilled 3 if he won?\n", "# Surprisingly, we don't get to know much about Skill of A even if we know he won against a skilled player\n", "print(infer.query(['Skill_A'], evidence={'Victory_A': \"Yes\", \"Skill_B\": \"3\"}))" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "acA-wBp_MQHs" }, "source": [ "However, if we know that the bonus was 0, it is most likely because he won against an oponent of same level, thus B of skill 3, thus A is more likely to be of skill 3" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "id": "O34zEil348ue" }, "outputs": [], "source": [ "print(infer.query(['Skill_A'], evidence={'Victory_A': \"Yes\", \"Skill_B\": \"3\", \"Bonus\": \"0\"}))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "id": "ECKluxJr0rwD" }, "outputs": [], "source": [ "print(infer.map_query(['Victory_A', 'Bonus'], evidence={'KD_A': \"Double\", \"KD_B\": \"Pos\"}))\n", "print(infer.map_query(['Victory_A', 'Bonus'], evidence={'WL_A': \"Neg\", \"KD_B\": \"Pos\"}))" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "GN7pTOr7MWLq" }, "source": [ "If a \"bad\" player beats a better one, then he will get bonus" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": {}, "colab_type": "code", "id": "5go2S4nr7NxV" }, "outputs": [], "source": [ "print(infer.map_query(['Bonus'], evidence={'Victory_A': \"Yes\" ,'KD_A': \"Neg\", \"WL_A\": \"Neg\", \"KD_B\": \"Pos\"}))" ] } ], "metadata": { "colab": { "collapsed_sections": [], "name": "UncertaintyReasoningProjectGIT.ipynb", "private_outputs": true, "provenance": [] }, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.9" }, "latex_envs": { "LaTeX_envs_menu_present": true, "autoclose": false, "autocomplete": true, "bibliofile": "biblio.bib", "cite_by": "apalike", "current_citInitial": 1, "eqLabelWithNumbers": true, "eqNumInitial": 1, "hotkeys": { "equation": "Ctrl-E", "itemize": "Ctrl-I" }, "labels_anchors": false, "latex_user_defs": false, "report_style_numbering": false, "user_envs_cfg": false } }, "nbformat": 4, "nbformat_minor": 1 }