01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南
02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖
03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南
04-玩转 LangChain:从文档加载到高效问答系统构建的全程实战
05-玩转 LangChain:深度评估问答系统的三种高效方法(示例生成、手动评估与LLM辅助评估)
06-从 0 到 1 掌握 LangChain Agents:自定义工具 + LLM 打造智能工作流!
07-【深度解析】从GPT-1到GPT-4:ChatGPT背后的核心原理全揭秘
01-【深度学习-Day 1】为什么深度学习是未来?一探究竟AI、ML、DL关系与应用
02-【深度学习-Day 2】图解线性代数:从标量到张量,理解深度学习的数据表示与运算
大家好!欢迎来到深度学习系列博客的第二篇。在上一篇中,我们初步了解了深度学习是什么以及它的重要性。从今天开始,我们将深入学习构建深度学习模型所必需的基础知识。而其中,线性代数扮演着至关重要的角色。
你可能会问:“为什么需要线性代数?” 简单来说,深度学习本质上是对数据进行一系列复杂的变换和计算。而线性代数正是描述和操作这些数据(通常以数组形式存在)的强大数学语言。无论是输入数据(如图像像素、文本词语)、模型参数(权重和偏置),还是中间计算结果,都可以用线性代数中的概念(向量、矩阵、张量)来表示。理解线性代数的核心概念和运算,是你看懂模型原理、高效实现算法、甚至排查错误的关键。
本篇文章将聚焦于深度学习中最常用、最核心的线性代数知识点,力求用通俗易懂的语言和实例,帮助你扫清障碍,为后续学习打下坚实基础。我们将涵盖:
不必担心复杂的证明和推导,我们的目标是理解概念并知道如何在实践中运用它们。让我们一起开始吧!
在深度学习的世界里,我们处理的所有信息,无论是图像、文本、声音还是表格数据,最终都需要转化为机器能够理解和处理的数字形式。线性代数提供了一套优雅而高效的结构来组织这些数字。
标量是最简单的数据结构,它就是一个单独的数字。可以把它想象成一个零维的数组。
在 Python 中,标量通常用基本的数字类型 int
或 float
表示。
# 标量示例
learning_rate = 0.001
loss = 0.54
integer_scalar = 10
print(f"Type of learning_rate: {type(learning_rate)}")
print(f"Type of integer_scalar: {type(integer_scalar)}")
向量是一组有序排列的数字,可以看作是一个一维数组。它有方向和大小。在线性代数中,向量通常默认指列向量(一列多行),但有时也表示为行向量(一行多列)。
[25, 1, 15]
。我们通常使用 NumPy 库来创建和操作向量。
import numpy as np
# 创建一个行向量 (实际上NumPy创建的是一维数组,没有严格区分行列)
feature_vector = np.array([25, 1, 15])
print(f"Feature Vector (1D array): {feature_vector}")
print(f"Shape: {feature_vector.shape}") # 输出 (3,) 表示一维,3个元素
# 可以显式创建行向量 (1xN 矩阵)
row_vector = np.array([[25, 1, 15]])
print(f"Row Vector (2D array): {row_vector}")
print(f"Shape: {row_vector.shape}") # 输出 (1, 3)
# 创建列向量 (Nx1 矩阵)
column_vector = np.array([[25], [1], [15]])
print(f"Column Vector (2D array): \n{column_vector}")
print(f"Shape: {column_vector.shape}") # 输出 (3, 1)
注意: NumPy 的一维数组在进行某些运算(如矩阵乘法)时,会根据上下文自动判断是行向量还是列向量,这有时会带来便利,但也可能导致混淆。在严格的数学表达中,区分行向量和列向量很重要。
矩阵是一个二维数组,由数字排列成的矩形网格构成,包含行 (rows) 和列 (columns)。一个矩阵的大小由其行数和列数定义,例如一个 m × n m \times n m×n 矩阵有 m m m 行 n n n 列。
import numpy as np
# 创建一个 3x2 的矩阵 (3 行 2 列)
matrix_A = np.array([[1, 2],
[3, 4],
[5, 6]])
print(f"Matrix A:\n{matrix_A}")
print(f"Shape: {matrix_A.shape}") # 输出 (3, 2)
# 模拟一个数据批次,假设有 2 个样本,每个样本有 4 个特征
data_batch = np.array([[0.1, 0.5, -0.2, 1.0],
[-0.4, 0.8, 0.0, 0.3]])
print(f"\nData Batch:\n{data_batch}")
print(f"Shape: {data_batch.shape}") # 输出 (2, 4)
张量是线性代数中数据结构的一般化形式,可以看作是多维数组。它是标量、向量、矩阵的自然扩展:
张量的“阶” (rank) 或“维数” (number of dimensions/axes) 指的是其索引的数量。例如,一个 3 阶张量需要 3 个索引来定位其中的一个元素,如 T i j k T_{ijk} Tijk。
张量是深度学习框架(如 TensorFlow, PyTorch)处理数据的标准方式。
NumPy 以及深度学习框架都原生支持张量操作。
import numpy as np
# 创建一个 3 阶张量 (例如,模拟一个 2x3x2 的张量)
tensor_3d = np.array([[[1, 2], [3, 4], [5, 6]],
[[7, 8], [9, 10], [11, 12]]])
print(f"3D Tensor:\n{tensor_3d}")
print(f"Shape: {tensor_3d.shape}") # 输出 (2, 3, 2)
print(f"Number of dimensions (rank): {tensor_3d.ndim}") # 输出 3
# 访问元素
print(f"Element at index (0, 1, 1): {tensor_3d[0, 1, 1]}") # 输出 4
理解这些基本的数据结构是进行后续线性代数运算和理解深度学习模型的基础。
掌握了数据的表示方法后,下一步就是了解如何对这些数据进行运算。这些运算构成了深度学习模型中信息流动和转换的基础。
元素级运算指的是对两个具有相同形状的张量(向量、矩阵或更高维张量)的对应元素执行某种运算,生成一个形状完全相同的结果张量。
import numpy as np
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
# 加法
C_add = A + B
print(f"A + B:\n{C_add}")
# 输出:
# [[ 6 8]
# [10 12]]
# 减法
C_sub = A - B
print(f"\nA - B:\n{C_sub}")
# 输出:
# [[-4 -4]
# [-4 -4]]
# 形状不同会报错
# C = np.array([1, 2])
# try:
# A + C
# except ValueError as e:
# print(f"\nError when adding A and C: {e}")
import numpy as np
A = np.array([[1, 2], [3, 4]])
scalar = 10
C_scalar_mul = scalar * A # 或者 A * scalar
print(f"scalar * A:\n{C_scalar_mul}")
# 输出:
# [[10 20]
# [30 40]]
import numpy as np
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
# 哈达玛积 (在 NumPy 中使用 * 运算符)
C_hadamard = A * B
print(f"A * B (Hadamard Product):\n{C_hadamard}")
# 输出:
# [[ 5 12]
# [21 32]]
关键区分: 在 NumPy 中,*
运算符默认执行的是元素级乘法(哈达玛积)。
转置是一种重要的矩阵操作,它将矩阵的行和列进行互换。
.T
属性对其本身没有效果,因为它没有严格的行列区分。需要先将其视为二维数组(矩阵)。import numpy as np
# NumPy 一维数组
vec = np.array([1, 2, 3])
print(f"Original vec shape: {vec.shape}, Transposed vec.T: {vec.T}, Shape: {vec.T.shape}")
# 输出: Original vec shape: (3,), Transposed vec.T: [1 2 3], Shape: (3,)
# 显式创建行向量 (1xN 矩阵)
row_vec = np.array([[1, 2, 3]]) # Shape (1, 3)
col_vec = row_vec.T
print(f"\nRow vector shape: {row_vec.shape}")
print(f"Transposed to column vector:\n{col_vec}")
print(f"Column vector shape: {col_vec.shape}") # Shape (3, 1)
import numpy as np
A = np.array([[1, 2, 3],
[4, 5, 6]]) # Shape (2, 3)
A_T = A.T
print(f"Original Matrix A (shape {A.shape}):\n{A}")
print(f"\nTransposed Matrix A_T (shape {A_T.shape}):\n{A_T}")
# 输出:
# Original Matrix A (shape (2, 3)):
# [[1 2 3]
# [4 5 6]]
#
# Transposed Matrix A_T (shape (3, 2)):
# [[1 4]
# [2 5]
# [3 6]]
点积和矩阵乘法是线性代数中极其重要的运算,是神经网络中信息传递和转换的核心。
import numpy as np
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
# 计算点积
dot_product = np.dot(a, b)
# 或者使用 @ 运算符 (Python 3.5+)
# dot_product_at = a @ b
print(f"Dot product of a and b: {dot_product}")
# 输出: Dot product of a and b: 32 (1*4 + 2*5 + 3*6 = 4 + 10 + 18 = 32)
import numpy as np
A = np.array([[1, 2],
[3, 4]]) # Shape (2, 2)
B = np.array([[5, 6, 7],
[8, 9, 10]]) # Shape (2, 3)
# 矩阵乘法 C = AB
# A的列数(2) == B的行数(2),可以相乘
# 结果 C 的形状是 (A的行数, B的列数) = (2, 3)
C_matmul = np.dot(A, B)
# 或者使用 @ 运算符
# C_matmul_at = A @ B
print(f"Matrix A (shape {A.shape}):\n{A}")
print(f"Matrix B (shape {B.shape}):\n{B}")
print(f"\nMatrix Multiplication C = AB (shape {C_matmul.shape}):\n{C_matmul}")
# 输出:
# Matrix A (shape (2, 2)):
# [[1 2]
# [3 4]]
# Matrix B (shape (2, 3)):
# [[ 5 6 7]
# [ 8 9 10]]
#
# Matrix Multiplication C = AB (shape (2, 3)):
# [[21 24 27] # C[0,0] = A[0,:] dot B[:,0] = [1,2] dot [5,8] = 1*5+2*8=21
# [47 54 61]] # C[1,0] = A[1,:] dot B[:,0] = [3,4] dot [5,8] = 3*5+4*8=47
# ...以此类推计算其他元素
我们可以用一个简单的图示来说明矩阵乘法中元素的计算过程:
graph LR
subgraph Matrix A (m x n)
direction TB
A_row_i("Row i: [Ai1, Ai2, ..., Ain]")
end
subgraph Matrix B (n x p)
direction TB
B_col_j("Column j:
[B1j]
[B2j]
...
[Bnj]")
end
subgraph Result Matrix C (m x p)
C_ij("Element Cij")
end
A_row_i -- 点积 (Dot Product) --> C_ij;
B_col_j -- 点积 (Dot Product) --> C_ij;
note for C_ij "$$C_{ij} = A_{i1}B_{1j} + A_{i2}B_{2j} + \dots + A_{in}B_{nj} = \sum_{k=1}^{n} A_{ik}B_{kj}$$"
掌握这些核心运算是理解神经网络如何处理信息的关键一步。
理论学习固然重要,但动手实践更能加深理解。Python 的 NumPy 库是进行科学计算,特别是线性代数运算的标准库。下面我们用 NumPy 来实践前面介绍的概念和运算。
import numpy as np
# 确保前面的导入已执行
print("--- 3.1 创建标量、向量、矩阵、张量 ---")
# 标量 (虽然 NumPy 中通常用 0 维数组表示,但直接用 Python 类型更常见)
scalar_val = 10
print(f"Scalar: {scalar_val}, Type: {type(scalar_val)}")
# 向量 (1D Array)
vector_v = np.array([1.0, 2.5, -3.0])
print(f"\nVector v: {vector_v}")
print(f"Shape: {vector_v.shape}") # (3,)
# 矩阵 (2D Array)
matrix_M = np.array([[1, 2, 3],
[4, 5, 6]])
print(f"\nMatrix M:\n{matrix_M}")
print(f"Shape: {matrix_M.shape}") # (2, 3)
# 张量 (3D Array)
tensor_T = np.arange(12).reshape((2, 3, 2)) # 创建一个 0 到 11 的数组,并重塑为 2x3x2
print(f"\nTensor T:\n{tensor_T}")
print(f"Shape: {tensor_T.shape}") # (2, 3, 2)
print("\n--- 3.2 执行基本运算 ---")
# 准备用于运算的矩阵 (确保形状兼容)
A = np.array([[1, 0], [-1, 2]]) # Shape (2, 2)
B = np.array([[3, -2], [1, 4]]) # Shape (2, 2)
C = np.array([[5], [6]]) # Shape (2, 1) - 列向量
s = 2 # 标量
# 元素级加法 (需要相同形状)
print(f"\nElement-wise Addition (A + B):\n{A + B}")
# 元素级减法
print(f"\nElement-wise Subtraction (A - B):\n{A - B}")
# 标量乘法
print(f"\nScalar Multiplication (s * A):\n{s * A}")
# 哈达玛积 (元素级乘法)
print(f"\nHadamard Product (A * B):\n{A * B}")
# 矩阵转置
print(f"\nMatrix Transpose (A.T):\n{A.T}")
print(f"Shape of A: {A.shape}, Shape of A.T: {A.T.shape}") # (2, 2) -> (2, 2)
print(f"\nMatrix Transpose (C.T):\n{C.T}")
print(f"Shape of C: {C.shape}, Shape of C.T: {C.T.shape}") # (2, 1) -> (1, 2)
# 向量点积 (需要长度相同)
v1 = np.array([1, 2, 3])
v2 = np.array([4, 5, 6])
print(f"\nVector Dot Product (v1 . v2): {np.dot(v1, v2)}") # 32
# 或者 v1 @ v2
# 矩阵乘法 (需要内维匹配)
# A(2x2) @ B(2x2) -> (2x2)
print(f"\nMatrix Multiplication (A @ B):\n{A @ B}") # 使用 @ 运算符
# 或者 np.dot(A, B)
# A(2x2) @ C(2x1) -> (2x1)
print(f"\nMatrix Multiplication (A @ C):\n{A @ C}")
# 尝试不兼容的乘法 B(2x2) @ C.T(1x2) -> 会报错
try:
B @ C.T
except ValueError as e:
print(f"\nError multiplying B(2x2) and C.T(1x2): {e}")
# C.T(1x2) @ A(2x2) -> (1x2)
print(f"\nMatrix Multiplication (C.T @ A):\n{C.T @ A}")
这段代码演示了如何在 NumPy 中轻松地创建和操作这些线性代数对象。熟练使用 NumPy 是进行深度学习实践的基础。
在学习和应用线性代数,特别是在编程实现时,新手常常会遇到一些问题。这里列举几个常见点:
print(matrix.shape)
检查参与运算的张量的形状。*
运算符。要求两个张量形状相同。@
运算符 (Python 3.5+) 或 np.dot()
函数。要求内维匹配。axis
参数。(batch, height, width, channels)
或 NLP 中的 (batch, sequence_length, embedding_dim)
。axis
参数的用法。例如,np.sum(tensor, axis=0)
会沿着第一个轴(通常是批次维度)求和。.shape
) 来确认操作是否符合预期。恭喜你完成了深度学习数学基础的第一部分——线性代数核心!回顾一下本篇的主要内容:
线性代数构成了深度学习算法的骨架。虽然我们没有深入探讨所有理论细节,但理解这些核心概念和运算对于你阅读后续内容、理解模型工作原理以及动手实践至关重要。
在下一篇文章 【深度学习-Day 3】必备数学(二) - 微积分关键 中,我们将探讨另一个数学基石——微积分,特别是导数、偏导数和链式法则,它们是理解模型如何“学习”(优化参数)的关键。敬请期待!