"""
Suite of classes related to content creators, including predicted user-item
scores, predicted user profiles, actual creator profiles, and a Creators class (which
encapsulates some of these concepts)
"""
import numpy as np
import scipy.sparse as sp
import trecs.matrix_ops as mo
from trecs.random import Generator
from trecs.base import BaseComponent
[docs]class Creators(BaseComponent): # pylint: disable=too-many-ancestors
"""
Class representing content creators in the system.
Each content creator is represented with a single vector that governs
the kinds of content each creator produces. All creator profiles can be
represented with a :obj:`numpy.ndarray` of size
``(number_of_creators, number_of_attributes)``.
This class inherits from :class:`~components.base_components.BaseComponent`.
Parameters
------------
actual_creator_profiles: array_like, optional
Representation of the creator's attribute profiles.
creation_probability: float, default 0.5
The probability that any given creator produces a new item at a
timestep.
size: tuple, optional
Size of the user representation. It expects a tuple. If ``None``,
it is chosen randomly.
verbose: bool, default False
If ``True``, enables verbose mode.
seed: int, optional
Seed for random generator.
Attributes
------------
Attributes from BaseComponent
Inherited by :class:`~trecs.components.base_components.BaseComponent`
actual_creator_profiles: :obj:`numpy.ndarray`
A matrix representing the *real* similarity between each item and
attribute.
create_new_items: callable
A function that defines user behaviors when interacting with items.
If None, users follow the behavior in :meth:`generate_new_items()`.
Raises
--------
TypeError
If parameters are of the wrong type.
ValueError
If both actual_creator_profiles and size are None.
"""
def __init__(
self,
actual_creator_profiles=None,
creation_probability=0.5,
size=None,
verbose=False,
seed=None,
): # pylint: disable=too-many-arguments
# general input checks
if actual_creator_profiles is not None:
if not isinstance(actual_creator_profiles, (list, np.ndarray, sp.spmatrix)):
raise TypeError(
"actual_creator_profiles must be a list, numpy.ndarray, or scipy sparse matrix"
)
if actual_creator_profiles is None and size is None:
raise ValueError("actual_creator_profiles and size can't both be None")
if actual_creator_profiles is None and not isinstance(size, tuple):
raise TypeError("size must be a tuple, is %s" % type(size))
if actual_creator_profiles is None and size is not None:
row_zeros = np.zeros(size[1]) # one row vector of zeroes
while actual_creator_profiles is None or mo.contains_row(
actual_creator_profiles, row_zeros
):
# generate matrix until no row is the zero vector
actual_creator_profiles = Generator(seed=seed).uniform(size=size)
if creation_probability > 1 or creation_probability < 0:
raise ValueError("Creation probability cannot be less than 0 or greater than 1")
self.actual_creator_profiles = np.asarray(actual_creator_profiles)
self.creation_probability = creation_probability
self.name = "actual_creator_profiles"
BaseComponent.__init__(
self, verbose=verbose, init_value=self.actual_creator_profiles, seed=seed
)
self.rng = Generator(seed=seed)
[docs] def generate_items(self):
"""
Generates new items. Each creator probabilistically creates a new item.
Item attributes are generated using each creator's profile
as a series of Bernoulli random variables. Therefore, item attributes
will be binarized. To change this behavior, simply write a custom
class that overwrites this method.
Returns
---------
:obj:`np.ndarray`
A numpy matrix of dimension :math:`|A|\\times|I_n|`, where
:math:`|I_n|` represents the number of new items, and :math:`|A|`
represents the number of attributes for each item.
"""
# Generate mask by tossing coin for each creator to determine who is releasing content
# This should result in a _binary_ matrix of size (num_creators,)
if (self.actual_creator_profiles < 0).any() or (self.actual_creator_profiles > 1).any():
raise ValueError("Creator profile attributes must be between zero and one.")
creator_mask = self.rng.binomial(
1, self.creation_probability, self.actual_creator_profiles.shape[0]
)
chosen_profiles = self.actual_creator_profiles[creator_mask == 1, :]
# for each creator that will add new items, generate Bernoulli trial
# for item attributes
items = self.rng.binomial(1, chosen_profiles.reshape(-1), chosen_profiles.size)
return items.reshape(chosen_profiles.shape).T
[docs] def update_profiles(self, interactions, items):
"""
This method can be implemented by child classes to update the
creator profiles over time.
Parameters
-----------
interactions: :obj:`numpy.ndarray` or list
A matrix where row :math:`i` corresponds to the attribute vector
that user :math:`i` interacted with.
"""
# this can be overwritten by a custom creator class
[docs] def store_state(self):
"""Store the actual creator profiles in the state history"""
self.state_history.append(np.copy(self.actual_creator_profiles))