神经网络
- [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
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 维矩阵(基本上是行向量)。