[普通]机器学习——softmax计算

作者(passion) 阅读(125次) 评论(0) 分类( 算法)

概念与应用

Softmax是机器学习中一个非常重要的工具,他可以兼容 logistics 算法、可以独立作为机器学习的模型进行建模训练、还可以作为深度学习的激励函数。
softmax的作用简单的说就计算一组数值中每个值的占比,公式一般性描述为:
设一共有n个用数值表示的分类S_k,k\in(0,n],其中n表示分类的个数。那么softmax计算公式为:
P(S_i)=\frac{e^{g_i}}{\sum_k^ne^{g_k}},i表示k中的某个分类,g_i表示该分类的值

在机器学习中经常用它来解决MECE原则的分类——每一个分类相互独立,所有的分类被完全穷尽。比如男人和女人就是负责MECE原则的。

softmax的例子

看一个例子能更好的理解softmax
设有三个数值A=5,B=1,C=-1,那么他们的softmax占比为:
P(A)=\frac{e^5}{e^5+e+e^{-1}}
P(B)=\frac{e}{e^5+e+e^{-1}}
P(C)=\frac{e^{-1}}{e^5+e+e^{-1}}
计算结果为:
P(A)=0.9817,P(B)=0.0180,P(C)=0.0003
P(A)+P(B)+P(C)=1

基本特性

从上面的计算结果可以看出softmax的一些特性:

  1. 归一化:最后的合计为1,每一个分类都是一个小于1的数值。

  2. 放大效果:上面的例子中单纯从数值来看,5和1的差距并不大,但是通过指数运算有明显的放大效果,5的占比能到98%以上。

  3. 散列性质,每一个比率虽然最后都会进行归一,但是他们放大之前的数值是可以相互不干扰的。

softmax的损失函数

softmax的损失函数可以用交叉熵来表述,也可以用极大似然评估来描述,后续的数学推导结论会发现2个算法的结果都是一样的。

熵与交叉熵

这里所说的熵来源于信息论,他表示“为了确保完整的信息被描述所需要的编码长度”。看起来是一个很拗口的概念,下面看一个例子。

假设26个英文字母每个字母出现概率都是相同的p(即\frac{1}{26}),那么记录26个英文字母所需要的信息量是log_x\frac{1}{p},这个公式就是表述26个字符的熵。如果取x=2表示用一个信息位表示2个信息(也就是我们用来衡量数据大小最小计算单位bit:0/1),那么计算出log_226=4.7004\approx5说明表述所有的英文字符需要5bit的信息量。

交叉熵

在实际使用中大部分事物都不是均匀分布的,比如一篇英文文章中'e'出现出现的频率明显多于其他字符,而且有时也无法知道真实分布的情况。这时计算信息量就可以使用交叉熵,它是在非均匀分布下信息量的一种表述表示:
H(p,g)=\sum_i^Np_i\log_x\frac{1}{q_i}。这里p_j表示每一个事物的真实概率,q_j表示对应的预估概率。
关于交叉熵的详细说明可以看本人这篇MNIST介绍的文章关于熵与交叉熵的解释说明

极大似然评估

softmax算法可以看做是一个概率问题,设A_i表示不同的分类,每个分类的概率表示为P(A_i|w_{ij}x_j),其中i \in (0,N]表示分类的个数。j\in(0.M] x_j表示特征数。设q_i=P(A_i|w_{ij}x_j)),那么在softmax中\sum_i^N q_i = 1。用p_i表示分类的真实分布,由于事物分类遵守MECE原则,所以所有的p_i组合在一起实际上是个由1和0组成的数组,只有一个元素为1值。可以参照logistics回归算法:P=q^p(1-q)^{(1-p)},softmax也可以使用类似的结构:P=\prod_i^N q_i^{p_i} 。用对数最大似然评估作为损失函数:
L=\log\prod_i^Nq_i^{p_i}=\sum_i^Np_i\log{q_i}
可以看出极大似然评估和交叉熵最后得到的是一模一样的表达式。
将公式扩展为M个样本的情况:
L=\frac{1}{M}\log\prod_k^M\prod_i^Nq_{ij}^{p_{ij}}=\frac{1}{M}\sum_k^M\sum_i^N p_{kj}\log q_{kj}, k\in(0,M]

损失函数的含义

前面已经提到softmax分类应该遵守MECE原则,所以一个样本属于某个分类会用“占位”的方法来标注。例如现在有三个分类,样本A属于第二个分类表示为[0,1,0]、样本B属于第三个分类表示为[0,0,1]、C属于第一个分类——[1,0,0]。每个数组可以看做是的样本分类的真实概率分布——属于某个分类该分类对应的概率就是1,其他分类概率是0。
特征和权重参数通过softmax计算之后得到的是一个概率分布。假设样本A的特征通过softmax计算后分类的概率是[0.2,0.6,0.2],这个时候对于损失函数的计算结果是:0×\ln 0.2+1×\ln 0.6 + 0 × \ln 0.2 = \ln 0.6 \approx -0.51
我们放大真实分布的比重为[0.1,0.8,0.1]后,计算结果:\ln 0.8 \approx -0.22,放大到[0.05,0.9,0.05]得:\approx -0.10。所以一个很直观的感受是:损失函数是从负数无限接近0。

下面通过大量的数据来模拟这个过程。假设所有的样本属于2个分类,样本分类的标注固定为[1,0],随机生成100个样本模拟分类的概率为:
\begin{matrix} p_1&p_2\\ 0.2&0.8 \\ 0.7&0.3 \\ 0.9&0.1 \\ …&… \end{matrix}
那么这100组数据和损失函数计算结果构成的关系如下图:

交叉熵与分类的概率的关系


由于所有样本的标注都是[1,0],所以

q_1

的概率越接近1、

q_2

越接近0越符合真实分布。可以看到当

q_1

接近1

q_2

接近0时,交叉熵的计算结果从负数方向接近0。可以执行模拟过程的源码用matplotlib看到更清晰的结果。


再使用一个过程来确认这个结果。softmax是体现一组数值的占比,被标记的那个分类占比越高越接近真实分布。现在假设有5000组样本,每个样本对应20个分类,每个分类的特征值在0~10之间随机产生,每个样本的标记在0~20之间随机设定。现在看看标记项的概率值与损失函数的关系:

标注项占比与交叉熵关系趋势


图中softmax highest表示标注项的概率(占比),corss entropy就是损失函数的计算结果。可以看到当标记项概率越接近1,损失的计算结果越接近0。如果有兴趣可以使用生成图像的代码了解分析过程。


建模

softmax计算

上面的内容介绍了softmax的公式以及损失函数。下面说明其如何运算。
在实际应用中一个样本的特征是一个的向量:X={x_1,x_2,x_3,....x_n},每一个特征在计算过程中都有一个权重,所以引入权重参数建立权重结构(直线结构):
g(x)=w_0+w_1x_1+w_2x_2+...+w_nx_n=w_jx_jj\in[0,n],x_0=1
所以softmax更加完整的代数表达式是:
softmax(x)=\frac{e^{w_{ij}x_j}}{\sum_j^ne^{w_ijx_j}}
其中i表示计算结果有多少个分类i\in(0,m),j表示特征的个数j\in[0.n]
o个样本时就扩展为一个2阶张量,那么用矩阵形式表述更加简洁:
softmax(X)=\frac{e^{XW^T}}{e^{XW^T}E}
用下标_{(o)}表示当前的特征属于第几个样本,例如x_{(1)3}表示第1个样本的第3个特征。矩阵的计算过程如下:

1.计算权重指数

G=exp\left( \begin{bmatrix} x_{(1)0}&x_{(1)1}&x_{(1)2}&\cdots&x_{(1)n}\\ x_{(2)0}&x_{(2)1}&x_{(2)2}&\cdots&x_{(2)n}\\ x_{(3)0}&x_{(3)1}&x_{(3)2}&\cdots&x_{(3)n}\\ \vdots&\vdots&\vdots&\ddots&\vdots\\ x_{(o)0}&x_{(o)1}&x_{(o)2}&\cdots&x_{(o)n} \end{bmatrix} × \begin{bmatrix} w_{10}&w_{20}&w_{30}&\cdots&w_{m0}\\ w_{11}&w_{21}&w_{31}&\cdots&w_{m1}\\ w_{12}&w_{22}&w_{32}&\cdots&w_{m2}\\ \vdots&\vdots&\vdots&\ddots&\vdots\\ w_{1n}&w_{2n}&w_{3n}&\cdots&w_{mn}\\ \end{bmatrix}\right)
矩阵中x_{(o)0} = 1
exp()表示矩阵每一个元素求e指数。所以得到:

G= \begin{bmatrix} e^{\sum_j^nw_{1j}x_{(1)j}}&e^{\sum_j^nw_{2j}x_{(1)j}}&e^{\sum_j^nw_{3j}x_{(1)j}}&\cdots&e^{\sum_j^nw_{mj}x_{(1)j}}\\ e^{\sum_j^nw_{1j}x_{(2)j}}&e^{\sum_j^nw_{2j}x_{(2)j}}&e^{\sum_j^nw_{3j}x_{(2)j}}&\cdots&e^{\sum_j^nw_{mj}x_{(2)j}}\\ \vdots&\vdots&\vdots&\ddots&\vdots\\ e^{\sum_j^nw_{1j}x_{(o)j}}&e^{\sum_j^nw_{2j}x_{(o)j}}&e^{\sum_j^nw_{3j}x_{(o)j}}&\cdots&e^{\sum_j^nw_{mj}x_{(o)j}} \end{bmatrix}

c_{ki}=c(w_{ij},x_{(k)j})=e^{\sum_j^nw_{ij}x_{(k)j}},k\in(0,o],有:

G=G(x)= \begin{bmatrix} c_{11}&c_{12}&c_{13}&\cdots&c_{1m}\\ c_{21}&c_{22}&c_{23}&\cdots&c_{2m}\\ \vdots&\vdots&\vdots&\ddots&\vdots\\ c_{o1}&c_{o2}&c_{o3}&\cdots&c_{om}\\ \end{bmatrix}

2.计算分母

现在Softmax=S(x)=\frac{G}{G×E}
E是一个形状为m×1元素全为1的矩阵:
E=\begin{bmatrix} 1\\ 1\\ \cdots\\ 1 \end{bmatrix}

分母:S=G×E=\begin{bmatrix} c_{11}&c_{12}&c_{13}&\cdots&c_{1m}\\ c_{21}&c_{22}&c_{23}&\cdots&c_{2m}\\ \vdots&\vdots&\vdots&\ddots&\vdots\\ c_{o1}&c_{o2}&c_{o3}&\cdots&c_{om}\\ \end{bmatrix}×\begin{bmatrix} 1\\ 1\\ \cdots\\ 1 \end{bmatrix}
所以:S=\begin{bmatrix} \sum_i^mc_{1i}\\ \sum_i^mc_{2i}\\ \sum_i^mc_{3i}\\ \cdots\\ \sum_i^mc_{oi} \end{bmatrix}=\begin{bmatrix}s_1\\s_2\\s_3\\\cdots\\s_o\end{bmatrix}

3.归一化

现在Q=softmax=\frac{G}{S}=\begin{bmatrix} c_{11}&c_{12}&c_{13}&\cdots&c_{1m}\\ c_{21}&c_{22}&c_{23}&\cdots&c_{2m}\\ \vdots&\vdots&\vdots&\ddots&\vdots\\ c_{o1}&c_{o2}&c_{o3}&\cdots&c_{om}\\ \end{bmatrix}\div\begin{bmatrix}s_1\\s_2\\s_3\\\cdots\\s_o\end{bmatrix}
所以最终Q=\begin{bmatrix} \frac{c_{11}}{d_1}&\frac{c_{12}}{d_1}&\frac{c_{13}}{d_1}&\cdots&\frac{c_{1m}}{d_1}\\ \frac{c_{21}}{d_2}&\frac{c_{22}}{d_2}&\frac{c_{23}}{d_2}&\cdots&\frac{c_{2m}}{d_2}\\ \vdots&\vdots&\vdots&\ddots&\vdots\\ \frac{c_{o1}}{d_o}&\frac{c_{12}}{d_o}&\frac{c_{12}}{d_o}&\cdots&\frac{c_{1m}}{d_o}\\ \end{bmatrix}=\begin{bmatrix} q_{11}&q_{12}&q_{13}&\cdots&q_{1m}\\ q_{21}&q_{22}&q_{23}&\cdots&q_{2m}\\ \vdots&\vdots&\vdots&\ddots&\vdots\\ q_{o1}&s_{o2}&q_{o3}&\cdots&q_{om}\\ \end{bmatrix}

交叉熵(极大似然评估)计算

根据交叉熵的公式L=\sum_i^mp_i\log q_i,这里p_i是样本的真实分类(标签label),q_i是softmax计算的结果。用矩阵结构表示:
L=\frac{1}{o}\left[P\log\left(Q^T\right)\right]^D×E,矩阵A_{m×n}^D表示取对角线元素形成一个1×n的矩阵。

1.对数及矩阵乘积

\left[P\log\left(S^T\right)\right]^D= \left[\begin{bmatrix} p_{(1)1}&p_{(1)2}&p_{(1)3}&\cdots&p_{(1)m}\\ p_{(2)1}&p_{(2)2}&p_{(2)3}&\cdots&p_{(2)m}\\ \vdots&\vdots&\vdots&\ddots&\vdots\\ p_{(o)1}&p_{(o)2}&p_{(o)3}&\cdots&p_{(o)m}\\ \end{bmatrix}×\log\left(\begin{bmatrix} s_{11}&s_{21}&\cdots&s_{o1}\\ s_{12}&s_{22}&\cdots&s_{o2}\\ \vdots&\vdots&\ddots&\vdots\\ s_{1m}&s_{2m}&\cdots&s_{om}\\ \end{bmatrix}\right)\right]^D
对数\log()表示对每个元素进行对数运算,他仅改变每个元素的值,对矩阵结构没任何影响,所以下面用s_{om}表示\log(s_{om})继续表示:
\left[P\log\left(S^T\right)\right]^D= \begin{bmatrix} \sum_i^m(p_{(1)i}s_{(1)i})& \sum_i^m(p_{(2)i}s_{(2)i})& \cdots& \sum_i^m(p_{(o)i}s_{(o)i}) \end{bmatrix}

2.交叉熵计算

L=\frac{1}{o}×\begin{bmatrix} \sum_i^m(p_{(1)i}s_{(1)i})& \sum_i^m(p_{(2)i}s_{(2)i})& \cdots& \sum_i^m(p_{(o)i}s_{(o)i}) \end{bmatrix}×\begin{bmatrix} 1\\ 1\\ \cdots\\ 1 \end{bmatrix}
\log符号带入公式得到最终的损失函数矩阵计算结果:
L=\frac{1}{o}\begin{bmatrix}\sum_k^o\sum_i^m\left[p_{(o)i}\log(s_{(o)i}) \right]\end{bmatrix}
把矩阵符号去掉,这里的结果和前面最大似然评估推导的结果一致。

参数优化

通过前文的介绍我们知道,损失函数的目标是获得“最大值”,这个最大值的含义是从负无穷方向接近0的一个极限过程。所以经常会看到很多文章会在指标函数前面添加一个负号,如下面这样:
Loss=-\frac{1}{o}\sum\sum p\ln q
这样就可以把这个过程转变为求“最小值”——从正无穷方向接近0,本质并没有多大区别。

既然这是一个极限过程,自然就可以用积分原理逐渐计算合理的参数。现在的目标是通过导数和找到递增量可以逐步求解w_{ij}值:
\nabla_{ij}表示w_{ij}的偏导函数:\nabla_{ij}=\frac{\partial L(w_{ij})}{\partial w_{ij}}w_{ij}的更新公式为:w_{ij}=w_{ij}+\eta\nabla_{ij}\eta表示每一步更新的步长。
如果损失函数前携带了负号,那么更新公式应该修改为w_{ij}=w_{ij}-\eta\nabla_{ij},即越来越小。

1.求偏导函数

目的已经明确,那么接下来就是数学运算了:
设softmax计算结果一共有M个分类,输入模型的一个样本一共有N个特征。
g_i表示权重计算的结果,下标i表示所属的分类,用数组可以表示为:
\begin{bmatrix} g_1=\sum_j^Nw_{1j}x_j\\ g_2=\sum_j^Nw_{2j}x_j\\ \vdots\\ g_i=\sum_j^Nw_{ij}x_j\\ \vdots\\ g_M=\sum_j^Nw_{Mj}x_j\\ \end{bmatrix}
q_i表示每一个分类softmax计算的结果:q_i=\frac{g_i}{\sum_k^Mg_k},k表示分类迭代求和的下标:用数组表示为:
\begin{bmatrix} q_1=\frac{e^{g_1}}{\sum_k^Me^{g_k}}\\ q_2=\frac{e^{g_2}}{\sum_k^Me^{g_k}}\\ \vdots\\ q_i=\frac{e^{g_i}}{\sum_k^Me^{g_k}}\\ \vdots\\ q_M=\frac{e^{g_M}}{\sum_k^Me^{g_k}}\\ \end{bmatrix}
Loss是最终的损失函数:Loss=\sum_k^M L_k = \sum_k^M p_k\ln q_kp_k表示每一个softmax分类对应的真实概率,取值0或1。
优化参数是不断的调优权重参数,所以把w_{ij}看做自变量求导:
\nabla_{ij}=\frac{\partial Loss}{\partial w_{ij}}
按照前面给出的公式将损失函数的计算分为3步:1)计算权重模型,2)计算softmax,3)计算交叉熵。现在把求导过程分为这3步对q_ig_i以及w_{ij}复合求导:

计算到这里需要注意一个问题。因为目标是对w_{ij}求导,所以在求和公式中包含w_{ij}的项(即包含g_i的项)和不包含的项求导的结果是不一样的,所以需要将g_i项单独拿出来求导。所以有:
\begin{split} \nabla_{ij}&=\frac{p_i}{q_i}\left[\frac{\left(e^{g_i}\right)'}{\sum_l^Me^{g_l}} - \frac{e^{g_i}}{\sum_l^Me^{g_l}}\frac{e^{g_i}}{\sum_l^Me^{g_l}}\left(g_k\right)'\right] + \sum_{k \neq i}^M\frac{p_k}{q_k}\left[\frac{\left(e^{g_k}\right)'}{\sum_l^Me^{g_l}} - \frac{e^{g_k}}{\sum_l^Me^{g_l}}\frac{e^{g_i}}{\sum_l^Me^{g_l}}\left(g_k\right)'\right]\\ &=\frac{p_i}{q_i}\left(q_i-q_i^2\right)\left(g_k\right)'-\sum_{k \neq i}^M\frac{p_k}{q_k}q_kq_i\left(g_k\right)'\\ &=\left[p_i-\left(p_iq_i+\sum_{k\neq i}^Mp_kq_i\right)\right]\left(g_k\right)'\\ &=\left(p_i-q_i\sum_{k}^Mp_k\right)\frac{\partial\sum_j^N w_{ij}x_j}{\partial w_{ij}}\\ &=\left(p_i-q_i\sum_{k}^Mp_k\right)x_j \end{split}
p_k是一个结构为[0,0,0,1,0,0......]的只有一个元素是1其余元素为0的数组,所以它的合计为1,因此得:

\nabla_{ij} = \left(p_i-q_i\right)x_j

虽然推导这个求偏导的过程要花费一些功夫,但是这个结果却非常简单——真实分布与预测分布的差值乘权重参数对应的特征值。如果交叉熵函数中使用了负号,那么导函数为\nabla_{ij}=(q_i-p_i)x_j,很多文章更喜欢用这种求最小值的方式。
观察\nabla_{ij}的表达式,p_ix_j都是已知的数值,在优化的过程中只有q_i会发生改变。所以当预测分布越接近真实分布时增量会越来越接近0。

2.多个样本与矩阵运算

上面求导的过程并没有考虑多个样本的情况,设现在有O个样本。那么求导公式变成:
\nabla_{ij}=\frac{1}{O}\sum_k^O(p_{(k)i}-q_i)x_{(k)j}
因为每一个子项的求导结果都是向0接近,所以求和再平分之后也是靠近0的。
现在模型参数的更新公式用矩阵表示为:
W=W+\eta D。其中D\nabla_{ij}的矩阵形,\eta是一个常量,Ww_{ij}的矩阵形。
设P表示样本真实分布的矩阵(即标记矩阵),Q是文章前面介绍的softmax矩阵计算的结果,X表示样本矩阵。那么D的矩阵表示为:D=(P-Q)^TX

计算法则总结与编码实现

算法总结

经过前面推导分析,softmax机器学习算法建模分为以下几项内容。

1.定义

O个样本、N个特征、M个分类,。
X是样本(feature)矩阵,形状为(O,N)
W是权重矩阵,形状为(M,N)
P是标签(label)矩阵,形状为(O,M)
Q是softmax计算后得到的矩阵,形状为(O,M)
E_1,E_2是两个用于合并计算的单位矩阵,形状为(M,1)和(O,1),E=\begin{bmatrix} 1\\1\\ \cdots \\1 \end{bmatrix}
矩阵A^T表示转置矩阵,A^D表示取矩阵的对角线元素(类似于特征)。

2.softmax计算

权重指数:G=e^{XW^T}
归一化:Q=Softmax=\frac{G}{G*E_1}

3.损失函数

Loss=\frac{1}{o}\left[Plog(Q^T)\right]^D×E

4.参数训练

W=W+\eta\frac{\left(P-Q\right)^T×X}{O},训练会重复这个计算,直到变化率“接近”0。

编码实现

以下代码在https://github.com/chkui/ml-math-softmax
如下图,sample.softmax_train.softmax_modual.Softmax类模拟了一个softmax机器学习的过程。

import numpy as npclass Softmax:
    def __init__(self, features, labels):
        self.__features = features
        self.__labels = labels
        self.__weight = np.zeros((labels.shape[1], features.shape[1]))
        # 用于 softmax 归一化计算分布的标量矩阵
        self.__e_softmax = np.ones((labels.shape[1], 1))
        # 用于 损失函数计算的标量矩阵
        self.__e_loss = np.ones((features.shape[0], 1))
        # flag用于标记运算符号
        # flag如果是-1,那么损失函数就是求最小值,那么优化器求差值。
        # flag如果是+1损失函数就是求最大值,那么优化器求和
        self.__flag = 1

    def __softmax(self):
        liner = self.__features * self.__weight.T
        exp = np.exp(liner)
        den = exp * self.__e_softmax
        q = exp / den        return q    def __loss(self, q):
        h = self.__labels * np.log(q.T)
        h = h.diagonal()
        loss = self.__flag * h * self.__e_loss / self.__e_loss.shape[0]
        return loss    def __optimizer(self, q, step):
        d = ((self.__flag * self.__labels - self.__flag * q).getT() * self.__features) / self.__features.shape[0]
        self.__weight = self.__weight + (self.__flag * step) * d    def train(self, handle, repeat=2000, step=0.1):
        """
        训练
        :param handle: 单轮训练的回调,用于输出各项数据 (count, loss, )
        :param repeat: 重复的轮次,每轮会执行一次存储 2000
        :param step: 优化器步近量
        :return:
        """
        print("Weight shape={}".format(self.__weight.shape))
        count = 0
        while count < repeat:
            q = self.__softmax()
            loss = self.__loss(q)
            self.__optimizer(q, step)
            count = count + 1
            handle(count, loss)

类中的__softmax__loss__optimizer方法分别对应前面介绍的三步计算(归一化,损失函数,参数优化),而在train方法中就是重复调用这三个方法来不断的优化权重参数。
为了执行训练sample.softmax_train.random_data.RandomData用于随机生成样本特征样本标签数据。
下图展示了执行5000次优化过程中Loss的变化趋势:

训练次数与损失函数的输出


Count表示执行训练的次数,Loss表示损失函数的输出值,可以发现几个特点:


  1. 在优化的过程中Loss是逐渐接近0的。

  2. 反复使用相同的样本(案例中随机生成了500个样本)优化器在前1000次有比较明显的效果,但是后续增长乏力。

由于使用的是随机数据,所以收敛的效果并不太理想,但是总的趋势还是收敛。后续的博文中本人会使用MNIST之类的真实数据来测试验证softmax。

Github的代码中除了softmax_train用于演示训练和收敛的效果,还有softmax_estimatorsoftmax_compute。前者提供了参数相关的磁盘操作,后者简单展示了softmax算法的编码实现,需要了解的可以到代码库中查看。



« 上一篇:Pytorch常用的交叉熵损失函数CrossEntropyLoss()详解
« 下一篇:Focal Loss tensorflow 实现
在这里写下您精彩的评论
  • 微信

  • QQ

  • 支付宝

返回首页
返回首页 img
返回顶部~
返回顶部 img
';