Как работать с большими, но не большими наборами данных? - PullRequest
4 голосов
/ 05 февраля 2020

У меня есть набор данных ~ 200 ГБ приблизительно 1,5 млрд наблюдений, на котором мне нужно провести некоторый условный анализ и агрегирование данных *.

Дело в том, что я не привык (и не обучен обращаться с ним) ) большие наборы данных. Я обычно работаю над R или Python (с какой-то Джулией на стороне), и я полностью теряюсь, когда просто не могу вписать набор данных в память.

Как люди обрабатывают эти наборы данных, которые помещаются на диск, но не в память? С чего мне начать искать решения? Есть ли место, где информация о больших, но не больших наборах данных централизована?

* Короче говоря, у меня есть другой набор данных (который помещается в память), и для каждой строки этого небольшого набора данных я хочу подсчитать количество наблюдений в большом наборе данных, которые соответствуют некоторым условиям из небольшого набора данных , Моя первоначальная реакция состояла в том, чтобы запускать код кусками, но это очень неэффективно и потребовало бы столетий монопроцессорных вычислений.

Поскольку он был специально задан, я опишу структуру моего файла.

У меня большой файл, назовем его БОЛЬШИМ, с (в частности) двумя переменными идентификатора, $ ID0 $ и $ ID1 $ и переменная даты $ date1 $ .

У меня есть небольшой файл, назовем его МАЛЫМ, с двумя переменными идентификатора, $ ID2 $ и $ ID3 $ и переменную даты $ date2 $ .

Для каждого $ ID2_i $ я хочу подсчитать все наблюдения так, чтобы $ \ {ID0 = ID2_i, date1

Ответы [ 3 ]

3 голосов
/ 05 февраля 2020

Существуют различные методы

Упорядочение набора данных (экономит время в будущем, но требует первоначальных затрат времени)

Разделение на части позволяет упростить многие операции, такие как тасование и т. Д.

Убедитесь, что каждый поднабор / блок является представительным для всего набора данных. Каждый файл чанка должен иметь одинаковое количество строк.

Это можно сделать, добавив строку в один файл за другим. Быстро, вы поймете, что неэффективно открывать каждый файл и писать строки. Особенно при чтении и записи на одном диске.
-> добавить буфер записи и чтения, который помещается в память.

enter image description here
enter image description here

Выберите размер, соответствующий вашим потребностям. Я выбрал именно этот размер, потому что мой текстовый редактор по умолчанию все еще может открывать его довольно быстро.

Меньшие куски могут повысить производительность, особенно если вы хотите получить метрики, такие как распределение классов, потому что вам нужно только l oop через один репрезентативный файл, чтобы получить оценку всего набора данных, которого может быть достаточно.
Большие фрагменты имеют лучшее представление всего набора данных в каждом файле, но вы также можете просто go через x меньшие фрагменты.

Я использую c# для этого, потому что у меня там больше опыта, и поэтому я могу использовать полный набор функций, таких как разбиение задач reading / processing / writing на разные потоки.

Если у вас есть опыт использования python или r, я подозреваю, что должны быть аналогичные функции. Распараллеливание может быть огромным фактором для таких больших наборов данных.

Наборы фрагментированных данных могут быть смоделированы в один набор данных с чередованием, который можно обрабатывать с помощью тензорных единиц обработки. Это, вероятно, даст один из лучших показателей и может быть выполнен локально, а также в облаке на действительно больших машинах. Но для этого нужно много узнать о тензорном потоке.

Использование ридера и пошаговое чтение файла

вместо того, чтобы делать что-то вроде all_of_it = file.read(), вы хотите использовать какой-нибудь потоковый ридер. Следующая функция читает построчно один из файлов фрагментов (или весь ваш набор данных объемом 300 ГБ), чтобы подсчитать каждый класс в файле. Обрабатывая по одной строке за раз, ваша программа не будет переполнять память.

Возможно, вы захотите добавить некоторые индикаторы прогресса, такие как X строк / с или X MBbs, чтобы оценить общее время процесса. .

def getClassDistribution(path):
    classes = dict()
    # open sample file and count classes
    with open(path, "r",encoding="utf-8",errors='ignore') as f:
        line = f.readline()
        while line:
            if line != '':
                labelstring = line[-2:-1]
                if labelstring == ',':
                    labelstring = line[-1:]
                label = int(labelstring)
                if label in classes:
                    classes[label] += 1
                else:
                    classes[label] = 1
            line = f.readline()
    return classes

enter image description here

Я использую комбинацию наборов данных и оценки.

Подводные камни для производительности

  • всякий раз, когда это возможно, избегайте вложенных циклов. Каждый l oop внутри другого l oop умножает сложность на n
  • , когда это возможно, обрабатывает данные за один go. Каждое l oop за другим добавляет сложность n
  • , если ваши данные поступают в формате csv, избегайте готовых функций, таких как cells = int(line.Split(',')[8]), это очень быстро приведет к узкому месту в пропускной способности памяти. Один правильный пример этого можно найти в getClassDistribution, где я хочу получить только метку.

следующая C# функция разбивает строку csv на элементы очень быстро.

// Call function
ThreadPool.QueueUserWorkItem((c) => AnalyzeLine("05.02.2020,12.20,10.13").Wait());

// Parralelize this on multiple cores/threads for ultimate performance
private async Task AnalyzeLine(string line)
{
    PriceElement elementToAdd = new PriceElement();
    int counter = 0;
    string temp = "";
    foreach (char c in line)
    {
        if (c == ',')
        {
            switch (counter)
            {
                case 0:
                    elementToAdd.spotTime = DateTime.Parse(temp, CultureInfo.InvariantCulture);
                    break;
                case 1:
                    elementToAdd.buyPrice = decimal.Parse(temp);
                    break;
                case 2:
                    elementToAdd.sellPrice = decimal.Parse(temp);
                    break;
            }
            temp = "";
            counter++;
        }
        else temp += c;
    }
    // compare the price element to conditions on another thread
    Observate(elementToAdd);
}

Создайте базу данных и загрузите данные

при обработке данных в формате csv вы можете загрузить данные в базу данных.
Базы данных созданы для размещения большого количества данных, и вы можете ожидать очень высоких производительность.
База данных, скорее всего, займет больше места на диске, чем необработанные данные. Это одна из причин, почему я отказался от использования базы данных.

Оптимизация оборудования

Если ваш код хорошо оптимизирован, узким местом, скорее всего, будет пропускная способность жесткого диска.

  • Если данные помещаются на локальный жесткий диск, используйте их локально, так как это избавит от задержек в сети (представьте 2-5 мс для каждой записи в локальной сети и 10-100 мс в удаленных местах) .
  • Используйте современный жесткий диск. Сегодня твердотельный накопитель NVME емкостью 1 ТБ стоит около 130 (Intel 600p 1 ТБ). Nsme ssd использует p cie и примерно в 5 раз быстрее, чем обычный ssd, и в 50 раз быстрее, чем обычный жесткий диск, особенно при быстрой записи в разные места (разбивка данных). В последние годы твердотельные накопители значительно расширились, и для такой задачи это было бы дикостью.

На следующем снимке экрана приведено сравнение производительности тренировки тензорного потока с теми же данными на той же машине. Только один раз сохраненный локально на стандартном ssd и один раз на сетевом хранилище в локальной сети (обычный жесткий диск).
enter image description here
enter image description here

3 голосов
/ 05 февраля 2020

Возможно, я неправильно понимаю вашу проблему, но разбиение большого файла (как уже предлагалось в комментариях) кажется мне наиболее простым подходом.

Скажем, вы разделяете файл размером 200 ГБ на 100 кусков, затем выполняете итерацию по каждому чанку и для каждого чанка сделайте желаемый подсчет, а затем агрегируйте результаты. Если операция для каждого чанка выполняется за несколько минут, у вас все будет хорошо, если вы не хотите делать это снова и снова.

Для получения более конкретных предложений, мне нужно знать немного больше о формате хранения данных , Мы говорим о большом .csv файле? В этом случае для R вы можете посмотреть на chunked API пакета readr . Для максимально быстрого подсчета в R может пригодиться пакет data.table.

Редактировать: добавить пример кода

Это не будет делайте именно то, что вы просили, но, надеюсь, охватите некоторые ключевые моменты, чтобы найти решение, как я и предлагал.

library(data.table)
library(readr)

ids <- seq.int(1, 1e2)
dates <- seq(as.Date("1999/01/01"), as.Date("2000/01/01"), by = "day")

big <- data.table(id0 = sample(ids, 1e6, replace = TRUE),
                  id1 = sample(ids, 1e6, replace = TRUE),
                  date1 = sample(dates, 1e6, replace = TRUE))

write.csv(big, "big.csv", row.names = FALSE)

small <- data.table(id2 = sample(ids, 1e2),
                    id3 = sample(ids, 1e2),
                    date2 = sample(dates, 1e2))

count_fun <- function(x, pos, acc) {
  setDT(x)
  tmp <- small[x, list(counts = .N),
               on = c("id2 == id0", "id3 == id1", "date2 > date1"),
               by = .EACHI, nomatch = NULL]
  acc[tmp<span class="math-container">$id2] <- acc[tmp$</span>id2] + tmp$counts
  acc
}

accumulator <- AccumulateCallback$new(count_fun, acc = rep(0, length(ids)))

counts <- read_csv_chunked("big.csv", accumulator, chunk_size = 1e4)
1 голос
/ 06 февраля 2020

Похоже на проблему O (n ^ 2): каждый элемент в BIG должен сравниваться со всеми остальными в BIG.

Возможно, вы можете уместить все поля, необходимые в памяти для сравнения (оставив в файл остальное). Например: 1,5 ГБ наблюдения x 1 дата (4 байта) x 2 идентификатора (8 байтов) могут уместиться в 18 ГБ.

Возможно, вы можете отсортировать БОЛЬШОЙ по дате, и тогда ваша проблема станет O (nx log (n) ).

Может быть, вы можете разделить БОЛЬШОЙ на куски, где ID3i = ID3j.

Есть много возможностей.

...