2

我正在用 scikit learn 建立一个机器学习项目。输入数据是平面 ROOT NTuples。

过去我一直在使用 root_numpy 将 NTuples 转换为保存在 h5 文件中的 pandas.DataFrame。

我想知道是否可以使用 uproot 来
a) 完全跳过 h5 转换?
b) 使用的内存不如从 h5 加载的 DataFrame 中使用的内存多?

我天真的第一次尝试看起来像这样:

'''
Runs preselection, keeps only desired variables in DataFrame
'''
def dropAndKeep(df, dropVariables=None, keepVariables=None, presel=None, inplace=True):

    if ((presel is not None) and (not callable(presel))):
        print("Please either provide a function to 'presel' or leave blank")
        raise ValueError

    if callable(presel):
        if not(inplace):
            df = df.drop(df[~presel(df)].index, inplace=False)
        else:
            df.drop(df[~presel(df)].index, inplace=True)

    if keepVariables is not None:
        dropThese = list( set(df.columns) - set(keepVariables) )
        return df.drop(columns=dropThese, inplace=inplace)

    if dropVariables is not None:
        return df.drop(columns=dropVariables, inplace=inplace)

'''
Loads a TTree from ROOT file into a DataFrame 
'''
def load_root(inFile, key, dropVariables=None, keepVariables=None, presel=None):
    df = uproot.open(inFile)[key].pandas.df()
    dropAndKeep(df, dropVariables, keepVariables, presel=presel, inplace=True)
    return df


inFile = "path/to/file.root"
key = "ntuple"
df = load_root(inFile, key)

这需要很长时间。有没有更好的方法来做到这一点?

4

4 回答 4

3

请注意,每次调用uproot.open(...)file [key]加载 TFile 和 TTree 元数据都使用纯 Python — uproot 中最慢的部分。如果您不止一次调用它,请尝试保留 TFile 和/或 TTree 对象并重新使用它们。

此外,看起来你的dropAndKeep函数只是删除行(事件),但如果我读错了并且它正在做列(分支),那么使用branchesuproot 的数组读取函数的参数只发送你想要的分支。由于 ROOT 文件中的数据是按列排列的,因此您无法避免读取不需要的事件——您必须在事后删除它们(在任何框架中)。

接下来,请注意,对于过滤事件等简单操作,Pandas 比 NumPy 慢得多。如果您想加快速度,请使用TTree.arrays而不是获取数组TTree.pandas.df,为您的选择构造一个 NumPy 布尔数组,并将其应用于TTree.arrays返回的 dict 中的每个数组。然后,您可以使用 Pandas 的 DataFrame 构造函数将所有这些放入 DataFrame 中(如果您真的需要 Pandas)。

确实你不需要通过 HDF5,你也不需要通过 Pandas。你的机器学习框架(TensorFlow?Torch?)几乎肯定有一个接口,可以接受零拷贝(或 GPU 的一个拷贝)的 NumPy 数组。强调 HDF5 或 Pandas 的教程这样做是因为对于大多数用户(非 HEP)来说,这些是最方便的界面。他们的数据可能已经在 HDF5 或 Pandas 中;我们的数据可能在 ROOT 中。

如果您的机器学习将在 GPU 上进行,那么您可能也希望在 GPU 上进行事件选择。CuPy是一个 NumPy 克隆,它完全在 GPU 上分配和运行,您的 TensorFlow/Torch 张量可能具有与 CuPy 数组的零拷贝接口。原则上,如果将 CuPy 数组用作asarray 解释的目标,则 uproot 应该能够直接从 ROOT 文件写入 CuPy 数组。不过,我还没有尝试过。

如果您可以控制要处理的 ROOT 文件,请尝试使它们的篮子变大(增加刷新大小)并且使它们的数据结构简单(例如纯数字或数字数组/向量,不要更深)。也许最重要的是,使用像 lz4 这样的轻量级压缩,而不是重量级的 Luke lzma。

Uproot 可以并行读取篮子,但这仅在它有大量非 Python 计算要做时才被证明是有用的,例如解压缩 lzma。

如果您要一遍又一遍地读取这些数组,您可能希望使用numpy.save编写中间文件,这实际上只是磁盘上的原始字节。这意味着在读回它时没有反序列化,而不是解码 ROOT 或 HDF5 文件所需的工作。因为它是一种非常简单的格式,您甚至可以使用numpy.memmap将其读回,当它懒惰地从磁盘加载数据时,它会窥视操作系统的页面缓存,甚至删除显式的字节副本。

并非所有这些技巧都同样有用。我试图把最重要的放在第一位,但在进行可能没有太大区别的大代码重写之前进行试验。有些技巧不能与其他技巧结合使用,例如 CuPy 和 memmap(memmap 总是延迟加载到主内存,而不是 GPU 内存)。但有些组合可能会富有成效。

祝你好运!

于 2019-11-12T12:33:32.420 回答
2

我的另一点。我在连根拔起 -> hdf5 阵营;这样我就可以完成一次慢速部分(将文件读入 hdf5),以及合并较小的文件并进行一些处理。我还保持压缩低或关闭。这可能需要 4 到 5 分钟将许多文件连根拔起读取到小于 10 秒的 hdf5 读取几个文件。

我可以补充的一点是,如果您有“锯齿状”数据,例如真实信息,那么直接使用对 hdf5 具有原生支持的 AwkwardArray 可以很好地工作。我使用 h5py 来处理 HDF5 文件。你可以在这里看到我在做什么:https ://gitlab.cern.ch/LHCb-Reco-Dev/pv-finder 。

这曾经也是这样设计的,因为我没有一个可以同时在任何地方运行 ROOT 和 ML 工具的环境,但现在我使用一个 environment.yml 文件,使用 Conda-forge ROOT和 ML 工具(PyTorch 等)。

于 2019-11-14T02:16:13.717 回答
2

而且每个人都忘记了一个明显的成分:RDataFrame.AsNumpy()参见例如https://root.cern.ch/doc/master/df026__AsNumpyArrays_8py.html

这样,就不需要临时文件,也不需要将所有内容加载到内存中。阅读以原生 C++ 速度进行。很高兴在https://root-forum.cern.ch上看到关于什么效果更好的报告!

于 2019-11-14T08:55:59.253 回答
1

看起来 Jim 提供了一个很好的选项概述,所以我将提供一个有点专业的策略:

由于您似乎正在执行“预选”步骤,我认为您可能会从以 NumPy、HDF5 或 Parquet 格式保存中间文件中受益;这样,您可以避免每次在磁盘上处理数据时重复选择计算(将这些格式加载到 NumPy 或 pandas 中就像保存它们一样简单)。所以,我的建议是加载一次 ROOT 风格的数据(并且只读取您感兴趣的分支),执行预选步骤,并保存中间文件以供以后使用。我举一个更具体的例子:

我有一个包含三个选择的工作流程。我们可以将它们表示为pandas.DataFrame.eval / pandas.DataFrame.query字符串。(pandas.evalnumexpr可用时加速)。这些类似于TTree::Draw选择。这是一个任意示例,其中我的树有 columns [electron_pt, regionA, regionB, regionC]

selectA = "(electron_pt >= 25) & (regionA == True)"
selectB = "(electron_pt >= 30) & (regionB == True)"
selectC = "(electron_pt >= 35) & (regionC == True)"

我可以一次将我的数据加载到数据框中并应用选择:

keep_columns = [......] # some list of branches to keep, must contain selection branches
df = uproot.open("file.root").get("tree").pandas.df(branches=keep_columns)

selections = {
    "A": selectA,
    "B": selectB,
    "C": selectC
}

现在我们可以遍历选择,查询数据框,并保存仅包含特定选择的中间格式。

for name, selection in selections.items():
    df.query(selection).to_hdf("file_selection{}.h5".format(name), name)
    # or save to parquet (if pyarrow is installed):
    # df.query(selection).to_parquet("file_section{}.parquet".format(name))

pandas.read_hdf然后稍后使用或将文件读回内存pandas.read_parquet

过去,当我对来自共同来源但需要分类为几个不同选择的数据训练 ML 分类器时,这种策略对我来说非常有效。

于 2019-11-12T17:44:01.967 回答