python - 如何优化 Python 中二维字符串数组的序数编码?

标签 python c numpy optimization encoding

我有一个 Pandas 系列,每行包含一个字符串数组:

0                                           []
1                                           []
2                                           []
3                                           []
4         [0007969760, 0007910220, 0007910309]
                          ...                 
243223                                      []
243224                            [0009403370]
243225                [0009403370, 0007190939]
243226                                      []
243227                                      []
Name: Item History, Length: 243228, dtype: object

我的目标是在这里做一些简单的序数编码,但要尽可能高效(在时间和内存方面),但有以下注意事项:
  • 空列表需要插入一个表示“空列表”的整数,该整数也是唯一的。 (例如,如果有 100 个唯一字符串,空列表可能被编码为 [101] )。
  • 必须以某种方式保存编码,以便我将来可以对其他列表进行相同的编码
  • 如果这些 future 列表包含初始输入数据中不存在的字符串,则它必须编码自己的单独整数以表示“在配对之前从未见过”。

  • 显而易见的问题是“你为什么不只使用 sklearn 的 OrdinalEncoder”。好吧,除了没有未知的项目处理程序之外,以这种方式按行应用实际上也非常慢(我们必须将它放在所有不同字符串的组合单个数组上,然后使用 Series.apply(lambda x: oe.transform(x)) 来转换每一行),因为它必须做一些字典理解来为每一行构建映射表,这需要时间。每次通话的时间不是很多,只有大约 0.01 秒,但对于我拥有的数据量来说,这仍然太慢了。

    一种解决方案是从每一行部分中取出 dict 理解,并在遍历行之前构建一个映射表,如在此函数中所示:
    def encode_labels(X, table, noHistory, unknownItem):
    
        res = np.empty(len(X), dtype=np.ndarray)
    
        for i in range(len(X)):
            if len(X[i]) == 0:
                res[i] = np.array([noHistory])
            else:
                res[i] = np.empty(len(X[i]), dtype=np.ndarray)
                for j in range(len(X[i])):
                    try:
                        res[i][j] = table[X[i][j]]
                    except KeyError:
                        res[i][j] = unknownItem
    
        return res
    

    这明显好于按行 .apply()但仍然不是最快的一段代码。我可以对它进行 cythonize 并进行一系列其他优化以获得更多的加速,但这并不是更好的数量级:
    %%cython
    
    cimport numpy as cnp
    import numpy as np
    from cpython cimport array
    import array
    
    cpdef list encode_labels_cy(cnp.ndarray X, dict table, int noHistory, int unknownItem, array.array rowLengths):
    
        cdef int[:] crc = rowLengths
    
        cdef list flattenedX = []    
        cdef Py_ssize_t i, j
        cdef list row = []
    
        for row in X:
            if len(row)==0:
                flattenedX.append('ZZ')
            else:
                flattenedX.extend(row)
    
        cdef Py_ssize_t lenX = len(flattenedX)
    
        cdef array.array res = array.array('i', [0]*lenX)
        cdef int[:] cres = res
    
        i=0
        while i < lenX:
            try:
                cres[i] = table[flattenedX[i]]
            except KeyError:
                cres[i] = unknownItem
            i += 1
    
        cdef list pyres = []
        cdef Py_ssize_t s = 0
    
        for k in crc:
            pyres.append(res[s:s+k])
            s+= k
    
        return pyres
    
    # classes is a dict of {string:int} mappings. noHistory and unknownItem are ints encoding those values
    
    %timeit encode_labels(X.values, classes, noHistory, unknownItem)
    %timeit encode_labels_cy(X.values, classes, noHistory, unknownItem, array.array('i', [1 if x == 0 else x for x in [len(j) for j in X]]))
    
    50.4 ms ± 2.76 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
    11.2 ms ± 1.11 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)
    

    (这是 5000 行样本,而不是整个数据集)。

    更新:我设法在 ctypes 中实现了一个实现,这比按行 .apply() 和我原来的原生 python 都快,但它仍然比 Cython 慢(在我看来这真的不应该是这种情况!)

    所以;我怎样才能更快?并理想地同时保持尽可能低的内存使用量?这不必是纯python。如果你可以在 Cython 或 ctypes 或其他东西中让它变得活泼,那就太好了。此代码将构成神经网络预处理的一部分,因此此时还有一些 GPU 等待数据;如果你能利用这些,那就更好了。多处理也可能是我还没有设法探索的一个选项,但问题在于它需要每个进程的 string:int 映射表的副本,这 a) 生成速度慢 b) 使用大量内存.

    编辑 :

    忘了提供一些数据。您可以运行以下命令来获取与我的格式类似的输入数据集:
    import numpy as np
    import pandas as pd
    
    a = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
    
    X = pd.Series([[a[np.random.randint(0, 26)] for i in range(np.random.randint(0, 10))] for j in range(5000)])
    
    classes = dict(zip(a, np.arange(0, 26)))
    unknownItem = 26
    noHistory = 27
    

    只有 5000 行,但这应该足以准确确定哪种方法更快。

    最佳答案

    使用下面的 Cython 函数,我得到了大约 5 的加速因子。它使用一个临时列表来存储相关数据的行式副本,该列表应该被初始化得足够大,以便它可以保存每一行的数据(即,如果每行的最大元素数是已知的,使用那个,否则使用启发式值,使必要的调整大小数量保持最少)。

    cpdef list encode_labels_cy_2(cnp.ndarray X, dict table, int noHistory, int unknownItem):
    
        cdef Py_ssize_t i, n
        cdef list result = []
        cdef list tmp = [noHistory] * 10  # initialize big enough so that it's likely to fit all elements of a row
    
        for row in X:
            n = len(row)
            while len(tmp) < n:  # if too small, resize
                tmp.append(noHistory)
            if n > 0:
                i = 0
                while i < n:
                    tmp[i] = table.get(row[i], unknownItem)
                    i += 1
            else:
                tmp[0] = noHistory
                i = 1
            result.append(tmp[:i])
    
        return result
    

    如果每行的元素数量变化很大并且无法进行良好的估计,您还可以调整 tmp 的大小。通过过度分配列出类似于 CPython's list growth pattern .

    关于python - 如何优化 Python 中二维字符串数组的序数编码?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60603357/

    相关文章:

    python - 错误名称 'dtype' 未定义

    python - 使用 Pandas python 35 将对象类型列转换为 float32 类型

    python - 在同一图形上将艺术家从一个轴移动到另一个轴

    python - 如何计算基于多级索引的时间戳差异?

    c++ - C头文件可以被视为接口(interface)吗?

    python - 如何使用 matplotlib 从数据帧创建简单的动画图

    python - 如何在 Python 中重新加载类对象的方法代码?

    c - gtk_container_get_children

    iphone - 从 iphone 中的 .wav 文件读取时数据为空?

    python - 在不知道输出数组大小的情况下像matlab一样在python中连接数组