Luozm +

Python实战经历总结-2

TF/Keras学习中遇到的Python技巧总结

原创文章,转载请注明:转自Luozm's Blog

python 关于大文件的读写

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

1. 读取

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

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

1.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是正确的。

1.2 使用list逐行储存并转化成DataFrame

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

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

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

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

2. 写入(保存)

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

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

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

2.1 HDF5

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

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

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

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

2.2 pickle

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

经过测试,使用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使用正常,但是压缩速度很慢,如果不需要保留空间则不用使用。

© Luozm . All rights reserved. | Top

Life

Theory

Develop