处理包含重复多值特征的数据集。

11

我们有一个数据集(样例),它采用稀疏表示法,包含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 次时?

你尝试过什么?你看过http://www.cs.cornell.edu/~adith/Criteo的代码吗? - RootTwo
@RootTwo,是的。他们提供了一个脚本,可以生成Vowpal Wabbit输入格式的数据集。我正在寻找一个更通用的解决方案,可用于其他机器学习库。 - Mo-
2个回答

7

这是一个非常普遍的问题,但据我所知,如果您想使用一些机器学习方法,最好先将数据转换为整洁数据格式

从@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可能存在,也可能不存在,并且每个特征都可能重复。对于这样大小的数据集,一个合理的做法是将其存储为元组列表,其中每个元组对应一个示例ID,如下所示:

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

35个显示特征的适当的 dict 结构是:

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

总的来说,这个数据结构可以概括为一个元组列表,其中每个元组的最后一个元素是一个字典。

假设您本地有sample.txt文件,您可以按照以下方式填充此结构:

最初的回答:

总体而言,这个数据结构可以被概括为一个元组列表,其中每个元组的最后一个元素是一个字典。

如果你本地有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,并读入numpy数组。你可以基于元组中任意一个元素轻松地对它进行排序,也可以快速迭代。

处理多值记录项的方法是将它们存储在列表字典中。这样可以轻松地收集它们的统计信息。


注:Original Answer翻译成"最初的回答"并不符合原文语义,因此未采纳。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接