文章目录
- 一、数据流程(5步走)
- 第1步:原始数据是什么?
- 第2步:加载数据(把文件读成数组)
- 第3步:采样(减少数量,加快实验)
- 第4步:展平成向量(最关键!)
- 第5步:计算L2距离
- 二、图片如何被"压扁"?用例子说明
- 例子1:2×2的小图片(理解原理)
- 例子2:32×32的真实图片
- 三、reshape的工作原理
- 四、实际代码演示
kNN需要向量才能算距离,所以必须把图片从(32, 32, 3)展平成(3072,)向量。每个3072维向量就是一张图片的所有像素信息排成一行。
类比:就像把一张照片从左到右、从上到下扫描一遍,变成一串数字,然后比较两串数字的相似程度。
整体流程:图像文件 → 加载成数组 → 采样减少数量 → 展平成向量 → 计算距离
一、数据流程(5步走)
第1步:原始数据是什么?
CIFAR-10数据集:5万个训练图片 + 1万个测试图片,每张图片32×32像素,RGB彩色。
存储格式:存在pickle文件里,每个文件存1万张图片。图片被压扁成一串数字:(10000, 3072),3072 = 32×32×3(宽×高×RGB)。
第2步:加载数据(把文件读成数组)
代码(cs231n/data_utils.py):
defload_CIFAR_batch(filename):withopen(filename,'rb')asf:datadict=load_pickle(f)X=datadict['data']# 形状 (10000, 3072) - 一串数字Y=datadict['labels']# 10000个标签# 重新整理成图片格式X=X.reshape(10000,3,32,32)# 变成 (10000, 3, 32, 32)X=X.transpose(0,2,3,1)# 变成 (10000, 32, 32, 3)X=X.astype("float")# 转成浮点数returnX,Y代码解释:
reshape(10000, 3, 32, 32):把3072个数字重新排列,变成3个通道、每个32×32的图片transpose(0,2,3,1):调整维度顺序,从(通道,高,宽)变成(高,宽,通道),这样Matplotlib才能正确显示astype("float"):把整数像素值(0-255)转成浮点数,方便计算
加载完整数据集:
defload_CIFAR10(ROOT):xs,ys=[],[]# 加载5个训练批次forbinrange(1,6):X,Y=load_CIFAR_batch(f'data_batch_{b}')xs.append(X)# 每个X形状 (10000, 32, 32, 3)ys.append(Y)# 合并所有训练数据Xtr=np.concatenate(xs)# (50000, 32, 32, 3)Ytr=np.concatenate(ys)# (50000,)# 加载测试数据Xte,Yte=load_CIFAR_batch('test_batch')returnXtr,Ytr,Xte,Yte结果:
- 训练集:5个文件合并 →
(50000, 32, 32, 3)(5万张图片) - 测试集:1个文件 →
(10000, 32, 32, 3)(1万张图片)
第3步:采样(减少数量,加快实验)
代码(knn.ipynb):
# 采样训练数据num_training=5000mask=list(range(num_training))X_train=X_train[mask]# 从50000采样到5000y_train=y_train[mask]# 采样测试数据num_test=500mask=list(range(num_test))X_test=X_test[mask]# 从10000采样到500y_test=y_test[mask]代码解释:
list(range(5000)):生成索引[0,1,2,…,4999]X_train[mask]:用索引切片,只取前5000个样本- 为什么采样?数据太多算得慢,先拿一部分试试效果
结果:
- 训练集:
(5000, 32, 32, 3) - 测试集:
(500, 32, 32, 3)
第4步:展平成向量(最关键!)
代码(knn.ipynb):
# Reshape the image data into rowsX_train=np.reshape(X_train,(X_train.shape[0],-1))X_test=np.reshape(X_test,(X_test.shape[0],-1))print(X_train.shape,X_test.shape)# 输出: (5000, 3072) (500, 3072)代码解释:
X_train.shape[0]:第一维大小,即5000(样本数)-1:自动计算,-1 = 32×32×3 = 3072reshape(5000, -1):保持5000行,把后面的维度展平成一列
第5步:计算L2距离
现在每张图片都是向量了,可以算距离了:
代码(knn.ipynb):
classifier=KNearestNeighbor()classifier.train(X_train,y_train)# 只是保存数据,不训练dists=classifier.compute_distances_two_loops(X_test)# dists 形状: (500, 5000)# dists[i, j] = 第i个测试图片和第j个训练图片的距离距离计算代码(k_nearest_neighbor.py):
defcompute_distances_two_loops(self,X):num_test=X.shape[0]# 500num_train=self.X_train.shape[0]# 5000dists=np.zeros((num_test,num_train))foriinrange(num_test):forjinrange(num_train):# 计算L2距离:对应位置相减、平方、求和、开根号dists[i,j]=np.sqrt(np.sum((X[i]-self.X_train[j])**2))returndists代码解释:
X[i]:第i个测试样本,形状(3072,)self.X_train[j]:第j个训练样本,形状(3072,)(X[i] - self.X_train[j]):对应位置相减,形状(3072,)** 2:每个元素平方np.sum(...):求和,得到一个数np.sqrt(...):开根号,得到距离
距离公式:d = ∑ k = 1 3072 ( x k − y k ) 2 d = \sqrt{\sum_{k=1}^{3072}(x_k - y_k)^2}d=∑k=13072(xk−yk)2,就是两个向量对应位置差的平方和再开根号。
二、图片如何被"压扁"?用例子说明
例子1:2×2的小图片(理解原理)
假设有一张2×2像素的RGB图片,形状是(2, 2, 3):
# 原始图片 (2, 2, 3)image=[# 第1行[[100,150,200],# 像素(0,0): R=100, G=150, B=200[110,160,210]],# 像素(0,1): R=110, G=160, B=210# 第2行[[120,170,220],# 像素(1,0): R=120, G=170, B=220[130,180,230]]# 像素(1,1): R=130, G=180, B=230]展平过程:
# 用reshape展平vector=np.reshape(image,(-1,))# 结果: [100, 150, 200, 110, 160, 210, 120, 170, 220, 130, 180, 230]# ↑像素(0,0)↑ ↑像素(0,1)↑ ↑像素(1,0)↑ ↑像素(1,1)↑规律:按行优先顺序,从左到右、从上到下,把每个像素的RGB值依次排列。
例子2:32×32的真实图片
一张32×32的RGB图片,形状是(32, 32, 3):
# 原始图片 (32, 32, 3)image=[# 第1行(32个像素)[[R00,G00,B00],[R01,G01,B01],...,[R0_31,G0_31,B0_31]],# 第2行(32个像素)[[R10,G10,B10],[R11,G11,B11],...,[R1_31,G1_31,B1_31]],# ...# 第32行(32个像素)[[R31_0,G31_0,B31_0],...,[R31_31,G31_31,B31_31]]]展平过程:
vector=np.reshape(image,(-1,))# 结果: [R00, G00, B00, R01, G01, B01, ..., R0_31, G0_31, B0_31,# R10, G10, B10, R11, G11, B11, ..., R1_31, G1_31, B1_31,# ...# R31_0, G31_0, B31_0, ..., R31_31, G31_31, B31_31]长度计算:32行 × 32列 × 3通道 = 3072个数字
三、reshape的工作原理
reshape不改变数据,只改变排列方式:
# 原始数据在内存中是一串连续的数字# reshape只是告诉NumPy如何解释这串数字# 例子:12个数字data=[1,2,3,4,5,6,7,8,9,10,11,12]# 解释成 2×2×3 的图片image=np.reshape(data,(2,2,3))# [[[1, 2, 3], [4, 5, 6]],# [[7, 8, 9], [10, 11, 12]]]# 解释成 12 的向量(展平)vector=np.reshape(image,(-1,))# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]# 数据没变,只是形状变了!关键理解:
- 数据在内存中就是一串连续的数字
reshape只是改变如何解释这串数字- 从
(32, 32, 3)变成(3072,),数据本身没变,只是从"三维表格"变成了"一维列表"
四、实际代码演示
importnumpyasnp# 创建一张2×2的RGB图片(随机值)image=np.random.randint(0,255,(2,2,3))print("原始图片形状:",image.shape)# (2, 2, 3)print("原始图片:\n",image)# [[[100 150 200]# [110 160 210]]# [[120 170 220]# [130 180 230]]]# 展平vector=np.reshape(image,(-1,))print("\n展平后形状:",vector.shape)# (12,)print("展平后:",vector)# [100 150 200 110 160 210 120 170 220 130 180 230]# 验证:可以还原回去image2=np.reshape(vector,(2,2,3))print("\n还原后是否相同:",np.array_equal(image,image2))# True32×32图片的展平过程(可视化)
原始图片 (32, 32, 3): ┌─────────────────────────┐ │ [R G B] [R G B] ... │ ← 第1行,32个像素 │ [R G B] [R G B] ... │ ← 第2行,32个像素 │ ... │ │ [R G B] [R G B] ... │ ← 第32行,32个像素 └─────────────────────────┘ ↓ reshape ↓ 展平向量 (3072,): [R G B R G B ... R G B R G B ... ... R G B ... R G B] ↑第1行↑ ↑第2行↑ ↑第32行↑结果:每张图片变成一个3072维的向量(一串3072个数字)。
数据形状变化表
| 步骤 | 训练集 | 测试集 | 说明 |
|---|---|---|---|
| 文件里 | (10000, 3072) | (10000, 3072) | 压扁的数字 |
| 加载后 | (50000, 32, 32, 3) | (10000, 32, 32, 3) | 图片格式 |
| 采样后 | (5000, 32, 32, 3) | (500, 32, 32, 3) | 减少数量 |
| 展平后 | (5000, 3072) | (500, 3072) | 向量格式 |
| 距离矩阵 | - | (500, 5000) | 测试×训练 |
为什么是3072维?
- 32 × 32 = 1024(像素数)
- 1024 × 3 = 3072(每个像素有RGB三个通道)
- 所以每张图片展平后是3072个数字