我有一个 Pandas 系列,每行包含一个字符串数组:
0 []
1 []
2 []
3 []
4 [0007969760, 0007910220, 0007910309]
...
243223 []
243224 [0009403370]
243225 [0009403370, 0007190939]
243226 []
243227 []
Name: Item History, Length: 243228, dtype: object
我的目标是在这里做一些简单的序数编码,但要尽可能高效(在时间和内存方面),但有以下注意事项:
[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
这明显好于按行
.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/