word-to-EEG generator (THINGS-EEG)
This generates 63-channel EEG from text: a word, a one-sentence description of a
scene, or both. The models are ridge regressions from CLIP ViT-B/32 text vectors to
the EEG of 10 subjects in THINGS-EEG (Haitao999/things-eeg,
Preprocessed_data_250Hz_whiten), with realistic noise models on top.
Files
| file | size | contents |
|---|---|---|
wte.py |
8 KB | inference code. Needs only numpy + sentence-transformers |
model.npz |
104 MB | word β EEG means (pooled and per subject), training-session noise |
model_test.npz |
122 MB | test-session noise adapter (4-session mixture) |
photo_model.npz |
14 MB | description β EEG and word+description β EEG means |
Use it directly from Hugging Face
1. Install (Python 3.9+):
pip install numpy sentence-transformers huggingface_hub
2. Log in. This repo is private, so you need to log in once per machine (or set the
HF_TOKEN environment variable):
hf auth login
3. Load and generate. This is copy-paste ready. The first run downloads the code and models
(240 MB) and the CLIP text encoder (600 MB); both are cached afterwards. A CPU is enough.
import sys
from huggingface_hub import snapshot_download
root = snapshot_download("BiscuitBobby/word-to-eeg-generator") # code + models, cached
sys.path.insert(0, root)
from wte import Generator, CHANNELS
g = Generator(root)
# word only
eeg = g.sample(word="dog", level="mean")
# word + scene description: the most informative input
desc = "A black dog lies on green grass in sunlight, facing left, a wooden fence behind it."
eeg = g.sample(word="dog", description=desc, level="mean")
# description only, in subject 3's style, with realistic single-trial noise
eeg = g.sample(description=desc, subject="sub-03", level="trial_test", n=5, seed=0)
print(eeg.shape) # (5, 63, 80) = samples x channels x time points
print(CHANNELS[:5]) # ['Fp1', 'Fp2', 'AF7', 'AF3', 'AFz']
4. Use the output.
import numpy as np
np.save("dog_eeg.npy", eeg) # float32, (n, 63, 80)
times_ms = (np.arange(18, 98) / 250 - 0.2) * 1000 # dataset time axis (see Output below)
oz = eeg[0, CHANNELS.index("Oz")] # channel Oz of the first sample
Optional plot:
import matplotlib.pyplot as plt
plt.plot(times_ms, oz)
plt.xlabel("time (ms)"); plt.ylabel("whitened units"); plt.title("Oz, 'dog'")
plt.show()
Command line. Download once, then run the script:
hf download BiscuitBobby/word-to-eeg-generator --local-dir word-to-eeg-generator
python word-to-eeg-generator/wte.py dog -d "A black dog lies on green grass in sunlight, facing left, a wooden fence behind it." -s sub-03 -l trial_test -n 5 --seed 0 -o dog.npy
The same files are also on Kaggle as the private dataset lemondobby/word-to-eeg-generator.
Inputs
word: an object name such as"dog","baseball bat"or"fire_truck". It is encoded as the average CLIP vector of 4 prompts ("a photo of a {word}.", ...).description: one concrete sentence, ideally 15β23 words, naming the object with its colour, pose and setting. It should read like the training captions, e.g. "A tan antelope with long ridged horns stands in a dry golden grassy field, facing left in profile." Short, terse descriptions measurably lose accuracy (see below).subject:'sub-01'β¦'sub-10'(that subject's style) orNone(the 10-subject average). With a description-only input the subject sets the noise only; the subject's mean offset is fitted on word vectors, so it needs a word.level:
| level | adds | subject | use it for |
|---|---|---|---|
mean |
no noise | optional | maximum word / scene information |
erp |
averaged-response noise (training session) | optional | realistic averaged ERPs |
trial |
single-trial noise (training session) | required | single trials like the training recordings |
erp_test |
40-repetition average noise (test session) | required | averaged ERPs like the test recordings |
trial_test |
single-trial noise (test session) | required | most realistic single trials |
Output
A float32 array of shape (n, 63, 80): n samples Γ 63 channels (wte.CHANNELS,
10-10 system, Fp1 β¦ O2) Γ 80 time points at 250 Hz. These are samples 18β97 of the
dataset's 250-sample epoch; by the dataset's own times field that is about β0.128 s to
+0.188 s. That field has 300 entries for 250 samples, though, so the absolute timing is
unverified. Values are in the dataset's whitened units, not microvolts.
Accuracy: which setup is best
Accuracy is identification on the 200 THINGS-EEG test objects. Neither these objects nor their photos were seen in training. For each test object the generator's noise-free output is correlated with the real EEG of all 200, and it scores a hit if its own object ranks first. Chance is 0.5% top-1 and 2.5% top-5. 2-way is a gentler score: how often an object's output matches its own EEG better than it matches another object's EEG, averaged over all pairs (chance 50%).
| input | target | top-1 | top-5 | 2-way |
|---|---|---|---|---|
| description only, detailed (~19 words) | 10-subject average | 15.0% | 34.0% | 88.3% |
| word + description, detailed (~19 words) | 10-subject average | 13.0% | 33.5% | 87.6% |
| word + dataset's original caption (~8 words) | 10-subject average | 10.0% | 31.0% | 87.1% |
| word only | 10-subject average | 9.5% | 28.5% | 83.9% |
| dataset's original caption only (~8 words) | 10-subject average | 8.5% | 31.5% | 87.3% |
word + description, subject set |
that subject's average | 7.0% (3.5β9.5%) | ||
word only, subject set |
that subject's average | 6.0% (3.5β8.5%) | ||
| word only, pooled (no subject) | each subject's average | 4.9% | ||
| real photo's CLIP image vector (upper bound, not in this package) | 10-subject average | 25.5% | 50.0% |
The "dataset's original caption" rows use THINGS-EEG's own short test captions (e.g. "Tan antelope with curved horns standing in dry grass field"). The "detailed" rows use the same 200 photos re-described in the ~19-word style the models were trained on.
What has the highest accuracy: give a detailed description (with or without the word)
and use level='mean'.
- Description only (15.0%) and word + description (13.0%) tied on the development data (6.8% each). Word + description was chosen before the test run, and the 2-point gap on test is within noise (one standard error is about Β±2.4 points on 200 items). Treat them as equivalent.
- The dataset's own short captions (~8 words) barely beat the word on top-1 (10.0% and 8.5% vs 9.5%), though they gain on top-5 and 2-way. Detailed ~19-word descriptions are what make the difference.
- For one subject's EEG, pass
subjecttogether with a word: it beats the pooled prediction (6.0% vs 4.9% for word only on test: better for 8 of 10 subjects, 1 tie; 10 of 10 on development data). - Noise lowers identifiability. A single
trial/trial_testsample identifies its object at chance (1.2% on a 100-way test with 1% chance; a real single trial gets 10.5%). That is how noisy real single-trial EEG is. Usemeanfor information and the noise levels for realism, or average many noisy samples.
How this compares to real EEG (why top-1 is low)
The test EEG averages 80 repetitions per person, so it is very consistent between people:
| predictor β target | top-1 | 2-way |
|---|---|---|
| real EEG, average of 5 people β average of the other 5 (100 random splits) | 76.1% | 99.5% |
| real EEG, one person β average of the other 9 | 45.5% | |
| this model, word + detailed description β the same 5-person averages | 10.6% | 86.7% |
| this model, word only β the same 5-person averages | 8.1% | 83.0% |
The brain responses are clearly distinct between objects, so the gap is in the model and its input, not in the test data:
- The main metric is strict. Exact top-1 out of 200 punishes near misses. The same outputs pick the right object over a random alternative 84β88% of the time.
- Text lacks what drives this EEG. This early visual window responds to what the particular photo looks like (colours, contrast, layout, object size). A word says none of that, and a description says only some of it.
- The model is linear and trained on noisy EEG. It is a ridge map from CLIP vectors, trained on 4 repetitions per training photo. Even the real photo's CLIP image vector only reaches 25.5%.
Adding visual detail (images generated from the text) or a better imageβEEG model could raise the score. Neither is tested or included here.
Realism
A quadratic real-vs-generated classifier scores AUC 0.5 when the two are indistinguishable.
| level | generated vs real | same-session real vs real (reference) |
|---|---|---|
trial_test |
0.567 | 0.534 |
erp_test |
0.649 | 0.482 |
trial (vs held-out training-session trials) |
0.550 | |
erp (vs held-out training-session averages) |
0.555 |
Amplitude ratio (generated / real) is 0.99β1.00. Single trials are close to real; test-session averaged responses are still detectable. Noise was fitted around the word-only prediction, so with a description it is slightly over-dispersed.
Limits
- The models know about single objects (the 1,854 THINGS concepts and their photos). Other text runs, but it hasn't been validated.
- The accuracy numbers above are for the 200 THINGS test objects with descriptions in the training-caption style.
- Derived from THINGS-EEG (Gifford et al., 2022). Check the dataset's licence before redistributing.