Python 数组体积大,时间成本高

Python large size array and high time cost

提问人:user6703592 提问时间:7/12/2023 最后编辑:user6703592 更新时间:7/12/2023 访问量:99

问:

我想创建一个作为机器学习模型的输入:np.ndarray

array_X = np.array([list(w.values) for w in df[['close', 'volume']].rolling(window=20)][19:-1])

这是时间序列中的标准方法,我们使用过去值的窗口作为输入来预测未来值。数组的形状是 。构建这样的数组会花费很多时间,有时还会出现数组消耗的内存过大的错误。2 * 20 * 20000000

有什么方法可以改善上述问题(时间成本和内存错误)吗?

python pandas numpy 性能 窗口函数

评论

0赞 user6703592 7/12/2023
@SebastianWozny 请看我的更新
0赞 dankal444 7/13/2023
大小为 40 * 20_000_000 样本的输入绝不是“时间序列中的标准方式”。我猜你所做的是试图让整个数据集适合 RAM。相反,以 32-128 个左右的样本为一组加载输入。

答:

3赞 Sebastian Wozny 7/12/2023 #1

您的原始代码给了我一个错误,因为数组中前几个条目的维度不匹配,这些条目太短了,因为窗口尚未满,因此我将其更改为丢弃第一个值:

def rolling_approach(df, window_size=3):
    return np.array(
        [w.values for w in df[["close", "volume"]].rolling(window=window_size)][
            window_size - 1 :
        ]
    )

PD的。对于这类操作,DataFrame.rolling 可能非常慢。shift 非常有效 下面是一个写出的例子:pandaswindow_size=3

pd.concat(
            [
                df[["close", "volume"]].shift().shift(),
                df[["close", "volume"]].shift(),
                df[["close", "volume"]],
            ],
            axis=1,
        )
        .values[2:, :]
        .reshape(-1, 3, 2)
    )

我们堆叠班次,然后重塑值。

推广到一个变量,我们得到:window_size

def shift_approach(df, window_size=3):
    shifted_df = pd.concat(
        [df[["close", "volume"]].shift(i) for i in range(window_size - 1, -1, -1)],
        axis=1,
    )
    reshaped_array = shifted_df.values[window_size - 1 :, :].reshape(-1, window_size, 2)
    return reshaped_array

shift性能比操作高出近两个数量级:rolling

enter image description here

在我的MacBook上,它可以很好地扩展到数亿行:enter image description here

def setup(N):
    np.random.seed(42)
    close_values = np.random.randint(1, 100, size=N)
    volume_values = np.random.randint(100, 1000, size=N)
    df = pd.DataFrame({"close": close_values, "volume": volume_values})
    return [df, 10]


approaches = [rolling_approach, shift_approach]
# Show correctness
for approach in approaches[1:]:
    data = setup(10)
    assert np.isclose(approach(*data), approaches[0](*data)).all()

run_performance_comparison(
    approaches,
    [
        1000,
        3000,
        5000,
        10000,
        30000,
        100_000,
        300_000,
        500_000,
        1_000_000,
        3_000_000,
        5_000_000,
        10_000_000,
    ],
    setup=setup,
    title="Performance Comparison",
    number_of_repetitions=2,
)

分析代码:

import timeit
from functools import partial

import matplotlib.pyplot as plt
from typing import List, Dict, Callable

from contextlib import contextmanager


@contextmanager
def data_provider(data_size, setup=lambda N: N, teardown=lambda: None):
    data = setup(data_size)
    yield data
    teardown()


def run_performance_comparison(approaches: List[Callable],
                               data_size: List[int],
                               setup=lambda N: N,
                               teardown=lambda: None,
                               number_of_repetitions=5, title='Performance Comparison', data_name='N'):
    approach_times: Dict[Callable, List[float]] = {approach: [] for approach in approaches}
    for N in data_size:
        with data_provider(N, setup, teardown) as data:
            for approach in approaches:
                function = partial(approach, *data)
                approach_time = min(timeit.Timer(function).repeat(repeat=number_of_repetitions, number=2))
                approach_times[approach].append(approach_time)

    for approach in approaches:
        plt.plot(data_size, approach_times[approach], label=approach.__name__)
    plt.yscale('log')
    plt.xscale('log')

    plt.xlabel(data_name)
    plt.ylabel('Execution Time (seconds)')
    plt.title(title)
    plt.legend()
    plt.show()

评论

0赞 user6703592 7/13/2023
我试过你的解决方案要快得多!所以剩下的问题是,大容量的数组似乎有一个内存错误,如 MemoryError: Unable to assign 84.4 MiB for an array with a shape (11056593,)
0赞 dankal444 7/13/2023
@user6703592可能会使用 np.float32(如果可能的话,甚至可以使用更小的数据类型)。
0赞 user6703592 7/13/2023
@dankal444事情很奇怪,有时从 df.values 生成的相同数组是可以的,但是数组从其他方式构建,例如 for 循环:array_X = [] for i in range(): array_X.append()...将出现内存错误。是否与临时缓存的某些溢出有关?
0赞 Sebastian Wozny 7/13/2023
分析内存可能非常棘手。这取决于你的平台、程序的其余部分以及 Python 在任何特定时间点想要做什么。我将解决方案扩展到几亿行,并且在我的 macbook 上没有收到 OOM 错误。也许在您的程序中撒上几个电话?可能什么都不做,但你永远不知道。gc.collect()
0赞 user6703592 8/24/2023
我在这里问了一个类似的问题:stackoverflow.com/questions/76965427/......,你有什么想法吗?