Simpy验证赌场必胜法

Simpy验证赌场必胜法

Louis 1433 2021-02-26

许久没有更新过技术学习的文章,主要是觉得最近工作上了解的内容比较简单重复,没什么好写的。然后草稿箱压了一篇关于MQTT物联网平台搭建的文章,由于比较局域懒,一直没有进行材料的整理,于是导致技术学习区的文章一直空缺。

由于是一篇技术笔记的文章,所以前面会有一点关于仿真框架的介绍。对写代码不感兴趣的朋友可以直接跳到后面有趣的部分。

Simpy概览

本文介绍的是一套工业流程的仿真框架,也可以使用在物流,餐厅等大量需要优化的场景,比如:

  • 如何最优化快递分拣人员的排班表以满足双十一突发的快递件量
  • 如何估算餐厅在用餐高峰的排队时长
  • 估算特定工序下,工厂生产所需要的物料成本/人力成本/时间成本

这类场景无法通过常规算法求出最优解, 但是我们可以通过大量业务实践中总结出一些接近的次优解。

实际生产中,随时调整厂房的生产线来试验最优解是非常昂贵的。引进仿真技术,可以给业务研究员无限的自由度去调整验证不同的优化方案。仿真的成本无非是计算机的算力,以及程序员编写业务逻辑的时间。

行业上其实已经存在一些工业仿真软件。但这类仿真软件往往针对某些特定场景高度定制化,数据埋点往往不全,缺乏通用的数据库接口,难以结合真实业务产生的数据进行仿真,这样就失去与真实业务进行比较的可能。

利用 SimPy 我们可以构建一套完全开源的仿真方案,可以完全私有定制业务场景。利用 Python 强大的生态,仿真数据从来源到输出分析,可以衔接所有开源流行的数据分析框架。

SimPy 是一个基于标准 Python 以进程为基础的离散事件仿真框架。

SimPy 中的进程是由 Python 生成器构成,生成器的特性可以模拟具有主动性的物件,比如客户、汽车、或者中介等等。SimPy也提供多种类的共享资源(shared resource)来描述拥挤点(比如服务器、收银台和隧道)。

仿真运行速度非常快,仿真中的模拟时间长短不影响仿真运行效率,仿真中的模拟时间单位可以任意指定,一秒、一年、一小时都是允许的。

Simpy核心概念

Environment

SimPy 是离散事件驱动的仿真库。所有活动部件,例如车辆、顾客,、即便是信息,都可以用 process (进程) 来模拟。这些 process 存放在 environment (环境) 。所有 process 之间,以及与environment 之间的互动,通过 event (事件) 来进行.

process 表达为 generators (生成器), 构建event(事件)并通过 yield 语句抛出事件。

当一个进程抛出事件,进程会被暂停,直到事件被激活(triggered)。多个进程可以等待同一个事件。 SimPy 会按照这些进程抛出的事件激活的先后, 来恢复进程。

其实中最重要的一类事件是 Timeout, 这类事件允许一段时间后再被激活, 用来表达一个进程休眠或者保持当前的状态持续指定的一段时间。这类事件通过 Environment.timeout来调用。

Resource 和 Store

Resource/Store 也是另外一类重要的核心概念, 但凡仿真中涉及的人力资源以及工艺上的物料消耗都会抽象用 Resource 来表达, 主要的 methodrequest. Store 处理各种优先级的队列问题, 表现跟 queue 一致, 通过 method get / put 存放 item

Store - 抽象队列

  • simpy.Store - 存取 item 遵循仿真时间上的先到后到
  • simpy.PriorityStore - 存取 item 遵循仿真时间上的先到后到同时考虑人为添加的优先级
  • simpy.FilterStore - 存取 item 遵循仿真时间上的先到后到, 同时队列中存在分类, 按照不同类别进行存取
  • simpy.Container - 表达连续/不可分的物质, 包括液体/气体的存放, 存取的是一个 float 数值

Resource - 抽象资源

  • simpy.Resource - 表达人力资源或某种限制条件, 例如某个工序可调用的工人数, 可以调用的机器数
  • simpy.PriorityResource - 兼容Resource的功能, 添加可以插队的功能, 高优先级的进程可以优先调用资源, 但只能是在前一个被服务的进程结束以后进行插队
  • simpy.PreemptiveResource - 兼容Resource的功能, 添加可以插队的功能, 高优先级的进程可以打断正在被服务的进程进行插队。

赌场必胜法

来了来了,赌场发家致富的方法。

先假设有一个赌场,赌博规则如下:

  • 玩家有48%的概率赢
  • 每轮玩家需要花钱进行下注
  • 如果输,玩家会输掉下注的钱;如果赢,玩家会得到下注金额2倍的奖励

玩家的策略:

  • 如果输了,就以上次赌注的两倍进行下一次赌博
  • 如果赢了,将赌注重置成1,然后重新赌博
import simpy
import random
import matplotlib.pyplot as plt
import numpy as np

# 初始资金一万元
funding = 10000
# 初始赌注一块钱
Bet = 1


# 这是一个赌场,有45%的概率赢钱
def casino():
    return random.random() < 0.45


funding_list = []


# 下注的行为
def gambling(env):
    global Bet, funding, funding_list
    while True:
        if casino():
            # 赢了,赢得本金,重置赌注
            funding += Bet
            print("win at %d , funding is %d" % (env.now, funding))
            Bet = 1
        else:
            # 输了,赔掉赌注,下次下注翻倍
            funding -= Bet
            print("lost at %d , funding is %d" % (env.now, funding))
            Bet = Bet * 2
            # 输光就没得玩了
        if funding <= 0:
            print("lost all at %d" % env.now)
            break
        funding_list.append(funding)
        yield env.timeout(1)


env = simpy.Environment()
env.process(gambling(env))
# 进行100轮赌注
env.run(1000)

plt.figure()
x = np.linspace(0, 1000, 1000)
y = np.array(funding_list)
y = np.pad(y, (0, 1000 - y.size), 'constant', constant_values=(0, 0))
plt.plot(x, y, linewidth=1.0, linestyle='-')
plt.grid()  # 生成网格
plt.xlabel("gambling_time")
plt.ylabel("funding")
plt.show()

第一个版本中没有止盈和止损策略,无论如何都要进行1000次下注,除非将钱输光。
第一次实验

效果还不错,经过连输好几次,好险,不过还是缓过来了,尖刀曲线到达7000之后,下一把就赢回来了。

第二次实验

我试了好几次都没有仿真出破产的结果,看上去在48%这个胜率下该策略还算可信。

调整了胜率为45%,同样也需要做好多次实验,才能得出一个破产的结果。

破产

来看看主角最后时刻经历了什么

win at 257 , funding is 10123
win at 258 , funding is 10124
lost at 259 , funding is 10123
win at 260 , funding is 10125
lost at 261 , funding is 10124
lost at 262 , funding is 10122
lost at 263 , funding is 10118
lost at 264 , funding is 10110
lost at 265 , funding is 10094
win at 266 , funding is 10126
lost at 267 , funding is 10125
win at 268 , funding is 10127
lost at 269 , funding is 10126
lost at 270 , funding is 10124
lost at 271 , funding is 10120
lost at 272 , funding is 10112
lost at 273 , funding is 10096
lost at 274 , funding is 10064
lost at 275 , funding is 10000
lost at 276 , funding is 9872
lost at 277 , funding is 9616
lost at 278 , funding is 9104
lost at 279 , funding is 8080
lost at 280 , funding is 6032
lost at 281 , funding is 1936
lost at 282 , funding is -6256
lost all at 282

从本金10127经历了14连输之后彻底破产。

尾声

本来这篇文章的案例是一个工业流程的时序仿真,但是代码似乎涉及保密协议,而且又比较丑,就不拿出来丢人了。这是最近在视频网站上看到的一个赌身家的办法。虽然曲线似乎比较平稳(除了那几个吓人的脉冲之外),但是本文的初衷不是劝大家进行赌博,而是使用一些代码方法或者数学方法进行结论验证,特别好玩。

参考文档:

Python SimPy 仿真系列文章 (1) - 知乎 (zhihu.com)

Overview — SimPy 4.0.2.dev1+g2973dbe documentation