RamezCh commited on
Commit
494c583
·
verified ·
1 Parent(s): 127b6bf

Upload folder using huggingface_hub

Browse files
sproto/dataset/outcome.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, List
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+ import torch
6
+ from pandas import DataFrame
7
+ from sklearn.preprocessing import MultiLabelBinarizer
8
+ from torch.utils.data import Dataset
9
+ from transformers import PreTrainedTokenizer
10
+ import ast
11
+ import pickle
12
+
13
+
14
+ def collate_batch(featurized_samples: List[Dict]):
15
+ input_ids = torch.nn.utils.rnn.pad_sequence(
16
+ [torch.tensor(x["input_ids"]) for x in featurized_samples], batch_first=True
17
+ )
18
+
19
+ attention_masks = torch.nn.utils.rnn.pad_sequence(
20
+ [torch.tensor(x["attention_mask"]) for x in featurized_samples], batch_first=True
21
+ )
22
+
23
+ batch = {
24
+ "input_ids": input_ids,
25
+ "attention_masks": attention_masks,
26
+ "tokens": [x["tokens"] for x in featurized_samples],
27
+ "targets": np.array([x["target"] for x in featurized_samples]),
28
+ "sample_ids": [x["sample_id"] for x in featurized_samples],
29
+ }
30
+
31
+ if "token_type_ids" in featurized_samples[0]:
32
+ token_type_ids = torch.nn.utils.rnn.pad_sequence(
33
+ [torch.tensor(x["token_type_ids"]) for x in featurized_samples], batch_first=True
34
+ )
35
+ batch["token_type_ids"] = token_type_ids
36
+
37
+ return batch
38
+
39
+
40
+ def sample_to_features_multilabel(
41
+ sample: pd.Series,
42
+ tokenizer: PreTrainedTokenizer,
43
+ labels: List[str],
44
+ max_length=512,
45
+ text_column="text",
46
+ ) -> Dict:
47
+ tokenized = tokenizer.encode_plus(
48
+ sample[text_column],
49
+ padding='max_length',
50
+ truncation=True,
51
+ pad_to_multiple_of=512 if max_length > 512 else None,
52
+ max_length=max_length,
53
+ )
54
+
55
+ featurized_sample = {
56
+ "input_ids": tokenized["input_ids"],
57
+ "attention_mask": tokenized["attention_mask"],
58
+ "tokens": tokenized.encodings[0].tokens,
59
+ "target": sample[labels].to_numpy().astype(int),
60
+ "sample_id": sample["hadm_id"],
61
+ }
62
+
63
+ if "token_type_ids" in tokenized:
64
+ featurized_sample["token_type_ids"] = tokenized["token_type_ids"]
65
+
66
+ return featurized_sample
67
+
68
+
69
+ class OutcomeDiagnosesDataset(Dataset):
70
+ def __init__(
71
+ self,
72
+ file_path,
73
+ tokenizer: PreTrainedTokenizer,
74
+ all_codes_path,
75
+ max_length=512,
76
+ text_column="text",
77
+ label_column="main_ccsr_code_encoded",
78
+ data=None
79
+ ):
80
+ self.data: DataFrame
81
+ self.tokenizer: PreTrainedTokenizer = tokenizer
82
+ self.max_length = max_length
83
+ self.text_column = text_column
84
+
85
+ if data is None:
86
+ if not isinstance(file_path, list):
87
+ self.data = pd.read_csv(
88
+ file_path, dtype={"hadm_id": str})
89
+ #self.data = self.data.iloc[:20, :]
90
+ else:
91
+ for i, fpath in enumerate(file_path):
92
+ if not i:
93
+ self.data = pd.read_csv(
94
+ fpath, dtype={"hadm_id": str})
95
+ else:
96
+ self.data = pd.concat([self.data, pd.read_csv(
97
+ fpath, dtype={"hadm_id": str})]).reset_index(drop=True)
98
+
99
+ else:
100
+ self.data = data
101
+
102
+ # binarize labels
103
+ lb = MultiLabelBinarizer()
104
+ with open(all_codes_path, 'rb') as all_codes_file:
105
+ all_codes = pickle.load(all_codes_file)
106
+
107
+ lb.fit(np.array([all_codes]).reshape(-1, 1))
108
+ binary_labels_set = lb.transform(
109
+ self.data[label_column].apply(ast.literal_eval))
110
+ self.labels = lb.classes_
111
+ binary_labels_df = pd.DataFrame(binary_labels_set, columns=self.labels)
112
+ self.data = pd.concat([self.data, binary_labels_df], axis=1)
113
+ # self.data[self.labels] = binary_labels_set
114
+
115
+ def __len__(self):
116
+ return len(self.data)
117
+
118
+ def __getitem__(self, index) -> Dict:
119
+ featurized_sample = sample_to_features_multilabel(
120
+ sample=self.data.iloc[index],
121
+ tokenizer=self.tokenizer,
122
+ labels=self.labels,
123
+ max_length=self.max_length,
124
+ text_column=self.text_column,
125
+ )
126
+ return featurized_sample
127
+
128
+ def get_num_classes(self):
129
+ return len(self.labels)
sproto/metrics/metrics.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from typing import Any, Callable, List, Literal, Optional
3
+
4
+ import numpy as np
5
+ import torch
6
+ import torchmetrics
7
+ from torchmetrics.classification.auroc import AUROC
8
+ from torchmetrics.classification.precision_recall_curve import PrecisionRecallCurve
9
+ from torchmetrics.functional.classification.auroc import _auroc_compute
10
+ from torchmetrics.metric import Metric
11
+ from torchmetrics.utilities.data import dim_zero_cat
12
+
13
+
14
+ class PR_AUC(Metric):
15
+ prauc: torch.Tensor
16
+
17
+ def __init__(self, num_classes, compute_on_step=False, dist_sync_on_step=False):
18
+ super().__init__(compute_on_step=compute_on_step,
19
+ dist_sync_on_step=dist_sync_on_step)
20
+ self.add_state("prauc", default=[], dist_reduce_fx="cat")
21
+ self.pr_curve = PrecisionRecallCurve(
22
+ num_classes=num_classes).to(self.device)
23
+ self.auc = torchmetrics.AUC().to(self.device)
24
+
25
+ def update(self, prediction: torch.Tensor, target: torch.Tensor):
26
+ precision, recall, thresholds = self.pr_curve(prediction, target)
27
+ auc_values = [self.auc(r, p) for r, p in zip(recall, precision)]
28
+
29
+ pr_auc = torch.mean(torch.tensor(
30
+ [v for v in auc_values if not v.isnan()])).to(self.device)
31
+ self.prauc += [pr_auc.detach()]
32
+
33
+ def compute(self):
34
+ self.prauc = torch.as_tensor(self.prauc)
35
+ return torch.mean(self.prauc)
36
+
37
+
38
+ class PR_AUCPerBucket(PR_AUC):
39
+ def __init__(self, num_classes, bucket, compute_on_step=False, dist_sync_on_step=False):
40
+ super().__init__(
41
+ num_classes=len(bucket),
42
+ compute_on_step=compute_on_step,
43
+ dist_sync_on_step=dist_sync_on_step,
44
+ )
45
+ self.bucket = set(bucket)
46
+ self.num_classes = num_classes
47
+
48
+ def update(self, prediction: torch.Tensor, target: torch.Tensor):
49
+
50
+ mask = np.zeros((self.num_classes), dtype=bool)
51
+ for c in range(self.num_classes):
52
+ if c in self.bucket:
53
+ mask[c] = True
54
+ filtered_target = target[:, mask]
55
+ filtered_preds = prediction[:, mask]
56
+
57
+ if len((filtered_target > 0).nonzero()) > 0:
58
+ precision, recall, thresholds = self.pr_curve(
59
+ filtered_preds, filtered_target)
60
+ auc_values = [self.auc(r, p) for r, p in zip(recall, precision)]
61
+
62
+ pr_auc = torch.mean(torch.tensor([v for v in auc_values if not v.isnan()])).to(
63
+ self.device
64
+ )
65
+ self.prauc += [pr_auc.detach()]
66
+
67
+
68
+ def calculate_pr_auc(prediction: torch.Tensor, target: torch.Tensor, num_classes, device):
69
+ pr_curve = PrecisionRecallCurve(num_classes=num_classes).to(device)
70
+ auc = torchmetrics.AUC().to(device)
71
+
72
+ precision, recall, thresholds = pr_curve(prediction, target)
73
+ auc_values = [auc(r, p) for r, p in zip(recall, precision)]
74
+
75
+ pr_auc = torch.mean(torch.tensor(
76
+ [v for v in auc_values if not v.isnan()])).to(device)
77
+ return pr_auc.detach()
78
+
79
+
80
+ class FilteredAUROC(AUROC):
81
+ num_classes: int
82
+
83
+ def compute(self) -> torch.Tensor:
84
+
85
+ preds = dim_zero_cat(self.preds)
86
+ target = dim_zero_cat(self.target)
87
+
88
+ # mask = np.ones((self.num_classes), dtype=bool)
89
+ # breakpoint()
90
+ # for c in range(self.num_classes):
91
+ # if torch.max(target[:, c]) == 0:
92
+ # mask[c] = False
93
+ mask = ((target.sum(axis=0) > 0) +
94
+ (target.sum(axis=0) == len(target))).cpu().numpy()
95
+ filtered_target = target[:, mask]
96
+ filtered_preds = preds[:, mask]
97
+
98
+ num_filtered_cols = np.count_nonzero(mask == False) # noqa
99
+ logging.info(
100
+ f"{num_filtered_cols} columns not considered for ROC AUC calculation!")
101
+
102
+ return _auroc_compute(
103
+ filtered_preds,
104
+ filtered_target,
105
+ self.mode,
106
+ self.num_classes - num_filtered_cols,
107
+ self.pos_label,
108
+ self.average,
109
+ self.max_fpr,
110
+ )
111
+
112
+
113
+ class FilteredAUROCPerBucket(AUROC):
114
+ num_classes: int
115
+
116
+ def __init__(
117
+ self,
118
+ bucket: List[int],
119
+ num_classes: Optional[int] = None,
120
+ pos_label: Optional[int] = None,
121
+ average: Optional[Literal["macro", "weighted", "none"]] = "macro",
122
+ max_fpr: Optional[float] = None,
123
+ compute_on_step: bool = True,
124
+ dist_sync_on_step: bool = False,
125
+ process_group: Optional[Any] = None,
126
+ dist_sync_fn: Optional[Callable] = None,
127
+ ):
128
+ super().__init__(
129
+ num_classes,
130
+ pos_label,
131
+ average,
132
+ max_fpr,
133
+ compute_on_step, # type: ignore
134
+ dist_sync_on_step,
135
+ process_group,
136
+ dist_sync_fn, # type: ignore
137
+ )
138
+ self.bucket = set(bucket)
139
+
140
+ def compute(self) -> torch.Tensor:
141
+
142
+ preds = dim_zero_cat(self.preds)
143
+ target = dim_zero_cat(self.target)
144
+
145
+ mask = np.zeros((self.num_classes), dtype=bool)
146
+ for c in range(self.num_classes):
147
+ if torch.max(target[:, c]) > 0 and c in self.bucket:
148
+ mask[c] = True
149
+ filtered_target = target[:, mask]
150
+ filtered_preds = preds[:, mask]
151
+
152
+ num_filtered_cols = np.count_nonzero(mask == False) # noqa
153
+ logging.info(
154
+ f"{num_filtered_cols} columns not considered for ROC AUC calculation!")
155
+
156
+ return _auroc_compute(
157
+ filtered_preds,
158
+ filtered_target,
159
+ self.mode,
160
+ self.num_classes - num_filtered_cols,
161
+ self.pos_label,
162
+ self.average,
163
+ self.max_fpr,
164
+ )
sproto/model/multi_proto.py ADDED
@@ -0,0 +1,663 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import os
3
+
4
+ import numpy as np
5
+ import pytorch_lightning as pl
6
+ import torch
7
+ import torch.nn as nn
8
+ import torch.nn.functional as F
9
+ import torchmetrics
10
+ import transformers
11
+ from pytorch_lightning.loggers import TensorBoardLogger
12
+ from transformers import AutoModel
13
+
14
+ import sproto.utils.utils as utils
15
+ from sproto.metrics import metrics
16
+
17
+
18
+ logger = logging.getLogger()
19
+
20
+
21
+ class MultiProtoModule(pl.LightningModule):
22
+ logger: TensorBoardLogger
23
+
24
+ def __init__(
25
+ self,
26
+ pretrained_model,
27
+ num_classes,
28
+ label_order_path,
29
+ use_sigmoid=False,
30
+ use_cuda=True,
31
+ lr_prototypes=5e-2,
32
+ lr_features=2e-6,
33
+ lr_others=2e-2,
34
+ num_training_steps=5000,
35
+ num_warmup_steps=1000,
36
+ loss="BCE",
37
+ save_dir="output",
38
+ use_attention=True,
39
+ use_global_attention=False,
40
+ dot_product=False,
41
+ normalize=None,
42
+ final_layer=False,
43
+ reduce_hidden_size=None,
44
+ use_prototype_loss=False,
45
+ prototype_vector_path=None,
46
+ attention_vector_path=None,
47
+ eval_buckets=None,
48
+ seed=7,
49
+ num_prototypes_per_class=1,
50
+ batch_size=10,
51
+
52
+ ):
53
+ assert use_attention != use_global_attention, "use_attention and use_global attention cannot be active at the same time"
54
+ super().__init__()
55
+ self.label_order_path = label_order_path
56
+ self.batch_size = batch_size
57
+ self.loss = loss
58
+ self.normalize = normalize
59
+ self.lr_features = lr_features
60
+ self.lr_prototypes = lr_prototypes
61
+ self.lr_others = lr_others
62
+ self.use_sigmoid = use_sigmoid
63
+
64
+ self.use_attention = use_attention
65
+ self.use_global_attention = use_global_attention
66
+ self.dot_product = dot_product
67
+
68
+ self.num_training_steps = num_training_steps
69
+ self.num_warmup_steps = num_warmup_steps
70
+ self.save_dir = save_dir
71
+ self.num_classes = num_classes
72
+
73
+ self.final_layer = final_layer
74
+ self.use_prototype_loss = use_prototype_loss
75
+ self.prototype_vector_path = prototype_vector_path
76
+ self.eval_buckets = eval_buckets
77
+ self.num_prototypes_per_class_tmp = num_prototypes_per_class
78
+
79
+ # ARCHITECTURE SETUP #
80
+
81
+ pl.seed_everything(seed=seed)
82
+
83
+ # define distance measure
84
+ self.pairwise_dist = nn.PairwiseDistance(p=2)
85
+
86
+ # load BERT
87
+ self.bert = AutoModel.from_pretrained(pretrained_model)
88
+
89
+ # freeze BERT layers if lr_features == 0
90
+ if lr_features == 0:
91
+ for param in self.bert.parameters():
92
+ param.requires_grad = False
93
+
94
+ # define hidden size
95
+ self.hidden_size = self.bert.config.hidden_size
96
+ self.reduce_hidden_size = reduce_hidden_size is not None
97
+ if self.reduce_hidden_size:
98
+ self.reduce_hidden_size = True
99
+ self.bert_hidden_size = self.bert.config.hidden_size
100
+ self.hidden_size = reduce_hidden_size
101
+
102
+ # initialize linear layer for dim reduction
103
+ # reset the seed to make sure linear layer is the same as in preprocessing
104
+ pl.seed_everything(seed=seed)
105
+ self.linear = nn.Linear(self.bert_hidden_size, self.hidden_size)
106
+
107
+ # load prototype vectors
108
+ if prototype_vector_path is not None:
109
+ prototype_vectors, self.num_prototypes_per_class = self.load_prototype_vectors(
110
+ prototype_vector_path
111
+ )
112
+ elif self.num_prototypes_per_class_tmp > 1:
113
+ prototype_vectors = torch.rand(
114
+ (self.num_prototypes_per_class_tmp, self.num_classes, self.hidden_size))
115
+ num_prototypes_per_class = torch.ones(
116
+ self.num_classes) * self.num_prototypes_per_class_tmp
117
+ else:
118
+ prototype_vectors = torch.rand(
119
+ (self.num_classes, self.hidden_size))
120
+ num_prototypes_per_class = torch.ones(
121
+ self.num_classes)
122
+ self.prototype_vectors = nn.Parameter(
123
+ prototype_vectors, requires_grad=True)
124
+
125
+ self.register_buffer('num_prototypes_per_class', num_prototypes_per_class)
126
+
127
+ self.prototype_to_class_map = self.build_prototype_to_class_mapping(
128
+ self.num_prototypes_per_class
129
+ )
130
+ self.num_prototypes = self.prototype_to_class_map.shape[0]
131
+
132
+ # load attention vectors
133
+ if attention_vector_path is not None:
134
+ attention_vectors = self.load_attention_vectors(
135
+ attention_vector_path)
136
+ elif self.num_prototypes_per_class_tmp > 1:
137
+ attention_vectors = torch.rand(
138
+ (self.num_prototypes_per_class_tmp, self.num_classes, self.hidden_size))
139
+ else:
140
+ attention_vectors = torch.rand(
141
+ (self.num_classes, self.hidden_size))
142
+ self.attention_vectors = nn.Parameter(
143
+ attention_vectors, requires_grad=True)
144
+
145
+ if self.final_layer:
146
+ self.final_linear = self.build_final_layer()
147
+
148
+ # EVALUATION SETUP #
149
+
150
+ # setup metrics
151
+ self.train_metrics = self.setup_metrics()
152
+
153
+ # initialise metrics for evaluation on test set
154
+ self.all_metrics = self.train_metrics
155
+ # self.all_metrics = {**self.train_metrics,
156
+ # **self.setup_extensive_metrics()}
157
+
158
+ torch.proto_vectors = self.prototype_vectors.data.clone().detach()
159
+ torch.attention_vectors = self.attention_vectors.data.clone().detach()
160
+
161
+ self.save_hyperparameters()
162
+ logger.info("Finished init.")
163
+
164
+ def build_final_layer(self):
165
+ prototype_identity_matrix = torch.zeros(
166
+ self.num_prototypes, self.num_classes)
167
+
168
+ for j in range(len(prototype_identity_matrix)):
169
+ prototype_identity_matrix[j, self.prototype_to_class_map[j]] = (
170
+ 1.0 /
171
+ self.num_prototypes_per_class[self.prototype_to_class_map[j]]
172
+ )
173
+ return nn.Parameter(prototype_identity_matrix.double(), requires_grad=True)
174
+
175
+ def load_prototype_vectors(self, prototypes_per_class_path):
176
+ prototypes_per_class = torch.load(prototypes_per_class_path)
177
+
178
+ # store the number of prototypes for each class
179
+ num_prototypes_per_class = torch.tensor(
180
+ [len(prototypes_per_class[key]) for key in prototypes_per_class]
181
+ )
182
+
183
+ with open(self.label_order_path) as label_order_file:
184
+ ordered_labels = label_order_file.read().split("\t")
185
+
186
+ # get dimension from any of the stored vectors
187
+ vector_dim = len(list(prototypes_per_class.values())[0][0])
188
+
189
+ stacked_prototypes_per_class = [
190
+ prototypes_per_class[label]
191
+ if label in prototypes_per_class
192
+ else [np.random.rand(vector_dim)]
193
+ for label in ordered_labels
194
+ ]
195
+
196
+ prototype_matrix = torch.tensor(
197
+ [val for sublist in stacked_prototypes_per_class for val in sublist]
198
+ )
199
+
200
+ return prototype_matrix, num_prototypes_per_class
201
+
202
+ def build_prototype_to_class_mapping(self, num_prototypes_per_class):
203
+ return torch.arange(num_prototypes_per_class.shape[0], device=self.device).repeat_interleave(
204
+ num_prototypes_per_class.long(),
205
+ dim=0)
206
+
207
+ # torch.arange(num_prototypes_per_class.shape[0]).repeat_interleave(
208
+ # num_prototypes_per_class.long(), dim=0)
209
+
210
+ def load_attention_vectors(self, attention_vectors_path):
211
+ attention_vectors = torch.load(
212
+ attention_vectors_path, map_location=self.device)
213
+
214
+ return attention_vectors
215
+
216
+ def setup_metrics(self):
217
+
218
+ self.f1 = torchmetrics.F1Score(threshold=0.269)
219
+ self.auroc_micro = metrics.FilteredAUROC(
220
+ num_classes=self.num_classes, compute_on_step=False, average="micro"
221
+ )
222
+ self.auroc_macro = metrics.FilteredAUROC(
223
+ num_classes=self.num_classes, compute_on_step=False, average="macro"
224
+ )
225
+ self.average_precision = torchmetrics.classification.MultilabelAveragePrecision(
226
+ num_labels=self.num_classes, compute_on_step=False, average="macro"
227
+ )
228
+
229
+ return {"auroc_micro": self.auroc_micro,
230
+ "auroc_macro": self.auroc_macro,
231
+ "average_precision": self.average_precision,
232
+ }
233
+
234
+ def setup_extensive_metrics(self):
235
+ self.pr_curve = metrics.PR_AUC(num_classes=self.num_classes)
236
+
237
+ extensive_metrics = {"pr_curve": self.pr_curve}
238
+
239
+ if self.eval_buckets:
240
+ buckets = self.eval_buckets
241
+
242
+ self.prcurve_0 = metrics.PR_AUCPerBucket(
243
+ bucket=buckets["<5"], num_classes=self.num_classes, compute_on_step=False
244
+ )
245
+
246
+ self.prcurve_1 = metrics.PR_AUCPerBdeep
247
+
248
+ self.prcurve_2 = metrics.PR_AUCPerBucket(
249
+ bucket=buckets["11-50"], num_classes=self.num_classes, compute_on_step=False
250
+ )
251
+
252
+ self.prcurve_3 = metrics.PR_AUCPerBucket(
253
+ bucket=buckets["51-100"], num_classes=self.num_classes, compute_on_step=False
254
+ )
255
+
256
+ self.prcurve_4 = metrics.PR_AUCPerBucket(
257
+ bucket=buckets["101-1K"], num_classes=self.num_classes, compute_on_step=False
258
+ )
259
+
260
+ self.prcurve_5 = metrics.PR_AUCPerBucket(
261
+ bucket=buckets[">1K"], num_classes=self.num_classes, compute_on_step=False
262
+ )
263
+
264
+ self.auroc_macro_0 = metrics.FilteredAUROCPerBucket(
265
+ bucket=buckets["<5"],
266
+ num_classes=self.num_classes,
267
+ compute_on_step=False,
268
+ average="macro",
269
+ )
270
+ self.auroc_macro_1 = metrics.FilteredAUROCPerBucket(
271
+ bucket=buckets["5-10"],
272
+ num_classes=self.num_classes,
273
+ compute_on_step=False,
274
+ average="macro",
275
+ )
276
+ self.auroc_macro_2 = metrics.FilteredAUROCPerBucket(
277
+ bucket=buckets["11-50"],
278
+ num_classes=self.num_classes,
279
+ compute_on_step=False,
280
+ average="macro",
281
+ )
282
+ self.auroc_macro_3 = metrics.FilteredAUROCPerBucket(
283
+ bucket=buckets["51-100"],
284
+ num_classes=self.num_classes,
285
+ compute_on_step=False,
286
+ average="macro",
287
+ )
288
+ self.auroc_macro_4 = metrics.FilteredAUROCPerBucket(
289
+ bucket=buckets["101-1K"],
290
+ num_classes=self.num_classes,
291
+ compute_on_step=False,
292
+ average="macro",
293
+ )
294
+ self.auroc_macro_5 = metrics.FilteredAUROCPerBucket(
295
+ bucket=buckets[">1K"],
296
+ num_classes=self.num_classes,
297
+ compute_on_step=False,
298
+ average="macro",
299
+ )
300
+
301
+ bucket_metrics = {
302
+ "pr_curve_0": self.prcurve_0,
303
+ "pr_curve_1": self.prcurve_1,
304
+ "pr_curve_2": self.prcurve_2,
305
+ "pr_curve_3": self.prcurve_3,
306
+ "pr_curve_4": self.prcurve_4,
307
+ "pr_curve_5": self.prcurve_5,
308
+ "auroc_macro_0": self.auroc_macro_0,
309
+ "auroc_macro_1": self.auroc_macro_1,
310
+ "auroc_macro_2": self.auroc_macro_2,
311
+ "auroc_macro_3": self.auroc_macro_3,
312
+ "auroc_macro_4": self.auroc_macro_4,
313
+ "auroc_macro_5": self.auroc_macro_5,
314
+ }
315
+
316
+ extensive_metrics = {**extensive_metrics, **bucket_metrics}
317
+
318
+ return extensive_metrics
319
+
320
+ def configure_optimizers(self):
321
+ joint_optimizer_specs = [
322
+ {"params": self.prototype_vectors, "lr": self.lr_prototypes},
323
+ {"params": self.attention_vectors, "lr": self.lr_others},
324
+ {"params": self.bert.parameters(), "lr": self.lr_features},
325
+ ]
326
+
327
+ if self.final_layer:
328
+ joint_optimizer_specs.append(
329
+ {"params": self.final_linear, "lr": self.lr_prototypes})
330
+
331
+ if self.reduce_hidden_size:
332
+ joint_optimizer_specs.append(
333
+ {"params": self.linear.parameters(), "lr": self.lr_others})
334
+
335
+ optimizer = torch.optim.AdamW(joint_optimizer_specs)
336
+
337
+ lr_scheduler = transformers.get_linear_schedule_with_warmup(
338
+ optimizer=optimizer,
339
+ num_warmup_steps=self.num_warmup_steps,
340
+ num_training_steps=self.num_training_steps,
341
+ )
342
+
343
+ return [optimizer], [lr_scheduler]
344
+
345
+ def on_train_start(self):
346
+ self.logger.log_hyperparams(self.hparams)
347
+
348
+ def training_step(self, batch, batch_idx):
349
+ targets = torch.tensor(batch["targets"], device=self.device)
350
+
351
+ if self.use_prototype_loss:
352
+ if batch_idx == 0:
353
+ self.prototype_loss = self.calculate_prototype_loss()
354
+ self.log("prototype_loss", self.prototype_loss,
355
+ on_epoch=True, batch_size=self.batch_size)
356
+
357
+ logits, max_indices, _ = self(batch)
358
+
359
+ if self.loss == "BCE":
360
+ train_loss = torch.nn.functional.binary_cross_entropy_with_logits(
361
+ logits, target=targets.float()
362
+ )
363
+ else:
364
+ train_loss = torch.nn.MultiLabelSoftMarginLoss()(
365
+ input=torch.sigmoid(logits), target=targets
366
+ )
367
+
368
+ self.log("train_loss", train_loss, on_epoch=True,
369
+ batch_size=self.batch_size)
370
+
371
+ if self.use_prototype_loss:
372
+ total_loss = train_loss + self.prototype_loss
373
+ else:
374
+ total_loss = train_loss
375
+
376
+ return total_loss
377
+
378
+ # @profile
379
+ def forward(self, batch):
380
+ attention_mask = batch["attention_masks"]
381
+ input_ids = batch["input_ids"]
382
+ token_type_ids = batch["token_type_ids"]
383
+
384
+ if attention_mask.device != self.device:
385
+ attention_mask = attention_mask.to(self.device)
386
+ input_ids = input_ids.to(self.device)
387
+ token_type_ids = token_type_ids.to(self.device)
388
+
389
+ bert_output = self.bert(
390
+ input_ids=input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids
391
+ )
392
+
393
+ bert_vectors = bert_output.last_hidden_state
394
+
395
+ if self.reduce_hidden_size:
396
+ # apply linear layer to reduce token vector dimension
397
+ token_vectors = self.linear(bert_vectors)
398
+ else:
399
+ token_vectors = bert_vectors
400
+
401
+ if self.normalize is not None:
402
+ token_vectors = nn.functional.normalize(
403
+ token_vectors, p=2, dim=self.normalize)
404
+
405
+ if self.num_prototypes_per_class_tmp > 1:
406
+ with torch.no_grad():
407
+ logits, max_indices, metadata = self.compute_max_indices(
408
+ attention_mask, batch, token_vectors)
409
+
410
+ logits, max_indices, metadata = self.compute_max_indices(
411
+ attention_mask, batch, token_vectors, max_indices)
412
+ else:
413
+ logits, max_indices, metadata = self.compute_max_indices(
414
+ attention_mask, batch, token_vectors)
415
+
416
+ return logits, max_indices, metadata
417
+
418
+ # @profile
419
+ def compute_max_indices(self, attention_mask, batch, token_vectors, max_indices=None):
420
+ metadata = None
421
+ if max_indices is not None:
422
+ attention_vectors = torch.take_along_dim(
423
+ self.attention_vectors, max_indices.unsqueeze(-1), dim=0)
424
+ prototype_vectors = torch.take_along_dim(
425
+ self.prototype_vectors, max_indices.unsqueeze(-1), dim=0)
426
+ n_prototypes = 1
427
+ else:
428
+ attention_vectors = self.attention_vectors
429
+ prototype_vectors = self.prototype_vectors
430
+ n_prototypes = self.num_prototypes_per_class_tmp
431
+
432
+ if self.use_attention or self.use_global_attention:
433
+
434
+ attention_mask_from_tokens = utils.attention_mask_from_tokens(
435
+ attention_mask, batch["tokens"])
436
+
437
+ (weighted_samples_per_class,
438
+ attention_per_token_and_class,) = self.calculate_token_class_attention(
439
+ token_vectors,
440
+ attention_vectors,
441
+ n_prototypes,
442
+ mask=attention_mask_from_tokens)
443
+
444
+ if self.normalize is not None:
445
+ weighted_samples_per_class = nn.functional.normalize(
446
+ weighted_samples_per_class, p=2, dim=self.normalize
447
+ )
448
+
449
+ if self.num_prototypes_per_class_tmp > 1:
450
+ weighted_samples_per_prototype = weighted_samples_per_class
451
+ else:
452
+ weighted_samples_per_prototype = weighted_samples_per_class.repeat_interleave(
453
+ self.num_prototypes_per_class.long(), dim=1)
454
+
455
+ if self.dot_product:
456
+ score_per_prototype = torch.einsum(
457
+ "bs,abs->ab", self.prototype_vectors, weighted_samples_per_prototype
458
+ )
459
+ elif max_indices is not None:
460
+ score_per_prototype = -self.pairwise_dist(
461
+ prototype_vectors,
462
+ weighted_samples_per_prototype
463
+ ).reshape(-1, self.num_classes)
464
+ elif self.num_prototypes_per_class_tmp > 1:
465
+ permuted = torch.permute(
466
+ weighted_samples_per_prototype, (1, 0, 2, 3))
467
+ score_per_prototype = -self.pairwise_dist(
468
+ prototype_vectors.reshape(self.num_prototypes_per_class_tmp *
469
+ self.num_classes, self.hidden_size),
470
+ permuted.reshape(-1, self.num_prototypes_per_class_tmp *
471
+ self.num_classes, self.hidden_size)
472
+ ).reshape(-1, self.num_prototypes_per_class_tmp, self.num_classes)
473
+ elif self.num_prototypes_per_class_tmp == 1:
474
+ score_per_prototype = -self.pairwise_dist(
475
+ self.prototype_vectors, weighted_samples_per_prototype
476
+ )
477
+
478
+ metadata = (
479
+ attention_per_token_and_class,
480
+ weighted_samples_per_prototype,
481
+ score_per_prototype,
482
+ batch["targets"],
483
+ batch["sample_ids"],
484
+ )
485
+
486
+ else:
487
+ score_per_prototype = - \
488
+ torch.cdist(token_vectors.mean(dim=1), self.prototype_vectors)
489
+ metadata = None, None, score_per_prototype, batch["targets"], batch["sample_ids"]
490
+
491
+ logits, max_indices = self.get_logits_per_class(
492
+ score_per_prototype, max_indices)
493
+
494
+ return logits, max_indices, metadata
495
+
496
+ # @profile
497
+ def calculate_token_class_attention(self, batch_samples, class_attention_vectors, n_prototypes, mask=None):
498
+ if class_attention_vectors.device != batch_samples.device:
499
+ class_attention_vectors = class_attention_vectors.to(
500
+ batch_samples.device)
501
+
502
+ if self.num_prototypes_per_class_tmp == 1:
503
+ score_per_token_and_class = torch.einsum(
504
+ "ikj,mj->imk", batch_samples, class_attention_vectors
505
+ )
506
+ elif self.num_prototypes_per_class_tmp != n_prototypes:
507
+ score_per_token_and_class = torch.einsum(
508
+ "ikj,imj->imk", batch_samples, class_attention_vectors
509
+ )
510
+ else:
511
+ score_per_token_and_class = torch.einsum(
512
+ "ikj,smj->simk", batch_samples, class_attention_vectors
513
+ )
514
+
515
+ if self.num_prototypes_per_class_tmp == 1:
516
+ expanded_mask = mask.unsqueeze(dim=1).expand(
517
+ mask.size(0), class_attention_vectors.size(0), mask.size(1))
518
+
519
+ expanded_mask = F.pad(input=expanded_mask,
520
+ pad=(
521
+ 0, score_per_token_and_class.shape[2] - expanded_mask.shape[2]),
522
+ mode='constant', value=0)
523
+
524
+ score_per_token_and_class = score_per_token_and_class.masked_fill(
525
+ (expanded_mask == 0),
526
+ float('-inf'))
527
+ elif self.num_prototypes_per_class_tmp != n_prototypes:
528
+ expanded_mask = mask.unsqueeze(dim=1).expand(
529
+ mask.size(0), class_attention_vectors.size(1), mask.size(1))
530
+ score_per_token_and_class = score_per_token_and_class.masked_fill(
531
+ (expanded_mask == 0),
532
+ float('-inf'))
533
+ else:
534
+ expanded_mask = mask.unsqueeze(dim=1).expand(self.num_prototypes_per_class_tmp, mask.size(
535
+ 0), class_attention_vectors.size(1), mask.size(1))
536
+ score_per_token_and_class = score_per_token_and_class.masked_fill(
537
+ (expanded_mask == 0),
538
+ float('-inf'))
539
+
540
+ if self.use_sigmoid:
541
+ attention_per_token_and_class = (
542
+ torch.sigmoid(score_per_token_and_class) /
543
+ score_per_token_and_class.shape[2]
544
+ )
545
+ else:
546
+ if self.num_prototypes_per_class_tmp == 1:
547
+ attention_per_token_and_class = F.softmax(
548
+ score_per_token_and_class, dim=2)
549
+ elif self.num_prototypes_per_class_tmp != n_prototypes:
550
+ attention_per_token_and_class = F.softmax(
551
+ score_per_token_and_class, dim=2)
552
+ else:
553
+ attention_per_token_and_class = F.softmax(
554
+ score_per_token_and_class, dim=3)
555
+
556
+ if self.num_prototypes_per_class_tmp == 1 or self.num_prototypes_per_class_tmp != n_prototypes:
557
+ weighted_samples_per_class = torch.einsum('ikjm,ikj->ikm',
558
+ batch_samples.unsqueeze(dim=1).expand(batch_samples.size(0), self.num_classes, batch_samples.size( 1), batch_samples.size(2)),
559
+ attention_per_token_and_class)
560
+
561
+ else:
562
+ expanded = batch_samples.unsqueeze(dim=1).expand(
563
+ batch_samples.size(0),
564
+ self.num_classes,
565
+ batch_samples.size(1),
566
+ batch_samples.size(2),
567
+ )
568
+ weighted_samples_per_class = torch.einsum("ikjm,sikj->sikm", expanded, attention_per_token_and_class)
569
+
570
+ return weighted_samples_per_class, attention_per_token_and_class
571
+
572
+ # @profile
573
+ def get_logits_per_class(self, score_per_prototype, max_indices=None):
574
+ if self.final_layer:
575
+ if score_per_prototype.device != self.final_linear.device:
576
+ score_per_prototype = score_per_prototype.to(
577
+ self.final_linear.device)
578
+
579
+ return torch.matmul(score_per_prototype, self.final_linear)
580
+
581
+ else:
582
+ if self.num_prototypes_per_class_tmp == 1:
583
+ max_logits_per_class = score_per_prototype
584
+ max_logits_per_class_index = None
585
+ elif max_indices is not None:
586
+ return score_per_prototype, max_indices
587
+ else:
588
+ max_logits_per_class, max_logits_per_class_index = torch.max(
589
+ score_per_prototype, dim=1)
590
+ return max_logits_per_class, max_logits_per_class_index
591
+
592
+ def calculate_prototype_loss(self):
593
+ prototype_loss = (
594
+ 100
595
+ / torch.tensor(
596
+ [
597
+ torch.cdist(
598
+ self.prototype_vectors[
599
+ (self.prototype_to_class_map == i).nonzero().flatten()
600
+ ][:1],
601
+ self.prototype_vectors[
602
+ (self.prototype_to_class_map == i).nonzero().flatten()
603
+ ][1:],
604
+ ).min()
605
+ for i in range(self.num_classes)
606
+ if len((self.prototype_to_class_map == i).nonzero()) > 1
607
+ ]
608
+ ).sum()
609
+ )
610
+ return prototype_loss
611
+
612
+ def validation_step(self, batch, batch_idx):
613
+ with torch.no_grad():
614
+ targets = torch.tensor(batch["targets"], device=self.device)
615
+
616
+ logits, max_indices, _ = self(batch)
617
+
618
+ for metric_name in self.train_metrics:
619
+ metric = self.train_metrics[metric_name]
620
+ metric(torch.sigmoid(logits), targets)
621
+
622
+ def validation_epoch_end(self, outputs) -> None:
623
+ for metric_name in self.train_metrics:
624
+ metric = self.train_metrics[metric_name]
625
+ self.log(f"val/{metric_name}", metric.compute(),
626
+ batch_size=self.batch_size)
627
+ metric.reset()
628
+
629
+ def test_step(self, batch, batch_idx):
630
+ with torch.no_grad():
631
+ targets = torch.tensor(batch["targets"], device=self.device)
632
+
633
+ logits, max_indices, _ = self(batch)
634
+ preds = torch.sigmoid(logits)
635
+ del logits
636
+ for metric_name in self.all_metrics:
637
+ metric = self.all_metrics[metric_name]
638
+ metric(preds, targets)
639
+
640
+ return preds, targets
641
+
642
+ def test_epoch_end(self, outputs) -> None:
643
+ log_dir = self.logger.log_dir
644
+ for metric_name in self.all_metrics:
645
+ metric = self.all_metrics[metric_name]
646
+ value = metric.compute()
647
+ self.log(f"test/{metric_name}", value, batch_size=self.batch_size)
648
+
649
+ with open(os.path.join(log_dir, "test_metrics.txt"), "a") as metrics_file:
650
+ metrics_file.write(f"{metric_name}: {value}\n")
651
+
652
+ metric.reset()
653
+
654
+ predictions = torch.cat([out[0] for out in outputs])
655
+
656
+ targets = torch.cat([out[1] for out in outputs])
657
+
658
+ pr_auc = metrics.calculate_pr_auc(
659
+ prediction=predictions, target=targets, num_classes=self.num_classes, device=self.device
660
+ )
661
+
662
+ with open(os.path.join(self.logger.log_dir, "PR_AUC_score.txt"), "w") as metrics_file:
663
+ metrics_file.write(f"PR AUC: {pr_auc.cpu().numpy()}\n")
sproto/utils/utils.py ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ from pathlib import Path
4
+ from typing import Optional, Union, Iterable, List
5
+
6
+ import matplotlib
7
+ import numpy as np
8
+ import torch
9
+ from pytorch_lightning.callbacks import ModelCheckpoint
10
+ import shutil
11
+ import re
12
+
13
+
14
+ def freeze_model_weights(model: torch.nn.Module) -> None:
15
+ for param in model.parameters():
16
+ param.requires_grad = False
17
+
18
+
19
+ #@profile
20
+ def attention_mask_from_tokens(masks, token_list):
21
+ mask_patterns = [["chief", "complaint", ":"],
22
+ ["present", "illness", ":"],
23
+ ["medical", "history", ":"],
24
+ ["medication", "on", "admission", ":"],
25
+ ["allergies", ":"],
26
+ ["physical", "exam", ":"],
27
+ ["family", "history", ":"],
28
+ ["social", "history", ":"],
29
+ # ["[CLS]"],
30
+ # ["[SEP]"],
31
+ ["\[CLS\]"],# Escaping the [ for the regex
32
+ ["\[SEP\]"],
33
+ ]
34
+ # test_mask = masks.clone()
35
+ for i, sentence in enumerate(token_list):
36
+ for j, pattern in enumerate(mask_patterns):
37
+ str_sentence = ' '.join(sentence)
38
+ # if pattern == ['[CLS]']:
39
+ # pattern = ['\[CLS\]']
40
+ # if pattern == ['[SEP]']:
41
+ # pattern = ['\[SEP\]']
42
+ r_pattern = r' '.join(pattern)
43
+ matches = [match.start() for match in re.finditer(r_pattern, str_sentence)]
44
+ for match in matches:
45
+ start_index = len(str_sentence[0:match].split())
46
+ # test_mask[i, start_index:start_index + len(pattern)] = 0
47
+ masks[i, start_index:start_index + len(pattern)] = 0
48
+
49
+ # for i, tokens in enumerate(token_list):
50
+ # for j, token in enumerate(tokens):
51
+ # for pattern in mask_patterns:
52
+ # if pattern == tokens[j:j + len(pattern)]:
53
+ # masks[i, j:j + len(pattern)] = 0
54
+
55
+ # assert (test_mask == masks).all()
56
+ return masks
57
+
58
+
59
+ def get_bert_vectors_per_sample(batch, bert, use_cuda, linear=None):
60
+ input_ids = batch["input_ids"]
61
+ attention_mask = batch["attention_masks"]
62
+ token_type_ids = batch["token_type_ids"]
63
+
64
+ if use_cuda:
65
+ input_ids = input_ids.cuda()
66
+ attention_mask = attention_mask.cuda()
67
+ token_type_ids = token_type_ids.cuda()
68
+
69
+ output = bert(input_ids=input_ids,
70
+ attention_mask=attention_mask,
71
+ token_type_ids=token_type_ids)
72
+
73
+ if linear is not None:
74
+ if use_cuda:
75
+ linear = linear.cuda()
76
+ token_vectors = linear(output.last_hidden_state)
77
+ else:
78
+ token_vectors = output.last_hidden_state
79
+
80
+ mean_over_tokens = token_vectors.mean(dim=1)
81
+
82
+ return mean_over_tokens, token_vectors
83
+
84
+
85
+ def get_attended_vector_per_sample(batch, bert, use_cuda, linear=None):
86
+ input_ids = batch["input_ids"]
87
+ attention_mask = batch["attention_masks"]
88
+ token_type_ids = batch["token_type_ids"]
89
+
90
+ if use_cuda:
91
+ input_ids = input_ids.cuda()
92
+ attention_mask = attention_mask.cuda()
93
+ token_type_ids = token_type_ids.cuda()
94
+
95
+ output = bert(input_ids=input_ids,
96
+ attention_mask=attention_mask,
97
+ token_type_ids=token_type_ids)
98
+
99
+ if linear is not None:
100
+ if use_cuda:
101
+ linear = linear.cuda()
102
+ token_vectors = linear(output.last_hidden_state)
103
+ else:
104
+ token_vectors = output.last_hidden_state
105
+
106
+ mean_over_tokens = token_vectors.mean(dim=1)
107
+
108
+ return mean_over_tokens, token_vectors
109
+
110
+
111
+ def pad_batch_samples(batch_samples: Iterable, num_tokens: int) -> List:
112
+ padded_samples = []
113
+ for sample in batch_samples:
114
+ missing_tokens = num_tokens - len(sample)
115
+ tokens_to_append = ["[PAD]"] * missing_tokens
116
+ padded_samples += sample + tokens_to_append
117
+ return padded_samples
118
+
119
+
120
+ class ProjectorCallback(ModelCheckpoint):
121
+ def __init__(
122
+ self,
123
+ train_dataloader,
124
+ project_n_batches=-1, # -1 means project all batches
125
+ dirpath: Optional[Union[str, Path]] = None,
126
+ filename: Optional[str] = None,
127
+ monitor: Optional[str] = None,
128
+ verbose: bool = False,
129
+ save_last: Optional[bool] = None,
130
+ save_top_k: Optional[int] = None,
131
+ save_weights_only: bool = False,
132
+ mode: str = "auto",
133
+ period: int = 1,
134
+ prefix: str = ""
135
+ ):
136
+ super().__init__(dirpath=dirpath, filename=filename, monitor=monitor, verbose=verbose, save_last=save_last,
137
+ save_top_k=save_top_k, save_weights_only=save_weights_only, mode=mode, period=period,
138
+ prefix=prefix)
139
+ self.train_dataloader = train_dataloader
140
+ self.project_n_batches = project_n_batches
141
+
142
+ def on_validation_end(self, trainer, pl_module):
143
+ """
144
+ After each validation step, save the learned token and prototype embeddings for analysis in the Projector.
145
+ """
146
+ super().on_validation_end(trainer, pl_module)
147
+
148
+ with torch.no_grad():
149
+
150
+ all_vectors = []
151
+ metadata = []
152
+ for i, batch in enumerate(self.train_dataloader):
153
+ _, _, batch_features = pl_module(batch, return_metadata=True)
154
+
155
+ targets = batch["targets"]
156
+
157
+ features = batch_features[0]
158
+ tokens = batch_features[1]
159
+ prototype_vectors = batch_features[2]
160
+
161
+ batch_size = features.shape[0]
162
+
163
+ window_len = features.shape[1]
164
+
165
+ for sample_i in range(batch_size):
166
+ for window_i in range(window_len):
167
+ window_vector = features[sample_i][window_i]
168
+ window_tokens = tokens[sample_i * window_len + window_i]
169
+
170
+ if window_tokens == "[PAD]" or window_tokens == "[SEP]":
171
+ continue
172
+
173
+ all_vectors.append(window_vector)
174
+ metadata.append([window_tokens, targets[sample_i]])
175
+
176
+ if ["PROTO_0", 0] not in metadata:
177
+ for j, vector in enumerate(prototype_vectors):
178
+ prototype_class = int(j // pl_module.prototypes_per_class)
179
+ all_vectors.append(vector.squeeze())
180
+ metadata.append([f"PROTO_{prototype_class}", prototype_class])
181
+
182
+ if self.project_n_batches != -1 and i >= self.project_n_batches - 1:
183
+ break
184
+
185
+ trainer.logger.experiment.add_embedding(torch.stack(all_vectors), metadata, global_step=trainer.global_step,
186
+ metadata_header=["tokens", "target"])
187
+
188
+ delete_intermediate_embeddings(trainer.logger.experiment.log_dir, trainer.global_step)
189
+
190
+
191
+ def delete_intermediate_embeddings(log_dir, current_step):
192
+ dir_content = os.listdir(log_dir)
193
+ for file_or_dir in dir_content:
194
+ try:
195
+ file_as_integer = int(file_or_dir)
196
+ abs_path = os.path.join(log_dir, file_or_dir)
197
+
198
+ if os.path.isdir(abs_path) and file_as_integer != current_step and file_as_integer != 0:
199
+ remove_dir(abs_path)
200
+
201
+ except:
202
+ continue
203
+
204
+ embedding_config = """embeddings {{
205
+ tensor_name: "default:{embedding_id}"
206
+ metadata_path: "{embedding_id}/default/metadata.tsv"
207
+ tensor_path: "{embedding_id}/default/tensors.tsv"\n}}"""
208
+
209
+ config_text = embedding_config.format(embedding_id="00000") + "\n" + \
210
+ embedding_config.format(embedding_id=f"{current_step:05}")
211
+
212
+ with open(os.path.join(log_dir, "projector_config.pbtxt"), "w") as config_file_write:
213
+ config_file_write.write(config_text)
214
+
215
+
216
+ def remove_dir(path):
217
+ try:
218
+ shutil.rmtree(path)
219
+ print(f"delete dir {path}")
220
+ except OSError as e:
221
+ print("Error: %s : %s" % (path, e.strerror))
222
+
223
+
224
+ def load_eval_buckets(eval_bucket_path):
225
+ buckets = None
226
+ if eval_bucket_path is not None:
227
+ with open(eval_bucket_path) as bucket_file:
228
+ buckets = json.load(bucket_file)
229
+ return buckets
230
+
231
+
232
+ def build_heatmaps(case_tokens, token_scores, tint="red", amplifier=8):
233
+ heatmap_per_prototype = []
234
+ for prototype_scores in token_scores:
235
+
236
+ template = '<span style="color: black; background-color: {}">{}</span>'
237
+ heatmap_string = ''
238
+ for word, color in zip(case_tokens, prototype_scores):
239
+ color = min(1, color * amplifier)
240
+ if tint == "red":
241
+ hex_color = matplotlib.colors.rgb2hex([1, 1 - color, 1 - color])
242
+ elif tint == "blue":
243
+ hex_color = matplotlib.colors.rgb2hex([1 - color, 1 - color, 1])
244
+ else:
245
+ hex_color = matplotlib.colors.rgb2hex([1 - color, 1, 1 - color])
246
+
247
+ if "##" not in word:
248
+ heatmap_string += '&nbsp'
249
+ word_string = word
250
+ else:
251
+ word_string = word.replace("##", "")
252
+
253
+ heatmap_string += template.format(hex_color, word_string)
254
+
255
+ heatmap_per_prototype.append(heatmap_string)
256
+
257
+ return heatmap_per_prototype