Dan*_*lly 5 c python optimization encoding numpy
我有一个 Pandas 系列,每行包含一个字符串数组:
0                                           []
1                                           []
2                                           []
3                                           []
4         [0007969760, 0007910220, 0007910309]
                          ...                 
243223                                      []
243224                            [0009403370]
243225                [0009403370, 0007190939]
243226                                      []
243227                                      []
Name: Item History, Length: 243228, dtype: object
Run Code Online (Sandbox Code Playgroud)
我的目标是在这里做一些简单的序数编码,但要尽可能高效(在时间和内存方面),但有以下注意事项:
[101])。显而易见的问题是“你为什么不只使用 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
Run Code Online (Sandbox Code Playgroud)
这明显优于 row-wise.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
Run Code Online (Sandbox Code Playgroud)
# 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)
Run Code Online (Sandbox Code Playgroud)
(这是 5000 行样本,而不是整个数据集)。
更新:我设法得到一个实现在ctypes的工作,那是不是都在行。适用()和我原来的本机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
Run Code Online (Sandbox Code Playgroud)
只有 5000 行,但这应该足以准确确定哪种方法更快。
这是一个基于NumPy's searchsorted-
k,v = classes.keys(),classes.values()
k,v = np.array(list(k)),np.array(list(v))
cl = np.concatenate(s)
sidx = k.argsort()
idx = np.searchsorted(k,cl, sorter=sidx)
out_of_bounds_mask = idx==len(k)
idx[out_of_bounds_mask] = 0
ssidx = sidx[idx]
invalidmask = k[ssidx] != cl
out_of_bounds_mask |= invalidmask
vals = v[ssidx]
vals[out_of_bounds_mask] = unknownItem
lens = list(map(len,s))
E = [noHistory] # use np.array() if you need outputs for empty entries as arrays
out = []
start = 0
for l in lens:
    if l==0:
        out.append(E)
    else:
        out.append(vals[start:start+l])
        start += l
Run Code Online (Sandbox Code Playgroud)
另一种方法基本上encode_labels来自发布的问题,但经过优化,减少了对输入的访问并避免了 try-catch -
def encode_labels2(X, table, noHistory, unknownItem):
    L0 = len(X)
    res = [[noHistory]]*L0
    for i in range(L0):
        L = len(X[i])
        if L != 0:
            res_i = [unknownItem]*L
            for j in range(L):
                Xij = X[i][j]
                if Xij in table:
                    res_i[j] = table[Xij]
            res[i] = res_i
    return res
Run Code Online (Sandbox Code Playgroud)
然后,我们就可以介绍numba的jit编译了。所以,变化是——
from numba import jit
@jit
def encode_labels2(X, table, noHistory, unknownItem):
# .. function stays the same
Run Code Online (Sandbox Code Playgroud)
几乎不会有警告,这似乎是因为我们使用的是列表而不是数组。这些可以忽略不计。
|   归档时间:  |  
           
  |  
        
|   查看次数:  |  
           532 次  |  
        
|   最近记录:  |