From f71532051d2430d21b49a90eae56c86b8e30b7a8 Mon Sep 17 00:00:00 2001 From: meilame-tayebjee Date: Thu, 27 Nov 2025 10:06:09 +0000 Subject: [PATCH 1/8] fix: keep notebook outputs fix plot word attributions --- notebooks/example.ipynb | 1102 ++++++++++++++++- .../utilities/plot_explainability.py | 24 +- 2 files changed, 1061 insertions(+), 65 deletions(-) diff --git a/notebooks/example.ipynb b/notebooks/example.ipynb index 6712468..0b225d8 100644 --- a/notebooks/example.ipynb +++ b/notebooks/example.ipynb @@ -25,12 +25,22 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 35, "id": "1", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The autoreload extension is already loaded. To reload it, use:\n", + " %reload_ext autoreload\n" + ] + } + ], "source": [ "import torch\n", + "import numpy as np\n", "from sklearn.model_selection import train_test_split\n", "from sklearn.preprocessing import LabelEncoder\n", "\n", @@ -51,6 +61,7 @@ " map_attributions_to_word,\n", " plot_attributions_at_char,\n", " plot_attributions_at_word,\n", + " figshow\n", ")\n", "\n", "%load_ext autoreload\n", @@ -70,10 +81,191 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "3", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
apet_finalelibelleCJNATTYPSRFCRT
06202ACONSEIL EN SYSTEMES ET LOGICIELS INFORMATIQUESNaNNaNX0.0P
19529Zpetit bricolageNaNNaNX0.0P
24332AMenuiserie intérieure, Agencement571014M0.0P
36831ZAgent commercial en immobilierNaNNaNR0.0NaN
46820ALocation meublé d'un appartement dans le centr...NaNNaNL0.0P
........................
16933849609ZAutres services personnels n.c.a.NaNNaNNaN0.0NaN
16933859700ZActivités des ménages en tant qu'employeurs de...NaNNaNNaN0.0NaN
16933869810ZActivités indifférenciées des ménages en tant ...NaNNaNNaN0.0NaN
16933879820ZActivités indifférenciées des ménages en tant ...NaNNaNNaN0.0NaN
16933889900ZActivités des organisations et organismes extr...NaNNaNNaN0.0NaN
\n", + "

1693389 rows × 7 columns

\n", + "
" + ], + "text/plain": [ + " apet_finale libelle CJ \\\n", + "0 6202A CONSEIL EN SYSTEMES ET LOGICIELS INFORMATIQUES NaN \n", + "1 9529Z petit bricolage NaN \n", + "2 4332A Menuiserie intérieure, Agencement 5710 \n", + "3 6831Z Agent commercial en immobilier NaN \n", + "4 6820A Location meublé d'un appartement dans le centr... NaN \n", + "... ... ... ... \n", + "1693384 9609Z Autres services personnels n.c.a. NaN \n", + "1693385 9700Z Activités des ménages en tant qu'employeurs de... NaN \n", + "1693386 9810Z Activités indifférenciées des ménages en tant ... NaN \n", + "1693387 9820Z Activités indifférenciées des ménages en tant ... NaN \n", + "1693388 9900Z Activités des organisations et organismes extr... NaN \n", + "\n", + " NAT TYP SRF CRT \n", + "0 NaN X 0.0 P \n", + "1 NaN X 0.0 P \n", + "2 14 M 0.0 P \n", + "3 NaN R 0.0 NaN \n", + "4 NaN L 0.0 P \n", + "... ... ... ... ... \n", + "1693384 NaN NaN 0.0 NaN \n", + "1693385 NaN NaN 0.0 NaN \n", + "1693386 NaN NaN 0.0 NaN \n", + "1693387 NaN NaN 0.0 NaN \n", + "1693388 NaN NaN 0.0 NaN \n", + "\n", + "[1693389 rows x 7 columns]" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "import pandas as pd\n", "\n", @@ -93,17 +285,198 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "5", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
apet_finalelibelleCJNATTYPSRFCRT
06202ACONSEIL EN SYSTEMES ET LOGICIELS INFORMATIQUESNaNNaNX0.0P
19529Zpetit bricolageNaNNaNX0.0P
24332AMenuiserie intérieure, Agencement571014M0.0P
36831ZAgent commercial en immobilierNaNNaNR0.0NaN
46820ALocation meublé d'un appartement dans le centr...NaNNaNL0.0P
........................
16933849609ZAutres services personnels n.c.a.NaNNaNNaN0.0NaN
16933859700ZActivités des ménages en tant qu'employeurs de...NaNNaNNaN0.0NaN
16933869810ZActivités indifférenciées des ménages en tant ...NaNNaNNaN0.0NaN
16933879820ZActivités indifférenciées des ménages en tant ...NaNNaNNaN0.0NaN
16933889900ZActivités des organisations et organismes extr...NaNNaNNaN0.0NaN
\n", + "

1693389 rows × 7 columns

\n", + "
" + ], + "text/plain": [ + " apet_finale libelle CJ \\\n", + "0 6202A CONSEIL EN SYSTEMES ET LOGICIELS INFORMATIQUES NaN \n", + "1 9529Z petit bricolage NaN \n", + "2 4332A Menuiserie intérieure, Agencement 5710 \n", + "3 6831Z Agent commercial en immobilier NaN \n", + "4 6820A Location meublé d'un appartement dans le centr... NaN \n", + "... ... ... ... \n", + "1693384 9609Z Autres services personnels n.c.a. NaN \n", + "1693385 9700Z Activités des ménages en tant qu'employeurs de... NaN \n", + "1693386 9810Z Activités indifférenciées des ménages en tant ... NaN \n", + "1693387 9820Z Activités indifférenciées des ménages en tant ... NaN \n", + "1693388 9900Z Activités des organisations et organismes extr... NaN \n", + "\n", + " NAT TYP SRF CRT \n", + "0 NaN X 0.0 P \n", + "1 NaN X 0.0 P \n", + "2 14 M 0.0 P \n", + "3 NaN R 0.0 NaN \n", + "4 NaN L 0.0 P \n", + "... ... ... ... ... \n", + "1693384 NaN NaN 0.0 NaN \n", + "1693385 NaN NaN 0.0 NaN \n", + "1693386 NaN NaN 0.0 NaN \n", + "1693387 NaN NaN 0.0 NaN \n", + "1693388 NaN NaN 0.0 NaN \n", + "\n", + "[1693389 rows x 7 columns]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "df" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "6", "metadata": {}, "outputs": [], @@ -129,7 +502,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "8", "metadata": {}, "outputs": [], @@ -153,10 +526,191 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "10", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
libelleCJNATTYPSRFCRTapet_finale
0CONSEIL EN SYSTEMES ET LOGICIELS INFORMATIQUES135141201550
1petit bricolage135141201720
2Menuiserie intérieure, Agencement693701355
3Agent commercial en immobilier135141000580
4Location meublé d'un appartement dans le centr...13514601578
........................
1693384Autres services personnels n.c.a.13514800727
1693385Activités des ménages en tant qu'employeurs de...13514800728
1693386Activités indifférenciées des ménages en tant ...13514800729
1693387Activités indifférenciées des ménages en tant ...13514800730
1693388Activités des organisations et organismes extr...13514800731
\n", + "

1693389 rows × 7 columns

\n", + "
" + ], + "text/plain": [ + " libelle CJ NAT TYP \\\n", + "0 CONSEIL EN SYSTEMES ET LOGICIELS INFORMATIQUES 135 14 12 \n", + "1 petit bricolage 135 14 12 \n", + "2 Menuiserie intérieure, Agencement 69 3 7 \n", + "3 Agent commercial en immobilier 135 14 10 \n", + "4 Location meublé d'un appartement dans le centr... 135 14 6 \n", + "... ... ... ... ... \n", + "1693384 Autres services personnels n.c.a. 135 14 8 \n", + "1693385 Activités des ménages en tant qu'employeurs de... 135 14 8 \n", + "1693386 Activités indifférenciées des ménages en tant ... 135 14 8 \n", + "1693387 Activités indifférenciées des ménages en tant ... 135 14 8 \n", + "1693388 Activités des organisations et organismes extr... 135 14 8 \n", + "\n", + " SRF CRT apet_finale \n", + "0 0 1 550 \n", + "1 0 1 720 \n", + "2 0 1 355 \n", + "3 0 0 580 \n", + "4 0 1 578 \n", + "... ... ... ... \n", + "1693384 0 0 727 \n", + "1693385 0 0 728 \n", + "1693386 0 0 729 \n", + "1693387 0 0 730 \n", + "1693388 0 0 731 \n", + "\n", + "[1693389 rows x 7 columns]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "df, _ = clean_and_tokenize_df(df, text_feature=\"libelle\")\n", "X = df[[\"libelle\", \"CJ\", \"NAT\", \"TYP\", \"CRT\", \"SRF\"]].values\n", @@ -167,10 +721,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "11", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "((1693389, 6), (1693389,))" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "X.shape, y.shape" ] @@ -185,7 +750,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "13", "metadata": {}, "outputs": [], @@ -213,7 +778,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "16", "metadata": {}, "outputs": [], @@ -231,10 +796,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "18", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "This tokenizer outputs tensors of size torch.Size([1, 12])\n", + "The tokens are here ['[CLS]', 'tr', '##ava', '##ux', 'd', \"'\", 'isolation', 'ex', '##ter', '##ieu', '##re', '[SEP]']\n", + "The total number of tokens is 30522\n" + ] + } + ], "source": [ "tokenizer = HuggingFaceTokenizer.load_from_pretrained(\"google-bert/bert-base-uncased\")\n", "print(\"This tokenizer outputs tensors of size \", tokenizer.tokenize(text[0]).input_ids.shape)\n", @@ -252,10 +827,23 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "20", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\n", + "This tokenizer outputs tensors of size torch.Size([1, 125])\n", + "The tokens are here ['[SEP]', 'travaux', 'd', \"'\", 'isolation', 'exterieure', '[CLS]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']\n", + "The total number of tokens is 5000\n" + ] + } + ], "source": [ "tokenizer = WordPieceTokenizer(vocab_size=5000, output_dim=125)\n", "tokenizer.train(text)\n", @@ -282,10 +870,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "id": "23", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "(\"TRAVAUX D'ISOLATION EXTERIEURE \", [135, 3, 7, 1, 0], np.int64(352))" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "train_dataset = TextClassificationDataset(\n", " texts=X_train[:, 0].tolist(),\n", @@ -306,10 +905,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "id": "25", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Input IDs shape: torch.Size([256, 125])\n" + ] + } + ], "source": [ "train_dataloader = train_dataset.create_dataloader(\n", " batch_size=256,\n", @@ -355,7 +962,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "id": "29", "metadata": {}, "outputs": [], @@ -392,7 +999,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "id": "31", "metadata": {}, "outputs": [], @@ -414,20 +1021,64 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "id": "32", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "TextEmbedder(\n", + " (embedding_layer): Embedding(5000, 96, padding_idx=1)\n", + " (transformer): ModuleDict(\n", + " (h): ModuleList(\n", + " (0): Block(\n", + " (attn): SelfAttentionLayer(\n", + " (c_q): Linear(in_features=96, out_features=96, bias=False)\n", + " (c_k): Linear(in_features=96, out_features=96, bias=False)\n", + " (c_v): Linear(in_features=96, out_features=96, bias=False)\n", + " (c_proj): Linear(in_features=96, out_features=96, bias=False)\n", + " )\n", + " (mlp): MLP(\n", + " (c_fc): Linear(in_features=96, out_features=384, bias=False)\n", + " (c_proj): Linear(in_features=384, out_features=96, bias=False)\n", + " )\n", + " )\n", + " )\n", + " )\n", + ")" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "text_embedder" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "id": "33", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "TextEmbedder input: tensor([[ 3, 326, 40, ..., 1, 1, 1],\n", + " [ 3, 1411, 1837, ..., 1, 1, 1],\n", + " [ 3, 199, 126, ..., 1, 1, 1],\n", + " ...,\n", + " [ 3, 1045, 1111, ..., 1, 1, 1],\n", + " [ 3, 387, 259, ..., 1, 1, 1],\n", + " [ 3, 386, 296, ..., 1, 1, 1]])\n", + "TextEmbedder output shape: torch.Size([256, 96])\n" + ] + } + ], "source": [ "# test the TextEmbedder: it takes as input a tensor of token ids and outputs a tensor of embeddings\n", "\n", @@ -457,10 +1108,23 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "id": "36", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "torch.Size([256, 55])\n", + "How will the categorical embedding be merged with the text one ? CategoricalForwardType.CONCATENATE_ALL\n", + "torch.Size([256, 96])\n", + "How will the categorical embedding be merged with the text one ? CategoricalForwardType.SUM_TO_TEXT\n", + "torch.Size([256, 25])\n", + "How will the categorical embedding be merged with the text one ? CategoricalForwardType.AVERAGE_AND_CONCAT\n" + ] + } + ], "source": [ "categorical_vocab_sizes = (X[:, 1:].max(axis=0) + 1).tolist()\n", "\n", @@ -510,7 +1174,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 19, "id": "38", "metadata": {}, "outputs": [], @@ -531,10 +1195,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "id": "39", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "logits shape: torch.Size([256, 732])\n" + ] + } + ], "source": [ "x_combined = torch.cat((text_embedder_output, cat_var_net_output), dim=1)\n", "logits = classification_head(x_combined)\n", @@ -560,10 +1232,51 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 21, "id": "42", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "TextClassificationModel(\n", + " (text_embedder): TextEmbedder(\n", + " (embedding_layer): Embedding(5000, 96, padding_idx=1)\n", + " (transformer): ModuleDict(\n", + " (h): ModuleList(\n", + " (0): Block(\n", + " (attn): SelfAttentionLayer(\n", + " (c_q): Linear(in_features=96, out_features=96, bias=False)\n", + " (c_k): Linear(in_features=96, out_features=96, bias=False)\n", + " (c_v): Linear(in_features=96, out_features=96, bias=False)\n", + " (c_proj): Linear(in_features=96, out_features=96, bias=False)\n", + " )\n", + " (mlp): MLP(\n", + " (c_fc): Linear(in_features=96, out_features=384, bias=False)\n", + " (c_proj): Linear(in_features=384, out_features=96, bias=False)\n", + " )\n", + " )\n", + " )\n", + " )\n", + " )\n", + " (categorical_variable_net): CategoricalVariableNet(\n", + " (categorical_embedding_0): Embedding(136, 25)\n", + " (categorical_embedding_1): Embedding(15, 25)\n", + " (categorical_embedding_2): Embedding(15, 25)\n", + " (categorical_embedding_3): Embedding(3, 25)\n", + " (categorical_embedding_4): Embedding(5, 25)\n", + " )\n", + " (classification_head): ClassificationHead(\n", + " (net): Linear(in_features=121, out_features=732, bias=True)\n", + " )\n", + ")" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "model = TextClassificationModel(\n", " text_embedder=text_embedder,\n", @@ -575,10 +1288,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 22, "id": "43", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "torch.Size([256, 732])" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Takes the same input as TextEmbedder + CategoricalVarNet -> same output as ClassificationHead (logits)\n", "model(input_ids=batch[\"input_ids\"], attention_mask=batch[\"attention_mask\"], categorical_vars=batch[\"categorical_vars\"]).shape" @@ -602,10 +1326,55 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 23, "id": "46", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "TextClassificationModule(\n", + " (model): TextClassificationModel(\n", + " (text_embedder): TextEmbedder(\n", + " (embedding_layer): Embedding(5000, 96, padding_idx=1)\n", + " (transformer): ModuleDict(\n", + " (h): ModuleList(\n", + " (0): Block(\n", + " (attn): SelfAttentionLayer(\n", + " (c_q): Linear(in_features=96, out_features=96, bias=False)\n", + " (c_k): Linear(in_features=96, out_features=96, bias=False)\n", + " (c_v): Linear(in_features=96, out_features=96, bias=False)\n", + " (c_proj): Linear(in_features=96, out_features=96, bias=False)\n", + " )\n", + " (mlp): MLP(\n", + " (c_fc): Linear(in_features=96, out_features=384, bias=False)\n", + " (c_proj): Linear(in_features=384, out_features=96, bias=False)\n", + " )\n", + " )\n", + " )\n", + " )\n", + " )\n", + " (categorical_variable_net): CategoricalVariableNet(\n", + " (categorical_embedding_0): Embedding(136, 25)\n", + " (categorical_embedding_1): Embedding(15, 25)\n", + " (categorical_embedding_2): Embedding(15, 25)\n", + " (categorical_embedding_3): Embedding(3, 25)\n", + " (categorical_embedding_4): Embedding(5, 25)\n", + " )\n", + " (classification_head): ClassificationHead(\n", + " (net): Linear(in_features=121, out_features=732, bias=True)\n", + " )\n", + " )\n", + " (loss): CrossEntropyLoss()\n", + " (accuracy_fn): MulticlassAccuracy()\n", + ")" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "import torch\n", "\n", @@ -639,10 +1408,64 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 57, "id": "49", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "torchTextClassifiers(\n", + " tokenizer = WordPieceTokenizer \n", + " HuggingFace tokenizer: PreTrainedTokenizerFast(name_or_path='', vocab_size=5000, model_max_length=1000000000000000019884624838656, is_fast=True, padding_side='right', truncation_side='right', special_tokens={'pad_token': '[PAD]'}, clean_up_tokenization_spaces=False, added_tokens_decoder={\n", + "\t0: AddedToken(\"[UNK]\", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),\n", + "\t1: AddedToken(\"[PAD]\", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),\n", + "\t2: AddedToken(\"[CLS]\", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),\n", + "\t3: AddedToken(\"[SEP]\", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),\n", + "}\n", + "),\n", + " model = TextClassificationModel(\n", + " (text_embedder): TextEmbedder(\n", + " (embedding_layer): Embedding(5000, 96, padding_idx=1)\n", + " (transformer): ModuleDict(\n", + " (h): ModuleList(\n", + " (0): Block(\n", + " (attn): SelfAttentionLayer(\n", + " (c_q): Linear(in_features=96, out_features=96, bias=False)\n", + " (c_k): Linear(in_features=96, out_features=96, bias=False)\n", + " (c_v): Linear(in_features=96, out_features=96, bias=False)\n", + " (c_proj): Linear(in_features=96, out_features=96, bias=False)\n", + " )\n", + " (mlp): MLP(\n", + " (c_fc): Linear(in_features=96, out_features=384, bias=False)\n", + " (c_proj): Linear(in_features=384, out_features=96, bias=False)\n", + " )\n", + " )\n", + " )\n", + " )\n", + " )\n", + " (categorical_variable_net): CategoricalVariableNet(\n", + " (categorical_embedding_0): Embedding(136, 25)\n", + " (categorical_embedding_1): Embedding(15, 25)\n", + " (categorical_embedding_2): Embedding(15, 25)\n", + " (categorical_embedding_3): Embedding(3, 25)\n", + " (categorical_embedding_4): Embedding(5, 25)\n", + " )\n", + " (classification_head): ClassificationHead(\n", + " (net): Linear(in_features=121, out_features=732, bias=True)\n", + " )\n", + "),\n", + " categorical_forward_type = AVERAGE_AND_CONCAT,\n", + " num_classes = 732,\n", + " embedding_dim = 96,\n", + ")" + ] + }, + "execution_count": 57, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "### Two main config objects, that mirror the parameters used above - and you're good to go !\n", "\n", @@ -657,7 +1480,7 @@ "training_config = TrainingConfig(\n", " lr=1e-3,\n", " batch_size=256,\n", - " num_epochs=10,\n", + " num_epochs=2,\n", ")\n", "\n", "ttc = torchTextClassifiers(\n", @@ -692,10 +1515,98 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 58, "id": "52", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "GPU available: True (cuda), used: True\n", + "TPU available: False, using: 0 TPU cores\n", + "HPU available: False, using: 0 HPUs\n", + "/home/onyxia/work/torchTextClassifiers/.venv/lib/python3.13/site-packages/pytorch_lightning/trainer/connectors/logger_connector/logger_connector.py:76: Starting from v1.9.0, `tensorboardX` has been removed as a dependency of the `pytorch_lightning` package, due to potential conflicts with other packages in the ML ecosystem. For this reason, `logger=True` will use `CSVLogger` as the default logger, unless the `tensorboard` or `tensorboardX` packages are found. Please `pip install lightning[extra]` or one of them to enable TensorBoard support by default\n", + "LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]\n", + "\n", + " | Name | Type | Params | Mode \n", + "----------------------------------------------------------------\n", + "0 | model | TextClassificationModel | 684 K | train\n", + "1 | loss | CrossEntropyLoss | 0 | train\n", + "2 | accuracy_fn | MulticlassAccuracy | 0 | train\n", + "----------------------------------------------------------------\n", + "684 K Trainable params\n", + "0 Non-trainable params\n", + "684 K Total params\n", + "2.737 Total estimated model params size (MB)\n", + "24 Modules in train mode\n", + "0 Modules in eval mode\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "74a9facf92bf4a88b92b01f2845d53af", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Sanity Checking: | | 0/? [00:00" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "all_plots = plot_attributions_at_char(\n", " text=text,\n", @@ -780,13 +1754,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 110, "id": "59", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "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2NhYuLi4iNDQ0BzHGzx4sKhbt64wMDAQGhoaokaNGuLHH3+UlnrPLmtZ+txuWf8nWa/J/G6zZ8/O97kSERUFmRB5XNqbiIiIiIiIOMeJiIiIiIgoP0yciIiIiIiI8sHEiYiIiIiIKB9MnIiIiIiIiPLBxImIiIiIiCgfTJyIiIiIiIjy8dldAFehUODx48cwMDCATCZTdThERERERKQiQggkJyejUqVKUFPLu0/ps0ucHj9+DEtLS1WHQUREREREJURsbCwsLCzyrPPZJU4GBgYA3jaOoaGhiqMhIiIiIiJVSUpKgqWlpZQj5KVEJE4rV67EwoULERcXhwYNGmD58uVwcHDId7+goCD06tULXbp0QXBwcIEeK2t4nqGhIRMnIiIiIiIq0BQelS8OsX37dnh4eGD27NmIjIxEgwYN0KZNGyQkJOS53/379zFx4kS0aNHiI0VKRERERESfK5UnTkuWLMGQIUMwYMAA1K1bF2vWrIGuri78/f3fuY9cLkefPn0wZ84cVK9e/SNGS0REREREnyOVJk7p6emIiIiAs7OzVKampgZnZ2ecP3/+nfvNnTsXpqamGDRoUL6PkZaWhqSkJKUbERERERFRYag0cXr27BnkcjnMzMyUys3MzBAXF5frPmfOnMH69evh5+dXoMfw8vKCkZGRdOOKekREREREVFglYnGIgkpOTka/fv3g5+cHExOTAu0zdepUeHh4SPezVs4gotLPasoBVYfwUdz37qDqEIiIiEo9lSZOJiYmUFdXR3x8vFJ5fHw8zM3Nc9SPiorC/fv30alTJ6lMoVAAAMqUKYO///4b1tbWSvtoaWlBS0urGKInIiIiIqLPhUqH6mlqasLOzg6hoaFSmUKhQGhoKJo2bZqjfp06dfDXX3/hypUr0q1z585o1aoVrly5wp4kIiIiIiIqFiofqufh4QE3NzfY29vDwcEBvr6+SElJwYABAwAA/fv3R+XKleHl5QVtbW3Ur19faX9jY2MAyFFORERERERUVFSeOLm6uuLp06eYNWsW4uLiYGtri8OHD0sLRsTExEBNTeWrphMRERER0WdMJoQQqg7iY0pKSoKRkRESExNhaGio6nCIqBhxcQgiIiLKS2FyA3blEBERERER5YOJExERERERUT6YOBEREREREeWDiRMREREREVE+mDgRERERERHlg4kTERERERFRPpg4ERERERER5YOJExERERERUT6YOBEREREREeWDiRMREREREVE+mDgRERERERHlg4kTERERERFRPpg4ERERERER5YOJExERERERUT6YOBEREREREeWDiRMREREREVE+mDgRERERERHlg4kTERERERFRPpg4ERERERER5YOJExERERERUT6YOBEREREREeWDiRMREREREVE+mDgRERERERHlg4kTERERERFRPpg4ERERERER5YOJExERERERUT4KnTjFxsbi4cOH0v2LFy9i3LhxWLdu3XsHsXLlSlhZWUFbWxuNGzfGxYsX31l39+7dsLe3h7GxMfT09GBra4vAwMD3fmwiIiIiIqL8FDpx6t27N06cOAEAiIuLwzfffIOLFy9i+vTpmDt3bqED2L59Ozw8PDB79mxERkaiQYMGaNOmDRISEnKtX65cOUyfPh3nz5/H1atXMWDAAAwYMABHjhwp9GMTEREREREVRKETp2vXrsHBwQEAsGPHDtSvXx/nzp3Dli1bsGHDhkIHsGTJEgwZMgQDBgxA3bp1sWbNGujq6sLf3z/X+k5OTvj222/xxRdfwNraGmPHjoWNjQ3OnDlT6McmIiIiIiIqiEInThkZGdDS0gIA/P777+jcuTMAoE6dOnjy5EmhjpWeno6IiAg4Ozv/G5CaGpydnXH+/Pl89xdCIDQ0FH///TdatmyZa520tDQkJSUp3YiIiIiIiAqj0IlTvXr1sGbNGpw+fRrHjh1D27ZtAQCPHz9G+fLlC3WsZ8+eQS6Xw8zMTKnczMwMcXFx79wvMTER+vr60NTURIcOHbB8+XJ88803udb18vKCkZGRdLO0tCxUjERERERERIVOnHx8fLB27Vo4OTmhV69eaNCgAQAgJCREGsJX3AwMDHDlyhWEh4dj/vz58PDwQFhYWK51p06disTEROkWGxv7UWIkIiIiIqLSo0xhd3BycsKzZ8+QlJSEsmXLSuVDhw6Frq5uoY5lYmICdXV1xMfHK5XHx8fD3Nz8nfupqamhRo0aAABbW1vcvHkTXl5ecHJyylFXS0tLGlpIRERERET0Pt7rOk7q6upKSRMAWFlZwdTUtFDH0dTUhJ2dHUJDQ6UyhUKB0NBQNG3atMDHUSgUSEtLK9RjExERERERFVShe5zi4+MxceJEhIaGIiEhAUIIpe1yubxQx/Pw8ICbmxvs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fPiwtGBETEwMnjx5ItVfvXo10tPT0b17d1SsWFG6LVq0qNCPTUREREREVBCF7nHq3Lkz9uzZg0mTJimV7927Fx07dnyvIEaPHq3Ug5VdWFiY0v379++/12MQERERERG9rwIlTr/88ov0d926dTF//nyEhYVJ85D++OMPnD17FhMmTCieKImIiIiIiFSoQInT0qVLle6XLVsWN27cwI0bN6QyY2Nj+Pv7Y8aMGUUbIRERERERkYoVKHH670VviYiIiIiIPifvdR0nIiIiIiKiz0mBepw8PDwwb9486OnpwcPDI8+6S5YsKZLAiIiIiIiISooCJU6XL19GRkYGACAyMhIymSzXeu8qJyIiIiIi+pQVKHE6ceKE9Pd/lwcnIiIiIiIq7Qo1xykjIwNlypTBtWvXiiseIiIiIiKiEqdQiZOGhgaqVKkCuVxeXPEQERERERGVOIVeVW/69OmYNm0anj9/XhzxEBERERERlTgFmuOU3YoVK3D37l1UqlQJVatWhZ6entL2yMjIIguOiIiIiIioJCh04tSlSxeunkdERERERJ+VQidOnp6exRAGERERERFRyVXoOU7Vq1fHP//8k6P85cuXqF69epEERUREREREVJIUOnG6f/9+rqvqpaWl4eHDh0USFBERERERUUlS4KF6ISEh0t9HjhyBkZGRdF8ulyM0NBTVqlUr2uiIiIiIiIhKgAInTl27dpX+dnNzU9qmoaEBKysrLF68uMgCIyIiIiIiKikKnDgpFAoAQLVq1RAeHg4TE5NiC4qIiIiIiKgkKfQcpzlz5sDAwCBHeXp6OjZt2lQkQREREREREZUkhV6OfMCAAWjbti1MTU2VypOTkzFgwAD079+/yIIjorxZTTmg6hA+ivveHVQdAhEREX3mCt3jJITI9QK4Dx8+VFowgoiIiIiIqLQocI9Tw4YNIZPJIJPJ8PXXX6NMmX93lcvliI6ORtu2bYslSCIiIiIiIlUq9Kp6V65cQZs2baCvry9t09TUhJWVFerXr1/kARIREREREalagROn2bNnAwCsrKzg6uoKbW1tAG/nNm3btg1Lly5FRERErhfHJSIiIiIi+pQVeo6Tm5sbtLW1cerUKbi5uaFixYpYtGgRWrdujT/++KM4YiQiIiIiIlKpQq2qFxcXhw0bNmD9+vVISkpCz549kZaWhuDgYNStW7e4YiQi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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "all_plots = plot_attributions_at_word(\n", " text=text,\n", + " words=words.values(),\n", " attributions_per_word=word_attributions,\n", " titles = list(map(lambda x: f\"Attributions for code {x}\", encoder.inverse_transform(np.array([predictions]).reshape(-1)).tolist())),\n", ")\n", diff --git a/torchTextClassifiers/utilities/plot_explainability.py b/torchTextClassifiers/utilities/plot_explainability.py index a5ad7f8..80b3042 100644 --- a/torchTextClassifiers/utilities/plot_explainability.py +++ b/torchTextClassifiers/utilities/plot_explainability.py @@ -53,8 +53,18 @@ def map_attributions_to_char(attributions, offsets, text): np.exp(attributions_per_char), axis=1, keepdims=True ) # softmax normalization +def get_id_to_word(text, word_ids, offsets): + words = {} + for idx, word_id in enumerate(word_ids): + if word_id is None: + continue + start, end = offsets[idx] + words[int(word_id)] = text[start:end] + + return words + -def map_attributions_to_word(attributions, word_ids): +def map_attributions_to_word(attributions, text, word_ids, offsets): """ Maps token-level attributions to word-level attributions based on word IDs. Args: @@ -69,8 +79,9 @@ def map_attributions_to_word(attributions, word_ids): np.ndarray: Array of shape (top_k, num_words) containing word-level attributions. num_words is the number of unique words in the original text. """ - + word_ids = np.array(word_ids) + words = get_id_to_word(text, word_ids, offsets) # Convert None to -1 for easier processing (PAD tokens) word_ids_int = np.array([x if x is not None else -1 for x in word_ids], dtype=int) @@ -99,7 +110,7 @@ def map_attributions_to_word(attributions, word_ids): ) # zero-out non-matching tokens and sum attributions for all tokens belonging to the same word # assert word_attributions.sum(axis=1) == attributions.sum(axis=1), "Sum of word attributions per top_k must equal sum of token attributions per top_k." - return np.exp(word_attributions) / np.sum( + return words, np.exp(word_attributions) / np.sum( np.exp(word_attributions), axis=1, keepdims=True ) # softmax normalization @@ -131,7 +142,7 @@ def plot_attributions_at_char( fig, ax = plt.subplots(figsize=figsize) ax.bar(range(len(text)), attributions_per_char[i]) ax.set_xticks(np.arange(len(text))) - ax.set_xticklabels(list(text), rotation=90) + ax.set_xticklabels(list(text), rotation=45) title = titles[i] if titles is not None else f"Attributions for Top {i+1} Prediction" ax.set_title(title) ax.set_xlabel("Characters in Text") @@ -142,7 +153,7 @@ def plot_attributions_at_char( def plot_attributions_at_word( - text, attributions_per_word, figsize=(10, 2), titles: Optional[List[str]] = None + text, words, attributions_per_word, figsize=(10, 2), titles: Optional[List[str]] = None ): """ Plots word-level attributions as a heatmap. @@ -159,14 +170,13 @@ def plot_attributions_at_word( "matplotlib is required for plotting. Please install it to use this function." ) - words = text.split() top_k = attributions_per_word.shape[0] all_plots = [] for i in range(top_k): fig, ax = plt.subplots(figsize=figsize) ax.bar(range(len(words)), attributions_per_word[i]) ax.set_xticks(np.arange(len(words))) - ax.set_xticklabels(words, rotation=90) + ax.set_xticklabels(words, rotation=45) title = titles[i] if titles is not None else f"Attributions for Top {i+1} Prediction" ax.set_title(title) ax.set_xlabel("Words in Text") From 3d8671448469659d24a3db976dd619d048047812 Mon Sep 17 00:00:00 2001 From: meilame-tayebjee Date: Thu, 27 Nov 2025 10:17:10 +0000 Subject: [PATCH 2/8] doc: quarto deployment of notebooks --- .github/workflows/deploy-notebooks.yml | 34 ++++++++++++++++++++++++++ .gitignore | 5 ++++ notebooks/_quarto.yml | 5 ++++ 3 files changed, 44 insertions(+) create mode 100644 .github/workflows/deploy-notebooks.yml create mode 100644 notebooks/_quarto.yml diff --git a/.github/workflows/deploy-notebooks.yml b/.github/workflows/deploy-notebooks.yml new file mode 100644 index 0000000..dc7ee11 --- /dev/null +++ b/.github/workflows/deploy-notebooks.yml @@ -0,0 +1,34 @@ +name: Publish Quarto Notebooks + +on: + push: + branches: + - main + - notebook_output + +jobs: + build-deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Needed to push to gh-pages + + - name: Setup Quarto + uses: quarto-dev/quarto-actions/setup@v2 + + - name: Render notebooks site + run: | + cd notebooks + quarto render + + - name: Deploy Quarto notebooks to gh-pages/notebooks + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_branch: gh-pages + publish_dir: notebooks/_site + destination_dir: notebooks + keep_files: true # <-- keeps existing Sphinx docs untouched diff --git a/.gitignore b/.gitignore index 5853749..e65366e 100644 --- a/.gitignore +++ b/.gitignore @@ -178,3 +178,8 @@ poetry.lock .vscode/ benchmark_results/ + +example_files/ +_site/ +.quarto/ +**/*.quarto_ipynb diff --git a/notebooks/_quarto.yml b/notebooks/_quarto.yml new file mode 100644 index 0000000..3e4b034 --- /dev/null +++ b/notebooks/_quarto.yml @@ -0,0 +1,5 @@ +project: + type: website + +website: + title: "Example notebooks" From 72929a2e8d5a7b7a11503778c6e21e23f0fe6a7a Mon Sep 17 00:00:00 2001 From: meilame-tayebjee Date: Thu, 27 Nov 2025 10:20:06 +0000 Subject: [PATCH 3/8] fix: permissions for GHA --- .github/workflows/deploy-docs.yml | 1 + .github/workflows/deploy-notebooks.yml | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 34a0560..130ff9a 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -5,6 +5,7 @@ on: branches: - main - docs-website + - notebook_output pull_request: branches: - main diff --git a/.github/workflows/deploy-notebooks.yml b/.github/workflows/deploy-notebooks.yml index dc7ee11..477fe76 100644 --- a/.github/workflows/deploy-notebooks.yml +++ b/.github/workflows/deploy-notebooks.yml @@ -6,6 +6,12 @@ on: - main - notebook_output +# Sets permissions for GitHub Pages deployment +permissions: + contents: read + pages: write + id-token: write + jobs: build-deploy: runs-on: ubuntu-latest From 781958a958eb56935b97872c56d80628a61a9e0f Mon Sep 17 00:00:00 2001 From: meilame-tayebjee Date: Thu, 27 Nov 2025 10:28:55 +0000 Subject: [PATCH 4/8] docs: notebooks in initial workflow + fix version --- .github/workflows/deploy-docs.yml | 32 ++++++++++++++------- .github/workflows/deploy-notebooks.yml | 40 -------------------------- 2 files changed, 22 insertions(+), 50 deletions(-) delete mode 100644 .github/workflows/deploy-notebooks.yml diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 130ff9a..a07b8e2 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -1,23 +1,20 @@ -name: Deploy Documentation +name: Deploy Documentation + Notebooks on: push: branches: - main - docs-website - - notebook_output pull_request: branches: - main workflow_dispatch: -# Sets permissions for GitHub Pages deployment permissions: contents: read pages: write id-token: write -# Allow one concurrent deployment concurrency: group: "pages" cancel-in-progress: true @@ -28,15 +25,15 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v5 with: - python-version: '3.14' + python-version: '3.12' - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v3 - name: Install dependencies run: | @@ -47,14 +44,29 @@ jobs: sudo apt-get update sudo apt-get install -y pandoc - - name: Build documentation + - name: Build Sphinx documentation run: | uv run sphinx-build -b html docs/source build/html + - name: Setup Quarto + uses: quarto-dev/quarto-actions/setup@v2 + + - name: Render Quarto notebooks + run: | + cd notebooks + quarto render + + - name: Combine Sphinx + Notebooks + run: | + mkdir -p final_site + cp -r build/html/* final_site/ + mkdir -p final_site/notebooks + cp -r notebooks/_site/* final_site/notebooks/ + - name: Upload artifact uses: actions/upload-pages-artifact@v4 with: - path: build/html + path: final_site deploy: needs: build diff --git a/.github/workflows/deploy-notebooks.yml b/.github/workflows/deploy-notebooks.yml deleted file mode 100644 index 477fe76..0000000 --- a/.github/workflows/deploy-notebooks.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Publish Quarto Notebooks - -on: - push: - branches: - - main - - notebook_output - -# Sets permissions for GitHub Pages deployment -permissions: - contents: read - pages: write - id-token: write - -jobs: - build-deploy: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Needed to push to gh-pages - - - name: Setup Quarto - uses: quarto-dev/quarto-actions/setup@v2 - - - name: Render notebooks site - run: | - cd notebooks - quarto render - - - name: Deploy Quarto notebooks to gh-pages/notebooks - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_branch: gh-pages - publish_dir: notebooks/_site - destination_dir: notebooks - keep_files: true # <-- keeps existing Sphinx docs untouched From ae5916983aa769f5d583866733684fe291b1b4d1 Mon Sep 17 00:00:00 2001 From: meilame-tayebjee Date: Thu, 27 Nov 2025 10:29:36 +0000 Subject: [PATCH 5/8] fix branch --- .github/workflows/deploy-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index a07b8e2..3f22d7b 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -4,7 +4,7 @@ on: push: branches: - main - - docs-website + - notebook_output pull_request: branches: - main From 2ec9b0d29f51bb02b74641ceeb3e3249f335c2af Mon Sep 17 00:00:00 2001 From: meilame-tayebjee Date: Thu, 27 Nov 2025 10:35:01 +0000 Subject: [PATCH 6/8] fix: ugly output for training --- notebooks/example.ipynb | 92 +---------------------------------------- 1 file changed, 2 insertions(+), 90 deletions(-) diff --git a/notebooks/example.ipynb b/notebooks/example.ipynb index 0b225d8..db36c32 100644 --- a/notebooks/example.ipynb +++ b/notebooks/example.ipynb @@ -1515,98 +1515,10 @@ }, { "cell_type": "code", - "execution_count": 58, + "execution_count": null, "id": "52", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "GPU available: True (cuda), used: True\n", - "TPU available: False, using: 0 TPU cores\n", - "HPU available: False, using: 0 HPUs\n", - "/home/onyxia/work/torchTextClassifiers/.venv/lib/python3.13/site-packages/pytorch_lightning/trainer/connectors/logger_connector/logger_connector.py:76: Starting from v1.9.0, `tensorboardX` has been removed as a dependency of the `pytorch_lightning` package, due to potential conflicts with other packages in the ML ecosystem. For this reason, `logger=True` will use `CSVLogger` as the default logger, unless the `tensorboard` or `tensorboardX` packages are found. Please `pip install lightning[extra]` or one of them to enable TensorBoard support by default\n", - "LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]\n", - "\n", - " | Name | Type | Params | Mode \n", - "----------------------------------------------------------------\n", - "0 | model | TextClassificationModel | 684 K | train\n", - "1 | loss | CrossEntropyLoss | 0 | train\n", - "2 | accuracy_fn | MulticlassAccuracy | 0 | train\n", - "----------------------------------------------------------------\n", - "684 K Trainable params\n", - "0 Non-trainable params\n", - "684 K Total params\n", - "2.737 Total estimated model params size (MB)\n", - "24 Modules in train mode\n", - "0 Modules in eval mode\n" - ] - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "74a9facf92bf4a88b92b01f2845d53af", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Sanity Checking: | | 0/? [00:00 Date: Thu, 27 Nov 2025 10:35:16 +0000 Subject: [PATCH 7/8] fix branch (only main) --- .github/workflows/deploy-docs.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 3f22d7b..4137335 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -4,7 +4,6 @@ on: push: branches: - main - - notebook_output pull_request: branches: - main From 5dad316a1f6a9a8b306e7177917698ad0222498f Mon Sep 17 00:00:00 2001 From: meilame-tayebjee Date: Thu, 27 Nov 2025 10:35:31 +0000 Subject: [PATCH 8/8] fix(tests): adapt to new version of word expl --- tests/test_pipeline.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_pipeline.py b/tests/test_pipeline.py index b272acc..27da44b 100644 --- a/tests/test_pipeline.py +++ b/tests/test_pipeline.py @@ -171,13 +171,17 @@ def run_full_pipeline(tokenizer, sample_text_data, categorical_data, labels, mod attributions = predictions["attributions"][text_idx] word_ids = predictions["word_ids"][text_idx] - word_attributions = map_attributions_to_word(attributions, word_ids) + words, word_attributions = map_attributions_to_word(attributions, text, word_ids, offsets) char_attributions = map_attributions_to_char(attributions, offsets, text) # Note: We're not actually plotting in tests, just calling the functions # to ensure they don't raise errors plot_attributions_at_char(text, char_attributions) - plot_attributions_at_word(text, word_attributions) + plot_attributions_at_word( + text=text, + words=words.values(), + attributions_per_word=word_attributions, + ) def test_wordpiece_tokenizer(sample_data, model_params):