{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# PMEmo: A Dataset for Music Emotion Recognition\n",
"Hui Zhang, Kejun Zhang, Yehang Yin, BaiXi Xing, Lingyun Sun, Shouqian Sun\n",
"\n",
"## Baselines in Dynamic Emotion Recognition\n",
"This notebook evaluates: \n",
"* standard regressors from scikit-learn on the dynamic audio features.\n",
"* standard regressors from scikit-learn on the dynamic EDA features.\n",
"* multimodal emotion recognition based on fusion featrues."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import os\n",
"import numpy as np\n",
"from math import sqrt\n",
"\n",
"from sklearn.utils import shuffle\n",
"from sklearn.linear_model import Lasso, ElasticNet, Ridge\n",
"from sklearn.svm import SVR, LinearSVR\n",
"from sklearn.neighbors import KNeighborsRegressor\n",
"from sklearn.tree import DecisionTreeRegressor\n",
"from sklearn.ensemble import RandomForestRegressor, AdaBoostRegressor\n",
"from sklearn.neural_network import MLPRegressor\n",
"from sklearn.metrics import mean_squared_error, make_scorer\n",
"from scipy.stats import pearsonr\n",
"from sklearn.preprocessing import StandardScaler\n",
"from sklearn.model_selection import cross_validate, KFold, train_test_split\n",
"from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer\n",
"from sklearn.pipeline import make_pipeline\n",
"from nltk.stem.snowball import SnowballStemmer\n",
"from tqdm import tqdm_notebook\n",
"import IPython.display as ipd"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Loading Data"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"DATASET_DIR = 'dataset'\n",
"\n",
"features = pd.read_csv(os.path.join(DATASET_DIR, 'dynamic_features.csv'))\n",
"annotations = pd.read_csv(os.path.join(DATASET_DIR, 'dynamic_annotations.csv'))\n",
"dataset = pd.merge(features, annotations, on=['musicId', 'frameTime'])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Metric and Multiple Regressors"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"def rmse(y, y_pred):\n",
" return sqrt(mean_squared_error(y, y_pred))\n",
"\n",
"regressors = {\n",
" 'Lasso': Lasso(),\n",
" 'ElasticNet': ElasticNet(),\n",
" 'Ridge': Ridge(),\n",
" 'kNN': KNeighborsRegressor(),\n",
" 'SVRrbf': SVR(kernel='rbf', gamma='scale'),\n",
" 'SVRpoly': SVR(kernel='poly', gamma='scale'),\n",
" 'SVRlinear': SVR(kernel='linear', gamma='scale'),\n",
" 'DT': DecisionTreeRegressor(max_depth=5),\n",
" 'RF': RandomForestRegressor(max_depth=5, n_estimators=10, max_features=1),\n",
"# 'MLP': MLPRegressor(hidden_layer_sizes=(200,50), max_iter=2000),\n",
"# 'AdaBoost': AdaBoostRegressor(n_estimators=10),\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"def cross_val_regression(regressors, features, labels, preprocessfunc):\n",
" columns = list(regressors.keys())\n",
" scores = pd.DataFrame(columns=columns, index=['RMSE'])\n",
"\n",
" for reg_name, reg in tqdm_notebook(regressors.items(), desc='regressors'):\n",
" scorer = {'rmse': make_scorer(rmse)}\n",
" reg = make_pipeline(*preprocessfunc, reg)\n",
" reg_score = cross_validate(reg, features, labels, scoring=scorer, cv=10, return_train_score=False) \n",
" scores.loc['RMSE', reg_name] = reg_score['test_rmse'].mean()\n",
"# scores.loc['R', reg_name] = reg_score['test_r'].mean()\n",
" return scores\n",
"\n",
"def format_scores(scores):\n",
" def highlight(s):\n",
" is_min = s == min(s)\n",
"# is_max = s == max(s)\n",
"# is_max_or_min = (is_min | is_max)\n",
" return ['background-color: yellow' if v else '' for v in is_min]\n",
" scores = scores.style.apply(highlight, axis=1, subset=pd.IndexSlice[:, :scores.columns[-2]])\n",
" return scores.format('{:.3f}')\n",
"\n",
"def regression_results(regressors, trainset, testset, featureNames, labelName, filePrefix, preprocessfunc):\n",
" X_train = trainset[featureNames]\n",
" y_train = trainset[labelName]\n",
" X_test = testset[featureNames]\n",
" y_test = testset[labelName]\n",
"\n",
" columns = ['musicId', 'y_test'] + list(regressors.keys())\n",
" results = pd.DataFrame(columns=columns)\n",
" results['musicId'] = testset['musicId']\n",
" results['y_test'] = y_test.values\n",
" \n",
" for reg_name, reg in tqdm_notebook(regressors.items(), desc='regressors'):\n",
" reg = make_pipeline(*preprocessfunc, reg)\n",
" reg.fit(X_train, y_train)\n",
" y_pred = reg.predict(X_test)\n",
" results[reg_name] = y_pred\n",
" results.to_csv(os.path.join('temp_results',f'{filePrefix}_regression_results_{labelName}.csv'))\n",
" \n",
"def compute_rmse_across_songs(resultsFile):\n",
" results = pd.read_csv(resultsFile,index_col=0).dropna(axis=1, how='any')\n",
" columns = results.columns[2:]\n",
" scores = pd.DataFrame(columns=columns, index=['rmse_across_segments', 'rmse_across_songs'])\n",
" rmse_across_songs = {}\n",
" testsongs_num = len(results['musicId'].unique())\n",
"\n",
" for reg_name in columns:\n",
" scores.loc['rmse_across_segments', reg_name] = rmse(results['y_test'], results[reg_name])\n",
" rmse_across_songs[reg_name] = 0\n",
"\n",
" for i, g in results.groupby('musicId'):\n",
" for reg_name in columns:\n",
" rmse_across_songs[reg_name] += rmse(g['y_test'], g[reg_name])\n",
"\n",
" for reg_name in columns:\n",
" scores.loc['rmse_across_songs', reg_name] = rmse_across_songs[reg_name]/testsongs_num\n",
" \n",
" mean_rmse = scores.mean(axis=1)\n",
" std_rmse = scores.std(axis=1)\n",
" \n",
" scores['Mean'] = mean_rmse\n",
" scores['std'] = std_rmse\n",
" ipd.display(format_scores(scores))"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[63, 490, 34, 743, 104, 177, 79, 894, 14, 668, 683, 151, 504, 516, 355, 98, 97, 579, 892, 837, 152, 169, 388, 391, 561, 850, 985, 958, 572, 514, 625, 791, 517, 507, 501, 1000, 803, 457, 403, 670, 51, 798, 59, 531, 466, 503, 794, 568, 279, 103, 350, 917, 428, 417, 393, 571, 354, 283, 906, 149, 56, 128, 742, 993, 94, 754, 199, 57, 576, 463, 284, 126, 488, 253, 227, 730, 861]\n"
]
}
],
"source": [
"songs = dataset['musicId'].unique()\n",
"songs = shuffle(songs, random_state=3)\n",
"test_num = round(len(songs)*0.1)\n",
"testsongs = songs[:test_num]\n",
"print(list(testsongs))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Multiple Regressors on Audio Features\n",
"\n",
"Evaluating regressors on 260-dim audio features."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"In Arousal dimension...\n"
]
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "d378f83ac05c405f9860957cb2f84b73",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"HBox(children=(IntProgress(value=0, description='regressors', max=9), HTML(value='')))"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"In Valence dimension...\n"
]
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "9b552d665e304ad1b0ef236d7cda405c",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"HBox(children=(IntProgress(value=0, description='regressors', max=9), HTML(value='')))"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n"
]
}
],
"source": [
"iftestset = dataset['musicId'].apply(lambda x: x in testsongs)\n",
"testset = dataset[iftestset]\n",
"trainset = dataset[~iftestset]\n",
"prefunc = [StandardScaler()]\n",
"featureNames = dataset.columns[2:262]\n",
"\n",
"print('In Arousal dimension...')\n",
"regression_results(regressors, trainset, testset, featureNames, 'Arousal(mean)', 'audio', prefunc)\n",
"\n",
"print('In Valence dimension...')\n",
"regression_results(regressors, trainset, testset, featureNames, 'Valence(mean)', 'audio', prefunc)"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"In Arousal dimension...\n"
]
},
{
"data": {
"text/html": [
" \n",
"
\n",
" \n",
" | \n",
" Lasso | \n",
" ElasticNet | \n",
" Ridge | \n",
" kNN | \n",
" SVRrbf | \n",
" SVRpoly | \n",
" SVRlinear | \n",
" DT | \n",
" RF | \n",
" Mean | \n",
" std | \n",
"
\n",
" \n",
" rmse_across_segments | \n",
" 0.187 | \n",
" 0.187 | \n",
" 0.131 | \n",
" 0.149 | \n",
" 0.128 | \n",
" 0.159 | \n",
" 0.131 | \n",
" 0.134 | \n",
" 0.153 | \n",
" 0.151 | \n",
" 0.023 | \n",
"
\n",
" rmse_across_songs | \n",
" 0.158 | \n",
" 0.158 | \n",
" 0.108 | \n",
" 0.128 | \n",
" 0.107 | \n",
" 0.136 | \n",
" 0.108 | \n",
" 0.119 | \n",
" 0.131 | \n",
" 0.128 | \n",
" 0.020 | \n",
"
\n",
"
"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"In Valence dimension...\n"
]
},
{
"data": {
"text/html": [
" \n",
" \n",
" \n",
" | \n",
" Lasso | \n",
" ElasticNet | \n",
" Ridge | \n",
" kNN | \n",
" SVRrbf | \n",
" SVRpoly | \n",
" SVRlinear | \n",
" DT | \n",
" RF | \n",
" Mean | \n",
" std | \n",
"
\n",
" \n",
" rmse_across_segments | \n",
" 0.157 | \n",
" 0.157 | \n",
" 0.139 | \n",
" 0.147 | \n",
" 0.131 | \n",
" 0.158 | \n",
" 0.138 | \n",
" 0.141 | \n",
" 0.142 | \n",
" 0.146 | \n",
" 0.010 | \n",
"
\n",
" rmse_across_songs | \n",
" 0.128 | \n",
" 0.128 | \n",
" 0.115 | \n",
" 0.123 | \n",
" 0.109 | \n",
" 0.134 | \n",
" 0.113 | \n",
" 0.118 | \n",
" 0.115 | \n",
" 0.120 | \n",
" 0.008 | \n",
"
\n",
"
"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"print('In Arousal dimension...')\n",
"compute_rmse_across_songs(os.path.join('temp_results','audio_regression_results_Arousal(mean).csv'))\n",
"print('In Valence dimension...')\n",
"compute_rmse_across_songs(os.path.join('temp_results','audio_regression_results_Valence(mean).csv'))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Multiple Regressors on EDA Features\n",
"\n",
"Evaluating regressors on dynamic EDA features."
]
},
{
"cell_type": "code",
"execution_count": 43,
"metadata": {},
"outputs": [],
"source": [
"eda_features = pd.read_csv('EDA_features_dynamic.csv').astype(float)\n",
"eda_dataset = pd.merge(eda_features, annotations, on=['musicId', 'frameTime']).dropna()\n",
"eda_dataset = eda_dataset.groupby(by=['musicId', 'frameTime'], as_index=False).mean()"
]
},
{
"cell_type": "code",
"execution_count": 45,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"In Arousal dimension...\n"
]
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "101cb40b060c4ce5aee3257b33d409c7",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"HBox(children=(IntProgress(value=0, description='regressors', max=9), HTML(value='')))"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"In Valence dimension...\n"
]
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "eec20073e9d1406ab34aeed97e25b57a",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"HBox(children=(IntProgress(value=0, description='regressors', max=9), HTML(value='')))"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n"
]
}
],
"source": [
"iftestset = eda_dataset['musicId'].apply(lambda x: x in testsongs)\n",
"testset = eda_dataset[iftestset]\n",
"trainset = eda_dataset[~iftestset]\n",
"prefunc = [StandardScaler()]\n",
"featureNames = list(set(eda_dataset.columns).difference({'subjectId', 'musicId', 'frameTime', \n",
" 'Arousal(mean)', 'Valence(mean)'}))\n",
"\n",
"print('In Arousal dimension...')\n",
"regression_results(regressors, trainset, testset, featureNames, 'Arousal(mean)', 'eda', prefunc)\n",
"\n",
"print('In Valence dimension...')\n",
"regression_results(regressors, trainset, testset, featureNames, 'Valence(mean)', 'eda', prefunc)"
]
},
{
"cell_type": "code",
"execution_count": 46,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"In Arousal dimension...\n"
]
},
{
"data": {
"text/html": [
" \n",
" \n",
" \n",
" | \n",
" Lasso | \n",
" ElasticNet | \n",
" Ridge | \n",
" kNN | \n",
" SVRrbf | \n",
" SVRpoly | \n",
" SVRlinear | \n",
" DT | \n",
" RF | \n",
" Mean | \n",
" std | \n",
"
\n",
" \n",
" rmse_across_segments | \n",
" 0.187 | \n",
" 0.187 | \n",
" 0.185 | \n",
" 0.204 | \n",
" 0.195 | \n",
" 0.209 | \n",
" 0.186 | \n",
" 0.187 | \n",
" 0.186 | \n",
" 0.192 | \n",
" 0.009 | \n",
"
\n",
" rmse_across_songs | \n",
" 0.158 | \n",
" 0.158 | \n",
" 0.156 | \n",
" 0.183 | \n",
" 0.169 | \n",
" 0.178 | \n",
" 0.154 | \n",
" 0.156 | \n",
" 0.156 | \n",
" 0.163 | \n",
" 0.011 | \n",
"
\n",
"
"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"In Valence dimension...\n"
]
},
{
"data": {
"text/html": [
" \n",
" \n",
" \n",
" | \n",
" Lasso | \n",
" ElasticNet | \n",
" Ridge | \n",
" kNN | \n",
" SVRrbf | \n",
" SVRpoly | \n",
" SVRlinear | \n",
" DT | \n",
" RF | \n",
" Mean | \n",
" std | \n",
"
\n",
" \n",
" rmse_across_segments | \n",
" 0.157 | \n",
" 0.157 | \n",
" 0.154 | \n",
" 0.171 | \n",
" 0.162 | \n",
" 0.165 | \n",
" 0.150 | \n",
" 0.158 | \n",
" 0.155 | \n",
" 0.159 | \n",
" 0.006 | \n",
"
\n",
" rmse_across_songs | \n",
" 0.128 | \n",
" 0.128 | \n",
" 0.126 | \n",
" 0.151 | \n",
" 0.139 | \n",
" 0.140 | \n",
" 0.123 | \n",
" 0.129 | \n",
" 0.126 | \n",
" 0.132 | \n",
" 0.009 | \n",
"
\n",
"
"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"print('In Arousal dimension...')\n",
"compute_rmse_across_songs(os.path.join('temp_results','eda_regression_results_Arousal(mean).csv'))\n",
"print('In Valence dimension...')\n",
"compute_rmse_across_songs(os.path.join('temp_results','eda_regression_results_Valence(mean).csv'))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Multimodal emotion recognition fusing audio and EDA featrues.\n",
"\n",
"Evaluating multimodal fusion methods using audio and EDA featrues fusion:\n",
"\n",
"1. early-fusion-by-feature-concatenation (EFFC): Concatenate the audio and text features to a single feature vector and train a single classification model.\n",
"2. late-fusion-by-linear-combination (LFLC): Train two regressors separately and combine their predictions afterward in a linear way."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Multimodal fusion methods 1 -- EFFC"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {},
"outputs": [],
"source": [
"fusion_dataset = pd.merge(eda_dataset, features, on=['musicId', 'frameTime'])"
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"In Arousal dimension...\n"
]
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "32559d87391643f887844b13304f085d",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"HBox(children=(IntProgress(value=0, description='regressors', max=9), HTML(value='')))"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"In Valence dimension...\n"
]
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "72186e9a9cf341e68d40d52aa9b093d1",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"HBox(children=(IntProgress(value=0, description='regressors', max=9), HTML(value='')))"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n"
]
}
],
"source": [
"fusion_dataset = fusion_dataset.groupby(by=['musicId', 'frameTime'], as_index=False).mean()\n",
"\n",
"iftestset = fusion_dataset['musicId'].apply(lambda x: x in testsongs)\n",
"testset = fusion_dataset[iftestset]\n",
"trainset = fusion_dataset[~iftestset]\n",
"prefunc = [StandardScaler()]\n",
"featureNames = list(set(fusion_dataset.columns).difference({'subjectId', 'musicId', 'frameTime', \n",
" 'Arousal(mean)', 'Valence(mean)'}))\n",
"print('In Arousal dimension...')\n",
"regression_results(regressors, trainset, testset, featureNames, 'Arousal(mean)', 'mean_fusion', prefunc)\n",
"\n",
"print('In Valence dimension...')\n",
"regression_results(regressors, trainset, testset, featureNames, 'Valence(mean)', 'mean_fusion', prefunc)"
]
},
{
"cell_type": "code",
"execution_count": 36,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"In Arousal dimension...\n"
]
},
{
"data": {
"text/html": [
" \n",
" \n",
" \n",
" | \n",
" Lasso | \n",
" ElasticNet | \n",
" Ridge | \n",
" kNN | \n",
" SVRrbf | \n",
" SVRpoly | \n",
" SVRlinear | \n",
" DT | \n",
" RF | \n",
" Mean | \n",
" std | \n",
"
\n",
" \n",
" rmse_across_segments | \n",
" 0.187 | \n",
" 0.187 | \n",
" 0.129 | \n",
" 0.147 | \n",
" 0.126 | \n",
" 0.153 | \n",
" 0.129 | \n",
" 0.135 | \n",
" 0.160 | \n",
" 0.150 | \n",
" 0.024 | \n",
"
\n",
" rmse_across_songs | \n",
" 0.158 | \n",
" 0.158 | \n",
" 0.108 | \n",
" 0.130 | \n",
" 0.108 | \n",
" 0.137 | \n",
" 0.107 | \n",
" 0.119 | \n",
" 0.134 | \n",
" 0.129 | \n",
" 0.020 | \n",
"
\n",
"
"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"In Valence dimension...\n"
]
},
{
"data": {
"text/html": [
" \n",
" \n",
" \n",
" | \n",
" Lasso | \n",
" ElasticNet | \n",
" Ridge | \n",
" kNN | \n",
" SVRrbf | \n",
" SVRpoly | \n",
" SVRlinear | \n",
" DT | \n",
" RF | \n",
" Mean | \n",
" std | \n",
"
\n",
" \n",
" rmse_across_segments | \n",
" 0.157 | \n",
" 0.157 | \n",
" 0.138 | \n",
" 0.146 | \n",
" 0.130 | \n",
" 0.161 | \n",
" 0.136 | \n",
" 0.140 | \n",
" 0.141 | \n",
" 0.145 | \n",
" 0.011 | \n",
"
\n",
" rmse_across_songs | \n",
" 0.128 | \n",
" 0.128 | \n",
" 0.114 | \n",
" 0.126 | \n",
" 0.109 | \n",
" 0.137 | \n",
" 0.113 | \n",
" 0.117 | \n",
" 0.115 | \n",
" 0.121 | \n",
" 0.009 | \n",
"
\n",
"
"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"print('In Arousal dimension...')\n",
"compute_rmse_across_songs(os.path.join('temp_results','mean_fusion_regression_results_Arousal(mean).csv'))\n",
"print('In Valence dimension...')\n",
"compute_rmse_across_songs(os.path.join('temp_results','mean_fusion_regression_results_Valence(mean).csv'))"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.4"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": true,
"sideBar": true,
"skip_h1_title": true,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {
"height": "calc(100% - 180px)",
"left": "10px",
"top": "150px",
"width": "410px"
},
"toc_section_display": true,
"toc_window_display": true
}
},
"nbformat": 4,
"nbformat_minor": 2
}