初始化参数直接决定了模型是否能快速收敛,从梯度下降的过程看,初始化参数$\theta_0$应该是对$f(x;\theta)$& 数据集 从全局上考虑之后得到一个比较直觉的猜测。 如果你的直觉准那么模型收敛的速度会比较快,或者不至于掉进一个局部最优解爬不出来。
之前看过一个调整初始bias处理不均衡样本的例子,还挺有趣的,这里给乡亲们介绍下。例子来自tenosrflow的tutorials,地址在这 https://www.tensorflow.org/tutorials/structured_data/imbalanced_data
1 简述一下例子
例子比较简单,是一个严重不均衡的二分类问题,数据来自Kaggle的信用卡交易记录,目标识别里面伪造的交易记录。
1.1 数据准备
用pandas加载一下数据(看到URL里的“google”字样,大概也许需要一把🪜):
<span class="line"><span style="color: #F6F6F4">raw_df </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> pd.</span><span style="color: #62E884">read_csv</span><span style="color: #F6F6F4">(</span><span style="color: #E7EE98">'https://storage.googleapis.com/download.tensorflow.org/data/creditcard.csv'</span><span style="color: #F6F6F4">)</span></span>样本里的Class字段标记是否为假的交易记录,Class=1的表示假的记录,从样本比例上看,非常不均衡

1.2 构造模型
为了方便构造模型,这里有个函数,也是来自tutorials:
<span class="line"><span style="color: #97E1F1; font-style: italic">def</span><span style="color: #F6F6F4"> </span><span style="color: #62E884">make_model</span><span style="color: #F6F6F4">(metrics </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> METRICS, output_bias </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> None):</span></span>
<span class="line"><span style="color: #F6F6F4"> if output_bias:</span></span>
<span class="line"><span style="color: #F6F6F4"> output_bias = tf.keras.initializers.Constant(output_bias)</span></span>
<span class="line"><span style="color: #F6F6F4"> </span></span>
<span class="line"><span style="color: #F6F6F4"> model = keras.Sequential([</span></span>
<span class="line"><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">keras</span><span style="color: #F6F6F4">.</span><span style="color: #97E1F1; font-style: italic">layers</span><span style="color: #F6F6F4">.</span><span style="color: #97E1F1; font-style: italic">Dense</span><span style="color: #F6F6F4">(</span><span style="color: #BF9EEE">16</span><span style="color: #F6F6F4">, activation </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #E7EE98">'relu'</span><span style="color: #F6F6F4">, input_shape </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> (train_features.shape[</span><span style="color: #F286C4">-</span><span style="color: #BF9EEE">1</span><span style="color: #F6F6F4">], )),</span></span>
<span class="line"><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">keras</span><span style="color: #F6F6F4">.</span><span style="color: #97E1F1; font-style: italic">layers</span><span style="color: #F6F6F4">.</span><span style="color: #97E1F1; font-style: italic">Dropout</span><span style="color: #F6F6F4">(</span><span style="color: #BF9EEE">0.5</span><span style="color: #F6F6F4">),</span></span>
<span class="line"><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">keras</span><span style="color: #F6F6F4">.</span><span style="color: #97E1F1; font-style: italic">layers</span><span style="color: #F6F6F4">.</span><span style="color: #97E1F1; font-style: italic">Dense</span><span style="color: #F6F6F4">(</span><span style="color: #BF9EEE">1</span><span style="color: #F6F6F4">, activation </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #E7EE98">'sigmoid'</span><span style="color: #F6F6F4">, bias_initializer </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> output_bias)</span></span>
<span class="line"><span style="color: #F6F6F4"> ])</span></span>
<span class="line"><span style="color: #F6F6F4"> </span></span>
<span class="line"><span style="color: #F6F6F4"> model.compile(</span></span>
<span class="line"><span style="color: #F6F6F4"> optimizer </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> keras.optimizers.</span><span style="color: #62E884">Adam</span><span style="color: #F6F6F4">(lr</span><span style="color: #F286C4">=</span><span style="color: #BF9EEE">1e</span><span style="color: #F286C4">-</span><span style="color: #BF9EEE">3</span><span style="color: #F6F6F4">),</span></span>
<span class="line"><span style="color: #F6F6F4"> loss </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> keras.losses.</span><span style="color: #62E884">BinaryCrossentropy</span><span style="color: #F6F6F4">(),</span></span>
<span class="line"><span style="color: #F6F6F4"> metrics</span><span style="color: #F286C4">=</span><span style="color: #F6F6F4">metrics</span></span>
<span class="line"><span style="color: #F6F6F4"> )</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4"> return model</span></span>结构比较简单,模型只有一层,16个神经元,输出层只有一个神经元,因为是二分类问题,带了一个sigmoid的激活函数,输出该样本是伪造交易的概率,即$P(\hat{y}=1|x)$。
1.3 初始模型效果
现在直接用默认初始化的模型(还没训练)输出样本的前1000个,看概率数据情况:
<span class="line"><span style="color: #F6F6F4">output </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> model.</span><span style="color: #62E884">predict</span><span style="color: #F6F6F4">(train_features[</span><span style="color: #F286C4">:</span><span style="color: #BF9EEE">1000</span><span style="color: #F6F6F4">])</span></span>
<span class="line"><span style="color: #F6F6F4">plt.</span><span style="color: #62E884">hist</span><span style="color: #F6F6F4">(output, bins</span><span style="color: #F286C4">=</span><span style="color: #BF9EEE">20</span><span style="color: #F6F6F4">)</span></span>
从直方图上看,输出的值大都集中在0.6以后,因为模型输出的样本是伪造交易的概率,这图明显不符合数据集的实际分布,于是会导致Loss非常巨大,要经过多轮迭代才能收敛~
<span class="line"><span style="color: #F6F6F4">tf.</span><span style="color: #62E884">reduce_mean</span><span style="color: #F6F6F4">(tf.keras.losses.</span><span style="color: #62E884">binary_crossentropy</span><span style="color: #F6F6F4">(y_true</span><span style="color: #F286C4">=</span><span style="color: #F6F6F4">train_labels[</span><span style="color: #F286C4">:</span><span style="color: #BF9EEE">1000</span><span style="color: #F6F6F4">], y_pred</span><span style="color: #F286C4">=</span><span style="color: #F6F6F4">output))</span></span>我这边输出:1.8788428,我想你可能会问~没有对比怎么知道大不大?是直觉~纯纯的“我觉得”~因为模型输出的分布和实际的分布差异巨大,所以你就想~凭空想~这个loss是非常大的。
2 设定bias
初始化的参数跟实际样本的概率分布差异比较大,会导致loss比较大(Cross Entropy),这样需要经过好多轮的迭代才能收敛,或者有可能无法收敛的。我们直接给他一个大的bias,让模型的初始输出就接近真实的分布。
2.1 理论推导
中间层的神经元比较多,还得考虑链式法则,调整起来是比较麻烦的,输出层只有一个神经元比较好调整,计算逻辑比较简单
Linear层的形式(一个样本的计算过程)
$$\hat{y}= \textbf{w} \cdot \textbf{x}+ b$$
因为使用了sigmoid激活函数,最后一层的输出是
$$\sigma(\hat{y}) = \frac{1}{1 + exp( -\textbf{w} \cdot \textbf{x}- b)}$$
从概率角度看上面输出的是$P(\hat{y}=1|\textbf{x})$,那么$P(\hat{y}=0|\textbf{x})$是
$$P(\hat{y}=0|\textbf{x}) = \frac{exp(-\textbf{w} \cdot \textbf{x}- b))}{1 + exp(-\textbf{w} \cdot \textbf{x}- b))}$$
根据1.1中给出的实际样本,负样本和正样本的比例应该是$$\frac{284315}{492}$$,也就是说期望模型输出概率最好是符合这个概率的
$$\frac{P(\hat{y}=0|\textbf{x})}{P(\hat{y}=1|\textbf{x})} \propto \frac{284315}{492}$$
这里注意,我没使用等号,用了等比$\propto$,因为戴帽子🎩的$\hat{y}$是预估值只是咱们期望他输出大概这么个概率,把上面的公式们化简一下:
$$\frac{P(\hat{y}=0|\textbf{x})}{P(\hat{y}=1|\textbf{x})} =\frac{exp(-\textbf{w} \cdot \textbf{x}- b))}{1 + exp(-\textbf{w} \cdot \textbf{x}- b))} \times (1 + exp(-\textbf{w} \cdot \textbf{x}- b)))=exp(-\textbf{w} \cdot \textbf{x}- b)$$
最后剩下的这部分$exp(-\textbf{w} \cdot \textbf{x}- b))=exp(-\textbf{w} \cdot \textbf{x})exp(-b)$,$w$我们不动,$b$本来就是bias嘛,通过调整它来影响模型的输出,使得输出分布接近样本的实际分布
$$exp(-b) = \frac{284315}{492} \rightarrow b = -\log(\frac{284315}{492}) = -6.35936$$
2.2 实践一下
于是我们根据计算的结果重新构造一个模型:
<span class="line"><span style="color: #F6F6F4">initial_bias </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">-</span><span style="color: #F6F6F4">np.</span><span style="color: #62E884">log</span><span style="color: #F6F6F4">(</span><span style="color: #BF9EEE">284315</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">/</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">492</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">model2 </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #62E884">make_model</span><span style="color: #F6F6F4">(output_bias</span><span style="color: #F286C4">=</span><span style="color: #F6F6F4">initial_bias)</span></span>看一下调整后初始模型(未训练)的输出情况
<span class="line"><span style="color: #F6F6F4">output2 </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> model2.</span><span style="color: #62E884">predict</span><span style="color: #F6F6F4">(train_features[</span><span style="color: #F286C4">:</span><span style="color: #BF9EEE">1000</span><span style="color: #F6F6F4">])</span></span>
<span class="line"><span style="color: #F6F6F4">plt.</span><span style="color: #62E884">hist</span><span style="color: #F6F6F4">(output2, bins</span><span style="color: #F286C4">=</span><span style="color: #BF9EEE">20</span><span style="color: #F6F6F4">)</span></span>

毕竟实际的数据集大多数都是0嘛,这也算合理。看一下当前的Loss
<span class="line"><span style="color: #F6F6F4">tf.</span><span style="color: #62E884">reduce_mean</span><span style="color: #F6F6F4">(tf.keras.losses.</span><span style="color: #62E884">binary_crossentropy</span><span style="color: #F6F6F4">(y_true</span><span style="color: #F286C4">=</span><span style="color: #F6F6F4">train_labels[</span><span style="color: #F286C4">:</span><span style="color: #BF9EEE">1000</span><span style="color: #F6F6F4">], y_pred</span><span style="color: #F286C4">=</span><span style="color: #F6F6F4">output2))</span></span>瞬间就跌倒了:0.008621918,这么看来相比之前没调整的初始化参数,缩小了200多倍~~
3 对比
来把两个模型分别迭代了20轮,观察一下Loss的变化情况:

实线是train loss, 虚线是val loss。蓝色是使用默认初始化参数的模型输出的,橘色是人为给定bias的模型,明显橘色在一开始loss就比较低,后面一直也都保持的比较好~蓝色在训练了20轮后也没有达到橘色的Loss~
PS:感恩谷歌~
