A note on python/numpy vectors
Last updated
Was this helpful?
Last updated
Was this helpful?
Python提供了广播操作的能力 更广泛地说 Python和NumPy带来了极佳的灵活性 我认为这既是Python作为一门编程语言的优势 也是它的劣势 其优势在于增加了语言的表达性 凭借其强大的灵活性 你只用仅仅一行代码就能 完成大量的工作 但这也带来一些缺点 因为广播操作和其强大的灵活性 有时会引入十分微妙 或者非常奇怪的bug 如果你并不熟悉诸如广播等特性的 各种复杂的工作机制的话 例如 如果你将一个列向量与行向量相加 你可能会期望它抛出维度不匹配 或者类型错误之类的报错 但实际上 你会得到一个行向量和列向量 求和后的矩阵 Python的这些奇怪表现有其内在的逻辑 但如果你对Python不熟悉 就会像我见过的一些学生 写出非常奇怪 非常难以发现的bug 所以我想在这个视频里和你们分享一些技巧 它们在排除各种奇怪的bug 和简化代码方面 对我帮助很大 我也希望 在这些技巧的帮助下 你们也能更容易的写出 没有bug的Python和NumPy代码
为了说明Python和NumPy中 尤其是在构建向量时 的一些不太直观的效果 让我来做个快速演示 令a=np.random.randn(5) 这会产生5个高斯随机变量 并储存在数组a中 输入print(a) 结果表明 a的形状是这种(5,)的结构 这在Python中叫做秩为1的数组 它既不是行向量 也不是列向量 这会略微导致一些不直观的影响 比如 我打印a的转置 它的结果看上去和a一样 所以 a和a的转置看起来一样 又如 打印a和a转置的内积 你也许认为a乘以a转置 或者说a的外积 是一个矩阵 但如果我这样写 得到的却是一个数字 所以 我建议你在编写神经网络时 不要使用这种数据结构 即形如(5,)或者(n,)这样的秩为1的数组 而是令a的形状为(5,1) 这会使a成为一个5乘1的列向量 在之前 a和a转置看起来是一样的 而现在a的转置是一个行向量 要注意这个细微的差别 使用这种数据结构时 打印a转置的结果中有两个方括号 而之前却只有一个方括号 区别在于 这是一个真正的1乘5的矩阵 而之前的是秩为1的数组 并且 如果你打印a和a转置的积 这样会得到向量的外积 对吧? 向量的外积是一个矩阵 下面我们更深入地了解一下刚才看到的情况 刚才我们运行的第一个命令是这个 它创建了这样一个数据结构 a.shape的值就是这个奇怪的(5,) 这称为秩为1的数组 这是个很奇怪的数据结构 它的行为并不总与行向量或者列向量相一致 这使得它会带来一些不直观的影响 所以我的建议是 当你在做编程练习时 或者准确地说 在实现作业中的逻辑回归或神经网络时 不要使用这种秩1为数组
相反 如果你在每次创建数组时 都确保使它是一个列向量 就像这样创建5乘1的向量 或者确保它是一个行向量 那么这些向量的行为将更容易理解 所以这里a.shape等于(5,1) 这实际上就是一个列向量 因此你可以把它当成5乘1的矩阵 也就是列向量 然后 这里a.shape的值是(1,5) 它的行为始终和行向量一致
所以当你需要一个向量时 可以二者择其一 但不要用秩为1的数组 我在编程时还经常会做这样一件事 如果我不太确定某一个向量的维度 我通常会将其放入断言语句中 就像在这里 用来确保a是5乘1的向量 所以它是个列向量 执行这些断言的成本很低 并且还能充当代码的文档 当你觉得需要的时候 就使用断言语句 不要犹豫 最后 如果出于某些原因 你得到了一个秩1为数组 你可以用reshape来改变它的形状 a=a.reshape((5,1)) 比如使其成为(5,1)或者(1,5)的数组 这样它就会始终表现为列向量或者行向量 我有时会见到学生 因为这些秩为1的数组的不直观的行为 出现一些很难找出的bug 通过在旧代码中消除秩为1的矩阵 我觉得我的代码变得更简单了 而且我并不觉得这样写会限制 代码的表达 我从来不用秩为1的数组 要点是 为了简化代码 不要使用秩为1的数组 始终使用n乘1的矩阵 本质上是列向量 或者使用1乘n的矩阵 本质上是行向量 自由使用断言语句 来复查矩阵和数组的维度 还有 不要怕使用reshape操作 来确保矩阵和向量是你所需要的维度 由此 我希望这些建议能帮你从源头消除Python代码的bug 也能让编程练习变得更容易