대부분의 데이터는 User feedback의 implicit data인 경우가 많습니다. 이럴 경우 negative sampling을 하여 학습을 위한 전처리 작업을 합니다.
아래에는 가장 일반적인 Negative sampling을 하는 코드예시입니다.
- 샘플링을 위해 예시 데이터로 유저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을 통하여 성능을 높일 수 있습니다. 이와 관련된 내용은 해당 포스트를 참고하시면 좋을 것 같습니다.
감사합니다.