深度学习详细笔记 (通俗易懂,这一篇就够了 )——Python向量化方法的深入解析

1. 向量化 (Vectorization)

在深度学习的世界中,数据量经常是巨大的。在这种背景下,使用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("向量化版本运行时间:" + 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循环版本运行时间:" + str(1000*(toc-tic)) + "ms")

可能的输出为:

250286.989866
向量化版本运行时间:1.5027523040771484ms
250286.989866
for循环版本运行时间:474.29513931274414ms

显然,使用for循环的时间是向量化版本的大约300倍。这突显了在深度学习计算中,利用向量化可以大大提高效率。

不仅如此,为了进一步提速,我们还可以使用GPU,它的计算能力远超于CPU。事实上,GPU和CPU都有并行计算的能力,这得益于它们都支持Single Instruction Multiple Data(SIMD)。简单地说,SIMDSIMDSIMD允许单一的指令同时处理多个数据点,从而大幅度提高了运算速度。特别是在做矩阵相关的运算时,GPU的性能由于其更多的并行处理单元而远超CPU。Python的numpy库的很多函数已经优化过,它们内部利用了SIMD指令,这也是为什么numpy函数如此高效的原因。

总之,无论是向量化还是利用GPU加速,目的都是为了让深度学习算法更快、更高效。

2. 更多的向量化示例 (More Vectorization Examples)

在我们之前的讨论中,已经明确提到:在计算时,应该尽量避免使用for循环,而更多地选择向量化矩阵运算,这样可以大大提高效率。如果你用过Python的numpy库,你可能已经知道我们通常使用np.dot()函数来进行这种矩阵运算。

当我们将这种向量化的方法应用于逻辑回归算法时,可以极大地减少for循环的使用,取而代之的是直接采用矩阵运算。但这里需要注意的是,算法的最外层循环(例如训练迭代)是不能被替换的。不过,我们可以在每次迭代中直接使用矩阵运算来计算J、dw和b。

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

回想一下我们在《神经网络与深度学习》的课程笔记(2)中所讨论的,整个训练数据集构成的输入矩阵 X X X的维度是 ( n x , m ) (nx, m) (nx,m),权重矩阵 w w w的维度是 ( n x , 1 ) (nx, 1) (nx,1) b b b是一个常数,输出矩阵 Y Y Y的维度是 ( 1 , m ) (1, m) (1,m)。有了向量化的思想,我们可以使用矩阵表示法来计算所有 m m m个样本的线性输出 Z Z Z

Z = w T X + b Z = w^TX + b Z=wTX+b

在Python的numpy库中,这可以轻易地用以下方式表示:

Z = np.dot(w.T, X) + b
A = sigmoid(Z)

其中, w T w^T wT表示 w w w的转置。

所以,通过利用向量化矩阵运算,我们可以同时对所有 m m m个样本进行计算,从而极大地提高运算速度,而无需依赖for循环。

4. 向量化逻辑回归的梯度输出 (Vectorizing Logistic Regression’s Gradient Output)

当我们谈论机器学习,特别是逻辑回归时,效率是关键。逻辑回归中的梯度下降是一个关键的优化方法,但使用传统的循环来计算它可能会很慢。那么,我们怎么提高它的速度呢?答案是“向量化”。

向量化是一种用数组或矩阵替代传统循环的技术,可以显著提高计算速度,特别是当我们处理大数据集时。

首先,让我们考虑逻辑回归的梯度。对于所有的样本m,我们有梯度dZ,它的维度是 (1, m)。这个梯度可以简单地表示为:
d Z = A − Y dZ = A - Y dZ=AY

接下来,我们需要计算另一个重要的参数db,它代表了b的梯度。它是dZ所有元素的平均值,可以这样表示:
d b = 1 m ∑ i = 1 m d z ( i ) db = \frac{1}{m} \sum_{i=1}^{m} dz(i) db=m1i=1mdz(i)
在Python中,我们可以使用以下代码来实现它:

db = 1/m * np.sum(dZ)

然后,我们有dw,它代表了w的梯度,可以这样表示:

dw=1mX⋅dZTdw = \frac{1}{m} X \cdot dZ^Tdw=m1​X⋅dZT

在Python中,我们可以使用以下代码来实现它:

dw = 1/m * np.dot(X, dZ.T)

所以,通过向量化,我们可以避免使用传统的for循环来计算逻辑回归中的梯度。一个完整的单次迭代的梯度下降算法可以这样表示:

Z = np.dot(w.T, X) + b
A = sigmoid(Z)
dZ = A - Y
dw = 1/m * np.dot(X, dZ.T)
db = 1/m * np.sum(dZ)
w = w - alpha * dw
b = b - alpha * db

这里,alpha是学习率,它决定了wb更新的速度。请注意,上述代码只表示一个单独的训练更新。在真实的场景中,为了达到最佳的结果,我们通常会在外部添加一个for循环来多次迭代此过程。

总之,通过使用向量化技术,我们不仅提高了代码的效率,而且使它更简洁易读。

注意: "sigmoid"是逻辑函数的一种常用实现,它将任何数字映射到0和1之间。

5. Broadcasting in Python (Python中的广播)

当我们在Python中使用NumPy这样的库来处理数组时,会遇到一个非常有用的技术叫做“广播”。广播允许我们用很直观的方式处理不同维度的数组。它背后的原理可能初看起来有点复杂,但一旦你掌握了,就会觉得非常强大和实用。

广播机制可以通过以下四点来理解:

  1. 首先,比较所有输入数组的维度。不足的部分会在其维度的前面加1,从而和最长的那个维度保持一致。
    举个例子,假设我们有一个形状为 ( 3 , ) (3,) (3,)的数组和一个形状为 ( 1 , 3 ) (1,3) (1,3)的数组,当它们进行运算时,第一个数组会被看作形状为 ( 1 , 3 ) (1,3) (1,3)

  2. 输出数组的维度是输入数组各个维度上的最大值。
    如果我们把一个形状为 ( 3 , 1 ) (3,1) (3,1)的数组和一个形状为 ( 3 , 4 ) (3,4) (3,4)的数组相加,那么输出数组的形状就会是 ( 3 , 4 ) (3,4) (3,4)

  3. 只有当输入数组在某个维度上的长度与输出数组相同,或者该维度的长度为1时,该输入数组才能用于计算。否则,计算会报错。
    这意味着,一个形状为 ( 3 , 4 ) (3,4) (3,4)的数组和一个形状为 ( 4 , 3 ) (4,3) (4,3)的数组是不能进行元素级别运算的,因为它们的维度不兼容。

  4. 当输入数组的某个维度长度为1时,它会沿着这个维度复制其内容,使其与输出数组在该维度上的长度一致。
    如果你用一个形状为 ( 3 , 1 ) (3,1) (3,1)的数组加上一个形状为 ( 3 , 4 ) (3,4) (3,4)的数组,那么第一个数组会沿着其第二个维度复制内容,好像它本来就是 ( 3 , 4 ) (3,4) (3,4)那样。

为了更好地理解,你可以想象广播像是一种让不同维度的数组能够和谐共存、一起工作的魔法。并且,通过使用reshape()函数,你可以轻松地调整数组的形状,确保它们可以正常地进行广播操作。

注意: 当你在编写代码时,尤其是涉及多维数组的计算时,最好明确你的数组的形状,以避免可能出现的错误或不可预见的结果。

6. 关于Python/Numpy向量的小提示

我们接下来要探讨Python中一些关于向量的细节,帮助你避免编码中的小陷阱。

在Python的Numpy库中,当你使用以下方式创建一个向量:

a = np.random.randn(5)

这样产生的向量a的形状是(5,)。这样的向量很特别,它既不是行向量,也不是列向量,我们称它为"rank 1 array"。这种向量在实际操作中可能会带来一些麻烦。比如,你试图转置这个向量,但结果还是它本身。为了避免这样的问题,当你需要创建一个(5,1)的列向量或一个(1,5)的行向量时,建议使用以下更规范的方式:

a = np.random.randn(5,1)
b = np.random.randn(1,5)

为了确保向量或数组的形状与你预期的相符,你可以使用assert语句来进行检查:

assert(a.shape == (5,1))

这个assert语句会检查a的形状是否为(5,1)。如果不是,程序会立刻报错并停止。养成使用assert语句的习惯,可以帮你及时捕捉潜在的错误,确保代码的准确性。

最后,如果需要,你还可以使用reshape函数来调整数组的形状,使其符合你的要求:

a.reshape((5,1))

希望这些小提示能帮你更好地在Python中处理向量,避免不必要的错误!

7. Jupyter/iPython笔记本快速指南 (Quick tour of Jupyter/iPython Notebooks)

Jupyter notebook(也被称为IPython notebook)是一个能够实现编程、数学、绘图和文本全方位互动的工具,它支持超过40种编程语言,包括最受欢迎的Python。在这个课程中,我们会使用Python语言,并且所有的编程练习都会在Jupyter notebook上完成。

8. 逻辑回归成本函数的解释 (Explanation of logistic regression cost function - optional)

在之前的课程中,我们已经初步讲解了逻辑回归的成本函数。现在,我们来更深入地理解这个成本函数是如何产生的。

首先,我们有预测输出 y ^ \hat{y} y^,其公式为:

y ^ = σ ( w T x + b ) \hat{y} = \sigma(w^T x + b) y^=σ(wTx+b)

这里, σ ( z ) = 1 1 + exp ⁡ ( − z ) \sigma(z) = \frac{1}{1 + \exp(-z)} σ(z)=1+exp(z)1 表示sigmoid函数。我们可以将 y ^ \hat{y} y^理解为给定输入x时,输出为正类(y=1)的预测概率:

y ^ = P ( y = 1 ∣ x ) \hat{y} = P(y=1 | x) y^=P(y=1∣x)

如果真实的y=1,预测概率为:

p ( y ∣ x ) = y ^ p(y|x) = \hat{y} p(yx)=y^

反之,如果y=0:

p ( y ∣ x ) = 1 − y ^ p(y|x) = 1 - \hat{y} p(yx)=1y^

我们可以结合上面两个方程得到:

P ( y ∣ x ) = y ^ y ( 1 − y ^ ) ( 1 − y ) P(y|x) = \hat{y}^y (1 - \hat{y})^{(1-y)} P(yx)=y^y(1y^)(1y)

为了简化问题,我们可以对上述概率应用对数函数。得到:

log ⁡ P ( y ∣ x ) = y log ⁡ y ^ + ( 1 − y ) log ⁡ ( 1 − y ^ ) \log P(y|x) = y \log \hat{y} + (1 - y) \log (1 - \hat{y}) logP(yx)=ylogy^+(1y)log(1y^)

我们的目标是让上述的概率尽可能大,所以,将其乘以-1,我们就得到了单个样本的损失函数,即:

L = − ( y log ⁡ y ^ + ( 1 − y ) log ⁡ ( 1 − y ^ ) ) L = - (y \log \hat{y} + (1 - y) \log (1 - \hat{y})) L=(ylogy^+(1y)log(1y^))

考虑到所有的m个训练样本,如果假设所有的样本都是独立同分布的,我们的目标是让整体的概率尽可能地大。通过引入对数函数并乘以-1,我们可以得到整体的成本函数:

J ( w , b ) = − 1 m ∑ i = 1 m [ y ( i ) log ⁡ y ^ ( i ) + ( 1 − y ( i ) ) log ⁡ ( 1 − y ^ ( i ) ) ] J(w, b) = - \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=1m[y(i)logy^(i)+(1y(i))log(1y^(i))]

在这里, 1 m \frac{1}{m} m1 是一个正则化因子,用于对所有样本的成本函数取平均。

9. 总结 (Summary)

在这一节,我们探讨了神经网络的基础:Python和向量化。当涉及深度学习程序时,利用向量化和矩阵运算可以极大地提高执行速度,节省宝贵的时间。通过逻辑回归为例,我们学习了如何将算法流程和梯度下降转换为向量化形式。此外,我们还简要介绍了Python编程的相关方法和策略。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 非常感谢您的建议,我们会考虑为大家提供一篇关于C语言入门的学习笔记。C语言是一门非常重要的编程语言,它广泛应用于操作系统、嵌入式系统、游戏开发等领域。对于初学者来说,学习C语言可以帮助他们掌握编程的基本概念和技能,为以后的学习打下坚实的基础。 在这篇学习笔记中,我们将介绍C语言的基本语法、数据类型、运算符、控制语句、函数等内容。我们会从简单的例子开始,逐步深入,帮助大家理解C语言的核心概念和编程思想。我们还会提供一些练习题,帮助大家巩固所学知识。 希望这篇学习笔记能够对初学者有所帮助,让大家更好地掌握C语言编程。如果您有其他建议或意见,欢迎随时联系我们。 ### 回答2: 作为一门计算机语言,C语言的使用广泛且重要,即便现在有许多其他语言的存在,但学习C语言仍然是计算机学习的必经之路。如今,学习C语言也方便了许多,因为有许多教学资源都是免费的。比如那些像《C语言入门这一篇就够了-学习笔记》这样的教学资源,它们都是由专业人士撰写的教材或教程,比较全面地讲解了C语言的基础知识、重要概念、应用场景等等。在许多情况下,这些资源甚至可以替代正式的计算机课程。 《C语言入门这一篇就够了-学习笔记》更像是一份学习笔记,但它却给使用者提供了许多有关学习C语言时深入思考的信息和提示。在这份文档中,讲解C语言基础知识的同时,也对C语言常用函数做了简单的讲解。此外,该文档还特别提到了如何阅读错误信息和如何进行Debug工作,这对学习C语言的初学者来说尤为重要。 在实际的编程过程中,C语言的知识是必不可少的。尽管现在各种编码语言都有自己的粉丝群体,但是对于技术人员来说,学习C语言仍是一项至关重要的任务。该语言的使用可以帮助我们更好地了解计算机,使我们对编程概念和计算机原理有更深入的理解。从这个角度来看,《C语言入门这一篇就够了-学习笔记》是一份非常有用的资源,尤其是对那些希望打好C语言基础和学习编程的初学者来说,它将是一个非常好的学习工具。 总之,《C语言入门这一篇就够了-学习笔记》这份文档内容通俗易懂,适合初学者,但它并不是唯一的学习资源。对于那些希望深入学习C语言的学生,还需要更多的学习材料以及更多的实践机会,那样才能真正掌握和熟练应用C语言。 ### 回答3: C语言是实现高效编程的一种常用编程语言。学习C语言,对于初学者来说是个具有挑战性的过程,需要花费大量的时间和精力。在这篇“C语言入门”学习笔记中,简明扼要地介绍了学习C语言的几个基本方面,包括C语言的基本语法、数据类型、运算符、控制语句、数组和指针等。 首先,C语言的基本语法是需要掌握的。了解C语言的基本语法对于编写代码非常重要。这篇学习笔记首先介绍了C语言的源文件结构和注释。在实际编写代码的时候注释中的内容非常重要,需要保证代码的可读性和可维护性。此外,学习C语言的前身C++和Java也都有很多相似的语法结构。 其次,数据类型是C语言中的重要概念之一。C语言有多个原生的数据类型,同时也可以通过结构体等方式创建自己的数据类型。在学习C语言中,需要注意各个数据类型的使用范畴和数据长度等。 运算符是C语言中实现计算的重要手段之一,了解不同的运算符和运算顺序对于编写代码非常重要。控制语句的使用也需要注意,可以通过if、for、while等语句来控制程序的运行次数和顺序,从而实现各种不同的功能。 数组和指针是C语言中另外两个非常重要的概念。数组可以对大量数据进行处理,比较灵活。指针是C语言中常用的一种内存管理机制,不同的指针类型可以指向不同的数据类型,可以更加灵活地管理内存。 综上所述,学习C语言需要具有较好的编程能力,同时也需要多练习和思考。这篇学习笔记中介绍了C语言的一些基本概念和实现方式,希望可以为初学者提供更多的帮助。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

快撑死的鱼

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值