В области науки о данных мы можем столкнуться со сценариями, когда нам нужно прочитать большой набор данных, размер которого превышает размер системной памяти. В этом случае вашей системе не хватит RAM / памяти при чтении такого огромного количества данных. Это также может привести к завершению работы ядра в ноутбуке jupyter или к сбою системы. Чтобы избежать таких сценариев, есть несколько довольно хороших методов, которые помогут нам читать большие наборы данных.
Для любого специалиста по данным очевидным выбором пакета python для чтения файла CSV будет pandas. В этом блоге мы расскажем о некоторых интересных методах чтения этих наборов данных.
Прежде чем мы углубимся в эти методы, было бы лучше, если бы у нас была настроена и готова наша среда:
conda install -c conda-forge jupyterlab
conda install -c anaconda pandas
jupyter notebook
Данные
Я использую набор данных конкурса kaggle PUBG Finish Placement Prediction (только ядра). Для анализа мы будем использовать обучающий набор данных. Учебный набор состоит из 4,4 миллиона строк, что в сумме составляет до 700 МБ данных!
Методы
Использование обычного метода pandas для чтения набора данных
>>>> pd.read_csv('train_V2.csv')
Это стандартный метод чтения файла CSV. Теперь давайте посмотрим, сколько времени занимает выполнение этого фрагмента кода. С помощью волшебных команд IPython:
>>>> %time pd.read_csv('train_V2.csv') CPU times: user 16.5 s, sys: 1.33 s, total: 17.8 s Wall time: 27.5 s
Это заняло у нас около 27 секунд, чтобы прочитать наш тренировочный набор. Тем не менее, мы можем увеличить скорость и сократить время стены.
Чтобы рассчитать объем памяти, потребляемой этим методом, мы можем использовать волшебные команды, которые доступны в расширении memory_profiler
IPython и называются %memit
>>>> %memit pd.read_csv('train_V2.csv') peak memory: 3085.73 MiB, increment: 3001.68 MiB
Здесь пиковая память указывает количество памяти, потребляемой read_csv
функцией. Для 700 МБ обучающих данных потребовалось около 3 ГБ памяти! Давайте посмотрим на наш следующий метод, чтобы узнать, сколько памяти он потребляет.
Использование метода модификации типа данных для чтения данных из набора данных
Этот метод просто включает в себя изменение типов данных каждого столбца нашего набора данных на типы данных, потребляющие меньше памяти. В нашем примере давайте сначала перечислим все типы данных в нашем обучающем наборе.
Сначала нам нужно знать, каков текущий тип данных обучающего набора. Мы больше не будем читать обучающий набор традиционным способом. Но мы возьмем несколько образцов обучающей выборки и сохраним их в другом файле. Для этого введите следующую команду в ячейку записной книжки jupyter:
!head train_V2.csv > sample_train.csv
Команда head
напечатает первые 10 строк файла train_V2.csv
и добавит его стандартный вывод в sample_train.csv
. Итак, теперь, когда у нас есть тренировочный набор с подвыборкой, мы можем легко прочитать набор данных с помощью обычной функции pandas.read_csv
:
sample_train = pd.read_csv('sample_train.csv') dict(zip(sample_train.columns,sample_train.dtypes))
Последняя строка предоставит нам словарь имен столбцов по их соответствующему типу данных:
{'Id': dtype('O'), 'groupId': dtype('O'), 'matchId': dtype('O'), 'assists': dtype('int64'), 'boosts': dtype('int64'), 'damageDealt': dtype('float64'), 'DBNOs': dtype('int64'), 'headshotKills': dtype('int64'), 'heals': dtype('int64'), 'killPlace': dtype('int64'), 'killPoints': dtype('int64'), 'kills': dtype('int64'), 'killStreaks': dtype('int64'), 'longestKill': dtype('float64'), 'matchDuration': dtype('int64'), 'matchType': dtype('O'), 'maxPlace': dtype('int64'), 'numGroups': dtype('int64'), 'rankPoints': dtype('int64'), 'revives': dtype('int64'), 'rideDistance': dtype('float64'), 'roadKills': dtype('int64'), 'swimDistance': dtype('float64'), 'teamKills': dtype('int64'), 'vehicleDestroys': dtype('int64'), 'walkDistance': dtype('float64'), 'weaponsAcquired': dtype('int64'), 'winPoints': dtype('int64'), 'winPlacePerc': dtype('float64')}
Как правило, всякий раз, когда загружается набор данных и pandas находит какие-либо числовые столбцы, по умолчанию ему присваивается тип данных int64 или float64. Поскольку по умолчанию используются 64-битные типы данных, вы можете даже представить, сколько места потребуется для наборов данных с более чем миллионами строк. Для понимания в следующей таблице будет показан диапазон значений, разрешенных для типов данных int и float:
int8
Byte (-128 to 127)int16
Integer (-32768 to 32767)int32
Integer (-2147483648 to 2147483647)int64
Integer (-9223372036854775808 to 9223372036854775807) float32 Half precision float64 Full precision
Для получения дополнительной информации о типах данных, используемых в пандах, обратитесь к документации pandas и numpy.
Очевидно, что для больших типов данных потребуется больше места. В нашем сценарии имеется 19 int64, 6 float64 and 4 object
столбцов с типом данных. Теперь давайте создадим словарь, который будет содержать int16 and float16
в качестве типов данных для всех столбцов типа integer и float.
dtype_list = list() for x in sample_train.dtypes.tolist(): if x=='int64': dtype_list.append('int16') elif(x=='float64'): dtype_list.append('float16') else: dtype_list.append('object') dtype_list = dict(zip(sample_train.columns.tolist(),dtype_list)) dtype_list
Приведенный выше фрагмент кода поможет нам получить словарь типов данных, аналогичный приведенному ниже:
{'Id': 'object', 'groupId': 'object', 'matchId': 'object', 'assists': 'int16', 'boosts': 'int16', 'damageDealt': 'float16', 'DBNOs': 'int16', 'headshotKills': 'int16', 'heals': 'int16', 'killPlace': 'int16', 'killPoints': 'int16', 'kills': 'int16', 'killStreaks': 'int16', 'longestKill': 'float16', 'matchDuration': 'int16', 'matchType': 'object', 'maxPlace': 'int16', 'numGroups': 'int16', 'rankPoints': 'int16', 'revives': 'int16', 'rideDistance': 'float16', 'roadKills': 'int16', 'swimDistance': 'float16', 'teamKills': 'int16', 'vehicleDestroys': 'int16', 'walkDistance': 'float16', 'weaponsAcquired': 'int16', 'winPoints': 'int16', 'winPlacePerc': 'float16'}
Теперь, когда у нас есть собственный словарь, который состоит из меньших типов данных, давайте посмотрим, как мы можем включить этот тип данных dict в функцию read_csv
, чтобы pandas читал наш обучающий набор именно так, как мы хотим. т.е. столбцы с меньшими типами данных.
>>>> %time pd.read_csv('train_V2.csv',dtype=dtype_list) CPU times: user 13.4 s, sys: 667 ms, total: 14.1 s Wall time: 16.6 s
Это заняло около 16,6 секунд, чтобы прочитать весь набор обучающих данных. Если мы сравним это время с нашим тривиальным методом, то это примерно 40% увеличение скорости чтения набора обучающих данных.
Теперь пришло время снова вычислить потребляемую память, используя ту же волшебную команду в предыдущем методе:
>>>> %memit pd.read_csv('train_V2.csv',dtype=dtype_list) peak memory: 1787.43 MiB, increment: 1703.09 MiB
Таким образом, этот метод потреблял почти половину памяти по сравнению с нашим традиционным методом, что неплохо.
Использование метода создания чанка для чтения CSV
Этот метод включает чтение данных по частям с параметром chunksize
в функции read_csv
. Давайте создадим размер блока, чтобы читать наш набор данных с помощью этого метода:
>>>> chunk_size = 10**6 >>>> chunk_size 1000000
Давайте разделим наш набор данных на блоки по 1000000. Таким образом, наш набор данных будет разделен на 4 блока размером 1000000, а размер последнего блока будет вычислен пандами. Теперь посчитаем, сколько времени занимает этот метод:
>>>> %timeit [chunk for chunk in pd.read_csv('train_V2.csv',chunksize=chunk_size)] 29.4 s ± 2.26 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
Таким образом, для загрузки данных каждого фрагмента потребовалось около 30 секунд, то есть всего 150 секунд для всех 5 фрагментов.
Также этим методом потребляется память:
>>>> %memit [chunk for chunk in pd.read_csv('train_V2.csv',chunksize=chunk_size)] peak memory: 1966.75 MiB, increment: 1880.20 MiB
Сравнение времени и памяти, затраченных всеми методами:
Заключение
В общем, это полностью зависит от набора данных, который вы пытаетесь прочитать. Метод 2, в котором мы обсуждали изменение типа данных столбцов в фрейме данных во время чтения, наверняка может быть использован в сценариях, где у вас мало системных ресурсов, или в соревнованиях kaggle. Но я думаю, что у этого метода есть одно предостережение: возможна некоторая потеря данных, поскольку мы сжимаем допустимый диапазон чисел по мере того, как мы переходим от большого набора данных к небольшому. Если скорость не так важна, и вы хотите сохранить целостность данных, рекомендуется использовать метод 3.
Спасибо Введение в курс машинного обучения от fast.ai и их классным форумам за то, что вдохновили меня на написание этого блога.
Вы можете найти мой код для этого сообщения в блоге:
Пожалуйста, дайте мне знать, как вам нравится этот пост в блоге, в разделе комментариев, или вы можете связаться со мной для любых конструктивных отзывов о моем твиттере @keurplkar