-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5 from AutoResearch/4-add-bandit-task
update: clear outputs of jupiter notebook
- Loading branch information
Showing
5 changed files
with
428 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
122 changes: 122 additions & 0 deletions
122
{{ cookiecutter.__project_slug }}/example_mains/js_psych_bandit.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
// To use the jsPsych package first install jspsych using `npm install jspsych` | ||
// This example uses the rdk plugin. Install it via `npm install @jspsych-contrib/plugin-rdk` | ||
// This example uses the html-keyboard-response plugin. Install it via `npm install @jspsych/plugin-html-keyboard-response` | ||
// Here is documentation on how to program a jspsych experiment using npm: | ||
// https://www.jspsych.org/7.3/tutorials/hello-world/#option-3-using-npm | ||
|
||
import {initJsPsych} from 'jspsych'; | ||
import 'jspsych/css/jspsych.css' | ||
import htmlKeyboardResponse from '@jspsych/plugin-html-keyboard-response'; | ||
import jsPsychHtmlChoice from '@jspsych-contrib/plugin-html-choice'; | ||
import fullscreen from '@jspsych/plugin-fullscreen'; | ||
import '../css/slot-machine.css' | ||
|
||
|
||
/** | ||
* This is the main function where you program your experiment. Install jsPsych via node and | ||
* use functions from there | ||
* @param id this is a number between 0 and number of participants. You can use it for example to counterbalance between subjects | ||
* @param condition this is a condition (4-32. Here we want to find out how the training length impacts the accuracy in a testing phase) | ||
* @returns {Promise<*>} the accuracy in the post-trainging phase relative to the pre-training phase | ||
*/ | ||
const main = async (id, condition) => { | ||
|
||
const timeline_variables = JSON.parse(condition) | ||
const nr_trials = timeline_variables.length | ||
let current_trial = 0 | ||
|
||
const jsPsych = initJsPsych({ | ||
show_progress_bar: true, | ||
auto_update_progress_bar: false, | ||
on_finish: function () { | ||
jsPsych.data.displayData(); | ||
} | ||
}); | ||
|
||
// create timeline (enter fullscreen) | ||
const timeline = [{ | ||
type: fullscreen, | ||
fullscreen_mode: true | ||
}]; | ||
|
||
|
||
// create html divs | ||
// let sm_blue = '<div style="position: absolute; top:0; left:0" class="slotmachine blue"></div>' | ||
// let sm_red = '<div style="position: absolute; top:0; right:0" class="slotmachine red"></div>' | ||
|
||
const sm_blue = (pos) => { | ||
let _pos = 'left: 0' | ||
if (pos === 'right') { | ||
_pos = 'right: 0' | ||
} | ||
return `<div style="position: absolute; top:10vh; height: 60vh; ${_pos}" class="slotmachine blue"></div>` | ||
} | ||
const sm_red = (pos) => { | ||
let _pos = 'left: 0' | ||
if (pos === 'right') { | ||
_pos = 'right: 0' | ||
} | ||
return `<div style="position: absolute; top:10vh; height: 60vh; ${_pos}" class="slotmachine red"></div>` | ||
} | ||
let score = 0 | ||
|
||
const test = { | ||
timeline: [ | ||
{ | ||
type: jsPsychHtmlChoice, | ||
html_array: () => { | ||
return [ | ||
sm_blue(jsPsych.timelineVariable('pos')[0]), | ||
sm_red(jsPsych.timelineVariable('pos')[1]) | ||
] | ||
}, | ||
on_load: () => { | ||
let content = document.getElementById('jspsych-content') | ||
let score_div = document.createElement('div') | ||
score_div.style.position = 'fixed' | ||
score_div.style.left = '50vw' | ||
score_div.style.bottom = '3vh' | ||
score_div.style.transform = 'translateX(-50%)' | ||
score_div.innerText = `Score: ${score} of ${nr_trials}` | ||
content.appendChild(score_div) | ||
}, | ||
trial_duration: null, | ||
values: jsPsych.timelineVariable('values'), | ||
response_ends_trial: true, | ||
time_after_response: 800, | ||
on_finish: (data) => { | ||
current_trial += 1 | ||
score += data.value | ||
let progress = current_trial / nr_trials | ||
jsPsych.setProgressBar(progress) | ||
} | ||
}, | ||
{ | ||
type: htmlKeyboardResponse, | ||
stimulus: '', | ||
trial_duration: 100, | ||
on_load: () => { | ||
let content = document.getElementById('jspsych-content') | ||
let score_div = document.createElement('div') | ||
score_div.style.position = 'fixed' | ||
score_div.style.left = '50vw' | ||
score_div.style.bottom = '3vh' | ||
score_div.innerText = `Score: ${score} of ${nr_trials}` | ||
score_div.style.transform = 'translateX(-50%)' | ||
content.appendChild(score_div) | ||
}, | ||
}, | ||
], | ||
timeline_variables: timeline_variables | ||
} | ||
|
||
timeline.push(test) | ||
|
||
await jsPsych.run(timeline) | ||
|
||
const observation = jsPsych.data.get().filter({trial_type: "html-choice"}).select('response') | ||
return JSON.stringify({condition: condition, observation: observation}) | ||
} | ||
|
||
|
||
export default main |
133 changes: 133 additions & 0 deletions
133
{{ cookiecutter.__project_slug }}/example_workflows/js_psych_bandit.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
""" | ||
Basic Workflow | ||
Single condition Variable (0-1), Single Observation Variable(0-1) | ||
Theorist: LinearRegression | ||
Experimentalist: Random Sampling | ||
Runner: Firebase Runner (no prolific recruitment) | ||
""" | ||
import json | ||
|
||
from autora.variable import VariableCollection, Variable | ||
from autora.experimentalist.random import pool | ||
from autora.experiment_runner.firebase_prolific import firebase_runner | ||
from autora.state import StandardState, on_state, Delta | ||
|
||
import numpy as np | ||
import pandas as pd | ||
from sklearn.linear_model import LinearRegression | ||
|
||
# *** Set up variables *** # | ||
# independent variable is coherence in percent (0 - 100) | ||
# dependent variable is accuracy (0 - 1) | ||
variables = VariableCollection( | ||
independent_variables=[Variable(name="coherence", allowed_values=np.linspace(0, 1, 101))], | ||
dependent_variables=[Variable(name="accuracy", value_range=(0, 1))]) | ||
|
||
# *** State *** # | ||
# With the variables, we can set up a state. The state object represents the state of our | ||
# closed loop experiment. | ||
|
||
|
||
state = StandardState( | ||
variables=variables, | ||
) | ||
|
||
# *** Components/Agents *** # | ||
# Components are functions that run on the state. The main components are: | ||
# - theorist | ||
# - experiment-runner | ||
# - experimentalist | ||
# See more about components here: https://autoresearch.github.io/autora/ | ||
|
||
|
||
# ** Theorist ** # | ||
# Here we use a linear regression as theorist, but you can use other theorists included in | ||
# autora (for a list: https://autoresearch.github.io/autora/theorist/) | ||
|
||
theorist = LinearRegression() | ||
|
||
# To use the theorist on the state object, we wrap it with the on_state functionality and return a | ||
# Delta object. | ||
# Note: The if the input arguments of the theorist_on_state function are state-fields like | ||
# experiment_data, variables, ... , then using this function on a state object will automatically | ||
# use those state fields. | ||
# The output of these functions is always a Delta object. The keyword argument in this case, tells | ||
# the state object witch field to update. | ||
|
||
|
||
@on_state() | ||
def theorist_on_state(experiment_data, variables): | ||
ivs = [iv.name for iv in variables.independent_variables] | ||
dvs = [dv.name for dv in variables.dependent_variables] | ||
x = experiment_data[ivs] | ||
y = experiment_data[dvs] | ||
return Delta(models=[theorist.fit(x, y)]) | ||
|
||
# ** Experimentalist ** # | ||
# Here, we use a random pool and use the wrapper to create a on state function | ||
# Note: The argument num_samples is not a state field. Instead, we will pass it in when calling | ||
# the function | ||
|
||
|
||
@on_state() | ||
def experimentalist_on_state(variables, num_samples): | ||
return Delta(conditions=pool(variables, num_samples)) | ||
|
||
# ** Experiment Runner ** # | ||
# We will run our experiment on firebase and need credentials. You will find them here: | ||
# (https://console.firebase.google.com/) | ||
# -> project -> project settings -> service accounts -> generate new private key | ||
|
||
firebase_credentials = { | ||
"type": "type", | ||
"project_id": "project_id", | ||
"private_key_id": "private_key_id", | ||
"private_key": "private_key", | ||
"client_email": "client_email", | ||
"client_id": "client_id", | ||
"auth_uri": "auth_uri", | ||
"token_uri": "token_uri", | ||
"auth_provider_x509_cert_url": "auth_provider_x509_cert_url", | ||
"client_x509_cert_url": "client_x509_cert_url" | ||
} | ||
|
||
# simple experiment runner that runs the experiment on firebase | ||
experiment_runner = firebase_runner( | ||
firebase_credentials=firebase_credentials, | ||
time_out=100, | ||
sleep_time=5) | ||
|
||
|
||
# Again, we need to wrap the runner to use it on the state. Here, we send the raw conditions. | ||
@on_state() | ||
def runner_on_state(conditions): | ||
data = experiment_runner(conditions) | ||
# Here, parse the return value of the runner. The return value depends on the specific | ||
# implementation of your online experiment (see testing_zone/src/design/main.js). | ||
# In this example, the experiment runner returns a list of strings, that contain json formatted | ||
# dictionaries. | ||
# Example: | ||
# data = ['{'coherence':.3, accuracy':.8}', ...] | ||
result = [] | ||
for item in data: | ||
result.append(json.loads(item)) | ||
return Delta(experiment_data=pd.DataFrame(result)) | ||
|
||
|
||
# Now, we can run our components | ||
for _ in range(3): | ||
state = experimentalist_on_state(state, num_samples=2) # Collect 2 conditions per iteration | ||
state = runner_on_state(state) | ||
state = theorist_on_state(state) | ||
|
||
|
||
# *** Report the data *** # | ||
# If you changed the theorist, also change this part | ||
def report_linear_fit(m: LinearRegression, precision=4): | ||
s = f"y = {np.round(m.coef_[0].item(), precision)} x " \ | ||
f"+ {np.round(m.intercept_.item(), 4)}" | ||
return s | ||
|
||
|
||
print(report_linear_fit(state.models[0])) | ||
print(report_linear_fit(state.models[-1])) |
Oops, something went wrong.