前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Logistic回归

Logistic回归

作者头像
Sarlren
发布2022-10-28 11:28:19
4100
发布2022-10-28 11:28:19
举报
文章被收录于专栏:Sarlren的笔记

Logistic回归

一些约定和基础

一般约定,x的上标(i)表示第i个样本;在矩阵中表示样本,通常将样本各个维度的特征写成列向量,一列就是一个样本的各个特征。 那么Y矩阵就是一个1*m矩阵,m是样本数目。还约定n_x为X中样本特征的维度。在python里的表示为

代码语言:javascript
复制
Y.shape  # (1, m)

在Logistic回归中,我们总希望通过z = w.transpose * x + b获得每个x(i)的预测值y-hat(i),而且我们希望y-hat(i)尽量接近y(i).这里的y-hat和概率论中的参数估计类似。 如果想要把y-hat概率化,我们需要sigmoid函数作用到上式结果中。即y-hat = sigmoid(z).

通常我们需要standardize图像。在图像的standardization通常是对flatten之后的向量/255。


损失函数

这里的损失函数是y和y-hat的二元函数。我们通常不使用均方差,这会出问题。在这里,我们的损失函数定义成

代码语言:javascript
复制
L(y, y-hat) = - (y * ln(y-hat) + (1 - y) *  ln(1 - y-hat))

lost总是对单一一个样本说的。如果要对所有样本看来,定义cost函数为 J = Σ(L(y, y-hat)) / m # 其中m是样本总数 我们要做的就是最小化J。


梯度下降法

这被用于更新w和b参数。类似牛顿迭代法,我们有

代码语言:javascript
复制
w := w - α * dw  # dw是Loss函数在w这一点的导数或者偏导数
b := b - α * db

在梯度下降法中,每个导数都是J或者L对于参数的导数。 具体到Logistic回归里面,我们的过程简化为两个样本的回归。过程为

代码语言:javascript
复制
(x1, w1, x2, w2, b) -> 
z = w1x1 + w2x2 + b -> 
a = sigmoid(z) ->  # 这里写成a容易表示
L(a, y)

刚刚说到,每个导数都是J或者L对于参数的导数。我们在反向传播要用到L对各个参数的导数,导数的表示法同上。具体为

代码语言:javascript
复制
da = -y/a + (1-y) / (1-a)  # 这可以根据L的函数表达式直接求导得来
dz = dL/da * da/dz = a - y,其中dL/da就是上一条的结果,da/dz可以根据sigmoid的函数表达式求导得来
dw1 = x1 * dz
dw2 = x2 * dz
db = dz

很明显,上述式子都是基于两个样本的,导数都是L的。如果要做成J的导数,就应该将所有值初始化为0,遍历m次,每次都做累加最后除以m就是J的导数。 说白了,分析过程是从L的导数然后再累加取平均,以这样的方式求出J的导数。 任何的深度学习导数计算都应该像这样计算。 最后得出来的结果伪代码是:

代码语言:javascript
复制
J = 0; dw1 = 0; dw2 = 0; db = 0;
for i in range(1, m + 1):
    z[i] = w.transpose * x[i] + b
    a[i] = sigmoid(z[i])
    J += L(y[i], a[i])
    dz[i] += a[i] - y[i]
    dw1 += x1[i] * dz[i] # 第i个样本的第一个特征
    dw2 += x2[i] * dz[i]
    db += dz[i]
J /= m; dw[1] /= m; dw2 /= m; db /= m

向量化

将各个导数算完之后,考虑对计算的优化,即向量化。这可以减少显示调用for的次数,提高性能。 在np中,将dw1和dw2初始化为0的列向量,只需要dw = np.zeros((n_x, 1)),其中param1是列。 现在看到X矩阵,它是一个(n_x, m)的矩阵。而且w是个m维行向量。我们不再像上面使用x1 x2这样的记号,将计算z的过程简化为

代码语言:javascript
复制
z = w.transpose.dot(x) + b  # 这里b是个数,但np会自动转化成m维行向量;算出来的z也是m维行向量

以此类推,我们可以将上一个结果全都向量化,代码为

代码语言:javascript
复制
w = np.zeros((n_x, 1))
b = 0  # np.zeros((1, m))
for iter in range(1000):  # 梯度下降1000次
    Z = w.transpose.dot(X) + b
    A = sigmoid(Z)
    dZ = A - Y  # Y也是(1, m)的向量
    dw = X.dot(dZ.transpose) / m
    db = np.sum(dZ) / m  # np的built-in方法也可以对np数组使用
    w -= lr * dw
    b -= lr * db

一些关于np的trick

比如说,有一个矩阵A = [[1, 2, 3, 4], [2, 2, 3, 4], [3, 2, 3, 4]] 我们想要按列加,得到[6, 6, 9, 12],只需写入

代码语言:javascript
复制
A = A.sum(axis=0)  # 0代表竖着

如果要算对应位置在和数里占的百分比,写成

代码语言:javascript
复制
temp = A.sum(axis=0)
per = temp / temp.reshape(1, 4)  # 首先,这个reshape可以确保矩阵的形状,没事可以多用用;这里还有broadcast内容,即3\*4矩阵除以1\*4矩阵,结果还是我们想要的3\*4。

np随机数数组的写法是np.random.randn(99),这生成一个99个数的数组。但这是不安全的。更安全的做法是写成np.random.randn(99, 1),这生成一个真正的矩阵,而不是数组。数组连向量都算不上。 还有在当不确定矩阵维数的时候,随意使用assert就好了,这对性能没有影响。即

代码语言:javascript
复制
assert(a.shape == (m, n))

np提供了归一化,即normalization的方法。例如x = [[0, 3, 4], [0, 5, 12]],通过

代码语言:javascript
复制
norm = np.linalg.norm(x, axis=1, keepdims=True)  # axis=1即为横向,算出来就是[[5], [13]]

np.squeeze可以将一维的数组内容取出来,比如[[1]]经过squeeze就是[1],[1]经过squeeze就是1,注意到取出来之后仍然类型是array,但已经可以用来当索引。

通常,对于(m, px_w, px_h, 3)的图像,我们在处理的时候需要把它变成数组。那么就应该调用:

代码语言:javascript
复制
X_flatten = X.reshape(X.shape[0], -1).T  # 这里-1是让np自己去算剩下那个维度的大小,转置之后就是想要的每一列都是一个样本

逆天,np的zeros必须括号里套括号传tuple,而random.randn只能一层括号。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-01-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Logistic回归
  • 一些约定和基础
  • 损失函数
  • 梯度下降法
  • 向量化
    • 一些关于np的trick
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档