이 튜토리얼에서는 MEG(뇌자도) 데이터를 활용해 뇌의 신경 활동으로부터 직접 언어적 특징을 디코딩하는 최신 NeuroAI 파이프라인 구축 방법을 다룹니다. NeuralSet과 딥러닝(합성곱 신경망, CNN)을 사용하여 원시 뇌파 신호를 의미 있는 언어 정보(예: 단어 길이)로 변환하는 엔드투엔드 시스템을 구축하는 과정을 보여줍니다. 이는 뇌-컴퓨터 인터페이스(BCI) 및 인지 신경과학 연구에 있어 실무적이고 모듈화된 워크플로우를 제공한다는 점에서 중요합니다.
번역된 본문
기술 / 인공지능 / 딥러닝 / 에디터 추천 / 머신러닝 / 소프트웨어 엔지니어링 / 스태프 튜토리얼
이 튜토리얼에서는 현대적인 NeuroAI 파이프라인을 사용하여 뇌 신호에서 직접 언어적 특징을 디코딩하는 방법을 살펴봅니다. 우리는 MEG(뇌자도) 데이터를 다루고, 원시 신경 활동을 의미 있는 예측으로 변환하는 엔드투엔드(end-to-end) 시스템을 구축합니다. 이 경우에는 뇌 반응으로부터 단어의 길이를 추정하는 작업을 수행합니다. 우리는 환경을 설정하고, 신경 이벤트를 로드 및 처리하며, 사용자 정의 특징 추출기를 설계하고, NeuralSet을 사용하여 구조화된 데이터 파이프라인을 구축합니다. 이를 바탕으로 합성곱 신경망(CNN)을 학습시켜 MEG 신호의 시간적, 공간적 구조 내 패턴을 학습하도록 합니다. 전 과정에서 실제 NeuroAI 연구 실무를 반영한 깔끔하고 모듈화된 워크플로우를 구축하는 데 중점을 둡니다.
코드 복사 완료 (다른 브라우저 사용)
[코드 내용 생략: pip_install 함수 정의 및 numpy, neuralset, neuralfetch 패키지 설치, torch, pandas, matplotlib 등 핵심 라이브러리 임포트]
필요한 모든 종속성을 설치하고 검증하여 NumPy 및 NeuralSet과 같은 핵심 패키지가 올바르게 구성되었는지 확인합니다. 파이프라인 후반부의 런타임 문제를 피하기 위해 빠른 NumPy 검사를 수행합니다. 그런 다음 데이터 처리, 모델링 및 시각화에 필요한 모든 핵심 라이브러리를 가져옵니다.
코드 복사 완료 (다른 브라우저 사용)
[코드 내용 생략: deep_import 함수 정의 및 랜덤 시드 설정, Study catalog 확인, MEG 데이터셋 선택 로직]
NeuralFetch 및 NeuralSet에서 모든 서브 모듈을 동적으로 가져와 사용 가능한 모든 연구 데이터가 올바르게 등록되었는지 확인합니다. 결과의 재현성을 위해 난수 생성기 시드(seed)를 설정하고, 사용 가능한 MEG 데이터셋을 식별하기 위해 연구 카탈로그를 검사합니다. 그런 다음 파이프라인의 기반으로 사용할 적절한 연구 데이터를 선택합니다.
코드 복사 완료 (다른 브라우저 사용)
[코드 내용 생략: CharCount 클래스 정의(BaseStatic 상속), 단어 길이 추출 로직, 체인(Chain) 구성 및 이벤트 실행, 샘플 단어 출력 로직]
Technology Artificial Intelligence Deep Learning Editors Pick Machine Learning Software Engineering Staff Tutorials In this tutorial, we explore how we can decode linguistic features directly from brain signals using a modern neuroAI pipeline. We work with MEG data and build an end-to-end system that transforms raw neural activity into meaningful predictions, in this case, estimating word length from brain responses. We set up the environment, load and process neural events, design a custom feature extractor, and construct a structured data pipeline using NeuralSet. From there, we train a convolutional neural network to learn patterns in the temporal and spatial structure of MEG signals. Throughout the process, we focus on building a clean, modular workflow that mirrors real-world neuroAI research practices. Copy Code Copied Use a different Browser import subprocess, sys, importlib, pkgutil def pip_install(*pkgs): print(f"pip install {' '.join(pkgs)} ...") r = subprocess.run([sys.executable, "-m", "pip", "install", "-q", *pkgs], capture_output=True, text=True) if r.returncode != 0: print("pip STDOUT:", r.stdout[-2000:]) print("pip STDERR:", r.stderr[-2000:]) raise RuntimeError("pip install failed; see output above.") print(" ok") pip_install("numpy>=2.0,<2.3") pip_install("neuralset") pip_install("neuralfetch") import numpy as np from numpy._core.umath import _center print(f"numpy {np.__version__} OK") import warnings, typing as tp warnings.filterwarnings("ignore") import pandas as pd import torch import torch.nn as nn import torch.nn.functional as F from torch.utils.data import DataLoader import matplotlib.pyplot as plt import neuralset as ns from neuralset import extractors as ext_mod We install and validate all required dependencies, ensuring critical packages such as NumPy and NeuralSet are properly configured. We perform a quick NumPy check to avoid runtime issues later in the pipeline. We then import all core libraries needed for data processing, modeling, and visualization. Copy Code Copied Use a different Browser def deep_import(pkg_name: str): try: pkg = importlib.import_module(pkg_name) except Exception as e: print(f"⚠️ could not import {pkg_name}: {e}") return if not hasattr(pkg, "__path__"): return for m in pkgutil.walk_packages(pkg.__path__, prefix=pkg_name + "."): try: importlib.import_module(m.name) except Exception: pass deep_import("neuralfetch") deep_import("neuralset") torch.manual_seed(0); np.random.seed(0) catalog = ns.Study.catalog() print(f"\n{len(catalog)} studies registered.") preferred = ["Fake2025Meg", "Test2025Meg", "Test2023Meg"] study_name = next((n for n in preferred if n in catalog), None) if study_name is None: meg_studies = [n for n, c in catalog.items() if "Meg" in c.neuro_types()] study_name = meg_studies[0] if meg_studies else None if study_name is None: raise RuntimeError( "No MEG study available. Catalog: " f"{sorted(catalog.keys())[:20]}… " "Install neuralfetch correctly (pip install neuralfetch) and re-run." ) print(f"→ Using study: {study_name}") We dynamically import all submodules from NeuralFetch and NeuralSet to ensure that all available studies are properly registered. We seed the random number generator for reproducibility and inspect the study catalog to identify available MEG datasets. We then select an appropriate study to use as the foundation for our pipeline. Copy Code Copied Use a different Browser class CharCount(ext_mod.BaseStatic): event_types: tp.Literal["Word"] = "Word" def get_static(self, event) -> torch.Tensor: return torch.tensor([float(len(event.text))], dtype=torch.float32) print("\nBuilding chain...") chain = ns.Chain(steps=[ {"name": study_name, "path": str(ns.CACHE_FOLDER)}, {"name": "QueryEvents", "query": "type in ['Word', 'Meg']"}, ]) events = chain.run() print(f" → {len(events)} events; types={sorted(events.type.unique().tolist())}") print(f" → Words: {(events.type=='Word').sum()} | " f"timelines: {events.timeline.nunique()}") print("\nSample words:") print(events[events.type=='Word'][["start","duration","text","timeline"]] .head(5).to_string(index=False)) print("\nBuilding segmenter...") segmenter = ns.dataloader.Segmenter( extractors={ "meg": {"name": "MegExtractor", "frequency": 100.0}, "char_count": CharCount(aggregation="trigger"), }, trigger_query="type == 'Word'", start=-0.2, duration=0.8, drop_incomplete=True, ) dataset = segmenter.apply(events) print(f" → SegmentDataset: {len(dataset)} segments") s0 = dataset[0] print(f"\nSingle item:\n meg : {tuple(s0.data['meg'].shape)}") print(f" char_count : {s0.data['char_count'].item()} " f"(word: {s0.segments[0].trigger.text!r})") We define a custom extractor that computes the character count of each word event, enabling us to create a supervised learning target. We build a processing chain to load and filter relevant events from the selected study. We then segment the MEG signals around word events and construct a dataset ready for modeling. Copy Code Copied Use a different Browser rng = np.random.RandomState(42) perm = rng.permutation(len(dataset)) n_tr, n_va = int(0.70*len(dataset)), int(0.15*len(dataset)) train_ds = dataset.select(perm[:n_tr]) val_ds = dataset.select(perm[n_tr:n_tr+n_va]) test_ds = dataset.select(perm[n_tr+n_va:]) print(f"\nSplit | train={len(train_ds)} val={len(val_ds)} test={len(test_ds)}") mk = lambda d, sh: DataLoader(d, batch_size=32, shuffle=sh, collate_fn=d.collate_fn, drop_last=False) train_loader, val_loader, test_loader = mk(train_ds, True), mk(val_ds, False), mk(test_ds, False) probe = next(iter(train_loader)) n_ch, n_t = probe.data["meg"].shape[-2:] print(f" → batch[meg] shape: {tuple(probe.data['meg'].shape)}") print(f" → batch[char] shape: {tuple(probe.data['char_count'].shape)}") class MEGDecoder(nn.Module): def __init__(self, n_channels: int, mid: int = 64): super().__init__() self.spatial = nn.Conv1d(n_channels, mid, 1) self.bn0 = nn.BatchNorm1d(mid) self.temporal1 = nn.Conv1d(mid, mid, 7, padding=3) self.bn1 = nn.BatchNorm1d(mid) self.temporal2 = nn.Conv1d(mid, mid//2, 7, padding=3) self.bn2 = nn.BatchNorm1d(mid//2) self.pool = nn.AdaptiveAvgPool1d(1) self.head = nn.Linear(mid//2, 1) self.drop = nn.Dropout(0.3) def forward(self, x): x = F.gelu(self.bn0(self.spatial(x))) x = F.gelu(self.bn1(self.temporal1(x))) x = self.drop(x) x = F.gelu(self.bn2(self.temporal2(x))) return self.head(self.pool(x).squeeze(-1)).squeeze(-1) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = MEGDecoder(n_channels=n_ch).to(device) print(f"\nDevice: {device} | params: {sum(p.numel() for p in model.parameters()):,}") train_targets = torch.cat([b.data["char_count"].squeeze(-1) for b in train_loader]) y_mean, y_std = train_targets.mean().item(), train_targets.std().item() + 1e-6 print(f"Target μ={y_mean:.2f} σ={y_std:.2f}") def prep(batch): x = batch.data["meg"].to(device).float() y = batch.data["char_count"].squeeze(-1).to(device).float() x = (x - x.mean(-1, keepdim=True)) / (x.std(-1, keepdim=True) + 1e-6) y = (y - y_mean) / y_std return x, y We split the dataset into training, validation, and test sets to ensure proper model evaluation. We create data loaders and inspect batch shapes to confirm correct data formatting. We then define a convolutional neural network and prepare normalized inputs and targets for stable training. Copy Code Copied Use a different Browser EPOCHS = 15 opt = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4) sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=EPOCHS) loss_fn = nn.MSELoss() hist = {"tr": [], "va": [], "r": []} def pearson(a, b): a, b = a - a.mean(), b - b.mean() return (a*b).sum() / (a.norm()*b.norm() + 1e-8) print("\n" + "="*64) print(f"{'Epoch':>5} | {'train':>9} | {'val':>9} | {'val_r':>7}") print("="*64) for ep in range(EPOCHS): model.train(); tr = [] for batch in train_loader: x, y = prep(batch) loss = loss_fn(model(x), y) opt.zero_grad(); loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) opt.st