๐ฉปfMRI
2. Using the GLM to fMRI: estimation
date
Apr 17, 2023
slug
fmri-glm-2-1
author
status
Public
tags
NI-edu
Dartbrain
summary
How to GLM with fMRI
type
Post
thumbnail
category
๐ฉปfMRI
updatedAt
Apr 18, 2023 05:10 AM
2.1 Recap of linear regression2.1.1 Notation2.1.2 Parameters in linear regression2.1.3 Residuals and model fit2.2 GLM in fMRI analyses2.2.1 Variables2.2.2 Regression on fMRI data & interpretation parameters2.2.3 Using the BOLD response in GLM modelsSummaryLinear ModelBOLD response in GLM
2.1 Recap of linear regression
2.1.1 Notation
- (์ ํ) ํ๊ท ๋ชจ๋ธ์ ์์ฑํ ๋, ์ฐ๋ฆฌ๋ ์ข ์ ๋ณ์์ ๋ ๋ฆฝ ๋ณ์๋ฅผ ์ง์ ํจ
- ์์ ์์์ y๋ ์ข ์ ๋ณ์, x๋ ๋ ๋ฆฝ ๋ณ์์ (N์ observations์ ์, P๋ predictors์ ์์)
np.random.seed(1)
# ๊ฐ์ ๋ฐ์ดํฐ์
๋ง๋ค๊ธฐ
prespecified_covariance = np.array([[1, .8], [.8, 1]]) # y์ x๋ 0.8์ ์๊ด๊ด๊ณ ์ง๋
# x ํ๊ท ์ 3, y ํ๊ท ์ 7์ด๋ฉด์, ์๋ก์ ์๊ด๊ด๊ณ๋ 0.8์ธ ๋ฐ์ดํฐ 30๊ฐ ๋ง๋ค๊ธฐ
data = np.random.multivariate_normal(mean=[3, 7], cov=prespecified_covariance, size=30)
X = data[:, 0, np.newaxis] # (30, 1)
y = data[:, 1, np.newaxis] # (30, 1)
# intercept modeling
n_obs = y.size
intercept = np.ones((n_obs, 1)) # (30, 1)
tuple_with_arrays = (intercept, X)
X_with_icept = np.hstack(tuple_with_arrays) # (30, 2)2.1.2 Parameters in linear regression
- linear regression์ ๋ค์๊ณผ ๊ฐ์ด ํํํจ
- ์ฌ๊ธฐ์, ๊ฐ (๋ ๋ฆฝ) ๋ณ์๊ฐ ์ข ์๋ณ์์ ์ํฅ์ ๋ฏธ์น๋ ์ ๋๋ฅผ beta-parameter๋ผ๊ณ ํจ
- beta-parameter๋ฅผ ์ถ์ ํ๋ ๋ฐฉ๋ฒ์ผ๋ก Ordinary Least Squares(OLS, ์ต์์ ๊ณฑ๋ฒ) ์ฌ์ฉํจ
- y_hat์ ์ค์ y(์ข ์๋ณ์)๊ฐ ์๋๋ผ ์ ํํ๊ท์์ผ๋ก ์ป์ ์ข ์๋ณ์์ ์ถ์ ์น์
- ์ค์ y(์ข ์๋ณ์)์ y_hat์ ์ฐจ์ด์ ์ ๊ณฑ์ ์ค์ด๋ ๊ฒ์ ๋ชฉํ๋ก ํจ
from numpy.linalg import inv
est_betas = inv(X_with_icept.T @ X_with_icept) @ X_with_icept.T @ y
print(est_betas)
>>>
[[4.25897963] # parameter of intercept
[0.88186203]] # parameter of x
2.1.3 Residuals and model fit
- residual์ ๋ค์๊ณผ ๊ฐ์ด ์ ์ํจ
- ์ค์ ์ข ์๋ณ์์์ ์์ธก์น๋ฅผ ๋บ ๊ฐ์
- ์๋์ MSE, R^2๋ ํ๊ท ์์ธก ์ ๋๋ฅผ ๋ํ๋ด๋ ์ฒ๋์

mse = np.mean((y - y_hat) ** 2) # Mean Square Error
numerator = np.sum((y - y_hat) ** 2) # ๊ด์ธก๊ฐ - ์ถ์ ๊ฐ(SSR, Residual Sum of Squares)
denominator = np.sum((y - np.mean(y)) ** 2) # ๊ด์ธก๊ฐ - ๊ด์ธก๊ฐ์ ํ๊ท (SST, Total Sum of Squares)
r_squared = 1 - numerator / denominator
print('The Rยฒ value is: %.3f' % r_squared)
>>> The Rยฒ value is: 0.549 # 1์ ๊ฐ๊น์ธ์๋ก ์ข์ ์ฑ๋ฅ2.2 GLM in fMRI analyses
2.2.1 Variables
- ์ฐ๋ฆฌ๋ regressionํ๊ธฐ ์ํด ๋ ๋ฆฝ๋ณ์์ ์ข ์๋ณ์๋ฅผ ์ง์ ํ ํ์๊ฐ ์์
- ๋ ๋ฆฝ๋ณ์๋ (์๊ฐ) ์๊ทน ์์ ์ด๋ฉฐ, ๋ ๋ฆฝ๋ณ์๋ก๋ถํฐ ์์ธกํ๊ณ ์ ํ๋ ๋ณ์(์ข ์๋ณ์)๋ voxel signal์
1) Defining dependent variables
- fMRI ๋ฐ์ดํฐ๋ก๋ discreteํ ๋ณ์์ด๋ฏ๋ก continuousํ๊ฒ plotํ๋ฉด ๋ค์๊ณผ ๊ฐ์ด ๊ทธ๋ฆด ์ ์์
voxel_signal = np.load('example_voxel_signal.npy')
plt.figure(figsize=(25, 5))
plt.plot(voxel_signal) # discrete value to continuous line
plt.show()
2) Defining independent variables
- 50์ด ๊ฐ๊ฒฉ์ผ๋ก 1์ด๊ฐ ์ฌ๊ฐํ ํ๋ฉด๊ณผ ์ ํ๋ฉด์ ๋ฒ๊ฐ์๊ฐ๋ฉฐ ์๊ทน์ ์ค ๋ ๋ค์๊ณผ ๊ฐ์ด ํ์ํ ์ ์์

onsets_squares = np.array([10, 110, 210, 310, 410, 510, 610, 710], dtype=int) # ์ฌ๊ฐํ ํ๋ฉด ์๊ทน
onsets_circles = np.array([60, 160, 260, 360, 460, 560, 660, 760], dtype=int) # ์ ํ๋ฉด ์๊ทน
onsets_all = np.concatenate((onsets_squares, onsets_circles))
print(onsets_all)
>>> [ 10 110 210 310 410 510 610 710 60 160 260 360 460 560 660 760]
predictor_all = np.zeros(800)
predictor_all[onsets_all] = 1
print(predictor_all)
>>>
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 1., ...]3) Resampling
- ์ฐ๋ฆฌ๊ฐ ๋ง๋ ๋ ๋ฆฝ๋ณ์(stimulus_predictor, 800)๋ ์ค์ ์ ํธ ์๊ฐ(400)๊ณผ ๋ค๋ฅธ ์๊ฐ ๋จ์๋ฅผ ๊ฐ์ง
- fMRI๋ ๋๋ต 2์ด๋์ ์๋๋ถํฐ ์๊น์ง ์ธต์ผ๋ก ์ฌ๋ผ๊ฐ๋ฉด์ ์ค์บํ๊ธฐ ๋๋ฌธ์
- ์ฌ๊ธฐ์์๋ 2์ด๋ง๋ค ํ๋์ ๋ฐ์ดํฐ ํฌ์ธํธ๋ก down-sampling ํตํด ํด๊ฒฐ ๊ฐ๋ฅ
- Python์์
scipy.interpolateํจํค์ง ์์interp1dํจ์๋ฅผ ํตํด ๋ฆฌ์ํ๋ง ๊ฐ๋ฅ interp1dํจ์ ์ฌ์ฉ ์์resampler๋ณ์ ์์ ์ํ๋ ์ค์ผ์ผ์ ์ ์ฅํ๋ฉด ๊ทธ ์ค์ผ์ผ์ ๋ง์ถฐ ๋ฆฌ์ํ๋ง ๊ฐ๋ฅ- (+ ์ค์ ๋ก๋ ์ ๋ฐํ๊ฒ ๊ณ์ฐํ๊ธฐ ์ํด ๋ฐ๋ฆฌ์ด ๊ท๋ชจ๋ก ์ฌ์ฉํจ, [initial upsampling of predictors] ๋ถ๋ถ ํ์ธํ ๊ฒ)
from scipy.interpolate import interp1d
original_scale = np.arange(0, 800, 1) # from 0 to 800 seconds
print(original_scale)
>>> 800
resampler = interp1d(original_scale, predictor_all)
desired_scale = np.arange(0, 800, 2)
print(desired_scale)
>>> 400 # (0, 2, 4, ... 800, in volumes)
predictor_all_ds = resampler(desired_scale)- resampling์ ๊ฑฐ์น ๋ ๋ฆฝ๋ณ์์ ์ข ์๋ณ์๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์๊ฐํํ ์ ์์
plt.figure(figsize=(25, 10))
plt.plot(voxel_signal)
plt.plot(predictor_all_ds + voxel_signal.mean(), lw=3)
plt.show()
2.2.2 Regression on fMRI data & interpretation parameters
- ์ฐ๋ฆฌ์ ์์ ๋ฐ์ดํฐ๋ฅผ ํ์ฉํด ํ๊ท์์ ๋ง๋ค์์ ๊ฒฝ์ฐ ๋ค์๊ณผ ๊ฐ์ ๊ฒฐ๋ก ์ ์ป์ ์ ์์
Code
def test_regression_signal_simple(mse_ans, r2_ans, X, y):
X = np.hstack((np.ones((X.shape[0], 1)), X))
b = np.linalg.lstsq(X, y, rcond=None)[0]
y_hat = X.dot(b)
c_ans_mse = np.mean((y - y_hat) ** 2)
c_ans_r2 = pearsonr(y, y_hat)[0] ** 2
try:
np.testing.assert_almost_equal(mse_ans, c_ans_mse)
except AssertionError as e:
try:
print('Your MSE is %.3f; I expected %.3f' % (mse_ans, c_ans_mse))
raise e
finally:
e = None
del e
else:
print('MSE is correct')
try:
np.testing.assert_almost_equal(r2_ans, c_ans_r2)
except AssertionError as e:
try:
print('Your R2 is %.3f; I expected %.3f' % (r2_ans, c_ans_r2))
raise e
finally:
e = None
del e
else:
print('R2 is correct')
print('Well done!')
test_regression_signal_simple(mse_simple, r2_simple, predictor_all_ds, voxel_signal)def plot_signal_and_predicted_signal(y, X, x_lim=None, y_lim=None):
""" Plots a signal and its GLM prediction. """
des = np.hstack((np.ones((y.size, 1)), X))
betas_simple = np.linalg.lstsq(des, y, rcond=None)[0]
plt.figure(figsize=(15, 5))
plt.plot(y)
plt.plot(des @ betas_simple, lw=2)
plt.xlabel('Time (in volumes)', fontsize=15)
plt.ylabel('Activity (A.U.)', fontsize=15)
if x_lim is not None:
plt.xlim(x_lim)
if y_lim is not None:
plt.ylim(y_lim)
plt.legend(['True signal', 'Predicted signal'], loc='upper right', fontsize=15)
plt.title("Signal and predicted signal", fontsize=25)
plt.grid()
plt.show()
plot_signal_and_predicted_signal(voxel_signal, predictor_all_ds, x_lim, y_lim)

2.2.3 Using the BOLD response in GLM models
- ์ฐ๋ฆฌ๊ฐ ํ์ค์์ BOLD ์ ํธ์ ์ฆ๊ฐ์ ๋ณํ๋ฅผ ๋ชฉ๊ฒฉํ๋ ๊ฒ์ ๋นํ์ค์ ์
- ์ง์ ํ ์ ๊ฒฝ ํ๋์ ์ฝ 5-7์ด ํ์ ์ต๊ณ ์กฐ์ ๋ฌํ๊ธฐ ๋๋ฌธ์
- ๋ฐ๋ผ์ ์ฐ๋ฆฌ๋ canonical HRF์ convolution ๊ณ์ฐ์ ํตํด์ BOLD ์ ํธ์ ์ ์ฌํ ๊ทธ๋ํ๋ฅผ ๋ง๋ค ๊ฒ์
ย
1) The canonical HRF(ํ์ค HRF)
- fMRI ๋ฐ์ดํฐ์ BOLD ์ ํธ ๋จ๋ณ๋ ๋ถ์(univariate analyses): ๋ณต์ ์ ํญ์ ๊ฐ์ ๋ฐฉ์์ผ๋ก ์๊ทน์ ๋ฐ์ํ๋ค๊ณ ๊ฐ์ โ using โcanonical haemodynamic response function (HRF)โ
- canonical HRF: formalization of how the a voxel is going to respond to a stimulus
- ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉํ๋ HRF function์ double-gamma HRF์(ํ๋๋ overshoot์ ๋ชจ๋ธ๋งํ๊ณ ๋ค๋ฅธ ํ๋๋ undershoot์ ๋ชจ๋ธ๋งํจ)
TR = 2 # ์ค์บ ์ง์ ์๊ฐ
osf = 2 # ์ค๋ฒ์ํ๋ง
length_hrf = 32 # sec
canonical_hrf = glover_hrf(tr=TR, oversampling=osf, time_length=length_hrf, onset=0) # glover_hrf๋ double-gamma HRF ํจ์์
canonical_hrf /= canonical_hrf.max()
print(canonical_hrf.size)
>>> 32
t = np.arange(0, canonical_hrf.size)
plt.plot(t, canonical_hrf)
ย
2) Convolution
- HRF์ ์๊ทน ์ฌ๊ฑด์ ์ฝ๋ณผ๋ฃจ์ ์ฐ์ฐ์ ์๊ทน๊ณผ BOLD response์ ์ง์ฐ๋๋ ๋ชจ์์ ์์ธกํ ์ ์์

predictor_conv = np.convolve(predictor_all.squeeze(), canonical_hrf)
print(predictor_conv)
>>> (831,)
predictor_conv = predictor_conv[:predictor_all.size]
print(predictor_conv.shape)
>>> (800,)
predictor_conv = predictor_conv[:, np.newaxis]
print(predictor_conv.shape)
>>> (800, 1)plt.plot(predictor_all)
plt.plot(predictor_conv)
ย
3) Linear scaling
- BOLD response๋ฅผ (์ฃผ์ด์ง ํํ์) ์ ํ์ผ๋ก ํ์ฅํ๋ ๊ฒ
plt.figure(figsize=(15, 5))
N = 40
one_stim = np.zeros(N)
one_stim[5] = 1
one_stim_conv = np.convolve(one_stim, canonical_hrf)[:N]
two_stim = np.zeros(N)
two_stim[[5, 6]] = 1
two_stim_conv = np.convolve(two_stim, canonical_hrf)[:N]
three_stim = np.zeros(N)
three_stim[[5, 6, 7]] = 1
three_stim_conv = np.convolve(three_stim, canonical_hrf)[:N]
plt.plot(one_stim_conv)
plt.plot(two_stim_conv)
plt.plot(three_stim_conv)
plt.show()
ย
4) Resampling revisited
- convolved predictor์ BOLD signal์ ์ค์ผ์ผ ๋ง์ถ๊ธฐ ์ํด resampling์ ๋ค์ ํจ
original_scale = np.arange(0, 800)
resampler = interp1d(original_scale, np.squeeze(predictor_conv))
desired_scale = np.arange(0, 800, 2)
predictor_conv_ds = resampler(desired_scale)ย
5) Initial upsampling of predictors
- ์๊ทน์ ๋ฑ ๋จ์ด์ง๋ ์ด ๋จ์๋ก(10s ๋ฑ) ๋ํ๋์ง ์์
- ๋ณด๋ค ์ ํํ ์ด ๋จ์๋ก ์๊ทน ์์ธก๊ธฐ๋ฅผ ๋ง๋ค๊ธฐ ์ํด 100์ผ๋ก ์ค๋ฒ์ํ๋ง์ ํจ
onsets2 = np.array([3.62, 16.26, 34.12, 42.98]) # ์ ๋ฐํ ์๊ทน ์๊ฐ
duration = 50 # ์๊ทน ๊ฐ ์ฌ์ด ์๊ฐ
osf = 100 # osf = OverSampling Factor
pred2 = np.zeros(duration * osf)
onsets2_in_msec = (onsets2 * osf).astype(int)
pred2[onsets2_in_msec] = 1osf = 100 * TR # TR = 2
hrf_ms = glover_hrf(tr=TR, oversampling=osf, time_length=32)
hrf_ms /= hrf_ms.max()
t = np.arange(0, hrf_ms.size)
plt.figure(figsize=(12, 5))
plt.plot(t, hrf_ms)
pred2_conv = np.convolve(pred2, hrf_ms)[:pred2.size]
plt.plot(pred2_conv)
6) Downsampling
- Initial upsampling์ ํ๋ ๊ฒฝ์ฐ ๋ค์ downsampling์ ํตํด ์ฐจ์์ ํฌ๊ธฐ๋ฅผ ๋ง์ถค
resampler = interp1d(original_scale, pred2_conv)
desired_scale = np.arange(0, 50, TR)
pred2_conv_ds = resampler(desired_scale)ย
7) Fitting an HRF-informed model
intercept = np.ones((predictor_conv_ds.size, 1))
X_conv = np.hstack((intercept, predictor_conv_ds))
betas_conv = inv(X_conv.T @ X_conv) @ X_conv.T @ voxel_signal
plt.subplot(2, 1, 1)
plt.plot(voxel_signal)
plt.plot(X_conv @ betas_conv, lw=2)
X_simple = np.hstack((icept, predictor_all_ds))
betas_simple = np.array([1000.64701684, 1.02307437])
plt.subplot(2, 1, 2)
plt.plot(voxel_signal)
plt.plot(X_simple @ betas_simple, lw=2)
print(betas_conv[1], betas_simple[1])
>>> (8.181, 1.023)
plot_signal_and_predicted_signal_zoom(voxel_signal, predictor_conv_ds, x_lim=(25, 65), y_lim=(995, 1010))
from numpy.linalg import lstsq # numpy implementation of OLS, because we're lazy
y_hat_conv = X_conv @ betas_conv
y_hat_orig = X_simple @ lstsq(X_simple, voxel_signal, rcond=None)[0]
MSE_conv = ((y_hat_conv - voxel_signal) ** 2).mean()
MSE_orig = ((y_hat_orig - voxel_signal) ** 2).mean()
print(MSE_conv, MSE_orig)
>>> (6.022, 10.327)
R2_conv = 1 - (np.sum((voxel_signal - y_hat_conv) ** 2) / np.sum((voxel_signal - voxel_signal.mean()) ** 2))
R2_orig = 1 - (np.sum((voxel_signal - y_hat_orig) ** 2) / np.sum((voxel_signal - voxel_signal.mean()) ** 2))
print(R2_conv, R2_orig)
>>> (0.41919, 0.00388)ย
8) Temporal basis functions
- HRF ํจ์๋ฅผ ์ด์ฉํ๋ฉด ์ ์ ํธ๋ณด๋ค ์๊ทน์ด ๋๋ฆฌ๊ฒ ๋ํ๋ ์ ์์
- ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด Temporal basis function์ ์ด์ฉํจ(ex. double-gamma basis functions)
- double-gamma basis functions์ 1๊ณ ๋ฏธ๋ถํ๋ฉด ์๊ฐ์ ์ง์ฐ์ ์กฐ์ ํ ์ ์๊ณ (temporal), 2๊ณ ๋ฏธ๋ถํ๋ฉด ๋๋น์ ์ฐจ์ด๋ฅผ ์กฐ์ ํ ์ ์์(dispersion) (๊ฑฐ๋ฆฌ-์๋-๊ฐ์๋์ ๊ด๊ณ๋ ์ ์ฌํจ)
tderiv_hrf = glover_time_derivative(tr=2, oversampling=2)
tderiv_hrf /= tderiv_hrf.max()
plt.plot(canonical_hrf, lw=3)
plt.plot(tderiv_hrf, c='tab:orange', lw=3)
y_tbs, X_tbs = simulate_signal(~)
# regression with HRF only
beta_canon = inv(X_canon_only.T @ X_canon_only) @ X_canon_only.T @ y_tbs
yhat_canon = X_canon_only @ beta_canon
# regression with HRF + temporal deriv.
beta_td = inv(X_tbs.T @ X_tbs) @ X_tbs.T @ y_tbs
yhat_td = X_tbs @ beta_td

Summary
Linear Model
Formation of Linear Model
Variables
- Independent variables: stimulus of experiments
predict_all[onset_all] = 1
- Dependent variables: fMRI signal data(make continuous from discrete values), and match for time level
scipy.interpolateresampler = interp1d(original_scale, predict_all)
BOLD response in GLM
The canonical HRF + Convolution
- ์ฐ๋ฆฌ๊ฐ ํ์ค์์ BOLD ์ ํธ์ ์ฆ๊ฐ์ ๋ณํ๋ฅผ ๋ชฉ๊ฒฉํ๋ ๊ฒ์ ๋นํ์ค์ ์
- ์ง์ ํ ์ ๊ฒฝ ํ๋์ ์ฝ 5-7์ด ํ์ ์ต๊ณ ์กฐ์ ๋ฌํ๊ธฐ ๋๋ฌธ์
- ๋ฐ๋ผ์ ์ฐ๋ฆฌ๋ canonical HRF์ convolution ๊ณ์ฐ์ ํตํด์ BOLD ์ ํธ์ ์ ์ฌํ ๊ทธ๋ํ๋ฅผ ๋ง๋ค ๊ฒ์
ย
canonical_hrf = glover_hrf(tr, oversampling, time_length, onset)
predictor_conv = np.convolve(predict_all, canonical_hrf)Resampling
- convolved predictor์ BOLD signal์ ์ค์ผ์ผ ๋ง์ถค
ย
original_scale = np.arange(0, 800)
resampler = interp1d(original_scale, np.squeeze(predictor_conv))
desired_scale = np.arange(0, 800, 2)
predictor_conv_ds = resampler(desired_scale)ย
Initial upsampling of predictors
- ์ ํํ ์๊ทน ์์ธก๊ธฐ(๋ ๋ฆฝ๋ณ์)๋ฅผ ์ํด ์ค๋ฒ์ํ๋งํจ
- ์ดํ HRF + Convolution ๊ณ์ฐ ๊ณผ์ ๊ฑฐ์นจ
- ๋ง์ง๋ง์ผ๋ก ์ค๋ฒ์ํ๋ง ํ์๋ ๋ค์ด์ํ๋ง ํน์ ๋ฆฌ์ํ๋ง ๊ณผ์ ์ ๊ฑฐ์ณ์ผ ํจ
ย
onsets2 = np.array([3.62, ...])
duration = 50
oversampling = 100
pred2[onsets2_in_msec] = 1Temporal basis function
- HRF ํจ์๋ฅผ ์ด์ฉํ๋ฉด ์ ์ ํธ๋ณด๋ค ์๊ทน์ด ๋๋ฆฌ๊ฒ ๋ํ๋ ์ ์์
- ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด Temporal basis function์ ์ด์ฉํจ(ex. double-gamma basis functions)
tderiv_hrf = glover_time_derivative(tr=2, oversampling=2)
tderiv_hrf /= tderiv_hrf.max()