如何加载gzip压缩的二进制文件及处理tar.gz机器学习数据集
1. 什么是.tar文件,为什么数据集都爱用这个格式
你可以把.tar理解成一个未压缩的文件打包盒:它的作用不是压缩缩小体积,而是把成千上万个零散的文件(比如数据集里几万张图片、对应的标签文本、说明文档)整合成单个文件,方便传输、校验完整性,不会出现传一半丢几个小文件的情况。
而.tar.gz就是先把所有零散文件打包成一个.tar的整包,再用gzip算法给这个整包做压缩缩小体积,相当于“先把所有文件装进纸箱封好,再用真空压缩袋抽走空气缩小体积”。
绝大多数公开数据集用这个格式的原因很简单:
- 跨平台通用:Windows、macOS、Linux所有系统都原生支持解压,不存在专属格式打不开的问题
- 保留文件结构:解压后会自动还原原来的文件夹层级(比如图片在images文件夹、标签在labels文件夹),不会搞混文件对应关系
- 压缩效率合适:gzip对文本、图像这类数据集常见文件的压缩率不错,压缩解压速度也快,不会让你等几个小时解压
2. 用代码解压.tar.gz文件的逐行解释
Python环境下不用额外装复杂工具,用标准库就能完成解压,以下是最通用的实现版本,逐行说明作用:
首先导入需要的内置模块,不需要额外pip安装:
import tarfile import os
tarfile是Python自带的专门处理tar格式包的库os是Python自带的处理文件路径、文件夹操作的库
接下来写解压逻辑,替换路径即可直接使用:
# 定义你下载好的.tar.gz文件的本地存储路径 compressed_file_path = "./SUN_attribute.tar.gz" # 定义解压后文件要存放的目标文件夹路径,不存在则自动创建 extract_target_path = "./datasets/SUN" # 校验目标文件夹是否存在,不存在则递归新建 if not os.path.exists(extract_target_path): os.makedirs(extract_target_path) # 打开压缩包,"r:gz"模式代表以读取方式打开、支持gzip压缩的tar包 with tarfile.open(compressed_file_path, "r:gz") as tar: # 将压缩包内所有内容按原有目录结构,解压到指定目标路径 tar.extractall(path=extract_target_path)
逐行逻辑说明:
- 前两行路径定义:明确告诉代码待解压文件位置、解压后存储位置,替换为自己的本地路径即可
os.path.exists判断逻辑:避免目标文件夹已存在时报错,无需手动提前新建文件夹tarfile.open部分:相当于用解压软件双击打开压缩包,with语法会在解压完成后自动关闭文件,不会出现文件占用问题tar.extractall:执行实际解压操作,保留压缩包内原有的文件夹层级
如果是单独的.gz文件(不是.tar.gz,比如MNIST的单文件压缩包),用gzip库即可,逻辑类似:
import gzip import shutil with gzip.open("./train-images-idx3-ubyte.gz", "rb") as f_in: with open("./train-images-idx3-ubyte", "wb") as f_out: shutil.copyfileobj(f_in, f_out)
这段代码的作用是读取gz压缩的单文件二进制内容,写入到同目录下的无压缩文件中,完成单文件解压。
3. 解压后的二进制文件是什么,怎么转成模型能用的数组
首先要建立一个基础认知:所有计算机里的文件本质上全是二进制文件,就是一堆0和1的组合。你平时看到的.txt、.jpg、.csv只是行业通用约定了不同的01排列规则(也就是文件格式规范),对应软件按照规则读取,就能显示成你能看懂的文字、图片。
你解压MNIST得到的“二进制文件”不是什么特殊格式,就是MNIST官方按照自定义的排列规则,把所有图片像素、标签值直接存储的连续01序列,没有加jpg、png这类图片格式的额外头信息,所以直接用图片查看器打不开,看起来就是“无法识别的二进制文件”。
要把它转成PyTorch能用的数组,只需要按照官方给出的排列规则,把这些01序列读出来转成张量即可。以MNIST图片文件为例,官方公开的格式规则是:
- 最开始4个字节:魔术码,用来标记这是图片文件还是标签文件,实际使用时可以忽略
- 接下来4个字节:存储总图片数量,比如训练集对应值为60000
- 接下来4个字节:存储单张图片高度,MNIST固定为28
- 接下来4个字节:存储单张图片宽度,MNIST固定为28
- 后续所有字节:按顺序存储每个像素的灰度值,每个像素占1个字节,值范围为0-255
对应的读取代码逐行解释:
import numpy as np import torch # 以二进制只读模式打开解压好的MNIST图片文件 with open("./train-images-idx3-ubyte", "rb") as f: # 读取前4个字节,按照32位大端整数格式解析为魔术码 magic_num = np.frombuffer(f.read(4), dtype=np.dtype(">i4"))[0] # 读取接下来4个字节,解析为总图片数量 img_count = np.frombuffer(f.read(4), dtype=np.dtype(">i4"))[0] # 读取接下来4个字节,解析为单张图片高度 img_height = np.frombuffer(f.read(4), dtype=np.dtype(">i4"))[0] # 读取接下来4个字节,解析为单张图片宽度 img_width = np.frombuffer(f.read(4), dtype=np.dtype(">i4"))[0] # 读取剩余所有字节,按0-255无符号整数解析,重构为[图片数, 高度, 宽度]维度的数组 images = np.frombuffer(f.read(), dtype=np.uint8).reshape(img_count, img_height, img_width) # 把numpy数组转为PyTorch张量,同时将像素值归一化到0-1区间,可直接送入DataLoader使用 images_tensor = torch.from_numpy(images).float() / 255.0
核心逻辑非常简单:不管是什么格式的二进制数据集,只要找到官方给出的格式说明(即多少字节对应什么内容、数值是什么存储类型),按顺序读取对应长度的字节,按指定类型解析,再重构为需要的维度即可,本质和按说明书拼乐高没有区别。
小提示:使用SUN、ImageNet这类普通图片数据集时,不需要手动读取二进制转数组,解压完成后直接用PyTorch的
ImageFolder类就能自动按文件夹结构读取图片、匹配标签,比MNIST这种存为二进制序列的格式操作更简单。
内容的提问来源于stack exchange,提问作者MrStealYourFrog

