Работа с наборами данных с повторяющимися многозначными функциями - программирование
Подтвердить что ты не робот

Работа с наборами данных с повторяющимися многозначными функциями

У нас есть набор данных, который находится в разреженном представлении и имеет 25 функций и 1 двоичную метку. Например, строка набора данных:

Label: 0
exid: 24924687
Features:
11:0 12:1 13:0 14:6 15:0 17:2 17:2 17:2 17:2 17:2 17:2
21:11 21:42 21:42 21:42 21:42 21:42 
22:35 22:76 22:27 22:28 22:25 22:15 24:1888
25:9 33:322 33:452 33:452 33:452 33:452 33:452 35:14

Итак, иногда функции имеют несколько значений, и они могут быть одинаковыми или разными, и веб-сайт сообщает:

Некоторые категориальные функции многозначны (порядок не имеет значения)

Мы не знаем, какова семантика функций и какое значение им присвоено (из-за некоторой озабоченности по поводу конфиденциальности они скрыты от общественности)

Мы знаем только:

  • Label означает, что пользователь нажал на рекомендованное объявление или нет.
  • Features описывает продукт, который был рекомендован пользователю.
  • Task предназначен для прогнозирования вероятности получения клика пользователем по объявлению продукта.

Любые комментарии по следующим проблемам приветствуются:

  1. Какой лучший способ импортировать такие наборы данных в структуру данных Python.
  2. Как работать с многозначными объектами, особенно если они имеют похожие значения, повторенные k раз?
4b9b3361

Ответ 1

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

Насколько я не могу сказать из документации, что @RootTwo приятно ссылается в своем комментарии, вы на самом деле имеете дело с двумя наборами данных: одним примером плоской таблицы и одной таблицей продуктов. (Вы можете позже присоединиться к двум, чтобы получить одну таблицу, если это необходимо.)

Давайте сначала создадим парсеры, которые декодируют разные строки в несколько информативную структуру данных:

Для строк с примерами мы можем использовать:

def process_example(example_line):
    # example ${exID}: ${hashID} ${wasAdClicked} ${propensity} ${nbSlots} ${nbCandidates} ${displayFeat1}:${v_1}
    #    0        1         2           3               4          5            6               7 ...
    feature_names = ['ex_id', 'hash', 'clicked', 'propensity', 'slots', 'candidates'] + \
                    ['display_feature_' + str(i) for i in range(1, 11)]
    are_numbers = [1, 3, 4, 5, 6]
    parts = example_line.split(' ')
    parts[1] = parts[1].replace(':', '')
    for i in are_numbers:
        parts[i] = float(parts[i])
        if parts[i].is_integer():
            parts[i] = int(parts[i])
    featues = [int(ft.split(':')[1]) for ft in parts[7:]]
    return dict(zip(feature_names, parts[1:7] + featues))

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

{'ex_id': 20184824,
 'hash': '57548fae76b0aa2f2e0d96c40ac6ae3057548faee00912d106fc65fc1fa92d68',
 'clicked': 0,
 'propensity': 1.416489e-07,
 'slots': 6,
 'candidates': 30,
 'display_feature_1': 728,
 'display_feature_2': 90,
 'display_feature_3': 1,
 'display_feature_4': 10,
 'display_feature_5': 16,
 'display_feature_6': 1,
 'display_feature_7': 26,
 'display_feature_8': 11,
 'display_feature_9': 597,
 'display_feature_10': 7}

Далее приведены примеры продуктов. Как вы упомянули, проблема заключается в множественном вхождении значений. Я думаю, что имеет смысл объединять уникальные пары "свойство-значение" по частоте. Информация не теряется, но она помогает нам кодировать аккуратный образец. Это должно ответить на ваш второй вопрос.

import toolz  # pip install toolz

def process_product(product_line):
    # ${wasProduct1Clicked} exid:${exID} ${productFeat1_1}:${v1_1} ...
    parts = product_line.split(' ')
    meta = {'label': int(parts[0]),
            'ex_id': int(parts[1].split(':')[1])}
    # extract feautes that are ${productFeat1_1}:${v1_1} separated by ':' into a dictionary
    features = [('product_feature_' + str(i), int(v))
                for i, v in map(lambda x: x.split(':'), parts[2:])]
    # count each unique value and transform them into
    # feature_name X feature_value X feature_frequency
    products = [dict(zip(['feature', 'value', 'frequency'], (*k, v)))
                for k, v in toolz.countby(toolz.identity, features).items()]
    # now merge the meta information into each product
    return [dict(p, **meta) for p in products]

который в основном извлекает метку и функции для каждого примера (пример для строки 40):

[{'feature': 'product_feature_11',
  'value': 0,
  'frequency': 1,
  'label': 0,
  'ex_id': 19168103},
 {'feature': 'product_feature_12',
  'value': 1,
  'frequency': 1,
  'label': 0,
  'ex_id': 19168103},
 {'feature': 'product_feature_13',
  'value': 0,
  'frequency': 1,
  'label': 0,
  'ex_id': 19168103},
 {'feature': 'product_feature_14',
  'value': 2,
  'frequency': 1,
  'label': 0,
  'ex_id': 19168103},
 {'feature': 'product_feature_15',
  'value': 0,
  'frequency': 1,
  'label': 0,
  'ex_id': 19168103},
 {'feature': 'product_feature_17',
  'value': 2,
  'frequency': 2,
  'label': 0,
  'ex_id': 19168103},
 {'feature': 'product_feature_21',
  'value': 55,
  'frequency': 2,
  'label': 0,
  'ex_id': 19168103},
 {'feature': 'product_feature_22',
  'value': 14,
  'frequency': 1,
  'label': 0,
  'ex_id': 19168103},
 {'feature': 'product_feature_22',
  'value': 54,
  'frequency': 1,
  'label': 0,
  'ex_id': 19168103},
 {'feature': 'product_feature_24',
  'value': 3039,
  'frequency': 1,
  'label': 0,
  'ex_id': 19168103},
 {'feature': 'product_feature_25',
  'value': 721,
  'frequency': 1,
  'label': 0,
  'ex_id': 19168103},
 {'feature': 'product_feature_33',
  'value': 386,
  'frequency': 2,
  'label': 0,
  'ex_id': 19168103},
 {'feature': 'product_feature_35',
  'value': 963,
  'frequency': 1,
  'label': 0,
  'ex_id': 19168103}]

Итак, когда вы обрабатываете ваш поток построчно, вы можете решить, отображать ли пример или продукт:

def process_stream(stream):
    for content in stream:
        if 'example' in content:
            yield process_example(content)
        else:
            yield process_product(content)

Я решил сделать генератор здесь, потому что он будет полезен для обработки данных функциональным способом, если вы решите не использовать pandas. В противном случае сжатие списка будет будь жареным.

Теперь для забавной части: мы читаем строки из данного (примера) URL-адреса одну за другой и назначьте их в соответствующие им наборы данных (пример или продукт). Я буду использовать reduce здесь, потому что это весело :-). Я не буду вдаваться в подробности того, что на самом деле делает map/reduce (это ваше дело). Вместо этого вы всегда можете использовать простой цикл for.

import urllib.request
import toolz  # pip install toolz

lines_stream = (line.decode("utf-8").strip() 
                for line in urllib.request.urlopen('http://www.cs.cornell.edu/~adith/Criteo/sample.txt'))

# if you care about concise but hacky approach you could do:
# blubb = list(toolz.partitionby(lambda x: 'hash' in x, process_file(lines_stream)))
# examples_only = blubb[slice(0, len(blubb), 2)]
# products_only = blubb[slice(1, len(blubb), 2)]

# but to introduce some functional approach lets implement a reducer
def dataset_reducer(datasets, content):
    which_one = 0 if 'hash' in content else 1
    datasets[which_one].append(content)
    return datasets

# and process the stream using the reducer. Which results in two datasets:
examples_dataset, product_dataset = toolz.reduce(dataset_reducer, process_stream(lines), [[], []])

Отсюда вы можете преобразовать свои наборы данных в аккуратный фрейм данных, который вы можете использовать для применения машинного обучения. Остерегайтесь NaN/пропущенных значений, распределений и т.д. Вы можете объединить два набора данных с merge, чтобы получить одну большую плоскую таблицу образцов X объектов. Тогда вы сможете более или менее использовать разные методы, например, scikit-learn.

import pandas

examples_dataset = pandas.DataFrame(examples_dataset)
product_dataset = pandas.concat(pandas.DataFrame(p) for p in product_dataset)

Примеры набора данных

   candidates  clicked  ...    propensity  slots
0          30        0  ...  1.416489e-07      6
1          23        0  ...  5.344958e-01      3
2          23        1  ...  1.774762e-04      3
3          28        0  ...  1.158855e-04      6

Набор данных продукта (product_dataset.sample(10))

       ex_id             feature  frequency  label  value
6   10244535  product_feature_21          1      0     10
9   37375474  product_feature_25          1      0      4
6   44432959  product_feature_25          1      0    263
15  62131356  product_feature_35          1      0     14
8   50383824  product_feature_24          1      0    228
8   63624159  product_feature_20          1      0     30
3   99375433  product_feature_14          1      0      0
9    3389658  product_feature_25          1      0     43
20  59461725  product_feature_31          8      0      4
11  17247719  product_feature_21          3      0      5

Помните о product_dataset. Вы можете "поворачивать" свои объекты в строках в виде столбцов (см. изменение формы документов).

Ответ 2

Пример файла имеет некоторые интересные функции в каждом примере. Выровненный в диктовке, каждый пример выглядит примерно так:

{'ex_id': int,
 'hash': str,
 'clicked': bool,
 'propensity': float,
 'slots': int,
 'candidates': int,
 'display_feature_1': [int],
 'display_feature_2': [int],
 'display_feature_3': [int],
 'display_feature_4': [int],
 'display_feature_5': [int],
 'display_feature_6': [int],
 'display_feature_7': [int],
 'display_feature_8': [int],
 'display_feature_9': [int],
 'display_feature_10': [int],
 'display_feature_11': [int],
 'display_feature_12': [int],
 'display_feature_13': [int],
 'display_feature_14': [int],
 'display_feature_15': [int],
 'display_feature_16': [int],
 'display_feature_17': [int],
 'display_feature_18': [int],
 'display_feature_19': [int],
 'display_feature_20': [int],
 'display_feature_21': [int],
 'display_feature_22': [int],
 'display_feature_23': [int],
 'display_feature_24': [int],
 'display_feature_25': [int],
 'display_feature_26': [int],
 'display_feature_27': [int],
 'display_feature_28': [int],
 'display_feature_29': [int],
 'display_feature_30': [int],
 'display_feature_31': [int],
 'display_feature_32': [int],
 'display_feature_33': [int],
 'display_feature_34': [int],
 'display_feature_35': [int]
}

посредством чего функции 1-35 могут присутствовать или не присутствовать, и каждый из них может или не может повторяться. Разумная вещь, которую нужно сделать для набора данных такого размера, это сохранить его как list из tuple с, откуда каждый tuple соответствует одному примеру ID, например:

(
  int, # exid
  str, # hash
  bool, # clicked
  float, # propensity
  int, # slots
  int, # candidates
  dict # the display features
)

Подходящей структурой dict для 35 функций отображения является

{k+1 : [] for k in range(35)}

В целом, этот пример структуры данных может быть обобщен в виде списка кортежей, причем последний элемент в каждом кортеже является словарем.

Предполагая, что у вас есть sample.txt локально, вы можете заполнить эту структуру следующим образом:

examples = []
with open('sample.txt', 'r') as fp:
    for line in fp:

        line = line.strip('\n')

        if line[:7] == 'example':
            items = line.split(' ')
            items = [item.strip(':') for item in items]
            examples.append((
                int(items[1]),                  # exid
                items[2],                       # hash
                bool(items[3]),                 # clicked
                float(items[4]),                # propensity
                int(items[5]),                  # slots
                int(items[6]),                  # candidates 
                {k+1 : [] for k in range(35)}   # the display features
            ))
            for k in range(10):
                examples[-1][6][k+1].append(int(items[k+7].split(':')[1]))

        else:
            items = line.split(' ')
            while len(items) > 2:
                keyval = items.pop()
                key = int(keyval.split(':')[0])
                val = int(keyval.split(':')[1])
                examples[-1][6][key].append(val)

Эта структура данных записей может быть преобразована в JSON и прочитана в пустой массив. Вы можете легко отсортировать его по любому из элементов в каждом из кортежей и быстро выполнить итерации.

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