📚 本章概述

本章将深入探讨NumPy中的数组形状操作和广播机制,这是NumPy最强大和最重要的特性之一。您将学习如何灵活地改变数组形状、理解广播的工作原理,以及掌握数组的拼接和分割技术。

🎯 学习目标

  • 掌握数组形状变换的各种方法
  • 深入理解广播机制的原理和应用
  • 学会数组的拼接、分割和堆叠操作
  • 了解数组的转置和轴操作
  • 掌握高效的数组重塑技巧

1. 数组形状基础

1.1 理解数组形状

import numpy as np
import matplotlib.pyplot as plt

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

print("📐 数组形状基础")
print("=" * 50)

# 创建不同形状的数组
arr_1d = np.array([1, 2, 3, 4, 5, 6])
arr_2d = np.array([[1, 2, 3], [4, 5, 6]])
arr_3d = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])

print("一维数组:")
print(f"数组: {arr_1d}")
print(f"形状: {arr_1d.shape}")
print(f"维度: {arr_1d.ndim}")
print(f"大小: {arr_1d.size}")

print("\n二维数组:")
print(f"数组:\n{arr_2d}")
print(f"形状: {arr_2d.shape}")
print(f"维度: {arr_2d.ndim}")
print(f"大小: {arr_2d.size}")

print("\n三维数组:")
print(f"数组:\n{arr_3d}")
print(f"形状: {arr_3d.shape}")
print(f"维度: {arr_3d.ndim}")
print(f"大小: {arr_3d.size}")

1.2 形状属性详解

# 1. 形状属性的含义
print("\n🔍 形状属性详解:")

# 创建示例数组
matrix = np.random.randint(1, 10, (3, 4, 5))
print(f"数组形状: {matrix.shape}")
print(f"解释: {matrix.shape[0]}个二维数组,每个{matrix.shape[1]}行{matrix.shape[2]}列")

# 各种形状的数组
shapes_examples = [
    (10,),          # 一维数组:10个元素
    (3, 4),         # 二维数组:3行4列
    (2, 3, 4),      # 三维数组:2个3x4的矩阵
    (2, 3, 4, 5),   # 四维数组:2个3x4x5的立体
]

for shape in shapes_examples:
    arr = np.zeros(shape)
    print(f"形状 {shape}: {arr.ndim}维数组,总元素 {arr.size} 个")

2. 数组重塑操作

2.1 reshape - 改变形状

# 2. reshape操作
print("\n🔄 reshape操作:")

# 创建一维数组
original = np.arange(12)
print(f"原始数组: {original}")
print(f"原始形状: {original.shape}")

# 重塑为不同形状
reshaped_2d = original.reshape(3, 4)
print(f"\n重塑为3x4:")
print(reshaped_2d)

reshaped_3d = original.reshape(2, 2, 3)
print(f"\n重塑为2x2x3:")
print(reshaped_3d)

# 使用-1自动计算维度
auto_reshape = original.reshape(4, -1)
print(f"\n自动计算维度 (4, -1):")
print(auto_reshape)
print(f"实际形状: {auto_reshape.shape}")

# reshape不改变原数组
print(f"\n原数组是否改变: {original.shape}")
print("注意: reshape返回视图,不复制数据")

2.2 resize - 就地改变形状

# 3. resize操作
print("\n📏 resize操作:")

# resize会就地修改数组
arr = np.arange(6)
print(f"原始数组: {arr}")

# 注意:resize会修改原数组
arr_copy = arr.copy()
arr_copy.resize(2, 3)
print(f"resize后:")
print(arr_copy)

# resize可以改变数组大小
arr2 = np.arange(4)
print(f"\n原始数组: {arr2}")
arr2.resize(6)  # 扩大数组
print(f"扩大后: {arr2}")  # 新元素用0填充

# 缩小数组
arr3 = np.arange(10)
print(f"\n原始数组: {arr3}")
arr3.resize(5)  # 缩小数组
print(f"缩小后: {arr3}")

2.3 flatten和ravel - 展平数组

# 4. 数组展平
print("\n📏 数组展平:")

matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print("原始矩阵:")
print(matrix)

# flatten - 返回副本
flattened = matrix.flatten()
print(f"\nflatten结果: {flattened}")
flattened[0] = 999
print(f"修改flatten结果后,原矩阵第一个元素: {matrix[0, 0]}")

# ravel - 返回视图(如果可能)
raveled = matrix.ravel()
print(f"\nravel结果: {raveled}")
raveled[0] = 888
print(f"修改ravel结果后,原矩阵第一个元素: {matrix[0, 0]}")

# 按不同顺序展平
matrix_reset = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(f"\n按行展平 (C顺序): {matrix_reset.flatten('C')}")
print(f"按列展平 (F顺序): {matrix_reset.flatten('F')}")

3. 广播机制

3.1 广播基础概念

# 5. 广播机制基础
print("\n📡 广播机制基础:")

# 标量与数组的广播
arr = np.array([1, 2, 3, 4])
scalar = 10

result = arr + scalar
print(f"数组: {arr}")
print(f"标量: {scalar}")
print(f"广播结果: {result}")

# 不同形状数组的广播
arr_2d = np.array([[1, 2, 3], [4, 5, 6]])
arr_1d = np.array([10, 20, 30])

print(f"\n二维数组 {arr_2d.shape}:")
print(arr_2d)
print(f"一维数组 {arr_1d.shape}: {arr_1d}")

result_broadcast = arr_2d + arr_1d
print(f"广播结果 {result_broadcast.shape}:")
print(result_broadcast)

3.2 广播规则详解

# 6. 广播规则详解
print("\n📋 广播规则详解:")

def show_broadcast_example(a_shape, b_shape):
    """演示广播示例"""
    try:
        a = np.ones(a_shape)
        b = np.ones(b_shape)
        result = a + b
        print(f"✅ {a_shape} + {b_shape} = {result.shape}")
        return True
    except ValueError as e:
        print(f"❌ {a_shape} + {b_shape} = 错误: {e}")
        return False

print("广播规则测试:")
# 规则1: 从右向左比较维度
show_broadcast_example((3, 4), (4,))      # 可以广播
show_broadcast_example((3, 4), (3, 4))    # 形状相同
show_broadcast_example((3, 4), (1, 4))    # 可以广播
show_broadcast_example((3, 4), (3, 1))    # 可以广播

# 规则2: 维度为1可以广播
show_broadcast_example((3, 1), (1, 4))    # 可以广播
show_broadcast_example((1, 3), (4, 1))    # 可以广播

# 不能广播的情况
show_broadcast_example((3, 4), (2, 4))    # 不能广播
show_broadcast_example((3, 4), (3, 2))    # 不能广播

3.3 广播的实际应用

# 7. 广播的实际应用
print("\n🎯 广播的实际应用:")

# 应用1: 数据标准化
print("应用1: 数据标准化")
data = np.random.normal(100, 15, (5, 3))  # 5个样本,3个特征
print("原始数据:")
print(data)

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

print(f"\n均值: {mean}")
print(f"标准差: {std}")

# 标准化 (利用广播)
standardized = (data - mean) / std
print(f"\n标准化后的数据:")
print(standardized)

# 验证标准化结果
print(f"标准化后均值: {np.mean(standardized, axis=0)}")
print(f"标准化后标准差: {np.std(standardized, axis=0)}")

# 应用2: 距离计算
print(f"\n应用2: 点到点距离计算")
points_a = np.array([[1, 2], [3, 4], [5, 6]])  # 3个点
points_b = np.array([[0, 0], [1, 1]])           # 2个点

print(f"点集A {points_a.shape}:")
print(points_a)
print(f"点集B {points_b.shape}:")
print(points_b)

# 计算所有点对之间的距离
# 使用广播计算欧几里得距离
diff = points_a[:, np.newaxis, :] - points_b[np.newaxis, :, :]
distances = np.sqrt(np.sum(diff**2, axis=2))

print(f"\n距离矩阵 {distances.shape}:")
print(distances)
print("解释: distances[i,j] 是点A[i]到点B[j]的距离")

3.4 广播的性能优势

# 8. 广播的性能优势
print("\n⚡ 广播的性能优势:")

import time

# 创建大数组
large_array = np.random.random((1000, 1000))
vector = np.random.random(1000)

# 方法1: 使用循环
def loop_method(arr, vec):
    result = np.zeros_like(arr)
    for i in range(arr.shape[0]):
        result[i] = arr[i] + vec
    return result

# 方法2: 使用广播
def broadcast_method(arr, vec):
    return arr + vec

# 性能测试
start = time.time()
result1 = loop_method(large_array[:100], vector)  # 只测试100行
loop_time = time.time() - start

start = time.time()
result2 = broadcast_method(large_array, vector)
broadcast_time = time.time() - start

print(f"循环方法时间 (100行): {loop_time:.6f}秒")
print(f"广播方法时间 (1000行): {broadcast_time:.6f}秒")
print(f"广播方法速度提升: {loop_time/broadcast_time*10:.1f}倍")

# 验证结果一致性
print(f"结果是否一致: {np.allclose(result1, result2[:100])}")

4. 数组拼接操作

4.1 concatenate - 通用拼接

# 9. 数组拼接
print("\n🔗 数组拼接操作:")

# 一维数组拼接
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
arr3 = np.array([7, 8, 9])

concat_1d = np.concatenate([arr1, arr2, arr3])
print(f"一维数组拼接:")
print(f"arr1: {arr1}")
print(f"arr2: {arr2}")
print(f"arr3: {arr3}")
print(f"拼接结果: {concat_1d}")

# 二维数组拼接
matrix1 = np.array([[1, 2], [3, 4]])
matrix2 = np.array([[5, 6], [7, 8]])

print(f"\n二维数组拼接:")
print(f"matrix1:\n{matrix1}")
print(f"matrix2:\n{matrix2}")

# 按行拼接 (axis=0)
concat_rows = np.concatenate([matrix1, matrix2], axis=0)
print(f"按行拼接 (axis=0):\n{concat_rows}")

# 按列拼接 (axis=1)
concat_cols = np.concatenate([matrix1, matrix2], axis=1)
print(f"按列拼接 (axis=1):\n{concat_cols}")

4.2 专用拼接函数

# 10. 专用拼接函数
print("\n🎯 专用拼接函数:")

a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])

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

# vstack - 垂直堆叠
vstack_result = np.vstack([a, b])
print(f"\nvstack (垂直堆叠):\n{vstack_result}")

# hstack - 水平堆叠
hstack_result = np.hstack([a, b])
print(f"\nhstack (水平堆叠):\n{hstack_result}")

# dstack - 深度堆叠
dstack_result = np.dstack([a, b])
print(f"\ndstack (深度堆叠) 形状: {dstack_result.shape}")
print(f"dstack 结果:\n{dstack_result}")

# column_stack - 列堆叠
col1 = np.array([1, 2, 3])
col2 = np.array([4, 5, 6])
col3 = np.array([7, 8, 9])

column_result = np.column_stack([col1, col2, col3])
print(f"\ncolumn_stack:")
print(f"输入: {col1}, {col2}, {col3}")
print(f"结果:\n{column_result}")

# row_stack - 行堆叠 (等同于vstack)
row_result = np.row_stack([col1, col2, col3])
print(f"\nrow_stack:\n{row_result}")

4.3 高维数组拼接

# 11. 高维数组拼接
print("\n📦 高维数组拼接:")

# 创建三维数组
arr_3d_1 = np.random.randint(1, 10, (2, 3, 4))
arr_3d_2 = np.random.randint(10, 20, (2, 3, 4))

print(f"三维数组1形状: {arr_3d_1.shape}")
print(f"三维数组2形状: {arr_3d_2.shape}")

# 沿不同轴拼接
for axis in range(3):
    result = np.concatenate([arr_3d_1, arr_3d_2], axis=axis)
    print(f"沿axis={axis}拼接,结果形状: {result.shape}")

# stack - 创建新维度
print(f"\nstack操作:")
stack_result = np.stack([arr_3d_1, arr_3d_2], axis=0)
print(f"stack沿axis=0,结果形状: {stack_result.shape}")

stack_result = np.stack([arr_3d_1, arr_3d_2], axis=1)
print(f"stack沿axis=1,结果形状: {stack_result.shape}")

5. 数组分割操作

5.1 split - 通用分割

# 12. 数组分割
print("\n✂️ 数组分割操作:")

# 一维数组分割
arr = np.arange(12)
print(f"原始数组: {arr}")

# 等分分割
split_equal = np.split(arr, 3)
print(f"等分为3部分: {split_equal}")

# 指定分割点
split_points = np.split(arr, [3, 7])
print(f"在位置3,7分割: {split_points}")

# 二维数组分割
matrix = np.arange(12).reshape(4, 3)
print(f"\n原始矩阵:\n{matrix}")

# 按行分割
row_splits = np.split(matrix, 2, axis=0)
print(f"按行分割为2部分:")
for i, part in enumerate(row_splits):
    print(f"部分{i+1}:\n{part}")

# 按列分割
col_splits = np.split(matrix, 3, axis=1)
print(f"\n按列分割为3部分:")
for i, part in enumerate(col_splits):
    print(f"部分{i+1}:\n{part}")

5.2 专用分割函数

# 13. 专用分割函数
print("\n🎯 专用分割函数:")

matrix = np.random.randint(1, 100, (6, 8))
print(f"原始矩阵形状: {matrix.shape}")
print(f"原始矩阵:\n{matrix}")

# vsplit - 垂直分割
vsplit_result = np.vsplit(matrix, 3)
print(f"\nvsplit分割为3部分:")
for i, part in enumerate(vsplit_result):
    print(f"部分{i+1}形状: {part.shape}")

# hsplit - 水平分割
hsplit_result = np.hsplit(matrix, 4)
print(f"\nhsplit分割为4部分:")
for i, part in enumerate(hsplit_result):
    print(f"部分{i+1}形状: {part.shape}")

# array_split - 不等分分割
unequal_split = np.array_split(np.arange(10), 3)
print(f"\n不等分分割:")
for i, part in enumerate(unequal_split):
    print(f"部分{i+1}: {part}")

5.3 分割的实际应用

# 14. 分割的实际应用
print("\n🎯 分割的实际应用:")

# 应用1: 数据集分割
print("应用1: 训练/验证/测试集分割")
data = np.random.random((1000, 10))  # 1000个样本,10个特征
labels = np.random.randint(0, 2, 1000)  # 二分类标签

# 按比例分割: 70% 训练, 20% 验证, 10% 测试
train_size = int(0.7 * len(data))
val_size = int(0.2 * len(data))

train_data = data[:train_size]
val_data = data[train_size:train_size+val_size]
test_data = data[train_size+val_size:]

train_labels = labels[:train_size]
val_labels = labels[train_size:train_size+val_size]
test_labels = labels[train_size+val_size:]

print(f"训练集: {train_data.shape}, 标签: {train_labels.shape}")
print(f"验证集: {val_data.shape}, 标签: {val_labels.shape}")
print(f"测试集: {test_data.shape}, 标签: {test_labels.shape}")

# 应用2: 批处理
print(f"\n应用2: 批处理")
batch_size = 32
num_batches = len(train_data) // batch_size

print(f"总样本数: {len(train_data)}")
print(f"批大小: {batch_size}")
print(f"批数量: {num_batches}")

# 创建批次
batches = np.array_split(train_data[:num_batches*batch_size], num_batches)
print(f"每批形状: {batches[0].shape}")
print(f"最后一批形状: {batches[-1].shape}")

6. 转置和轴操作

6.1 转置操作

# 15. 转置操作
print("\n🔄 转置操作:")

# 二维数组转置
matrix = np.array([[1, 2, 3], [4, 5, 6]])
print(f"原始矩阵 {matrix.shape}:")
print(matrix)

# 方法1: .T属性
transpose1 = matrix.T
print(f"\n转置结果1 (.T) {transpose1.shape}:")
print(transpose1)

# 方法2: transpose()方法
transpose2 = matrix.transpose()
print(f"\n转置结果2 (.transpose()) {transpose2.shape}:")
print(transpose2)

# 方法3: np.transpose()函数
transpose3 = np.transpose(matrix)
print(f"\n转置结果3 (np.transpose()) {transpose3.shape}:")
print(transpose3)

# 高维数组转置
arr_3d = np.random.randint(1, 10, (2, 3, 4))
print(f"\n三维数组原始形状: {arr_3d.shape}")

# 默认转置(反转所有轴)
default_transpose = arr_3d.T
print(f"默认转置形状: {default_transpose.shape}")

# 指定轴的转置
custom_transpose = np.transpose(arr_3d, (2, 0, 1))
print(f"自定义转置 (2,0,1) 形状: {custom_transpose.shape}")

6.2 轴操作函数

# 16. 轴操作函数
print("\n🎯 轴操作函数:")

# swapaxes - 交换轴
arr = np.random.randint(1, 10, (2, 3, 4))
print(f"原始数组形状: {arr.shape}")

swapped = np.swapaxes(arr, 0, 2)
print(f"交换轴0和轴2后形状: {swapped.shape}")

# moveaxis - 移动轴
moved = np.moveaxis(arr, 0, -1)
print(f"移动轴0到最后形状: {moved.shape}")

# rollaxis - 滚动轴
rolled = np.rollaxis(arr, 2, 0)
print(f"滚动轴2到位置0形状: {rolled.shape}")

# 实际应用:图像数据格式转换
print(f"\n实际应用:图像数据格式转换")
# 假设图像数据格式为 (height, width, channels)
image_hwc = np.random.randint(0, 256, (224, 224, 3))
print(f"HWC格式: {image_hwc.shape}")

# 转换为 (channels, height, width) 格式(深度学习常用)
image_chw = np.transpose(image_hwc, (2, 0, 1))
print(f"CHW格式: {image_chw.shape}")

# 批量图像:从 (batch, height, width, channels) 到 (batch, channels, height, width)
batch_images_bhwc = np.random.randint(0, 256, (32, 224, 224, 3))
batch_images_bchw = np.transpose(batch_images_bhwc, (0, 3, 1, 2))
print(f"批量图像 BHWC: {batch_images_bhwc.shape}")
print(f"批量图像 BCHW: {batch_images_bchw.shape}")

7. 高级形状操作

7.1 squeeze和expand_dims

# 17. 维度压缩和扩展
print("\n📏 维度压缩和扩展:")

# squeeze - 压缩长度为1的维度
arr_with_ones = np.random.random((1, 5, 1, 3, 1))
print(f"原始形状: {arr_with_ones.shape}")

squeezed = np.squeeze(arr_with_ones)
print(f"squeeze后形状: {squeezed.shape}")

# 指定压缩特定轴
squeezed_axis = np.squeeze(arr_with_ones, axis=0)
print(f"squeeze轴0后形状: {squeezed_axis.shape}")

# expand_dims - 扩展维度
arr_2d = np.random.random((3, 4))
print(f"\n原始2D数组形状: {arr_2d.shape}")

# 在不同位置添加维度
expanded_0 = np.expand_dims(arr_2d, axis=0)
print(f"在轴0扩展: {expanded_0.shape}")

expanded_1 = np.expand_dims(arr_2d, axis=1)
print(f"在轴1扩展: {expanded_1.shape}")

expanded_minus1 = np.expand_dims(arr_2d, axis=-1)
print(f"在最后扩展: {expanded_minus1.shape}")

# 实际应用:为广播准备数组
print(f"\n实际应用:为广播准备数组")
vector = np.array([1, 2, 3, 4])
matrix = np.random.random((3, 4))

print(f"向量形状: {vector.shape}")
print(f"矩阵形状: {matrix.shape}")

# 将向量变为列向量以便广播
vector_col = np.expand_dims(vector, axis=0)
print(f"向量变为行向量: {vector_col.shape}")

result = matrix + vector_col
print(f"广播结果形状: {result.shape}")

7.2 newaxis的使用

# 18. newaxis的使用
print("\n🆕 newaxis的使用:")

arr = np.array([1, 2, 3, 4])
print(f"原始数组: {arr}, 形状: {arr.shape}")

# newaxis等同于None,用于添加新轴
arr_row = arr[np.newaxis, :]
print(f"添加行维度: {arr_row}, 形状: {arr_row.shape}")

arr_col = arr[:, np.newaxis]
print(f"添加列维度:\n{arr_col}, 形状: {arr_col.shape}")

# 多个newaxis
arr_3d = arr[np.newaxis, :, np.newaxis]
print(f"添加多个维度形状: {arr_3d.shape}")

# 实际应用:矩阵运算
print(f"\n实际应用:外积计算")
a = np.array([1, 2, 3])
b = np.array([4, 5, 6, 7])

# 计算外积
outer_product = a[:, np.newaxis] * b[np.newaxis, :]
print(f"向量a: {a}")
print(f"向量b: {b}")
print(f"外积:\n{outer_product}")

8. 实际应用案例

8.1 图像处理应用

# 19. 图像处理应用
print("\n🖼️ 图像处理应用:")

# 模拟RGB图像数据
np.random.seed(42)
image = np.random.randint(0, 256, (100, 100, 3), dtype=np.uint8)
print(f"原始图像形状: {image.shape} (高度, 宽度, 通道)")

# 1. 图像转置(旋转90度)
rotated = np.transpose(image, (1, 0, 2))
print(f"旋转后形状: {rotated.shape}")

# 2. 通道分离
red_channel = image[:, :, 0]
green_channel = image[:, :, 1]
blue_channel = image[:, :, 2]
print(f"红色通道形状: {red_channel.shape}")

# 3. 灰度转换(使用广播)
gray_weights = np.array([0.299, 0.587, 0.114])
gray_image = np.sum(image * gray_weights, axis=2)
print(f"灰度图像形状: {gray_image.shape}")

# 4. 批量处理
batch_size = 8
batch_images = np.random.randint(0, 256, (batch_size, 100, 100, 3))
print(f"批量图像形状: {batch_images.shape}")

# 批量灰度转换
batch_gray = np.sum(batch_images * gray_weights, axis=3)
print(f"批量灰度图像形状: {batch_gray.shape}")

# 5. 图像拼接(创建图像网格)
grid_images = batch_images[:4].reshape(2, 2, 100, 100, 3)
# 重新排列为网格
grid = grid_images.transpose(0, 2, 1, 3, 4).reshape(200, 200, 3)
print(f"图像网格形状: {grid.shape}")

8.2 数据分析应用

# 20. 数据分析应用
print("\n📊 数据分析应用:")

# 模拟时间序列数据
np.random.seed(42)
days = 365
features = 5
time_series = np.random.normal(100, 10, (days, features))

print(f"时间序列数据形状: {time_series.shape} (天数, 特征数)")

# 1. 计算移动平均(使用形状操作)
window_size = 7
# 创建滑动窗口
windows = np.array([time_series[i:i+window_size] 
                   for i in range(days-window_size+1)])
print(f"滑动窗口形状: {windows.shape}")

# 计算移动平均
moving_avg = np.mean(windows, axis=1)
print(f"移动平均形状: {moving_avg.shape}")

# 2. 数据重塑用于机器学习
# 将时间序列转换为监督学习格式
def create_sequences(data, seq_length):
    sequences = []
    targets = []
    for i in range(len(data) - seq_length):
        sequences.append(data[i:i+seq_length])
        targets.append(data[i+seq_length])
    return np.array(sequences), np.array(targets)

seq_length = 10
X, y = create_sequences(time_series, seq_length)
print(f"序列数据形状: {X.shape} (样本数, 序列长度, 特征数)")
print(f"目标数据形状: {y.shape} (样本数, 特征数)")

# 3. 数据标准化(使用广播)
mean = np.mean(X, axis=(0, 1), keepdims=True)
std = np.std(X, axis=(0, 1), keepdims=True)
X_normalized = (X - mean) / std
print(f"标准化后数据形状: {X_normalized.shape}")
print(f"标准化后均值: {np.mean(X_normalized, axis=(0, 1))}")
print(f"标准化后标准差: {np.std(X_normalized, axis=(0, 1))}")

9. 性能优化技巧

9.1 内存效率

# 21. 内存效率优化
print("\n💾 内存效率优化:")

# 1. 视图 vs 副本
original = np.arange(1000000)
print(f"原始数组内存使用: {original.nbytes / 1024 / 1024:.2f} MB")

# reshape创建视图(不复制数据)
reshaped = original.reshape(1000, 1000)
print(f"reshape是否共享内存: {np.shares_memory(original, reshaped)}")

# flatten创建副本
flattened = reshaped.flatten()
print(f"flatten是否共享内存: {np.shares_memory(reshaped, flattened)}")

# ravel尽可能创建视图
raveled = reshaped.ravel()
print(f"ravel是否共享内存: {np.shares_memory(reshaped, raveled)}")

# 2. 就地操作
arr = np.random.random(1000000)
print(f"\n就地操作示例:")
print(f"操作前内存地址: {id(arr)}")

# 就地操作不创建新数组
arr *= 2
print(f"就地乘法后内存地址: {id(arr)}")

# 非就地操作创建新数组
arr_new = arr * 2
print(f"非就地乘法后内存地址: {id(arr_new)}")

9.2 计算效率

# 22. 计算效率优化
print("\n⚡ 计算效率优化:")

import time

# 创建大数组
size = 1000000
a = np.random.random(size)
b = np.random.random(size)

# 方法1: 多步操作
start = time.time()
temp1 = a + b
temp2 = temp1 * 2
result1 = np.sqrt(temp2)
time1 = time.time() - start

# 方法2: 链式操作
start = time.time()
result2 = np.sqrt((a + b) * 2)
time2 = time.time() - start

print(f"多步操作时间: {time1:.6f}秒")
print(f"链式操作时间: {time2:.6f}秒")
print(f"结果是否相同: {np.allclose(result1, result2)}")

# 广播 vs 循环的性能对比
matrix = np.random.random((1000, 1000))
vector = np.random.random(1000)

# 广播方法
start = time.time()
result_broadcast = matrix + vector
broadcast_time = time.time() - start

# 循环方法
start = time.time()
result_loop = np.zeros_like(matrix)
for i in range(matrix.shape[0]):
    result_loop[i] = matrix[i] + vector
loop_time = time.time() - start

print(f"\n广播方法时间: {broadcast_time:.6f}秒")
print(f"循环方法时间: {loop_time:.6f}秒")
print(f"广播方法速度提升: {loop_time/broadcast_time:.1f}倍")

10. 本章小结

10.1 核心知识点

  1. 数组形状操作

    • reshape: 改变数组形状
    • resize: 就地改变形状和大小
    • flatten/ravel: 数组展平
  2. 广播机制

    • 自动处理不同形状数组的运算
    • 从右向左比较维度
    • 维度为1可以扩展
  3. 数组拼接

    • concatenate: 通用拼接函数
    • vstack/hstack: 垂直/水平堆叠
    • stack: 创建新维度
  4. 数组分割

    • split: 通用分割函数
    • vsplit/hsplit: 垂直/水平分割
    • array_split: 不等分分割
  5. 转置和轴操作

    • transpose: 转置操作
    • swapaxes: 交换轴
    • squeeze/expand_dims: 维度压缩和扩展

10.2 最佳实践

  • 🚀 优先使用视图: reshape、ravel等操作尽可能返回视图
  • 📊 理解广播: 充分利用广播机制简化代码
  • 💾 注意内存: 区分视图和副本,合理使用就地操作
  • 🎯 选择合适的函数: 根据需求选择最适合的拼接/分割函数

10.3 常见陷阱

  • 形状不匹配: 拼接时确保除指定轴外其他维度匹配
  • 广播错误: 理解广播规则,避免意外的形状错误
  • 内存浪费: 不必要的数组复制影响性能
  • 轴参数错误: 搞错axis参数导致操作结果错误

10.4 下一步学习

  • 📚 学习线性代数运算
  • 🔧 掌握随机数生成
  • 📁 了解文件IO操作
  • ⚡ 深入性能优化技巧

11. 练习题

11.1 基础练习

  1. 形状操作

    • 创建一个12元素的数组,重塑为不同形状
    • 练习flatten和ravel的区别
    • 使用squeeze和expand_dims操作
  2. 广播练习

    • 实现矩阵每行减去该行均值
    • 计算矩阵与向量的外积
    • 标准化二维数据
  3. 拼接分割

    • 拼接多个数组创建数据集
    • 将数据集分割为训练/验证/测试集
    • 练习不同轴的拼接操作

11.2 进阶练习

  1. 图像处理

    • 实现图像的旋转和翻转
    • 创建图像拼贴
    • 批量处理图像数据
  2. 时间序列

    • 创建滑动窗口
    • 实现移动平均
    • 数据重塑用于序列预测
  3. 数据分析

    • 实现数据透视表功能
    • 多维数据的聚合操作
    • 相关性矩阵计算

11.3 挑战练习

  1. 性能优化

    • 比较不同操作的内存使用
    • 优化大数组的处理
    • 实现高效的数据变换
  2. 算法实现

    • 用形状操作实现卷积
    • 实现矩阵分块算法
    • 开发数据预处理管道

恭喜您完成第5章的学习! 🎉

您已经掌握了NumPy的数组形状操作和广播机制,这是NumPy最强大的特性之一。在下一章中,我们将学习线性代数运算和随机数生成。

👉 继续学习第6章:线性代数与随机数