Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

556
LINES

< > BotCompany Repo | #1028063 // Auto Classifier v4 [learning message classifier]

JavaX source code (Dynamic Module) [tags: use-pretranspiled] - run with: Stefan's OS

Uses 911K of libraries. Click here for Pure Java version (19296L/104K).

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

Author comment

Began life as a copy of #1028058

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: #1028063
Snippet name: Auto Classifier v4 [learning message classifier]
Eternal ID of this version: #1028063/29
Text MD5: 791dd66fdbc6d9952d9dd3c09c99e6c6
Transpilation MD5: e54f432d5d0deec7b2f3c4e78c933f4e
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-05-18 14:29:18
Source code size: 17669 bytes / 556 lines
Pitched / IR pitched: No / No
Views / Downloads: 247 / 1721
Version history: 28 change(s)
Referenced in: [show references]