numpy 从列表中随机选择,避免给定元素(负采样)

v09wglhw  于 2023-10-19  发布在  其他
关注(0)|答案(3)|浏览(119)

我有一个项目对的列表,其中每个项目都由一个数字id索引。
例如
| 项目1|项目2|
| --|--|
| 1 | 1 |
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 3 | 2 |
| 4 | 5 |
| ......你好。|......你好。|
数字对不一定从0开始排列,并且可能不包括其间的所有值(例如,项目7可能存在,即使项目6不在数据集中)。
我从这个列表中随机抽样,并希望创建多个负样本(即,不存在的样本)。
目前,我的代码看起来像这样,但花的时间太长了:

import pandas as pd
import numpy as np

num_neg_samples = 4

# sample data
rng = np.random.default_rng()

all_pairs = pd.DataFrame({
    'TARGET_ID': rng.choice(1_000_000, size=500_000, replace=True),
    'CONTEXT_ID': rng.choice(1_000_000, size=500_000, replace=True)
})
pair_sample = all_pairs.sample(10240)
print(pair_sample.head())

import time
start = time.time()
targets = np.repeat(pair_sample['TARGET_ID'].values,(num_neg_samples+1),axis=0)
contexts = pair_sample['CONTEXT_ID']
negids = []
print(f'{len(pair_sample["TARGET_ID"])} target_ids to create neg samples for')
for index, target_id in enumerate(pair_sample['TARGET_ID']):
    neg_samples = rng.choice(
        all_pairs.loc[all_pairs['TARGET_ID'] != target_id, 'TARGET_ID'].values, 
        size=num_neg_samples
    )
    negids.append(neg_samples)

print(time.time() - start, 'seconds')

batch = (negatives, contexts, targets)

测试结果:

TARGET_ID  CONTEXT_ID
252373       5238      953345
290732     589947      869541
124135     365468      373147
129140     566125      542728
450409     688717      936377

10240 target_ids to create neg samples for
26.611750602722168 seconds

每轮训练我抽取10240对。因此,我希望最终得到40960个负对,在我的数据集中每个“目标项”有4个。
有没有人有好的方法来加速这段代码?任何帮助都非常感谢。
编辑:随着问题的出现:成对项是一起出现在搜索结果中的项。我想训练一个类似于word 2 vec或自动编码器的模型,它生成一个嵌入,这个嵌入对于在搜索结果中一起出现的项目是相似的。为了改进嵌入,我也想用负样本进行训练,即。不同时出现在搜索结果中的成对项目。
编辑2:请注意,我可用的对可能包括重复,即。相同的一对每个项目ID将在item_1item_2列中出现多次。

af7jpaap

af7jpaap1#

对DataFrame进行切片的成本很高。使用python和sets对我来说快了5倍:

num_neg_samples = 4

targets = set(pair_sample['TARGET_ID'])

negids = [rng.choice(list(targets-{t}), size=num_neg_samples, replace=False)
          for t in pair_sample['TARGET_ID']]

输出量:

[array([563401, 169859, 180204, 953531]),
 array([310634,  27685, 992711, 801847]),
 array([519938, 717685, 150933, 466746]),
 array([496852, 370208, 930286, 293928]),
 ...
]

使用python的random.sample快10倍:

num_neg_samples = 4

targets = set(pair_sample['TARGET_ID'])

negids = [random.sample(list(targets-{t}), k=num_neg_samples)
          for t in pair_sample['TARGET_ID']]
xkftehaa

xkftehaa2#

您可以通过使用concurrent.futures.ThreadPoolExecutor在并行线程中收集过滤后的负样本来加快处理速度。我在我的机器上获得了4-5倍的速度提升(初始方法:~93秒,螺纹:约20秒):

import time
from concurrent import futures
from functools import partial

def select_neg_sample(target_id, all_pairs, size=4):
    return rng.choice(
        all_pairs.loc[all_pairs['TARGET_ID'] != target_id, 'TARGET_ID'].values,
        size=size
    )

start = time.time()
targets = np.repeat(pair_sample['TARGET_ID'].values, (num_neg_samples + 1), axis=0)
contexts = pair_sample['CONTEXT_ID']
print(f'{len(pair_sample["TARGET_ID"])} target_ids to create neg samples for')

with futures.ThreadPoolExecutor() as executor:
    it = executor.map(partial(select_neg_sample, all_pairs=all_pairs),
                      pair_sample['TARGET_ID'])
    neg_ids = list(it)

print(time.time() - start, 'seconds')
print(f'neg_ids size: {len(neg_ids)}, last entry: {neg_ids[-1]}')
TARGET_ID  CONTEXT_ID
294231     704025      731665
22308      430988      862487
484542      70459      196505
119601     762881      339710
309284     459537      433841
10240 target_ids to create neg samples for
19.128324270248413 seconds
neg_ids size: 10240, last entry: [ 33955 538037 312413 692326]
w8biq8rn

w8biq8rn3#

根据@mozway的回答和这个,我在一个相关的帖子中发现:https://stackoverflow.com/a/44349133/5240684,我想出了以下,这是足够的我的情况:

import pandas as pd
import numpy as np
import time

num_neg_samples = 4

# sample data
rng = np.random.default_rng()

all_pairs = pd.DataFrame({
    'TARGET_ID': rng.choice(100_000, size=500_000, replace=True),
    'CONTEXT_ID': rng.choice(100_000, size=500_000, replace=True)
})
pair_sample = all_pairs.sample(10240)
print(pair_sample.head())

start = time.time()
unique_targets = all_pairs['TARGET_ID'].unique()
# index to be able to use range
unique_targets_indexed = dict(zip(unique_targets, range(len(unique_targets))))
        
targets = pair_sample['TARGET_ID'].values
contexts = pair_sample['CONTEXT_ID'].values

negative_targets = np.repeat(pair_sample['TARGET_ID'].values,(num_neg_samples),axis=0)
negative_contexts = [unique_targets[random_neg(0,len(unique_targets), unique_targets_indexed[t])] for t in negative_targets]
print(f'\nTook {time.time() - start} seconds')

测试结果:

TARGET_ID  CONTEXT_ID
284483      70099       74132
164208       3090       13895
186524      89871       81281
270224      46461       56410
470391       7434       81272

Took 0.19039297103881836 seconds

即使从1 mio不同的目标id中采样,我的机器上的处理时间也增加到只有大约0.31秒。
基本思想是将目标id的索引设置为从0到len(unique_targets)的范围,这样我们就可以从一个范围而不是从一个集合中采样一个随机整数,因为这样更快。
这样,我最终得到batch = (contexts, targets, negative_contexts, negative_targets)作为我的批处理。
谢谢你给我指明了正确的方向。

相关问题