2024年10月

托福这劳什子终于画上了句号,上个月完全有20天的时间属于是白白浪费掉了(# ̄~ ̄#),接下来我会继续更新概率论沉思录专栏并把之前的拖更补上(还得兼顾学日语,给自己加油!)。

导言

概率论只不过是把常识用数学公式表达了出来。

——拉普拉斯(Laplace, 1819)

我们在上一篇博客
《概率论沉思录:合情推理》
中介绍了合情推理
[1][2]
中所要满足的合情条件,即:

\[\begin{aligned}
(Ⅰ)& \space 用实数表示合情程度(数值化条件)。\\
(Ⅱ)& \space 定性地与常识相符(类直觉条件)。\\
(Ⅲ)& \space 具有一致性(一致性条件)。
\end{aligned}
\]

其中,第
\((Ⅲ)\)
点的一致性条件又具体包含下列三个含义:

\[\begin{aligned}
& (Ⅲ\text{a}) \space 如果可通过多种方式推出结论,那么每种方式都须给出相同结果(非路径依赖性)。\\
& (Ⅲ\text{b}) \space 机器人总是考虑与问题有关的所有证据,而不会随意忽略信息(非意识形态性)。\\
& (Ⅲ\text{c}) \space 机器人总是通过分配相同的合情性来表示相同的知识状态(全同性)。
\end{aligned}
\]

上述条件都是定性的。在这一篇博客中我们将看到,上述条件皆不是空穴来风,而且不多不少刚刚好。一旦我们导出了满足上述合情条件的合情推理定量规则,我们就会发现,我们实际上就得到了概率的原始定义(乘法规则 + 加法规则 + 无差别原则)。

其中,条件
\((Ⅰ)(Ⅱ)(Ⅲ\text{a})\)
是机器人大脑的“结构性”条件,决定了
推理机器人大脑的内部运作规则
(这里的“大脑”可以指电路 / 神经网络 / ...),导出概率的
乘法规则(product rule)

\[p(AB\mid C) = p(A\mid C)p(B\mid AC)=p(B\mid C)p(A\mid BC)
\]


加法规则(sum rule)

\[p(A\mid B) + p(\bar{A}\mid B) = 1
\]


\(p(x)\)
是任意连续单调递增函数,值域为
\(0\leqslant p(x) \leqslant 1\)

而条件
\((Ⅲ\text{b})(Ⅲ\text{c})\)
是“接口”条件,进一步建立了
推理机器人与客观世界的联系
。其中,
\((Ⅲ\text{c})\)
导出概率的
无差别原则(principle of indifference)

\[p(A_i\mid B) = \frac{1}{n}, \quad 1 \leqslant i \leqslant n
\]


\(\{A_1, \cdots, A_n\}\)
为互斥且穷尽的命题集合,即背景信息
\(B\)
决定了其中一个且仅一个必须为真)

接下来我们来看概率的乘法规则、加法规则和无差别原则究竟是怎样由合情条件导出的。

1 乘法规则

我们首先寻找将逻辑积
\(AB\)
的合情性和
\(A\)

\(B\)
的合情性相关联的规则,也即找到
\(AB\mid C\)
的表达式。我们将机器人判定
\(AB\)
为真的过程分解为对
\(A\)

\(B\)
进行分步决策的过程,也即:

  1. 判定
    \(B\)
    为真;

    \(B\mid C\)

  2. 接受
    \(B\)
    为真,判定
    \(A\)
    真。

    \(A\mid BC\)

(先判定
\(A\)
为真的情况同理。我们将对应于每个步骤的合情性添加在了末尾)

我们用自然语言来解释一下。要想使命题
\(AB\)
为真,命题
\(B\)
必为真(根据逻辑积的定义),因此我们需要合情性
\(B\mid C\)
。接着,我们需要进一步判定
\(A\)
为真,此时我们需要合情性
\(A\mid BC\)
而非
\(A\mid C\)
。这是因为,如果机器人知道
\(B\)
为假,则无论
\(A\)
的合情性如何(即
\(A\mid \bar{B}C\)
),
\(AB\)
都肯定为假。而一旦机器人知道
\(A\mid BC\)
,它就不再需要知道
\(A\mid C\)
了(这不会增加什么关于
\(AB\)
的新信息)。

此外,机器人并不需要知道
\(A\mid B\)

\(B\mid A\)
。因为在不知道信息
\(C\)
的情况下,
\(A\)

\(B\)
可能具有的合情性与机器人知道
\(C\)
为真时的判断无关。例如,如果机器人已经知道地球是圆的,那么在对今天的宇宙学问题做判断时,就不需要考虑如果不知道地球是圆的,它可能具有的观点(即考虑额外的可能性)。

(当然,由于逻辑积是可交换的,即
\(AB=BA\)
,所以我们可在上述语句中交换
\(A\)

\(B\)
,就得到了
\(BA\mid C\)

\(=AB\mid C\)
)。机器人可以以任意一种方式获得相同的
\(AB\mid C\)
的值,即一致性条件
\((Ⅲ\text{a})\)
:非路径依赖性)。

更进一步,我们有以下命题:

命题1
\(AB\mid C\)

\(B \mid C\)

\(A \mid BC\)
的某个函数,即:

\[AB \mid C = F[(B \mid C), (A\mid BC)]\tag{1}
\]

(同样也应为
\(A\mid C\)

\(B\mid AC\)
的某个函数)

如果对上述推理有疑问的话,不妨考虑下其它的替代方案,比如
\(AB\mid C=F[(B \mid C), (A\mid C)]\)
。但这种形式不能满足定性合情条件
\((Ⅱ)\)
:类直觉条件,也即违反人类常识。因为给定
\(C\)

\(A\)
可能很合情,
\(B\)
也可能很合情,但
\(AB\)
有可能很不合情。比如一个人左眼是蓝色的很合情,右眼是棕色的也很合情,但既有蓝色左眼又有棕色右眼就很不合情了。


关于这一点,已经被概率论/因果推断“剧透”过的同学应该就知道,左眼是蓝色与右眼是棕色这两个命题在因果上确实是独立的,但是在概率上仍然是不独立的。这是因为它们都有控制眼睛颜色的遗传基因来做为共变因(所谓混淆因子,confounder),以构成“
\(A\)
真则
\(B\)
假”的逻辑相关性(而非物理因果性)。

接下来我们来看看为满足我们的合情条件,结合函数
\(F(x, y)\)
需要具有怎样的性质(下面不妨设
\(x=(B\mid C)\)
,
\(y=(A\mid BC)\)
)。

我们先来考虑定性需求中的
\((Ⅱ)\)
:类直觉条件。给定先验信息的变化
\(C\rightarrow C^{\prime}\)
,使
\(B\)
变得更合情,但
\(A\)
不变:

\[B\mid C^{\prime} > B \mid C, \\
A\mid BC^{\prime} = A\mid BC
\]

常识要求
\(AB\)
只能变得更合情,而不能相反:

\[AB \mid C^{\prime} \geqslant AB \mid C
\]

等号当且仅当
\(A\mid BC\)
对应于不可能时成立。这要求
\(F(x, y)\)
是关于
\(x\)
的单调递增函数且当且仅当
\(y\)
不可能时函数关于
\(x\)
偏导数为0。同理,
\(F(x, y)\)
还需要是关于
\(y\)
的单调递增函数且当且仅当
\(x\)
不可能时函数关于
\(y\)
偏导数为0。此外,函数
\(F(x, y)\)
还必须是连续的,否则
\((1)\)
式右侧的一个合情性值的小幅增大也可能导致
\(AB\mid C\)
的大幅增大。

综上所述,我们有以下命题:

命题2
\(F(x, y)\)
必须是
\(x\)

\(y\)
的连续单调递增函数。如果假设它是可微的(虽然不必要,但可简化我们的推导),我们有

\[F_1(x, y) \equiv \frac{\partial F}{\partial x} \geqslant 0, \quad F_2(x, y) \equiv \frac{\partial F}{\partial y} \geqslant 0 \tag{2}
\]

其中第一个式子等号当且仅当
\(y\)
表示不可能时成立。第二个式子等号当且仅当
\(x\)
表示不可能时成立(我们这里用
\(F_i\)
表示关于
\(F\)
的第
\(i\)
个参数的微分)。

接下来,我们施加“结构一致性”合情条件
\((Ⅲ\text{a})\)
:非路径依赖性。比如,对于合情性
\(ABC\mid D\)
,因为由布尔代数的结合性有
\(ABC=(AB)C=A(BC)\)
,我们需要满足不管根据何种顺序计算合情性,都会得到相同的结果。比如,一种可能的顺序是先认为
\(BC\)
是一个命题,然后重复两次应用式子
\((1)\)

\[(ABC \mid D) = F[(BC \mid D), (A\mid BCD)] = F\{F[(C\mid D), (B\mid CD)], (A\mid BCD)\}
\]

另外一种可能的顺序是把
\(AB\)
当成一个命题:

\[(ABC \mid D) = F[(C \mid D), (AB\mid CD)] = F\{(C\mid D), F[(B\mid CD), (A\mid BCD)]\}
\]

这两个顺序所导出的合情性结果必须相等。此时,我们有下列命题:

命题3
机器人进行一致性推理的必要条件是,函数必须满足方程

\[F[F(x, y), z] = F[x, F(y, z)] \tag{3}
\]

阿贝尔最早在书中使用了这个方程,奥采尔将其称之为“结合方程”(The Associativity Equation)。

根据上述函数方程,最终我们可以证明下列结论(证明详情参见原书,书中假定了
\(F\)
的可微性):

结论1
如果要满足函数方程
\((3)\)
,那么我们要寻找的关系就必须采取如下函数形式:

\[w(AB\mid C) = w(A\mid BC)w(B\mid C) = w(B\mid AC)w(A\mid C)\tag{4}
\]

我们将其称之为
乘法规则(product rule)
。这里
\(w(x)\)
为满足形式

\[w(x) \equiv \exp \left\{ \int^x \frac{\mathrm{d} x}{H(x)}\right\}
\]

的连续单调正值函数(到目前为止,它可以递增且可以递减,且取值任意),其中积分没有下限,
\(H(x)\)
为任意函数。


\((4)\)
是要达成一致性合情条件
\((Ⅲ\text{a})\)
:非路径依赖性而必须要满足的必要条件。用数学归纳法也可证明,式
\((4)\)
对任意数量的命题也都适用(如
\(ABCDEFG\mid H\)
)。

事实上,除了连续单调递减之外,合情条件
\((Ⅱ)\)
:类直觉条件对函数
\(w(x)\)
施加了额外的条件。例如在式
\((4)\)
的第一种形式中,我们现在假设当给定
\(C\)

\(A\)
是确定的,那么在由
\(C\)
的知识产生的“逻辑环境”中,命题
\(AB=B\)

\(AB\)
为真当且仅当
\(B\)
为真)根据我们在上一章中讨论的最原始的公理,相同真值的命题必定有相同的合情程度,即

\[AB\mid C = B\mid C
\]

除此之外,我们根据常识还需要有

\[A\mid BC = A\mid C
\]

因为给定
\(C\)

\(A\)
已经确定了(即
\(C\)
蕴含
\(A\)
),那么当给出任意与
\(C\)
不矛盾的其它信息
\(B\)
时,
\(A\)
仍然是确定的。


已经被概率论“剧透”过的同学应该就知道,给定
\(C\)

\(A\)
是确定的
\(\Rightarrow\)
给定
\(C\)
时,
\(A\)

\(B\)
条件独立。

综上所述,式
\((4)\)
变为

\[w(B\mid C) = w(A\mid C)w(B\mid C)
\]

无论
\(B\)
对机器人多么合情或不合情,它都必须成立。所以我们有以下命题:

命题4
函数
\(w(x)\)
还必须要具有如下性质:

\[\text{确定性由 } w(A\mid C)=1 \text{ 表示}\tag{5}
\]

反过来,假设当给定
\(C\)

\(A\)
是不可能的(即
\(C\)
蕴含
\(\overline{A}\)
),此时式
\((4)\)
变为

\[w(A \mid C) = w(A\mid C)w(B\mid C)
\]

无论
\(B\)
具有怎样的合情性,这个等式都必须成立。只有两个可能的
\(w(A\mid C)\)
值满足这个条件:
\(0\)

\(+\infin\)

\(-\infin\)
被排除了,否则根据连续性,
\(w(B\mid C)\)
必须能够取负值,这与上式矛盾)。因此,我们有以下命题:

命题5
函数
\(w(x)\)
满足:

\[\text{不可能由 } w(A\mid C)=0 \text{ 或 } +\infin\text{ 表示}\tag{6}
\]

综上所述,
\(w(x)\)
除了必须满足是连续单调正值函数外,根据合情条件
\((Ⅱ)\)
:类直觉条件,它还需要满足下列要求:如果是增函数,则范围是从
\(0\)
(不可能)到
\(1\)
(确定);如果是减函数,它的范围必须是从
\(+\infin\)
(不可能)到
\(1\)
(确定)(到目前为止,我们的条件还没说明它如何在这些范围内变化)。

这两种可能的表示方式在内容上没什么不同。给定符合上述标准并用
\(+\infin\)
表示不可能的任意单减函数
\(w_1(x)\)
,我们同样可以定义同样符合上述标准并用
\(0\)
表示不可能的单增函数
\(w_2(x)\equiv 1/ w_1(x)\)
。因此,不失一般性,我们现在选择第一种形式。于是,我们有下列命题:

命题6
\(w(x)\)
为满足下列要求的函数:

\[0 \leqslant w(x) \leqslant 1, \quad \text{且}w(x)\text{连续单调递增} \tag{7}
\]

不过到目前为止,除了上述条件之外,
\(w(x)\)
还是具有任意性。

2 加法规则

接下来我们来进一步对
\(w(x)\)
加以限制。由于我们现在考虑的命题属于亚里士多德逻辑类型,他们必须是非真即假的,其逻辑积
\(A\overline{A}\)
总是假的(无矛盾律),逻辑和
\(A+\overline{A}\)
总是真的(排中律)。这也就是说,
\(A\)
为假的合情性必须在某种程度上取决于它为真的合情性。如果我们定义
\(u\equiv w(A\mid B)\)

\(v\equiv w(\overline{A}\mid B)\)
,则这意味着必定存在某种函数关系

\[v = S(u)
\]

显然,如果我们要满足合情条件
\((Ⅱ)\)
:类直觉条件,则必有下列命题:

命题7
\(S(u)\)

\(0\leqslant u \leqslant 1\)
的连续单调递减函数,并且有极值

\[S(0)=1, S(1)=0 \tag{8}
\]

但是进一步我们会发现,它不能是具有这些属性的任意函数,因为它还必须与
\(AB\)

\(A\overline{B}\)
的乘法规则一致:

\[\begin{aligned}
w(AB\mid C) = w(A\mid C)w(B\mid AC),\\
w(A\overline{B}\mid C) = w(A\mid C) w(\overline{B}\mid AC)
\end{aligned}
\]


\(v=S(u)\)
的关系代入上式得

\[w(AB\mid C)=w(A\mid C)S(w(\overline{B}\mid AC))=w(A\mid C) S\left[\frac{w(A\overline{B}\mid C)}{w(A\mid C)}\right]
\]

我们再次应用交换性:
\(w(AB\mid C)\)
关于
\(A\)

\(B\)
对称,因此一致性
\((Ⅲ\text{a})\)
:非路径依赖性要求

\[w(A\mid C) S\left[\frac{w(A\overline{B}\mid C)}{w(A\mid C)}\right] = w(B\mid C) S\left[\frac{w(B\overline{A}\mid C)}{w(B\mid C)}\right] \tag{9}
\]

这对于所有命题
\(A, B, C\)
都成立。特别地,给定任意新命题
\(D\)
,当
\(\overline{B}=AD\)
时上式当然也成立。此时,我们在上一篇博客
《概率论沉思录:合情推理》
中推导过下列结论:

\[A\overline{B} = \overline{B}, \quad B\overline{A} = \overline{A}
\]

这样,我们可以做如下代换:

\[\begin{aligned}
w(A\overline{B}\mid C) = w(\overline{B}\mid C)=S[w(B\mid C)],\\
w(B\overline{A}\mid C) = w(\overline{A}\mid C)=S[w(A\mid C)]
\end{aligned}
\]


\(x\equiv w(A\mid C), y=w(B \mid C)\)
,则有
\(w(A\overline{B}\mid C)=S(y)\)
,
\(w(B\overline{A}\mid C)=S(x)\)
。代入式
\((6)\)
得到下列命题:

命题8

\[xS[\frac{S(y)}{x}] = y S[\frac{S(x)}{y}],\quad 0\leqslant S(y)\leqslant x,\space 0\leqslant x \leqslant 1\tag{10}
\]

(关于这里的定义域,是因为
\(S(y)=w(\overline{B}\mid C)=w(AD\mid C)=w(A\mid C) w(D\mid AC)\)
,而
\(w(A\mid C)=x\)
,且对任意命题
\(D\)

\(0\leqslant w(D\mid AC)\leqslant 1\)
,故
\(0\leqslant S(y) \leqslant x\)
。注意,由于对称性,同样有
\(0\leqslant S(x) \leqslant y,\space 0\leqslant y \leqslant 1\)

这表明,为继续满足乘法规则,
\(S(x)\)
必须具有缩放属性。在
\(y=1\)
的特殊情况下,它变为

\[S[S(x)] = x
\]

这表明
\(S(x)\)
是一个自反函数:
\(S(x) = S^{-1}(x)\)
(即反函数和原函数相同)。因此,有
\(v=S(u)\)
则必有
\(u=S^{-1}(v)=S(v)\)
。这体现了一个明显的事实,也即
\(A\)

\(\overline{A}\)
之间的关系是自反的,至于字母和带上横线的字母哪个表示原命题,哪个表示命题的否定,都无关紧要。我们在上一篇博客定义命题的否定时就注意到了这一点(虽然当时可能还不明显)。

事实上,我们有下列命题(详细证明过程请参见原书):

命题9
满足上述条件的
\(S\)
(且满足
\(S(0)=1\)
)的唯一解是

\[S(x) = (1 - x^m)^{1/m},\quad 0\leqslant x \leqslant 1, \space 0 < m < +\infin \tag{11}
\]

反过来,我们也可以验证式
\((11)\)
是式
\((10)\)
的解。式
\((11)\)
是满足函数方程
\((10)\)
和左边界条件
\(S(0)=1\)
的最一般函数。然后,我们会发现它自动满足右边界条件
\(S(1)=0\)

由于对函数方程
\((10)\)
的推导使用了
\(\overline{B}=AD\)
的特殊选择,我们到目前为止只表明了式
\((11)\)
是满足一般的一致性要求式
\((9)\)
的必要条件。要检查其是否充分,将式
\((11)\)
代入式
\((9)\)
,我们得到

\[w^m (A\mid C) - w^m (A\overline{B}\mid C) = w^m (B\mid C) - w^m (B\overline{A}\mid C)
\]

该式可由乘法规则得到。因此,我们证明了式
\((11)\)

\(S(x)\)
在式
\((9)\)
意义下的一致性的充要条件。

到目前为止,我们的结果可总结如下:逻辑积的结合性要求合情性
\(x = A\mid B\)
的单调函数
\(w(x)\)
必须遵守乘法规则式
\((4)\)
。而我们的结果式
\((11)\)
指出,这个函数也必须遵守下列规则:

结论2
对于正数
\(m\)
,函数
\(w(x)\)
必须满足:

\[w^m(A\mid B) + w^m (\overline{A}\mid B) = 1 \tag{12}
\]

(由
\(x^m + (1 - x^m)^{\frac{1}{m} \cdot m}=1\)
得到)

我们将其称之为
加法规则(sum rule)

当然,乘法规则也可以写成

\[w^m(AB\mid C) = w^m(A\mid BC)w^m(B\mid C) = w^m(B\mid AC)w^m(A\mid C)
\]

我们发现
\(m\)
的值实际上无关紧要,因为无论
\(m\)
取什么值都可以定义一个新函数

\[p(x) \equiv w^m (x)
\]

而如果
\(w(x)\)

\(0\)

\(1\)
之间的连续单调递增函数,那么
\(w^m(x)\)
必然也满足该条件。这样,我们的规则变为

1. 乘法规则

\[p(AB\mid C) = p(A\mid C)p(B\mid AC) = p(B\mid C)p(A\mid BC) \tag{13}
\]

2. 加法规则

\[p(A\mid B) + p(\overline{A}\mid B) = 1 \tag{14}
\]

其中
\(p(x)\)
是任意连续单调递增函数,且值域为
\(0\leqslant p(x) \leqslant 1\)

除了乘法规则和加法规则之外,是否需要更多的关系来得到一套完备的合情推理规则,以便确定任意逻辑函数
\(f(A_1, \cdots, A_n)\)
的合情性呢?在乘法规则和加法规则中,我们已经得到了合取
\(AB\)
和否定
\(\overline{A}\)
的合情性公式。而由于我们在上一篇博客
《概率论沉思录:合情推理》
中已经提到,合取和否定是运算的完备集合,可以从中构造出所有逻辑函数。因此,通过反复应用乘法规则和加法规则,我们可以得到
\(A_1, \cdots, A_n\)
生成的布尔代数中任意命题的合情性。

为了证明这一点,我们首先寻求逻辑和
\(A + B\)
的公式。反复应用乘法规则和加法规则,我们可以得到

\[\begin{aligned}
p(A + B \mid C) &= 1 - p(\overline{A}\space{ }\overline{B} \mid C) \\
& = 1 - p(\overline{A}\mid C)p(\overline{B}\mid \overline{A} C)\\
& = 1 - p(\overline{A}\mid C) \left[ 1 - p(B\mid \overline{A}C)\right]\\
& = p(A\mid C) + p(\overline{A}B\mid C) \\
& = p(A\mid C) + p(B\mid C)p(\overline{A}|BC) \\
& = p(A\mid C) + p(B\mid C)\left[1 - p(A\mid BC)\right] \\
& = p(A\mid C) + p(B\mid C) - p(AB\mid C)
\end{aligned}
\]

最后,我们有

\[p(A + B \mid C) = p(A\mid C) + p(B\mid C) - p(AB\mid C) \tag{15}
\]

我们将最后得到的这个式子称为
广义加法规则(generalized sum rule)
。显然,原始加法规则
\((14)\)
是广义加法规则
\((15)\)

\(B=\overline{A}\)
时的特例。

我们在上一篇博客中提到,除相互矛盾之外的任何逻辑函数都可以用析取范式(DNF)表示为基本合取式的逻辑和。现在,我们已知任何一个基本合取式
\(\{Q_i, 1\leqslant i \leqslant 2^n\}\)

\(n\)
为命题数)的合情性都可以通过重复应用乘法规则确定,因此重复应用
\((15)\)
将产生
\(Q_i\)
的任意逻辑和的合情性。

于是,每当背景信息足以确定基本合取式的合情性时,我们的规则就足以确定
\({A_1, \cdots, A_n}\)
生成的布尔代数中每个命题的合情性。因此,正如合取和否定是演绎逻辑的一组完备运算集,上述乘法和加法的规则也是合情推理的一组完备规则集。

3 无差别原则(初始化数值)

到目前为止,我们得到的乘法规则和加法规则描述了不同命题直接合情性之间的关系,也即描述了机器人“大脑”内部运作的基本规则。然而,我们并没有说明合情性是怎么和我们的客观世界产生联系的,也即
机器人是怎么根据背景信息对合情性进行初始化赋值的
。为此,我们必须诉诸合情条件中尚未使用的“接口”条件
\((Ⅲ\text{c})\)
:全同性。

在广义加法规则
\((15)\)
的基础之上,逐步添加更多命题
\(A_3, A_4, A_5, \cdots\)
等,用数学归纳法可以证明,如果我们有
\(n\)
两两互斥的命题
\({A_1,\cdots, A_n}\)
,那么上式可以推广为:

\[p(A_1 + \cdots + A_m \mid B) = \sum_{i=1}^m p (A_i\mid B), 1\leqslant m \leqslant n\tag{16}
\]

接下来,我们假定命题
\({A_1,\cdots, A_n}\)
不仅是互斥的,而且是穷尽的(exhaustive),即背景信息决定了其中一个且仅一个必须为真,在这种情况下,我们有下列命题:

命题10

\(m=n\)
时,上述和式必须等于1:

\[\sum_{i=1}^n p (A_i\mid B) = 1 \tag{17}
\]

到目前为止,我们还不能确定每个数值
\(p(A_i\mid B)\)
。我们可能凭借直觉,直接做出
\(p (A_i\mid B) = \frac{1}{n}\)
的论断。然而在这里,我们需要压制住所有直觉,从逻辑分析的角度去进行论证。

我们现考虑一个互斥且穷尽的命题集合:

\[\{A_1, A_2, \cdots, A_n\}
\]

我们把它看做是
\(n\)
个贴有标签
\(1, 2, \cdots, n\)
的盒子。现在,我们把盒子的标签进行任意的打乱,得到重新编号的盒子集合:

\[\{A_1^{\prime}, A_2^{\prime}, \cdots, A_n^{\prime}\}
\]

我们设现在第贴上标签
\(k\)
的盒子
\(A^{\prime}_k\)
实际上对应的是原来的盒子
\(A_i\)
。由于本质上是同一个盒子(命题),那么从客观角度而言,我们规定对于机器人必须有:

\[p(A_i\mid B) = p(A^{\prime}_k\mid B), \quad i = 1, 2, \cdots, n
\]

上述方程我们称为
变换方程(transformation equations)
,对于任何信息
\(B\)
都必须成立。

但是刚刚是从做为”上帝视角“的客观角度而言,对于机器人而言它并不知道盒子的标签是如何打乱的,也即它对于原始命题集合
\(\{A_1, A_2, \cdots, A_n\}\)
和打乱标签后的命题集合
\(\{A_1^{\prime}, A_2^{\prime}, \cdots, A_n^{\prime}\}\)
的知识状态是完全相同的。而我们的一致性合情条件
\((Ⅲ\text{c})\)
要求机器人在等同的知识状态中就要赋予相同的合情性,也就是说还必须得有:

\[p(A_k\mid B) = p(A^{\prime}_k\mid B), \quad k = 1, 2, \cdots, n
\]

我们称其为
对称方程(symmetry equations)


如果你是物理壬的话对这个方程应该会很有直觉,可以把
\(B\)
理解为给定的哈密顿量,对命题
\(A_k\)

\(A^{\prime}_k\)
的概率赋值可以理解为找对应的平衡态/基态的问题。在无任何自发对称性破缺的情况下(也就是满足合情条件
\((Ⅲ\text{c})\)
:全同性),最后的平衡态也应该具有唯一性,于是自然就会得到我们这个结论。

联立变换方程和对称方程,我们有

\[p(A_i\mid B) = p(A_k \mid B) \quad i=1,2,\cdots, n
\]

这包括了
\(n\)
个等式,每个
\(i\)
都对应某个
\(k\)

不过,以上只是一种特定的打乱方式,我们要求对于任意的标签打乱方式这些关系都必须要成立。一共有
\(n!\)
标签打乱方式,因此有
\(n!\)
个等价的问题。而对于给定的
\(i\)
,上式中的
\(k\)
实际上将遍历所有其它的所有
\(n-1\)
个下标。因此,想满足上述的等式的话,唯一的可能性是所有的
\(p(A_i\mid B)\)
相等。再加上
\(\{A_1^{\prime}, A_2^{\prime}, \cdots, A_n^{\prime}\}\)
是穷尽的,式
\((17)\)
必须成立,从而我们得到下列结论:

结论3
对命题集合
\(\{A_1, A_2, \cdots, A_n\}\)
的合情性进行初始化赋值的唯一的可能是

\[p(A_i\mid B) = \frac{1}{n}, \quad 1 \leqslant i \leqslant n \tag{18}
\]

我们终于得到了一组合情性的确定数值!我们将这个结果称为
无差别原则(principle of indifference)

于是,我们的机器人在内部的存储器电路中只需要存储
\(p_i\)
的数值即可。接下来合情性
\(x\equiv A\mid B\)
这个概念就可以退场了,我们不需要再使用它。我们可以完全通过量
\(p\)
来实现我们的合情推理理论,我们将其称为
概率(probability)

概率
\(p\)
定义了可以测量合情程度的一种
特定尺度
。虽然所有可能的单调函数在原则上都可以很好地服务于此目的,但我们选择这个(满足无差别原则的)特定的函数不是以为它更准确,而是因为它更方便。这种情况类似于热力学中
定标
的情况。所有可能的经验温标
\(t\)
都是彼此的单调函数,我们之所以决定使用开尔文温标
\(T\)
,不是因为它比其它温标更准确,而是因为它更方便。热力学定理在这个温标下具有最简单的形式,比如我们熟知的
\(\mathrm{d}U = T\mathrm{d}S - P\mathrm{d}V, \mathrm{d}G = - S\mathrm{d}T + V\mathrm{d}P\)
等等中的
\(T\)
都是开尔文温标。


之前我们的加法规则:
\(p(A\mid C) + p(\overline{A}\mid C) = 1\)
和两个边界条件:
\(p(A\mid C)=1\)
(若
\(A\)
为真)、
\(p(A\mid C)=0\)
(若
\(A\)
为假)事实上已经完成了
第一次定标
,也即限制了
\(p(A\mid C)\)

\(p(\overline{A}\mid C)\)
的关系和各自的值域(即
\([0, 1]\)
的范围内)。第一次定标可以理解为,使每个人的合情性打分在打分区间上是一样的。但是,即使我们已经对
\(p\)
加以了限定,但
\(p\)
仍然是一个任意的函数(每个人都不同),因此我们还需要
第二次定标
,也就是我们这里的全同性规则:
\(P(A_i\mid B) = \frac{1}{n}\)
。第二次定标使每个人的合情性打分从数值上来说都符合相同的标准。这样,我们就可以将每个人的主观感觉转换为统一的数值加以比较了。两次定标的直观理解可以参见下图(图中黑色和红色的曲线可以视为两个不同人的合情性打分/概率):

还可以马上从式
\((17)\)
中导出符合我们直觉的另一个规则。考虑概率论中的传统”伯努利坛子“问题:坛子中的10个球具有相同的大小和重量,标号为
\(\{1, 2, \cdots, 10\}\)
,其中的3个(标号为
\(4, 6, 7\)
)为黑球,另外7个是白球。我们摇动坛子并随机取一个球。式
\((10)\)
中的背景信息
\(B\)
由这两句陈述组成。我们取出一个黑球的概率是多少?

定义命题:
\(A_i \equiv 取出的是第i个球(1\leqslant i \leqslant 10)\)
。由于这10种可能性都有相同的背景信息,所以式
\((18)\)
适用,机器人为这10种可能性分配相同的概率值

\[p(A_i\mid B) = \frac{1}{10}, \quad 1 \leqslant i \leqslant 10
\]

说“取出一个黑球”就是“取出的球标号为4、6或7”:

\[p(黑球\mid B) = p(A_4 + A_6 + A_7\mid B)
\]

而这些都是互斥的命题(即它们表示互斥的事件),因此式
\((16)\)
适用:

\[p(黑球\mid B) = p(A_4) + p(A_6) + p(A_7) = \frac{3}{10}
\]

而这正如直觉告诉我们的那样。更一般地,如果有
\(N\)
个这样的球,命题
\(A\)
被定义为在任意的
\(M\)
个球的子集上为真(
\(0\leqslant M \leqslant N\)
),在其补集上为假,我们有:

\[p(A\mid B) = \frac{M}{N}
\]

这正是詹姆斯·伯努利(James Bernoulli)给出的概率的原始数学定义,它在接下来的150年中被大多数作者所使用。例如,拉普拉斯的巨著《分析概率论》
[3]
以这句话开头:

\[事件的概率是满足条件的实例数量与所有实例数量之比,前提是没有任何事情导致我们预期\\
这些实例中的任何一个会比其它实例发生得更多,也就是对我们来说,它们是等可能的。
\]

4 和定性属性的联系

最后,让我们看一下定量规则是如何与我们在上一篇博客
《概率论沉思录:合情推理》
中提到的定性三段论相关联的。首先,显而易见的是,在
\(p(A\mid B)\rightarrow 0\)

\(p(A\mid B)\rightarrow 1\)
的极限情形下,加法规则
\((14)\)
描述了亚里士多德逻辑的原始假设:若
\(A\)
为真,则
\(\overline{A}\)
必定为假,等等。

事实上,所有这些逻辑都包括我们在上一篇博客中所提到的两种强三段论以及从它们推演出的所有内容。这两种强三段论即:

\[\begin{aligned}
A \Rightarrow B \\
\underline{\quad \quad \space \space \space A真}\\
B真
\end{aligned}\quad\quad\quad\quad
\begin{aligned}
A \Rightarrow B \\
\underline{\quad \quad \space \space \space B假}\\
A假
\end{aligned}
\tag{19}
\]

(现在使用蕴含标记
\(\Rightarrow\)
来表示大前提)

它们有无穷无尽的推论。这里的大前提就是我们之前一直所说的背景信息(常识),我们用字母
\(C\)
来表示,即

\[C \equiv A \Rightarrow B
\]

那么,这两种三段论分别是要确定
\(p(B\mid AC)\)

\(p(A\mid \overline{B}C)\)
,根据乘法规则
\((13)\)
我们可以将它们表示为:

\[p(B\mid AC) = \frac{p(AB\mid C)}{p(A\mid C)}, \quad p(A\mid \overline{B}C) = \frac{p(A\overline{B}\mid C)}{p(\overline{B}\mid C)}
\]

接着,根据式
\((19)\)
的大前提
\(A\Rightarrow B\)
,我们有逻辑方程
\(AB=A\)
与变量关系
\(\overline{A} + B = 1, A\overline{B}=0\)
(参见上一篇博客的结论)。于是我们有
\(p(AB \mid C) = p(A\mid C)\)

\(p(A\overline{B})=0\)
,于是

\[p(B\mid AC) = 1, \quad p(A\mid \overline{B}C) = 0
\]

这正是三段论式
\((19)\)
所陈述的内容。因此,关系很简单:
亚里士多德演绎逻辑是我们的合情推理规则在机器人对其结论越来越确信时的极限形式

除此之外,我们的规则也包含了演绎逻辑中没有的内容:我们在上一篇博客中所提到的弱三段论的定量形式。比如,对于第一种弱三段论:

\[\begin{aligned}
A \Rightarrow B \\
\underline{\quad \quad \space \space \space B真}\\
A变得更合情
\end{aligned}\quad\quad\quad\quad
\tag{20}
\]

就可以写作:

\[p(A\mid BC) = p( B \mid AC) \frac{p(A\mid C)}{p(B\mid C)}
\]

其中由于
\(p(B\mid AC)=1\)
,而
\(p(B\mid C)\leqslant 1\)
(概率的固有数值范围),所以

\[p(A \mid BC) \geqslant p(A\mid C)
\]

而这正和弱三段论
\((20)\)
相吻合。

对于第2种三段论:

\[\begin{aligned}
A \Rightarrow B \\
\underline{\quad \quad \space \space \space A假}\\
B变得更不合情
\end{aligned}\quad\quad\quad\quad
\tag{21}
\]

可以写作:

\[p(B \mid \overline{A}C) = p(B\mid C)\frac{p(\overline{A} \mid BC)}{p(\overline{A}\mid C)}
\]


\(p(A \mid BC) \geqslant p(A\mid C)\)
得,
\(p(\overline{A}\mid BC) \leqslant p (\overline{A}\mid C)\)
,那么

\[p(B\mid \overline{A}C) \leqslant p(B\mid C)
\]

这也和弱三段论
\((21)\)
吻合。

最后,我们来看警察推理所使用的三段论(参见上一篇博客
《概率论沉思录:合情推理》
)。也即命题
\(A\)
为「男子是坏人」,命题
\(B\)
为「男子做出上述行为」,
\(C\)
为背景信息「
\(A\)
真则
\(B\)
更合情」(按警察的经验,好人几乎不可能有此行为,而坏人有此行为则更合理),则弱三段论定义如下:

\[\begin{aligned}
A真则B更合情 \\
\underline{\quad \quad \space \space \space B真}\\
A变得更合情
\end{aligned}\quad\quad\quad\quad
\tag{22}
\]

它可以写作:

\[p(A \mid BC) = p(A\mid C)\frac{p(B \mid AC)}{p(B\mid C)}
\]

而跟背景信息
\(C\)
,我们有
\(p(B\mid AC) > p(B\mid C)\)
,于是

\[p(A\mid BC) > p(A\mid C)
\]

而这正如我们的弱三段论所述。

事实上,引入概率
\(p\)
之后我们得到的不止上述的定性描述,我们还可以定量地分析合情性具体变化了多少。我们在上一篇博客中的“思维计算机”一节曾提问“是什么决定了
\(A\)
的合情性是大幅增加到几乎确定的程度,还只是提升了可以忽略不计的一点点并使得数据
\(B\)
几乎无关紧要?”现在我们给出的答案是,因为
\(p(B\mid AC)\leqslant 1\)
,所以只有当
\(p(B\mid C)\)
非常小时,
\(A\)
的合情性才会大幅增加。也就是说,如果警察经常几乎没有看见路人这样做过,那么当他看见男子的行为(
\(B\)
)时,就几乎会肯定男子有罪(
\(A\)
)。此外,如果知道
\(A\)
为真只能使
\(B\)
的合情性有微不足道的增加,那么观察到
\(B\)
反过来也只能使
\(A\)
的合情性有几乎可以忽略不计的增加。

除了上述我们展示的几个经典的弱三段论之外,还有许多弱三段论都可以通过上述的合情推理定量规则来表示(参见Polya的著作
[4]
),感兴趣的童鞋可以去进一步延伸阅读。

5 评注

主观与客观

在我们发展的理论中,任何概率赋值都必然是“主观的”,因为它只描述了一种知识状态,而不是任何可以在物理实验中测量的东西(这里的知识状态是推理机器人的、或按照合情条件推理的其它人的)。与此同时,我们的接口条件
\((Ⅲ\text{b})(Ⅲ\text{c})\)
又使得这些概率赋值是完全“客观的”,因为他们与不同用户的个性无关。它们是根据问题给出的陈述来描述(或者说编码)信息的一种手段,与你我对于所涉及命题可能拥有的个人感受(希望、恐惧、价值判断等)无关。这种意义上的“客观性”正是成为受人敬重的科学推断理论所需要的。

维恩图
有读者可能会问:“我们为什么不用维恩图来解释广义加法规则
\(p(A + B \mid C) = p(A\mid C) + p(B\mid C) - p(AB\mid C)\)
呢?这能它的含义更加清晰。”我们认为,维恩图的使用是存在局限性的,因为它要求事件
\(A\)

\(B\)
所对应的区域面积是可加的,也就说它要求事件
\(A\)

\(B\)
可以被分解为一些互斥子命题的析取。我们想象将
\(A\)

\(B\)
一直细分为图中的各个点,也即最终的“基本”命题
\(\omega_i\)
(当然,物理学家会拒绝称它们为“原子”命题(#^.^#))。

然而,我们推理的大多数命题,如
\(A\)
:「今天会下雨」、
\(B\)
:「屋顶会漏水」只是事实性的描述性语句,它们在具体的问题情景下不一定能分解成更多的基本命题。当然,你也可以引入一些无关紧要的东西来强制分解。例如,即使上面定义的
\(B\)
与企鹅无关,我们也可以将其分解为析取
\(B = BC_1 + BC_2 + BC_3 + \cdots + BC_N\)
,其中
\(C_k\)
表示「南极洲的企鹅数量是
\(k\)
」。通过使
\(N\)
足够大,我们肯定能得到一个有效的布尔代数陈述,但这是无事找事,且无法帮助我们推断屋顶是否会漏水的命题。

柯尔莫哥洛夫公理

1933年,柯尔莫哥洛夫提出了一种用集合论和测度论的语言表达概率论的方法,对我们前面提到的维恩图所暗示的内容进行了形式化和公理化。事实上,在柯尔莫哥洛夫系统中最初似乎是由他随意提出的(柯尔莫哥洛夫也因此遭到批评)的概率测度的四个公理,都可以作为满足我们一致性条件的结论被推导出来。因此,我们将发现我们在许多技术问题上支持柯尔莫哥洛夫,反对他的批评者。

然而,我们的概率系统在概念上与柯尔莫哥洛夫的系统不同,因为我们不用集合来解释命题,而是将概率分布解释为不完全信息的载体。这导致的部分结果就是,我们的系统拥有柯尔莫哥洛夫系统中根本没有的分析资源,这使我们能够阐述和解决更多问题(在后面的章节中将进行讨论)。

频率派和贝叶斯派

这一小节是我自己加的,意在将贝叶斯学派(本书的视角)和频率学派做个对比,方便之后的学习:

频率学派 贝叶斯学派
历史沿革 初期思想可追溯到19实际,而在20世纪初得到了系统的发展。这一时期的代表人物包括
罗纳德·A·费希尔(Ronald A. Fisher)

耶尔齐·尼曼(Jerzy Neyman)
。他们推崇
基于重复试验来获取参数的固定值
,并基于此进行统计推断。
起源可追溯到18世纪的
托马斯·贝叶斯(Thomas Bayes)

皮埃尔·西蒙·拉普拉斯(Pierre Simon Laplace)
。他们通过结合
先验知识和观测数据

更新
对未知参数的
信念
数学根基 柯尔莫哥洛夫(Kolmogorov)公理化体系 5条合情条件
  • 依赖布尔代数
尝试描述/建模的内容 样本空间中的事件本身 作为扩展的逻辑,人类对事件的认知/知识/信念。
世界观简述
  • 上帝视角:事件本身是随机的/世界带有某种随机性
  • 所谓概率是事件本身的性质
  • 随着独立重复实验的进行,人们对事件概率值的估计会越来越准确,但是概率值本身是不变的。
  • 观察者视角:人类对世界的认知是不完备的
  • 所谓概率描述了人类对事件的感觉/认知/知识/信念,即观察者对事件的知识状态
  • 随着人获取更多信息,概率值会不断更新和改变
  • *(对于)万事万物(的认知)皆分布
概率的定义 统计定义:
独立重复试验中发生的频率趋于极限
\(p\)

古典概率:
实验中有
\(N\)
个等可能结果,事件
\(E\)
包含了其中
\(M\)
个结果,则概率
\(P(E)=M/N\)
一个实数
,代表人类对事件的感觉/认知/知识/信念,经过了定标和归一化,不同人之间可以相互比较。
对参数估计过程的描述
  • 参数存在一个固定的真值,数据是随机和变动的
  • 使用
    点估计值(一个数值)+置信区间(confidence interval)
    来描述参数估计的结果。形式为
    \(估计值^{+上限差}_{-下限差}\)
  • 95%
    置信区间
    :多次重复试验,进行点估计并计算置信区间,其中的95%会包含(套住)真值(真值不变区间变)
  • 数据是固定的,而待估计的参数是未知和变动的
  • 使用
    后验分布(一个函数)
    来描述参数估计的结果。但是
    也可以使用可信区间(credible interval)
    来简化输出,例如:
    \(概率密度最大值/均值/中值^{+上限差}_{-下限差}\)
  • 95%
    可信区间
    :参数落在此区间的概率为95%(区间不变真值变)
处理问题的额外工具
  • 需要各种特定工具(ad-hoc devices)
  • 只需要讨论概率,不需要其它工具

    参考

    • [1] Jaynes E T. Probability theory: The logic of science[M]. Cambridge university press, 2003.
    • [2] 杰恩斯. 廖海仁译. 概率论沉思录[M]. 人民邮电出版社, 2024.
    • [3] Laplace P S. Théorie analytique des probabilités[M]. Courcier, 1820.
    • [4] Polya G. Mathematics and Plausible Reasoning: Patterns of plausible inference[M]. Princeton University Press, 1990.

    MySQL 死锁
    是指两个或多个事务互相等待对方持有的锁,从而导致所有事务都无法继续执行的现象。在 InnoDB 存储引擎中,死锁是通过锁机制产生的,特别是在并发较高、业务逻辑复杂的情况下,更容易发生死锁。

    一、MySQL 死锁的成因

    MySQL 的死锁一般发生在
    行级锁
    上。常见的死锁成因包括:

    1. 事务 A 和事务 B 持有互相需要的锁
      :事务 A 锁住了记录 1,事务 B 锁住了记录 2,事务 A 尝试获取记录 2 的锁,而事务 B 试图获取记录 1 的锁,造成了死锁。
    2. 不同顺序的锁定
      :两个事务对同一组资源请求加锁,但是加锁顺序不同,导致互相等待。例如,事务 A 按照顺序锁定记录 1 和记录 2,而事务 B 以相反的顺序锁定记录 2 和记录 1。
    3. 使用了 gap lock (间隙锁)
      :在 InnoDB 的 Next-Key Locking 机制下,间隙锁定也可能导致死锁,尤其是在范围查询时,多个事务试图锁定同一间隙。
    4. 长事务和锁等待时间过长
      :事务执行时间长,未及时释放锁,造成其他事务等待锁超时或死锁。

    二、死锁检测与处理

    MySQL 使用
    死锁检测
    来处理死锁问题。MySQL 会自动检测事务是否处于死锁状态,并中止其中一个事务,释放锁以允许另一个事务继续执行。InnoDB 存储引擎通过引入死锁检测机制来解决这个问题,当检测到死锁时,会选择一个事务进行回滚,以打破僵局。被回滚的事务会抛出
    Deadlock found when trying to get lock
    错误。

    三、如何避免和处理 MySQL 的死锁?

    1.
    合理设计索引

    使用合适的索引可以减少加锁的范围,降低死锁的发生概率。没有索引时,MySQL 会对表中的所有记录加锁,增加了锁冲突的机会。因此,合理地设计和使用索引,确保查询能够快速找到数据,避免不必要的锁争用,能够显著减少死锁风险。

    2.
    保持加锁顺序一致

    事务操作表中的多条记录时,保持一致的加锁顺序可以有效减少死锁问题。例如,如果两个事务都需要加锁相同的资源,确保它们按照相同的顺序请求锁,避免死锁。

    3.
    减少事务的锁定时间

    尽量缩短事务的执行时间,减少锁的持有时间。将事务划分为更小的逻辑单元,避免长时间占用资源。同时,将非必要的复杂操作尽量移到事务外执行。

    4.
    减少并发度

    在并发较高的情况下,增加锁冲突和死锁的几率较高。可以通过控制并发度来减少锁争用,比如使用乐观锁机制,避免频繁加锁。

    5.
    使用表锁替代行锁

    对于一些写操作集中的场景,可以考虑使用表锁替代行锁,以避免行级锁导致的死锁。不过表锁会导致并发性能下降,所以需要根据业务场景选择合适的锁。

    6.
    锁定更小的范围

    尽量通过使用主键索引和合适的条件,减少事务锁定的行范围。特别是在
    UPDATE

    DELETE
    操作中,使用精准的查询条件来限制锁的作用范围。

    7.
    分批提交事务

    对于批量操作,考虑将大事务拆解成多个小事务,减少一次性加锁的行数和操作范围,减少锁的持有时间。

    8.
    选择合适的事务隔离级别

    适当降低事务隔离级别可以减少锁冲突的几率。例如,可以将事务隔离级别从
    Serializable
    调整为
    Read Committed

    Repeatable Read
    ,来减少行锁定的情况。

    9.
    加锁操作使用
    SELECT ... FOR UPDATE

    当你需要在查询数据后立即进行更新时,可以使用
    SELECT ... FOR UPDATE
    来显式地锁定行,避免在更新时再去加锁造成的死锁。

    四、常见死锁示例

    以下是一个常见的死锁示例,两个事务尝试对相同的记录加锁但顺序不同:

    -- 事务 A
    START TRANSACTION;
    UPDATE orders SET status = 'shipped' WHERE id = 1; -- 锁住记录 1
    -- 此时,事务 B 在等待锁定记录 1
    
    -- 事务 B
    START TRANSACTION;
    UPDATE orders SET status = 'shipped' WHERE id = 2; -- 锁住记录 2
    -- 此时,事务 A 在等待锁定记录 2
    
    -- 事务 A 尝试更新记录 2,但事务 B 持有锁,事务 A 等待
    UPDATE orders SET status = 'shipped' WHERE id = 2;
    
    -- 事务 B 尝试更新记录 1,但事务 A 持有锁,事务 B 等待
    
    -- 死锁发生,MySQL 自动检测并回滚其中一个事务
    

    五、如何检测和分析死锁?

    通过以下方式可以检测和分析 MySQL 中的死锁:

    1.
    启用
    innodb_print_all_deadlocks
    参数

    通过设置
    innodb_print_all_deadlocks=ON
    ,可以在 MySQL 日志中输出所有的死锁信息,便于分析和调试。

    2.
    使用 SHOW ENGINE INNODB STATUS 命令

    在 MySQL 发生死锁后,可以使用
    SHOW ENGINE INNODB STATUS
    命令查看死锁信息。该命令会输出最近发生的死锁情况,帮助开发者找到死锁的根源。

    SHOW ENGINE INNODB STATUS\G
    

    输出中包含的信息包括:

    • 哪个事务被回滚
    • 发生死锁时,事务分别持有哪些锁,等待哪些锁
    • 事务操作的 SQL 语句

    3.
    MySQL 慢查询日志

    开启 MySQL 慢查询日志,也可以间接帮助发现由于锁等待导致的性能问题,虽然不能直接显示死锁,但可以作为锁冲突问题排查的辅助工具。

    六、死锁后的应对策略

    当发生死锁时,MySQL 会自动回滚其中一个事务,开发人员需要捕获并处理这种异常。

    在代码中,你可以使用如下方式处理死锁:

    try {
        // 执行事务
        ...
    } catch (SQLException e) {
        if (e.getErrorCode() == 1213) { // 1213 代表死锁错误代码
            // 死锁检测,进行重试
            retryTransaction();
        } else {
            // 其他异常处理
            throw e;
        }
    }
    

    通过捕获死锁异常并进行适当的重试,系统可以在发生死锁后继续执行,从而提升系统的健壮性。

    七、总结

    MySQL 死锁是数据库在并发场景下常见的问题,特别是对于大规模、复杂的业务系统,死锁问题更为频繁。通过合理的索引设计、保持加锁顺序一致、缩短事务时间、优化锁策略等手段,可以有效减少死锁的发生。同时,当死锁发生时,MySQL 具备死锁检测和自动回滚机制,开发人员可以通过合理的异常处理和重试机制,来提高系统的稳定性和可靠性。

    秋是慢入的,但冷却是突然的,晴不知夏去,一雨方觉秋深!上海有点冷了。

    强化学习笔记之【ACE:Off-PolicyActor-CriticwithCausality-AwareEntropyRegularization】


    前言:

    至少先点个赞吧,写的很累的


    该论文是清华项目组组内博士师兄写的文章,项目主页为
    ACE (ace-rl.github.io)
    ,于2024年7月发表在ICML期刊

    因为最近组内(其实只有我)需要从零开始做一个相关项目,前面的几篇文章都是铺垫

    本文章为强化学习笔记第5篇

    本文初编辑于2024.10.5,好像是这个时间,忘记了,前后写了两个多星期

    CSDN主页:
    https://blog.csdn.net/rvdgdsva

    博客园主页:
    https://www.cnblogs.com/hassle

    博客园本文链接:


    论文一览

    这篇强化学习论文主要介绍了一个名为
    ACE
    的算法,完整名称为
    Off-Policy Actor-Critic with Causality-Aware Entropy Regularization
    ,它通过引入因果关系分析和因果熵正则化来解决现有模型在不同动作维度上的不平等探索问题,旨在改进强化学习【注释1】中探索效率和样本效率的问题,特别是在高维度连续控制任务中的表现。

    【注释1】:
    强化学习入门这一篇就够了


    论文摘要

    在policy【注释2】学习过程中,不同原始行为的不同意义被先前的model-free RL 算法所忽视。利用这一见解,我们探索了不同行动维度和奖励之间的因果关系,以评估训练过程中各种原始行为的重要性。我们引入了一个因果关系感知熵【注释3】项(causality-aware entropy term),它可以有效地识别并优先考虑具有高潜在影响的行为,以实现高效的探索。此外,为了防止过度关注特定的原始行为,我们分析了梯度休眠现象(gradientdormancyphenomenon),并引入了休眠引导的重置机制,以进一步增强我们方法的有效性。与无模型RL基线相比,我们提出的算法
    ACE
    :Off-policy
    A
    ctor-criticwith
    C
    ausality-aware
    E
    ntropyregularization。在跨越7个域的29种不同连续控制任务中显示出实质性的性能优势,这强调了我们方法的有效性、多功能性和高效的样本效率。 基准测试结果和视频可在https://ace-rl.github.io/上获得。

    【注释2】:
    强化学习算法中on-policy和off-policy

    【注释3】:
    最大熵 RL:从Soft Q-Learning到SAC - 知乎


    论文主要贡献:

    【1】
    因果关系分析
    :通过引入因果政策-奖励结构模型,评估不同动作维度(即原始行为)对奖励的影响大小(称为“因果权重”)。这些权重反映了每个动作维度在不同学习阶段的相对重要性。

    作出上述改进的原因是:考虑一个简单的例子,一个机械手最初应该学习放下手臂并抓住物体,然后将注意力转移到学习手臂朝着最终目标的运动方向上。因此,在策略学习的不同阶段强调对最重要的原始行为的探索是 至关重要的。在探索过程中刻意关注各种原始行为,可以加速智能体在每个阶段对基本原始行为的学习,从而提高掌握完整运动任务的效率。

    此处可供学习的资料:

    【2】
    因果熵正则化
    :在最大熵强化学习框架的基础上(如SAC算法),加入了
    因果加权的熵正则化项
    。与传统熵正则化不同,这一项根据各个原始行为的因果权重动态调整,强化对重要行为的探索,减少对不重要行为的探索。

    作出上述改进的原因是:论文引入了一个因果策略-奖励结构模型来计算行动空间上的因果权重(causal weights),因果权重会引导agent进行更有效的探索, 鼓励对因果权重较大的动作维度进行探索,表明对奖励的重要性更大,并减少对因果权重较小的行为维度的探 索。一般的最大熵目标缺乏对不同学习阶段原始行为之间区别的重要性的认识,可能导致低效的探索。为了解决这一限制,论文引入了一个由因果权重加权的策略熵作为因果关系感知的熵最大化目标,有效地加强了对重要原始行为的探索,并导致了更有效的探索。

    此处可供学习的资料:

    【3】
    梯度“休眠”现象(Gradient Dormancy)
    :论文观察到,模型训练时有些梯度会在某些阶段不活跃(即“休眠”)。为了防止模型过度关注某些原始行为,论文引入了
    梯度休眠导向的重置机制
    。该机制通过周期性地对模型进行扰动(reset),避免模型陷入局部最优,促进更广泛的探索。

    作出上述改进的原因是:该机制通过一个由梯度休眠程度决定的因素间歇性地干扰智能体的神经网络。将因果关系感知探索与这种新颖的重置机制相结合,旨在促进更高效、更有效的探索,最终提高智能体的整体性能。

    通过在多个连续控制任务中的实验,ACE 展示出了显著优于主流强化学习算法(如SAC、TD3)的表现:

    • 29个不同的连续控制任务
      :包括 Meta-World(12个任务)、DMControl(5个任务)、Dexterous Hand(3个任务)和其他稀疏奖励任务(6个任务)。
    • 实验结果
      表明,ACE 在所有任务中都达到了更好的样本效率和更高的最终性能。例如,在复杂的稀疏奖励场景中,ACE 凭借其因果权重引导的探索策略,显著超越了 SAC 和 TD3 等现有算法。

    论文中的对比实验图表显示了 ACE 在多种任务下的显著优势,尤其是在
    稀疏奖励和高维度任务
    中,ACE 凭借其探索效率的提升,能更快达到最优策略。


    论文代码框架

    在ACE原论文的第21页,这玩意儿应该写在正篇的,害的我看了好久的代码去排流程

    不过说实话这伪代码有够简洁的,代码多少有点糊成一坨了

    这是一个强化学习(RL)算法的框架,具体是一个结合因果推断(Causal Discovery)的离策略(Off-policy)Actor-Critic方法。下面是对每个模块及其参数的说明:

    1. 初始化模块

    • Q网络 (
      \(Q_\phi\)
      )

      :用于估计动作价值,(\phi) 是权重参数。
    • 策略网络 ( $\pi_\theta $)
      :用于生成动作策略,(\theta) 是其权重。
    • 重放缓冲区 ($ D$ )
      :存储环境交互的数据,以便进行采样。
    • 局部缓冲区 ( $D_c $)
      :存储因果发现所需的局部数据。
    • 因果权重矩阵 ($ B_{a \rightarrow r|s} $)
      :用于捕捉动作与奖励之间的因果关系。
    • 扰动因子 (
      \(f\)
      )

      :用于对策略进行微小扰动,增加探索。

    2. 因果发现模块

    • 每 ( $$I$$ ) 步更新

      • 样本采样
        :从局部缓冲区 (
        \(D_c\)
        ) 中抽样 (
        \(N_c\)
        ) 条转移。
      • 更新因果权重矩阵
        :调整 ($ B_{a \rightarrow r|s}$ ),用于反映当前策略和奖励之间的因果关系。

    3. 策略优化模块

    • 每个梯度步骤

      • 样本采样
        :从重放缓冲区 (
        \(D\)
        ) 中抽样 ($ N$ ) 条转移。
      • 计算因果意识熵 (
        \(H_c(\pi(\cdot|s))\)
        )

        :衡量在给定状态下策略的随机性和确定性,用于修改策略。
      • 目标 Q 值计算
        :更新目标 Q 值,用于训练 Q 网络。
      • 更新 Q 网络
        :减少预测的 Q 值与目标 Q 值之间的误差。
      • 更新策略网络
        :最大化当前状态下的 Q 值,以提高收益。

    4. 重置机制模块

    • 每个重置间隔

      • 计算梯度主导度 ( $\beta_\gamma $)
        :用来量化策略更新的影响程度。
      • 初始化随机网络
        :为新的策略更新准备初始权重 ( $\phi_i $)。
      • 软重置策略和 Q 网络
        :根据因果权重进行平滑更新,帮助实现更稳定的优化。
      • 重置策略和 Q 优化器
        :在重置时清空状态,以便进行新的学习过程。


    论文源代码主干

    源代码上千行呢,这里只是贴上main_casual里面的部分代码,并且删掉了很大一部分代码以便理清程序脉络

    def train_loop(config, msg = "default"):
        # Agent
        agent = ACE_agent(env.observation_space.shape[0], env.action_space, config)
    
        memory = ReplayMemory(config.replay_size, config.seed)
        local_buffer = ReplayMemory(config.causal_sample_size, config.seed)
    
        for i_episode in itertools.count(1):
            done = False
    
            state = env.reset()
            while not done:
                if config.start_steps > total_numsteps:
                    action = env.action_space.sample()  # Sample random action
                else:
                    action = agent.select_action(state)  # Sample action from policy
    
                if len(memory) > config.batch_size:
                    for i in range(config.updates_per_step):
                        #* Update parameters of causal weight
                        if (total_numsteps % config.causal_sample_interval == 0) and (len(local_buffer)>=config.causal_sample_size):
                            causal_weight, causal_computing_time = get_sa2r_weight(env, local_buffer, agent, sample_size=config.causal_sample_size, causal_method='DirectLiNGAM')
                            print("Current Causal Weight is: ",causal_weight)
                            
                        dormant_metrics = {}
                        # Update parameters of all the networks
                        critic_1_loss, critic_2_loss, policy_loss, ent_loss, alpha, q_sac, dormant_metrics = agent.update_parameters(memory, causal_weight,config.batch_size, updates)
    
                        updates += 1
                next_state, reward, done, info = env.step(action) # Step
                total_numsteps += 1
                episode_steps += 1
                episode_reward += reward
    
                #* Ignore the "done" signal if it comes from hitting the time horizon.
                if '_max_episode_steps' in dir(env):  
                    mask = 1 if episode_steps == env._max_episode_steps else float(not done)
                elif 'max_path_length' in dir(env):
                    mask = 1 if episode_steps == env.max_path_length else float(not done)
                else: 
                    mask = 1 if episode_steps == 1000 else float(not done)
    
                memory.push(state, action, reward, next_state, mask) # Append transition to memory
                local_buffer.push(state, action, reward, next_state, mask) # Append transition to local_buffer
                state = next_state
    
            if total_numsteps > config.num_steps:
                break
    
            # test agent
            if i_episode % config.eval_interval == 0 and config.eval is True:
                eval_reward_list = []
                for _  in range(config.eval_episodes):
                    state = env.reset()
                    episode_reward = []
                    done = False
                    while not done:
                        action = agent.select_action(state, evaluate=True)
                        next_state, reward, done, info = env.step(action)
                        state = next_state
                        episode_reward.append(reward)
                    eval_reward_list.append(sum(episode_reward))
    
                avg_reward = np.average(eval_reward_list)
              
        env.close() 
    


    代码流程解释

    1. 初始化
      :


      • 通过配置文件
        config
        设置环境和随机种子。
      • 使用
        ACE_agent
        初始化强化学习智能体,该智能体会在后续过程中学习如何在环境中行动。
      • 创建存储结果和检查点的目录,确保训练过程中的配置和因果权重会被记录下来。
      • 初始化了两个重放缓冲区:
        memory
        用于存储所有的历史数据,
        local_buffer
        则用于因果权重的更新。
    2. 主训练循环
      :


      • 采样动作
        :如果总步数较小,则从环境中随机采样动作,否则从策略中选择动作。通过这种方式,确保早期探索和后期利用。

      • 更新因果权重
        :在特定间隔内,从局部缓冲区中采样数据,通过
        get_sa2r_weight
        函数使用DirectLiNGAM算法计算从动作到奖励的因果权重。这个权重会作为额外信息,帮助智能体优化策略。

      • 更新网络参数
        :当
        memory
        中的数据足够多时,开始通过采样更新Q网络和策略网络,使用计算出的因果权重来修正损失函数。

      • 记录与保存模型
        :每隔一定的步数,算法会测试当前策略的性能,记录并比较奖励是否超过历史最佳值,如果是,则保存模型的检查点。

      • 使用
        wandb
        记录训练过程中的指标,例如损失函数、奖励和因果权重的计算时间,这些信息可以帮助调试和分析训练过程。


    论文模块代码及实现

    因果发现模块

    因果发现模块
    主要通过
    get_sa2r_weight
    函数实现,并且与
    DirectLiNGAM
    模型结合,负责计算因果权重。具体代码在训练循环中如下:

    causal_weight, causal_computing_time = get_sa2r_weight(env, local_buffer, agent, sample_size=config.causal_sample_size, causal_method='DirectLiNGAM')
    

    在这个代码段,
    get_sa2r_weight
    函数会基于当前环境、样本数据(
    local_buffer
    )和因果模型(这里使用的是
    DirectLiNGAM
    ),计算与行动相关的因果权重(
    causal_weight
    )。这些权重会影响后续的策略优化和参数更新。关键逻辑包括:

    1. 采样间隔
      :因果发现是在
      total_numsteps % config.causal_sample_interval == 0
      时触发,确保只在指定的步数间隔内计算因果权重,避免每一步都进行因果计算,减轻计算负担。
    2. 局部缓冲区

      local_buffer
      中存储了足够的样本(
      config.causal_sample_size
      ),这些样本用于因果关系的发现。
    3. 因果方法

      DirectLiNGAM
      是选择的因果模型,用于从状态、行动和奖励之间推导出因果关系。

    因果权重计算完成后,程序会将这些权重应用到策略优化中,并且记录权重及计算时间等信息。

    def get_sa2r_weight(env, memory, agent, sample_size=5000, causal_method='DirectLiNGAM'):
        ······
        return weight, model._running_time
    

    这个代码的核心是利用DirectLiNGAM模型计算给定状态、动作和奖励之间的因果权重。接下来,用LaTeX公式详细表述计算因果权重的过程:

    1. 数据预处理

      将从
      memory
      中采样的
      states
      (状态)、
      actions
      (动作)和
      rewards
      (奖励)进行拼接,构建输入数据矩阵
      \(X_{\text{ori}}\)


      \[X_{\text{ori}} = [S, A, R]
      \]

      其中,
      \(S\)
      代表状态,
      \(A\)
      代表动作,
      \(R\)
      代表奖励。接着,构建数据框
      \(X\)
      来进行因果分析。

    2. 因果模型拟合


      X_ori
      转换为
      X
      是为了利用
      pandas
      数据框的便利性和灵活性

      使用 DirectLiNGAM 模型对矩阵
      \(X\)
      进行拟合,得到因果关系的邻接矩阵
      \(A_{\text{model}}\)


      \[A_{\text{model}} = \text{DirectLiNGAM}(X)
      \]

      该邻接矩阵表示状态、动作、奖励之间的因果结构,特别是从动作到奖励的影响关系。

    3. 提取动作对奖励的因果权重

      通过邻接矩阵提取动作对奖励的因果权重
      \(w_{\text{r}}\)
      ,该权重从邻接矩阵的最后一行中选择与动作对应的元素:


      \[w_{\text{r}} = A_{\text{model}}[-1, \, d_s:(d_s + d_a)]
      \]

      其中,
      \(d_s\)
      是状态的维度,
      \(d_a\)
      是动作的维度。

    4. 因果权重的归一化

      对因果权重
      \(w_{\text{r}}\)
      进行Softmax归一化,确保它们的总和为1:


      \[w = \frac{e^{w_{\text{r},i}}}{\sum_{i} e^{w_{\text{r},i}}}
      \]

    5. 调整权重的尺度

      最后,因果权重根据动作的数量进行缩放:


      \[w = w \times d_a
      \]

    最终输出的权重
    \(w\)
    表示每个动作对奖励的因果影响,经过归一化和缩放处理,可以用于进一步的策略调整或分析。

    策略优化模块

    以下是对函数工作原理的逐步解释:

    策略优化模块
    主要由
    agent.update_parameters
    函数实现。
    agent.update_parameters
    这个函数的主要目的是在强化学习中更新策略 (
    policy
    ) 和价值网络(critic)的参数,以提升智能体的性能。这个函数实现了一个基于软演员评论家(SAC, Soft Actor-Critic)的更新机制,并且加入了因果权重与"休眠"神经元(dormant neurons)的处理,以提高模型的鲁棒性和稳定性。

    critic_1_loss, critic_2_loss, policy_loss, ent_loss, alpha, q_sac, dormant_metrics = agent.update_parameters(memory, causal_weight, config.batch_size, updates)
    

    通过
    agent.update_parameters
    函数,程序会更新以下几个部分:

    1. Critic网络(价值网络)

      critic_1_loss

      critic_2_loss
      分别是两个 Critic 网络的损失,用于评估当前策略的价值。
    2. Policy网络(策略网络)

      policy_loss
      表示策略网络的损失,用于优化 agent 的行动选择。
    3. Entropy损失

      ent_loss
      用来调节策略的随机性,帮助 agent 在探索和利用之间找到平衡。
    4. Alpha
      :表示自适应的熵系数,用于调整探索与利用之间的权衡。

    这些参数的更新在每次训练循环中被调用,并使用
    wandb.log
    记录损失和其他相关的训练数据。

    update_parameters

    ACE_agent
    类中的一个关键函数,用于根据经验回放缓冲区中的样本数据来更新模型的参数。下面是对其工作原理的详细解释:

    1. 采样经验数据

    首先,函数从
    memory
    中采样一批样本(
    state_batch

    action_batch

    reward_batch

    next_state_batch

    mask_batch
    ),其中包括状态、动作、奖励、下一个状态以及掩码,用于表示是否为终止状态。

    state_batch, action_batch, reward_batch, next_state_batch, mask_batch = memory.sample(batch_size=batch_size)
    
    • state_batch
      :当前的状态。
    • action_batch
      :在当前状态下执行的动作。
    • reward_batch
      :执行该动作后获得的奖励。
    • next_state_batch
      :执行动作后到达的下一个状态。
    • mask_batch
      :掩码,用于表示是否为终止状态(1 表示非终止,0 表示终止)。

    2. 计算目标 Q 值

    利用当前策略(policy)网络,采样下一个状态的动作
    next_state_action
    和其对应的概率分布对数
    next_state_log_pi
    。然后利用目标 Q 网络
    critic_target
    估计下一时刻的最小 Q 值,并结合奖励和折扣因子
    \(\gamma\)
    计算下一个 Q 值:

    \[{min\_qf\_next\_target} = \min(Q_1^{\text{target}}(s', a'), Q_2^{{target}}(s', a')) - \alpha \cdot \log \pi(a'|s')
    \\
    {next\_q\_value} = r + \gamma \cdot \text{mask\_batch} \cdot {min\_qf\_next\_target}
    \]

    with torch.no_grad():
        next_state_action, next_state_log_pi, _ = self.policy.sample(next_state_batch, causal_weight)
        qf1_next_target, qf2_next_target = self.critic_target(next_state_batch, next_state_action)
        min_qf_next_target = torch.min(qf1_next_target, qf2_next_target) - self.alpha * next_state_log_pi
        next_q_value = reward_batch + mask_batch * self.gamma * (min_qf_next_target)
    
    • 通过策略网络
      self.policy
      为下一个状态
      next_state_batch
      采样动作
      next_state_action
      和相应的策略熵
      next_state_log_pi

    • 使用目标 Q 网络计算
      qf1_next_target

      qf2_next_target
      ,并取两者的最小值来减少估计偏差。

    • 最终使用贝尔曼方程计算
      next_q_value
      ,即当前的奖励加上折扣因子
      \(\gamma\)
      乘以下一个状态的 Q 值。

    • 这里,
      \(\alpha\)
      是熵项的权重,用于平衡探索和利用的权衡,而
      mask_batch
      是为了处理终止状态的情况。

      使用无偏估计来计算目标 Q 值。通过目标网络 (
      critic_target
      ) 计算出下一个状态和动作的 Q 值,并使用奖励和掩码更新当前 Q 值

    3. 更新 Q 网络

    接着,使用当前 Q 网络
    critic
    估计当前状态和动作下的 Q 值
    \(Q_1\)

    \(Q_2\)
    ,并计算它们与目标 Q 值的均方误差损失:

    \[\text{qf1\_loss} = \text{MSE}(Q_1(s, a), \text{next\_q\_value})
    \\

    \text{qf2\_loss} = \text{MSE}(Q_2(s, a), \text{next\_q\_value})
    \]

    最终 Q 网络的总损失是两个 Q 网络损失之和:

    \[\text{qf\_loss} = \text{qf1\_loss} + \text{qf2\_loss}
    \]

    然后,通过反向传播
    qf_loss
    来更新 Q 网络的参数。

    qf1, qf2 = self.critic(state_batch, action_batch)
    qf1_loss = F.mse_loss(qf1, next_q_value)
    qf2_loss = F.mse_loss(qf2, next_q_value)
    qf_loss = qf1_loss + qf2_loss
    
    self.critic_optim.zero_grad()
    qf_loss.backward()
    self.critic_optim.step()
    
    • qf1

      qf2
      是两个 Q 网络的输出,用于减少正向估计偏差。
    • 损失函数是 Q 值的均方误差(MSE),
      qf1_loss

      qf2_loss
      分别计算两个 Q 网络的误差,最后将两者相加为总的 Q 损失
      qf_loss
    • 通过
      self.critic_optim
      优化器对损失进行反向传播和参数更新。

    4. 策略网络更新

    每隔若干步(通过
    target_update_interval
    控制),开始更新策略网络
    policy
    。首先,重新采样当前状态下的策略
    \(\pi(a|s)\)
    ,并计算 Q 值和熵权重下的策略损失:

    \[\text{policy\_loss} = \mathbb{E}\left[ \alpha \cdot \log \pi(a|s) - \min(Q_1(s, a), Q_2(s, a)) \right]
    \]

    这个损失通过反向传播更新策略网络。

    if updates % self.target_update_interval == 0:
        pi, log_pi, _ = self.policy.sample(state_batch, causal_weight)
        qf1_pi, qf2_pi = self.critic(state_batch, pi)
        min_qf_pi = torch.min(qf1_pi, qf2_pi)
        policy_loss = ((self.alpha * log_pi) - min_qf_pi).mean()
    
        self.policy_optim.zero_grad()
        policy_loss.backward()
        self.policy_optim.step()
    
    • 通过策略网络对当前状态
      state_batch
      进行采样,得到动作
      pi
      及其对应的策略熵
      log_pi
    • 计算策略损失
      policy_loss
      ,即
      \(\alpha\)
      倍的策略熵减去最小的 Q 值。
    • 通过
      self.policy_optim
      优化器对策略损失进行反向传播和参数更新。

    5. 自适应熵调节

    如果开启了自动熵项调整(
    automatic_entropy_tuning
    ),则会进一步更新熵项
    \(\alpha\)
    的损失:

    \[\alpha_{\text{loss}} = -\mathbb{E}\left[\log \alpha \cdot (\log \pi(a|s) + \text{target\_entropy}) \right]
    \]

    并通过梯度下降更新
    \(\alpha\)

    如果
    automatic_entropy_tuning
    为真,则会更新熵项:

    if self.automatic_entropy_tuning:
        alpha_loss = -(self.log_alpha * (log_pi + self.target_entropy).detach()).mean()
        self.alpha_optim.zero_grad()
        alpha_loss.backward()
        self.alpha_optim.step()
        self.alpha = self.log_alpha.exp()
        alpha_tlogs = self.alpha.clone()
    else:
        alpha_loss = torch.tensor(0.).to(self.device)
        alpha_tlogs = torch.tensor(self.alpha) # For TensorboardX logs
    
    • 通过计算
      alpha_loss
      更新
      self.alpha
      ,调整策略的探索-利用平衡。

    6. 返回值

    • qf1_loss
      ,
      qf2_loss
      : 两个 Q 网络的损失
    • policy_loss
      : 策略网络的损失
    • alpha_loss
      : 熵权重的损失
    • alpha_tlogs
      : 用于日志记录的熵权重
    • next_q_value
      : 平均下一个 Q 值
    • dormant_metrics
      : 休眠神经元的相关度量

    重置机制模块

    重置机制模块在代码中主要体现在
    update_parameters
    函数中,并通过
    梯度主导度
    (dominant metrics) 和
    扰动函数
    (perturbation functions) 实现对策略网络和 Q 网络的重置。

    重置逻辑

    函数根据设定的
    reset_interval
    判断是否需要对策略网络和 Q 网络进行扰动和重置。这里使用了"休眠"神经元的概念,即一些在梯度更新中影响较小的神经元,可能会被调整或重置。

    函数计算了休眠度量
    dormant_metrics
    和因果权重差异
    causal_diff
    ,通过扰动因子
    perturb_factor
    来决定是否对网络进行部分或全部的扰动与重置。

    重置机制模块的原理

    重置机制主要由以下部分组成:

    1. 计算梯度主导度 ( $\beta_\gamma $)

    在更新策略时,计算
    主导梯度
    ,即某些特定神经元或参数在更新中主导作用的比率。代码中通过调用
    cal_dormant_grad(self.policy, type='policy', percentage=0.05)
    实现这一计算,代表提取出 5% 的主导梯度来作为判断因子。

    dormant_metrics = cal_dormant_grad(self.policy, type='policy', percentage=0.05)
    

    根据主导度 ($ \beta_\gamma$ ) 和权重 ($ w$ ),可以得到因果效应的差异。代码里用
    causal_diff
    来表示因果差异:

    \[\text{causal\_diff} = \max(w) - \min(w)
    \]

    2. 软重置策略和 Q 网络

    软重置机制通过平滑更新策略网络和 Q 网络,避免过大的权重更新导致的网络不稳定。这在代码中由
    soft_update
    实现:

    soft_update(self.critic_target, self.critic, self.tau)
    

    具体来说,软更新的公式为:

    \[\theta_{\text{target}} = \tau \theta_{\text{source}} + (1 - \tau) \theta_{\text{target}}
    \]

    其中,(
    \(\tau\)
    ) 是一个较小的常数,通常介于 ( [0, 1] ) 之间,确保目标网络的更新是缓慢的,以提高学习的稳定性。

    3. 策略和 Q 优化器的重置
    4. 重置机制模块的应用

    每当经过一定的重置间隔时,判断是否需要扰动策略和 Q 网络。通过调用
    perturb()

    dormant_perturb()
    实现对网络的扰动(perturbation)。扰动因子由梯度主导度和因果差异共同决定。

    策略与 Q 网络的扰动会在以下两种情况下发生:

    a. 重置间隔达成时

    代码中每当更新次数
    updates
    达到设定的重置间隔
    self.reset_interval
    ,并且
    updates > 5000
    时,才会触发策略与 Q 网络的重置逻辑。这是为了确保扰动不是频繁发生,而是在经过一段较长的训练时间后进行。

    具体判断条件:

    if updates % self.reset_interval == 0 and updates > 5000:
    
    b. 主导梯度或因果效应差异满足条件时

    在达到了重置间隔后,首先会计算
    梯度主导度

    因果效应的差异
    。这可以通过计算因果差异
    causal_diff
    或梯度主导度
    dormant_metrics['policy_grad_dormant_ratio']
    来决定是否需要扰动。

    • 梯度主导度
      计算方式通过
      cal_dormant_grad()
      函数实现,如果梯度主导度较低,意味着网络中的某些神经元更新幅度过小,则需要对网络进行扰动。

    • 因果效应差异
      通过计算
      causal_diff = np.max(causal_weight) - np.min(causal_weight)
      得到,如果差异过大,则可能需要重置。

    然后根据这些值通过扰动因子
    factor
    进行判断:

    factor = perturb_factor(dormant_metrics['policy_grad_dormant_ratio'])
    

    如果扰动因子 (
    \(\text{factor} < 1\)
    ),网络会进行扰动:

    if factor < 1:
        if self.reset == 'reset' or self.reset == 'causal_reset':
            perturb(self.policy, self.policy_optim, factor)
            perturb(self.critic, self.critic_optim, factor)
            perturb(self.critic_target, self.critic_optim, factor)
    
    c. 总结
    • 更新次数达到设定的重置间隔
      ,且经过了一定时间的训练(
      updates > 5000
      )。
    • 梯度主导度
      较低或
      因果效应差异
      过大,导致计算出的扰动因子 ( $\text{factor} < 1 $)。

    这两种条件同时满足时,策略和 Q 网络将被扰动或重置。

    扰动因子的计算

    在这段代码中,
    factor
    是基于网络中梯度主导度或者因果效应差异计算出来的扰动因子。扰动因子通过函数
    perturb_factor()
    进行计算,该函数会根据神经元的梯度主导度(
    dormant_ratio
    )或因果效应差异(
    causal_diff
    )来调整
    factor
    的大小。

    扰动因子(factor)

    扰动因子
    factor
    的计算公式如下:

    \[\text{factor} = \min\left(\max\left(\text{min\_perturb\_factor}, 1 - \text{dormant\_ratio}\right), \text{max\_perturb\_factor}\right)
    \]

    其中:

    • (
      \(\text{dormant\_ratio}\)
      ) 是网络中梯度主导度,即表示有多少神经元的梯度变化较小(或者接近零),处于“休眠”状态。

    • (
      \(\text{min\_perturb\_factor}\)
      ) 是最小扰动因子值,代码中设定为
      0.2

    • (
      \(\text{max\_perturb\_factor}\)
      ) 是最大扰动因子值,代码中设定为
      0.9

    • dormant_ratio
      :


      • 表示网络中处于“休眠状态”的梯度比例。这个比例通常通过计算神经网络中梯度幅度小于某个阈值的神经元数量来获得。
        dormant_ratio
        越大,表示越多神经元的梯度变化很小,说明网络更新不充分,需要扰动。
    • max_perturb_factor
      :


      • 最大扰动因子值,用来限制扰动因子的上限,代码中设定为 0.9,意味着最大扰动幅度不会超过 90%。
    • min_perturb_factor
      :


      • 最小扰动因子值,用来限制扰动因子的下限,代码中设定为 0.2,意味着即使休眠神经元比例很低,扰动幅度也不会小于 20%。

    在计算因果效应的部分,扰动因子
    factor
    还会根据因果效应差异
    causal_diff
    来调整。
    causal_diff
    是通过计算因果效应的最大值与最小值的差异来获得的:

    \[\text{causal\_diff} = \max(\text{causal\_weight}) - \min(\text{causal\_weight})
    \]

    计算出的
    causal_diff
    会影响
    causal_factor
    ,并进一步对
    factor
    进行调整:

    \[\text{causal\_factor} = \exp(-8 \cdot \text{causal\_diff}) - 0.5
    \]

    组合扰动因子的公式

    最后,如果选择了因果重置(
    causal_reset
    ),扰动因子将使用因果差异计算出的
    causal_factor
    进行二次调整:

    \[\text{factor} = \text{perturb\_factor}(\text{causal\_factor})
    \]

    综上所述,
    factor
    的最终值是由梯度主导度或因果效应差异来控制的,当休眠神经元比例较大或因果效应差异较大时,
    factor
    会减小,导致网络进行扰动。

    评估代码

    这段代码主要实现了在强化学习(RL)训练过程中,定期评估智能体(agent)的性能,并在某些条件下保存最佳模型的检查点。我们可以分段解释该代码:

    1. 定期评估条件

    if i_episode % config.eval_interval == 0 and config.eval is True:
    

    这部分代码用于判断是否应该执行智能体的评估。条件为:

    • i_episode % config.eval_interval == 0
      :表示每隔
      config.eval_interval
      个训练回合(
      i_episode
      是当前回合数)进行一次评估。
    • config.eval is True
      :确保
      eval
      设置为
      True
      ,也就是说,评估功能开启。

    如果满足这两个条件,代码将开始执行评估操作。

    2. 初始化评估列表

    eval_reward_list = []
    

    用于存储每个评估回合(episode)的累计奖励,以便之后计算平均奖励。

    3. 进行评估

    for _ in range(config.eval_episodes):
    

    评估阶段将运行多个回合(由
    config.eval_episodes
    指定的回合数),以获得智能体的表现。

    3.1 回合初始化
    state = env.reset()
    episode_reward = []
    done = False
    
    • env.reset()
      :重置环境,获得初始状态
      state
    • episode_reward
      :初始化一个列表,用于存储当前回合中智能体获得的所有奖励。
    • done = False
      :用
      done
      来跟踪当前回合是否结束。
    3.2 执行智能体动作
    while not done:
        action = agent.select_action(state, evaluate=True)
        next_state, reward, done, info = env.step(action)
        state = next_state
        episode_reward.append(reward)
    
    • 动作选择

      agent.select_action(state, evaluate=True)
      在评估模式下根据当前状态
      state
      选择动作。
      evaluate=True
      表示该选择是在评估模式下,通常意味着探索行为被关闭(即不进行随机探索,而是选择最优动作)。

    • 环境反馈

      next_state, reward, done, info = env.step(action)
      通过执行动作
      action
      ,环境返回下一个状态
      next_state
      ,当前奖励
      reward
      ,回合是否结束的标志
      done
      ,以及附加信息
      info

    • 状态更新
      :当前状态被更新为
      next_state
      ,并将获得的奖励
      reward
      存储在
      episode_reward
      列表中。

    循环持续,直到回合结束(即
    done == True
    )。

    3.3 存储回合奖励
    eval_reward_list.append(sum(episode_reward))
    

    当前回合结束后,累计奖励(
    sum(episode_reward)
    )被添加到
    eval_reward_list
    ,用于后续计算平均奖励。

    4. 计算平均奖励

    avg_reward = np.average(eval_reward_list)
    

    在所有评估回合结束后,计算
    eval_reward_list
    的平均值
    avg_reward
    。这是当前评估阶段智能体的表现指标。

    5. 保存最佳模型

    if config.save_checkpoint:
        if avg_reward >= best_reward:
            best_reward = avg_reward
            agent.save_checkpoint(checkpoint_path, 'best')
    
    • 如果
      config.save_checkpoint

      True
      ,则表示需要检查是否保存模型。
    • 通过判断
      avg_reward
      是否超过了之前的最佳奖励
      best_reward
      ,如果是,则更新
      best_reward
      ,并保存当前模型的检查点。
    agent.save_checkpoint(checkpoint_path, 'best')
    

    这行代码会将智能体的状态保存到指定的路径
    checkpoint_path
    ,并标记为
    "best"
    ,表示这是性能最佳的模型。

    论文复现结果

    咳咳,可以发现程序只记录了 0~1000 的数据,从 1001 开始的每一个数据都显示报错所以被舍弃掉了。

    后面重新下载了github代码包,发生了同样的报错信息

    报错信息是:你在 X+1 轮次中尝试记载 X 轮次中的信息,所以这个数据被舍弃掉了

    大概是主程序哪里有问题吧,我自己也没调 bug

    不过这个项目结题了,主要负责这个项目的博士师兄也毕业了,也不好说些什么(虽然我有他微信),至少论文里面的模块挺有用的啊(手动滑稽)

    在C#中使用Kubernetes (k8s) 通常通过官方的Kubernetes .NET客户端与Kubernetes API进行交互。以下是如何在C#中使用Kubernetes的简要指南。

    1. 安装Kubernetes .NET客户端

    首先,在你的项目中安装官方的Kubernetes客户端库:

    使用NuGet安装:

    dotnet add package KubernetesClient

    2. 基本示例:列出Pod

    安装库之后,可以编写代码来连接到Kubernetes集群并执行操作。以下是列出Kubernetes中所有Pod的简单示例。

    usingk8s;usingk8s.Models;usingSystem;usingSystem.Threading.Tasks;namespaceK8sExample
    {
    classProgram
    {
    static async Task Main(string[] args)
    {
    //从本地kube配置文件加载配置(默认路径为 ~/.kube/config) var config =KubernetesClientConfiguration.BuildConfigFromConfigFile();//创建Kubernetes客户端 IKubernetes client = newKubernetes(config);//列出默认命名空间中的所有Pod var pods = await client.ListNamespacedPodAsync("default");foreach (var pod inpods.Items)
    {
    Console.WriteLine($
    "Pod 名称: {pod.Metadata.Name}");
    }
    }
    }
    }

    3. 常见操作

    通过Kubernetes API,你可以在C#中进行以下操作:

    • 创建和管理资源
      :通过客户端,你可以创建、更新或删除资源,如Pod、Service、Deployment等。你可以提供YAML文件或在C#中直接定义资源。

    • 监控
      :你可以监听Kubernetes集群中的变化,例如Pod状态的更新或事件日志。

    • 扩展部署
      :使用HorizontalPodAutoscaler可以自动扩展部署。

    4. 认证和配置

    确保你的C#程序能够成功认证并连接到Kubernetes集群。可以通过以下几种方式进行配置:

    • 本地kubeconfig文件
      :使用默认的
      ~/.kube/config
      文件(如示例中)。
    • 集群内配置
      :如果C#应用程序运行在Kubernetes集群中,可以使用集群中的ServiceAccount进行认证。
    var config = KubernetesClientConfiguration.InClusterConfig();

    5. 其他工具和库

    • KubeClient
      :另一个用于C#的Kubernetes客户端库。
    • k8s-dotnet
      :官方的C#客户端库。

    函数

    在Mysql中函数是一组预定义的指令,用于执行特定的操作并返回结果,可类比Java中的方法.在SQL中函数根据其
    作用范围和返回结果
    方法分为两大类:单行函数,分组函数

    单行函数

    单行函数的特点为对一行数据进行操作,并只返回一种结果.单行函数通常用于处理单个记录数据

    • 单行函数又可分为:字符函数,数学函数,其他函数,流程控制函数

    字符函数

    • CHAR_LENGTH(S),LENGTH(S):
      返回字符串的长度

    eg:查询员工姓名,姓名字数。

    SELECT emplyee_name,CHARACTER_LENGTH(emplyee_name) FROM emplyees;
    
    • CONCAT(S1,S2,…Sn):
      将两个以上的字符串连接

    eg:将字符串'aaa','bbb','ccc'进行拼接。

    SELECT CONCAT('aaa','bbb','ccc');
    
    • UPPER(),LOWER():
      对字符进行大小写转化

    eg::查询员工邮箱,并转为大写显示

    SELECT UPPER(email) FROM emplyees;
    
    • substr,substring(S, start, length):
      提取字符串S从start位置开始,长度为length的字字符串

    eg:提取hello world中的hello

    SELECT substr('hello world',1,5);`
    
    • replace(S, old, new):
      在字符串S中将所有的old替换为new

    eg:查询员工电话号码,要求去除中间的横线 ’-’

    SELECT REPLACE(phone_number, '-', '') FROM emplyees;
    

    数学函数

    • ROUND(X):
      对浮点数X进行四舍五入

      eg:查询员工工资,和其四舍五入的整数值

      SELECT salary,ROUND(salary) FROM employees;
      
    • CEIL(X):
      对浮点数X向上取整,即返回≥X的最小整数

      eg:查询员工工资,并且向上取整

      SELECT salary,CEIL(salary) FROM employees;
      
    • FLOOR(X):
      对浮点数X向下取整,即返回≤X的最大整数

      eg:查询员工工资,并且向下取整

      SELECT salary,FLOOR(salary) FROM employees;
      
    • TRUNCATE(X,length):
      对浮点数的小数部分进行截取→常用于进行保留小数操作

      SELECT TRUNCATE(1.9999,2);->1.99
      
    • MOD(X,Y)
      :对两个数进行区域操作即X%Y

    日期函数

    • NOW(),SYSDATE()
      :返回当前系统日期+时间
    • CURDATE():
      返回当前系统日期,不包括时间
    • CURTIME()
      :返回当前系统时间,不包括日期
    • DATE_FORMAT(date,format):
      用于格式化日期,date是要格式化的数据,format是格式化的模式,格式化的通配符号如下表:
    格式符 功能
    %Y 4位年份
    %y 2位年份
    %m 月份(01,02,…,11,12)
    %c 月份(1,2,…,11,12)
    %d 日(01,02,…)
    %H 小时(24小时制)
    %h 小时(12小时制)
    %i 分钟(00,01,…,58,59)
    %s 秒(00,01,…,58,59)

    eg:查询员工姓名、入职时间,入职时间按照xxxx年xx月xx日输出

    SELECT DATE_FORMAT(hiredate,'%Y年%m月%d日') FROM employees;
    

    流程控制函数

    流程控制函数在SQL中根据条件选择性地返回不同的结果,其允许在查询过程中实现条件逻辑

    • IF(expr,true_val,false_val):
      若表达式
      expr
      为真,则返回结果
      true_val
      ,否则返回
      false_val
      的结果

    eg: 如果查询的年纪大于18则返回adult,否则返回minor

    SELECT age,IF(age>=18,'adult','minor');
    
    • CASE
      语法结构:类似于switch…case结构,用于实现多支路条件选择
    SELECT exper1,exper2...,
    CASE exper1
    		 WHEN value1 THEN result1
    		 WHEN value2 THEN result2
    		 WHEN value3 THEN result3
    		 ...
    		 ELSE result
    END
    FROM table_name;
    

    eg:根据查询的部门号返回部门名称

    SELECT department_id
    CASE department_id
    		 WHEN 1 THEN '经理办公室'
    		 WHEN 2 THEN '财务部'
    		 WHEN 3 THEN '后勤部'
    		 ELSE 'unkown'
    END AS department_name
    FROM departments;
    
    • 搜索CASE:语法结构与朴素CASE类似,但搜索CASE可根据多种不同的表达式条件返回不同值
    SELECT exper1,exper2...,
    CASE 
    		 WHEN condition 1 THEN result1
    		 WHEN condition 2 THEN result2
    		 ...
    		 ELSE result
    END 
    FROM table_name;
    

    eg:查询员工姓名以及工资,工资按照一定规则发放,入职时间在2015-01-01之前的员工工资*2,入职时间在2018-01-01之前的员工工资*1.5,其他不变

    SELECT 
    employee_name,hiredate,salary 原工资,
    CASE
        WHEN hiredate<'2015-01-01' THEN salary*2
    		WHEN hiredate<'2018-01-01' THEN salary*1.3
    		ELSE salary
    END AS 新工资
    FROM employees;
    

    分组函数

    分组函数也称为聚合函数,用于对一组值进行操作,并返回单个结构

    • COUNT(*):
      计算指定列中非NULL值的数量,
      SUM(column)
      :计算指定列之和,
      AVG(column)
      :计算指定列平均数,
      MAX(colum):
      取出指定列最大值,
      MIN(colum)
      :取出指定列最小值

    eg:查询所有员工工资总和、平均值、最大值、最小值、员工个数;

    SELECT SUM(salary),AVG(salary),MAX(salary),MIN(salary),COUNT(*) FROM employees;
    

    分组查询

    分组查询可根据某个或某些列对数据进行分组

    按单个字段分组

    • 通过
      GROUP BY
      关键字:按指定列进行分组
    SELECT 列表
    FROM 表
    [WHERE 筛选条件]
    GROUP BY 分组
    [ORDER BY 排序]
    

    eg:查询每个部门的最高工资

    SELECT MAX(salary) 
    FROM employees 
    GROUP BY department_id;
    

    在分组前进行条件筛选

    在GROUP BY语句之间使用 WHERE语句对查询结果降序筛选

    eg:查询每个部门入职时间在2010-01-01之后,并且工资最高的员工信息

    SELECT *
    FROM employees
    WHERE hiredate >'2010-01-01'
    GROUP BY department_id;
    

    在分组之后进行条件筛选

    • 通过
      HAVING
      语句可以在
      GRUOP BY
      语句之后进行条件筛选

      eg:查询员工人数大于120的部门

      SELECT *
      FROM employees
      GROUP BY department_id
      HAVING COUNT(*)>120;
      

    按多字段分组

    • 在GRUOP BY语句后可接多个字段实现多字段分组

    eg:查询每个部门,男女员工的平均工资

    SELECT department_id,sex,AVG(salary) AS 平均工资
    FROM employees
    GROUP BY department_id,sex;
    

    连接查询

    连接查询是SQL中十分重要的知识点,就有”连接不会,通宵也白搭”,连接查询也称为多表查询,用于将
    两个以上的表的数据基于某些相关条件组合在一起
    ,通过连接查询,可以从表中提取数据,生成一个新的结果集

    • 笛卡尔积现象:
      假设有两个表,表1有n行数据,表2有m行数据,
      在使用连接查询时会产生n*m条数据,因此在条件查询时需要假设连接的条件

    内连接(INNER JOIN)

    内连接用于返回
    两个表中所有满足连接条件的所有行数据,内连接可分为:等值连接,非等值连接,自连接

    等值连接

    等值连接是一种常见的连接方式,其基于两表中某一列的相等条件进行连接

    其语法结构如下:

    SELECT colum1,colum2,....,
    FROM table1 
    INNER JOIN table2
    ON table1.colum = table.colum;
    

    eg:查询员工姓名以及所在的部门名称

    SELECT employee_name AS 员工名,department_name AS 部门名
    FROM employees e	
    INNER JOIN departments d ON 
    e.department_id=d.department_id;
    

    非等值连接

    非等值连接基于两表中某一列的不等条件进行连接.如大于,小于,不等于等等

    语法结构:

    SELECT colum1,colum2,....,
    FROM table1 
    INNER JOIN
    ON table1.colum <operator> table2.colum;
    

    其中
    operator
    可以是
    >,<,≥,≤,≠,BETWEEN…AND
    等等;

    eg:查询员工工资及工资等级

    SELECT e.salary,j.grade_level
    FROM employees e
    INNER JOIN job_grades j
    ON e.salary BETWEEN j.lowest_sal AND j.higest_sal;
    

    自连接

    自连接是指同一个表的连接.这种连接通常用于处理表中有层次结构或函数递归关系的数据

    eg:查询员工姓名以及对应的直系领导

    SELECT t1.employee_name AS 员工,t2.employee_name AS 领导
    FROM employees t1
    INNER JOIN employees t2
    ON t1.manager_id=t2.employee_id;
    

    外连接

    外连接用于返回主表中满足连接条件的行,同时保留另一个表中没有匹配的行

    左/右外连接

    • 左(右)连接顾名思义即根据表位置区分主表,即在左连接即左表,右连接即右侧

    语法结构:

    SELECT colum1,colum2...,
    FROM table1 [LEFT|RIGHT]
    JOIN ON [连接条件];
    

    eg:查询员工姓名以及所在的部门名称,没有部门信息的员工也要查询出来

    SELECT employee_name AS 员工姓名,department_name AS 部门名称
    FROM employees e LEFT
    JOIN departments d
    ON e.department_id=d.department_id;