Python 关于大文件的读写

1、前言

项目时遇到训练集过大的情况,无法直接读入内存,而使用keras的fit_generator()感觉也遇到了IO瓶颈。 于是想把验证集从训练集中分离出来,每次只把验证集读取进内存,节省一定的时间。在这个过程中遇到了一系列问题,记录下来以备查找。

2、 读取

备注: Pandas.DataFrame是一个很好用的数据结构,但是在读取大文件时请小心,不然容易造成悲剧。

我遇到的问题是:训练集是一个62G的BSON文件,需要根据索引从中找出验证集中数据的位置,读取出来并写入到一个独立文件中,以备后续使用。

(1)使用DataFrame逐行储存

我首先想到的方法就是使用DataFrame格式,直接把每个读到的数据逐行存进df里。

示例代码如下:

import os
from tqdm import *
import bson
import pandas as pd
# Input data files are available in the "../input/" directory.
data_dir = "../input/"
train_bson_path = os.path.join(data_dir, "train.bson")
# First load the lookup tables from the CSV files.
train_offsets_df = pd.read_csv("train_offsets.csv", index_col=0)  # index and features of every instance in the training set file
val_images_df = pd.read_csv("val_images.csv", index_col=0)  # index of which instance is belong to validation set
num_val_images = len(val_images_df)
train_bson_file = open(train_bson_path, "rb")  # open training set file
val_full_dataset = pd.DataFrame(columns=["x", "y"])  # create df to save val set
with tqdm(total=num_val_images) as pbar:  # show estimited time and progress
    for c, d in enumerate(val_images_df.itertuples()):
        offset_row = train_offsets_df.loc[d[1]]  # find the corresponding location
        # Read this product's data from the BSON file.
        train_bson_file.seek(offset_row["offset"])  # find where to start reading (random read)
        item_data = train_bson_file.read(offset_row["length"])
        # Grab the image from the product.
        item = bson.BSON.decode(item_data)
        bson_img = item["imgs"][d[3]]["picture"]
        val_full_dataset.loc[c] = {"x": bson_img, "y": d[2]}  # save into the dataframe
        pbar.update()

代码可以运行,可是在实际运行中,数据的读取速度急速下降,从最开始的700/s下降到100/s,甚至还在继续下降。这段简单的代码实际运行了25个小时。

经过一系列测试发现,代码的瓶颈在于val_full_dataset.loc[c] = {"x": bson_img, "y": d[2]},重点是这个df.loc[]会随着df的变长而越来越慢。

接下来我又测试了df.append(),得到了与loc相似的结论,都说明了它们的运行速度与df本身的长度有关。所以在定长的df中使用loc是正确的。

(2)使用list逐行储存并转化成DataFrame

经过一系列的测试,我发现还是直接使用Python自带的list数据结构能够解决这个问题。

对上述代码进行一些微小的调整:

  • val_full_dataset = pd.DataFrame(columns=["x", "y"]) ——> val_full_dataset = []

  • val_full_dataset.loc[c] = {"x": bson_img, "y": d[2]} ——> val_full_dataset.append({"x": bson_img, "y": d[2]})

Creating Pandas DataFrames from Lists and Dictionaries中发现,实际上我们生成的这个由dict组成的list是可以直接转换成DataFrame的:

  • 最后把生成的list转换成DataFrame:val_full_dataset_df = pd.DataFrame(val_full_dataset)

在测试中,经过这一点微小的改动,我们程序的运行时间从25个小时缩短到了两分钟。

3、写入(保存)

在读取并成功保存到DataFrame之后,我们需要把它写入到文件中。

官方文档: IO Tools (Text, CSV, HDF5, …)

从文档中发现,df可以直接写成三种文件类型:textbinarySQL。由于我们的文件很大,SQL我不太熟悉,因此选用binary文件进行储存。

(1) HDF5

在这些二进制文件中,我最先尝试了hdf5

在使用前需要首先安装依赖包(PyTables):pip3 install tables

  • 简单写入:df.to_hdf("file_name","key")key是储存到文件中的一个键值

  • 简单读取:df = pd.read_hdf("file_name","key")

例如:val_full_dataset_df.to_hdf("val_dataset.h5", "df")

但是在实际运行中报错:OverflowError: value too large to convert to int,经测试发现可能是由于文件过大导致,写成较小文件时正常。

(2) pickle

经过list的启发,我想使用Python自带的pickle格式试一试。

  • 简单写入:df.to_pickle('val_dataset.pkl')

  • 简单读取:df = pd.read_pickle('val_dataset.pkl')

经过测试,使用pickle格式写入文件用时20s左右,而读取文件仅仅使用2.2s,,并且保留原BSON文件大小,完全满足要求。

默认的pickle格式还有压缩选项:If ‘infer’(by default), then use gzip, bz2, zip, or xz if filename ends in ‘.gz’, ‘.bz2’, ‘.zip’, or ‘.xz’, respectively.

可是经过测试,gz也会出现Overflow问题,zip报错;bz2xz使用正常,但是压缩速度很慢,如果不需要保留空间则不用使用。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

倾城一少

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值