1const TRAINING_CLASS_WORKER_URL = 'oop/trainingClass_worker.js?v=20260602-duplicates'; 2 3class TRAINING_DATA { 4 constructor(status_id, vect_names_id, vect_progress_id, vect_bar_progress_id, training_progress_id, training_bar_progress_id, training_message_id, trainingChart1, trainingChart2, trainingChart3, trainingChart4, display_1_id, display_2_id, display_3_id, display_4_id, display_5_id, ml_config, predictions_config, worker_transformers_flag){ 5 this.status_id = status_id; 6 this.display_1_id = display_1_id; 7 this.display_2_id = display_2_id; 8 this.display_3_id = display_3_id; 9 this.display_4_id = display_4_id; 10 this.display_5_id = display_5_id; 11 12 this.ml_config = {window: ml_config}; 13 this.predictions_config = {button: predictions_config}; 14 15 this.trainingChart1 = trainingChart1; 16 this.trainingChart2 = trainingChart2; 17 this.trainingChart3 = trainingChart3; 18 this.trainingChart4 = trainingChart4; 19 20 this.data_array = []; 21 // Accessible copy of titles and abstracts. 22 this.data_titles = []; 23 this.data_abstracts = []; 24 this.data_titles_and_abstracts = []; 25 this.data_labels = []; 26 this.data_ids = []; 27 28 // Missing or unlabelled data. 29 this.data_array_missing = []; 30 31 // Vector variables. 32 this.tfidf_vectObj = []; 33 34 this.lsa_vectObj = []; 35 36 this.doc2vec_tokens = []; 37 this.doc2vec_vocab = []; 38 this.doc2vec_vocab_freq = []; 39 this.doc2vec_model = []; 40 41 // Generic vector. 42 this.vectors = []; 43 this.vector_names = []; 44 this.vector_config = []; 45 46 this.stopwords = ["0o", "0s", "3a", "3b", "3d", "6b", "6o", "a", "a1", "a2", "a3", "a4", "ab", "able", "about", "above", "abst", "ac", "accordance", "according", "accordingly", "across", "act", "actually", "ad", "added", "adj", "ae", "af", "affected", "affecting", "affects", "after", "afterwards", "ag", "again", "against", "ah", "ain", "ain't", "aj", "al", "all", "allow", "allows", "almost", "alone", "along", "already", "also", "although", "always", "am", "among", "amongst", "amoungst", "amount", "an", "and", "announce", "another", "any", "anybody", "anyhow", "anymore", "anyone", "anything", "anyway", "anyways", "anywhere", "ao", "ap", "apart", "apparently", "appear", "appreciate", "appropriate", "approximately", "ar", "are", "aren", "arent", "aren't", "arise", "around", "as", "a's", "aside", "ask", "asking", "associated", "at", "au", "auth", "av", "available", "aw", "away", "awfully", "ax", "ay", "az", "b", "b1", "b2", "b3", "ba", "back", "bc", "bd", "be", "became", "because", "become", "becomes", "becoming", "been", "before", "beforehand", "begin", "beginning", "beginnings", "begins", "behind", "being", "believe", "below", "beside", "besides", "best", "better", "between", "beyond", "bi", "bill", "biol", "bj", "bk", "bl", "bn", "both", "bottom", "bp", "br", "brief", "briefly", "bs", "bt", "bu", "but", "bx", "by", "c", "c1", "c2", "c3", "ca", "call", "came", "can", "cannot", "cant", "can't", "cause", "causes", "cc", "cd", "ce", "certain", "certainly", "cf", "cg", "ch", "changes", "ci", "cit", "cj", "cl", "clearly", "cm", "c'mon", "cn", "co", "com", "come", "comes", "con", "concerning", "consequently", "consider", "considering", "contain", "containing", "contains", "corresponding", "could", "couldn", "couldnt", "couldn't", "course", "cp", "cq", "cr", "cry", "cs", "c's", "ct", "cu", "currently", "cv", "cx", "cy", "cz", "d", "d2", "da", "date", "dc", "dd", "de", "definitely", "describe", "described", "despite", "detail", "df", "di", "did", "didn", "didn't", "different", "dj", "dk", "dl", "do", "does", "doesn", "doesn't", "doing", "don", "done", "don't", "down", "downwards", "dp", "dr", "ds", "dt", "du", "due", "during", "dx", "dy", "e", "e2", "e3", "ea", "each", "ec", "ed", "edu", "ee", "ef", "effect", "eg", "ei", "eight", "eighty", "either", "ej", "el", "eleven", "else", "elsewhere", "em", "empty", "en", "end", "ending", "enough", "entirely", "eo", "ep", "eq", "er", "es", "especially", "est", "et", "et-al", "etc", "eu", "ev", "even", "ever", "every", "everybody", "everyone", "everything", "everywhere", "ex", "exactly", "example", "except", "ey", "f", "f2", "fa", "far", "fc", "few", "ff", "fi", "fifteen", "fifth", "fify", "fill", "find", "fire", "first", "five", "fix", "fj", "fl", "fn", "fo", "followed", "following", "follows", "for", "former", "formerly", "forth", "forty", "found", "four", "fr", "from", "front", "fs", "ft", "fu", "full", "further", "furthermore", "fy", "g", "ga", "gave", "ge", "get", "gets", "getting", "gi", "give", "given", "gives", "giving", "gj", "gl", "go", "goes", "going", "gone", "got", "gotten", "gr", "greetings", "gs", "gy", "h", "h2", "h3", "had", "hadn", "hadn't", "happens", "hardly", "has", "hasn", "hasnt", "hasn't", "have", "haven", "haven't", "having", "he", "hed", "he'd", "he'll", "hello", "help", "hence", "her", "here", "hereafter", "hereby", "herein", "heres", "here's", "hereupon", "hers", "herself", "hes", "he's", "hh", "hi", "hid", "him", "himself", "his", "hither", "hj", "ho", "home", "hopefully", "how", "howbeit", "however", "how's", "hr", "hs", "http", "hu", "hundred", "hy", "i", "i2", "i3", "i4", "i6", "i7", "i8", "ia", "ib", "ibid", "ic", "id", "i'd", "ie", "if", "ig", "ignored", "ih", "ii", "ij", "il", "i'll", "im", "i'm", "immediate", "immediately", "importance", "important", "in", "inasmuch", "inc", "indeed", "index", "indicate", "indicated", "indicates", "information", "inner", "insofar", "instead", "interest", "into", "invention", "inward", "io", "ip", "iq", "ir", "is", "isn", "isn't", "it", "itd", "it'd", "it'll", "its", "it's", "itself", "iv", "i've", "ix", "iy", "iz", "j", "jj", "jr", "js", "jt", "ju", "just", "k", "ke", "keep", "keeps", "kept", "kg", "kj", "km", "know", "known", "knows", "ko", "l", "l2", "la", "largely", "last", "lately", "later", "latter", "latterly", "lb", "lc", "le", "least", "les", "less", "lest", "let", "lets", "let's", "lf", "like", "liked", "likely", "line", "little", "lj", "ll", "ll", "ln", "lo", "look", "looking", "looks", "los", "lr", "ls", "lt", "ltd", "m", "m2", "ma", "made", "mainly", "make", "makes", "many", "may", "maybe", "me", "mean", "means", "meantime", "meanwhile", "merely", "mg", "might", "mightn", "mightn't", "mill", "million", "mine", "miss", "ml", "mn", "mo", "more", "moreover", "most", "mostly", "move", "mr", "mrs", "ms", "mt", "mu", "much", "mug", "must", "mustn", "mustn't", "my", "myself", "n", "n2", "na", "name", "namely", "nay", "nc", "nd", "ne", "near", "nearly", "necessarily", "necessary", "need", "needn", "needn't", "needs", "neither", "never", "nevertheless", "new", "next", "ng", "ni", "nine", "ninety", "nj", "nl", "nn", "no", "nobody", "non", "none", "nonetheless", "noone", "nor", "normally", "nos", "not", "noted", "nothing", "novel", "now", "nowhere", "nr", "ns", "nt", "ny", "o", "oa", "ob", "obtain", "obtained", "obviously", "oc", "od", "of", "off", "often", "og", "oh", "oi", "oj", "ok", "okay", "ol", "old", "om", "omitted", "on", "once", "one", "ones", "only", "onto", "oo", "op", "oq", "or", "ord", "os", "ot", "other", "others", "otherwise", "ou", "ought", "our", "ours", "ourselves", "out", "outside", "over", "overall", "ow", "owing", "own", "ox", "oz", "p", "p1", "p2", "p3", "page", "pagecount", "pages", "par", "part", "particular", "particularly", "pas", "past", "pc", "pd", "pe", "per", "perhaps", "pf", "ph", "pi", "pj", "pk", "pl", "placed", "please", "plus", "pm", "pn", "po", "poorly", "possible", "possibly", "potentially", "pp", "pq", "pr", "predominantly", "present", "presumably", "previously", "primarily", "probably", "promptly", "proud", "provides", "ps", "pt", "pu", "put", "py", "q", "qj", "qu", "que", "quickly", "quite", "qv", "r", "r2", "ra", "ran", "rather", "rc", "rd", "re", "readily", "really", "reasonably", "recent", "recently", "ref", "refs", "regarding", "regardless", "regards", "related", "relatively", "research", "research-articl", "respectively", "resulted", "resulting", "results", "rf", "rh", "ri", "right", "rj", "rl", "rm", "rn", "ro", "rq", "rr", "rs", "rt", "ru", "run", "rv", "ry", "s", "s2", "sa", "said", "same", "saw", "say", "saying", "says", "sc", "sd", "se", "sec", "second", "secondly", "section", "see", "seeing", "seem", "seemed", "seeming", "seems", "seen", "self", "selves", "sensible", "sent", "serious", "seriously", "seven", "several", "sf", "shall", "shan", "shan't", "she", "shed", "she'd", "she'll", "shes", "she's", "should", "shouldn", "shouldn't", "should've", "show", "showed", "shown", "showns", "shows", "si", "side", "significant", "significantly", "similar", "similarly", "since", "sincere", "six", "sixty", "sj", "sl", "slightly", "sm", "sn", "so", "some", "somebody", "somehow", "someone", "somethan", "something", "sometime", "sometimes", "somewhat", "somewhere", "soon", "sorry", "sp", "specifically", "specified", "specify", "specifying", "sq", "sr", "ss", "st", "still", "stop", "strongly", "sub", "substantially", "successfully", "such", "sufficiently", "suggest", "sup", "sure", "sy", "system", "sz", "t", "t1", "t2", "t3", "take", "taken", "taking", "tb", "tc", "td", "te", "tell", "ten", "tends", "tf", "th", "than", "thank", "thanks", "thanx", "that", "that'll", "thats", "that's", "that've", "the", "their", "theirs", "them", "themselves", "then", "thence", "there", "thereafter", "thereby", "thered", "therefore", "therein", "there'll", "thereof", "therere", "theres", "there's", "thereto", "thereupon", "there've", "these", "they", "theyd", "they'd", "they'll", "theyre", "they're", "they've", "thickv", "thin", "think", "third", "this", "thorough", "thoroughly", "those", "thou", "though", "thoughh", "thousand", "three", "throug", "through", "throughout", "thru", "thus", "ti", "til", "tip", "tj", "tl", "tm", "tn", "to", "together", "too", "took", "top", "toward", "towards", "tp", "tq", "tr", "tried", "tries", "truly", "try", "trying", "ts", "t's", "tt", "tv", "twelve", "twenty", "twice", "two", "tx", "u", "u201d", "ue", "ui", "uj", "uk", "um", "un", "under", "unfortunately", "unless", "unlike", "unlikely", "until", "unto", "uo", "up", "upon", "ups", "ur", "us", "use", "used", "useful", "usefully", "usefulness", "uses", "using", "usually", "ut", "v", "va", "value", "various", "vd", "ve", "ve", "very", "via", "viz", "vj", "vo", "vol", "vols", "volumtype", "vq", "vs", "vt", "vu", "w", "wa", "want", "wants", "was", "wasn", "wasnt", "wasn't", "way", "we", "wed", "we'd", "welcome", "well", "we'll", "well-b", "went", "were", "we're", "weren", "werent", "weren't", "we've", "what", "whatever", "what'll", "whats", "what's", "when", "whence", "whenever", "when's", "where", "whereafter", "whereas", "whereby", "wherein", "wheres", "where's", "whereupon", "wherever", "whether", "which", "while", "whim", "whither", "who", "whod", "whoever", "whole", "who'll", "whom", "whomever", "whos", "who's", "whose", "why", "why's", "wi", "widely", "will", "willing", "wish", "with", "within", "without", "wo", "won", "wonder", "wont", "won't", "words", "world", "would", "wouldn", "wouldnt", "wouldn't", "www", "x", "x1", "x2", "x3", "xf", "xi", "xj", "xk", "xl", "xn", "xo", "xs", "xt", "xv", "xx", "y", "y2", "yes", "yet", "yj", "yl", "you", "youd", "you'd", "you'll", "your", "youre", "you're", "yours", "yourself", "yourselves", "you've", "yr", "ys", "yt", "z", "zero", "zi", "zz"]; 47 48 this.stopwords_multilingual = []; 49 fetch('/stopwords/stopwords-iso.json').then(res => res.json()).then(data => { 50 this.stopwords_multilingual = data; 51 //Create all stopwords 52 this.stopwords_multilingual.all = Object.values(this.stopwords_multilingual).flat().filter((v, i, a) => a.indexOf(v) === i); 53 });
54 55 56 //Vectorization progress variables. 57 this.vect_names_id = vect_names_id; 58 this.vect_progress_id = vect_progress_id; 59 this.vect_bar_progress_id = vect_bar_progress_id; 60 61 this.training_progress_id = training_progress_id; 62 this.training_bar_progress_id = training_bar_progress_id; 63 this.training_message_id = training_message_id; 64 this.training_data = []; 65 this.prediction_data = []; 66 67 //Create webworker. 68 this.worker_transformers_flag = worker_transformers_flag === true; 69 this.worker = new Worker(TRAINING_CLASS_WORKER_URL); 70 71 if (this.worker_transformers_flag){ 72 this.worker_sentence_transformers = new Worker('oop/transformers_worker.js', {type: 'module'}); 73 } else { 74 this.worker_sentence_transformers = []; 75 } 76 77 78 // Miscellaneous. 79 this.list_of_colours = [ 80 "#42A5F5", // Blue 81 "#FF5252", // Red 82 "#4CAF50", // Green 83 "#FFD740", // Yellow 84 "#FF4081", // Pink 85 "#8BC34A", // Light Green 86 "#FF9800", // Orange 87 "#9C27B0", // Purple 88 "#795548", // Brown 89 "#00BCD4", // Cyan 90 "#FF5722", // Deep Orange 91 "#3F51B5", // Indigo 92 "#9E9E9E", // Grey 93 "#607D8B", // Blue Grey 94 "#F44336", // Dark Red 95 "#2196F3", // Dark Blue 96 "#00ACC1", // Dark Cyan 97 "#9C27B0" // Dark Purple 98 ]; 99 100 101 //Result variables and plotting. 102 this.proba_threshold = 0.5; 103 this.prediction_label_mode = "threshold"; 104 this.prediction_relevant_threshold = 0.95; 105 this.prediction_irrelevant_threshold = 0.05; 106 this.roc_curves = []; 107 this.roc_curve_mean = []; 108 this.metrics = []; 109 this.metrics_stats = []; 110 111 112 //Active learning variables. 113 this.sorting_probabilities = []; 114 115 116 //Similarity tool. 117 this.similarity_array = []; 118 this.similarity_summary = null; 119 this.currentSimIndex = 0; 120 121 this.similarity_pairs_action_discarded = []; 122 this.similarity_array_discarded = []; 123 this.discarded_similarity_indexes = new Set(); 124 this.currentDiscardedIndex = 0; 125 126 } 127 128 _get_element(id) { 129 return typeof _ === "function" && id ? _(id) : null; 130 } 131 132 _format_number(value, digits = 2, fallback = "n/a") { 133 if (value === null || value === undefined || value === "") { 134 return fallback; 135 } 136 137 const number = Number(value); 138 return Number.isFinite(number) ? number.toFixed(digits) : fallback; 139 } 140 141 _format_percent(value, digits = 2, fallback = "n/a") { 142 if (value === null || value === undefined || value === "") { 143 return fallback; 144 } 145 146 const number = Number(value); 147 return Number.isFinite(number) ? (100 * number).toFixed(digits) + "%" : fallback; 148 } 149 150 _set_progress_bar(barId, progress) { 151 const barEl = this._get_element(barId); 152 if (!barEl) { 153 return; 154 } 155 156 const safeProgress = Number.isFinite(Number(progress)) 157 ? Math.min(1, Math.max(0, Number(progress))) 158 : 0; 159 const percentage = (safeProgress * 100).toFixed(1) + "%"; 160 barEl.style.width = percentage; 161 barEl.innerHTML = percentage; 162 } 163 164 _restart_workers() { 165 if (this.worker instanceof Worker) { 166 this.worker.terminate(); 167 } 168 169 if (this.worker_sentence_transformers instanceof Worker) { 170 this.worker_sentence_transformers.terminate(); 171 } 172 173 this.worker = new Worker(TRAINING_CLASS_WORKER_URL); 174 this.worker_sentence_transformers = this.worker_transformers_flag 175 ? new Worker('oop/transformers_worker.js', {type: 'module'}) 176 : []; 177 } 178 179 _has_training_results() { 180 return Boolean( 181 this.training_data && 182 typeof this.training_data === "object" && 183 Array.isArray(this.training_data.ensemble_cv1_ypred_best_stacked) && 184 Array.isArray(this.training_data.ensemble_cv1_y) 185 ); 186 } 187 188 _has_prediction_results() { 189 return Boolean( 190 this.prediction_data && 191 typeof this.prediction_data === "object" && 192 Array.isArray(this.prediction_data.y_proba_stacked) && 193 Array.isArray(this.prediction_data.y_label_stacked) 194 ); 195 } 196 197 get data_labels() { 198 return this._build_entry_field_arrays({ labels: true }).labels; 199 } 200 201 set data_labels(value) { 202 this._data_labels_compat = Array.isArray(value) ? value : []; 203 } 204 205 get data_ids() { 206 return this._build_entry_field_arrays({ ids: true }).ids; 207 } 208 209 set data_ids(value) { 210 this._data_ids_compat = Array.isArray(value) ? value : []; 211 } 212 213 _rebuild_cached_entry_fields() { 214 const fields = this._build_entry_field_arrays(); 215 this.data_titles = fields.titles;
216 this.data_abstracts = fields.abstracts; 217 this.data_titles_and_abstracts = fields.titlesAndAbstracts; 218 this.data_labels = fields.labels; 219 this.data_ids = fields.ids; 220 return fields; 221 } 222 223 _clear_cached_entry_fields() { 224 this.data_titles = []; 225 this.data_abstracts = []; 226 this.data_titles_and_abstracts = []; 227 this.data_labels = []; 228 this.data_ids = []; 229 } 230 231 _build_entry_field_arrays(entries = this.data_array, options = null) { 232 if (options == null && entries && typeof entries === "object" && !Array.isArray(entries)) { 233 options = entries; 234 entries = this.data_array; 235 } 236 237 const safeEntries = Array.isArray(entries) ? entries : []; 238 const useDefaults = !options || Object.keys(options).length === 0; 239 const includeTitles = useDefaults || options.titles === true; 240 const includeAbstracts = useDefaults || options.abstracts === true; 241 const includeText = useDefaults || options.text === true || options.titlesAndAbstracts === true; 242 const includeLabels = useDefaults || options.labels === true; 243 const includeIds = useDefaults || options.ids === true; 244 const titles = includeTitles ? new Array(safeEntries.length) : []; 245 const abstracts = includeAbstracts ? new Array(safeEntries.length) : []; 246 const titlesAndAbstracts = includeText ? new Array(safeEntries.length) : []; 247 const labels = includeLabels ? new Array(safeEntries.length) : []; 248 const ids = includeIds ? new Array(safeEntries.length) : []; 249 250 for (let index = 0; index < safeEntries.length; index++) { 251 const entry = safeEntries[index] || {}; 252 const needsTitle = includeTitles || includeText; 253 const needsAbstract = includeAbstracts || includeText; 254 const title = needsTitle ? (entry.title ?? "") : ""; 255 const abstract = needsAbstract ? (entry.abstract ?? "") : ""; 256 257 if (includeTitles) titles[index] = title; 258 if (includeAbstracts) abstracts[index] = abstract; 259 if (includeText) titlesAndAbstracts[index] = `${title} ${abstract}`.trim(); 260 if (includeLabels) labels[index] = entry.label ?? null; 261 if (includeIds) ids[index] = entry.paper_id ?? null; 262 } 263 264 return { titles, abstracts, titlesAndAbstracts, labels, ids }; 265 } 266 267 _encode_numeric_matrix_for_snapshot(matrix) { 268 if (!Array.isArray(matrix) || matrix.length === 0) return matrix; 269 270 const rowLengths = matrix.map(row => Array.isArray(row) || ArrayBuffer.isView(row) ? row.length : -1); 271 if (rowLengths.some(length => length < 0)) return matrix; 272 273 const totalLength = rowLengths.reduce((sum, length) => sum + length, 0); 274 if (totalLength === 0) { 275 return { 276 __snapshot_type: "NumericMatrixFloat32", 277 row_lengths: rowLengths, 278 value: snapshot_array_buffer_to_base64(new Float32Array(0).buffer) 279 }; 280 } 281 282 const flattened = new Float32Array(totalLength); 283 let offset = 0; 284 for (const row of matrix) { 285 for (let i = 0; i < row.length; i++) { 286 const value = Number(row[i]); 287 if (!Number.isFinite(value)) return matrix; 288 flattened[offset + i] = value; 289 } 290 offset += row.length; 291 } 292 293 return { 294 __snapshot_type: "NumericMatrixFloat32", 295 row_lengths: rowLengths, 296 value: snapshot_array_buffer_to_base64(flattened.buffer) 297 }; 298 } 299 300 _decode_numeric_matrix_from_snapshot(encoded) { 301 if (!encoded || encoded.__snapshot_type !== "NumericMatrixFloat32" || !Array.isArray(encoded.row_lengths)) { 302 return encoded; 303 } 304 305 const buffer = snapshot_base64_to_array_buffer(encoded.value); 306 const values = new Float32Array(buffer); 307 const matrix = []; 308 let offset = 0; 309 310 encoded.row_lengths.forEach(length => { 311 const safeLength = Math.max(0, Math.floor(Number(length) || 0)); 312 matrix.push(Array.from(values.subarray(offset, offset + safeLength))); 313 offset += safeLength; 314 }); 315 316 return matrix; 317 } 318 319 _export_vectors_for_snapshot(compactVectors = false) { 320 if (!compactVectors) return this.vectors; 321 322 return (Array.isArray(this.vectors) ? this.vectors : []).map(vectorLayer => 323 this._encode_numeric_matrix_for_snapshot(vectorLayer) 324 ); 325 } 326 327 _import_vectors_from_snapshot(vectors) { 328 return (Array.isArray(vectors) ? vectors : []).map(vectorLayer => 329 this._decode_numeric_matrix_from_snapshot(vectorLayer) 330 ); 331 } 332 333 _export_training_results_for_snapshot(options = {}) { 334 const includeVectorInputs = options.includeVectorInputs === true; 335 if (!this.training_data || typeof this.training_data !== "object" || Array.isArray(this.training_data)) { 336 return this.training_data || []; 337 } 338 339 const result = { ...this.training_data }; 340 if (!includeVectorInputs) { 341 [ 342 "X_all", 343 "X", 344 "X_unlabelled", 345 "x_all", 346 "x", 347 "x_unlabelled" 348 ].forEach(key => { 349 if (Object.prototype.hasOwnProperty.call(result, key)) { 350 delete result[key]; 351 } 352 }); 353 result.vector_inputs_omitted = true; 354 } 355 356 return result; 357 } 358
359 export_snapshot_state(options = {}) { 360 const includeModelCaches = options.includeModelCaches === true; 361 const compactVectors = options.compactVectors === true; 362 const includeVectors = options.includeVectors !== false; 363 const includeEntries = options.includeEntries !== false; 364 const includeTrainingVectorInputs = options.includeTrainingVectorInputs !== false; 365 366 return { 367 data_array: includeEntries ? this.data_array : [], 368 data_titles: [], 369 data_abstracts: [], 370 data_titles_and_abstracts: [], 371 data_labels: [], 372 data_ids: [], 373 data_array_missing: includeEntries ? this.data_array_missing : [], 374 tfidf_vectObj: includeModelCaches ? this.tfidf_vectObj : [], 375 lsa_vectObj: includeModelCaches ? this.lsa_vectObj : [], 376 doc2vec_tokens: includeModelCaches ? this.doc2vec_tokens : [], 377 doc2vec_vocab: includeModelCaches ? this.doc2vec_vocab : [], 378 doc2vec_vocab_freq: includeModelCaches ? this.doc2vec_vocab_freq : [], 379 doc2vec_model: includeModelCaches ? this.doc2vec_model : [], 380 vectors: includeVectors ? this._export_vectors_for_snapshot(compactVectors) : [], 381 vector_names: this.vector_names, 382 vector_config: this.vector_config, 383 vectors_compact: compactVectors, 384 training_data: this._export_training_results_for_snapshot({ 385 includeVectorInputs: includeTrainingVectorInputs 386 }), 387 prediction_data: this.prediction_data, 388 proba_threshold: this.proba_threshold, 389 prediction_label_mode: this.prediction_label_mode, 390 prediction_relevant_threshold: this.prediction_relevant_threshold, 391 prediction_irrelevant_threshold: this.prediction_irrelevant_threshold, 392 roc_curves: this.roc_curves, 393 roc_curve_mean: this.roc_curve_mean, 394 metrics: this.metrics, 395 metrics_stats: this.metrics_stats, 396 auc_roc: this.auc_roc, 397 auc_roc_stats: this.auc_roc_stats, 398 sorting_probabilities: this.sorting_probabilities, 399 similarity_array: this.similarity_array, 400 similarity_summary: this.similarity_summary, 401 currentSimIndex: this.currentSimIndex, 402 similarity_pairs_action_discarded: this.similarity_pairs_action_discarded, 403 similarity_array_discarded: this.similarity_array_discarded, 404 discarded_similarity_indexes: Array.from(this.discarded_similarity_indexes || []), 405 currentDiscardedIndex: this.currentDiscardedIndex 406 }; 407 } 408 409 import_snapshot_state(state) { 410 if (!state || typeof state !== "object") { 411 throw new Error("Invalid training snapshot data."); 412 } 413 414 this._restart_workers(); 415 416 this.data_array = Array.isArray(state.data_array) ? state.data_array : []; 417 this.data_array_missing = Array.isArray(state.data_array_missing) ? state.data_array_missing : []; 418 this._clear_cached_entry_fields(); 419 420 this.tfidf_vectObj = Array.isArray(state.tfidf_vectObj) ? state.tfidf_vectObj : []; 421 this.lsa_vectObj = Array.isArray(state.lsa_vectObj) ? state.lsa_vectObj : []; 422 this.doc2vec_tokens = Array.isArray(state.doc2vec_tokens) ? state.doc2vec_tokens : []; 423 this.doc2vec_vocab = Array.isArray(state.doc2vec_vocab) ? state.doc2vec_vocab : []; 424 this.doc2vec_vocab_freq = Array.isArray(state.doc2vec_vocab_freq) ? state.doc2vec_vocab_freq : []; 425 this.doc2vec_model = state.doc2vec_model || []; 426 427 this.vectors = this._import_vectors_from_snapshot(state.vectors); 428 this.vector_names = Array.isArray(state.vector_names) ? state.vector_names : []; 429 this.vector_config = Array.isArray(state.vector_config) ? state.vector_config : []; 430 this.training_data = state.training_data || []; 431 this.prediction_data = state.prediction_data || []; 432 this.proba_threshold = Number.isFinite(Number(state.proba_threshold)) ? Number(state.proba_threshold) : 0.5; 433 this.prediction_label_mode = state.prediction_label_mode === "confidence" ? "confidence" : "threshold"; 434 this.prediction_relevant_threshold = Number.isFinite(Number(state.prediction_relevant_threshold)) 435 ? Math.min(1, Math.max(0, Number(state.prediction_relevant_threshold))) 436 : 0.95; 437 this.prediction_irrelevant_threshold = Number.isFinite(Number(state.prediction_irrelevant_threshold)) 438 ? Math.min(1, Math.max(0, Number(state.prediction_irrelevant_threshold))) 439 : 0.05; 440 if (this.prediction_irrelevant_threshold >= this.prediction_relevant_threshold) { 441 this.prediction_irrelevant_threshold = 0.05; 442 this.prediction_relevant_threshold = 0.95; 443 } 444 this.roc_curves = Array.isArray(state.roc_curves) ? state.roc_curves : []; 445 this.roc_curve_mean = Array.isArray(state.roc_curve_mean) ? state.roc_curve_mean : []; 446 this.metrics = Array.isArray(state.metrics) ? state.metrics : []; 447 this.metrics_stats = Array.isArray(state.metrics_stats) ? state.metrics_stats : []; 448 this.auc_roc = Array.isArray(state.auc_roc) ? state.auc_roc : []; 449 this.auc_roc_stats = Array.isArray(state.auc_roc_stats) ? state.auc_roc_stats : []; 450 this.sorting_probabilities = Array.isArray(state.sorting_probabilities) ? state.sorting_probabilities : []; 451 452 this.similarity_array = Array.isArray(state.similarity_array) ? state.similarity_array : []; 453 this.similarity_summary = state.similarity_summary || null; 454 this.currentSimIndex = Math.min( 455 Math.max(Number.isInteger(Number(state.currentSimIndex)) ? Number(state.currentSimIndex) : 0, 0), 456 Math.max(this.similarity_array.length - 1, 0) 457 ); 458 this.similarity_pairs_action_discarded = Array.isArray(state.similarity_pairs_action_discarded) ? state.similarity_pairs_action_discarded
458: []; 459 this.similarity_array_discarded = Array.isArray(state.similarity_array_discarded) ? state.similarity_array_discarded : []; 460 this.discarded_similarity_indexes = new Set(Array.isArray(state.discarded_similarity_indexes) ? state.discarded_similarity_indexes : []); 461 this.currentDiscardedIndex = Math.min( 462 Math.max(Number.isInteger(Number(state.currentDiscardedIndex)) ? Number(state.currentDiscardedIndex) : 0, 0), 463 Math.max(this.similarity_pairs_action_discarded.length - 1, 0) 464 ); 465 } 466 467 restore_snapshot_progress(display_config = { model: 1, metric: "ACC" }) { 468 const vectorNamesEl = this.vect_names_id ? _(this.vect_names_id) : null; 469 const vectorRunningEl = this.vect_progress_id ? _(this.vect_progress_id) : null; 470 const vectorBarEl = this.vect_bar_progress_id ? _(this.vect_bar_progress_id) : null; 471 const trainingRunningEl = this.training_progress_id ? _(this.training_progress_id) : null; 472 const trainingBarEl = this.training_bar_progress_id ? _(this.training_bar_progress_id) : null; 473 const trainingMessageEl = this.training_message_id ? _(this.training_message_id) : null; 474 const hasVectors = Array.isArray(this.vectors) && this.vectors.length > 0; 475 const hasTraining = this._has_training_results(); 476 const hasPredictions = this._has_prediction_results(); 477 478 if (vectorNamesEl) { 479 vectorNamesEl.innerHTML = hasVectors ? this.vector_names.join("<br>") : ""; 480 } 481 if (vectorRunningEl) { 482 vectorRunningEl.innerHTML = hasVectors ? "Vectorization restored from snapshot." : "No vectorization running"; 483 } 484 if (vectorBarEl) { 485 const percent = hasVectors ? 100 : 0; 486 vectorBarEl.style.width = percent + "%"; 487 vectorBarEl.innerHTML = percent + "%"; 488 vectorBarEl.setAttribute("aria-valuenow", String(percent)); 489 } 490 491 if (trainingRunningEl) { 492 trainingRunningEl.innerHTML = hasTraining ? "Finished training" : "No training running"; 493 } 494 if (trainingBarEl) { 495 const percent = hasTraining ? 100 : 0; 496 trainingBarEl.style.width = percent + "%"; 497 trainingBarEl.innerHTML = percent + "%"; 498 trainingBarEl.setAttribute("aria-valuenow", String(percent)); 499 } 500 if (trainingMessageEl && hasTraining) { 501 trainingMessageEl.innerHTML = "Training results restored from snapshot."; 502 } 503 504 if (hasTraining) { 505 try { 506 if ((!Array.isArray(this.roc_curve_mean) || this.roc_curve_mean.length === 0) && this.training_data.ensemble_cv1_ypred_best_stacked) { 507 this.calculate_roc_curve(); 508 } 509 if (this.trainingChart1) { 510 this.update_training_progress_2(display_config); 511 } 512 if (this.trainingChart2 && this.display_3_id) { 513 this.report_training_results(); 514 } 515 } catch (error) { 516 console.warn("Training results were restored, but the charts could not be redrawn.", error); 517 } 518 } 519 520 if (hasPredictions && this.trainingChart3 && this.trainingChart4) { 521 try { 522 this.report_predicted_results(); 523 } catch (error) { 524 console.warn("Prediction results were restored, but the charts could not be redrawn.", error); 525 } 526 } 527 } 528 529 add_entries(data) { 530 for (var i = 0; i < data.length; i++) { 531 let entry = data[i]; 532 533 // Check for missing required fields 534 if (entry.title == null || entry.abstract == null || entry.paper_id == null) { 535 this.data_array_missing.push(entry); 536 } else { 537 // Ensure label is either 0 or 1; otherwise, set to null 538 if (entry.label !== 0 && entry.label !== 1) { 539 entry.label = null; 540 } 541 542 this.data_array.push(entry); 543 } 544 } 545 this._clear_cached_entry_fields(); 546 }; 547 548 //Add all entries without taking into account if they are in the selected_for_training or not. 549 add_entries_active(data){ 550 for (var i = 0; i < data.length; i++) { 551 if (data[i].title == null || data[i].abstract == null || data[i].paper_id == null) { 552 this.data_array_missing.push(data[i]); 553 } else { 554 this.data_array.push(data[i]); 555 } 556 } 557 this._clear_cached_entry_fields(); 558 }; 559 560 561 reset_entries_active(){ 562 this.data_array_missing = []; 563 this.data_array = []; 564 this.data_titles = []; 565 this.data_abstracts = []; 566 this.data_titles_and_abstracts = []; 567 this.data_labels = []; 568 this.data_ids = []; 569 570 }; 571 572 sync_entries_active(data) { 573 this.reset_entries_active(); 574 this.add_entries_active(Array.isArray(data) ? data : []); 575 } 576 577 _normalize_publication_indexes(indexes, expectedLength) {
578 return Array.from(new Set((Array.isArray(indexes) ? indexes : [indexes]) 579 .map(index => Math.floor(Number(index))) 580 .filter(index => Number.isFinite(index) && index >= 0 && index < expectedLength))) 581 .sort((a, b) => a - b); 582 } 583 584 _clear_model_cache_after_publication_change() { 585 this.tfidf_vectObj = []; 586 this.lsa_vectObj = []; 587 this.doc2vec_tokens = []; 588 this.doc2vec_vocab = []; 589 this.doc2vec_vocab_freq = []; 590 this.doc2vec_model = []; 591 this.training_data = []; 592 this.prediction_data = []; 593 this.metrics = []; 594 this.metrics_stats = []; 595 this.auc_roc = []; 596 this.auc_roc_stats = []; 597 this.roc_curves = []; 598 this.roc_curve_mean = []; 599 } 600 601 remove_entries_by_indexes(indexes, expectedLength = this.data_array.length) { 602 const oldLength = Math.max(0, Math.floor(Number(expectedLength) || 0)); 603 const removedIndexes = this._normalize_publication_indexes(indexes, oldLength); 604 if (removedIndexes.length === 0) { 605 return { oldLength, removedIndexes, vectorRows: [], probabilitiesWereAligned: false }; 606 } 607 608 const removed = new Set(removedIndexes); 609 const vectorRows = []; 610 const keptVectors = []; 611 const keptVectorNames = []; 612 const keptVectorConfig = []; 613 614 this.vectors.forEach((layer, layerIndex) => { 615 if (Array.isArray(layer) && layer.length === oldLength) { 616 vectorRows.push({ 617 layerIndex: keptVectors.length, 618 name: this.vector_names[layerIndex], 619 config: this.vector_config[layerIndex], 620 rows: new Map(removedIndexes.map(index => [index, layer[index]])) 621 }); 622 keptVectors.push(layer.filter((_, index) => !removed.has(index))); 623 keptVectorNames.push(this.vector_names[layerIndex]); 624 keptVectorConfig.push(this.vector_config[layerIndex]); 625 } 626 }); 627 628 this.vectors = keptVectors; 629 this.vector_names = keptVectorNames; 630 this.vector_config = keptVectorConfig; 631 632 const probabilitiesWereAligned = this.sorting_probabilities.length === oldLength; 633 const receipt = { 634 oldLength, 635 removedIndexes, 636 vectorRows, 637 probabilitiesWereAligned, 638 sortingProbabilities: probabilitiesWereAligned 639 ? new Map(removedIndexes.map(index => [index, this.sorting_probabilities[index]])) 640 : new Map() 641 }; 642 643 if (this.data_array.length === oldLength) { 644 this.data_array = this.data_array.filter((_, index) => !removed.has(index)); 645 this._clear_cached_entry_fields(); 646 } 647 648 this.sorting_probabilities = probabilitiesWereAligned 649 ? this.sorting_probabilities.filter((_, index) => !removed.has(index)) 650 : []; 651 this._clear_model_cache_after_publication_change(); 652 return receipt; 653 } 654 655 restore_entry_at_index(insertIndex, originalIndex, receipt) { 656 if (!receipt || !Array.isArray(receipt.vectorRows)) return; 657 658 receipt.vectorRows.forEach(snapshot => { 659 const row = snapshot.rows.get(originalIndex); 660 if (Array.isArray(this.vectors[snapshot.layerIndex]) && row !== undefined) { 661 this.vectors[snapshot.layerIndex].splice(insertIndex, 0, row); 662 } 663 }); 664 665 if ( 666 receipt.probabilitiesWereAligned && 667 receipt.sortingProbabilities.has(originalIndex) && 668 this.sorting_probabilities.length === this.data_array.length 669 ) { 670 this.sorting_probabilities.splice(insertIndex, 0, receipt.sortingProbabilities.get(originalIndex)); 671 } else if (this.sorting_probabilities.length > 0) { 672 this.sorting_probabilities = []; 673 } 674 675 this._clear_model_cache_after_publication_change(); 676 } 677 678 vectorize_data(methods, language, univBERT_config, tfidf_config, lsa_config, doc2vec_config, specter_config, sentence_transformers_config, sentence_transformers_api_config){ 679 680 681 // Chain promises for sequential execution 682 let promiseChainVect = Promise.resolve(); 683 684 //Determine the language 685 if (language == "english"){ 686 this.stopwords = this.stopwords_multilingual.en; 687 } else if (language == "multilingual"){ 688 this.stopwords = this.stopwords_multilingual.all; 689 }; 690 691 692 //univBERT 693 if (methods[0]) { 694 promiseChainVect = promiseChainVect.then(() => this.universalBERT_vectorize_webworker(univBERT_config)); 695 } 696 // TF-IDF 697 if (methods[1]) { 698 promiseChainVect = promiseChainVect.then(() => this.tfidf_vectorize_webworker(tfidf_config)); 699 } 700 // LSA 701 if (methods[2]) { 702 promiseChainVect = promiseChainVect.then(() => this.lsa_vectorize_webworker(tfidf_config, lsa_config)); 703 }; 704 // Doc2Vec 705 if (methods[3]) { 706 promiseChainVect = promiseChainVect.then(() => this.doc2vec_vectorize_webworker(doc2vec_config)); 707 }; 708 709 // SPECTER BERT 710 if (methods[4]) { 711 promiseChainVect = promiseChainVect.then(() => this.specter_vectorize_webworker(specter_config)); 712 }; 713 714 // Sentence transformers 715 if (methods[5]) { 716 promiseChainVect = promiseChainVect.then(() => this.sentence_transformers_vectorize_webworker(sentence_transformers_config)); 717 }; 718 719 // Sentence transformers 720 if (methods[6]) { 721 promiseChainVect = promiseChainVect.then(() => this.sentence_transformers_api_vectorize_webworker(sentence_transformers_api_config)); 722 }; 723 724 // Wait for all vectorizations to finish. 725 promiseChainVect.then(() => { 726 727 //Finish vectorization. 728 this.finish_bar_vectorization(); 729 730 });
731 732 // Return the promise chain in case any action happens after. 733 return promiseChainVect; 734 }; 735 736 737 738 tfidf_vectorize_webworker(tfidf_config, vect_graph){ 739 return new Promise((resolve, reject) => { 740 const vect_name = generateUniqueName('TF-IDF', this.vector_names); 741 const entryFields = this._build_entry_field_arrays({ text: true }); 742 const totalEntries = Math.max(1, entryFields.titlesAndAbstracts.length); 743 this.new_vectorization_message(vect_name); 744 let norm, analyzer; 745 if (tfidf_config[0]) { 746 norm = 'l2'; 747 } else { 748 norm = 'l1'; 749 }; 750 if (tfidf_config[1]) { 751 analyzer = 'word'; 752 } else { 753 analyzer = 'char'; 754 }; 755 756 this.worker.postMessage({ 757 method: 'tf-idf', 758 data: entryFields.titlesAndAbstracts, 759 tfidfConfig: tfidf_config, 760 norm: norm, 761 analyzer: analyzer, 762 stopwords: this.stopwords 763 }); 764 765 this.worker.onmessage = (event) => { 766 const { tfidfVectors, tfidfVectObj } = event.data; 767 this.tfidf_vectObj = tfidfVectObj; 768 this.vectors.push(tfidfVectors); 769 this.vector_names.push(vect_name);
770 this.vector_config.push(tfidf_config); 771 // Update graph. 772 this.update_bar_vectorization(tfidfVectors.length / totalEntries, vect_name); 773 resolve(); // Resolve the promise when done 774 }; 775 }); 776 }; 777 778 779 lsa_vectorize_webworker(tfidf_config, lsa_config){ 780 return new Promise((resolve, reject) => { 781 let norm, analyzer; 782 const vect_name = generateUniqueName('LSA', this.vector_names); 783 const entryFields = this._build_entry_field_arrays({ text: true }); 784 const totalEntries = Math.max(1, entryFields.titlesAndAbstracts.length); 785 this.new_vectorization_message(vect_name); 786 if (tfidf_config[0]) { 787 norm = 'l2'; 788 } else { 789 norm = 'l1'; 790 }; 791 if (tfidf_config[1]) { 792 analyzer = 'word'; 793 } else { 794 analyzer = 'char'; 795 }; 796 797 this.worker.postMessage({ 798 method: 'lsa', 799 data: entryFields.titlesAndAbstracts, 800 tfidfConfig: tfidf_config, 801 lsaConfig: lsa_config, 802 norm: norm, 803 analyzer: analyzer, 804 stopwords: this.stopwords 805 }); 806 807 this.worker.onmessage = (event) => { 808 const {lsaVectors, tfidfVectors, lsaObj, tfidfVectObj} = event.data; 809 this.lsa_vectObj = lsaObj; 810 this.vectors.push(lsaVectors); 811 this.vector_names.push(vect_name); 812 this.vector_config.push([tfidf_config, lsa_config]); 813 814 // Update graph. 815 this.update_bar_vectorization(lsaVectors.length / totalEntries, vect_name); 816 resolve(); // Resolve the promise when done 817 }; 818 }); 819 }; 820 821 universalBERT_vectorize_webworker(univBERT_config) { 822 return new Promise((resolve, reject) => { 823 // Clear the vectors. 824 825 let universalBERT_vectors = []; 826 const vect_name = generateUniqueName('univBERT', this.vector_names); 827 const entryFields = this._build_entry_field_arrays({ titles: true, abstracts: true, text: true }); 828 const totalEntries = Math.max(1, entryFields.titlesAndAbstracts.length); 829 this.new_vectorization_message(vect_name); 830 //Post task to webworker. 831 this.worker.postMessage({ 832 method: 'univBERT', 833 data_titles_and_abstracts: entryFields.titlesAndAbstracts, 834 data_titles: entryFields.titles, 835 data_abstracts: entryFields.abstracts, 836 univBERT_config: univBERT_config 837 }); 838 839 this.worker.onmessage = (event) => { 840 const {array} = event.data; 841 842 universalBERT_vectors.push(...array); 843 844 // Update graph. 845 this.update_bar_vectorization(universalBERT_vectors.length / totalEntries, vect_name); 846 // resolve promise if vectorisation is finished. 847 if (universalBERT_vectors.length == entryFields.titlesAndAbstracts.length){ 848 this.vectors.push(universalBERT_vectors);; 849 this.vector_names.push(vect_name); 850 this.vector_config.push(univBERT_config); 851 852 resolve(); 853 }; 854 }; 855 }); 856 }; 857 858 859 860 specter_vectorize_webworker(specter_config) { 861 return new Promise((resolve, reject) => { 862 //Clear the vector array. 863 this.specter_vectors = []; 864 let specter_vectors = []; 865 const vect_name = generateUniqueName('SPECTER', this.vector_names); 866 const totalEntries = Math.max(1, this.data_array.length); 867 this.new_vectorization_message(vect_name); 868 869 //Post task to webworker. 870 this.worker.postMessage({ 871 method: 'SPECTER', 872 data_array: this.data_array, 873 specter_config: specter_config 874 }); 875 876 this.worker.onmessage = (event) => { 877 const {array} = event.data; 878 specter_vectors.push(...array); 879 880 // Update graph. 881 this.update_bar_vectorization(specter_vectors.length / totalEntries, vect_name); 882 // resolve promise if vectorisation is finished. 883 if (specter_vectors.length == this.data_array.length){ 884 this.vectors.push(specter_vectors); 885 this.vector_names.push(vect_name); 886 this.vector_config.push(specter_config); 887 resolve(); 888 }; 889 }; 890 }); 891 }; 892 893 894 895 doc2vec_vectorize_webworker(doc2vec_config){ 896 return new Promise((resolve, reject) => { 897 //Reset vectors. 898 this.doc2vec_tokens = []; 899 this.doc2vec_vocab = []; 900 this.doc2vec_model = []; 901 const vect_name = generateUniqueName('Doc2Vec', this.vector_names); 902 const entryFields = this._build_entry_field_arrays({ text: true });
903 this.new_vectorization_message(vect_name); 904 //Post task to webworker. 905 this.worker.postMessage({ 906 method: 'Doc2Vec', 907 data_titles_and_abstracts: entryFields.titlesAndAbstracts, 908 doc2vec_config: doc2vec_config, 909 doc2vec_stopwords:this.stopwords 910 }); 911 912 this.worker.onmessage = (event) => { 913 if (event.data.type == 'doc2vec_progress'){ 914 // Update graph. 915 this.update_bar_vectorization((event.data.epoch+1)/doc2vec_config.epochs, vect_name); 916 } else if (event.data.type == 'doc2vec_final') { 917 this.doc2vec_tokens = event.data.doc2vec_tokens; 918 this.doc2vec_vocab = event.data.doc2vec_vocab; 919 this.doc2vec_vocab_freq = event.data.doc2vec_vocab_freq; 920 this.doc2vec_model = event.data.doc2vec_model; 921 this.vectors.push(event.data.doc2vec_vectors); 922 this.vector_names.push(vect_name); 923 this.vector_config.push(doc2vec_config); 924 resolve(); 925 }; 926 }; 927 }); 928 }; 929 930 931 sentence_transformers_vectorize_webworker(sentence_transformers_config) { 932 return new Promise((resolve, reject) => { 933 //Clear the vector array. 934 let sentence_transformers_vectors = []; 935 const vect_name = generateUniqueName('Transformers (js)', this.vector_names); 936 const entryFields = this._build_entry_field_arrays({ titles: true, abstracts: true, text: true }); 937 const totalEntries = Math.max(1, entryFields.titlesAndAbstracts.length); 938 this.new_vectorization_message(vect_name); 939 940 //Post task to webworker. 941 this.worker_sentence_transformers.postMessage({ 942 method: 'transformers.js', 943 data_titles_and_abstracts: entryFields.titlesAndAbstracts, 944 data_titles: entryFields.titles, 945 data_abstracts: entryFields.abstracts, 946 sentence_transformers_config:sentence_transformers_config 947 }); 948 949 this.worker_sentence_transformers.onmessage = (event) => { 950 const {array} = event.data; 951 sentence_transformers_vectors.push(...array); 952 953 // Update graph. 954 this.update_bar_vectorization(sentence_transformers_vectors.length / totalEntries, vect_name); 955 // resolve promise if vectorisation is finished. 956 if (sentence_transformers_vectors.length == entryFields.titlesAndAbstracts.length){ 957 this.vectors.push(convertTypedArrays(sentence_transformers_vectors)); 958 this.vector_names.push(vect_name); 959 this.vector_config.push(sentence_transformers_config); 960 resolve(); 961 }; 962 }; 963 }); 964 }; 965 966 sentence_transformers_api_vectorize_webworker(sentence_transformers_api_config) { 967 return new Promise((resolve, reject) => { 968 //Clear the vector array. 969 let sentence_transformers_api_vectors = []; 970 const vect_name = generateUniqueName('Transformers (API)', this.vector_names); 971 const entryFields = this._build_entry_field_arrays({ titles: true, abstracts: true, text: true }); 972 const totalEntries = Math.max(1, entryFields.titlesAndAbstracts.length); 973 this.new_vectorization_message(vect_name); 974 975 //Post task to webworker. 976 this.worker_sentence_transformers.postMessage({ 977 method: 'transformers.api', 978 data_titles_and_abstracts: entryFields.titlesAndAbstracts, 979 data_titles: entryFields.titles, 980 data_abstracts: entryFields.abstracts, 981 stopwords: this.stopwords, 982 sentence_transformers_api_config:sentence_transformers_api_config 983 }); 984 985 this.worker_sentence_transformers.onmessage = (event) => { 986 const {array} = event.data; 987 sentence_transformers_api_vectors.push(...array); 988 989 // Update graph. 990 this.update_bar_vectorization(sentence_transformers_api_vectors.length / totalEntries, vect_name); 991 // resolve promise if vectorisation is finished. 992 if (sentence_transformers_api_vectors.length == entryFields.titlesAndAbstracts.length){ 993 this.vectors.push(convertTypedArrays(sentence_transformers_api_vectors)); 994 this.vector_names.push(vect_name); 995 this.vector_config.push(sentence_transformers_api_config); 996 resolve(); 997 }; 998 }; 999 }); 1000 }; 1001 1002 1003 1004 1005 update_bar_vectorization(progress, vect_type) { 1006 // Add name to list
1007 const namesEl = this._get_element(this.vect_names_id); 1008 if (namesEl && !namesEl.innerHTML.includes(vect_type)) { 1009 namesEl.innerHTML += " <b>" + vect_type + "</b>"; 1010 } 1011 1012 // Add method being processed 1013 const progressEl = this._get_element(this.vect_progress_id); 1014 if (progressEl) { 1015 progressEl.innerHTML = "Performing <b>" + vect_type + "</b> vectorization."; 1016 } 1017 1018 // Update the bar 1019 this._set_progress_bar(this.vect_bar_progress_id, progress); 1020 }; 1021 1022 new_vectorization_message(vect_type) { 1023 const el = _(this.vect_progress_id); 1024 if (el) { 1025 el.innerHTML = "Initializing <b>" + vect_type + "</b> vectorization."; 1026 } 1027 }; 1028 1029 finish_bar_vectorization() { 1030 const el = _(this.vect_progress_id); 1031 if (el) { 1032 el.innerHTML = "Finished vectorization"; 1033 } 1034 }; 1035 1036 reset_vectorization() { 1037 const namesEl = _(this.vect_names_id); 1038 if (namesEl) namesEl.innerHTML = ''; 1039 1040 const progressEl = _(this.vect_progress_id); 1041 if (progressEl) progressEl.innerHTML = 'No vectorization running'; 1042 1043 const barEl = _(this.vect_bar_progress_id); 1044 if (barEl) { 1045 barEl.style.width = "0%"; 1046 barEl.innerHTML = "0%"; 1047 } 1048 1049 // Clear vector arrays 1050 this.vectors = []; 1051 this.vector_names = []; 1052 this.vector_config = []; 1053 } 1054 1055 1056 1057 1058 training(classif, classif_config, classifier_config_stacked, classif_opt, cv_config, ensemble_config, random_state, display_config){ 1059 //Check that at least 1 vectorization is on. 1060 if (this.vectors.length > 0) { 1061 this.train_models_webworker(classif, classif_config, classifier_config_stacked, classif_opt, cv_config, ensemble_config, random_state, display_config); 1062 }; 1063 }; 1064 1065 train_models_webworker(classif, classif_config, classif_config_stacked, classif_opt, cv_config, ensemble_config, random_state, display_config){ 1066 const entryFields = this._build_entry_field_arrays({ labels: true, ids: true }); 1067 1068 // Disable config panel. 1069 _(this.ml_config.window).disabled = true; 1070 1071 // Disable config panel. 1072 _(this.predictions_config.button).disabled = true; 1073 1074 //Post task to webworker. 1075 this.worker.postMessage({ 1076 method: 'Training', 1077 classif: classif, 1078 classif_config: classif_config, 1079 classif_config_stacked: classif_config_stacked, 1080 classif_opt: classif_opt, 1081 cv_config: cv_config, 1082 ensemble_config: ensemble_config, 1083 random_state: random_state, 1084 vectors: this.vectors, 1085 vector_names: this.vector_names, 1086 data_labels: entryFields.labels, 1087 data_ids: entryFields.ids 1088 }); 1089 1090 this.worker.onmessage = (event) => { 1091 if (event.data.type == 'training_config'){ 1092 this.update_display_config(event.data.training_descriptor); 1093 } else if (event.data.type == 'training_progress_1') { 1094 this.update_training_progress_1(cv_config, classif_opt, event.data.opt_idx, event.data.perm1, event.data.perm2, event.data.fold1, event.data.fold2, event.data.message); 1095 } else if (event.data.type == 'training_progress_2') { 1096 this.training_data = event.data.training_data; 1097 this.update_training_progress_2(display_config); 1098 } else if (event.data.type == 'training_progress_3') { 1099 this.update_training_progress_3(); 1100 } else if(event.data.type == 'training_progress_4') { 1101 this.update_training_progress_4(cv_config, event.data.perm1, event.data.fold1, event.data.message); 1102 } else if (event.data.type == 'training_final') { 1103 this.finish_training_progress(); 1104 //Get data from worker. 1105 this.training_data = event.data.training_data; 1106 1107 // Calculate roc curve. 1108 this.calculate_roc_curve(); 1109 1110 //Store the training threshold. 1111 this.proba_threshold = this.training_data.classif_config.threshold; 1112 //Report the training results. 1113 this.report_training_results(); 1114 1115 // Enable config panel. 1116 _(this.ml_config.window).disabled = false; 1117 1118 //Enable predictions. 1119 _(this.predictions_config.button).disabled = false; 1120 1121 //Predict unseen labels if available. 1122 if (this.training_data.y_unlabelled.length > 0){ 1123 this.predict_unseen_labels(); 1124 }; 1125 }; 1126 }; 1127 }; 1128 1129 //Update display with training information. 1130 update_display_config(training_descriptor){ 1131 _(this.training_message_id).innerHTML = training_descriptor; 1132 }; 1133 1134 1135 //Function to display the training performance. 1136 update_training_progress_1(cv_config, classif_opt, opt_idx, perm1, perm2, fold1, fold2, message){ 1137 //Print section of training. 1138 _(this.training_progress_id).innerHTML = "CV<sub>Outer</sub>
1138 [" + (perm1+1) + ", " + (fold1+1) + "], CV<sub>Inner</sub> [" + (perm2+1) + ", " + (fold2+1) + "], param: " + (opt_idx+1) + "/" +classif_opt.length + message; 1139 let total_steps = classif_opt.length * cv_config[0] * cv_config[1] * cv_config[2] * cv_config[3]; 1140 let done_steps = (opt_idx) * cv_config[0] * cv_config[1] * cv_config[2] * cv_config[3] + (perm1) * cv_config[1] * cv_config[2] * cv_config[3] + (fold1) * cv_config[2] * cv_config[3] + (perm2) * cv_config[3] + fold2 + 1; 1141 1142 //Update the bar. 1143 this._set_progress_bar(this.training_bar_progress_id, total_steps > 0 ? done_steps / total_steps : 0); 1144 }; 1145 1146 1147 update_training_progress_2(display_config){ 1148 1149 let cv1_plus = this.training_data.mean_cv1[display_config.model - 1].map((x, i) => 100*(x + this.training_data.std_cv1[0][i])); 1150 let cv1 = this.training_data.mean_cv1[display_config.model- 1].map(x => 100*x); 1151 let cv1_minus = this.training_data.mean_cv1[display_config.model- 1].map((x, i) => 100*(x - this.training_data.std_cv1[0][i])); 1152 1153 let cv2_plus = this.training_data.mean_cv2[display_config.model- 1].map((x, i) => 100*(x + this.training_data.std_cv2[0][i])); 1154 let cv2 = this.training_data.mean_cv2[display_config.model- 1].map(x => 100*x); 1155 let cv2_minus = this.training_data.mean_cv2[display_config.model- 1].map((x, i) => 100*(x - this.training_data.std_cv2[0][i])); 1156 1157 1158 // Clear existing data 1159 this.trainingChart1.data.datasets[0].data = []; 1160 this.trainingChart1.data.datasets[1].data = []; 1161 1162 //Change the data. 1163 this.trainingChart1.data.datasets[0].data = cv1_plus; 1164 this.trainingChart1.data.datasets[1].data = cv1; 1165 this.trainingChart1.data.datasets[2].data = cv1_minus; 1166 1167 this.trainingChart1.data.datasets[3].data = cv2_plus; 1168 this.trainingChart1.data.datasets[4].data = cv2; 1169 this.trainingChart1.data.datasets[5].data = cv2_minus; 1170 1171 1172 this.trainingChart1.data.labels = this.training_data.classif_opt; 1173 _(this.display_1_id).innerHTML = "CV performance vs. parameter of model with "+ this.training_data.X_names[display_config.model - 1]; 1174 1175 this.trainingChart1.options.scales.xAxes[0].ticks.callback = function(value, index, values){ 1176 return index; 1177 }; 1178 1179 this.trainingChart1.options.scales.yAxes[0].ticks.max = index_of_max(flatten([cv1_plus, cv2_plus]))[0] + 1; 1180 this.trainingChart1.options.scales.yAxes[0].ticks.min = index_of_min(flatten([cv1_minus, cv2_minus]))[0] - 1; 1181 this.trainingChart1.options.scales.yAxes[0].scaleLabel.labelString = display_config.metric + " (%)"; 1182 // Update the chart 1183 this.trainingChart1.update(); 1184 }; 1185 1186 update_training_progress_3(){ 1187 _(this.training_progress_id).innerHTML = "Finished model discovery, retraining with optimal parameters"; 1188 //Update the bar. 1189 _(this.training_bar_progress_id).style.width = "0%"; 1190 _(this.training_bar_progress_id).innerHTML = "0%"; 1191 } 1192 1193 update_training_progress_4(cv_config, perm1, fold1, message){ 1194 //Print section of training. 1195 _(this.training_progress_id).innerHTML = "CV<sub>Outer</sub> [" + (perm1+1) + ", " + (fold1+1) + "]" + message; 1196 let total_steps = cv_config[0] * cv_config[1]; 1197 let done_steps = (perm1) * cv_config[1] + (fold1) + 1; 1198 1199 //Update the bar. 1200 this._set_progress_bar(this.training_bar_progress_id, total_steps > 0 ? done_steps / total_steps : 0); 1201 }; 1202 1203 1204 reset_training(){ 1205 _(this.training_progress_id).innerHTML = "No training running"; 1206 _(this.training_bar_progress_id).style.width = "0%"; 1207 _(this.training_bar_progress_id).innerHTML = "0%"; 1208 _(this.training_message_id).innerHTML = ""; 1209 }; 1210 1211 report_training_results(){ 1212 //Calculate the metrics 1213 this.calculate_performance(); 1214 1215 const metric = index => Array.isArray(this.metrics_stats?.[index]) 1216 ? this.metrics_stats[index][0] 1217 : null; 1218 const setMetric = (suffix, html) => { 1219 const el = this._get_element(this.display_3_id + suffix); 1220 if (el) { 1221 el.innerHTML = html; 1222 } 1223 }; 1224 1225 //Report the metrics.
1226 setMetric("ACC", "<b>ACC</b>: " + this._format_percent(metric(1))); 1227 setMetric("BAC", "<b>BAC</b>: " + this._format_percent(metric(0))); 1228 setMetric("AUC", "<b>AUC</b>: " + this._format_number(this.auc_roc_stats?.[0])); 1229 setMetric("TPR/FPR", "<b>TPR/FPR</b>: " + this._format_percent(metric(2)) + "/" + this._format_percent(metric(3))); 1230 setMetric("TNR/FNR", "<b>TNR/FNR</b>: " + this._format_percent(metric(4)) + "/" + this._format_percent(metric(5))); 1231 setMetric("PPV/NPV", "<b>PPV/NPV</b>: " + this._format_percent(metric(6)) + "/" + this._format_percent(metric(7))); 1232 setMetric("F1", "<b>F1</b>: " + this._format_number(metric(8))); 1233 setMetric("MCC", "<b>MCC</b>: " + this._format_number(metric(9))); 1234 //Display the roc curve. 1235 this.display_roc_curve(); 1236 }; 1237 1238 calculate_performance(){ 1239 let y_pred = this.training_data.ensemble_cv1_ypred_best_stacked.map(x=>flatten(x).map(z=>Number(z>this.proba_threshold))); 1240 this.metrics = transpose(y_pred.map((iypred, index) => calculateMetrics(flatten(this.training_data.ensemble_cv1_y[index]), flatten(iypred)))); 1241 this.metrics_stats = this.metrics.map(x=>[average(x), std(x)]); 1242 this.auc_roc = this.roc_curves.map(x=>auc_roc(x)); 1243 this.auc_roc_stats = [average(this.auc_roc), std(this.auc_roc)]; 1244 }; 1245 1246 calculate_roc_curve(){ 1247 //Calculate the ROC curve. 1248 this.roc_curves = this.training_data.ensemble_cv1_ypred_best_stacked.map((x, index) => calculateROC(flatten(this.training_data.ensemble_cv1_y[index]), flatten(x))); 1249 this.roc_curve_mean = averageROC(this.roc_curves); 1250 }; 1251 1252 display_roc_curve(){ 1253 if (!this.trainingChart2?.data?.datasets || this.trainingChart2.data.datasets.length < 5) { 1254 return; 1255 } 1256 1257 const metric = index => { 1258 const rawValue = this.metrics_stats?.[index]?.[0]; 1259 if (rawValue === null || rawValue === undefined || rawValue === "") { 1260 return null; 1261 } 1262 1263 const value = Number(rawValue); 1264 return Number.isFinite(value) ? value : null; 1265 }; 1266 const tpr = metric(2); 1267 const fpr = metric(3); 1268 1269 // Add decision threshold line for TPR. 1270 this.trainingChart2.data.datasets[1].data = []; 1271 this.trainingChart2.data.datasets[1].data = tpr == null ? [] : [{x:0, y:tpr}, {x:1, y:tpr}]; 1272 1273 // Add decision threshold line for FPR. 1274 this.trainingChart2.data.datasets[2].data = []; 1275 this.trainingChart2.data.datasets[2].data = fpr == null ? [] : [{x:fpr, y:0}, {x:fpr, y:1}]; 1276 // Add threshold point. 1277 this.trainingChart2.data.datasets[3].data = []; 1278 this.trainingChart2.data.datasets[3].data = tpr == null || fpr == null ? [] : [{x:fpr, y:tpr}]; 1279 1280 //ROC curve. 1281 this.trainingChart2.data.datasets[4].data = []; 1282 this.trainingChart2.data.datasets[4].data = Array.isArray(this.roc_curve_mean) ? this.roc_curve_mean : []; 1283 1284 const displayEl = this._get_element(this.display_2_id); 1285 if (displayEl) { 1286 displayEl.innerHTML = "ROC curve"; 1287 } 1288 1289 // Update the chart 1290 this.trainingChart2.update(); 1291 }; 1292 1293 roc_curve_click(event, array, probability_input_id){ 1294 //Check if click is the ROC curve. 1295 if (array.length != 0){ 1296 if (array[0]._datasetIndex == 4){ 1297 1298 //Get probability. 1299 this.proba_threshold = this.roc_curve_mean[array[0]._index].probability; 1300 1301 //Change value in input. 1302 _(probability_input_id).value = this.proba_threshold; 1303 1304 //Report the results. 1305 this.report_training_results(); 1306 1307 //Predict unseen labels if available. 1308 if (this.training_data.y_unlabelled.length>0){ 1309 this.predict_unseen_labels(); 1310 }; 1311 }; 1312 } 1313 }; 1314 1315 doc_index_proba_click(event, array, text_ids){ 1316 let doc_data; 1317 //Check if click is in one of the documents. 1318 if (array.length != 0){ 1319 if (array[0]._datasetIndex == 0){ 1320 $('#'+text_ids.window).modal('show'); 1321 //Extract doc data. 1322 doc_data = this.data_array.filter((_,i) => this.training_data.prediction_mask[i])[array[0]._index]; 1323 _(text_ids.doi).innerHTML = 'DOI: <a href="https://doi.org/'+doc_data.paper_id+'" target="_blank">'+doc_data.paper_id+'</a>'; 1324 _(text_ids.title).innerHTML = "Title: "+doc_data.title; 1325 _(text_ids.abstract).innerHTML = "Abstract: "+doc_data.abstract; 1326 } 1327 } 1328 }; 1329 1330 probability_threshold_changed(probability){ 1331 const parsedProbability = Number(probability); 1332 this.proba_threshold = Number.isFinite(parsedProbability) 1333 ? Math.min(1, Math.max(0, parsedProbability)) 1334 : 0.5; 1335 1336 //Report the results. 1337 this.report_training_results(); 1338 1339 //Predict unseen labels if available. 1340 if (this.training_data.y_unlabelled.length>0){ 1341 if (this._has_prediction_results()) { 1342 this.refresh_prediction_labels(); 1343 } else { 1344 this.predict_unseen_labels(); 1345 } 1346 }; 1347 } 1348 1349 set_prediction_label_policy(mode, irrelevantThreshold, relevantThreshold){ 1350 const nextMode = mode === "confidence" ? "confidence" : "threshold"; 1351 const nextIrrelevant = Math.min(1, Math.max(0, Number(irrelevantThreshold))); 1352 const nextRelevant = Math.min(1, Math.max(0, Number(relevantThreshold))); 1353 1354 if (nextMode === "confidence" && 1355 (!Number.isFinite(nextIrrelevant) || !Number.isFinite(nextRelevant) || 1356 nextIrrelevant >= nextRelevant)) { 1357 throw new Error("The irrelevant confidence must be lower than the relevant confidence."); 1358 } 1359 1360 this.prediction_label_mode = nextMode; 1361 if (nextMode === "confidence") { 1362 this.prediction_irrelevant_threshold = nextIrrelevant; 1363 this.prediction_relevant_threshold = nextRelevant; 1364 } 1365 if (this._has_prediction_results()) this.refresh_prediction_labels(); 1366 } 1367 1368 build_prediction_labels(probabilities){ 1369 const safeProbabilities = Array.isArray(probabilities) ? probabilities : []; 1370 if (this.prediction_label_mode === "confidence") { 1371 return safeProbabilities.map(value => { 1372 const probability = Number(value); 1373 if (!Number.isFinite(probability)) return null; 1374 if (probability >= this.prediction_relevant_threshold) return 1; 1375 if (probability <= this.prediction_irrelevant_threshold) return 0; 1376 return null; 1377 }); 1378 } 1379 return safeProbabilities.map(value => Number(value) > this.proba_threshold ? 1 : 0); 1380 } 1381 1382 refresh_prediction_labels(){ 1383 if (!this.prediction_data || !Array.isArray(this.prediction_data.y_proba_stacked)) return; 1384 this.prediction_data.y_label_stacked = this.build_prediction_labels( 1385 this.prediction_data.y_proba_stacked 1386 ); 1387 this.report_predicted_results(); 1388 } 1389 1390 optimise_probability_theshold(parameter, proba_display){ 1391 //Record performances. 1392 let metric = null; 1393 let bestPerformance = -Infinity; 1394 let bestThreshold = null; 1395 1396 //Metrics 1397 if (parameter == "ACC"){ 1398 metric = 1; 1399 } else if (parameter == "BAC"){ 1400 metric = 0; 1401 } else if (parameter == "TPR"){ 1402 metric = 2; 1403 } else if (parameter == "TNR"){ 1404 metric = 4; 1405 } else if (parameter == "PPV"){ 1406 metric = 6; 1407 } else if (parameter == "NPV"){ 1408 metric = 7; 1409 } else if (parameter == "F1"){ 1410 metric = 8; 1411 } else if (parameter == "MCC"){ 1412 metric = 9; 1413 } else if (parameter == "P4"){ 1414 metric = 10; 1415 }; 1416 1417 this.roc_curve_mean.forEach(point => { 1418 //record the performance. 1419 this.proba_threshold = point.probability; 1420 this.calculate_performance(); 1421 1422 let currentPerformance = this.metrics_stats[metric][0]; 1423 1424 // Check if this is the best performance so far 1425 if (currentPerformance > bestPerformance) { 1426 bestPerformance = currentPerformance; 1427 bestThreshold = point.probability; 1428 } 1429 }); 1430 1431 //Show optimised probability 1432 this.probability_threshold_changed(bestThreshold); 1433 _(proba_display).value = bestThreshold; 1434 }; 1435 1436 1437 finish_training_progress(){ 1438 _(this.training_progress_id).innerHTML = "Finished training"; 1439 }; 1440 1441 stop_training(){ 1442 //Terminate the webworker process and iniciate again. 1443 this.worker.terminate(); 1444 this.worker_sentence_transformers.terminate(); 1445 1446 this.worker = new Worker(TRAINING_CLASS_WORKER_URL); 1447 this.worker_sentence_transformers = new Worker('oop/transformers_worker.js', {type: 'module'}); 1448 1449 // Enable the config panel again 1450 _(this.ml_config.window).disabled = false; 1451 1452 //Clear training data. 1453 this.training_data = []; 1454 1455 // Disable prediction button. 1456 _(this.predictions_config.button).disabled = true; 1457 1458 this.reset_training(); 1459 }; 1460 1461 stop_vectorization(){ 1462 //Terminate the webworker process and iniciate again. 1463 this.worker.terminate(); 1464 this.worker_sentence_transformers.terminate(); 1465 1466 this.worker = new Worker(TRAINING_CLASS_WORKER_URL); 1467 this.worker_sentence_transformers = new Worker('oop/transformers_worker.js', {type: 'module'}); 1468 1469 // Reset vectorization bar. 1470 this.reset_vectorization(); 1471 1472 // Enable config panel. 1473 _(this.ml_config.window).disabled = false; 1474 1475 // Remove all vectors. 1476 this.vectors = []; 1477 this.vector_names = []; 1478 this.vector_config = []; 1479 }; 1480 1481 predict_unseen_labels(){ 1482 //Check that vectorization and model training has been performed. 1483 if (this.vectors.length != 0) { 1484 if (this.training_data.length != 0){ 1485 this.predict_unseen_labels_webworker(); 1486 } 1487 }; 1488 }; 1489 1490 1491 predict_unseen_labels_webworker(){ 1492 this.worker.postMessage({ 1493 method: 'Prediction', 1494 tr_res: this.training_data, 1495 proba_threshold: this.proba_threshold 1496 }); 1497 1498 this.worker.onmessage = (event) => { 1499 if (event.data.type == 'prediction_final'){ 1500 1501 //Store predicted labels. 1502 this.prediction_data = event.data.prediction_data; 1503 this.prediction_data.y_label_stacked = this.build_prediction_labels( 1504 this.prediction_data.y_proba_stacked 1505 ); 1506 1507 //Report the results in the chart. 1508 this.report_predicted_results(); 1509 }; 1510 }; 1511 }; 1512 1513 1514 report_predicted_results(){ 1515 //Chart3 update. 1516 let rel_percentage = countOccurrences(this.prediction_data.y_label_stacked, 1); 1517 let irrel_percentage = countOccurrences(this.prediction_data.y_label_stacked, 0); 1518 let unlabelled_percentage = this.prediction_data.y_label_stacked.length - rel_percentage - irrel_percentage; 1519 let prediction_mask = this.training_data.prediction_mask; 1520 this.trainingChart3.data.labels = ["Relevant", "Irrelevant", "Left unlabelled"]; 1521 this.trainingChart3.data.datasets[0].backgroundColor = ["#4CAF50", "#FF5252", "#9E9E9E"]; 1522 this.trainingChart3.data.datasets[0].data = [rel_percentage, irrel_percentage, unlabelled_percentage]; 1523 1524 // Update the chart
1525 this.trainingChart3.update(); 1526 _(this.display_4_id).innerHTML = "Classification of unlabelled entries"; 1527 1528 //Chart4 update. 1529 // Add threshold line. 1530 const entryFields = this._build_entry_field_arrays({ ids: true }); 1531 this.trainingChart4.options.lineAt = this.prediction_label_mode === "confidence" 1532 ? [this.prediction_irrelevant_threshold, this.prediction_relevant_threshold] 1533 : [this.proba_threshold]; 1534 this.trainingChart4.options.scales.xAxes[0].ticks.callback = function(value, index, values){ 1535 return index; 1536 }; 1537 this.trainingChart4.data.datasets[0].data = this.prediction_data.y_proba_stacked; 1538 this.trainingChart4.data.labels = entryFields.ids.filter((_,i) => prediction_mask[i]); 1539 this.trainingChart4.data.datasets[0].backgroundColor = this.prediction_data.y_label_stacked.map(x => { 1540 if (x == 0) { 1541 return "#FF5252"; 1542 } else if (x == 1) { 1543 return "#4CAF50"; 1544 } 1545 return "#9E9E9E"; 1546 }); 1547 1548 this.trainingChart4.update(); 1549 1550 _(this.display_5_id).innerHTML = "Relevance probability of entries"; 1551 }; 1552 1553 1554 save_predicted_results(filename) { 1555 const wb = XLSX.utils.book_new(); 1556 1557 try { 1558 // --- Prepare Data --- 1559 const prediction_mask = this.training_data.prediction_mask; 1560 const training_mask = this.training_data.training_mask; 1561 1562 const data_prediction = this.data_array.filter((_, i) => prediction_mask[i]); 1563 const data_training = this.data_array.filter((_, i) => training_mask[i]); 1564 1565 const label = copy_array(this.prediction_data.y_label_stacked); 1566 const probability = copy_array(this.prediction_data.y_proba_stacked); 1567 1568 // --- Sheet 1: All --- 1569 let counter = 0; 1570 const data_all = this.data_array.map((entry, index) => { 1571 const newEntry = { ...entry }; 1572 if (prediction_mask[index]) { 1573 newEntry.label = label[counter]; 1574 counter++; 1575 } 1576 return newEntry; 1577 }); 1578 XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(data_all), "All"); 1579 1580 // --- Sheet 2: Training --- 1581 XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(data_training), "Training"); 1582 1583 // --- Sheet 3: Predictions --- 1584 const dataWithLabels = data_prediction.map((entry, index) => ({ 1585 ...entry, 1586 label: label[index], 1587 probability: probability[index] 1588 })); 1589 XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(dataWithLabels), "Predictions"); 1590 1591 // --- Try Excel Export --- 1592 XLSX.writeFile(wb, filename); 1593 1594 } catch (error) { 1595 alert('â ï¸ Length of text is longer than maximum possible in XLSX, falling back to CSV.'); 1596 const baseName = filename.replace(/\.[^/.]+$/, ""); // remove extension 1597 // Fallback: save as UTF-8 CSV files 1598 1599 // Rebuild data since we may be outside try block 1600 const prediction_mask = this.training_data.prediction_mask; 1601 const training_mask = this.training_data.training_mask; 1602 1603 const data_prediction = this.data_array.filter((_, i) => prediction_mask[i]); 1604 const data_training = this.data_array.filter((_, i) => training_mask[i]); 1605 1606 const label = copy_array(this.prediction_data.y_label_stacked); 1607 const probability = copy_array(this.prediction_data.y_proba_stacked); 1608 1609 // All entries 1610 let counter = 0; 1611 const data_all = this.data_array.map((entry, index) => { 1612 const newEntry = { ...entry }; 1613 if (prediction_mask[index]) { 1614 newEntry.label = label[counter]; 1615 counter++; 1616 } 1617 return newEntry; 1618 }); 1619 1620 // Predictions 1621 const dataWithLabels = data_prediction.map((entry, index) => ({ 1622 ...entry, 1623 label: label[index], 1624 probability: probability[index] 1625 })); 1626 1627 // Save CSVs 1628 saveCSV(data_all, baseName, "All"); 1629 saveCSV(data_training, baseName, "Training"); 1630 saveCSV(dataWithLabels, baseName, "Predictions"); 1631 } 1632 }; 1633 1634 export_ris(filename, option = "relevant") { 1635 1636 // --------------------------------------- 1637 // Rebuild "All" data exactly as in export 1638 // --------------------------------------- 1639 const prediction_mask = this.training_data.prediction_mask; 1640 const training_mask = this.training_data.training_mask; // kept for parity 1641 1642 const label = copy_array(this.prediction_data.y_label_stacked); 1643 const probability = copy_array(this.prediction_data.y_proba_stacked); 1644 1645 let counter = 0; 1646 const data_all = this.data_array.map((entry, index) => { 1647 const newEntry = { ...entry }; 1648 if (prediction_mask[index]) { 1649 newEntry.label = label[counter]; 1650 newEntry.probability = probability[counter]; 1651 counter++; 1652 } 1653 return newEntry; 1654 }); 1655 1656 // --------------------------------------- 1657 // Filter entries 1658 // --------------------------------------- 1659 let entries; 1660 if (option === "relevant") {
1661 entries = data_all.filter(entry => entry.label === 1); 1662 } else if (option === "irrelevant") { 1663 entries = data_all.filter(entry => entry.label === 0); 1664 } else if (option === "all") { 1665 entries = data_all; 1666 } else { 1667 return; 1668 } 1669 1670 if (!entries.length) return; 1671 1672 const escapeRIS = (text) => 1673 text ? text.replace(/\r?\n/g, ' ').replace(/\t/g, ' ').trim() : ''; 1674 1675 // --------------------------------------- 1676 // Build RIS content (standard order) 1677 // --------------------------------------- 1678 const risContent = entries.map(entry => { 1679 let risEntry = ''; 1680 1681 // 1ï¸â£ Type 1682 risEntry += `TY - JOUR\n`; 1683 1684 // 2ï¸â£ Title 1685 if (entry.title) { 1686 risEntry += `TI - ${escapeRIS(entry.title)}\n`; 1687 } 1688 1689 // 3ï¸â£ Authors 1690 if (entry.authors) { 1691 entry.authors 1692 .split(';') 1693 .map(a => a.trim()) 1694 .filter(Boolean) 1695 .forEach(author => { 1696 risEntry += `AU - ${escapeRIS(author)}\n`; 1697 }); 1698 } 1699 1700 // 4ï¸â£ Year 1701 if (entry.year) { 1702 risEntry += `PY - ${escapeRIS(entry.year)}\n`; 1703 } 1704 1705 // 5ï¸â£ Journal 1706 if (entry.journal) { 1707 risEntry += `T2 - ${escapeRIS(entry.journal)}\n`; 1708 } 1709 1710 // 6ï¸â£ Abstract 1711 if (entry.abstract) { 1712 risEntry += `AB - ${escapeRIS(entry.abstract)}\n`; 1713 } 1714 1715 // 7ï¸â£ Keywords 1716 if (entry.keywords) { 1717 entry.keywords 1718 .split(';') 1719 .map(k => k.trim()) 1720 .filter(Boolean) 1721 .forEach(keyword => { 1722 risEntry += `KW - ${escapeRIS(keyword)}\n`; 1723 }); 1724 } 1725 1726 // 8ï¸â£ DOI or ID 1727 if (entry.paper_id != null) { 1728 const paperIdStr = String(entry.paper_id); 1729 if (paperIdStr.includes('/')) { 1730 risEntry += `DO - ${escapeRIS(paperIdStr)}\n`; 1731 } else { 1732 risEntry += `ID - ${escapeRIS(paperIdStr)}\n`; 1733 } 1734 } 1735 1736 // 9ï¸â£ Notes + label (+ probability if present) 1737 let noteText = entry.notes ? escapeRIS(entry.notes) + ' ' : ''; 1738 noteText += `Label: ${entry.label === 1 ? 'included' : 'excluded'}`; 1739 1740 if (entry.probability != null) { 1741 noteText += ` | Probability: ${entry.probability}`; 1742 } 1743 1744 risEntry += `N1 - ${noteText}\n`; 1745 1746 // ð End record 1747 risEntry += `ER - \n`; 1748 1749 return risEntry; 1750 }).join('\n'); 1751 1752 // --------------------------------------- 1753 // Download RIS file 1754 // --------------------------------------- 1755 const blob = new Blob([risContent], { 1756 type: "application/x-research-info-systems" 1757 }); 1758 1759 const link = document.createElement("a"); 1760 const downloadUrl = URL.createObjectURL(blob); 1761 link.href = downloadUrl; 1762 link.download = filename.endsWith('.ris') ? filename : filename + '.ris'; 1763 document.body.appendChild(link); 1764 link.click(); 1765 document.body.removeChild(link); 1766 setTimeout(() => URL.revokeObjectURL(downloadUrl), 1000); 1767 } 1768 1769 1770 1771 1772 save_performance_metrics(filename){ 1773 let metrics = deep_copy_array(this.metrics); 1774 let metrics_stats = deep_copy_array(this.metrics_stats); 1775 1776 //Add auc roc. 1777 metrics.push(deep_copy_array(this.auc_roc)); 1778 metrics_stats.push(deep_copy_array(this.auc_roc_stats)); 1779 1780 //Add threshold to metrics. 1781 metrics 1782 1783 //transpose 1784 metrics = transpose(metrics); 1785 metrics_stats = transpose(metrics_stats); 1786 1787 let index_array = metrics.map((x,i)=>'Perm '+ i).concat('Avg.', 'Std'); 1788 index_array.unshift(''); 1789 let names_array = ['BAC', 'ACC', 'TPR', 'FPR', 'TNR', 'FNR', 'PPV', 'NPV', 'F1', 'MCC','P4', 'TP', 'TN', 'FP', 'FN', 'AUC']; 1790 1791 1792 1793 metrics.unshift(names_array); 1794 //Push avg. and std 1795 metrics.push(metrics_stats[0]); 1796 metrics.push(metrics_stats[1]); 1797 1798 //Add index. 1799 let table = metrics.map((x,i)=>[index_array[i], ...x]); 1800 let aoa = XLSX.utils.aoa_to_sheet(table); 1801 const wb = XLSX.utils.book_new(); 1802 XLSX.utils.book_append_sheet(wb, aoa, 'Metrics'); 1803 1804 // Write to file 1805 XLSX.writeFile(wb, filename); 1806 1807 }; 1808 1809 save_roc_curve(filename){ 1810 // Prepare the data for export 1811 let rocData = []; 1812 1813 // Add the ROC curve data to the array 1814 this.roc_curve_mean.forEach(point => { 1815 rocData.push([point.x, point.y, point.probability]); 1816 }); 1817 1818 //Add prob. Threshold 1819 rocData[0].push(this.proba_threshold); 1820 1821 rocData.unshift(['False Positive Rate', 'True Positive Rate', 'Probability/Score', 'Threshold']); 1822 let aoa = XLSX.utils.aoa_to_sheet(rocData); 1823 const wb = XLSX.utils.book_new(); 1824 XLSX.utils.book_append_sheet(wb, aoa, 'Mean ROC'); 1825 1826 // Write to file 1827 XLSX.writeFile(wb, filename); 1828 }; 1829 1830 1831 //Save the vectors to excel. 1832 save_vectors(filename){ 1833 const wb = XLSX.utils.book_new(); 1834 let aoa, name_array; 1835 1836 for (i = 0; i<this.vectors.length;++i){ 1837 aoa = deep_copy_2d_array(this.vectors[i]); 1838 aoa = XLSX.utils.aoa_to_sheet(aoa); 1839 XLSX.utils.book_append_sheet(wb, aoa, this.vector_names[i]); 1840 }; 1841 1842 //Write the excel. 1843 XLSX.writeFile(wb, filename); 1844 }; 1845 1846 save_vectors_csv() { 1847 for (let i = 0; i < this.vectors.length; ++i) { 1848 const vectorData = this.vectors[i]; 1849 const vectorName = this.vector_names[i]; 1850 1851 // Convert the 2D array to CSV format 1852 const csvContent = vectorData.map(row => row.join(',')).join('\n'); 1853 1854 // Create a Blob from the CSV content 1855 const blob = new Blob([csvContent], { type:'text/csv'}); 1856 1857 // Create a link element for downloading the file 1858 const link = document.createElement('a'); 1859 const downloadUrl = URL.createObjectURL(blob); 1860 link.href = downloadUrl; 1861 link.download = `${vectorName}.csv`; 1862 1863 // Append the link, trigger download, and remove the link 1864 document.body.appendChild(link); 1865 link.click(); 1866 document.body.removeChild(link); 1867 setTimeout(() => URL.revokeObjectURL(downloadUrl), 1000); 1868 } 1869 }; 1870 1871 1872 load_vectors(data, names, progress_id, warning_id){ 1873 let status = ''; 1874 let warning_status = ''; 1875 for (var i = 0; i<names.length;++i){ 1876 if (data[i].length == this.data_array.length){ 1877 this.vectors.push(data[i]); 1878 this.vector_names.push(names[i]); 1879 status = status + ' ' + names[i]; 1880 } else { 1881 warning_status = warning_status + ' ' + names[i]; 1882 }; 1883 }; 1884 //Print message in data loading. 1885 _(progress_id).innerHTML = status + " vectors loaded"; 1886 if (warning_status != ''){_(warning_id).innerHTML = '<b>Warning</b>: '+ warning_status + ' not loaded due to size mismatch with previously loaded entries.'}; 1887 }; 1888 1889 1890 //Active training function. 1891 active_training(classif, classif_config, classif_config_stacked, ensemble_config, random_state){ 1892 1893 let promiseChainTrain = Promise.resolve(); 1894 1895 if (this.vectors.length > 0) { 1896 promiseChainTrain = promiseChainTrain.then(() => this.active_training_model_webworker(classif, classif_config, classif_config_stacked, ensemble_config, random_state)) 1897 1898 }; 1899 1900 return promiseChainTrain; 1901 }; 1902 1903 active_training_model_webworker(classif, classif_config, classif_config_stacked, ensemble_config, random_state){ 1904 return new Promise((resolve, reject) => { 1905 const entryFields = this._build_entry_field_arrays({ labels: true, ids: true });
1906 //Post task to webworker. 1907 this.worker.postMessage({ 1908 method: 'ActiveTraining', 1909 classif: classif, 1910 classif_config: classif_config, 1911 classif_config_stacked: classif_config_stacked, 1912 ensemble_config: ensemble_config, 1913 random_state: random_state, 1914 vectors: this.vectors, 1915 vector_names: this.vector_names, 1916 data_labels: entryFields.labels, 1917 data_ids: entryFields.ids 1918 }); 1919 1920 this.worker.onmessage = (event) => { 1921 if (event.data.type == 'active_prediction'){ 1922 this.sorting_probabilities = event.data.prediction_data; 1923 resolve(); 1924 } 1925 }; 1926 }); 1927 }; 1928 1929 1930 calculate_similarity_scores(option, language, display, scanMode = "fuzzy", comparisonText = "all", samplePercentage = 100) { 1931 const entryFields = this._build_entry_field_arrays({ titles: true, abstracts: true, ids: true }); 1932 1933 //Determine the language 1934 if (language == "english"){ 1935 this.stopwords = this.stopwords_multilingual.en; 1936 } else if (language == "multilingual"){ 1937 this.stopwords = this.stopwords_multilingual.all; 1938 }; 1939 1940 1941 return new Promise((resolve, reject) => { 1942 //Post task to webworker. 1943 this.worker.postMessage({ 1944 method: 'SimilarityMeasures', 1945 data_titles: entryFields.titles, 1946 data_abstracts: entryFields.abstracts, 1947 data_ids: entryFields.ids, 1948 stopwords: this.stopwords, 1949 option: option, 1950 scan_mode: scanMode, 1951 comparison_text: comparisonText, 1952 sample_percentage: samplePercentage 1953 }); 1954 1955 this.worker.onmessage = (event) => { 1956 if (event.data.type == 'progress'){ 1957 const workerPercentage = Number(event.data.progress); 1958 const currentEntry = Number(event.data.i); 1959 const fallbackPercentage = Number.isFinite(currentEntry) 1960 ? 100 * (currentEntry + 1) / Math.max(1, this.data_array.length) 1961 : 0; 1962 const percentage = Math.min( 1963 100, 1964 Math.max(0, Number.isFinite(workerPercentage) ? workerPercentage : fallbackPercentage) 1965 ); 1966 _(display).textContent = event.data.message || `Progress: ${percentage.toFixed(2)}%`; 1967 1968 } else if (event.data.type == 'final'){ 1969 this.similarity_array = event.data.similarity_array; 1970 this.similarity_summary = event.data.summary || null; 1971 this.similarity_pairs_action_discarded = []; 1972 this.similarity_array_discarded = []; 1973 this.discarded_similarity_indexes = new Set(); 1974 this.currentSimIndex = 0; 1975 this.currentDiscardedIndex = 0; 1976 resolve(); 1977 }; 1978 }; 1979 this.worker.onerror = event => { 1980 reject(new Error(event.message || "Similarity calculation failed.")); 1981 }; 1982 }); 1983 }; 1984 1985 sort_similarity_scores(sort_method) { 1986 if (!this.similarity_array || this.similarity_array.length === 0) return; 1987 1988 this.similarity_array.sort((a, b) => { 1989 let aSim, bSim; 1990 1991 switch (sort_method) { 1992 case 'title_similarity': 1993 aSim = (typeof a.titleSimilarity === 'number' && !isNaN(a.titleSimilarity)) ? a.titleSimilarity : -Infinity; 1994 bSim = (typeof b.titleSimilarity === 'number' && !isNaN(b.titleSimilarity)) ? b.titleSimilarity : -Infinity; 1995 break; 1996 1997 case 'abstract_similarity': 1998 aSim = (typeof a.abstractSimilarity === 'number' && !isNaN(a.abstractSimilarity)) ? a.abstractSimilarity : -Infinity; 1999 bSim = (typeof b.abstractSimilarity === 'number' && !isNaN(b.abstractSimilarity)) ? b.abstractSimilarity : -Infinity; 2000 break; 2001 2002 case 'mean_similarity': 2003 default: 2004 aSim = (typeof a.meanSimilarity === 'number' && !isNaN(a.meanSimilarity)) ? a.meanSimilarity : -Infinity; 2005 bSim = (typeof b.meanSimilarity === 'number' && !isNaN(b.meanSimilarity)) ? b.meanSimilarity : -Infinity; 2006 break; 2007 } 2008 2009 return bSim - aSim; // descending order 2010 });
2011 } 2012 2013 discard_similarity_indexes(indexes) { 2014 if (!this.similarity_array || this.similarity_array.length === 0) return; 2015 2016 const discardedIndexes = new Set((Array.isArray(indexes) ? indexes : [indexes]) 2017 .map(index => Math.floor(Number(index))) 2018 .filter(index => Number.isFinite(index) && index >= 0 && !this.discarded_similarity_indexes.has(index))); 2019 if (discardedIndexes.size === 0) return; 2020 2021 discardedIndexes.forEach(index => { 2022 const representativePair = this.similarity_array.find(pair => pair.indexA === index || pair.indexB === index); 2023 if (!representativePair) return; 2024 2025 this.similarity_pairs_action_discarded.push({ 2026 discardedIndex: index, 2027 discardedType: representativePair.indexA === index ? "A" : "B", 2028 pair: { ...representativePair } 2029 }); 2030 this.discarded_similarity_indexes.add(index); 2031 }); 2032 2033 const discardedPairs = this.similarity_array.filter(pair => 2034 discardedIndexes.has(pair.indexA) || discardedIndexes.has(pair.indexB) 2035 ); 2036 const discardedPairKeys = new Set(this.similarity_array_discarded.map(pair => `${pair.indexA}:${pair.indexB}`)); 2037 discardedPairs.forEach(pair => { 2038 const key = `${pair.indexA}:${pair.indexB}`; 2039 if (!discardedPairKeys.has(key)) { 2040 this.similarity_array_discarded.push(pair); 2041 discardedPairKeys.add(key); 2042 } 2043 }); 2044 this.similarity_array = this.similarity_array.filter(pair => 2045 !discardedIndexes.has(pair.indexA) && !discardedIndexes.has(pair.indexB) 2046 ); 2047 this.currentSimIndex = Math.min(this.currentSimIndex, Math.max(this.similarity_array.length - 1, 0)); 2048 } 2049 2050 restore_similarity_action() { 2051 if (!this.similarity_pairs_action_discarded || this.similarity_pairs_action_discarded.length === 0) return null; 2052 2053 const discardedEntry = this.similarity_pairs_action_discarded[this.currentDiscardedIndex]; 2054 if (!discardedEntry) return null; 2055 2056 const discardedIndex = discardedEntry.discardedIndex; 2057 this.discarded_similarity_indexes.delete(discardedIndex); 2058 const pairsToRestore = this.similarity_array_discarded.filter(pair => 2059 (pair.indexA === discardedIndex || pair.indexB === discardedIndex) && 2060 !this.discarded_similarity_indexes.has(pair.indexA) && 2061 !this.discarded_similarity_indexes.has(pair.indexB) 2062 ); 2063 const activePairKeys = new Set(this.similarity_array.map(pair => `${pair.indexA}:${pair.indexB}`)); 2064 pairsToRestore.forEach(pair => { 2065 const key = `${pair.indexA}:${pair.indexB}`; 2066 if (!activePairKeys.has(key)) { 2067 this.similarity_array.push(pair); 2068 activePairKeys.add(key); 2069 } 2070 }); 2071 2072 const restoredPairKeys = new Set(pairsToRestore.map(pair => `${pair.indexA}:${pair.indexB}`)); 2073 this.similarity_array_discarded = this.similarity_array_discarded.filter(pair => 2074 !restoredPairKeys.has(`${pair.indexA}:${pair.indexB}`) 2075 ); 2076 this.similarity_pairs_action_discarded.splice(this.currentDiscardedIndex, 1); 2077 this.currentDiscardedIndex = Math.min( 2078 this.currentDiscardedIndex, 2079 Math.max(this.similarity_pairs_action_discarded.length - 1, 0) 2080 ); 2081 return discardedEntry; 2082 } 2083 2084 2085 remove_similarity_pairs_with_index(type) { 2086 if (!this.similarity_array || this.similarity_array.length === 0) return; 2087 2088 // --- Identify the index being discarded --- 2089 const currentPair = this.similarity_array[similarity_data_active.currentSimIndex]; 2090 let index; 2091 if (type === "A") { 2092 index = currentPair.indexA; 2093 } else if (type === "B") { 2094 index = currentPair.indexB; 2095 } else { 2096 return; // Invalid type 2097 } 2098 2099 // --- 1ï¸â£ Store the specific pair and which option was discarded --- 2100 this.similarity_pairs_action_discarded.push({ 2101 discardedIndex: index, 2102 discardedType: type, 2103 pair: { ...currentPair} 2104 }); 2105 2106 // --- 2ï¸â£ Identify and store all pairs that will be discarded as a result --- 2107 const discardedPairs = this.similarity_array.filter(pair => 2108 pair.indexA === index || pair.indexB === index 2109 ); 2110 2111 this.similarity_array_discarded.push(...discardedPairs); 2112 2113 // --- Filter out the removed pairs from active list --- 2114 this.similarity_array = this.similarity_array.filter(pair => 2115 pair.indexA !== index && pair.indexB !== index 2116 ); 2117 2118 // --- Adjust currentSimIndex safely --- 2119 if (this.similarity_array.length > 0) { 2120 if (this.currentSimIndex >= this.similarity_array.length) { 2121 this.currentSimIndex = this.similarity_array.length - 1; 2122 } 2123 } else { 2124 this.currentSimIndex = 0; // reset safely if all are removed 2125 } 2126 } 2127 2128 2129 add_similarity_pairs_with_index() { 2130 if (!this.similarity_pairs_action_discarded || this.similarity_pairs_action_discarded.length === 0) return; 2131 2132 const discardedEntry = similarity_data_active.similarity_pairs_action_discarded[similarity_data_active.currentDiscardedIndex]; 2133 if (!discardedEntry) return; 2134 2135 const discardedIndex = discardedEntry.discardedIndex; 2136 2137 // 1 Find all discarded pairs related to this index 2138 const pairsToRestore = this.similarity_array_discarded.filter(pair => 2139 pair.indexA === discardedIndex || pair.indexB === discardedIndex 2140 ); 2141 2142 if (pairsToRestore.length === 0) return; 2143 2144 // 2 Add them back to the active similarity array 2145 this.similarity_array.push(...pairsToRestore); 2146 2147 // 3 Remove those restored pairs from discarded list 2148 this.similarity_array_discarded = this.similarity_array_discarded.filter(pair => 2149 pair.indexA !== discardedIndex && pair.indexB !== discardedIndex 2150 ); 2151 2152 // 4 Remove the entry from action_discarded 2153 this.similarity_pairs_action_discarded.splice(similarity_data_active.currentDiscardedIndex, 1); 2154 2155 // 5 Adjust index safely 2156 if (this.similarity_pairs_action_discarded.length > 0) { 2157 if (similarity_data_active.currentDiscardedIndex >= this.similarity_pairs_action_discarded.length) { 2158 similarity_data_active.currentDiscardedIndex = this.similarity_pairs_action_discarded.length - 1; 2159 } 2160 } else { 2161 similarity_data_active.currentDiscardedIndex = 0; 2162 } 2163 } 2164 2165 2166 2167 2168 2169 2170 2171 cleanup() { 2172 // Terminate main worker safely 2173 if (this.worker instanceof Worker) { 2174 this.worker.terminate(); 2175 this.worker = null; 2176 } 2177 2178 // Terminate transformers worker safely 2179 if (this.worker_sentence_transformers instanceof Worker) { 2180 this.worker_sentence_transformers.terminate(); 2181 this.worker_sentence_transformers = null; 2182 } 2183 2184 // Clear training and prediction data 2185 this.training_data = []; 2186 this.prediction_data = []; 2187 2188 //Clear similarity array. 2189 this.similarity_array = []; 2190 this.similarity_summary = null; 2191 this.similarity_pairs_action_discarded = []; 2192 this.similarity_array_discarded = []; 2193 this.discarded_similarity_indexes = new Set(); 2194 2195 2196 // Clear vector data and names 2197 this.vectors = []; 2198 this.vector_names = []; 2199 this.vector_config = []; 2200 2201 // Reset probability threshold 2202 this.proba_threshold = 0; 2203 2204 // Clear metrics and ROC data 2205 this.metrics = []; 2206 this.metrics_stats = []; 2207 this.auc_roc = []; 2208 this.auc_roc_stats = []; 2209 this.roc_curves = []; 2210 this.roc_curve_mean = []; 2211 2212 // Clear document index data 2213 this.data_array = []; 2214 this._clear_cached_entry_fields(); 2215 this.training_data = { 2216 prediction_mask: [], 2217 y_unlabelled: [], 2218 y: [], 2219 ensemble_cv1_ypred_best_stacked: [] 2220 }; 2221 } 2222 2223 2224 };
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.