У меня есть серия 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]
). - Кодировка должна быть каким-то образом сохранена, чтобы в будущем я мог идентично кодировать другие списки
- Если эти будущие списки содержат строку, которой нет в исходных входных данных, она должна закодировать свое собственное отдельное целое число, чтобы обозначить «Никогда не видел это до сопряжения».
Очевидный вопрос: «Почему Вы не просто используете OrdinalEncoder из sklearn ". Что ж, кроме отсутствия обработчика неизвестных элементов, на самом деле также ужасно медленно применять его по строкам таким образом (нам нужно было бы разместить его в объединенном одном массиве всех отдельных строк, а затем использовать Series.apply(lambda x: oe.transform(x))
для преобразования каждой строки ), потому что для построения таблицы сопоставления для каждой строки требуется определенное диктовочное понимание, а для этого требуется время. Не очень много времени на вызов , всего около 0,01 секунды, но это все еще слишком медленно для объема данных, которые у меня есть.
Одно из решений - вывести это изощренное понимание из часть каждой строки и создайте таблицу сопоставления перед циклом по строкам, как в этой функции:
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()
, но все же не самый быстрый фрагмент кода. Я могу цитонизировать его и выполнить ряд других оптимизаций, чтобы ускорить процесс, но это не на порядок лучше:
%%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 или что-то, это здорово. Этот код станет частью предварительной обработки для нейронного net, поэтому в этот момент также есть несколько графических процессоров, которые ждут данных; если вы можете сделать это, используйте их, тем лучше. Многопроцессорная обработка также может быть вариантом, который мне пока не удалось изучить, но проблема в том, что для этого требуется копия строки: таблица отображения int на процесс, а) медленная генерация и б) использование большого количества памяти. ,
РЕДАКТИРОВАТЬ :
Забыл предоставить некоторые данные. Вы можете запустить следующее, чтобы получить входной набор данных, который похож на мой формат:
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 строк, но этого должно быть достаточно, чтобы точно определить, какой метод быстрее.