Uses 911K of libraries. Click here for Pure Java version (20530L/109K).
1 | !7 |
2 | |
3 | cmodule AutoClassifier > DynConvo { |
4 | // THEORY BUILDING BLOCKS (Theory + MsgProp + subclasses) |
5 | |
6 | srecord Theory(BasicLogicRule statement) { |
7 | new PosNeg<Msg> examples; |
8 | //bool iff; // <=> instead of only => |
9 | toString { ret str(statement.lhs instanceof MPTrue ? "Every message is " + statement.rhs |
10 | : bidiMode ? statement.lhs + " <=> " + statement.rhs : statement); } |
11 | } |
12 | |
13 | // propositions about a message. check returns null if unknown |
14 | asclass MsgProp { abstract Bool check(Msg msg); } |
15 | |
16 | static transformable record MPTrue() > MsgProp { |
17 | Bool check(Msg msg) { true; } |
18 | toString { ret "always"; } |
19 | } |
20 | |
21 | transformable record HasLabel(S label) > MsgProp { |
22 | Bool check(Msg msg) { ret msg2label_new.get(msg, label); } |
23 | toString { ret label; } |
24 | } |
25 | |
26 | transformable record DoesntHaveLabel(S label) > MsgProp { |
27 | Bool check(Msg msg) { ret not(msg2label_new.get(msg, label)); } |
28 | toString { ret "not " + label; } |
29 | } |
30 | |
31 | transformable record FeatureValueIs(S feature, O value) > MsgProp { |
32 | Bool check(Msg msg) { ret eq(getMsgFeature(msg, feature), value); } |
33 | toString { ret feature + "=" + value; } |
34 | } |
35 | |
36 | // feature extracts the text from msg |
37 | transformable record MMOMatch(S feature, S pattern) > MsgProp { |
38 | Bool check(Msg msg) { ret mmo_match2(pattern, (S) getMsgFeature(msg, feature)); } |
39 | toString { ret renderFunctionCall("MMOMatch", pattern, feature); } |
40 | } |
41 | |
42 | // LABEL class (with best theories) |
43 | |
44 | class Label { |
45 | S name; |
46 | |
47 | *() {} |
48 | *(S *name) {} |
49 | |
50 | TreeSetWithDuplicates<Theory> bestTheories = new(reverseComparatorFromCalculatedField theoryScore()); |
51 | |
52 | double score() { ret theoryScore(first(bestTheories)); } |
53 | Theory bestTheory() { ret first(bestTheories); } |
54 | } |
55 | |
56 | // FEATURE base classes (FeatureEnv + FeatureExtractor) |
57 | |
58 | sinterface FeatureEnv<A> { |
59 | A mainObject(); |
60 | O getFeature(S name); |
61 | } |
62 | |
63 | sinterface FeatureExtractor<A> { |
64 | O get(FeatureEnv<A> env); |
65 | } |
66 | |
67 | // PREDICTION class (output of classifier) |
68 | |
69 | srecord Prediction(S label, bool plus, double adjustedConfidence) { |
70 | toString { |
71 | ret predictedLabel() + " (confidence: " + iround(adjustedConfidence) + "%)"; |
72 | } |
73 | |
74 | S predictedLabel() { |
75 | ret (plus ? "" : "not ") + label; |
76 | } |
77 | } |
78 | |
79 | // DATA (backend) |
80 | |
81 | sbool bidiMode = true; // treat all theories as bidirectional |
82 | L<Msg> msgs; // all messages (order not used yet) |
83 | transient Map<Msg, MapSO> msg2features = new AutoMap<Msg, MapSO>(lambda1 calcMsgFeatures); |
84 | Set<S> allLabels = syncTreeSet(); |
85 | transient new Map<S, Label> labelsByName; |
86 | new LinkedHashSet<Theory> theories; |
87 | transient Q thinkQ; |
88 | transient new L<IVF1<S>> onNewLabel; |
89 | new DoubleKeyedMap<Msg, S, Bool> msg2label_new; |
90 | transient new Map<S, FeatureExtractor<Msg>> featureExtractors; |
91 | |
92 | // DATA (GUI) |
93 | |
94 | switchable double minAdjustedScoreToDisplay = 50; |
95 | switchable bool autoNext = false; |
96 | L<Msg> shownMsgs; |
97 | S analysisText, labelsForMsgText; |
98 | transient JTable theoryTable, labelsTable, trainedExamplesTable, objectsTable; |
99 | transient JTabbedPane tabs; |
100 | transient SingleComponentPanel scpPredictions; |
101 | S labelForDeepThought; |
102 | transient JComboBox cbLabelForDeepThought; |
103 | |
104 | // START CODE |
105 | |
106 | start { |
107 | thinkQ = dm_startQ("Thought Queue"); |
108 | thinkQ.add(r { |
109 | // legacy + after deletion cleaning |
110 | setField(allLabels := asSyncTreeSet(msg2label_new.bKeys())); |
111 | updateLabelsByName(); |
112 | |
113 | onNewLabel.add(lbl -> change()); |
114 | |
115 | makeTheoriesAboutLabels(); |
116 | makeTheoriesAboutFeaturesAndLabels(); |
117 | |
118 | for (S field : fields(Msg)) |
119 | featureExtractors.put(field, env -> getOpt(env.mainObject(), field)); |
120 | |
121 | makeTextExtractors("text"); |
122 | |
123 | callFAllOnAll(onNewLabel, allLabels); |
124 | onNewLabel.add(lbl -> setComboBoxItems(cbLabelForDeepThought, allLabels)); |
125 | |
126 | msg2labelUpdated(); |
127 | updatePredictions(); |
128 | checkAllTheories(); |
129 | //showRandomMsg(); |
130 | }); |
131 | } |
132 | |
133 | // THEORY MAKING |
134 | |
135 | void makeTheoriesAboutLabels { |
136 | // For any label X: |
137 | onNewLabel.add(lbl -> { |
138 | // test theory (for every M: M has label X) |
139 | addTheory(new Theory(BasicLogicRule(new MPTrue, new HasLabel(lbl)))); |
140 | // test theory (for every M: M doesn't have label X) |
141 | addTheory(new Theory(BasicLogicRule(new MPTrue, new DoesntHaveLabel(lbl)))); |
142 | }); |
143 | } |
144 | |
145 | void makeTheoriesAboutFeaturesAndLabels { |
146 | // for every label X: |
147 | onNewLabel.add(lbl -> { |
148 | // For any feature F: |
149 | for (S feature : keys(featureExtractors)) |
150 | // for every seen value V of F: |
151 | for (O value : possibleValuesOfFeatureRelatedToLabel(feature, lbl)) |
152 | for (O rhs : ll(new HasLabel(lbl), new DoesntHaveLabel(lbl))) |
153 | // test theory (for every M: msg M's feature F has value V => msg has/doesn't have label x)) |
154 | addTheory(new Theory(BasicLogicRule( |
155 | new FeatureValueIs(feature, value), rhs))); |
156 | }); |
157 | } |
158 | |
159 | // THEORY MAKING (helper functions) |
160 | |
161 | Set possibleValuesOfFeature(S feature) { |
162 | if (isBoolField(Msg, feature)) |
163 | ret litset(false, true); |
164 | ret litset(); |
165 | } |
166 | |
167 | Set possibleValuesOfFeatureRelatedToLabel(S feature, S label) { |
168 | Set set = possibleValuesOfFeature(feature); |
169 | fOr (Msg msg : getMsgsRelatedToLabel(label)) |
170 | set.add(getMsgFeature(msg, feature)); |
171 | ret set; |
172 | } |
173 | |
174 | // CALCULATE FEATURES |
175 | |
176 | O getMsgFeature(Msg msg, S feature) { |
177 | ret msg2features.get(msg).get(feature); |
178 | } |
179 | |
180 | // returns AutoMap with no realized entries |
181 | Map<S, O> calcMsgFeatures(Msg msg) { |
182 | new Var<FeatureEnv<Msg>> env; |
183 | AutoMap<S, O> map = new AutoMap<S, O>(feature -> featureExtractors.get(feature).get(env!)); |
184 | env.set(new FeatureEnv<Msg> { |
185 | Msg mainObject() { ret msg; } |
186 | O getFeature(S feature) { ret map.get(feature); } |
187 | }); |
188 | ret map; |
189 | } |
190 | |
191 | // GUI: Show messages |
192 | |
193 | void showMsgs(L<Msg> l) { |
194 | setField(shownMsgs := l); |
195 | setMsgs(l); |
196 | if (l(shownMsgs) == 1) { |
197 | Msg msg = first(shownMsgs); |
198 | setField(labelsForMsgText := or2(renderBoolMap(getMsgLabels(msg)), "-")); |
199 | setField(analysisText := joinWithEmptyLines( |
200 | "Trained Labels: " + labelsForMsgText, |
201 | "Features:\n" + formatColonProperties_quoteStringValues( |
202 | msg2features.get(msg)) |
203 | )); |
204 | setSCPComponent(scpPredictions, |
205 | scrollableStackWithSpacing(map(predictionsForMsg(msg), p -> { |
206 | S percent = iround(p.adjustedConfidence) + "%"; |
207 | S neg = "not " + p.label; |
208 | Bool knownValue = msg2label_new.get(msg, p.label); |
209 | embedded S strong(S html) { ret b(html, style := "font-size: 18; color: #008000"); } |
210 | embedded JComponent makeButton(bool known, bool predicted, S label) { |
211 | S html = predicted ? jlabel_centerHTML(joinWithBR( |
212 | strong(htmlencode(label)), percent)) |
213 | : label; |
214 | S toolTip = predicted ? "Predicted with " + percent + " confidence" + stringIf(!known, ". Click to confirm") |
215 | : !known ? "Click to set this label for message" : ""; |
216 | if (known) ret setTooltip(toolTip, jcenteredlabel(html)); |
217 | JButton btn = setTooltip(toolTip, jbutton(html, rThread { sendInput2(label) })); |
218 | ret predicted ? btn : jfullcenter(btn); |
219 | } |
220 | |
221 | ret withSideMargin(jhgridWithSpacing( |
222 | makeButton(isTrue(knownValue), p.plus, p.label), |
223 | makeButton(isFalse(knownValue), !p.plus, neg) |
224 | )); |
225 | }))); |
226 | } else setFields(analysisText := "", labelsForMsgText := ""); |
227 | } |
228 | |
229 | void updatePredictions() { |
230 | showMsgs(shownMsgs); |
231 | } |
232 | |
233 | void showRandomMsg { |
234 | showMsgs(randomElementAsList(msgs)); |
235 | } |
236 | |
237 | void showPrevMsg { |
238 | showMsgs(llNonNulls(prevInCyclicList(msgs, first(shownMsgs)))); |
239 | } |
240 | |
241 | void showNextMsg { |
242 | showMsgs(llNonNulls(nextInCyclicList(msgs, first(shownMsgs)))); |
243 | } |
244 | |
245 | // CALCULATE PREDICTIONS FOR MESSAGE |
246 | |
247 | L<Prediction> predictionsForMsg(Msg msg) { |
248 | // positive labels first, then "not"s. sort by score in each group |
249 | new L<Prediction> out; |
250 | for (Label label : values(labelsByName)) { |
251 | Theory t = label.bestTheory(), continue if null; |
252 | Bool lhs = evalTheoryLHS(t, msg), continue if null; |
253 | bool prediction = t.statement.rhs instanceof DoesntHaveLabel ? !lhs : lhs; |
254 | double conf = threeB1BScore(t.examples), adjusted = adjustConfidence(conf); |
255 | //if (adjusted < minAdjustedScoreToDisplay) continue; |
256 | out.add(new Prediction(label.name, prediction, adjusted)); |
257 | } |
258 | ret sortedByCalculatedFieldDesc(out, p -> /*pair(p.plus,*/ p.adjustedConfidence/*)*/); |
259 | } |
260 | |
261 | // go from range 50-100 to 0-100 (looks better/more intuitive) |
262 | double adjustConfidence(double x) { |
263 | ret max(0, (x-50)*2); |
264 | } |
265 | |
266 | // rough reverse function of adjustConfidence |
267 | double unadjustConfidence(double x) { |
268 | ret x/2+50; |
269 | } |
270 | |
271 | // GUI: Enter labels |
272 | |
273 | void acceptPrediction(Prediction p) { |
274 | if (p != null) sendInput2(p.predictedLabel()); |
275 | } |
276 | |
277 | void rejectPrediction(Prediction p) { |
278 | if (p != null) sendInput2(cloneWithFlippedBoolField plus(p).predictedLabel()); |
279 | } |
280 | |
281 | @Override |
282 | void sendInput2(S s) { |
283 | // treat input as a label |
284 | if (l(shownMsgs) == 1) { |
285 | Msg shown = first(shownMsgs); |
286 | new Matches m; |
287 | if "not ..." { |
288 | S label = cleanLabel(m.rest()); |
289 | doubleKeyedMapPutVerbose(+msg2label_new, shown, label, false); |
290 | msg2labelUpdated(label); |
291 | if (autoNext) showRandomMsg(); |
292 | } else { |
293 | S label = cleanLabel(s); |
294 | doubleKeyedMapPutVerbose(+msg2label_new, shown, label, true); |
295 | msg2labelUpdated(label); |
296 | if (autoNext) showRandomMsg(); |
297 | } |
298 | change(); |
299 | } |
300 | } |
301 | |
302 | // MESSAGE LABEL HANDLING |
303 | |
304 | Map<S, Bool> getMsgLabels(Msg msg) { |
305 | ret msg2label_new.getA(msg); |
306 | } |
307 | |
308 | Set<Msg> getMsgsRelatedToLabel(S label) { ret msg2label_new.asForB(label); } |
309 | |
310 | void msg2labelUpdated(S label) { |
311 | for (Theory t : cloneList(labelByName(label).bestTheories)) |
312 | checkTheory(t); |
313 | msg2labelUpdated(); |
314 | } |
315 | |
316 | void msg2labelUpdated() { |
317 | callFAllOnAll(onNewLabel, addAll_returnNew(allLabels, msg2label_new.bKeys())); |
318 | updateTrainedExamplesTable(); |
319 | } |
320 | |
321 | // QUERY: get all labels + best theory each |
322 | |
323 | Map<S, Theory> labelsToBestTheoryMap() { |
324 | Map<S, L<Theory>> map = multiMapToMap(multiMapIndex targetLabelOfTheory(theories)); |
325 | ret mapValues(map, theories -> highestBy theoryScore(theories)); |
326 | } |
327 | |
328 | Map<Msg, Bool> examplesForLabel(S label) { |
329 | ret msg2label_new.getB(label); |
330 | } |
331 | |
332 | // GUI: Main layout |
333 | |
334 | visual |
335 | withCenteredButtons(super, |
336 | "<", rInThinkQ(r showPrevMsg), |
337 | "Show random msg", rInThinkQ(r showRandomMsg), |
338 | ">", rInThinkQ(r showNextMsg), |
339 | jPopDownButton_noText(flattenObjectArray( |
340 | "Check theories", rInThinkQ(r checkAllTheories), |
341 | "Forget bad theories", rInThinkQ(r { forgetBadTheories(0) }), |
342 | "Forget all theories", rInThinkQ(r clearTheories), |
343 | "Update predictions", rInThinkQ(r updatePredictions), |
344 | dm_importAndExportAllDataMenuItems()))); |
345 | |
346 | JComponent mainPart() { |
347 | tablePopupMenuItemsThreaded_top(labelsTable = sexyTable(), |
348 | "Copy examples to clipboard", rEnter { |
349 | copyTextToClipboard_lineCountInfoBox(collectAsLines text(keysWithValueTrue(examplesForLabel((S) selectedTableCell(labelsTable, "Label"))))) |
350 | }, |
351 | "Copy counterexamples to clipboard", rEnter { |
352 | copyTextToClipboard_lineCountInfoBox(collectAsLines text(keysWithValueFalse(examplesForLabel((S) selectedTableCell(labelsTable, "Label"))))) |
353 | }); |
354 | ret jhsplit(jvsplit( |
355 | jCenteredSection("Focused Message", |
356 | centerAndSouthWithMargin(super.mainPart(), |
357 | jCenteredSection("Labels assigned to message", dm_centeredLabel labelsForMsgText()))), |
358 | //jhsplit( |
359 | jCenteredSection("Predictions for message (green)", scpPredictions = singleComponentPanel()), |
360 | /*jCenteredSection("Deep Thought", northAndCenterWithMargin( |
361 | withLabel("Think about label:", cbLabelForDeepThought = dm_comboBox labelForDeepThought(allLabels)), |
362 | jcenteredlabel("TODO"))) |
363 | )*/), |
364 | with(r updateTabs, tabs = jtabs( |
365 | "", with(r updateObjectsTable, withRightAlignedButtons( |
366 | tablePopupMenuItemsThreaded( |
367 | onDoubleClickOrEnter(rThread showSelectedObject, |
368 | objectsTable = sexyTable()), |
369 | "Delete", r deleteSelectedMessages), |
370 | "Import messages...", rThreadEnter importMsgs)), |
371 | "", with(r updateLabelsTable, labelsTable), |
372 | "", with(r updateTheoryTable, tableWithSearcher2_returnPanel(theoryTable = sexyTable())), |
373 | "", with(r updateTrainedExamplesTable, tableWithSearcher2_returnPanel(trainedExamplesTable = sexyTable())) |
374 | ))); |
375 | } |
376 | |
377 | // GUI: Update tables & tabs |
378 | |
379 | void updateTrainedExamplesTable { |
380 | dataToTable_uneditable(trainedExamplesTable, map(msg2label_new.map1, (msg, map) -> |
381 | litorderedmap( |
382 | "Message" := (msg.fromUser ? "User" : "Bot") + ": " + msg.text, |
383 | "Labels" := renderBoolMap(map)))); |
384 | } |
385 | |
386 | void updateTabs { |
387 | setTabTitles(tabs, |
388 | firstLetterToUpper(nMessages(msgs)), |
389 | firstLetterToUpper(nLabels(labelsByName)), |
390 | firstLetterToUpper(nTheories(theories)), |
391 | n2(msg2label_new.aKeys(), "Trained Example")); |
392 | } |
393 | |
394 | void updateTheoryTable { |
395 | L<Theory> sorted = sortedByCalculatedFieldDesc theoryScore(theories); |
396 | dataToTable_uneditable(theoryTable, map(sorted, t -> litorderedmap( |
397 | "Score" := renderTheoryScore(t), |
398 | "Theory" := str(t)))); |
399 | } |
400 | |
401 | void updateObjectsTable enter { |
402 | dataToTable_uneditable_ifHasTable(objectsTable, map(msgs, msg -> |
403 | litorderedmap("Text" := msg.text) |
404 | )); |
405 | } |
406 | |
407 | void updateLabelsTable enter { |
408 | L<Label> sorted = sortedByCalculatedFieldDesc(values(labelsByName), l -> l.score()); |
409 | dataToTable_uneditable_ifHasTable(labelsTable, map(sorted, label -> { |
410 | Cl<Theory> bestTheories = label.bestTheories.tiedForFirst(); |
411 | Map<Msg, Bool> examples = examplesForLabel(label.name); |
412 | ret litorderedmap( |
413 | "Label" := label.name, |
414 | "Examples/Counterexamples" := countKeysWithValue(true, examples) + "/" + countKeysWithValue(false, examples), |
415 | "Prediction Confidence" := renderTheoryScore(first(bestTheories)), |
416 | "Best Theory" := empty(bestTheories) ? "" : |
417 | (l(bestTheories) > 1 ? "[+" + (l(bestTheories)-1) + "] " : "") + first(bestTheories)); |
418 | })); |
419 | } |
420 | |
421 | void theoriesChanged { |
422 | updateTheoryTable(); |
423 | updateLabelsTable(); |
424 | updateTabs(); |
425 | updatePredictions(); |
426 | change(); |
427 | } |
428 | |
429 | // THEORY SCORING |
430 | |
431 | S renderTheoryScore(Theory t) { |
432 | //ret renderPosNegCounts(t.examples); |
433 | ret t == null || t.examples.isEmpty() ? "" : iround(theoryScore(t)) + "%" |
434 | + " / " + renderPosNegScore2(t.examples); |
435 | } |
436 | |
437 | // adjusted + 3b1b |
438 | double theoryScore(Theory t) { |
439 | ret t == null ? -100 : adjustConfidence(threeB1BScore(t.examples)); |
440 | } |
441 | |
442 | // QUEUE HELPER |
443 | |
444 | Runnable rInThinkQ(Runnable r) { ret rInQ(thinkQ, r); } |
445 | |
446 | // ADD + REMOVE + CLEAN UP THEORIES |
447 | |
448 | void addTheory(Theory theory) { |
449 | if (theories.add(theory)) { |
450 | addTheoryToCollectors(theory); |
451 | theoriesChanged(); |
452 | } |
453 | } |
454 | |
455 | void clearTheories { theories.clear(); theoriesChanged(); } |
456 | |
457 | // theories with exaclty minScore will go too |
458 | void forgetBadTheories(double minScore) { |
459 | if (removeElementsThat(theories, t -> theoryScore(t) <= minScore)) |
460 | theoriesChanged(); |
461 | } |
462 | |
463 | // CHECK PROPOSITIONS + THEORIES |
464 | |
465 | Bool checkMsgProp(O prop, Msg msg) { |
466 | if (prop cast And) ret checkMsgProp(prop.a, msg) && checkMsgProp(prop.b, msg); |
467 | if (prop cast Not) ret not(checkMsgProp(prop.a, msg)); |
468 | ret ((MsgProp) prop).check(msg); |
469 | } |
470 | |
471 | Bool evalTheoryLHS(Theory theory, Msg msg) { |
472 | ret theory == null ? null |
473 | : checkMsgProp(theory.statement.lhs, msg); |
474 | } |
475 | |
476 | Bool testTheoryOnMsg(Theory theory, Msg msg) { |
477 | Bool lhs = evalTheoryLHS(theory, msg); |
478 | Bool rhs = checkMsgProp(theory.statement.rhs, msg); |
479 | if (lhs == null || rhs == null) null; |
480 | if (bidiMode) |
481 | ret eq(lhs, rhs); |
482 | else |
483 | ret isTrue(rhs) || isFalse(lhs); |
484 | } |
485 | |
486 | void checkAllTheories { |
487 | for (Theory theory : theories) |
488 | checkTheory_noTrigger(theory); |
489 | theoriesChanged(); |
490 | } |
491 | |
492 | void checkTheory(Theory theory) { |
493 | checkTheory_noTrigger(theory); |
494 | theoriesChanged(); |
495 | } |
496 | |
497 | void checkTheory_noTrigger(Theory theory) { |
498 | new PosNeg<Msg> pn; |
499 | for (Msg msg : msgs) |
500 | pn.add(msg, testTheoryOnMsg(theory, msg)); |
501 | if (!eq(theory.examples, pn)) { |
502 | removeTheoryFromCollectors(theory); |
503 | theory.examples = pn; |
504 | addTheoryToCollectors(theory); |
505 | change(); |
506 | } |
507 | } |
508 | |
509 | S targetLabelOfTheory(Theory theory) { |
510 | O o = theory.statement.rhs; |
511 | if (o cast HasLabel) ret o.label; |
512 | if (o cast DoesntHaveLabel) ret o.label; |
513 | null; |
514 | } |
515 | |
516 | // CANONICALIZE LABELS |
517 | |
518 | S cleanLabel(S label) { ret upper(label); } |
519 | |
520 | // THEORY + LABEL UPDATES |
521 | |
522 | void addTheoryToCollectors(Theory theory) { |
523 | S lbl = targetLabelOfTheory(theory); |
524 | if (lbl != null) |
525 | labelByName(lbl).bestTheories.add(theory); |
526 | } |
527 | |
528 | void removeTheoryFromCollectors(Theory theory) { |
529 | S lbl = targetLabelOfTheory(theory); |
530 | if (lbl != null) |
531 | labelByName(lbl).bestTheories.remove(theory); |
532 | } |
533 | |
534 | Label labelByName(S name) { |
535 | ret getOrCreate(labelsByName, name, () -> new Label(name)); |
536 | } |
537 | |
538 | void updateLabelsByName() { |
539 | for (S lbl : allLabels) |
540 | labelByName(lbl); |
541 | for (Theory t : theories) |
542 | addTheoryToCollectors(t); |
543 | } |
544 | |
545 | // MAKE FEATURE EXTRACTORS |
546 | |
547 | void makeTextExtractors(S textFeature) { |
548 | for (WithName<IF1<S, O>> f : textExtractors()) { |
549 | IF1<S, O> theFunction = f!; |
550 | featureExtractors.put(f.name, env -> theFunction.get((S) env.getFeature(textFeature))); |
551 | } |
552 | } |
553 | |
554 | L<WithName<IF1<S, O>>> textExtractors() { |
555 | new L<WithName<IF1<S, O>>> l; |
556 | l.add(WithName<>("number of words", lambda1 numberOfWords)); |
557 | l.add(WithName<>("number of characters", lambda1 l)); |
558 | for (char c : characters("\"', .-_")) |
559 | l.add(WithName<>("contains " + quote(c), s -> contains(s, c))); |
560 | /*for (S word : concatAsCISet(lambdaMap words(collect text(msgs)))) |
561 | l.add(WithName<>("contains word " + quote(word), s -> containsWord(s, word)));*/ |
562 | ret l; |
563 | } |
564 | |
565 | // GUI: Import messages dialog, warn on delete, other stuff |
566 | |
567 | void importMsgs { |
568 | inputMultiLineText("Messages to import (one per line)", voidfunc(S text) { |
569 | Cl<S> toImport = listMinusSet(asOrderedSet(tlft(text)), collectAsSet text(msgs)); |
570 | if (msgs == null) msgs = ll(); |
571 | for (S line : toImport) |
572 | msgs.add(new Msg(true, line)); |
573 | change(); |
574 | infoBox(nMessages(toImport) + " imported"); |
575 | updateObjectsTable(); |
576 | showRandomMsg(); |
577 | }); |
578 | } |
579 | |
580 | bool warnOnDelete() { true; } |
581 | |
582 | void showSelectedObject enter { |
583 | showMsgs(llNotNulls(get(msgs, selectedRow(objectsTable)))); |
584 | } |
585 | |
586 | void deleteSelectedMessages { |
587 | Set<Msg> toDelete = asSet(getMulti(msgs, selectedRows(objectsTable))); |
588 | removeFromCollection(msgs, toDelete); |
589 | removeAll(msg2features, toDelete); |
590 | msg2label_new.removeAllA(toDelete); |
591 | change(); |
592 | updateObjectsTable(); |
593 | showRandomMsg(); |
594 | } |
595 | |
596 | // DEEP THOUGHT (make more complex theories for label) |
597 | |
598 | void deepThought(S label) { |
599 | } |
600 | } |
Began life as a copy of #1028063
download show line numbers debug dex old transpilations
Travelled to 7 computer(s): bhatertpkbcr, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tvejysmllsmz, vouqrxazstgt, xrpafgyirdlv
No comments. add comment
Snippet ID: | #1028066 |
Snippet name: | Auto Classifier v5 [learning message classifier] |
Eternal ID of this version: | #1028066/30 |
Text MD5: | 28da243219e7f859a308c41d5f12514d |
Transpilation MD5: | d994e645303cfdfd02142b8f06886da7 |
Author: | stefan |
Category: | javax / a.i. |
Type: | JavaX source code (Dynamic Module) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2020-06-07 19:27:39 |
Source code size: | 19856 bytes / 600 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 276 / 3457 |
Version history: | 29 change(s) |
Referenced in: | [show references] |