想要将拿到的数据做切片处理,但是每训练一次网络就需要切一次,这样的效率太低了,然后就想着先把切片后的数据存储起来,然后训练网络的时候直接调用就可以。学习了一下python的pkl文件的读写,记录下来,方便以后查阅。

pkl文件读写

  • pkl文件
  • pickle模块概述
  • 持久化模块
  • pickle模块的作用
  • 文件访问模式
  • Python with 关键字
  • 读写pkl文件
  • 正常写入、读取
  • 连续写入后读取
  • 4维矩阵的读写
  • cannot serialize a bytes object larger than 4 GiB
  • list.append & np.append
  • 参考资料

pkl文件

pkl格式的文件是python用于保存文件用的。

pkl文件是python里面保存文件的一种格式,如果直接打开会显示一堆序列化的东西(二进制文件)。需要使用rb类型来打开。

pickle模块概述

持久化模块

pickle模块是Python专用的持久化模块所谓的持久化就是让数据持久化保存,可以持久化包括自定义类在内的各种数据,比较适合Python本身复杂数据的存储。

但是持久化后的字符串是只能用于Python环境,不能用作与其他语言进行数据交换。pickle的本意是腌渍的意思,就是将物品永久地保存成文件,用的时候读出来还能用。

pickle模块的作用

pickle模块的作用是把Python对象直接保存到文件里,而不需要先把它们转化为字符串再保存,也不需要用底层的文件访问操作,直接把它们写入一个二进制文件里。

pickle模块会创建一个Python语言专用的二进制格式,不需要使用者考虑任何文件细节,它会帮你完成读写对象的操作。用pickle比打开文件、转换数据格式以及写入文件的操作能够节省不少代码。

pickle本身和json的功能是相同的,都是将Python数据对象保存为持久化的文件,区别是pickle能够保存Python的复杂的数据类型,包括列表、元组、自定义类等,而json只能保存字典类型的数据,同时pickle只能用Python打开,而json却可以被其他语言所读取。

文件访问模式

访问模式

说明

r

以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。

w

打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。

a

打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。

rb

以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。

wb

以二进制格式打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。

ab

以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。

r+

打开一个文件用于读写。文件指针将会放在文件的开头。

w+

打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。

a+

打开一个文件用于读写,如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果改文件不存在,创建新文件用于读写。

rb+

以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头

wb+

以二进制格式打开一个文件用于读写。如果改文件已存在则会覆盖。如果改文件不存在,创建新文件。

ab+

以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果改文件不存在,创建新文件用于读写。

Python with 关键字

Python 中的 with 语句用于异常处理封装了 try…except…finally 编码范式,提高了易用性。

with 语句使代码更清晰、更具可读性, 它简化了文件流等公共资源的管理。

在处理文件对象时使用 with 关键字是一种很好的做法。

file = open('./test_runoob.txt', 'w')
file.write('hello world !')
file.close()

以上代码如果在调用 write 的过程中,出现了异常,则 close 方法将无法被执行,因此资源就会一直被该程序占用而无法被释放。 接下来我们呢可以使用 try…except…finally 来改进代码:

file = open('./test_runoob.txt', 'w')
try:
    file.write('hello world')
finally:
    file.close()

以上代码我们对可能发生异常的代码处进行 try 捕获,发生异常时执行 except 代码块,finally 代码块是无论什么情况都会执行,所以文件会被关闭,不会因为执行异常而占用资源。

使用 with 关键字

with open('./test_runoob.txt', 'w') as file:
    file.write('hello world !')

使用 with 关键字系统会自动调用 f.close() 方法, with 的作用等效于 try/finally 语句是一样的

读写pkl文件

pickle.dump(): 将对象序列化成二进制对象;
pickle.load(): 读取指定的二进制对象,并返回序列化对象。

正常写入、读取

import pickle
import numpy as np

output_dir = '/home/pytorch/LiangXiaohan/MI_Dataverse/MI_same_limb/window_size_150/'
data = [1, 2, 3, 4, 5]

with open(output_dir+ "-data_eeg.pkl", "wb") as fp_data:
  	pickle.dump(data, fp_data)

with open(output_dir + "-data_eeg.pkl", "rb") as fp_data:
  	data_pkl = pickle.load(fp_data)

data_pkl

输出:

[1, 2, 3, 4, 5]

连续写入后读取

import pickle
import numpy as np

### 生成滑动窗口数据
def windows_create(data_lengh, window_size, stirde):
    # data size [channel, samples]
	start = 0
	while ((start+window_size) < data_lengh):
		yield int(start), int(start + window_size)
		start += stirde

### 滑动窗分割数据并将数据保存为pkl文件
def segment_signal_pkl(output_dir, persion_data, data, label, window_size, stirde):
    # data size [Train/Test_samp, channel, samples]           label size [Train/Test_samp, 1]
    shape = data.shape
    output_data = output_dir + "sub-" + persion_data + "-data_eeg.pkl"
    output_label = output_dir + "sub-" + persion_data + "-label_eeg.pkl"
    file_data = open(output_data, 'ab')
    file_label = open(output_label, 'ab')
    for i in range(0, shape[0]):
        for (start, end) in windows_create(shape[2], window_size, stirde) :
            if ((data[i, :, start:end]).shape[1]== window_size):
                if (start == 0):
                    segments = data[i, :, start:end]
                    labels = label[i]
                else:
                    segments = np.vstack((segments, data[i, :, start:end]))
                    labels = np.append(labels, label[i])
        segments = segments.reshape(int(segments.shape[0]/shape[1]), shape[1], window_size)
        pickle.dump(segments, file_data)
        pickle.dump(labels, file_label)
        segments = []
        labels = []
    file_data.close()
    file_label.close()

output_dir = '/home/pytorch/LiangXiaohan/MI_Dataverse/MI_same_limb/window_size_150/'
persion_data = '001'

data = np.random.rand(20, 62, 500)
label = np.random.rand(20, 1)

segment_signal_pkl(output_dir, persion_data, data, label, 150, 10)

cnn_features = []
labels = []
with open(output_dir + "sub-" + persion_data + "-data_eeg.pkl", "rb") as fp_data:
	for i in range(0, 20):
  		cnn_features.append(pickle.load(fp_data))
with open(output_dir + "sub-" + persion_data + "-label_eeg.pkl", "rb") as fp_label:
	for i in range(0, 20):
  		labels.append(pickle.load(fp_label))

np.array(cnn_features).shape

输出:

(20, 35, 62, 150)

:在连续写入后进行读取操作,这里与之前有点不同,就是读取的时候不能一次性全部读出来,每次读取读到的数据是,连续写入过程中其中一次写入的数据,所以在读取的时候我们需要用for循环来读取。

:原本的分割数据的函数如下所示,但是这个函数有个问题,那就是越运行越慢,跑完一组数据需要一天的时间,分析了一下发现原因在这句话segments = np.vstack((segments, data[i, :, start:end])),随着程序的运行,存储的数据也越来越多,造成segments占用的内存越来越大,这就造成程序运行越来越慢,进而想到将函数更改为上面的样子,这就出现了连续读写pkl文件的问题。

### 滑动窗分割数据
def segment_signal(data, label, window_size, stirde):
    # data size [Train/Test_samp, channel, samples]           label size [Train/Test_samp, 1]
    shape = data.shape 
    for i in tqdm(range(0, shape[0])):
        for (start, end) in windows_create(shape[2], window_size, stirde):
            if ((data[i, :, start:end]).shape[1]== window_size):
                if (start == 0) and (i == 0):
                    segments = data[i, :, start:end]
                    labels = label[i]
                else:
                    segments = np.vstack((segments, data[i, :, start:end]))
                    labels = np.append(labels, label[i])
    segments = segments.reshape(int(segments.shape[0]/shape[1]), shape[1], window_size)
    # segments size [Train/Test_samp, channel, window_size]           labels size [Train/Test_samp, 1]
    return segments, labels

4维矩阵的读写

shape = np.array(Pearson_feature).shape
shape

输出:

(900, 65, 62, 62)
with open(output_dir + "sub-" + persion_data + "-feature_eeg.pkl", "wb") as fp_feature:
    for i in tqdm(range(0, 900)):
        pickle.dump(Pearson_feature[i], fp_feature)
feature_data = []
with open(output_dir + "sub-" + persion_data + "-feature_eeg.pkl", "rb") as fp_feature:
	for i in range(0, 900):
		feature_data.append(pickle.load(fp_feature))
shape = np.array(feature_data).shape
shape

输出:

(900, 65, 62, 62)

可以看到对于4维矩阵写入文件和读取出来都需要使用for循环。

更新:2023年2月10日。

今天突然发现四维矩阵可以正常的保存和读取,之前的时候报错,也不知道为啥!!!

np.array(data_2D).shape

输出:

(900, 800, 9, 11)
with open(output_dir + "sub-" + persion_data + "-aaa_eeg.pkl", "wb") as fp_aaa:
    pickle.dump(data_2D, fp_aaa)
with open(output_dir + "sub-" + persion_data + "-aaa_eeg.pkl", "rb") as fp_datas:
    X = pickle.load(fp_datas)
np.array(X).shape

输出:

(900, 800, 9, 11)

更新:2023年2月12日。

cannot serialize a bytes object larger than 4 GiB

直接在pickle.dump中增加一个protocol = 4这个参数就行。

import pickle
 
pickle.dump(data,open('file','wb'),protocol = 4)

原因如下所示:

python 记载pkl文件 python保存pkl文件_Python

python 记载pkl文件 python保存pkl文件_python 记载pkl文件_02

list.append & np.append

在对数据进行处理,然后将处理后的数据暂时存储到一个变量中。起初,我使用的是np.array数据类型,发现随着存储的数据越来越多,程序运行的就越来越慢,然后考虑到对处理后的数据进行连续存储处理,这样每次存储结束就清空np.array数组,这样的操作确实解决了程序运行缓慢的问题,但是在读取pkl文件的时候就需要用for循环,这样写的代码很难看。然后不经意间用到了list,发现就算储存的数据量很大也不会出现程序运行缓慢的问题了,也就不需要连续存储的操作了,然后在读取文件的时候也可以直接读取,不再使用for循环了,这样程序写起来就好看多了。

参考资料

Python数据存储:pickle库

Python with 关键字