上次的Logistic Regression 只能处理二元分类问题,而在其推广的Softmax回归能处理多元分类问题->称为多元Logistic Regression。
有 K 个 类别就有 K 个 wj列矩阵。
-
因为对于每个实例x,都要计算ta成为K个特征的概率
-
Z j = g j ( x ) = w j T x Z_j = g_j(x)=w^T_jx Zj=gj(x)=wjTx
-
X = \begin{pmatrix} 1 & x_1 & x_1^2 & \cdots & x_1^n \\ 1 & x_2 & x_2^2 & \cdots & x_2^n \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ 1 & x_m & x_m^2 & \cdots & x_m^n \\ \end{pmatrix} \\
W = ( w 1 T w 2 T ⋮ w K T ) z = g ( x ) = W x = ( z 1 z 2 ⋮ z K ) W = \begin{pmatrix} w^T_1 \\ w^T_2 \\ \vdots \\ w^T_K \end{pmatrix} \\ z =g(x)=Wx=\begin{pmatrix} z_1 \\ z_2 \\ \vdots \\ z_K \end{pmatrix} \\ W=⎝⎜⎜⎜⎛w1Tw2T⋮wKT⎠⎟⎟⎟⎞z=g(x)=Wx=⎝⎜⎜⎜⎛z1z2⋮zK⎠⎟⎟⎟⎞
-
softmax 函数预测概率
-
softmax函数输出为一个向量(每一个分量 j 是x预测为 j 类别的概率)
σ ( z ) = ( σ ( z 1 ) σ ( z 2 ) ⋮ σ ( z K ) ) \sigma(z)=\begin{pmatrix} \sigma(z_1) \\ \sigma(z_2) \\ \vdots \\ \sigma(z_K) \end{pmatrix} \\ σ(z)=⎝⎜⎜⎜⎛σ(z1)σ(z2)⋮σ(zK)⎠⎟⎟⎟⎞σ ( z j ) = e z j ∑ k = 1 K e z k \sigma(z_j) = \frac{e^{z_j}}{\sum_{k=1}^K e^{z_k}} σ(zj)=∑k=1Kezkezj
当k=2,分子分母同时除以ezj就是logistic函数
Softmax回归模型(分子是列向量,展开)(概率)
- 回归模型假设函数
h w ( x ) = σ ( g ( x ) ) = 1 ∑ k = 1 K e w k T x ( e w 1 T x e w 2 T x ⋮ e w k T x ) h_w(x)=\sigma(g(x))=\frac{1}{\sum^{K}_{k=1}e^{w^T_kx}}\begin{pmatrix} e^{w^T_1x} \\ e^{w^T_2x} \\ \vdots \\ e^{w^T_kx} \end{pmatrix} hw(x)=σ(g(x))=∑k=1KewkTx1⎝⎜⎜⎜⎜⎛ew1Txew2Tx⋮ewkTx⎠⎟⎟⎟⎟⎞
- 如果通过训练确定了参数模型W,可构建多选分类函数如下
H ( h w ( x ) ) = a r g m a x k h w ( x ) k = a r g m a x ( w k T x ) − > K H(h_w(x))=argmax_k\ h_w(x)_k=argmax(w_k^Tx) ->K H(hw(x))=argmaxk hw(x)k=argmax(wkTx)−>K
损失函数,交叉熵:
J ( W ) = − 1 m ∑ i = 1 m ∑ j = 1 K I ( y i = j ) ln h w ( x i ) j J(W)=-\frac{1}{m}\sum_{i=1}^{m}\sum_{j=1}^{K}I(y_i=j)\ln h_w(x_i)_j J(W)=−m1i=1∑mj=1∑KI(yi=j)lnhw(xi)j
注: I 是 指示函数 yj== j 的时候为1
梯度下降
-
If w.shape in Logistic Regression is (n+1, 1), here it must be a matrix (n+1, K).
要更新W就要更新每一个wj,需要计算J(W)对每个wj的梯度
公式:
∇ w j J ( W ) = 1 m ∑ i = 1 m ( h w ( x i ) j − I ( y i = j ) ) x i \nabla_{w_j}J(W)=\frac{1}{m}\sum_{i=1}^{m}{(h_w(x_i)_j-I(y_i=j))x_i} ∇wjJ(W)=m1i=1∑m(hw(xi)j−I(yi=j))xi更新参数W的公式
W : = W − η ( ∇ w 1 J ( W ) T ∇ w 2 J ( W ) T ⋮ ∇ w K J ( W ) T ) W:=W-\eta\begin{pmatrix} \nabla_{w_1}J(W)^T \\ \nabla_{w_2}J(W)^T \\ \vdots \\ \nabla_{w_K}J(W)^T \end{pmatrix} W:=W−η⎝⎜⎜⎜⎛∇w1J(W)T∇w2J(W)T⋮∇wKJ(W)T⎠⎟⎟⎟⎞
感觉脑子不够用了…
代码实现(代码来自书籍)
import numpy as np
class SoftmaxRegression:
def __init__(self, n_iter=200, eta=1e-3, tol=None):
# 训练迭代次数
self.n_iter = n_iter
# 学习率
self.eta = eta
# 误差变化阈值
self.tol = tol
# 模型参数W(训练时初始化)
self.W = None
def _z(self, X, W):
'''g(x)函数: 计算x与w的内积.'''
if X.ndim == 1:
return np.dot(W, X) # 如果维度是一维的,就同书中公式那样进行乘积
return np.matmul(X, W.T) # 如果不是,则 (m, n+1) (n+1, K) -> (m, K)
def _softmax(self, Z): # (m, K)
'''softmax函数'''
E = np.exp(Z) # 上边公式中的分子
if Z.ndim == 1: # 维度为1,则直接类似于归一化的操作
return E / np.sum(E) # 归一化
return E / np.sum(E, axis=1, keepdims=True) # 保持维度的,同行相加 ->对应所有种类的值求sum
def _predict_proba(self, X, W):
'''h(x)函数: 预测y为各个类别的概率.'''
Z = self._z(X, W)
return self._softmax(Z)
def _loss(self, y, y_proba):
'''计算损失'''
m = y.size # (m, k)
p = y_proba[range(m), y] # 选去所有行的和???无法理解。可能是进行指示函数的步骤,但是难看。
print(">>y:", y)
print(">>p:", p)
return -np.sum(np.log(p)) / m #
def _gradient(self, xi, yi, yi_proba):
'''计算梯度'''
K = yi_proba.size #
y_bin = np.zeros(K)
y_bin[yi] = 1
return (yi_proba - y_bin)[:, None] * xi # 不行了, 这个操作好像了升维的。
def _stochastic_gradient_descent(self, W, X, y):
'''随机梯度下降算法'''
# 若用户指定tol, 则启用早期停止法.
if self.tol is not None:
loss_old = np.inf
end_count = 0
# 使用随机梯度下降至多迭代n_iter次, 更新w.
m = y.size
idx = np.arange(m)
for step_i in range(self.n_iter):
# 计算损失
y_proba = self._predict_proba(X, W)
loss = self._loss(y, y_proba)
print('%4i Loss: %s' % (step_i, loss))
# 早期停止法
if self.tol is not None:
# 随机梯度下降的loss曲线不像批量梯度下降那么平滑(上下起伏),
# 因此连续多次(而非一次)下降不足阈值, 才终止迭代.
if loss_old - loss < self.tol:
print('haha')
end_count += 1
if end_count == 5:
break
else:
end_count = 0
loss_old = loss
# 每一轮迭代之前, 随机打乱训练集.
np.random.shuffle(idx)
for i in idx:
# 预测xi为各类别概率
yi_proba = self._predict_proba(X[i], W)
# 计算梯度
grad = self._gradient(X[i], y[i], yi_proba)
# 更新参数w
W -= self.eta * grad
def _preprocess_data_X(self, X):
'''数据预处理'''
# 扩展X, 添加x0列并置1.
m, n = X.shape # (m, n+1)
X_ = np.empty((m, n + 1))
X_[:, 0] = 1
X_[:, 1:] = X
return X_
def train(self, X_train, y_train):
'''训练'''
# 预处理X_train(添加x0=1)
X_train = self._preprocess_data_X(X_train)
# 初始化参数向量W
k = np.unique(y_train).size # 获得种类数量
_, n = X_train.shape # (m, n+1)
self.W = np.random.random((k, n)) * 0.05 # 初始化参数W (k种类, n+1特征)
# 执行随机梯度下降训练W
self._stochastic_gradient_descent(self.W, X_train, y_train)
def predict(self, X):
'''预测'''
# 预处理X_test(添加x0=1)
X = self._preprocess_data_X(X)
# 对每个实例计算向量z.
Z = self._z(X, self.W)
# 向量z中最大分量的索引即为预测的类别.
return np.argmax(Z, axis=1)