[ML] Negative sampling

chrisjune
7 min readDec 4, 2024

--

대부분의 데이터는 User feedback의 implicit data인 경우가 많습니다. 이럴 경우 negative sampling을 하여 학습을 위한 전처리 작업을 합니다.

아래에는 가장 일반적인 Negative sampling을 하는 코드예시입니다.

  1. 샘플링을 위해 예시 데이터로 유저4만명, 상품2만개, Interaction 20만개를 가정합니다.
import pandas as pd
import numpy as np
import random

random.seed(42)
np.random.seed(42)

num_users = 40000 # Number of users
num_items = 20000 # Number of items
num_interactions = 200000 # Number of positive interactions

user_ids = np.arange(1, num_users + 1)
item_ids = np.arange(1, num_items + 1)

positive_user_ids = np.random.choice(user_ids, size=num_interactions, replace=True)
positive_item_ids = np.random.choice(item_ids, size=num_interactions, replace=True)

timestamps = pd.date_range(start='2024-01-01', periods=100000, freq='T')
random_timestamps = np.random.choice(timestamps, size=num_interactions, replace=True)

# Create the interactions DataFrame
interactions = pd.DataFrame({
'UserId': positive_user_ids,
'ItemId': positive_item_ids,
'Time': random_timestamps
})

2. Negative sample을 고를때 제외하기 위하여 Interaction을 set으로 만들어줍니다.

user_item_dict = interactions.groupby('UserId')['ItemId'].apply(set).to_dict()

3. 전체 User를 순회하며 전체상품에서 Interaction을 제외한 상품을 샘플링하는 메서드를 만들어줍니다.

from tqdm import tqdm 
def negative_sampling(user_ids, item_ids, user_item_dict, num_negatives=4):
negative_samples = []
all_items = set(item_ids)

for user_id in tqdm(user_ids):
interacted_items = user_item_dict.get(user_id, set())
non_interacted_items = all_items - interacted_items

if len(non_interacted_items) >= num_negatives:
negatives = random.sample(non_interacted_items, num_negatives)
else:
negatives = list(non_interacted_items)

for item_id in negatives:
negative_samples.append({'UserId': user_id, 'ItemId': item_id, 'Label': 0})
return negative_samples

unique_user_ids = interactions['UserId'].unique()
unique_item_ids = interactions['ItemId'].unique()

negatives = negative_sampling(unique_user_ids, unique_item_ids, user_item_dict, num_negatives=4)

negatives_df = pd.DataFrame(negatives)

수행시간은 M1맥북프로기준 15초정도 소요되었습니다.

Triplet으로 Negative sampling을 하려면 아래와 같이 메서드를 만드시면 됩니다.

from tqdm import tqdm 
def negative_sampling(user_ids, item_ids, user_item_dict):
negative_samples = []
all_items = set(item_ids)
triplets = []
for user_id in tqdm(user_ids):
postive_items = user_item_dict.get(user_id, set())
negative_items = all_items - postive_items

# If there are enough non-interacted items, sample num_negatives items
if negative_items:
for positive_item in postive_items:
neg_item = np.random.choice(list(negative_items))
triplets.append({"userId": user_id, "posItemId": positive_item, "negItemId": neg_item})
return triplets

# Get unique user IDs and item IDs from the interactions
unique_user_ids = interactions['UserId'].unique()
unique_item_ids = interactions['ItemId'].unique()

# Generate negative samples
negatives = negative_sampling(unique_user_ids, unique_item_ids, user_item_dict, num_negatives=4)

# Convert negative samples to DataFrame
negatives_df = pd.DataFrame(negatives)

수행시간은 M1맥북프로기준 6분정도 소요되었습니다.

위의 예시처럼 전체 상품에서 Negative를 뽑을 수도 있지만, 데이터가 훨씬 더 많고 sparse하고, 학습속도를 효율적으로 하기 위하여 In-batch negative sampling과 logQ loss correction을 통하여 성능을 높일 수 있습니다. 이와 관련된 내용은 해당 포스트를 참고하시면 좋을 것 같습니다.

감사합니다.

--

--

No responses yet