Create and Rate¶
Create and Rate is an experimental template supported by PsyNet. It allows matching any creation (microphone recording, GSP, text input) with ratings in the same experiment. For example, it can be used to validate stimuli during the experiment or it can be used in a chain, in which creations are passed to the next iteration if they are rated highest by the majority (selection) or obtain the highest average score (rating).
To write your own Create and Rate experiment, you need to implement three classes:
The creator class,
the rater class,
and the trial maker
Let’s present an image of an animal to the creators and ask them to come up with a description of that animal:
from markupsafe import Markup
from psynet.modular_page import ImagePrompt, ModularPage, TextControl, PushButtonControl
from psynet.trial.create_and_rate import (
CreateAndRateNode,
CreateAndRateTrialMakerMixin,
CreateTrialMixin,
RateTrialMixin,
)
from psynet.trial.imitation_chain import ImitationChainTrial
def animal_prompt(text, img_url):
return ImagePrompt(
url=img_url,
text=Markup(text),
width="300px",
height="300px",
)
class CreateTrial(CreateTrialMixin, ImitationChainTrial):
time_estimate = 5
def show_trial(self, experiment, participant):
return ModularPage(
"create_trial",
animal_prompt(text="Describe the animal", img_url=self.context["img_url"]),
TextControl(),
time_estimate=self.time_estimate,
)
We can now specify a rater which will see the description and the image of the animal and has to rate how well the description matches the image:
class SingleRateTrial(RateTrialMixin, ImitationChainTrial):
time_estimate = 5
def show_trial(self, experiment, participant):
assert self.trial_maker.target_selection_method == "load_balanced"
assert len(self.targets) == 1
target = self.targets[0]
creation = self.get_target_answer(target)
return ModularPage(
"rate_trial",
animal_prompt(
text=f"How well does this description match the animal?<br><strong>{creation}</strong>",
img_url=self.context["img_url"],
),
PushButtonControl(
choices=[1, 2, 3, 4, 5],
labels=["not at all", "a little", "somewhat", "very", "perfectly"],
arrange_vertically=False,
),
)
The last thing we need to implement is the trial maker. You need to decide how the ratings are made, whether the raters select or rate (here they do the latter). If they rate, do they validate one stimulus or all at once? Should they rate the creations at the current iteration or also the creation which was passed on from the previous iteration?
The trial maker needs the following parameters:
num_creators
, which sets the number of creators e.g. two creators,num_raters
, number of raters; if people only rate 1 stimulus, the number of raters needs to be an integer multiple of the number of rated stimuli, i.e.num_creators
(and optionally the previous iteration),node_class
, the class of the Node; in most use-casesCreateAndRateNode
is fine,creator_class=CreateTrial
, set this to your creator classrater_class=RateClass
, set this to your rater class
Optionally, you can set
include_previous_iteration
(defaultFalse
) which indicates if the previous iteration is rated. If this is the case you need to specify a seed in thestart_nodes
, e.g.:
start_nodes = [
CreateAndRateNode(context={"img_url": "static/dog.jpg"}, seed=seed_definition)
]
rate_mode
which can be set to"rate"
if people can give integer ratings to the stimuli or"select"
if raters are faced with all creations at once and have to pick onetarget_selection_method
, can be set to"all"
(required ifrate_mode=="rate"
) indicating that raters rate all creations or set to"one"
which randomly selects one target (internally it prioritizes creations that obtained least ratings)randomize_target_order
(defaultTrue
) which indicates if the presentation order of the targets is randomized. In most cases this should be set toTrue
.verbose
can be set toTrue
to print the Create and Rate decisions to the experiment log
The TrialMaker class just needs to inherit from CreateAndRateTrialMakerMixin and some TrialMaker class, e.g. ImitationChainTrialMaker:
class CreateAndRateTrialMaker(CreateAndRateTrialMakerMixin, ImitationChainTrialMaker):
pass
It is also possible to customize the behaviour. For example, say we want to separate raters and creators into two
different groups which is set in participant.var.is_creator
. We can then implement the following:
class CreateAndRateTrialMaker(CreateAndRateTrialMakerMixin, ImitationChainTrialMaker):
def get_trial_class(self, node, participant, experiment):
proposed_role_class = self.get_trial_class(node, participant, experiment)
if participant.var.is_creator:
if proposed_role_class == self.creator_class:
return self.creator_class
else:
if proposed_role_class == self.rater_class:
return self.rater_class
return None
Let’s now put all pieces together:
from markupsafe import Markup
import psynet.experiment
from psynet.consent import NoConsent
from psynet.modular_page import ImagePrompt, ModularPage, PushButtonControl, TextControl
from psynet.page import SuccessfulEndPage
from psynet.timeline import Timeline
from psynet.trial.create_and_rate import (
CreateAndRateNode,
CreateAndRateTrialMakerMixin,
CreateTrialMixin,
RateTrialMixin,
)
from psynet.trial.imitation_chain import ImitationChainTrial, ImitationChainTrialMaker
def animal_prompt(text, img_url):
return ImagePrompt(
url=img_url,
text=Markup(text),
width="300px",
height="300px",
)
class CreateTrial(CreateTrialMixin, ImitationChainTrial):
time_estimate = 5
def show_trial(self, experiment, participant):
return ModularPage(
"create_trial",
animal_prompt(text="Describe the animal", img_url=self.context["img_url"]),
TextControl(),
time_estimate=self.time_estimate,
)
class SingleRateTrial(RateTrialMixin, ImitationChainTrial):
time_estimate = 5
def show_trial(self, experiment, participant):
assert len(self.targets) == 1
target = self.targets[0]
creation = self.get_target_answer(target)
return ModularPage(
"rate_trial",
animal_prompt(
text=f"How well does this description match the animal?<br><strong>{creation}</strong>",
img_url=self.context["img_url"],
),
PushButtonControl(
choices=[1, 2, 3, 4, 5],
labels=["not at all", "a little", "somewhat", "very", "perfectly"],
arrange_vertically=False,
),
)
class CreateAndRateTrialMaker(CreateAndRateTrialMakerMixin, ImitationChainTrialMaker):
pass
start_nodes = [
CreateAndRateNode(context={"img_url": "static/dog.jpg"})
]
class Exp(psynet.experiment.Experiment):
label = "Basic Create and Rate Experiment"
initial_recruitment_size = 1
timeline = Timeline(
NoConsent(),
CreateAndRateTrialMaker(
num_creators=2,
num_raters=2,
node_class=CreateAndRateNode,
creator_class=CreateTrial,
rater_class=SingleRateTrial,
include_previous_iteration=False,
rate_mode="rate",
target_selection_method="one",
verbose=True,
# trial_maker params
id_="create_and_rate_trial_maker",
chain_type="across",
expected_trials_per_participant=len(start_nodes),
max_trials_per_participant=len(start_nodes),
start_nodes=start_nodes,
chains_per_experiment=len(start_nodes),
balance_across_chains=False,
check_performance_at_end=True,
check_performance_every_trial=False,
propagate_failure=False,
recruit_mode="n_trials",
target_n_participants=None,
wait_for_networks=False,
max_nodes_per_chain=10,
),
SuccessfulEndPage(),
)
This gives you a simple Create and Rate experiment in just 120 lines 😉