01-编程基础与数学基石: NumPy数值计算库


NumPy数值计算库:AI开发的数学引擎

一、为什么需要NumPy?

1.1 Python原生列表的痛点

python 复制代码
# Python原生列表的问题
python_list = [1, 2, 3, 4, 5]

# 问题1:元素级运算需要循环
doubled = [x * 2 for x in python_list]  # 必须写循环
print(f"列表乘法: {doubled}")

# 问题2:多维数据难以表示
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
# 想取第一列?需要列表推导式
first_col = [row[0] for row in matrix]

# 问题3:数学运算慢(Python是解释型语言)
import time
large_list = list(range(1000000))
start = time.time()
result = [x ** 2 for x in large_list]
print(f"列表平方耗时: {time.time() - start:.4f}秒")

1.2 NumPy的解决方案

NumPy = Numeric Python,核心是ndarray(N-dimensional array)

python 复制代码
import numpy as np

# NumPy数组的优势
np_array = np.array([1, 2, 3, 4, 5])

# 优势1:向量化运算(无需循环)
doubled = np_array * 2
print(f"NumPy数组乘法: {doubled}")  # [2 4 6 8 10]

# 优势2:真正的多维数组
matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
first_col = matrix[:, 0]  # 简洁的切片语法
print(f"第一列: {first_col}")  # [1 4 7]

# 优势3:底层用C实现,速度快
large_array = np.arange(1000000)
start = time.time()
result = large_array ** 2
print(f"NumPy平方耗时: {time.time() - start:.4f}秒")
# 通常比Python列表快10-100倍!

# 优势4:内存连续,高效存储
print(f"内存占用: {large_array.nbytes / 1024 / 1024:.2f} MB")

1.3 NumPy在AI开发中的核心地位

复制代码
AI开发数据流:
原始数据 → NumPy数组 → 预处理 → PyTorch/TensorFlow张量 → 模型
         ↑
    80%的AI代码在这里

为什么AI离不开NumPy?

  • 图像数据本质是三维数组(高×宽×通道)
  • 文本特征用矩阵表示(样本×词向量)
  • 模型参数是矩阵(权重×偏置)
  • 损失函数、梯度计算都是向量化运算

二、ndarray的创建与基本属性

2.1 创建ndarray的多种方式

python 复制代码
import numpy as np

# === 从列表创建 ===
arr1 = np.array([1, 2, 3, 4, 5])
print(f"从列表: {arr1}")

arr2 = np.array([[1, 2, 3], [4, 5, 6]])
print(f"二维数组:\n{arr2}")

# 注意:列表中的元素类型必须一致
mixed = np.array([1, 2, 3.5, 4])
print(f"类型自动提升: {mixed}")  # [1. 2. 3.5 4.] 全部转为float

# === 使用NumPy内置函数创建 ===
# 全零数组
zeros = np.zeros((3, 4))  # 3行4列
print(f"全零数组:\n{zeros}")

# 全一数组
ones = np.ones((2, 3))
print(f"全一数组:\n{ones}")

# 单位矩阵
eye = np.eye(4)  # 4x4单位矩阵
print(f"单位矩阵:\n{eye}")

# 未初始化数组(内容随机)
empty = np.empty((2, 2))
print(f"未初始化: {empty}")  # 值不确定,速度快

# 填充固定值
full = np.full((3, 3), 7)
print(f"填充7:\n{full}")

# === 创建序列数组 ===
# arange:类似range,但返回数组
arange_arr = np.arange(0, 10, 2)  # start, stop, step
print(f"arange: {arange_arr}")  # [0 2 4 6 8]

# linspace:等间距点(包含终点)
lin_arr = np.linspace(0, 1, 5)  # start, stop, 点数
print(f"linspace: {lin_arr}")  # [0. 0.25 0.5 0.75 1.]

# 对数间隔
log_arr = np.logspace(0, 3, 4)  # 10^0 到 10^3,4个点
print(f"logspace: {log_arr}")  # [1. 10. 100. 1000.]

# === 随机数组(重要!)===
# 均匀分布[0,1)
rand_arr = np.random.rand(2, 3)  # 2x3
print(f"均匀分布:\n{rand_arr}")

# 标准正态分布
randn_arr = np.random.randn(3, 3)
print(f"正态分布:\n{randn_arr}")

# 指定范围的随机整数
randint_arr = np.random.randint(0, 100, size=(3, 3))
print(f"随机整数:\n{randint_arr}")

2.2 理解ndarray的核心属性

python 复制代码
# 创建一个三维数组(模拟3张RGB图像)
# shape: (batch_size, height, width, channels)
img_batch = np.random.rand(32, 224, 224, 3)

print("=== ndarray核心属性 ===")
print(f"shape(形状): {img_batch.shape}")
# shape是一个元组,描述每个维度的大小
# 含义:32张图,每张224x224像素,3个颜色通道

print(f"ndim(维度数): {img_batch.ndim}")
# 4维数组

print(f"size(总元素数): {img_batch.size}")
# 32 * 224 * 224 * 3 = 4,816,896

print(f"dtype(数据类型): {img_batch.dtype}")
# float64(双精度浮点)

print(f"itemsize(单个元素字节): {img_batch.itemsize}")
# float64占8字节

print(f"nbytes(总字节数): {img_batch.nbytes / 1024 / 1024:.2f} MB")
# 总内存占用

# === 理解shape的层次 ===
arr = np.array([
    [[1, 2], [3, 4]],
    [[5, 6], [7, 8]]
])
print(f"\n三维数组:\n{arr}")
print(f"shape: {arr.shape}")  # (2, 2, 2)
# 解读:2个矩阵,每个2行2列

2.3 dtype详解(理解数据类型)

python 复制代码
# NumPy的数据类型比Python更精细
# 原因:控制内存和计算精度

# === 常用dtype ===
types = [
    ('int8', np.int8, '有符号8位整数', -128, 127),
    ('int32', np.int32, '有符号32位整数', -2^31, 2^31-1),
    ('float32', np.float32, '单精度浮点', 'AI模型常用'),
    ('float64', np.float64, '双精度浮点', 'NumPy默认'),
    ('bool', np.bool, '布尔值', 'True/False'),
]

# 创建时指定dtype
arr_int8 = np.array([1, 2, 3], dtype=np.int8)
print(f"int8数组: {arr_int8}, 占用: {arr_int8.nbytes}字节")

arr_float32 = np.array([1.5, 2.5, 3.5], dtype=np.float32)
print(f"float32数组: {arr_float32}, 精度: {arr_float32.dtype}")

# 类型转换(astype)
arr_float = np.array([1.5, 2.7, 3.9])
arr_int = arr_float.astype(np.int32)  # 截断小数
print(f"float转int: {arr_float} -> {arr_int}")

# AI实践:图像数据转换
image_uint8 = np.random.randint(0, 256, (224, 224, 3), dtype=np.uint8)
print(f"原始图像(0-255): {image_uint8.dtype}")

# 归一化到[0,1](必须转float)
image_float = image_uint8.astype(np.float32) / 255.0
print(f"归一化后: {image_float.dtype}, 范围: [{image_float.min():.2f}, {image_float.max():.2f}]")

三、索引、切片与布尔索引

3.1 基础索引(理解视图vs副本)

python 复制代码
# 创建示例数组
arr = np.array([[1, 2, 3, 4],
                [5, 6, 7, 8],
                [9, 10, 11, 12]])

print(f"原始数组:\n{arr}")

# === 基础索引 ===
print(f"\n单个元素: arr[1, 2] = {arr[1, 2]}")  # 第2行第3列 = 7
print(f"等价写法: arr[1][2] = {arr[1][2]}")   # 不推荐,效率低

# 获取一行
row = arr[1, :]  # 第2行所有列
print(f"第2行: {row}")

# 获取一列
col = arr[:, 2]  # 所有行第3列
print(f"第3列: {col}")

# 获取子矩阵
sub = arr[0:2, 1:3]  # 前2行,第2-3列
print(f"子矩阵:\n{sub}")

# === 重要概念:视图(View) vs 副本(Copy) ===
sub_view = arr[0:2, 0:2]
sub_view[0, 0] = 999  # 修改视图

print(f"\n修改视图后原数组:\n{arr}")
# 原数组也被修改了!因为切片返回的是视图

# 如果需要独立副本,使用.copy()
sub_copy = arr[0:2, 0:2].copy()
sub_copy[0, 0] = 888
print(f"修改副本不影响原数组:\n{arr}")

3.2 高级切片技巧

python 复制代码
# 创建示例
arr = np.arange(24).reshape(4, 6)
print(f"原始数组 4x6:\n{arr}")

# === 步长切片 ===
# 每隔一个取一个
step_arr = arr[::2, ::2]  # 行步长2,列步长2
print(f"步长2采样:\n{step_arr}")

# 反向(翻转)
reverse_arr = arr[::-1, :]  # 上下翻转
print(f"上下翻转:\n{reverse_arr}")

# 同时反向
full_reverse = arr[::-1, ::-1]  # 180度旋转
print(f"完全反转:\n{full_reverse}")

# === ... 省略号(处理高维数组)===
arr_3d = np.random.rand(2, 3, 4)
print(f"\n3D数组shape: {arr_3d.shape}")

# ...代表"所有剩余维度"
print(f"arr_3d[0, ...] shape: {arr_3d[0, ...].shape}")  # 等价于arr_3d[0, :, :]
print(f"arr_3d[..., 0] shape: {arr_3d[..., 0].shape}")  # 等价于arr_3d[:, :, 0]

# === newaxis增加维度 ===
arr_1d = np.array([1, 2, 3])
print(f"1D shape: {arr_1d.shape}")

# 增加行维度(变成列向量)
col_vec = arr_1d[:, np.newaxis]
print(f"列向量 shape: {col_vec.shape}")  # (3, 1)

# 增加列维度(变成行向量)
row_vec = arr_1d[np.newaxis, :]
print(f"行向量 shape: {row_vec.shape}")  # (1, 3)

# AI实践:批量处理时的维度调整
batch = np.random.rand(32, 224, 224, 3)  # (batch, h, w, c)
# 想取每个batch的第一行
first_row = batch[:, 0, :, :]  # shape: (32, 224, 3)
print(f"取每张图第一行: {first_row.shape}")

3.3 布尔索引(条件筛选)

python 复制代码
# 布尔索引是AI数据清洗的核心工具

# 创建示例数据
data = np.array([12, 45, 23, 67, 89, 34, 56, 78])
print(f"原始数据: {data}")

# === 基础布尔索引 ===
# 创建条件掩码
mask = data > 50
print(f"大于50的掩码: {mask}")  # [False False False True True False True True]

# 使用掩码筛选
filtered = data[mask]
print(f"大于50的值: {filtered}")  # [67 89 56 78]

# 一行写法
result = data[data > 50]
print(f"一行写法: {result}")

# === 多条件组合 ===
# 与 (&)、或 (|)、非 (~)
mask_and = (data > 30) & (data < 70)  # 30-70之间
print(f"30-70之间: {data[mask_and]}")

mask_or = (data < 20) | (data > 80)   # 小于20或大于80
print(f"小于20或大于80: {data[mask_or]}")

mask_not = ~(data > 50)  # 不大于50
print(f"不大于50: {data[mask_not]}")

# === 实战:异常值检测与处理 ===
# 模拟传感器数据
sensor_data = np.random.randn(1000) * 10 + 50  # 均值50,标准差10
print(f"\n传感器数据: 均值={sensor_data.mean():.2f}, 标准差={sensor_data.std():.2f}")

# 检测异常值(3倍标准差外)
mean = sensor_data.mean()
std = sensor_data.std()
outliers = np.abs(sensor_data - mean) > 3 * std

print(f"异常值数量: {outliers.sum()}")
print(f"异常值: {sensor_data[outliers]}")

# 处理异常值:替换为中位数
median = np.median(sensor_data)
sensor_data[outliers] = median
print(f"处理后异常值: {sensor_data[outliers].sum()}")  # 全被替换成中位数

# === AI实战:图像二值化 ===
# 模拟灰度图像(0-255)
image = np.random.randint(0, 256, (10, 10))
print(f"\n原始图像:\n{image}")

# 二值化:大于128的设为1,否则0
binary = np.where(image > 128, 1, 0)
print(f"二值化后:\n{binary}")

# 更复杂的条件:三段式
# 小于100置0,100-200置1,大于200置2
segmented = np.piecewise(image, 
                          [image < 100, (image >= 100) & (image <= 200), image > 200],
                          [0, 1, 2])
print(f"分段处理:\n{segmented}")

四、广播机制(Broadcasting)

4.1 广播的原理

广播是NumPy最强大也最易混淆的特性,理解它才能写出高效代码。

python 复制代码
# 核心思想:不同形状的数组进行运算时,NumPy会自动扩展较小数组

# === 最简单的广播:标量 vs 数组 ===
arr = np.array([1, 2, 3, 4, 5])
result = arr + 10  # 标量10被广播到所有元素
print(f"数组+标量: {arr} + 10 = {result}")

# 原理:10被扩展为 [10, 10, 10, 10, 10]

# === 一维 vs 二维广播 ===
matrix = np.array([[1, 2, 3],
                   [4, 5, 6]])
vector = np.array([10, 20, 30])

result = matrix + vector
print(f"\n矩阵 + 向量:\n{matrix}\n+\n{vector}\n=\n{result}")

# 广播过程:
# vector形状(3,) → 广播为(2,3) → [[10,20,30], [10,20,30]]
# 然后逐元素相加

4.2 广播规则(理解这个就够了)

规则:从后往前比较维度,满足以下条件之一即可广播:

  1. 维度相等
  2. 其中一个维度为1
  3. 其中一个维度不存在
python 复制代码
# 规则1:维度相等
arr1 = np.random.rand(3, 4)
arr2 = np.random.rand(3, 4)
# ✅ 可以广播:形状完全相同

# 规则2:其中一个为1
arr1 = np.random.rand(3, 4)
arr2 = np.random.rand(1, 4)
# ✅ arr2的1可以广播为3 → (3,4)

arr1 = np.random.rand(3, 4, 5)
arr2 = np.random.rand(4, 1)
# ✅ (3,4,5) vs (4,1)
#   从后往前:5 vs 1 ✅ 1广播为5
#          4 vs 4 ✅ 相等
#          3 vs (无) ✅ 维度不存在

# 规则3:无法广播的例子
arr1 = np.random.rand(3, 4)
arr2 = np.random.rand(2, 4)
# ❌ 3 vs 2,都不为1,无法广播 → ValueError

4.3 广播实战案例

python 复制代码
# === 案例1:数据标准化(零均值单位方差)===
data = np.random.randn(100, 50)  # 100个样本,50个特征

# 计算每列的均值和标准差
col_mean = data.mean(axis=0)     # shape (50,)
col_std = data.std(axis=0)       # shape (50,)

# 广播:将(50,)广播到(100,50)
normalized = (data - col_mean) / col_std
print(f"标准化后: 均值={normalized.mean():.2f}, 标准差={normalized.std():.2f}")

# === 案例2:为每个样本添加偏置 ===
features = np.random.rand(32, 256)  # 32个样本,256维特征
bias = np.random.rand(256)          # 每个特征的偏置

# 广播加法
biased_features = features + bias
print(f"添加偏置后shape: {biased_features.shape}")

# === 案例3:外积计算(无需循环)===
x = np.array([1, 2, 3])
y = np.array([4, 5, 6, 7])

# 想要计算 x_i * y_j 的所有组合
outer_product = x[:, np.newaxis] * y[np.newaxis, :]
print(f"外积:\n{outer_product}")
# [[ 4  5  6  7]
#  [ 8 10 12 14]
#  [12 15 18 21]]

# === 案例4:图像数据增强(减去均值)===
# 模拟一批RGB图像
batch = np.random.randint(0, 256, (64, 224, 224, 3)).astype(np.float32)

# 计算全局RGB均值(3个通道)
channel_mean = batch.mean(axis=(0, 1, 2))  # shape (3,)
print(f"RGB均值: {channel_mean}")

# 广播减去均值
batch_centered = batch - channel_mean
print(f"去均值后shape: {batch_centered.shape}")

# === 案例5:计算样本间的距离矩阵 ===
# 计算所有样本对之间的欧氏距离
samples = np.random.rand(100, 10)  # 100个样本,10维

# 利用广播:|a-b|^2 = a^2 + b^2 - 2ab
# (100,1,10) vs (1,100,10) 广播为 (100,100,10)
diff = samples[:, np.newaxis, :] - samples[np.newaxis, :, :]
distances = np.sqrt((diff ** 2).sum(axis=2))
print(f"距离矩阵shape: {distances.shape}")  # (100, 100)

五、通用函数(ufunc)

5.1 什么是ufunc?

ufunc = Universal Function,对数组逐元素操作的函数

python 复制代码
# ufunc的核心优势:向量化、无需循环、速度快

# 创建测试数据
arr = np.array([1, 2, 3, 4, 5])

# 数学运算
print(f"原始: {arr}")
print(f"平方: {np.square(arr)}")     # [1 4 9 16 25]
print(f"开方: {np.sqrt(arr)}")       # [1. 1.414 1.732 2. 2.236]
print(f"指数: {np.exp(arr)}")        # e^x
print(f"自然对数: {np.log(arr)}")    # ln(x)
print(f"以10为底: {np.log10(arr)}")  # log10(x)

# 三角函数
angles = np.array([0, np.pi/6, np.pi/4, np.pi/3, np.pi/2])
print(f"\n角度: {angles}")
print(f"sin: {np.sin(angles)}")
print(f"cos: {np.cos(angles)}")

5.2 常用数学ufunc详解

python 复制代码
# === 1. 指数和对数(AI中最常用)===
# Softmax函数的核心
def softmax(x):
    """Softmax实现,展示exp和sum的配合"""
    exp_x = np.exp(x - np.max(x))  # 减去最大值防止溢出
    return exp_x / np.sum(exp_x)

logits = np.array([2.0, 1.0, 0.1])
probs = softmax(logits)
print(f"Softmax输出: {probs}")
print(f"概率和: {probs.sum()}")

# === 2. 统计函数 ===
data = np.random.randn(1000)

print(f"求和: {np.sum(data):.2f}")
print(f"均值: {np.mean(data):.2f}")
print(f"中位数: {np.median(data):.2f}")
print(f"标准差: {np.std(data):.2f}")
print(f"方差: {np.var(data):.2f}")
print(f"最小值: {np.min(data):.2f}")
print(f"最大值: {np.max(data):.2f}")
print(f"取值范围: {np.ptp(data):.2f}")  # max-min

# axis参数(指定计算轴)
matrix = np.array([[1, 2, 3],
                   [4, 5, 6],
                   [7, 8, 9]])

print(f"\n原矩阵:\n{matrix}")
print(f"按列求和(axis=0): {np.sum(matrix, axis=0)}")  # [12 15 18]
print(f"按行求和(axis=1): {np.sum(matrix, axis=1)}")  # [6 15 24]
print(f"全部求和: {np.sum(matrix)}")  # 45

# keepdims保持维度
row_sum = np.sum(matrix, axis=1, keepdims=True)
print(f"保持维度: {row_sum.shape}")  # (3,1)

# === 3. 累加和累乘 ===
arr = np.array([1, 2, 3, 4, 5])
print(f"累加: {np.cumsum(arr)}")  # [1 3 6 10 15]
print(f"累乘: {np.cumprod(arr)}")  # [1 2 6 24 120]

5.3 点积与矩阵乘法(AI核心运算)

python 复制代码
# === np.dot:点积/矩阵乘法 ===
# 向量点积
v1 = np.array([1, 2, 3])
v2 = np.array([4, 5, 6])
dot_product = np.dot(v1, v2)  # 1*4 + 2*5 + 3*6 = 32
print(f"向量点积: {dot_product}")

# 矩阵乘法(最重要的运算)
A = np.array([[1, 2],
              [3, 4]])
B = np.array([[5, 6],
              [7, 8]])

C = np.dot(A, B)
print(f"\n矩阵A:\n{A}")
print(f"矩阵B:\n{B}")
print(f"矩阵乘法A@B:\n{C}")

# 手动验证:C[0,0] = A[0,0]*B[0,0] + A[0,1]*B[1,0] = 1*5 + 2*7 = 19

# === 其他矩阵乘法方式 ===
# 方法1:np.dot
result1 = np.dot(A, B)

# 方法2:@运算符(Python 3.5+,推荐)
result2 = A @ B

# 方法3:np.matmul
result3 = np.matmul(A, B)

print(f"三种方式等价: {np.allclose(result1, result2)}")

# === 神经网络中的矩阵乘法 ===
# 输入层:32个样本,每个784维
input_layer = np.random.randn(32, 784)

# 权重矩阵:784个输入,256个输出
weights = np.random.randn(784, 256)

# 偏置:256个神经元
bias = np.random.randn(256)

# 全连接层计算
output = input_layer @ weights + bias  # shape (32, 256)
print(f"\n全连接层输出shape: {output.shape}")

# === 理解维度匹配规则 ===
# (m, n) @ (n, p) = (m, p)
# 关键:第一个矩阵的列数 = 第二个矩阵的行数

5.4 聚合函数的高效使用

python 复制代码
# 创建3D数据(模拟视频帧:10帧,224x224,3通道)
video = np.random.randn(10, 224, 224, 3)

# === 多轴聚合 ===
# 计算每帧的均值(保留batch维度)
frame_mean = video.mean(axis=(1, 2, 3))  # shape (10,)
print(f"每帧均值shape: {frame_mean.shape}")

# 计算所有像素的全局均值
global_mean = video.mean()
print(f"全局均值: {global_mean:.4f}")

# === 实战:计算准确率 ===
predictions = np.array([0, 1, 1, 0, 1, 0, 1, 1, 0, 0])
labels = np.array([0, 1, 0, 0, 1, 1, 1, 0, 0, 1])

accuracy = np.mean(predictions == labels)
print(f"\n准确率: {accuracy:.2f}")

# === 实战:梯度裁剪 ===
gradients = np.random.randn(1000) * 10
print(f"裁剪前: 最大值={gradients.max():.2f}, 最小值={gradients.min():.2f}")

# 限制梯度范围[-5, 5]
clipped = np.clip(gradients, -5, 5)
print(f"裁剪后: 最大值={clipped.max():.2f}, 最小值={clipped.min():.2f}")

六、随机数生成(np.random)

6.1 随机数种子:可重复性的关键

python 复制代码
# AI训练必须设置随机种子,否则结果不可复现

# 不设置种子,每次运行结果不同
print("无种子:")
print(np.random.rand(3))  # 每次不同

# 设置种子,结果固定
np.random.seed(42)
print("\n设置种子42:")
print(np.random.rand(3))  # [0.37454012 0.95071431 0.73199394]

np.random.seed(42)  # 再次设置相同种子
print(np.random.rand(3))  # [0.37454012 0.95071431 0.73199394] 相同!

# 最佳实践:在程序开头统一设置
SEED = 42
np.random.seed(SEED)
# 如果使用其他库,也要设置
# random.seed(SEED)
# torch.manual_seed(SEED)

6.2 各种分布采样

python 复制代码
np.random.seed(42)

# === 均匀分布 ===
# [0,1) 均匀分布
uniform = np.random.rand(1000)
print(f"均匀分布: 均值={uniform.mean():.3f}, 标准差={uniform.std():.3f}")

# [low, high) 均匀分布
uniform_range = np.random.uniform(low=-1, high=1, size=(3, 3))
print(f"[-1,1)均匀分布:\n{uniform_range}")

# === 正态分布(高斯分布)===
# 标准正态 N(0,1)
normal_std = np.random.randn(1000)
print(f"\n标准正态: 均值={normal_std.mean():.3f}, 标准差={normal_std.std():.3f}")

# 自定义正态 N(μ, σ²)
normal_custom = np.random.normal(loc=5, scale=2, size=1000)
print(f"N(5,4): 均值={normal_custom.mean():.3f}, 标准差={normal_custom.std():.3f}")

# === 整数分布 ===
# [low, high) 随机整数
integers = np.random.randint(low=0, high=10, size=(2, 5))
print(f"\n随机整数:\n{integers}")

# === 选择与排列 ===
data = np.array([10, 20, 30, 40, 50])

# 随机选择(可重复)
choice_with_replacement = np.random.choice(data, size=5, replace=True)
print(f"有放回采样: {choice_with_replacement}")

# 随机选择(不重复)
choice_no_replacement = np.random.choice(data, size=3, replace=False)
print(f"无放回采样: {choice_no_replacement}")

# 随机打乱
np.random.shuffle(data)
print(f"打乱后: {data}")

# 随机排列(返回新数组)
permuted = np.random.permutation(10)
print(f"0-9随机排列: {permuted}")

6.3 AI实战:数据增强采样

python 复制代码
np.random.seed(42)

class DataAugmentation:
    """图像数据增强工具"""
    
    @staticmethod
    def random_flip(image):
        """随机水平翻转"""
        if np.random.rand() > 0.5:
            return np.fliplr(image)
        return image
    
    @staticmethod
    def random_rotation(image, max_angle=20):
        """随机旋转(模拟)"""
        angle = np.random.uniform(-max_angle, max_angle)
        # 实际应用中会使用scipy.ndimage.rotate
        return image  # 简化演示
    
    @staticmethod
    def random_brightness(image, max_delta=0.2):
        """随机亮度调整"""
        delta = np.random.uniform(-max_delta, max_delta)
        return np.clip(image + delta, 0, 1)
    
    @staticmethod
    def random_crop(image, crop_size):
        """随机裁剪"""
        h, w = image.shape[:2]
        top = np.random.randint(0, h - crop_size)
        left = np.random.randint(0, w - crop_size)
        return image[top:top+crop_size, left:left+crop_size]

# 使用示例
sample_image = np.random.rand(224, 224, 3)
aug = DataAugmentation()

# 随机应用多种增强
augmented = sample_image.copy()
if np.random.rand() > 0.5:
    augmented = aug.random_flip(augmented)
if np.random.rand() > 0.5:
    augmented = aug.random_brightness(augmented)

print(f"数据增强完成,输出shape: {augmented.shape}")

# === 实战:Mini-batch采样 ===
def sample_mini_batch(data, labels, batch_size):
    """随机采样mini-batch"""
    n_samples = len(data)
    indices = np.random.choice(n_samples, size=batch_size, replace=False)
    return data[indices], labels[indices]

# 模拟数据集
X = np.random.randn(10000, 784)
y = np.random.randint(0, 10, 10000)

# 采样batch
batch_X, batch_y = sample_mini_batch(X, y, batch_size=32)
print(f"Batch X shape: {batch_X.shape}")
print(f"Batch y shape: {batch_y.shape}")

七、线性代数基础(np.linalg)

7.1 矩阵运算基础

python 复制代码
# 创建示例矩阵
A = np.array([[1, 2],
              [3, 4]])
B = np.array([[5, 6],
              [7, 8]])

# === 矩阵转置 ===
print(f"矩阵A:\n{A}")
print(f"A的转置:\n{A.T}")

# === 矩阵的迹(对角线之和)===
trace = np.trace(A)
print(f"\n矩阵的迹: {trace}")  # 1 + 4 = 5

# === 矩阵的行列式 ===
det = np.linalg.det(A)
print(f"行列式: {det:.2f}")  # 1*4 - 2*3 = -2

# 行列式的几何意义:线性变换的面积缩放因子
# |det| > 1: 面积放大;|det| < 1: 面积缩小

7.2 逆矩阵

python 复制代码
# === 逆矩阵:A @ A^(-1) = I ===
A = np.array([[2, 1],
              [5, 3]])

# 计算逆矩阵
A_inv = np.linalg.inv(A)
print(f"原矩阵A:\n{A}")
print(f"\n逆矩阵A⁻¹:\n{A_inv}")

# 验证
identity = A @ A_inv
print(f"\nA @ A⁻¹:\n{identity}")
print(f"是否为单位矩阵: {np.allclose(identity, np.eye(2))}")

# 求解线性方程组 Ax = b
# 例:2x + y = 8, 5x + 3y = 19
A = np.array([[2, 1],
              [5, 3]])
b = np.array([8, 19])

# 方法1:使用逆矩阵
x = np.linalg.inv(A) @ b
print(f"解 (x, y) = {x}")  # [5. -2.] 即 x=5, y=-2

# 方法2:使用solve(数值更稳定,推荐)
x = np.linalg.solve(A, b)
print(f"使用solve: {x}")

# 验证
print(f"Ax = {A @ x}, b = {b}")

# 注意:只有方阵且可逆才能求逆
# 不可逆矩阵(奇异矩阵)
singular = np.array([[1, 2],
                     [2, 4]])  # 第二行是第一行的2倍
try:
    inv_singular = np.linalg.inv(singular)
except np.linalg.LinAlgError as e:
    print(f"奇异矩阵无法求逆: {e}")

7.3 特征值与特征向量

python 复制代码
# 特征值分解:A·v = λ·v
# v是特征向量,λ是特征值(代表变换的缩放因子)

A = np.array([[4, 2],
              [1, 3]])

# 计算特征值和特征向量
eigenvalues, eigenvectors = np.linalg.eig(A)

print(f"矩阵A:\n{A}")
print(f"\n特征值: {eigenvalues}")
print(f"特征向量:\n{eigenvectors}")

# 验证:A·v = λ·v
for i in range(len(eigenvalues)):
    v = eigenvectors[:, i]
    lambda_v = eigenvalues[i] * v
    Av = A @ v
    print(f"\n验证特征向量{i+1}:")
    print(f"λ·v = {lambda_v}")
    print(f"A·v = {Av}")
    print(f"相等: {np.allclose(lambda_v, Av)}")

# 特征值的应用:主成分分析(PCA)原理
# 协方差矩阵的特征向量就是主成分方向

7.4 AI实战:线性回归的闭式解

python 复制代码
# 线性回归:y = X·w,最小二乘解 w = (X^T·X)^(-1)·X^T·y

# 生成合成数据
np.random.seed(42)
X = np.random.randn(100, 3)  # 100个样本,3个特征
true_w = np.array([2, -1, 0.5])
y = X @ true_w + np.random.randn(100) * 0.1  # 添加噪声

# === 最小二乘求解 ===
# 方法1:正规方程
w_hat = np.linalg.inv(X.T @ X) @ X.T @ y
print(f"真实权重: {true_w}")
print(f"估计权重: {w_hat}")
print(f"误差: {np.linalg.norm(true_w - w_hat):.6f}")

# 方法2:使用lstsq(更稳定)
w_hat_lstsq, residuals, rank, s = np.linalg.lstsq(X, y, rcond=None)
print(f"lstsq估计: {w_hat_lstsq}")

# === 预测 ===
X_new = np.random.randn(10, 3)
y_pred = X_new @ w_hat
print(f"\n预测值前3个: {y_pred[:3]}")

# === 计算R²分数 ===
y_pred_train = X @ w_hat
ss_res = np.sum((y - y_pred_train) ** 2)
ss_tot = np.sum((y - np.mean(y)) ** 2)
r2 = 1 - (ss_res / ss_tot)
print(f"R²分数: {r2:.4f}")  # 接近1表示拟合良好

八、实战项目:从零实现KNN分类器

综合运用NumPy所有知识点的完整示例:

python 复制代码
import numpy as np
from collections import Counter

class KNNClassifier:
    """
    K近邻分类器 - 使用NumPy实现
    原理:一个样本的类别由它最近的K个邻居投票决定
    """
    
    def __init__(self, k=3):
        self.k = k
        self.X_train = None
        self.y_train = None
    
    def fit(self, X, y):
        """训练:只保存数据"""
        self.X_train = X
        self.y_train = y
    
    def _compute_distances(self, X_test):
        """
        计算测试集与训练集的距离矩阵
        利用广播机制高效计算欧氏距离
        """
        # X_test: (n_test, n_features)
        # X_train: (n_train, n_features)
        
        # 平方和:|a-b|^2 = a^2 + b^2 - 2ab
        # (n_test, 1, n_features) vs (1, n_train, n_features)
        diff = X_test[:, np.newaxis, :] - self.X_train[np.newaxis, :, :]
        distances = np.sqrt(np.sum(diff ** 2, axis=2))
        
        return distances  # shape: (n_test, n_train)
    
    def predict(self, X_test):
        """预测类别"""
        # 1. 计算距离
        distances = self._compute_distances(X_test)
        
        # 2. 找到最近的K个邻居的索引
        # np.argsort返回排序后的索引
        k_nearest_indices = np.argsort(distances, axis=1)[:, :self.k]
        
        # 3. 获取这些邻居的标签
        k_nearest_labels = self.y_train[k_nearest_indices]
        
        # 4. 投票决定类别
        predictions = []
        for labels in k_nearest_labels:
            # 统计每个标签出现的次数
            vote_counts = Counter(labels)
            # 选择出现最多的标签
            most_common = vote_counts.most_common(1)[0][0]
            predictions.append(most_common)
        
        return np.array(predictions)
    
    def predict_proba(self, X_test):
        """预测概率分布"""
        distances = self._compute_distances(X_test)
        k_nearest_indices = np.argsort(distances, axis=1)[:, :self.k]
        k_nearest_labels = self.y_train[k_nearest_indices]
        
        # 计算每个类别的概率
        n_classes = len(np.unique(self.y_train))
        probas = []
        for labels in k_nearest_labels:
            counts = np.bincount(labels, minlength=n_classes)
            probas.append(counts / self.k)
        
        return np.array(probas)
    
    def accuracy(self, X_test, y_test):
        """计算准确率"""
        y_pred = self.predict(X_test)
        return np.mean(y_pred == y_test)


# === 生成测试数据 ===
np.random.seed(42)

def generate_data(n_samples=300):
    """生成三类数据,每类100个样本"""
    X = []
    y = []
    
    # 类别0:以(0,0)为中心
    X0 = np.random.randn(100, 2) + np.array([0, 0])
    y0 = np.zeros(100, dtype=int)
    
    # 类别1:以(3,0)为中心
    X1 = np.random.randn(100, 2) + np.array([3, 0])
    y1 = np.ones(100, dtype=int)
    
    # 类别2:以(1.5, 3)为中心
    X2 = np.random.randn(100, 2) * 0.8 + np.array([1.5, 3])
    y2 = np.full(100, 2, dtype=int)
    
    X = np.vstack([X0, X1, X2])
    y = np.hstack([y0, y1, y2])
    
    # 打乱数据
    indices = np.random.permutation(len(X))
    return X[indices], y[indices]

# === 训练和评估 ===
print("=" * 60)
print("KNN分类器实战")
print("=" * 60)

# 生成数据
X, y = generate_data(300)
print(f"数据集大小: {X.shape}")
print(f"类别分布: {Counter(y)}")

# 划分训练集和测试集
split_ratio = 0.8
split_idx = int(len(X) * split_ratio)
X_train, X_test = X[:split_idx], X[split_idx:]
y_train, y_test = y[:split_idx], y[split_idx:]

print(f"\n训练集: {len(X_train)}, 测试集: {len(X_test)}")

# 训练模型
knn = KNNClassifier(k=5)
knn.fit(X_train, y_train)

# 预测
y_pred = knn.predict(X_test)
accuracy = knn.accuracy(X_test, y_test)

print(f"\n预测结果前10个: {y_pred[:10]}")
print(f"真实标签前10个: {y_test[:10]}")
print(f"准确率: {accuracy:.4f}")

# 预测概率
probas = knn.predict_proba(X_test[:5])
print(f"\n前5个样本的概率分布:")
for i, prob in enumerate(probas):
    print(f"  样本{i}: 类别0={prob[0]:.2f}, 类别1={prob[1]:.2f}, 类别2={prob[2]:.2f}")

# === 不同K值的影响 ===
print("\n" + "=" * 60)
print("K值对准确率的影响")
print("=" * 60)

for k in [1, 3, 5, 7, 9, 11, 15, 20]:
    knn = KNNClassifier(k=k)
    knn.fit(X_train, y_train)
    acc = knn.accuracy(X_test, y_test)
    print(f"K={k:2d}, 准确率={acc:.4f}")

# === 可视化决策边界(简化版)===
print("\n" + "=" * 60)
print("决策边界分析")
print("=" * 60)

# 创建网格点
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.linspace(x_min, x_max, 50),
                     np.linspace(y_min, y_max, 50))

# 预测网格点的类别
grid_points = np.c_[xx.ravel(), yy.ravel()]
knn = KNNClassifier(k=5)
knn.fit(X_train, y_train)
grid_pred = knn.predict(grid_points)
grid_pred = grid_pred.reshape(xx.shape)

# 计算边界清晰度(预测熵)
# 熵值越高,边界越模糊
from scipy.stats import entropy
unique, counts = np.unique(grid_pred, return_counts=True)
if len(unique) > 1:
    probs = counts / counts.sum()
    boundary_entropy = entropy(probs)
    print(f"决策边界熵: {boundary_entropy:.4f}")
    print("熵值越小,类别分离越清晰")

print("\nKNN实现完成!✅")

九、性能优化技巧

9.1 向量化 vs 循环

python 复制代码
# 创建数据
arr = np.random.randn(1000000)

# ❌ 慢:Python循环
def add_one_loop(arr):
    result = []
    for x in arr:
        result.append(x + 1)
    return np.array(result)

# ✅ 快:NumPy向量化
def add_one_vectorized(arr):
    return arr + 1

# 性能对比
import time

start = time.time()
result_loop = add_one_loop(arr)
time_loop = time.time() - start

start = time.time()
result_vec = add_one_vectorized(arr)
time_vec = time.time() - start

print(f"循环耗时: {time_loop:.4f}秒")
print(f"向量化耗时: {time_vec:.4f}秒")
print(f"加速比: {time_loop / time_vec:.1f}x")

# 结论:永远使用向量化操作!

9.2 内存优化

python 复制代码
# 1. 选择合适的数据类型
arr_float64 = np.random.randn(1000000)
arr_float32 = arr_float64.astype(np.float32)

print(f"float64内存: {arr_float64.nbytes / 1024 / 1024:.2f} MB")
print(f"float32内存: {arr_float32.nbytes / 1024 / 1024:.2f} MB")

# 2. 使用视图而非副本
arr = np.arange(1000000)

# 视图(不复制内存)
view = arr[::2]  # 只改变步长,不复制数据
print(f"视图是视图: {view.base is arr}")  # True

# 副本(复制内存)
copy = arr[::2].copy()
print(f"副本是视图: {copy.base is arr}")  # False

# 3. 原地操作(避免创建新数组)
arr = np.random.randn(1000)

# 创建新数组
arr_new = arr + 1

# 原地操作(更快,省内存)
arr += 1

# 4. 使用np.empty而非np.zeros
# 如果后续会填充所有值,用empty更快
arr = np.empty((1000, 1000))
arr[:] = 42  # 填充值

十、常见错误与解决方案

错误 原因 解决方案
ValueError: operands could not be broadcast together 形状不兼容 检查shape,使用reshapenewaxis
MemoryError 数组太大 使用分块处理或更小的dtype
LinAlgError: Singular matrix 矩阵不可逆 使用np.linalg.lstsq代替inv
浮点精度问题 数值计算误差 使用np.allclose()比较浮点数
修改了视图影响原数组 忘记.copy() 明确使用.copy()创建副本
python 复制代码
# 错误示例与修正
# 错误1:广播错误
A = np.random.rand(3, 4)
B = np.random.rand(3, 5)
try:
    C = A + B
except ValueError as e:
    print(f"错误: {e}")
# 修正:检查维度
print(f"A shape: {A.shape}, B shape: {B.shape}")

# 错误2:浮点数比较
a = 0.1 + 0.2
b = 0.3
print(f"直接比较: {a == b}")  # False!
print(f"正确比较: {np.allclose(a, b)}")  # True

# 错误3:忘记copy导致意外修改
original = np.array([1, 2, 3])
view = original[:2]
view[0] = 999
print(f"原数组被修改: {original}")  # [999, 2, 3]

# 修正
original = np.array([1, 2, 3])
copy = original[:2].copy()
copy[0] = 999
print(f"原数组未修改: {original}")  # [1, 2, 3]

十一、学习检查清单

基础掌握(必须)

  • 创建ndarray(array, zeros, ones, arange, linspace
  • 查看属性(shape, dtype, ndim, size
  • 索引和切片(包括步长、负索引)
  • 改变形状(reshape, ravel, flatten
  • 数组运算(+, -, *, /, **
  • 统计函数(sum, mean, std, min, max

进阶掌握(重要)

  • 广播机制的理解和应用
  • 布尔索引和条件筛选
  • 轴参数(axis=0/1)的理解
  • 随机数生成和种子设置
  • 矩阵乘法(@, dot

扩展了解(按需)

  • 线性代数(inv, det, eig, solve
  • 文件读写(loadtxt, savetxt, save, load
  • 高级索引(花式索引)
  • 通用函数(ufunc)

十二、总结

NumPy的核心价值:

  1. ndarray:高效的多维数组容器
  2. 向量化:用C速度执行Python级别的运算
  3. 广播:优雅处理不同形状的数组
  4. ufunc:丰富的数学函数库
  5. 随机数:完整的数据采样工具
  6. 线性代数:AI算法的基础支撑

学习建议:

  • 先用10个核心函数(array, reshape, mean, std, dot, random, where, concatenate, split, transpose
  • 理解shape和broadcasting(这是NumPy的灵魂)
  • 能用向量化就别用循环
  • 遇到问题先查shape

下一步:

  • 学习Pandas(基于NumPy的数据分析库)
  • 学习Matplotlib(数据可视化)
  • 学习PyTorch/TensorFlow(GPU加速的张量计算)

记住:NumPy是AI开发的数学引擎,掌握NumPy就等于掌握了AI数据处理的核心。

相关推荐
DaqunChen2 小时前
PHP怎么合并数组_array_merge函数指南【指南】
jvm·数据库·python
Dfreedom.2 小时前
聚类算法对比分析:K-Means、DBSCAN 与层次聚类
人工智能·算法·机器学习·kmeans·聚类
InfinteJustice2 小时前
如何在 Laravel Excel 导入时检测并阻止重复列值
jvm·数据库·python
2301_777599372 小时前
Quill 编辑器光标意外跳转至顶部的解决方案
jvm·数据库·python
weixin_586061462 小时前
Quill 编辑器光标跳转到顶部的解决方案
jvm·数据库·python
Hacker_seagull2 小时前
Sqlmap 工具保姆级使用教程
经验分享·python·web安全·网络安全
2301_782659182 小时前
Go 中使用 go-json-rest 时调用 Write 方法的正确方式
jvm·数据库·python