项目文件夹

文件
wehub-resource-sync 593b94c120
pytest / Unit Tests (push) Has been cancelled
pytest / Integration (integration_tests_a) (push) Has been cancelled
pytest / Integration (integration_tests_b) (push) Has been cancelled
pytest / Integration (integration_tests_c) (push) Has been cancelled
pytest / Integration (integration_tests_d) (push) Has been cancelled
pytest / Integration (integration_tests_e) (push) Has been cancelled
pytest / Integration (integration_tests_f) (push) Has been cancelled
pytest / Integration (integration_tests_g) (push) Has been cancelled
pytest / Integration (integration_tests_h) (push) Has been cancelled
pytest / Integration (integration_tests_i) (push) Has been cancelled
pytest / Integration (integration_tests_j) (push) Has been cancelled
pytest / Distributed (distributed_a) (push) Has been cancelled
pytest / Distributed (distributed_b) (push) Has been cancelled
pytest / Distributed (distributed_c) (push) Has been cancelled
pytest / Distributed (distributed_d) (push) Has been cancelled
pytest / Distributed (distributed_e) (push) Has been cancelled
pytest / Distributed (distributed_f) (push) Has been cancelled
pytest / Minimal Install (push) Has been cancelled
pytest / Event File (push) Has been cancelled
pytest (slow) / py-slow (push) Has been cancelled
Publish JSON Schema / publish-schema (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:49:20 +08:00

107 行
3.9 KiB
Python

import logging
import time
from typing import Any
import numpy as np
import pandas as pd
import scipy
from ludwig.utils.types import DataFrame
def _negative_sample_user(interaction_row: np.array, neg_pos_ratio: int, extra_samples: int) -> tuple[list[int], int]:
"""Returns a list of negative item indices for given user-item interactions.
If there are not enough negative items, takes all of them and adds the difference to the extra_samples
otherwise, samples with replacement.
Params:
interaction_row: user-item interaction row
neg_pos_ratio: number of negative samples per positive sample
extra_samples: number of additional samples to add to the negative sample list
Returns:
Tuple of list of negative item indices and number of extra samples
"""
# Find all items that are not interacted with by the user
neg_items = np.where(interaction_row == 0)[1]
available_samples = len(neg_items)
# Randomly sample negative items
npos = interaction_row.shape[1] - len(neg_items)
samples_required = npos * neg_pos_ratio + extra_samples
should_sample = samples_required <= available_samples
neg_items = np.random.choice(neg_items, samples_required, replace=False) if should_sample else neg_items
return neg_items.tolist(), max(0, samples_required - available_samples)
def negative_sample(
df: DataFrame,
user_id_col: str = "customer_id",
item_id_col: str = "article_id",
label_col: str = "label",
neg_pos_ratio: int = 1,
neg_val: Any = 0,
log_pct: int = 0,
):
"""Negative sampling for implicit feedback datasets.
Params:
df: DataFrame containing user-item interactions
user_id_col: column name for user ids
item_id_col: column name for item ids
label_col: column name for interaction labels (e.g. 1 for positive interaction)
n_neg: number of negative samples per positive sample
neg_val: label value for the negative samples
percent_print: print progress every percent_print percent. 0 to disable
Returns:
Input DataFrame with negative samples appended
Source: https://petamind.com/fast-uniform-negative-sampling-for-rating-matrix/
"""
# TODO(joppe): support out of memory negative sampling using Dask
if not isinstance(df, pd.DataFrame):
df = df.compute()
# Initialize sparse COOrdinate matrix from users and items in existing interactions
user_id_cat = df[user_id_col].astype("category").cat
user_id_codes = user_id_cat.codes.values
item_id_cat = df[item_id_col].astype("category").cat
item_id_codes = item_id_cat.codes.values
interactions_sparse = scipy.sparse.coo_matrix((df[label_col], (user_id_codes, item_id_codes)))
# Convert to dense user-item matrix so we can iterate
interactions_dense = interactions_sparse.todense()
nrows = interactions_dense.shape[0]
niter_log = int(nrows * log_pct / 100)
start_time = time.time()
user_indices, item_indices = [], []
extra_samples = 0
for user_idx, interaction_row in enumerate(interactions_dense):
if log_pct > 0 and user_idx % niter_log == 0:
logging.info(
f"Negative sampling progress: {float(user_idx) * 100 / nrows:0.0f}% in {time.time() - start_time:0.2f}s"
)
neg_items_for_user, extra_samples = _negative_sample_user(interaction_row, neg_pos_ratio, extra_samples)
# Add to negative user-item pairs
item_indices += neg_items_for_user
user_indices += [user_idx] * len(neg_items_for_user)
negative_samples = pd.DataFrame(
{
# Map back to original user and item ids
user_id_col: user_id_cat.categories[user_indices],
item_id_col: item_id_cat.categories[item_indices],
label_col: [neg_val] * len(item_indices),
}
)
return pd.concat([df[[user_id_col, item_id_col, label_col]], negative_samples])