大模型系列6--神经网络(WIP)

神经网络

  • [1. 背景](#1. 背景)
  • [2. 理论知识](#2. 理论知识)
    • [2.1. 单个神经元](#2.1. 单个神经元)
      • [2.1.1. 基础](#2.1.1. 基础)
      • [2.1.2. 神经元激活代码](#2.1.2. 神经元激活代码)
    • [2.2. 多个神经元](#2.2. 多个神经元)
      • [2.2.1. 基础](#2.2.1. 基础)
      • [2.2.2. 神经元激活代码](#2.2.2. 神经元激活代码)
      • [2.2.3. 反向传播](#2.2.3. 反向传播)
  • [3. 神经网络编程基础](#3. 神经网络编程基础)
    • [3.1. 基本概念](#3.1. 基本概念)
    • [3.2. 逻辑回归](#3.2. 逻辑回归)
    • [3.3. 梯度下降法(Gradient Descent)](#3.3. 梯度下降法(Gradient Descent))
      • [3.3.1. 基础知识](#3.3.1. 基础知识)
      • [3.3.2. 梯度下降的形式化说明](#3.3.2. 梯度下降的形式化说明)
      • [3.3.3. 复合函数梯度计算图](#3.3.3. 复合函数梯度计算图)
      • [3.3.4. 逻辑回归的梯度下降](#3.3.4. 逻辑回归的梯度下降)
      • [3.3.5. m m m个样本的梯度下降](#3.3.5. m m m个样本的梯度下降)
      • [3.3.6. 梯度下降向量化](#3.3.6. 梯度下降向量化)
    • [3.4. 逻辑回归的数学解释](#3.4. 逻辑回归的数学解释)
      • [3.4.1. 单样本损失函数的来由](#3.4.1. 单样本损失函数的来由)
      • [3.4.2. 推广到多样本损失函数](#3.4.2. 推广到多样本损失函数)
      • [3.4.3. 反向思考一轮](#3.4.3. 反向思考一轮)
  • [4. Python编程基础](#4. Python编程基础)
    • [4.1. numpy广播](#4.1. numpy广播)
    • [4.2. 使用明确的矩阵维度表示](#4.2. 使用明确的矩阵维度表示)

1. 背景

为了学习Transformer,我开始投入一部分精力来学习神经网络。这篇文章会持续更新。

文章参考:https://towardsdatascience.com/math-neural-network-from-scratch-in-python-d6da9f29ce65

文章参考:https://blog.csdn.net/ourkix/article/details/134042761

文章参考:https://zhuanlan.zhihu.com/p/589538557

文章参考:https://www.bilibili.com/video/BV16r4y1Y7jv/?p=2\&spm_id_from=pageDriver\&vd_source=dcc37f900cca3b9bbf065e588e781e14

2. 理论知识

2.1. 单个神经元

2.1.1. 基础

如下图单个神经元

  • 输入为 x 1 x_1 x1, x 2 x_2 x2
  • 经过权重参数和偏置参数得: x 1 ∗ w 1 + x 2 ∗ w 2 + b x_1 * w_1 + x_2 * w_2 + b x1∗w1+x2∗w2+b
  • 最后总和经过激活函数得: y = f ( x 1 ∗ w 1 + x 2 ∗ w 2 + b ) y = f(x_1 * w_1 + x_2 * w_2 + b) y=f(x1∗w1+x2∗w2+b)
  • 常用的激活函数为 y = 1 1 + e − x y = \frac{1}{1+e^{-x}} y=1+e−x1,该激活函数可以将(负无穷,正无穷)映射到(0,1)的范围。

2.1.2. 神经元激活代码

如下代码定义一个基本的神经元,只实现forward函数

  • 神经元接受初始化参数:weight 和 bias
  • forward函数:进行input和weight进行向量点乘,并sigmoid激活
python 复制代码
import numpy as np

def sigmoid(x):
    return 1/(1+np.exp(-x))

class Neuron:
    def __init__(self, weight, bias):
        self.weight = weight
        self.bias = bias

    def forward(self, input):
        output = np.dot(input, self.weight) + self.bias
        return sigmoid(output)

weight = [1, 2]
bias = 10
neuron = Neuron(weight, bias)
print(neuron.forward([1, 2])) # sigmoid(1*1 + 2*2 + 10)
print(neuron.forward([-2, -4])) # sigmoid(-2 -8 + 10)

2.2. 多个神经元

2.2.1. 基础

考虑如下图的一个最简单的网络,有两个神经元的隐含层和一个神经元的输出层构成,它有如下公式
h 1 = f ( x 1 ∗ w 1 + x 2 ∗ w 2 + b 1 ) h 2 = f ( x 1 ∗ w 3 + x 2 ∗ w 4 + b 2 ) o 1 = f ( h 1 ∗ w 5 + h 2 ∗ w 6 + b 3 ) h_1 = f(x_1 * w_1 + x_2 * w_2 + b1) \\ h_2 = f(x_1 * w_3 + x_2 * w_4 + b2) \\ o_1 = f(h_1 * w_5 + h_2 * w_6 + b3) h1=f(x1∗w1+x2∗w2+b1)h2=f(x1∗w3+x2∗w4+b2)o1=f(h1∗w5+h2∗w6+b3)

也就是说,对于输入 x 1 , x 2 x_1, x_2 x1,x2经过相应权重和偏差计算可以得到输出,这个输出和真实值会有差距,如何来衡量这个差距呢?一般我们会使用"均方误差损失MSE"来衡量
L = 1 n ∑ i = 1 n ( o i ′ − o i ) 2 L = \frac{1}{n}\sum_{i=1}^{n}(o_i' - o_i)^2 L=n1i=1∑n(oi′−oi)2

这里假定 o i o_i oi为真实值, o i ′ o_i' oi′为预测值。

2.2.2. 神经元激活代码

通过神经元组合,可以构建一个weight和bias为固定值的小网络

python 复制代码
import numpy as np

def sigmoid(x):
    return 1/(1+np.exp(-x))

class Neuron:
    def __init__(self, weight, bias):
        self.weight = weight
        self.bias = bias

    def forward(self, input):
        output = np.dot(input, self.weight) + self.bias
        return sigmoid(output)

# x1, x2 -> h1, h2 -> o1
class NeuralNetwork:
    def __init__(self):
        weight = [1, 2]
        bias = 1
        self.h1 = Neuron(weight, bias)
        self.h2 = Neuron(weight, bias)
        self.o1 = Neuron(weight, bias)

    def forward(self, input):
        a = self.h1.forward(input)
        b = self.h2.forward(input)
        return self.o1.forward([a, b])

net = NeuralNetwork()
print(net.forward(np.array([-2, -4]))) # 0.731131354942724
print(sigmoid(-9)) # 0.00012339457598623172
print(sigmoid(0.000123394 * 1 + 0.000123394 * 2 + 1)) # 0.7311313546030448

# a = sigmoid(-9) 0.000123394
# b = sigmoid(-9) 0.000123394
# o = sigmoid(0.000123394 * 1 + 0.000123394 * 2 + 1) # 0.7311313546030448

2.2.3. 反向传播

简单总结下上述的几个公式
h 1 = f ( x 1 ∗ w 1 + x 2 ∗ w 2 + b 1 ) h 2 = f ( x 1 ∗ w 3 + x 2 ∗ w 4 + b 2 ) o 1 = f ( h 1 ∗ w 5 + h 2 ∗ w 6 + b 3 ) L = 1 n ∑ i = 1 n ( o i ′ − o i ) 2 h_1 = f(x_1 * w_1 + x_2 * w_2 + b1) \\ h_2 = f(x_1 * w_3 + x_2 * w_4 + b2) \\ o_1 = f(h_1 * w_5 + h_2 * w_6 + b3) \\ L = \frac{1}{n}\sum_{i=1}^{n}(o_i' - o_i)^2 h1=f(x1∗w1+x2∗w2+b1)h2=f(x1∗w3+x2∗w4+b2)o1=f(h1∗w5+h2∗w6+b3)L=n1i=1∑n(oi′−oi)2

为了找到最合适的权重和偏置,以最小化 L L L,我们可以采用梯度下降的方案来解决,通过 L L L分别对 w 1 w_1 w1或者 w 2 w_2 w2求偏导,然后使得 w 1 w_1 w1或者 w 2 w_2 w2都向着 L L L变小的方向移动一个小的step。

To be continued

3. 神经网络编程基础

本章节主要来讲解整个逻辑回归从0到1。

3.1. 基本概念

x x x: 表示一个 n x n_x nx维数据,为输入数据,维度为 ( n x , 1 ) (n_x, 1) (nx,1);
y y y:表示输出结果,取值为(0,1);
( x ( i ) , y ( i ) ) (x^{(i)}, y^{(i)}) (x(i),y(i)):表示第 i i i组数据,可能是训练数据,也可能是测试数据,此处默认为训练数据;
X = [ x ( 1 ) , x ( 2 ) , . . . , x ( m ) ] X = [x^{(1)}, x^{(2)}, . . . , x^{(m)}] X=[x(1),x(2),...,x(m)]:表示所有的训练数据集的输入值,放在一个 n x ∗ m n_x * m nx∗m的矩阵中,其中 m m m表示样本数目;
Y = [ y ( 1 ) , y ( 2 ) , . . . , y ( m ) ] Y = [y^{(1)}, y^{(2)}, . . . , y^{(m)}] Y=[y(1),y(2),...,y(m)]:对应表示所有训练数据集的输出值,维度为 1 ∗ m 1 * m 1∗m

上述的每个样本都是一个列向量,如 x ( i ) x^{(i)} x(i)的形式,多个样本按照列顺序排布,最终形成矩阵 X X X, 其中 X X X.shape = ( n x , m ) (n_x, m) (nx,m)

3.2. 逻辑回归

y y y的预测值 y ^ \hat{y} y^,是 y y y的一个估计。准确来说,我们使用 y ^ \hat{y} y^来表达 y y y等于 1 1 1的概率,它是一个0到1的值。举例来说,如果 x x x是一个图片,那么 y ^ \hat{y} y^可以用来表示这张图片是一只猫的概率有多大。我们用 w w w来表示逻辑回归的参数, b b b表示偏移量,构造函数 y ^ = w T ∗ x + b \hat{y} = w^T* x + b y^=wT∗x+b来进行预测y值。这是线性回归中使用的函数,但是对于二元分类来说 ,它有个问题。要用 y ^ \hat{y} y^表示 y y y等于 1 1 1的概率值,它在0和1之间,因此我们需要再输出之前再加上一个sigmoid函数,来控制输出范围。

经过修改,逻辑回归的输出函数为
y ^ = σ ( w T x + b ) ,其中 σ ( z ) = 1 1 + e − z \hat{y} = \sigma(w^Tx + b),其中\sigma(z) = \frac{1}{1 + e^{-z}} y^=σ(wTx+b),其中σ(z)=1+e−z1

对于给定的数据 { ( x ( 1 ) , y ( 1 ) ) , ( x ( 2 ) , y ( 2 ) ) , . . . , ( x ( m ) , y ( m ) ) } \{(x^{(1)},y^{(1)}) , (x^{(2)}, y^{(2)}), . . . , (x^{(m)},y^{(m)})\} {(x(1),y(1)),(x(2),y(2)),...,(x(m),y(m))},期望得到 y ^ ≈ y \hat{y} \approx y y^≈y。为了让模型学习到 w , b w, b w,b参数,需要定义一个损失函数(代价函数)。我们用 L ( y ^ , y ) L(\hat{y}, y) L(y^,y)来表示预测输出值 y ^ \hat{y} y^和真实值 y y y到底有多接近。

一般情况下我们会使用平方误差(预测值和真实值的平差差)来作为损失函数,但在逻辑回归中一般不这样做,这是因为我们的目标不是凸优化,它可能有多个局部最优值,梯度下降可能找不到全局最优值。在逻辑回归任务重,我们一般会使用另外一个损失函数:
L ( y ^ , y ) = − y l o g ( y ^ ) − ( 1 − y ) l o g ( 1 − y ^ ) L(\hat{y}, y) = -ylog(\hat{y}) - (1-y)log(1 - \hat{y}) L(y^,y)=−ylog(y^)−(1−y)log(1−y^)

当 y = 1 y=1 y=1的时候,损失函数简化为 l o g ( y ^ ) log(\hat{y}) log(y^),如果要最小化损失函数 L L L,则需要将 y ^ \hat{y} y^尽可能的大,它的取值范围为0-1,所以 y ^ \hat{y} y^会无限接近于1

当 y = 0 y=0 y=0的时候,损失函数简化为 − l o g ( 1 − y ^ ) -log(1-\hat{y}) −log(1−y^),如果要 L L L尽可能的小,需要 y ^ \hat{y} y^尽可能的小,所以 y ^ \hat{y} y^会无限接近于0(注意log函数是增函数,两个负号之后, − l o g ( 1 − y ^ ) -log(1-\hat{y}) −log(1−y^)还是增函数)

损失函数是基于单个样本来定义的,为了衡量其在全部样本的表现,使用所有样本的损失值均值来衡量,即
J ( w , b ) = 1 m ∑ i = 1 m L ( y ^ ( i ) , y ( i ) ) = 1 m ∑ i = 1 m ( − y ( i ) l o g ( y ^ ( i ) ) − ( 1 − y ( i ) ) l o g ( 1 − y ^ ( i ) ) ) J(w, b) = \frac{1}{m} \sum_{i=1}^{m} {L(\hat{y}^{(i)}, y^{(i)}) } \\ = {\frac{1}{m} \sum_{i=1}^{m} (-y^{(i)}log(\hat{y}^{(i)}) - (1-y^{(i)})log(1 - \hat{y}^{(i)})) } J(w,b)=m1i=1∑mL(y^(i),y(i))=m1i=1∑m(−y(i)log(y^(i))−(1−y(i))log(1−y^(i)))

在训练模型的时候,通过调整 w , b w,b w,b的值,以使得 J ( w , b ) J(w,b) J(w,b)最小。

3.3. 梯度下降法(Gradient Descent)

3.3.1. 基础知识

我们以一个单参数的w和b来举例,它的二维图示如下图所示:

这是一个凸函数,它像一个大碗。我们需要寻找合适 ( w , b ) (w,b) (w,b),以最小化 J ( w , b ) J(w,b) J(w,b)。算法随机初始化一个点,然后随着梯度下降的方向,直到全局最优。因为它是凸函数,不管从哪里初始化,都可以到达全局最优点。

如果上述函数是非凸函数,则有多个最小值,梯度下降就很容易落入局部最优值。

3.3.2. 梯度下降的形式化说明

梯度下降的更新逻辑是
w : = w − α d J ( w ) d w w := w - \alpha \frac{dJ(w)}{d w} w:=w−αdwdJ(w)

这里的 α \alpha α是学习率, d J ( w ) d w \frac{dJ(w)}{d w} dwdJ(w)为代价函数在 w w w处的梯度值

我们以单 w w w参数的函数来举例,如下图它是一个一元曲线图 。下图的右半边是增函数 ,它的导数大于0;左半边是减函数 ,它的导数小于0。每次梯度更新是使用 w w w减去一个梯度值,从左边来看,因为导数小于0,则每次减法相当于增加 w w w,从右边来看,因为导数大于0,则每次减法相当于减少 w w w。也就是说,不管 w w w的初始值是在最小值左侧,还是右侧, w w w都始终向着全局最小的位置行进。

当有多个参数的时候,这里的导数就变成的求偏导,梯度更新是每个参数都沿着梯度下降的方向,即
w : = w − α d J ( w , b ) d w , b : = b − α d J ( w , b ) d b w := w - \alpha \frac{dJ(w,b)}{d w} , b := b - \alpha \frac{dJ(w,b)}{d b} w:=w−αdwdJ(w,b),b:=b−αdbdJ(w,b)

3.3.3. 复合函数梯度计算图

对于如下的流程,如果我们学过复合函数的导数公式,我们有

d J d b = d J d v d v d u d u d b = 3 ∗ 1 ∗ c = 3 ∗ 1 ∗ 2 = 6 \frac{dJ}{db} = \frac{dJ}{dv}\frac{dv}{du}\frac{du}{db} = 3 * 1 * c = 3*1*2 = 6 dbdJ=dvdJdudvdbdu=3∗1∗c=3∗1∗2=6

这如何从微分意义上来大致解答呢?我们采用类似于微分增量 的概念,让 b b b增加一个微小量 0.001 0.001 0.001,即从 3 3 3增加到 3.001 3.001 3.001, 我们观察 J J J的增量,即可用来近似表达 d J d b \frac{dJ}{db} dbdJ的值。

3.3.4. 逻辑回归的梯度下降

回顾前述的公式
y ^ = a = σ ( w T x + b ) ,其中 σ ( z ) = 1 1 + e − z L ( y ^ , y ) = − y l o g ( y ^ ) − ( 1 − y ) l o g ( 1 − y ^ ) J ( w , b ) = 1 m ∑ i = 1 m L ( y ^ ( i ) , y ( i ) ) \hat{y} = a = \sigma(w^Tx + b),其中\sigma(z) = \frac{1}{1 + e^{-z}} \\ L(\hat{y}, y) = -ylog(\hat{y}) - (1-y)log(1 - \hat{y}) \\ J(w, b) = \frac{1}{m} \sum_{i=1}^{m} {L(\hat{y}^{(i)}, y^{(i)}) } y^=a=σ(wTx+b),其中σ(z)=1+e−z1L(y^,y)=−ylog(y^)−(1−y)log(1−y^)J(w,b)=m1i=1∑mL(y^(i),y(i))

我们用 a a a代替 y ^ \hat{y} y^来计算单个样本的代价函数,则 L L L对 a a a的导数 d L d a \frac{dL}{da} dadL为
d L d a = − ( y ∗ 1 a + ( 1 − y ) ∗ 1 1 − a ∗ ( − 1 ) ) = − ( y a + y − 1 1 − a ) = − y − a y + a y − a a ( 1 − a ) = a − y a ( 1 − a ) \begin{align*} \frac{dL}{da} &= -(y * \frac{1}{a} + (1-y) * \frac{1}{1-a} * (-1)) \\ &= -(\frac{y}{a} + \frac{y-1}{1-a}) = -\frac{y-ay + ay - a}{a(1-a)} \\ &= \frac{a-y}{a(1-a)} \\ \end{align*} dadL=−(y∗a1+(1−y)∗1−a1∗(−1))=−(ay+1−ay−1)=−a(1−a)y−ay+ay−a=a(1−a)a−y
a a a对 z z z的导数 d a d z \frac{da}{dz} dzda为
d a d z = − 1 ( 1 + e − z ) 2 ∗ e − z ∗ ( − 1 ) = e − z ∗ 1 ( 1 + e − z ) 2 = 1 − a a ∗ a 2 = a ( 1 − a ) \begin{align*} \frac{da}{dz} &= - \frac{1}{(1+e^{-z})^2} * e^{-z} * (-1) \\ &= e^{-z} * \frac{1}{(1+e^{-z})^2} \\ &= \frac{1-a}{a} * a^2 = a(1-a) \end{align*} dzda=−(1+e−z)21∗e−z∗(−1)=e−z∗(1+e−z)21=a1−a∗a2=a(1−a)

则连续求导法 d L d z \frac{dL}{dz} dzdL为
d L d z = d L d a ∗ d a d z = a − y a ( 1 − a ) ∗ a ( 1 − a ) = a − y \begin{align*} \frac{dL}{dz} &= \frac{dL}{da} * \frac{da}{dz} \\ &= \frac{a-y}{a(1-a)} * a(1-a) \\ &= a-y \end{align*} dzdL=dadL∗dzda=a(1−a)a−y∗a(1−a)=a−y

假设 w w w是个二元函数,满足 z = w 1 x 1 + w 2 x 2 + b z = w_1x_1 + w_2x_2 + b z=w1x1+w2x2+b。我们继续推导,求 L L L对 w w w和 b b b的导数,即计算 d L d w \frac{dL}{dw} dwdL和 d L d b \frac{dL}{db} dbdL的值:
d L d w 1 = d L d z d z d w 1 = ( a − y ) ∗ x 1 d L d w 2 = d L d z d z d w 2 = ( a − y ) ∗ x 2 d L d b = d L d z d z d b = ( a − y ) \begin{align*} \frac{dL}{dw_1} &= \frac{dL}{dz}\frac{dz}{dw_1} = (a-y) * x_1 \\ \frac{dL}{dw_2} &= \frac{dL}{dz}\frac{dz}{dw_2} = (a-y) * x_2 \\ \frac{dL}{db} &= \frac{dL}{dz}\frac{dz}{db} = (a-y) \end{align*} dw1dLdw2dLdbdL=dzdLdw1dz=(a−y)∗x1=dzdLdw2dz=(a−y)∗x2=dzdLdbdz=(a−y)

3.3.5. m m m个样本的梯度下降

上述是单个样本的梯度下降,我们现在计算 m m m个样本的梯度下降,再次回顾 m m m个样本的损失函数
J ( w , b ) = 1 m ∑ i = 1 m L ( a ( i ) , y ( i ) ) J(w,b) = \frac{1}{m} \sum_{i=1}^{m} L(a^{(i)}, y^{(i)}) J(w,b)=m1i=1∑mL(a(i),y(i))

则求对 w 1 w_1 w1的偏导为
d J ( w , b ) d w 1 = 1 m ∑ i = 1 m d L ( a ( i ) , y ( i ) ) d w 1 1 m ∑ i = 1 m ( a ( i ) − y ( i ) ) ∗ x 1 ( i ) \frac{dJ(w,b)}{dw_1} = \frac{1}{m} \sum_{i=1}^{m} \frac{dL(a^{(i)}, y^{(i)})}{dw_1} \\ \frac{1}{m} \sum_{i=1}^{m}(a^{(i)}-y^{(i)})*x_1^{(i)} dw1dJ(w,b)=m1i=1∑mdw1dL(a(i),y(i))m1i=1∑m(a(i)−y(i))∗x1(i)

后续为了简化起见,在某些地方会用 d w 1 dw_1 dw1来表示 d J ( w , b ) d w 1 \frac{dJ(w,b)}{dw_1} dw1dJ(w,b)

梯度下降算法

偷懒一把,直接从视频中截图出梯度下降算法的实现。其实通过前述的推导,我们已经明确知道了 d J ( w , b ) d w 1 \frac{dJ(w,b)}{dw_1} dw1dJ(w,b)是如何计算的,也就不难得到如下的算法迭代公式。

它的思想很简单

  • 初始化损失和梯度都为0(为后续的sum求和作准备)
  • 根据前述 J ( w , b ) J(w,b) J(w,b)的计算公式,计算出 J ( w , b ) J(w,b) J(w,b)
  • 然后应用前述的梯度计算方法,计算出 d J ( w , b ) d w 1 \frac{dJ(w,b)}{dw_1} dw1dJ(w,b)的值,这里的损失求和和梯度求和是在遍历样本的过程中进行累加的
  • 最后遍历完所有样本求均值
  • 在得到前述的梯度和之后,应用梯度下降的更新公式,代入学习率,即可得到新的 w w w和 b b b

3.3.6. 梯度下降向量化

上述梯度计算的是通过两层for循环迭代,另外一种思路是采用矩阵向量运算,这样可以更好利用并行化优化。

向量化和非向量化的代码耗时

python 复制代码
import numpy as np
import time
a = np.random.rand(1000000)
b = np.random.rand(1000000)

tic = time.time()
c = np.dot(a, b)
toc = time.time()
print(c)
print("Vectorized version:"+ str(1000*(toc-tic))+"ms")

c = 0
tic = time.time()
for i in range(1000000):
    c += a[i]*b[i]
toc = time.time()
print(c)
print("For loop:"+ str(1000*(toc-tic))+ "ms")

输出结果

250121.78770186222
Vectorized version:23.240327835083008ms
250121.7877018585
For loop:487.37192153930664ms

可以看到两者有20多倍的耗时差,这得益于向量化的能力,CPU一般可以很容易使用SIMD指令来加速矩阵运算。想写循环时候,检查 numpy 是否存在类似的内置函数,从而避免使用循环(loop)方式。

我们首先使用向量化来优化前向传播的一些计算

已知
z ( 1 ) = ( w T x ( 1 ) + b ) , z ( 2 ) = ( w T x ( 2 ) + b ) , z ( 3 ) = ( w T x ( 3 ) + b ) z^{(1)} = (w^Tx^{(1)} + b), z^{(2)} = (w^Tx^{(2)} + b), z^{(3)} = (w^Tx^{(3)} + b) z(1)=(wTx(1)+b),z(2)=(wTx(2)+b),z(3)=(wTx(3)+b)

从而,我们有
Z = [ z ( 1 ) , z ( 2 ) , . . . , z ( m ) ] = [ ( w T x ( 1 ) + b ) , ( w T x ( 2 ) + b ) , . . . , ( w T x ( m ) + b ) ] = [ w T x ( 1 ) , w T x ( 2 ) , . . . , w T x ( m ) ] + [ b , b , . . . . , b ] = w T [ x ( 1 ) , x ( 2 ) , . . . , x ( m ) ] + [ b , b , . . . . , b ] \begin{align*} Z &= [z^{(1)}, z^{(2)}, ..., z^{(m)}] \\ &= [(w^Tx^{(1)} + b), (w^Tx^{(2)} + b), ..., (w^Tx^{(m)} + b)] \\ &= [w^Tx^{(1)}, w^Tx^{(2)}, ..., w^Tx^{(m)}] + [b,b,....,b]\\ &= w^T[x^{(1)}, x^{(2)}, ..., x^{(m)}]+[b,b,....,b] \end{align*} Z=[z(1),z(2),...,z(m)]=[(wTx(1)+b),(wTx(2)+b),...,(wTx(m)+b)]=[wTx(1),wTx(2),...,wTx(m)]+[b,b,....,b]=wT[x(1),x(2),...,x(m)]+[b,b,....,b]

显而易见,我们可以定义一个输入矩阵 X = [ x ( 1 ) , x ( 2 ) , . . . , x ( m ) ] X = [x^{(1)}, x^{(2)}, ..., x^{(m)}] X=[x(1),x(2),...,x(m)],它是一个 n ∗ m n*m n∗m的矩阵,其中 n n n为单个样本的维度, m m m为样本个数,它的每列是一个样本,则
Z = w T X + [ b , b , . . . . , b ] Z = w^TX+[b,b,....,b] Z=wTX+[b,b,....,b]

在代码实现中,我们可以写 Z = n p . d o t ( w T , X ) + b Z = np.dot(w^T, X) + b Z=np.dot(wT,X)+b,用向量加上实数 b b b,python会自动将 b b b进行展开成向量,那么关于激活值 a a a呢?

我们已知
a ( 1 ) = σ ( z ( 1 ) ) , a ( 2 ) = σ ( z ( 2 ) ) , a ( 3 ) = σ ( z ( 3 ) ) a^{(1)} = \sigma(z^{(1)}), a^{(2)} = \sigma(z^{(2)}), a^{(3)} = \sigma(z^{(3)}) a(1)=σ(z(1)),a(2)=σ(z(2)),a(3)=σ(z(3))

则激活值向量为
A = [ a ( 1 ) , a ( 2 ) , . . . , a ( m ) ] = [ σ ( z ( 1 ) ) , σ ( z ( 2 ) ) , . . . , σ ( z ( m ) ) ] = σ ( Z ) \begin{align*} A &= [a^{(1)}, a^{(2)}, ..., a^{(m)}] \\ &= [\sigma(z^{(1)}), \sigma(z^{(2)}), ..., \sigma(z^{(m)})] \\ &=\sigma(Z) \end{align*} A=[a(1),a(2),...,a(m)]=[σ(z(1)),σ(z(2)),...,σ(z(m))]=σ(Z)

再次回忆代价函数 J ( w , b ) J(w,b) J(w,b),将它使用激活值来表示,则
J ( w , b ) = 1 m ∑ i = 1 m − y ( i ) l o g a ( i ) − ( 1 − y ( i ) ) l o g ( 1 − a ( i ) ) J(w, b) = \frac{1}{m} \sum_{i=1}^{m} {-y^{(i)}loga^{(i)} -(1-y^{(i)})log(1-a^{(i)})} J(w,b)=m1i=1∑m−y(i)loga(i)−(1−y(i))log(1−a(i))

若令 Y = [ y ( 1 ) , y ( 2 ) , . . . , y ( m ) ] Y=[y^{(1)}, y^{(2)}, ..., y^{(m)}] Y=[y(1),y(2),...,y(m)],则
J ( w , b ) = 1 m ∑ i = 1 m − y ( i ) l o g a ( i ) − ( 1 − y ( i ) ) l o g ( 1 − a ( i ) ) = − Y T l o g ( A ) − ( 1 − Y ) T l o g ( 1 − A ) \begin{align*} J(w, b) &= \frac{1}{m} \sum_{i=1}^{m} {-y^{(i)}loga^{(i)} -(1-y^{(i)})log(1-a^{(i)})} \\ &=-Y^Tlog(A)-(1-Y)^Tlog(1-A) \end{align*} J(w,b)=m1i=1∑m−y(i)loga(i)−(1−y(i))log(1−a(i))=−YTlog(A)−(1−Y)Tlog(1−A)

其次,我们使用向量化来优化两层for循环的梯度下降算法

回顾一下梯度计算算法的流程:

我们用 d Z = [ d z ( 1 ) , d z ( 2 ) , . . . , d z ( m ) ] dZ = [dz^{(1)}, dz^{(2)}, ..., dz^{(m)} ] dZ=[dz(1),dz(2),...,dz(m)],则我们有
d Z = [ d z ( 1 ) , d z ( 2 ) , . . . , d z ( m ) ] = [ a ( 1 ) − y ( 1 ) , a ( 2 ) − y ( 2 ) , . . . , a ( m ) − y ( m ) ] = [ a ( 1 ) , a ( 2 ) , . . . , a ( m ) ] − [ y ( 1 ) , y ( 2 ) , . . . , y ( m ) ] = A − Y \begin{align*} dZ &= [dz^{(1)}, dz^{(2)}, ..., dz^{(m)} ] \\ & = [a^{(1)} - y^{(1)}, a^{(2)} - y^{(2)}, ..., a^{(m)} - y^{(m)} ] \\ &= [a^{(1)}, a^{(2)}, ..., a^{(m)}] - [y^{(1)}, y^{(2)}, ..., y^{(m)}] \\ & = A - Y \end{align*} dZ=[dz(1),dz(2),...,dz(m)]=[a(1)−y(1),a(2)−y(2),...,a(m)−y(m)]=[a(1),a(2),...,a(m)]−[y(1),y(2),...,y(m)]=A−Y

梯度更新的计算公式使用 d z ( i ) dz^{(i)} dz(i)来表示的话,则为
d w 1 = 1 m ∑ i = 1 m d L ( a ( i ) , y ( i ) ) d w 1 = 1 m ∑ i = 1 m x 1 ( i ) ∗ d z ( i ) d w 2 = 1 m ∑ i = 1 m d L ( a ( i ) , y ( i ) ) d w 2 = 1 m ∑ i = 1 m x 2 ( i ) ∗ d z ( i ) d b = 1 m ∑ i = 1 m d L ( a ( i ) , y ( i ) ) d b = 1 m ∑ i = 1 m d z ( i ) dw_1 = \frac{1}{m} \sum_{i=1}^{m} \frac{dL(a^{(i)}, y^{(i)})}{dw_1} = \frac{1}{m} \sum_{i=1}^{m} {x_1^{(i)} * dz^{(i)}} \\ dw_2 = \frac{1}{m} \sum_{i=1}^{m} \frac{dL(a^{(i)}, y^{(i)})}{dw_2} = \frac{1}{m} \sum_{i=1}^{m} {x_2^{(i)} * dz^{(i)} } \\ db = \frac{1}{m} \sum_{i=1}^{m} \frac{dL(a^{(i)}, y^{(i)})}{db} = \frac{1}{m} \sum_{i=1}^{m} { dz^{(i)}} dw1=m1i=1∑mdw1dL(a(i),y(i))=m1i=1∑mx1(i)∗dz(i)dw2=m1i=1∑mdw2dL(a(i),y(i))=m1i=1∑mx2(i)∗dz(i)db=m1i=1∑mdbdL(a(i),y(i))=m1i=1∑mdz(i)

则 d b = 1 m ∗ n p . s u m ( d Z ) db = \frac{1}{m} * np.sum(dZ) db=m1∗np.sum(dZ)。

接下来,我们来求 d w dw dw,由于 w w w是列向量,为了便于梯度更新时候的矩阵运算,我们令 d w = [ d w 1 , d w 2 , . . . , d w n ] T dw = [dw_1, dw_2, ..., dw_n]^T dw=[dw1,dw2,...,dwn]T,也是一个列向量,则 d w dw dw可以表示为:
d w = [ d w 1 d w 2 ⋮ d w n ] = 1 m ∗ [ x 1 ( 1 ) x 1 ( 2 ) ⋯ x 1 ( m ) x 2 ( 1 ) x 2 ( 2 ) ⋯ x 2 ( m ) ⋮ ⋮ ⋮ ⋮ x n ( 1 ) x n ( 2 ) ⋯ x n ( m ) ] ∗ [ d z ( 1 ) d z ( 2 ) ⋮ d z ( m ) ] = 1 m ∗ [ x ( 1 ) , x ( 2 ) , . . . , x ( m ) ] ∗ [ d z ( 1 ) d z ( 2 ) ⋮ d z ( m ) ] \begin{align*} dw &= \left[ \begin{matrix} dw_1 \\ dw_2 \\ \vdots \\ dw_n \\ \end{matrix}\right] \\ &= \frac{1}{m} * \left[ \begin{matrix} x_1^{(1)} & x_1^{(2)} & \cdots & x_1^{(m)} \\ x_2^{(1)} & x_2^{(2)} & \cdots & x_2^{(m)} \\ \vdots & \vdots & \vdots & \vdots \\ x_n^{(1)} & x_n^{(2)} & \cdots & x_n^{(m)} \\ \end{matrix} \right] * \left[ \begin{matrix} dz^{(1)} \\ dz^{(2)} \\ \vdots\\ dz^{(m)} \\ \end{matrix} \right] \\ &= \frac{1}{m} * [x^{(1)}, x^{(2)}, ..., x^{(m)}]*\left[ \begin{matrix} dz^{(1)} \\ dz^{(2)} \\ \vdots\\ dz^{(m)} \\ \end{matrix} \right] \end{align*} dw= dw1dw2⋮dwn =m1∗ x1(1)x2(1)⋮xn(1)x1(2)x2(2)⋮xn(2)⋯⋯⋮⋯x1(m)x2(m)⋮xn(m) ∗ dz(1)dz(2)⋮dz(m) =m1∗[x(1),x(2),...,x(m)]∗ dz(1)dz(2)⋮dz(m)

回忆一下前述输入矩阵 X X X和 Z Z Z的表示形式, d w dw dw可以简化为
d w = 1 m ∗ X ∗ d Z T dw = \frac{1}{m} * X * dZ^T dw=m1∗X∗dZT

这可以使用代码 d w = 1 m ∗ n p . d o t ( X , d Z T ) dw = \frac{1}{m} * np.dot(X, dZ^T) dw=m1∗np.dot(X,dZT)。我们也可以将其写成样本展开的形式为
d w = 1 m ( x ( 1 ) d z ( 1 ) + x ( 2 ) d z ( m ) + . . . + x ( m ) d z ( m ) ) dw = \frac{1}{m} (x^{(1)}dz^{(1)} + x^{(2)}dz^{(m)} + ... + x^{(m)}dz^{(m)}) dw=m1(x(1)dz(1)+x(2)dz(m)+...+x(m)dz(m))

经过上述的矩阵简化之后,我们的前向和反向传播的公式可以简化为
Z = w T X + [ b , b , . . . . , b ] = n p . d o t ( w T , X ) + b A = [ a ( 1 ) , a ( 2 ) , . . . , a ( m ) ] = σ ( Z ) J ( w , b ) = − Y T l o g ( A ) − ( 1 − Y ) T l o g ( 1 − A ) d Z = [ d z ( 1 ) , d z ( 2 ) , . . . , d z ( m ) ] = A − Y d w = 1 m ∗ X ∗ d Z T = 1 m ∗ n p . d o t ( X , d Z T ) d b = 1 m ∗ n p . s u m ( d Z ) w : = w − α ∗ d w b : = b − α ∗ d b \begin{align*} Z &= w^TX+[b,b,....,b] = np.dot(w^T, X) + b \\ A&= [a^{(1)}, a^{(2)}, ..., a^{(m)}] =\sigma(Z) \\ J(w, b) &=-Y^Tlog(A)-(1-Y)^Tlog(1-A) \\ dZ &= [dz^{(1)}, dz^{(2)}, ..., dz^{(m)} ] = A - Y \\ dw &= \frac{1}{m} * X * dZ^T = \frac{1}{m} * np.dot(X, dZ^T) \\ db &= \frac{1}{m} * np.sum(dZ) \\ w :&= w - \alpha * dw \\ b :&= b - \alpha * db \end{align*} ZAJ(w,b)dZdwdbw:b:=wTX+[b,b,....,b]=np.dot(wT,X)+b=[a(1),a(2),...,a(m)]=σ(Z)=−YTlog(A)−(1−Y)Tlog(1−A)=[dz(1),dz(2),...,dz(m)]=A−Y=m1∗X∗dZT=m1∗np.dot(X,dZT)=m1∗np.sum(dZ)=w−α∗dw=b−α∗db

利用前几个公式实现前向和反向传播,利用最后两个公式完成梯度更新,上述的公式没有任何for循环,全是向量运算,足够高效。

对应python代码

python 复制代码
def sigmoid(z):
	return 1/(1 + np.exp(-z))

def compute gradient(X,y,w, b):
	m=X.shape[1]
	z=np.dot(w.T,X)+b
	a= sigmoid(z)
	dw=(1/m)*np.dot(X, (a-y).T)
	db =(1/m)*np.sum(a-y)
	return dw, db

3.4. 逻辑回归的数学解释

3.4.1. 单样本损失函数的来由

对于给定的一个样本 ( x , y ) (x, y) (x,y),对于 x x x,逻辑回归中的预测结果 y ^ \hat{y} y^表示如下
y ^ = σ ( w T x + b ) ,其中 σ ( z ) = 1 1 + e − z \hat{y} = \sigma(w^Tx + b),其中\sigma(z) = \frac{1}{1 + e^{-z}} y^=σ(wTx+b),其中σ(z)=1+e−z1

这里 y ^ \hat{y} y^表示 y = 1 y=1 y=1的概率,它的输出值在 [ 0 , 1 ] [0,1] [0,1]之间。由于这是一个二分类问题,真实值 y y y只能取值 0 0 0或者 1 1 1,所以我们有:
p ( y = 1 ∣ x ) = y ^ p ( y = 0 ∣ x ) = 1 − y ^ \begin{align*} p(y=1|x) &= \hat{y} \\ p(y=0|x) &= 1 - \hat{y} \end{align*} p(y=1∣x)p(y=0∣x)=y^=1−y^

上述公式是两个式子,为了简便计算,我们将其合并为一个式子来表达,即
p ( y ∣ x ) = y ^ y ∗ ( 1 − y ^ ) ( 1 − y ) p(y|x) = \hat{y}^y * (1-\hat{y})^{(1-y)} p(y∣x)=y^y∗(1−y^)(1−y)

我们可以代入 y = 1 y=1 y=1,满足 p ( y = 1 ∣ x ) = y ^ p(y=1|x) = \hat{y} p(y=1∣x)=y^;代入 y = 0 y=0 y=0,满足 p ( y = 0 ∣ x ) = 1 − y ^ p(y=0|x) = 1 - \hat{y} p(y=0∣x)=1−y^。

对于给定的一个样本 ( x , y ) (x,y) (x,y),我们的目标是最大化 p ( y ∣ x ) p(y|x) p(y∣x),它等价于最大化 l o g ( p ( y ∣ x ) ) log(p(y|x)) log(p(y∣x)),这是因为 l o g log log函数是严格递增的函数,故而我们目标是最大化
l o g ( ( p ( y ∣ x ) ) = l o g ( y ^ y ∗ ( 1 − y ^ ) ( 1 − y ) ) = y l o g y ^ + ( 1 − y ) l o g ( 1 − y ^ ) log((p(y|x)) = log(\hat{y}^y * (1-\hat{y})^{(1-y)}) = ylog\hat{y} + (1-y)log(1-\hat{y}) log((p(y∣x))=log(y^y∗(1−y^)(1−y))=ylogy^+(1−y)log(1−y^)

回想前述的损失函数的定义:
L ( y ^ , y ) = − y l o g ( y ^ ) − ( 1 − y ) l o g ( 1 − y ^ ) L(\hat{y}, y) = -ylog(\hat{y}) - (1-y)log(1 - \hat{y}) L(y^,y)=−ylog(y^)−(1−y)log(1−y^)

所以我们有
l o g ( ( p ( y ∣ x ) ) = − L ( y ^ , y ) log((p(y|x)) = -L(\hat{y}, y) log((p(y∣x))=−L(y^,y)

考虑到损失函数前面有个负号,因此最大化输出概率值 p ( y ∣ x ) p(y|x) p(y∣x)等价于最小化损失函数 L ( y ^ , y ) L(\hat{y},y) L(y^,y),这是单个样本的损失函数表达式。

3.4.2. 推广到多样本损失函数

对于有 m m m个样本,我们的目标是寻找参数 ( w , b ) (w,b) (w,b),以最大化联合概率函数
P ( y ( 1 ) , y ( 2 ) , . . . , y ( m ) ∣ x ( 1 ) , x ( 2 ) , . . . , x ( m ) ) P({y}^{(1)},{y}^{(2)},...,{y}^{(m)}|x^{(1)}, x^{(2)}, ..., x^{(m)}) P(y(1),y(2),...,y(m)∣x(1),x(2),...,x(m))

假设所有的样本满足独立同分布的特性,则联合概率密度函数等于所有样本概率密度函数的乘积,即
P ( y ( 1 ) , y ( 2 ) , . . . , y ( m ) ∣ x ( 1 ) , x ( 2 ) , . . . , x ( m ) ) = p ( y ( 1 ) ∣ x ( 1 ) ) ∗ p ( y ( 2 ) ∣ x ( 2 ) ) ∗ . . . ∗ p ( y ( m ) ∣ x ( m ) ) = ∏ i = 1 m p ( y ( i ) ∣ x ( i ) ) \begin{align*} P({y}^{(1)},{y}^{(2)},...,{y}^{(m)}|x^{(1)}, x^{(2)}, ..., x^{(m)}) &= p({y}^{(1)} | x^{(1)}) * p({y}^{(2)} | x^{(2)}) * ... * p({y}^{(m)} | x^{(m)}) \\ & = \prod_{i=1}^m p({y}^{(i)} | x^{(i)}) \end{align*} P(y(1),y(2),...,y(m)∣x(1),x(2),...,x(m))=p(y(1)∣x(1))∗p(y(2)∣x(2))∗...∗p(y(m)∣x(m))=i=1∏mp(y(i)∣x(i))

同样利用 l o g log log的特性,两边求对数,以将乘积变成加法,则等同于最大化:
l o g ( ∏ i = 1 m p ( y ( i ) ∣ x ( i ) ) ) = ∑ i = 1 m l o g ( p ( y ( i ) ∣ x ( i ) ) ) = − ∑ i = 1 m L ( y ^ , y ) log(\prod_{i=1}^m p({y}^{(i)} | x^{(i)})) = \sum_{i=1}^{m}log(p({y}^{(i)} | x^{(i)})) = - \sum_{i=1}^{m} L(\hat{y}, y) log(i=1∏mp(y(i)∣x(i)))=i=1∑mlog(p(y(i)∣x(i)))=−i=1∑mL(y^,y)

由于上面的式子最右边有个负号,因此最大化联合概率密度函数等同于最小化
∑ i = 1 m L ( y ^ , y ) \sum_{i=1}^{m} L(\hat{y}, y) i=1∑mL(y^,y)

前面乘以系数 1 m \frac{1}{m} m1,这就等同于我们的前述所述的多样本损失函数的定义
J ( w , b ) = 1 m ∑ i = 1 m L ( a ( i ) , y ( i ) ) J(w,b) = \frac{1}{m} \sum_{i=1}^{m} L(a^{(i)}, y^{(i)}) J(w,b)=m1i=1∑mL(a(i),y(i))

3.4.3. 反向思考一轮

我们期望找到参数 ( w , b ) (w,b) (w,b),它能使得 m m m个样本出现的概率最大,即最大化这些样本的联合概率密度函数
P ( y ( 1 ) , y ( 2 ) , . . . , y ( m ) ∣ x ( 1 ) , x ( 2 ) , . . . , x ( m ) ; w , b ) P({y}^{(1)},{y}^{(2)},...,{y}^{(m)}|x^{(1)}, x^{(2)}, ..., x^{(m)}; w,b) P(y(1),y(2),...,y(m)∣x(1),x(2),...,x(m);w,b)

这种是我们概率论中的常考题型,参考最大似然估计一章,它详细解释了如何依赖样本值来计算出 w w w和 b b b。

联合概率的公式不容易计算,我们一般认为样本之间满足独立同分布的特性,这就可以将联合概率密度写成单样本概率密度函数的乘积。
p ( y ( 1 ) ∣ x ( 1 ) ) ∗ p ( y ( 2 ) ∣ x ( 2 ) ) ∗ . . . ∗ p ( y ( m ) ∣ x ( m ) ) = ∏ i = 1 m p ( y ( i ) ∣ x ( i ) ) p({y}^{(1)} | x^{(1)}) * p({y}^{(2)} | x^{(2)}) * ... * p({y}^{(m)} | x^{(m)})= \prod_{i=1}^m p({y}^{(i)} | x^{(i)}) p(y(1)∣x(1))∗p(y(2)∣x(2))∗...∗p(y(m)∣x(m))=i=1∏mp(y(i)∣x(i))

概率密度的乘积同样很难计算,我们进一步通过 l o g log log对数的形式,将其转换为加法运算,即最大化
∑ i = 1 m l o g ( p ( y ( i ) ∣ x ( i ) ) ) \sum_{i=1}^{m}log(p({y}^{(i)} | x^{(i)})) i=1∑mlog(p(y(i)∣x(i)))

在概率论书籍中,到达这一步之后,我们会根据题目中给出的样本概率分布,将概率密度函数代入到 p ( y ( i ) ∣ x ( i ) ) p({y}^{(i)} | x^{(i)}) p(y(i)∣x(i))中,进行化简。对于本文的二元预测来说,我们定义了 y ^ \hat{y} y^为 p ( y = 1 ∣ x ) p(y=1|x) p(y=1∣x),进而可以得到二元样本 ( x , y ) (x,y) (x,y)的概率分布函数(对于离散型随机变量 y y y,我们通常称之为概率分布函数)为 p ( y ∣ x ) = y ^ y ∗ ( 1 − y ^ ) ( 1 − y ) p(y|x) =\hat{y}^y * (1-\hat{y})^{(1-y)} p(y∣x)=y^y∗(1−y^)(1−y),代入到上面的式子,即最大化
∑ i = 1 m ( y ( i ) l o g y ^ ( i ) + ( 1 − y ( i ) ) l o g ( 1 − y ^ ( i ) ) ) \sum_{i=1}^{m}( y^{(i)}log\hat{y}^{(i)} + (1-y^{(i)})log(1-\hat{y}^{(i)}) ) i=1∑m(y(i)logy^(i)+(1−y(i))log(1−y^(i)))

又因为我们的 y ^ = σ ( w T x + b ) \hat{y} = \sigma(w^Tx+b) y^=σ(wTx+b),则可以进一步代入上式,即最大化
∑ i = 1 m ( y ( i ) l o g σ ( w T x ( i ) + b ) + ( 1 − y ( i ) ) l o g ( 1 − σ ( w T x ( i ) + b ) ) ) \sum_{i=1}^{m}( y^{(i)}log \sigma(w^Tx^{(i)}+b) + (1-y^{(i)})log(1- \sigma(w^Tx^{(i)}+b)) ) i=1∑m(y(i)logσ(wTx(i)+b)+(1−y(i))log(1−σ(wTx(i)+b)))

再重复一遍,我们的目标是求出参数 ( w , b ) (w,b) (w,b),以使得上述公式的值最大。对于概率论的书籍中,这个时候,我们会将上述公式对 w w w和 b b b分别求偏导,使得所有的偏导值为0,解这个多元方程组,就可以使用样本值来表达 w w w和 b b b。令 P P P代表上述的公式,我们有
∂ P ∂ w 1 = 0 ∂ P ∂ w 2 = 0 ∂ P ∂ b = 0 \begin{align*} \frac{\partial{P}}{\partial w_1} &= 0 \\ \frac{\partial{P}}{\partial w_2} &= 0 \\ \frac{\partial{P}}{\partial b} &= 0 \end{align*} ∂w1∂P∂w2∂P∂b∂P=0=0=0

想一想我们梯度下降在做的事情,我们初始化 ( w , b ) (w,b) (w,b)之后通过不断梯度下降的迭代,使得代价函数 J ( w , b ) J(w,b) J(w,b)最小。当其最小的时候,其实就是上述的各个偏导数为0。

4. Python编程基础

4.1. numpy广播

  • 矩阵按列求和
python 复制代码
import numpy as np
A = np.array([[56.0,0.0,4.4,68.0],
              [1.2,104.0,52.0,8.0],
              [1.8,135.0,99.0,0.9]])
print(A)

## 按列求和
sumA = A.sum(axis=0)
print(sumA)  # 输出 [ 59.  239.  155.4  76.9]
  • 矩阵除法广播
python 复制代码
## 求各个列中不同元素的占比
print(A/sumA)

输出
[[0.94915254 0.         0.02831403 0.88426528]
 [0.02033898 0.43514644 0.33462033 0.10403121]
 [0.03050847 0.56485356 0.63706564 0.01170351]]

一个 m ∗ n m*n m∗n的矩阵除以 1 ∗ m 1*m 1∗m的矩阵,相当于 m ∗ n m*n m∗n的矩阵的每一行都和该行向量对应元素相除。与之所类比,我们同样可以进行加、减、乘运算。

  • 矩阵加上一个实数
python 复制代码
## 4.2. 每个元素都加上100
print(A+100)

输出
[[156.  100.  104.4 168. ]
 [101.2 204.  152.  108. ]
 [101.8 235.  199.  100.9]]
  • 维度延展
    广播机制首先需要判断参与计算的两个数组能否被广播机制处理?即判断是否广播兼容,规则是,比较两个数组的shape,从shape的尾部开始一一比对
    • (1) 如果两个数组的维度相同,从后向前逐个维进行比对,若轴长度相同或者其中一方的长度为1,则广播兼容:例如 m ∗ n m*n m∗n和 1 ∗ n 1*n 1∗n,它们都是2维的,从后向前比对,先是 n n n,两个轴长度相同;其次是 m m m和 1 1 1,其中一方为 1 1 1,故而满足广播兼容。
    • (2) 如果两个数组的维度不同,那么给低维度的数组前扩展提升一维,且扩展维的轴长度为1,然后继续形同(1)进行处理:例如 k ∗ m ∗ n k*m*n k∗m∗n和 1 ∗ n 1*n 1∗n,它们的维度不同,需要先给 1 ∗ n 1*n 1∗n扩展一个维度变成 1 ∗ 1 ∗ n 1*1*n 1∗1∗n,然后按照方法(1)进行处理。

4.2. 使用明确的矩阵维度表示

使用明确的维度表示,不要使用一维数组,转向使用 n ∗ 1 n*1 n∗1维矩阵(基本上是列向量),或者 1 ∗ n 1 * n 1∗n 维矩阵(基本上是行向量)。

相关推荐
张人玉23 分钟前
人工智能——猴子摘香蕉问题
人工智能
草莓屁屁我不吃27 分钟前
Siri因ChatGPT-4o升级:我们的个人信息还安全吗?
人工智能·安全·chatgpt·chatgpt-4o
小言从不摸鱼31 分钟前
【AI大模型】ChatGPT模型原理介绍(下)
人工智能·python·深度学习·机器学习·自然语言处理·chatgpt
AI科研视界1 小时前
ChatGPT+2:修订初始AI安全性和超级智能假设
人工智能·chatgpt
霍格沃兹测试开发学社测试人社区1 小时前
人工智能 | 基于ChatGPT开发人工智能服务平台
软件测试·人工智能·测试开发·chatgpt
小R资源1 小时前
3款免费的GPT类工具
人工智能·gpt·chatgpt·ai作画·ai模型·国内免费
artificiali4 小时前
Anaconda配置pytorch的基本操作
人工智能·pytorch·python
酱香编程,风雨兼程5 小时前
深度学习——基础知识
人工智能·深度学习
Lossya5 小时前
【机器学习】参数学习的基本概念以及贝叶斯网络的参数学习和马尔可夫随机场的参数学习
人工智能·学习·机器学习·贝叶斯网络·马尔科夫随机场·参数学习
#include<菜鸡>5 小时前
动手学深度学习(pytorch土堆)-04torchvision中数据集的使用
人工智能·pytorch·深度学习