深度学习从理论到实践—logistic 回归(1)

2,119 阅读6分钟

一直以来想做一系列关于深度学习的分享,由于之前工作节奏紧张,属于个人时间比较少,再加上自己对深度学习认识有限,所以分享的内容相对比较零散,且内容也没有经过推敲,所以分享内容质量普遍一般。这一次借着《掘金技术社区签约计划第二期》东风,自己也想好好写几篇文章从而检验一下最近自己在写作上、表达上是否有所进步

今天将会聊点什么呢? 关于今天想要聊的,都一一这里列出

  • Logistic 回归历史
  • Logistic 回归的应用场景
  • 为什么今天我们还要学习 Logistic 回归
  • sigmoid 函数
  • logistic 回归的分类
  • logistic 回归的损失函数
  • logistic 回归代码实现

Logistic 回归的由来

Logistic 回归出现 20 世纪,最开始被应用在生物科学领域,随后也被应用到社会学科,大家耳熟能详的例子就是垃圾邮件分类的问题。

从垃圾邮件的识别谈起

我们先从一个简单例子谈起,我们需要做的是将接收到邮件按照是垃圾邮件或者不是垃圾邮件进行分类,也就是我们给出预测结果是否为垃圾邮件,这样可以 1 表示是垃圾邮件,用 0 来表示不是垃圾邮件。

001.png

我们知道线性回归对于样本预测输出是在连续空间,那么当拿到一封邮件对其进行预测,线性回归会给我们返回一个具体的值,这个值是一个实数。 事先可以设定一个阈值,然后根据输出值和事先设计好的阈值做对比。不过线性归回输出值是一个整个实数范围,这样显然并不适合做分类问题,我们做分类问题希望输出对应于每一个类别一个 0 到 1 之间概率值。

sigmoid 函数

说到 Logistic 回归,就不得不说一说这个 sigmoid 非线性函数。通过使用这个函数可以将实数空间映射到 0 到 1 之间的数,从而满足了上面对输出要求,就满足我们预测应该是一个概率值的要求。

sigmoid.png

来简单观察一下上图,对于 sigmoid 函数来说,当输入趋近于正无穷时,函数值就无限接近于 1,而当 sigmoid 函数输入趋近于负无穷,则函数值会趋近于 0,当输入为 0 时,函数值为 0.5

logistic 归回模型

这里我们还是拿垃圾邮件为例,首先我们拿到数据 x(i)x^{(i)} 是一个样本,上标表示第 ii 个样本。

在机器学习中,对于样本表示通常用上标表示样本的序号,而用下标表示样本某一个特征例如 xj(i)x^{(i)}_j 这里 ii 表示第 ii 个样本,jj 表示样本的第 jj 个特征。

z(i)=Wx(i)+Bz^{(i)} = Wx^{(i)} + B\\
hθ(x(i))=sigmoid(z(i))h_{\theta}(x^{(i)}) = sigmoid(z^{(i)})

logistic 回归分类

  • 二元 logistic 回归:分类结果只有两个 2 种可能,例如结果是垃圾邮件或不是垃圾邮件
  • 多项式 Logistic 回归(Multinomial Logistic Regression)
  • 排序 logistic 回归(Ordinal Logistic Regression): 工作满意度:不满意、满意和非常满意,个人的表现:表现差、一般和优秀

损失函数

在开始解释损失函数之前,我们先把 logistic 归回的损失函数给出来。

L(hθ(x),y)=ylog(hθ(x))(1y)log(1hθ(x))L(h_{\theta}(x),y) = -y \log(h_{\theta}(x)) -(1-y)\log(1 - h_{\theta}(x))

上面为 Logistic 回归的损失函数,这个损失函数是由 2 部分组成,下面我们根据 y 取值不同而分为两种情况

log(hθ(x))ify=1log(1hθ(x))ify=0- \log(h_{\theta}(x)) \, if \, y = 1\\ -\log(1 - h_{\theta}(x)) \, if\, y=0

上面用了一点小 trick 可以把两个式子合并为一个,根据 y = 1 或者 y = 0 L(hθ(x),y)L(h_{\theta}(x),y)

屏幕快照 2022-07-02 上午8.10.10.png

在上面图中左侧,也就是当 y=1y=1 情况下,hθ(x)h_{\theta}(x) 越趋近于 1 也就是说明模型分的是正确的,看图像不难发现当 hθ(x)h_{\theta}(x) 趋近于 1 时,log(hθ(x))-\log(h_{\theta}(x)) 趋近于 0 ,而当hθ(x)h_{\theta}(x) 趋近于 0 时,log(hθ(x))-\log(h_{\theta}(x)) 趋近于正无穷,也就是会的得到一个很大损失值。

在上面图中右侧,也就是当 y=1y=1 情况下,1hθ(x)1 - h_{\theta}(x) 越趋近于 1 也就是说明模型分的是正确的,看图像不难发现当 hθ(x)h_{\theta}(x) 趋近于 0 时,log(1hθ(x))-\log(1- h_{\theta}(x)) 趋近于 0 ,而当1hθ(x)1 - h_{\theta}(x) 趋近于 1 时,log(1hθ(x))-\log(1 - h_{\theta}(x)) 趋近于正无穷,也就是会的得到一个很大损失值。

对于类别通常采用交叉熵做损失函数,也可以看做负对数似然

log(hθ(x))y=0log(1hθ(x))y=1-\log(h_{\theta}(x)) \, y =0\\ -\log(1 - h_{\theta}(x)) \, y =1

梯度下降

上面我们已经列出模型,以及目标函数,接下来就是计算工作,在机器学习也好、深度学习也好,求解都是找到一个可以损失函数最小的参数,也就是解一个最优化问题。梯度下降是一种算法,这种算法简单又使用适合解决在复杂问题中找到最优解。

屏幕快照 2022-07-01 下午8.25.05.png

z(i)=w1x1(i)+w2x2(i)+bz^{(i)} = w_1x_1^{(i)} + w_2x_2^{(i)} + b
y^=a=σ(z(i))\hat{y} = a = \sigma(z^{(i)})

权重 w1w_1 对于 LL 损失函数求偏导

可以根据链式法则来求 w1w_1 对于 LL 求偏导,看从损失函数一路返回到 w1w_1 都经过了哪些运算,可以通过下图一目了然。

屏幕快照 2022-07-02 下午5.18.25.png

Lw1=Laazzw1\frac{\partial L}{\partial w_1} = \frac{\partial L}{\partial a}\frac{\partial a}{\partial z}\frac{\partial z}{\partial w_1}

通过链式法则,我们将 Lw1\frac{\partial L}{\partial w_1} 用三个偏导数连乘积形式来表示也就是 Laazzw1\frac{\partial L}{\partial a}\frac{\partial a}{\partial z}\frac{\partial z}{\partial w_1},接下来我们就把这几个偏导数一一计算出来。

计算 L/a\partial L/\partial a
La=a(ylog(a)(1y)log(1a))=ya(1y)11a(1)=ya+1y1a\frac{\partial L}{\partial a} = \frac{\partial }{\partial a} \left( -y \log(a) - (1- y) \log(1-a)\right)\\ = -\frac{y}{a} - (1-y)\frac{1}{1-a}(-1)\\ = -\frac{y}{a} + \frac{1-y}{1-a}
az=a(1a)\frac{\partial a}{\partial z} = a(1-a)
zw1=x1\frac{\partial z}{\partial w_1} = x_1
Lw1=((ya+1y1a)a(1a))x1=(y+ay+aay)x1=(ay)x1\frac{\partial L}{\partial w_1} = \left(\left( -\frac{y}{a} + \frac{1-y}{1-a} \right)a(1-a)\right)x_1\\ = (-y + ay + a -ay)x_1\\ = (a - y)x_1

通过对上面公式进行总结

Lw1=(ay)x1\frac{\partial L}{\partial w_1} = (a-y)x_1

根据上面的式子 zw1=x1\frac{\partial z}{\partial w_1} = x_1 由此可以推导

Lz=ay\frac{\partial L}{\partial z} = a - y

计算偏置 b 的偏导数

LzLzzb\frac{\partial L}{\partial z}\frac{\partial L}{\partial z} \frac{\partial z}{\partial b}

代码实现

首先我们来导入要实现一个线性回归的库,这里是基于 numpy 实现 logistic 回归

import numpy as np
from sklearn.datasets import make_classification
%pylab inline

准备数据库

在开始之前我们主要工作是准备数据库,这里我们准备用 sklearn 提供 make_classification 方法来一个简单数据集

X,y=make_classification(n_samples=100,n_features=5,n_classes=2)

数据集有 100 个样本,每一个样本有 5 个特征,对应标注 0 或 1 分别代表两个分类,数据格式都是 numpy.ndarry

X[:1],y[:1]

输出

(array([[ 0.14627759, -2.60540797, 2.1241627 , -3.01934888, -0.64080716]]), array([0]))
y[:10] #array([0, 1, 1, 1, 0, 0, 1, 0, 0, 1])

实现 sigmoid 函数

a=11+eza = \frac{1}{1 + e^{-z}}
def sigmoid_activation(result):
    final_result = 1/(1+np.exp(-result))
    return final_result
w = np.random.random_sample((5))
y_pred = np.dot(w,X[:10].T) + 0
print(y_pred)
[-1.93415864 0.79534054 0.54350266 -0.00318949 0.24851224 -1.27336116 1.21513918 -0.12481527 0.37896053 0.21282776]
y_pred = sigmoid_activation(y_pred)
print(y_pred)
print(y_pred.shape)
[0.53153085 0.66573929 0.65308485 0.62227193 0.6368713 0.55445377 0.68378194 0.61510837 0.64419585 0.63483296] (10,)
y_pred[3] = 0.3
def predict(y_pred):
    # y_pred = np.zeros_like(y_pred)
    ret = y_pred.copy()
    ret = np.where(y_pred>0.5,1,0)
    return ret
y_hat = predict(y_pred)
print(y_hat)#[1 1 1 0 1 1 1 1 1 1]

初始化参数

def parameterInit(n_features):
    w = np.random.random_sample((5))
    b = 0
    return w,b
def optimize(w, b, X, Y):
    m = X.shape[0]
    
    #预测 w(1,n_features) x(m,features) x.T (features,m) wx.T = (1,m)
    #预测结果
    predict_result = sigmoid_activation(np.dot(w,X.T)+b)
    # print(predict_result.shape)
    # Y_T.shape(100)
    Y_T = Y.T
    cost = (-1/m)*(np.sum((Y_T*np.log(predict_result)) + ((1-Y_T)*(np.log(1-predict_result)))))
    
    #计算梯度
    # 1/m(x.T)(5,m) ()
    dw = (1/m)*(np.dot(X.T, (predict_result-Y.T).T))
    db = (1/m)*(np.sum(predict_result-Y.T))
    
    grads = {"dw": dw, "db": db}
    
    return grads, cost

拆分数据集为训练数据集和测试数据集

X_train = X[:80]
X_test = X[80:]
y_train = y[:80]
y_test = y[80:]
from tqdm import trange
epochs = 10
batch_size = 1
tbar = trange(epochs)
costs = []
lr = 0.1

#初始化参数
w,b = parameterInit(5)

for i in tbar:
  
  sample = np.random.randint(0,X_train.shape[0],size=(batch_size))
  X_sample = X_train[sample]
  y_sample = y_train[sample]
  grads, cost = optimize(w,b,X_sample,y_sample)

  dw = grads["dw"]
  db = grads["db"]
  #更新梯度
  w = w - (lr * (dw.T))
  b = b - (lr * db)
  costs.append(cost)

coeff = {"w": w, "b": b}
gradient = {"dw": dw, "db": db}
w_1 = coeff['w']
b_1 = coeff['b']
print(w_1,b_1)

预测

y_test_pred = np.dot(w_1,X_test.T) + b_1
y_test_pred = sigmoid_activation(y_test_pred)
y_test_pred = np.where(y_test_pred>0.5,1,0)
print(y_test_pred)#[0 1 0 1 1 1 1 0 0 0 0 0 1 1 1 1 0 1 1 1]

计算准确度

acc = (y_test_pred == y_test).sum()/len(y_test)
print(acc)#0.8

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿