第一节课-第二周:神经网络的编程基础(Basics of Neural Network programming)

二、神经网络的编程基础(Basics of Neural Network programming)

2.1 二分类(Binary Classification)

2.2 逻辑回归(Logistic Regression)

2.3 逻辑回归的代价函数(Logistic Regression Cost Function)

2.4 梯度下降法(Gradient Descent)

2.5 导数(Derivatives)

2.6 更多的导数例子(More Derivative Examples)

2.7 计算图(Computation Graph)

2.8 使用计算图求导数(Derivatives with a Computation Graph)

2.9 逻辑回归中的梯度下降(Logistic Regression Gradient Descent)

2.10 m 个样本的梯度下降(Gradient Descent on m Examples)

2.11 向量化(Vectorization)

2.12 向量化的更多例子(More Examples of Vectorization)

2.13 向量化逻辑回归(Vectorizing Logistic Regression)

2.14 向量化 logistic 回归的梯度输出(Vectorizing Logistic Regression's Gradient)

2.15 Python 中的广播(Broadcasting in Python)

2.16 关于 python _ numpy 向量的说明(A note on python or numpy vectors)参考视频:

2.17 Jupyter/iPython Notebooks快速入门(Quick tour of Jupyter/iPython Notebooks)

2.18 (选修)logistic 损失函数的解释(Explanation of logistic regression cost function)

 

2.1 二分类(Binary Classification)

 

在神经网络的计算中,通常先有一个叫做前向暂停(forward pause)或叫做前向传播(foward propagation)的步骤,接着有一个叫做反向暂停(backward pause) 或叫做反向传播(backward propagation)的步骤。所以这周我也会向你介绍为什么神经网络的训练过程可以分为前向传播和反向传播两个独立的部分。

在课程中我将使用逻辑回归(logistic regression)来传达这些想法,以使大家能够更加容易地理解这些概念。 逻辑回归是一个用于二分类(binary classification)的算法。

假如你有一张图片作为输入,比如这只猫,如果识别这张图片为猫,则输出标签1作为结果;如果识别出不是猫,那么输出标签0作为结果。现在我们可以用字母 y来表示输出的结果标签,如下图所示:

 在计算机中为了保存一张图片,需要保存三个矩阵,它们分别对应图片中的红、绿、蓝三种颜色通道,如果你的图片大小为64x64像素,那么你就有三个规模为64x64的矩阵,分别对应图片中红、绿、蓝三种像素的强度值。为了便于表示,这里我画了三个很小的矩阵,注意它们的规模为5x4 而不是64x64,如下图所示:

为了把这些像素值放到一个特征向量中,我们需要把这些像素值提取出来,然后放入一个特征向量。为了把这些像素值转换为特征向量 x, 我们需要像下面这样定义一个特征向量x 来表示这张图片,我们把所有的像素都取出来,例如255、231等等,直到取完所有的红色像素,接着最后是255、134、…、255、134等等,直到得到一个特征向量,把图片中所有的红、绿、蓝像素值都列出来。如果图片的大小为64x64像素,那么向量 x的总维度,将是64乘以64乘以3,这是三个像素矩阵中像素的总量。在这个例子中结果为12,288。现在我们用n_x=12288,来表示输入特征向量的维度,有时候为了简洁,我会直接用小写的n来表示输入特征向量x的维度。所以在二分类问题中,我们的目标就是习得一个分类器,它以图片的特征向量作为输入,然后预测输出结果y为1还是0,也就是预测图片中是否有猫:

 

 

2.2 逻辑回归(Logistic Regression)

本节将主要介绍逻辑回归的Hypothesis Function(假设函数)

对于二元分类问题来讲,给定一个输入特征向量X, 它可能对应一张图片,你想识别这张图片识别看它是否是一只猫或者不是一只猫的图片,你想要一个算法能够输出预测,你只能称之为y_, 也就是你对实际值 y的估计。更正式地来说,你想让 y_表示 y等于1的一种可能性或者是机会,前提条件是给定了输入特征。换句话来说,如果X是我们在上个视频看到的图片, 你想让 y_来告诉你这是一只猫的图片的机率有多大。X是一个n_x维的向量(相当于有n_x个特征的特征向量)。我们用w来表示逻辑回归的参数,这也是一个n_x维向量(因为实际上是特征权重,维度与特征向量相同),参数里面还有b, 这是一个实数(表示偏差)。所以给出输入x以及参数w和b之后,我们怎样产生输出预测值y_一件你可以尝试却不可行的事是让:

 

2.3 逻辑回归的代价函数(Logistic Regression Cost Function)

为了训练逻辑回归模型的参数w和参数b我们,需要一个代价函数,通过训练代价函数来得到参数w和参数b。先看一下逻辑回归的输出函数:

1

为了让模型通过学习调整参数w,你需要给予一个样本的训练集,这会让你在训练集上找到参数w和参数b,来得到你的输出。

损失函数:

损失函数又叫做误差函数,用来衡量算法的运行情况,Loss function: 

这个L称为的损失函数,来衡量预测输出值和实际值有多接近。一般我们用预测值和实际值的平方差或者它们平方差的一半,但是通常在逻辑回归中我们不这么做,因为当我们在学习逻辑回归参数的时候,会发现我们的优化目标不是凸优化,只能找到多个局部最优值,梯度下降法很可能找不到全局最优值,虽然平方差是一个不错的损失函数,但是我们在逻辑回归模型中会定义另外一个损失函数。

我们在逻辑回归中用到的损失函数是:

为什么要用这个函数作为逻辑损失函数?当我们使用平方误差作为损失函数的时候,你会想要让这个误差尽可能地小,对于这个逻辑回归损失函数,我们也想让它尽可能地小,为了更好地理解这个损失函数怎么起作用,我们举两个例子:

2.4 梯度下降法(Gradient Descent)

梯度下降法可以做什么?

在你测试集上,通过最小化代价函数(成本函数J(w,b)来训练的参数w和b,

梯度下降法的形象化说明

如图,代价函数(成本函数)是一个凸函数(convex function),像一个大碗一样。

可以用如图那个小红点来初始化参数w和b,也可以采用随机初始化的方法,对于逻辑回归几乎所有的初始化方法都有效,因为函数是凸函数,无论在哪里初始化,应该达到同一点或大致相同的点。

我们以如图的小红点的坐标来初始化参数w和b。

2. 朝最陡的下坡方向走一步,不断地迭代

我们朝最陡的下坡方向走一步,如图,走到了如图中第二个小红点处。

我们可能停在这里也有可能继续朝最陡的下坡方向再走一步,如图,经过两次迭代走到第三个小红点处。

3.直到走到全局最优解或者接近全局最优解的地方

通过以上的三个步骤我们可以找到全局最优解,也就是代价函数(成本函数)J(w,b) 这个凸函数的最小值点。

梯度下降法的细节化说明(仅有一个参数)

假定代价函数(成本函数)J(w) 只有一个参数w,即用一维曲线代替多维曲线,这样可以更好画出图像。

整个梯度下降法的迭代过程就是不断地向左走,直至逼近最小值点。

整个梯度下降法的迭代过程就是不断地向右走,即朝着最小值点方向走。

梯度下降法的细节化说明(两个参数)

逻辑回归的代价函数(成本函数)J(w,b) 是含有两个参数的。

2.5 导数(Derivatives)

 为了高效应用神经网络和深度学习,你并不需要非常深入理解微积分。 

2.6 更多的导数例子(More Derivative Examples)

在这个例子中,函数在不同点处的斜率是不一样的,先来举个例子:

为了总结这堂课所学的知识,我们再来看看几个例子:

 2.7 计算图(Computation Graph)

 一个神经网络的计算,都是按照前向或反向传播过程组织的。首先我们计算出一个新的网络的输出(前向过程),紧接着进行一个反向传输操作。后者我们用来计算出对应的梯度或导数计算图解释了为什么我们用这种方式组织这些计算过程。 我们将举一个例子说明计算图是什么。让我们举一个比逻辑回归更加简单的,或者说不那么正式的神经网络的例子。

2.8 使用计算图求导数(Derivatives with a Computation Graph)

看看你如何利用它计算出函数的导数。

这是一个流程图:

 

2.9 逻辑回归中的梯度下降(Logistic Regression Gradient Descent)

本节我们讨论怎样通过计算偏导数来实现逻辑回归的梯度下降算法。它的关键点是几个重要公式,其作用是用来实现逻辑回归中梯度下降算法。但是在本节中,我将使用计算图对梯度下降算法进行计算。我必须要承认的是,使用计算图来计算逻辑回归的梯度下降算法有点大材小用了。但是,我认为以这个例子作为开始来讲解,可以使你更好的理解背后的思想。从而在讨论神经网络时,你可以更深刻而全面地理解神经网络。接下来让我们开始学习逻辑回归的梯度下降算法。

2017-08-16 16-05-25

2017-08-16 16-07-49

现在你已经知道了怎样计算导数,并且实现针对单个训练样本的逻辑回归的梯度下降算法。但是,训练逻辑回归模型不仅仅只有一个训练样本,而是有m个训练样本的整个训练集。因此在下一节中,我们将这些思想应用到整个训练样本集中,而不仅仅只是单个样本上。

 

2.10 m 个样本的梯度下降(Gradient Descent on m Examples)

在之前的视频中 , 你已经看到如何计算导数,以及应用梯度下降在逻辑回归的一个训练 样本上。现在我们想要把它应用在𝑚 个训练样本上。
 

 

但之前我们已经演示了如何计算这项,即之前幻灯中演示的如何对单个训练样本进行计算。所以你真正需要做的是计算这些微分,如我们在之前的训练样本上做的。并且求平均,这会给你全局梯度值,你能够把它直接应用到梯度下降算法中。 所以这里有很多细节,但让我们把这些装进一个具体的算法。同时你需要一起应用的就 是逻辑回归和梯度下降。
 

但这种计算中有两个缺点,也就是说应用此方法在逻辑回归上你需要编写两个 for 循环。 第一个 for 循环是一个小循环遍历𝑚个训练样本,第二个 for 循环是一个遍历所有特征的 for 循环 。这个例子中我们只有 2 个特征,所以 𝑛 等于 2 并且 𝑛 𝑥 等于 2
但如果你有更多特征, 你开始编写你的因此𝑑𝑤 1 𝑑𝑤 2 ,你有相似的计算从 𝑑𝑤 3 一直下去到 𝑑𝑤 𝑛 。所以看来你需要一 个 for 循环遍历所有 𝑛 个特征。
当你应用深度学习算法,你会发现在代码中显式地使用 for 循环使你的算法很低效,同 时在深度学习领域会有越来越大的数据集。所以能够应用你的算法且没有显式的 for 循环会是重要的,并且会帮助你适用于更大的数据集。所以这里有一些叫做 向量化 技术, 它可以允许你的代码摆脱这些显式的 for 循环。

 

所以在接下来的几个视频中,我们会谈到 向量化 ,以及如何应用向量化而连一个 for 循环都不使用。所以学习了这些,我希望你有关于 如何应用逻辑回归,或是用于逻辑回归的梯度下降 ,事情会变得更加清晰。当你进行编程练习,但在真正做编程练习之前让我们先谈谈向量化。然后你可以应用全部这些东西,应用一个梯度下降的迭代而不使用任何 for 循环。
 
 

2.11 向量化(Vectorization)

向量化 是非常基础的去除代码中 for 循环的艺术,在深度学习安全领域、深度学习实践中,你会经常发现自己训练大数据集,因为深度学习算法处理大数据集效果很棒,所以你的代码运行速度非常重要,否则如果在大数据集上,你的代码可能花费很长时间去运行,你将要等待非常长的时间去得到结果。所以在深度学习领域,运行向量化是一个关键的技巧,让我们举个栗子说明什么是向量化。
在逻辑回归中你需要去计算 𝑧 = 𝑤𝑇𝑥 + 𝑏,𝑤、𝑥都是列向量 。如果你有很多的特征那么 就会有一个非常大的向量,所以𝑤 ∈ ℝ 𝑛 𝑥 , 𝑥 ∈ ℝ 𝑛 𝑥 ,所以如果你想使用非向量化方法去计算𝑤 𝑇 𝑥 ,你需要用如下方式( python
 

这是一个非向量化的实现,你会发现这真的很慢,作为一个对比,向量化实现将会非常 直接计算𝑤 𝑇 𝑥 ,代码如下:
z=np.dot(w,x)+b
这是向量化计算 𝑤 𝑇 𝑥 的方法,你将会发现这个 非常快

让我们用一个小例子说明一下,在我的我将会写一些代码(以下为教授在他的 Jupyter notebook 上写的 Python 代码,)
import numpy as np     # 导入 numpy
a = np.array([1,2,3,4]) # 创建一个数据 a
print(a)
# [1 2 3 4]
 
import time # 导入时间库
a = np.random.rand(1000000)
b = np.random.rand(1000000) # 通过 round 随机得到两个一百万维度的数组
tic = time.time() # 现在测量一下当前时间
 
# 向量化的版本
c = np.dot(a,b)
toc = time.time()
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”) # 打印 for 循环的版本的时间

 

在两个方法中,向量化和非向量化计算了相同的值,如你所见,向量化版本花费了 1.5 毫秒,非向量化版本的 for 循环花费了大约几乎 500 毫秒,非向量化版本多花费了 300 倍时间。所以在这个例子中,仅仅是向量化你的代码,就会运行 300 倍快。这意味着如果向量化方法需要花费一分钟去运行的数据,for 循环将会花费 5 个小时去运行。一句话总结,以上都是再说和 for 循环相比,向量化可以快速得到结果。你可能听过很多类似如下的话,“ 大规模的深度学习使用了 GPU 或者图像处理单元实现” ,但是我做的所有的案例都是在 jupyter notebook 上面实现,这里只有 CPU CPU GPU 都有并行化的指令,他们有时候会叫做 SIMD 指令,这个代表了一个单独指令多维数据,这个的基础意义是,如果你使用了 built-in 函数 , np.function 或者并不要求你实现循环的函数,它可以让 python 的充分利用并行化计算,这是事实在 GPU CPU 上面计算, GPU 更加擅长 SIMD 计算,但是 CPU 事实上也不是太差,可能没有 GPU 那么擅长吧。接下来的视频中,你将看到向量化怎么能够加速你的代码,经验法则是,无论什么时候,避免使用明确的 for 循环
 

【这种方法能不能用在视觉SLAM系统里面,减小计算量】

 

2.12 向量化的更多例子(More Examples of Vectorization)

经验提醒我,当我们在写神经网络程序时,或者在写逻辑 (l ogistic ) 回归,或者其他神经网络模型时,应该避免写循环( loop ) 语句。虽然有时写循环 ( loop ) 是不可避免的,但是我们可以使用比如 numpy 的内置函数或者其他办法去计算。当你这样使用后,程序效率总是快于循环( loop)

 

让我们看另外一个例子。如果你想计算向量 𝑢 = 𝐴𝑣 ,这时矩阵乘法定义为,矩阵乘法的 定义就是:𝑢 𝑖 = ∑𝑗 𝐴ij𝑣𝑖 ,这取决于你怎么定义 𝑢 𝑖 值。同样使用非向量化实现, 𝑢 = 𝑛𝑝. 𝑧𝑒𝑟𝑜𝑠(𝑛, 1) , 并且通过两层循环 𝑓𝑜𝑟(𝑖): 𝑓𝑜𝑟(𝑗): ,得到 𝑢[𝑖] = 𝑢[𝑖] + 𝐴[𝑖][𝑗] ∗ 𝑣[𝑗] 。现在 就有了𝑖 𝑗 的两层循环,这就是非向量化。向量化方式就可以用 𝑢 = 𝑛𝑝. 𝑑𝑜𝑡(𝐴, 𝑣) 右边这种向量化实现方式,消除了两层循环使得代码运行速度更快。

下面通过另一个例子继续了解向量化。如果你已经有一个向量 𝑣 ,并且想要对向量 𝑣 的每个元素做指数操作,得到向量𝑢 等于 𝑒 𝑣 1 𝑒 𝑣 2 ,一直到 𝑒 𝑣 𝑛 次方。这里是非向量化的实现方式,首先你初始化了向量𝑢 = 𝑛𝑝. 𝑧𝑒𝑟𝑜𝑠(𝑛, 1) ,并且通过循环依次计算每个元素。但事实证明可以通过 python numpy 内置函数,帮助你计算这样的单个函数。所以我会引入import numpy as np,执行 𝑢 = 𝑛𝑝. 𝑒𝑥𝑝(𝑣) 命令。注意到,在之前有循环的代码中,这里仅用了一行代码,向量𝑣 作为输入, 𝑢 作为输出。你已经知道为什么需要循环,并且通过右边代码实现,效率会明显的快于循环方式。

    

事实上, numpy 库有很多向量函数。 比如 u=np.log 是计算对数函数(𝑙𝑜𝑔)、np.abs()是计算数据的绝对值、 np.maximum() 计算元素 𝑦 中的最大值,你也可以np.maximum(v,0) 、 𝑣 ∗∗ 2 代表获得元素 𝑦 每个值得平方、 1/𝑣 获取元素 𝑦 的倒数等等。所以当你想写循环时候,检查 numpy 是否存在类似的内置函数,从而避免使用循环( loop)
方式。

      将刚才所学到的内容,运用在逻辑回归的梯度下降上,看看我们是否能简化两个计算过程中的某一步。 这是我们逻辑回归的求导代码,有两层循环。在这例子我们有𝑛 个特征值。如果你有超过两个特征时,需要循环 𝑑𝑤 1 𝑑𝑤 2 𝑑𝑤 3 等等。所以 𝑗 的实际值是 1、 2 𝑛 𝑥 ,就是你想要更新的值。所以我们想要消除第二循环,在这一行,这样我们就不 用初始化 𝑑𝑤 1 𝑑𝑤 2 都等于 0 。去掉这些,而是定义 𝑑𝑤 为一个向量,设置 𝑢 = 𝑛𝑝. 𝑧𝑒𝑟𝑜𝑠(𝑛(𝑥),1)。定义了一个 𝑥 行的一维向量,从而替代循环。我们仅仅使用了一个向量操 作 𝑑𝑤 = 𝑑𝑤 + 𝑥 (𝑖)𝑑𝑧 (𝑖) 。最后,我们得到 𝑑𝑤 = 𝑑𝑤/𝑚 。现在我们通过将两层循环转成一 层循环,我们仍然还有这个循环训练样本。

希望这个视频给了你一点向量化感觉,减少一层循环使你代码更快,但事实证明我们能做得更好。所以在下个视频,我们将进一步的讲解逻辑回归,你将会看到更好的监督学习结果。在训练中不需要使用任何 for 循环,你也可以写出代码去运行整个训练集。 

 

2.13 向量化逻辑回归(Vectorizing Logistic Regression)

 我们将讨论如何实现逻辑回归的向量化计算。 这样就能处理整个数据集,甚至不会用一个明确的 for 循环就能实现对于整个数据集梯度下降算法的优化。我对这项技术感到非常激动,并且当我们后面谈到神经网络时同样也不会用到一个明确的 for 循环。

 

我们回顾一下逻辑回归的前向传播步骤。如果你有 𝑚 个训 练样本,然后对第一个样本进行预测,你需要这样计算。计算 𝑧 ,我正在使用这个熟悉的公式 𝑧(1) = 𝑤𝑇𝑥(1) + 𝑏 。然后计算激活函数 𝑎(1) = 𝜎(𝑧(1)) ,计算第一个样本的预测值 𝑦
对第二个样本进行预测,你需要计算 𝑧(2) = 𝑤𝑇𝑥(2) + 𝑏 , 𝑎(2) = 𝜎(𝑧(2)) 。 对第三个样本进行预测,你需要计算 𝑧(3) = 𝑤𝑇𝑥(3) + 𝑏 ,𝑎(3) = 𝜎(𝑧(3)) ,依次类推。 如果你有 𝑚 个训练样本,你可能需要这样做 𝑚 次,可以看出,为了完成前向传播步骤, 即对我们的 𝑚 个样本都计算出预测值。有一个办法可以并且不需要任何一个明确的 for 循 环。让我们来看一下你该怎样做。

所以 𝑤 转置可以是一个行向量。所以第一项 𝑤 𝑇 𝑋 将计算 𝑤 的转置乘以 𝑥 (1) 𝑤 转置乘以 𝑥 (2) 等等。然后我们加上第二项 [𝑏𝑏. . . 𝑏] ,你最终将 𝑏 加到了每个元素上。所以你最终得到了 另 一 个 1 × 𝑚 的 向 量 , [𝑧(1)𝑧(2). . . 𝑧(𝑚)] = 𝑤𝑇𝑋 + [𝑏𝑏. . . 𝑏] = [𝑤𝑇𝑥(1) + 𝑏, 𝑤𝑇𝑥(2) + 𝑏. . . 𝑤𝑇𝑥(𝑚) + 𝑏]
 
𝑤𝑇𝑥(1) + 𝑏 这是第一个元素, 𝑤𝑇𝑥(2) + 𝑏 这是第二个元素,𝑤𝑇 𝑥(𝑚) + 𝑏 这是第 𝑚 个元素。
 
如果你参照上面的定义,第一个元素恰好是 𝑧 (1) 的定义,第二个元素恰好是 𝑧 (2) 的定义,等等。所以,因为𝑋 是一次获得的,当你得到你的训练样本,一个一个横向堆积起来,这里我将 [𝑧 (1) 𝑧 (2) . . . 𝑧 (𝑚) ] 定义为大写的 𝑍 ,你用小写 𝑧 表示并将它们横向排在一起。所以当你将不同训练样本对应的小写 𝑥 横向堆积在一起时得到大写变量 𝑋 并且将小写变量也用相同方法处理,将它们横向堆积起来,你就得到大写变量 𝑍 。结果发现,为了计算 𝑊𝑇𝑋 + [𝑏𝑏. . . 𝑏] ,numpy 命令是𝑍 = 𝑛𝑝. 𝑑𝑜𝑡(𝑤. 𝑇,𝑋) + 𝑏 。这里在 Python 中有一个巧妙的地方,这里 𝑏 是一个实数,或者你可以说是一个 1 × 1 矩阵,只是一个普通的实数。但是当你将这个向量加上这个实数时,Python 自动把这个实数 𝑏 扩展成一个 1 × 𝑚 的行向量。 所以这种情况下的操作似乎有点不可思议,它在 Python 中被称作广播 ( brosdcasting ) , 我们将在下一个视频中进行进一步的讲解。话说回来它只用一行代码,用这一行代码,你可以计算大写的 𝑍 ,而大写 𝑍 是一个包含所有小写 𝑧 (1) 𝑧 (𝑚) 1 × 𝑚 的矩阵。这就是 𝑍 的内容, 关于变量 𝑎 又是如何呢?
 
 
我们接下来要做的就是找到一个同时计算 [𝑎 (1) 𝑎 (2) . . . 𝑎 (𝑚) ] 的方法。就像把小写 𝑥 堆积起来得到大写 𝑋 和横向堆积小写 𝑧 得到大写 𝑍 一样,堆积小写变量 𝑎 将形成一个的变量,我们将它定义为大写 𝐴 。在编程作业中,你将看到怎样用一个向量在 sigmoid 函数 中进行计算。所以 sigmoid 函数中输入大写 𝑍 作为变量并且非常高效地输出大写 𝐴 。 
 
 
在这张幻灯片中我们已经看到,不需要 for 循环,利用 𝑚 个训练样本一次 性计算出小写 𝑧 和小写 𝑎 ,用一行代码即可完成。
Z = np.dot(w.T,X) + b
这一行代码: 𝐴 = [𝑎 (1) 𝑎 (2) . . . 𝑎 (𝑚) ] = 𝜎(𝑍) ,通过恰当地运用 𝜎 一次性计算所有 𝑎。这就是在同一时间内你如何完成一个所有 𝑚 个训练样本的 前向传播 向量化计算。
 
 
 

2.14 向量化 logistic 回归的梯度输出(Vectorizing Logistic Regression's Gradient)

 
如何向量化计算的同时,对整个训练集预测结果𝑎 。  本次的重点是如何 计算 𝑚 个数据的梯度,并且实现一个非常高效的逻辑回归算法 (Logistic Regression )
 
 
之前我们在讲梯度计算的时候,列举过几个例子, 𝑑𝑧(1) = 𝑎(1) − 𝑦(1)𝑑𝑧(2) = 𝑎(2) − 𝑦(2) …… 等等一系列类似公式。现在,对 𝑚 个训练数据做同样的运算,我们可以定义一个新的变量 𝑑𝑍 = [𝑑𝑧 (1) , 𝑑𝑧 (2) . . . 𝑑𝑧 (𝑚) ] ,所有的 𝑑𝑧 变量横向排列,因此, 𝑑𝑍 是一个 1 × 𝑚 的矩阵,或者说,一个 𝑚 维行向量。在之前的幻灯片中,我们已经知道如何计算 𝐴 ,即 [𝑎(1) , 𝑎 (2) . . . 𝑎 (𝑚) ] , 我们需要找到这样的一个行向量 𝑌 = [𝑦 (1) 𝑦 (2) . . . 𝑦 (𝑚) ] ,由此,我们可以这样计算 𝑑𝑍 = 𝐴 − 𝑌 = [𝑎 (1) − 𝑦(1)𝑎(2) − 𝑦(2). . . 𝑎(𝑚) − 𝑦(𝑚)] ,不难发现第一个元素就是𝑑𝑧(1) ,第二个元素就是 𝑑𝑧 (2) …… 所以我们现在仅需一行代码,就可以同时完成这所有的计 算。
 

【说得那么高大上,向量化,就是计算之前把数据用向量或矩阵运算一下嘛】!!!

𝑑𝑤 = 0
𝑑𝑤+= 𝑥(1) ∗ 𝑑𝑧(1)
𝑑𝑤+= 𝑥(2) ∗ 𝑑𝑧(2)
………….
𝑑𝑤+= 𝑥(𝑚) ∗ 𝑑𝑧(𝑚)
𝑑𝑤 = 𝑑𝑤 /𝑚
𝑑𝑏 = 0
𝑑𝑏+= 𝑑𝑧(1)
𝑑𝑏+= 𝑑𝑧(2)
………….
𝑑𝑏+= 𝑑𝑧(𝑚)
𝑑𝑏 = 𝑑𝑏 /𝑚

 

但用上述方法计算 𝑑𝑤 仍然需要一个循环遍历训练集,我们现在要做的就是将其向量化!
现在,让我们回顾一下,看看我们之前怎么实现的逻辑回归,可以发现,没有向量化是 非常低效的,如下图所示代码:

 

我们的目标是不使用 for 循环,而是向量,我们可以这么做:

最后,我们得到了一个高度向量化的、非常高效的逻辑回归的梯度下降算法,我们将在下次视频中讨论 Python 中的 Broadcasting 技术。
 
 

2.15 Python 中的广播(Broadcasting in Python)

这是一个不同食物 ( 100g) 中不同营养成分的卡路里含量表格,表格为 3 4 ,列表示不同的食物种类,从左至右依次为苹果,牛肉,鸡蛋,土豆。行表示不同的营养成分,从上到下依次为碳水化合物,蛋白质,脂肪
 
那么,我们现在想要计算不同食物中不同营养成分中的卡路里百分比。 现在计算苹果中的碳水化合物卡路里百分比含量,首先计算苹果(100g )中三种营养成 分卡路里总和 56+1.2+1.8 = 59 ,然后用 56/59 = 94.9% 算出结果。 可以看出苹果中的卡路里大部分来自于碳水化合物,而牛肉则不同。 对于其他食物,计算方法类似。首先,按列求和,计算每种食物中(100g )三种营养成
分总和,然后分别用不用营养成分的卡路里数量除以总和,计算百分比。 那么,能否不使用 for 循环完成这样的一个计算过程呢?
 
假设上图的表格是一个 3 4 列的矩阵 𝐴 ,记为 𝐴3×4 ,接下来我们要使用 Python numpy 库完成这样的计算。我们打算使用两行代码完成,第一行代码对每一列进行求和,第 二行代码分别计算每种食物每种营养成分的百分比。 在 jupyter notebook 中输入如下代码,按 shift+Enter 运行,输出如下。

而第二个 A/cal.reshape(1,4) 指令则调用了 numpy 中的广播机制。这里使用 3 × 4 的矩阵𝐴 除以 1 × 4 的矩阵 𝑐𝑎𝑙 。技术上来讲,其实并不需要再将矩阵 𝑐𝑎𝑙 reshape ( 重塑 ) 成 1 × 4,因为矩阵 𝑐𝑎𝑙 本身已经是 1 × 4 了。但是当我们写代码时不确定矩阵维度的时候,通常会对矩阵进行重塑来确保得到我们想要的列向量或行向量。重塑操作 reshape 是一个常量时间的操作,时间复杂度是𝑂(1),它的调用代价极低。

 

那么一个 3 × 4 的矩阵是怎么和 1 × 4的矩阵做除法的呢?让我们来看一些更多的广播的例子。

numpy 中,当一个 4 × 1 的列向量与一个常数做加法时,实际上会将常数扩展为一 个 4 × 1 的列向量,然后两者做逐元素加法。结果就是右边的这个向量。这种 广播机制 对于行向量和列向量均可以使用。 【感觉是一种内部集成得一种运算法则】
 
再看下一个例子。
 
用一个 2 × 3 的矩阵和一个 1 × 3 的矩阵相加,其泛化形式是 𝑚 × 𝑛 的矩阵和 1 × 𝑛 的矩阵相加。在执行加法操作时,其实是将 1 × 𝑛 的矩阵复制成为 𝑚 × 𝑛 的矩阵,然后两 者做逐元素加法得到结果。针对这个具体例子,相当于在矩阵的第一列加 100 ,第二列加 200 , 第三列加 300 。这就是在前一张幻灯片中计算卡路里百分比的广播机制,只不过这里是除法 操作(广播机制与执行的运算种类无关)。
 
 
如果两个数组的后缘维度的轴长度相符或其中一方的轴长度为 1 ,则认为它们是广播兼 容的。广播会在缺失维度和轴长度为 1 的维度上进行。
 
 
总结一下 broadcasting ,可以看看下面的图:
 

 

2.16 关于 python _ numpy 向量的说明(A note on python or numpy vectors)参考视频:

本节主要讲 Python 中的 numpy 一维数组的特性,以及与行向量或列向量的区别。并介绍了老师在实际应用中的一些小技巧,去避免在 coding 中由于这些特性而导致的 bug Python 的特性允许你使用广播( broadcasting )功能,这是 Python numpy 程序语言库中最灵活的地方。而我认为这是程序语言的优点,也是缺点。优点的原因在于它们创造出 语言的表达性,Python 语言巨大的灵活性使得你仅仅通过一行代码就能做很多事情。但是这也是缺点,由于广播巨大的灵活性,有时候你对于广播的特点以及广播的工作原理这些细 节不熟悉的话,你可能会产生很细微或者看起来很奇怪的 bug 例如,如果你将一个列向量 添加到一个行向量中,你会以为它报出维度不匹配或类型错误之类的错误,但是实际上你会 得到一个行向量和列向量的求和。 在 Python 的这些奇怪的影响之中,其实是有一个内在的逻辑关系的。但是如果对 Python 不熟悉的话,我就曾经见过的一些学生非常生硬、非常艰难地去寻找 bug 。所以我在这里想 做的就是分享给你们一些技巧,这些技巧对我非常有用,它们能消除或者简化我的代码中所 有看起来很奇怪的 bug 。同时我也希望通过这些技巧,你也能更容易地写没有 bug Python numpy 代码。

 

为了演示 Python-numpy 的一个容易被忽略的效果,特别是怎样在 Python-numpy 中构造向量,让我来做一个快速示范。首先设置𝑎 = 𝑛𝑝. 𝑟𝑎𝑛𝑑𝑜𝑚. 𝑟𝑎𝑛𝑑𝑛(5) ,这样会生成存储在数组 𝑎 中的 5 个高斯随机数变量。之后输出 𝑎 ,从屏幕上可以得知,此时 𝑎 shape (形 状)是一个(5, ) 的结构。这在 Python 中被称作 一个一维数组 。它既不是一个行向量也不是一个列向量,这也导致它有一些不是很直观的效果。举个例子,如果我输出一个转置阵,最 终结果它会和𝑎 看起来一样,所以 𝑎 𝑎 的转置阵最终结果看起来一样。而如果我输出 𝑎 𝑎 的 转置阵的内积,你可能会想:𝑎 乘以 𝑎 的转置返回给你的可能会是一个矩阵。但是如果我这样 做,你只会得到一个数

所以我建议当你编写神经网络时,不要在它的 shape (5, ) 还是 (𝑛, ) 或者一维数组时使用数据结构。相反,如果你设置 𝑎 (5,1) ,那么这就将置于 5 1 列向量中。在先前的操作里 𝑎 𝑎 的转置看起来一样,而现在这样的 𝑎 变成一个新的 𝑎 的转置,并且它是一 个行向量。请注意一个细微的差别,在这种数据结构中,当我们输出 𝑎 的转置时有两对方 括号,而之前只有一对方括号,所以这就是 1 5 列的矩阵和一维数组的差别。

如果你输出 𝑎 𝑎 的转置的乘积,然后会返回给你一个向量的外积,是吧?所以这两个向量的外积返回给你的是一个矩阵。

就我们刚才看到的,再进一步说明。首先我们刚刚运行的命令是这个 (𝑎 = 𝑛𝑝. 𝑟𝑎𝑛𝑑𝑜𝑚. 𝑟𝑎𝑛𝑑𝑛(5)),而且它生成了一个数据结构 (𝑎. 𝑠ℎ𝑎𝑝𝑒) 𝑎. 𝑠ℎ𝑎𝑝𝑒 (5, ) ,一个有趣 的东西。这被称作 𝑎 的一维数组,同时这也是一个非常有趣的数据结构。它不像行向量和
列向量那样表现的很一致,这也让它的一些影响不那么明显。 所以我建议,当你在编程练习 或者在执行逻辑回归和神经网络时,你不需要使用这些一维数组。

相反,如果你每次创建一个数组,你都得让它成为一个列向量,产生一个 (5,1) 向量或者 你让它成为一个行向量,那么你的向量的行为可能会更容易被理解。所以在这种情况下, 𝑎. 𝑠ℎ𝑎𝑝𝑒等同于(5,1) 。这种表现很像 𝑎 ,但是实际上却是一个列向量。同时这也是为什么当 它是一个列向量的时候,你能认为这是矩阵(5,1) ;同时这里 𝑎. 𝑠ℎ𝑎𝑝𝑒 将要变成(1,5) ,这就 像行向量一样。所以当你需要一个向量时,我会说用这个或那个( column vector or row vector ) , 但绝不会是一维数组。
 
我写代码时还有一件经常做的事,那就是如果我不完全确定一个向量的维度(dimension), 我经常会扔进一个断言语句(assertion statement)。像这样,去确保在这种情况下是一个(5,1) 向量,或者说是一个列向量。这些断言语句实际上是要去执行的,并且它们也会有助于为你 的代码提供信息。 所以不论你要做什么,不要犹豫直接插入断言语句。如果你不小心以一维
数组来执行,你也能够重新改变数组维数 𝑎 = 𝑟𝑒𝑠ℎ𝑎𝑝𝑒 ,表明一个 (5,1) 数组或者一个 (1,5) 数组,以致于它表现更像列向量或行向量。
 
我有时候看见学生因为一维数组不直观的影响,难以定位 bug 而告终。通过在原先的代 码里清除一维数组,我的代码变得更加简洁。而且实际上就我在代码中表现的事情而言, 我 从来不使用一维数组。 因此,要去简化你的代码,而且不要使用一维数组。总是使用 𝑛 × 1 维矩阵(基本上是列向量),或者 1 × 𝑛 维矩阵(基本上是行向量),这样你可以减少很多 assert 语句来节省核矩阵和数组的维数的时间。另外,为了确保你的矩阵或向量所需要的维数时,不要羞于 reshape 操作。
【值得借鉴】
 
 

2.17 Jupyter/iPython Notebooks 快速入门(Quick tour of Jupyter/iPython Notebooks)

这里就不再仔细介绍了!

 

2.18 (选修)logistic 损失函数的解释(Explanation of logistic regression cost function)

 我们已经分析了逻辑回归的损失函数表达式,在这节中,我将给出一个简洁的证明来说明逻辑回归的损失函数为什么是这种形式。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

他人是一面镜子,保持谦虚的态度

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值