From b59e8a1605f5cdb4d75837b82c90cdfdb9351098 Mon Sep 17 00:00:00 2001
From: rschoene <rene.schoene@tu-dresden.de>
Date: Fri, 22 Nov 2019 17:22:20 +0100
Subject: [PATCH] WIP: Begin to integrate new datasets for learner.

- LearnerScenarioDefinition: Add csv-format, output-mappings and base-location (definition- and data-files are now relative paths)
- LearnerSettings: Merge columns (now stored as "kind" in column), verbosity of training
- LearnerTest:
  - Ignore long-running and failing tests, add new tests for datasets
  - Reuse item names from scenario settings
  - Support csv-format
---
 .../src/main/resources/activity_network.eg    |   1 +
 .../main/resources/activity_normalizer.json   |   1 +
 ...aded_learner_activity_phone_and_watch.json |   1 +
 ...d_learner_preferences_brightness_iris.json |   1 +
 .../main/resources/preference_definition.json |   1 +
 .../src/main/resources/preference_network.eg  |   1 +
 .../main/resources/preference_normalizer.json |   1 +
 eraser.starter/starter-setting.yaml           |   9 +-
 .../feedbackloop.learner_backup/Learner.java  |  48 ++--
 .../MachineLearningImpl.java                  |  14 +-
 .../feedbackloop.learner_backup/Main.java     |  62 ++++-
 .../data/LearnerScenarioDefinition.java       |  41 ++-
 .../learner_backup/data/LearnerSettings.java  |  12 +-
 .../data/SimpleColumnDefinition.java          |   8 +
 .../2019-oct-28-activity_definition.json      |  41 +++
 .../resources/2019-oct-28-activity_network.eg |  24 ++
 .../2019-oct-28-activity_normalizer.json      | 238 ++++++++++++++++++
 .../main/resources/2019-oct-28-learner.json   |  25 ++
 .../resources/2019-oct-28-loaded_learner.json |  26 ++
 .../main/resources/activity_definition.json   |  33 ++-
 .../learner_activity_phone_and_watch.json     |   7 +-
 .../learner_preferences_brightness_iris.json  |   4 +-
 ...aded_learner_activity_phone_and_watch.json |   9 +-
 ...d_learner_preferences_brightness_iris.json |   6 +-
 .../main/resources/preference_definition.json |  15 +-
 .../LearnerSubjectUnderTest.java              |   4 +-
 .../learner_backup/LearnerTest.java           |  27 +-
 .../learner_backup/LearnerTestConstants.java  |  27 +-
 .../learner_backup/LearnerTestSettings.java   |  12 +-
 .../learner_backup/LearnerTestUtils.java      | 208 ++++++++-------
 .../learner_backup/LearnerTestUtilsTest.java  |  88 +++++++
 .../2019-oct-28-activity_definition.json      |   1 +
 .../resources/2019-oct-28-activity_network.eg |   1 +
 .../2019-oct-28-activity_normalizer.json      |   1 +
 .../test/resources/2019-oct-28-learner.json   |   1 +
 .../resources/2019-oct-28-loaded_learner.json |   1 +
 36 files changed, 788 insertions(+), 212 deletions(-)
 create mode 120000 eraser.starter/src/main/resources/activity_network.eg
 create mode 120000 eraser.starter/src/main/resources/activity_normalizer.json
 create mode 120000 eraser.starter/src/main/resources/loaded_learner_activity_phone_and_watch.json
 create mode 120000 eraser.starter/src/main/resources/loaded_learner_preferences_brightness_iris.json
 create mode 120000 eraser.starter/src/main/resources/preference_definition.json
 create mode 120000 eraser.starter/src/main/resources/preference_network.eg
 create mode 120000 eraser.starter/src/main/resources/preference_normalizer.json
 create mode 100644 feedbackloop.learner_backup/src/main/resources/2019-oct-28-activity_definition.json
 create mode 100644 feedbackloop.learner_backup/src/main/resources/2019-oct-28-activity_network.eg
 create mode 100644 feedbackloop.learner_backup/src/main/resources/2019-oct-28-activity_normalizer.json
 create mode 100644 feedbackloop.learner_backup/src/main/resources/2019-oct-28-learner.json
 create mode 100644 feedbackloop.learner_backup/src/main/resources/2019-oct-28-loaded_learner.json
 create mode 100644 feedbackloop.learner_backup/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/LearnerTestUtilsTest.java
 create mode 120000 feedbackloop.learner_backup/src/test/resources/2019-oct-28-activity_definition.json
 create mode 120000 feedbackloop.learner_backup/src/test/resources/2019-oct-28-activity_network.eg
 create mode 120000 feedbackloop.learner_backup/src/test/resources/2019-oct-28-activity_normalizer.json
 create mode 120000 feedbackloop.learner_backup/src/test/resources/2019-oct-28-learner.json
 create mode 120000 feedbackloop.learner_backup/src/test/resources/2019-oct-28-loaded_learner.json

diff --git a/eraser.starter/src/main/resources/activity_network.eg b/eraser.starter/src/main/resources/activity_network.eg
new file mode 120000
index 00000000..3c37b327
--- /dev/null
+++ b/eraser.starter/src/main/resources/activity_network.eg
@@ -0,0 +1 @@
+../../../../feedbackloop.learner_backup/src/main/resources/activity_network.eg
\ No newline at end of file
diff --git a/eraser.starter/src/main/resources/activity_normalizer.json b/eraser.starter/src/main/resources/activity_normalizer.json
new file mode 120000
index 00000000..edc204d0
--- /dev/null
+++ b/eraser.starter/src/main/resources/activity_normalizer.json
@@ -0,0 +1 @@
+../../../../feedbackloop.learner_backup/src/main/resources/activity_normalizer.json
\ No newline at end of file
diff --git a/eraser.starter/src/main/resources/loaded_learner_activity_phone_and_watch.json b/eraser.starter/src/main/resources/loaded_learner_activity_phone_and_watch.json
new file mode 120000
index 00000000..152f4382
--- /dev/null
+++ b/eraser.starter/src/main/resources/loaded_learner_activity_phone_and_watch.json
@@ -0,0 +1 @@
+../../../../feedbackloop.learner_backup/src/main/resources/loaded_learner_activity_phone_and_watch.json
\ No newline at end of file
diff --git a/eraser.starter/src/main/resources/loaded_learner_preferences_brightness_iris.json b/eraser.starter/src/main/resources/loaded_learner_preferences_brightness_iris.json
new file mode 120000
index 00000000..154e9040
--- /dev/null
+++ b/eraser.starter/src/main/resources/loaded_learner_preferences_brightness_iris.json
@@ -0,0 +1 @@
+../../../../feedbackloop.learner_backup/src/main/resources/loaded_learner_preferences_brightness_iris.json
\ No newline at end of file
diff --git a/eraser.starter/src/main/resources/preference_definition.json b/eraser.starter/src/main/resources/preference_definition.json
new file mode 120000
index 00000000..a8dfc425
--- /dev/null
+++ b/eraser.starter/src/main/resources/preference_definition.json
@@ -0,0 +1 @@
+../../../../feedbackloop.learner_backup/src/main/resources/preference_definition.json
\ No newline at end of file
diff --git a/eraser.starter/src/main/resources/preference_network.eg b/eraser.starter/src/main/resources/preference_network.eg
new file mode 120000
index 00000000..c3ee89df
--- /dev/null
+++ b/eraser.starter/src/main/resources/preference_network.eg
@@ -0,0 +1 @@
+../../../../feedbackloop.learner_backup/src/main/resources/preference_network.eg
\ No newline at end of file
diff --git a/eraser.starter/src/main/resources/preference_normalizer.json b/eraser.starter/src/main/resources/preference_normalizer.json
new file mode 120000
index 00000000..3ff4cdb3
--- /dev/null
+++ b/eraser.starter/src/main/resources/preference_normalizer.json
@@ -0,0 +1 @@
+../../../../feedbackloop.learner_backup/src/main/resources/preference_normalizer.json
\ No newline at end of file
diff --git a/eraser.starter/starter-setting.yaml b/eraser.starter/starter-setting.yaml
index 8c1aa978..1326620d 100644
--- a/eraser.starter/starter-setting.yaml
+++ b/eraser.starter/starter-setting.yaml
@@ -26,7 +26,7 @@ load:
 activity:
   factory: de.tudresden.inf.st.eraser.feedbackloop.learner_backup.MachineLearningHandlerFactoryImpl
   # File to read in. Expected format depends on factory
-  file: ../datasets/backup/activity_definition.json
+  file: src/main/resources/loaded_learner_activity_phone_and_watch.json
   external: true
   # Use dummy model in which the current activity is directly editable. Default: false.
   dummy: false
@@ -36,10 +36,11 @@ activity:
 
 # Model for preference learning. If dummy is true, then the file parameter is ignored.
 preference:
-  factory: de.tudresden.inf.st.eraser.starter.TestMachineLearningHandlerFactory
+#  factory: de.tudresden.inf.st.eraser.starter.TestMachineLearningHandlerFactory
+  factory: de.tudresden.inf.st.eraser.feedbackloop.learner_backup.MachineLearningHandlerFactoryImpl
   # File to read in. Expected format depends on factory
-  file: src/main/resources/testHandler.properties
-#  file: ../datasets/backup/preference_data.csv
+#  file: src/main/resources/testHandler.properties
+  file: src/main/resources/loaded_learner_preferences_brightness_iris.json
   external: true
   # Use dummy model in which the current activity is directly editable. Default: false.
   dummy: false
diff --git a/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/Learner.java b/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/Learner.java
index cc4c9fa5..6954c37d 100644
--- a/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/Learner.java
+++ b/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/Learner.java
@@ -5,6 +5,7 @@ import de.tudresden.inf.st.eraser.feedbackloop.learner_backup.data.LearnerSettin
 import de.tudresden.inf.st.eraser.feedbackloop.learner_backup.data.SimpleColumnDefinition;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
+import org.encog.ConsoleStatusReportable;
 import org.encog.Encog;
 import org.encog.ml.data.MLData;
 import org.encog.ml.data.versatile.NormalizationHelper;
@@ -60,34 +61,44 @@ public class Learner {
     modelFile.deleteOnExit();
   }
 
-  /**
-   * Begin training using the training set specified in settings.
-   * @throws MalformedURLException if the location of the training set in the settings is malformed
-   */
-  void train() throws MalformedURLException {
-    URL location = new File(settings.initialDataFile).toURI().toURL();
-    train(location);
-  }
+//  /**
+//   * Begin training using the training set specified in settings.
+//   * @throws MalformedURLException if the location of the training set in the settings is malformed
+//   */
+//  void train() throws MalformedURLException {
+//    URL location = new File(settings.initialDataFile).toURI().toURL();
+//    train(location);
+//  }
 
   /**
    * Begin training with the given initial training set.
    * @param location the location of the training set
    */
-  void train(URL location) {
+  void train(URL location, String csvFormatString) {
     logger.info("Training for {} begins using {}", settings.name, location);
     VersatileDataSource source;
     File csvFile = new File(location.getFile());
-    source = new CSVDataSource(csvFile, true, CSVFormat.DECIMAL_POINT);
+    CSVFormat csvFormat;
+    switch (csvFormatString) {
+      case "DECIMAL_POINT": csvFormat = CSVFormat.DECIMAL_POINT; break;
+      case "DECIMAL_COMMA": csvFormat = CSVFormat.DECIMAL_COMMA; break;
+      default:
+        logger.warn("Unknown CSV format, using default decimal point");
+        csvFormat = CSVFormat.DECIMAL_POINT;
+    }
+    source = new CSVDataSource(csvFile, true, csvFormat);
     VersatileMLDataSet data = new VersatileMLDataSet(source);
-    final int inputSize = settings.inputColumns.size();
+    List<ColumnDefinition> targets = new ArrayList<>();
+    final int inputSize = settings.columns.size();
     for (int index = 0; index < inputSize; index++) {
-      SimpleColumnDefinition columnDefinition = settings.inputColumns.get(index);
-      data.defineSourceColumn(columnDefinition.name, index, columnDefinition.type);
+      SimpleColumnDefinition columnDefinition = settings.columns.get(index);
+      ColumnDefinition sourceColumn = data.defineSourceColumn(columnDefinition.name, index, columnDefinition.type);
+      if (columnDefinition.kind == SimpleColumnDefinition.ColumnKind.target) {
+        targets.add(sourceColumn);
+      }
     }
-    List<ColumnDefinition> targets = new ArrayList<>();
-    for (int targetIndex = 0; targetIndex < settings.targetColumns.size(); targetIndex++) {
-      SimpleColumnDefinition columnDefinition = settings.targetColumns.get(targetIndex);
-      targets.add(data.defineSourceColumn(columnDefinition.name, inputSize + targetIndex, columnDefinition.type));
+    if (targets.isEmpty()) {
+      logger.warn("No targets specified for {}!", settings.name);
     }
     if (targets.size() == 1) {
       data.defineSingleOutputOthersInput(targets.get(0));
@@ -96,6 +107,9 @@ public class Learner {
     }
     data.analyze();
     EncogModel model = new EncogModel(data);
+    if (settings.verboseTraining) {
+      model.setReport(new ConsoleStatusReportable());
+    }
     model.selectMethod(data, settings.trainingMethod);
     data.normalize();
     normalizationHelper = data.getNormHelper();
diff --git a/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/MachineLearningImpl.java b/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/MachineLearningImpl.java
index f729b0f8..b1f0062b 100644
--- a/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/MachineLearningImpl.java
+++ b/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/MachineLearningImpl.java
@@ -5,9 +5,7 @@ import de.tudresden.inf.st.eraser.jastadd.model.*;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
-import java.io.File;
 import java.io.IOException;
-import java.net.MalformedURLException;
 import java.net.URL;
 import java.time.Instant;
 import java.util.*;
@@ -45,7 +43,7 @@ public class MachineLearningImpl implements MachineLearningDecoder, MachineLearn
    */
   public MachineLearningImpl(LearnerGoal goal, URL configURL) throws IOException {
     scenarioDefinition = LearnerScenarioDefinition.loadFrom(configURL);
-    URL learnerSettingsURL = filenameToURL(scenarioDefinition.definitionFile);
+    URL learnerSettingsURL = scenarioDefinition.getDefinitionFileAsURL();
     this.learner = new Learner(learnerSettingsURL);
     this.goal = goal;
   }
@@ -189,11 +187,11 @@ public class MachineLearningImpl implements MachineLearningDecoder, MachineLearn
   void startTraining() throws IOException, ClassNotFoundException {
     switch (this.scenarioDefinition.kind) {
       case normal:
-        learner.train(filenameToURL(this.scenarioDefinition.dataFiles.get(0)));
+        learner.train(this.scenarioDefinition.getDataFilesAsURL().get(0), this.scenarioDefinition.csvFormat);
         break;
       case loaded:
-        learner.load(filenameToURL(this.scenarioDefinition.dataFiles.get(0)),
-            filenameToURL(this.scenarioDefinition.dataFiles.get(1)), true);
+        List<URL> dataFilesAsURL = this.scenarioDefinition.getDataFilesAsURL();
+        learner.load(dataFilesAsURL.get(0), dataFilesAsURL.get(1), true);
     }
     this.lastModelUpdate = Instant.now();
   }
@@ -204,8 +202,4 @@ public class MachineLearningImpl implements MachineLearningDecoder, MachineLearn
     }
     learner.shutdown();
   }
-
-  private static URL filenameToURL(String filename) throws MalformedURLException {
-    return new File(filename).toURI().toURL();
-  }
 }
diff --git a/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/Main.java b/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/Main.java
index 4aa97dab..d8ff6ed7 100644
--- a/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/Main.java
+++ b/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop.learner_backup/Main.java
@@ -1,7 +1,10 @@
 package de.tudresden.inf.st.eraser.feedbackloop.learner_backup;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import de.tudresden.inf.st.eraser.feedbackloop.learner_backup.data.LearnerScenarioDefinition;
 import de.tudresden.inf.st.eraser.feedbackloop.learner_backup.data.LearnerSettings;
+import de.tudresden.inf.st.eraser.jastadd.model.*;
+import de.tudresden.inf.st.eraser.util.ParserUtils;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.encog.ml.data.versatile.NormalizationHelper;
@@ -10,11 +13,16 @@ import org.encog.util.csv.ReadCSV;
 
 import java.io.File;
 import java.io.IOException;
+import java.net.MalformedURLException;
 import java.net.URL;
 import java.nio.file.Paths;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.stream.Collectors;
 
+import static de.tudresden.inf.st.eraser.jastadd.model.MachineLearningHandlerFactory.MachineLearningHandlerFactoryTarget.ACTIVITY_RECOGNITION;
+
 @SuppressWarnings("unused")
 public class Main {
 
@@ -28,7 +36,49 @@ public class Main {
 //    learner.train("datasets/backup/activity_data.csv","datasets/backup/preference_data.csv");
 //    activity_validation_learner();
 //    testSettings();
-    testLoadNormalizationHelper();
+//    testLoadNormalizationHelper();
+    testLearnerWithDatasetFromMunich();
+  }
+
+  private static void testLearnerWithDatasetFromMunich() {
+    MachineLearningHandlerFactory factory = new MachineLearningHandlerFactoryImpl();
+    try {
+      URL configURL = Paths.get("src", "main", "resources", "activity_definition-2019-oct-28.json").toUri().toURL();
+      LearnerScenarioDefinition scenarioDefinition = LearnerScenarioDefinition.loadFrom(configURL);
+      Root model = createRootWithItemsFrom(scenarioDefinition);
+      factory.setKnowledgeBaseRoot(model);
+      factory.initializeFor(ACTIVITY_RECOGNITION, configURL);
+      MachineLearningModel mlModel = factory.createModel();
+      List<Item> changedItems = new ArrayList<>(); // TODO
+      mlModel.getEncoder().newData(changedItems);
+      MachineLearningResult result = mlModel.getDecoder().classify();
+    } catch (IOException | ClassNotFoundException e) {
+      logger.catching(e);
+    }
+  }
+
+  private static Root createRootWithItemsFrom(LearnerScenarioDefinition scenarioDefinition) {
+    Root result = Root.createEmptyRoot();
+    ParserUtils.createUnknownGroup(
+        result.getSmartHomeEntityModel(),
+        scenarioDefinition.relevantItemNames.stream().map(Main::createItem).collect(Collectors.toList()));
+    return result;
+  }
+
+  private static Item createItem(String itemName) {
+    Item item;
+    if (itemName.contains("OpenClose")) {
+      // contact item
+      item = new ContactItem();
+    } else if (itemName.contains("Fibaro")) {
+      // boolean item
+      item = new SwitchItem();
+    } else {
+      // double item
+      item = new NumberItem();
+    }
+    item.setID(itemName);
+    return item;
   }
 
   private static void testLoadNormalizationHelper() {
@@ -52,13 +102,9 @@ public class Main {
       return;
     }
     System.out.println("settings.name = " + settings.name);
-    System.out.println("settings.inputColumns = " + settings.inputColumns
-        .stream()
-        .map(col -> "(" + col.name + "," + col.type + ")")
-        .collect(Collectors.joining(";")));
-    System.out.println("settings.targetColumns = " + settings.targetColumns
+    System.out.println("settings.columns = " + settings.columns
         .stream()
-        .map(col -> "(" + col.name + "," + col.type + ")")
+        .map(col -> "(" + col.kind + ": " + col.name + "," + col.type + ")")
         .collect(Collectors.joining(";")));
   }
 
@@ -68,7 +114,7 @@ public class Main {
     Learner learner = new Learner(new ObjectMapper().readValue(
         Paths.get("src", "main", "resources", "activity_definition.json").toFile(),
         LearnerSettings.class));
-    learner.train(Paths.get("src", "test", "activity_data.csv").toUri().toURL());
+    learner.train(Paths.get("src", "test", "activity_data.csv").toUri().toURL(), "DECIMAL_POINT");
 //    learner.preference_train("../datasets/backup/preference_data.csv");
     int wrong = 0;
     int right = 0;
diff --git a/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/data/LearnerScenarioDefinition.java b/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/data/LearnerScenarioDefinition.java
index c6151128..5d7d511b 100644
--- a/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/data/LearnerScenarioDefinition.java
+++ b/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/data/LearnerScenarioDefinition.java
@@ -5,9 +5,15 @@ import de.tudresden.inf.st.eraser.feedbackloop.learner_backup.LearnerKind;
 import de.tudresden.inf.st.eraser.feedbackloop.learner_backup.MachineLearningImpl;
 import de.tudresden.inf.st.eraser.util.ParserUtils;
 
+import java.io.File;
 import java.io.IOException;
+import java.net.MalformedURLException;
 import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * Settings to initialize {@link MachineLearningImpl}.
@@ -18,13 +24,46 @@ import java.util.List;
 public class LearnerScenarioDefinition {
   public List<String> relevantItemNames;
   public List<String> targetItemNames;
+  public Map<String, String> nonTrivialOutputMappings = new HashMap<>();
   public String definitionFile;
+  public String csvFormat = "DECIMAL_POINT";
   public List<String> dataFiles;
   public LearnerKind kind = LearnerKind.normal;
   /** Save models at shutdown */
   public boolean saveModels = false;
+  public transient URL myBaseLocation;
 
   public static LearnerScenarioDefinition loadFrom(URL location) throws IOException {
-    return ParserUtils.loadFrom(location, LearnerScenarioDefinition.class);
+    LearnerScenarioDefinition result = ParserUtils.loadFrom(location, LearnerScenarioDefinition.class);
+    result.myBaseLocation = new URL(location.getProtocol(), location.getHost(), location.getPort(), new File(location.getFile()).getParent(), null);
+    return result;
+  }
+
+  public URL getDefinitionFileAsURL() throws MalformedURLException {
+    return filenameToURL(definitionFile);
+  }
+
+  public List<URL> getDataFilesAsURL() throws MalformedURLException {
+    List<URL> result = new ArrayList<>();
+    for (String dataFile : dataFiles) {
+      URL url = filenameToURL(dataFile);
+      result.add(url);
+    }
+    return result;
+  }
+
+  private URL filenameToURL(URL base, String filename) throws MalformedURLException {
+    // construct new URL with same content except for file part
+    String newFilePart;
+    if (filename.startsWith("/")) {
+      newFilePart = filename;
+    } else {
+      newFilePart = (base.getFile().endsWith("/") ? base.getFile() : base.getFile() + "/" ) + filename;
+    }
+    return new URL(base.getProtocol(), base.getHost(), base.getPort(), newFilePart, null);
+  }
+
+  private URL filenameToURL(String filename) throws MalformedURLException {
+    return filenameToURL(myBaseLocation, filename);
   }
 }
diff --git a/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/data/LearnerSettings.java b/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/data/LearnerSettings.java
index f849873c..ff1b5636 100644
--- a/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/data/LearnerSettings.java
+++ b/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/data/LearnerSettings.java
@@ -19,10 +19,10 @@ import java.util.List;
 public class LearnerSettings {
   /** Description what this learner is about to learn */
   public String name;
-  /** Input columns (of the CSV used for training) */
-  public List<SimpleColumnDefinition> inputColumns;
-  /** Target columns (of the CSV used for training) */
-  public List<SimpleColumnDefinition> targetColumns;
+  /** All available columns (of the CSV used for training, may be set to be ignored) */
+  public List<SimpleColumnDefinition> columns;
+  /** Whether to be verbose while training */
+  public boolean verboseTraining = false;
   /** Training method */
   public String trainingMethod = MLMethodFactory.TYPE_FEEDFORWARD;
   /** Training parameter. Used in {@link EncogModel#holdBackValidation(double, boolean, int)}  */
@@ -35,11 +35,7 @@ public class LearnerSettings {
   /** Training parameter. Used in {@link EncogModel#crossvalidate(int, boolean)}  */
   public int validationFolds = 5;
 
-  /** Filename to load initial data from. Should be in another settings file! */
-  public String initialDataFile = null;
-
   public static LearnerSettings loadFrom(URL location) throws IOException {
     return ParserUtils.loadFrom(location, LearnerSettings.class);
   }
-
 }
diff --git a/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/data/SimpleColumnDefinition.java b/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/data/SimpleColumnDefinition.java
index 17e6109c..e8d1f905 100644
--- a/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/data/SimpleColumnDefinition.java
+++ b/feedbackloop.learner_backup/src/main/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/data/SimpleColumnDefinition.java
@@ -23,4 +23,12 @@ public class SimpleColumnDefinition {
   public double mean = Double.NaN;
   public double sd = Double.NaN;
   public List<String> classes = new ArrayList<>();
+  /** {@link ColumnKind} is either input, target, or ignored */
+  public ColumnKind kind;
+
+  public enum ColumnKind {
+    input,
+    target,
+    ignored
+  }
 }
diff --git a/feedbackloop.learner_backup/src/main/resources/2019-oct-28-activity_definition.json b/feedbackloop.learner_backup/src/main/resources/2019-oct-28-activity_definition.json
new file mode 100644
index 00000000..1078c7fd
--- /dev/null
+++ b/feedbackloop.learner_backup/src/main/resources/2019-oct-28-activity_definition.json
@@ -0,0 +1,41 @@
+{
+  "name": "activity",
+  "columns": [
+    { "kind": "input", "name": "Date", "type": "ignore" },
+    { "kind": "input", "name": "time", "type": "ignore" },
+    { "kind": "target", "name": "activity_label", "type": "nominal" },
+    { "kind": "input", "name": "grideye_room_window", "type": "ignore" },
+    { "kind": "input", "name": "grideye_room_door", "type": "ignore" },
+    { "kind": "input", "name": "Fibaro_2_work_motion", "type": "nominal" },
+    { "kind": "input", "name": "H2_weather_temp", "type": "ignore" },
+    { "kind": "input", "name": "H2_weather_pressure", "type": "ignore" },
+    { "kind": "input", "name": "H2_weather_hum", "type": "ignore" },
+    { "kind": "input", "name": "Fibaro_4_TV_motion", "type": "nominal" },
+    { "kind": "input", "name": "Fibaro_3_door_motion", "type": "nominal" },
+    { "kind": "input", "name": "Fibaro_11_OpenClose", "type": "nominal" },
+    { "kind": "input", "name": "Aeotec_8_room_Temp", "type": "ignore" },
+    { "kind": "input", "name": "Aeotec_8_room_lum", "type": "ignore" },
+    { "kind": "input", "name": "Aeotec_8_room_Hum", "type": "ignore" },
+    { "kind": "input", "name": "Aeotec_8_room_uv", "type": "ignore" },
+    { "kind": "input", "name": "Fibaro_5_room_Temp", "type": "ignore" },
+    { "kind": "input", "name": "Fibaro_5_room_lum", "type": "ignore" },
+    { "kind": "input", "name": "Fibaro_3_door_lum", "type": "ignore" },
+    { "kind": "input", "name": "Fibaro_3_door_Temp", "type": "ignore" },
+    { "kind": "input", "name": "Fibaro_2_work_lum", "type": "ignore" },
+    { "kind": "input", "name": "Fibaro_4_TV_Temp", "type": "ignore" },
+    { "kind": "input", "name": "Fibaro_4_TV_lum", "type": "ignore" },
+    { "kind": "input", "name": "Fibaro_2_work_Temp", "type": "ignore" },
+    { "kind": "input", "name": "TV_OnOff", "type": "ignore" },
+    { "kind": "input", "name": "DPS310_pressure_room", "type": "ignore" },
+    { "kind": "input", "name": "DPS310_temp_room", "type": "ignore" },
+    { "kind": "input", "name": "DPS310_altitude_room", "type": "ignore" },
+    { "kind": "input", "name": "work_device_online_state", "type": "nominal" },
+    { "kind": "input", "name": "Fibaro_15_OpenClose", "type": "ignore" },
+    { "kind": "input", "name": "GridEye_room_window_x", "type": "continuous" },
+    { "kind": "input", "name": "GridEye_room_door_y", "type": "continuous" },
+    { "kind": "input", "name": "GridEye_room_door_x", "type": "continuous" },
+    { "kind": "input", "name": "GridEye_room_window_y", "type": "continuous"}
+  ],
+  "verboseTraining": true,
+  "validationFolds": 3
+}
diff --git a/feedbackloop.learner_backup/src/main/resources/2019-oct-28-activity_network.eg b/feedbackloop.learner_backup/src/main/resources/2019-oct-28-activity_network.eg
new file mode 100644
index 00000000..f2cf7839
--- /dev/null
+++ b/feedbackloop.learner_backup/src/main/resources/2019-oct-28-activity_network.eg
@@ -0,0 +1,24 @@
+encog,BasicNetwork,java,3.4.0,1,1574428376334
+[BASIC]
+[BASIC:PARAMS]
+[BASIC:NETWORK]
+beginTraining=0
+connectionLimit=0
+contextTargetOffset=0,0,0
+contextTargetSize=0,0,0
+endTraining=2
+hasContext=f
+inputCount=19
+layerCounts=7,16,20
+layerFeedCounts=7,15,19
+layerContextCount=0,0,0
+layerIndex=0,7,23
+output=-0.340836329,-0.3423459838,-0.9971504865,-0.994969938,-1,-0.9999999999,-0.3279989961,-0.9797933007,-0.9984924393,-0.9999897248,-0.9998031211,-0.9898578199,0.9998409154,0.9972211116,0.9805206206,-0.9932796274,-0.9544652313,0.9968045856,-0.9445173438,-0.9995702614,-0.9977451063,0.9999910694,1,-1,1,-1,-1,-1,1,1,-1,-1,-1,1,-1,-1,-1,1,0.9921568627,0.3833992095,0.1529411765,-1,1
+outputCount=7
+weightIndex=0,112,412
+weights=-0.404137706,-0.2885181231,0.9668734481,0.0062269301,1.0128231886,0.0089469213,0.4585189612,-0.0894394019,-0.0797318623,-0.3468553546,-1.2300755961,-0.3468429248,-0.5145871857,-0.2848383941,-0.3264209087,0.5741982601,0.27127421,-0.5434136125,0.5485149158,0.5003078803,-0.2698965768,0.7983927357,-0.0333252064,0.3447572796,-0.1882659481,0.7247422012,0.3064477151,-1.0138871821,0.1416051,0.6583373536,0.2705332613,-1.1855603499,0.9929388464,0.5796871274,-0.3399669435,0.8569628254,0.2075255413,-0.5600042384,-0.4138292438,0.6538720983,-0.6923520436,-0.3319059439,-0.4537568575,-0.0025677392,-0.6905085845,0.9053047001,-0.6162521138,-0.3960775091,-0.4368636028,-0.9001436588,1.0750416574,0.7660161008,-0.5990507184,-0.2425415361,0.0274320766,-0.3558952255,-0.71936614,0.5830552943,-0.171895279,0.1974145518,0.7948972407,1.0487310214,-0.566601431,0.1010319343,0.172866604,3.0551601629,3.0000824008,0.3866019399,2.7196342543,-13.0154758675,-6.9544569017,4.3141833144,-2.4103093678,0.2125104309,0.7730445211,-7.4019442181,0.099425633,-10.1082331563,-3.2519692,-4.1723085126,1.1032763871,1.434494168,1.0655828482,2.259228432,0.4309682109,0.8421555229,-1.2418354028,-0.7857195285,-0.1460260544,5.7534066269,-2.0174021556,0.9996651657,-3.1600904683,-3.926442431,-0.780245191,-2.6582836523,-0.3842677407,0.8001100617,0.0932911402,0.8260885108,0.3243592641,0.929464689,0.0688818482,0.2795366272,-0.8677541061,0.1766944912,-0.5401835409,-0.1872442079,0.7241379909,-0.4751260736,-0.7012144045,0.6693119708,0.9182400855,-0.8851888016,0.4365092184,0.2855724153,-0.5807331971,-0.5545832569,-0.4287869651,-0.4256322973,-0.1423483588,0.3741760439,0.0626357057,-0.2969373002,0.1952954195,0.3347612998,-0.319608364,-0.0253925855,-0.25709248,0.0673034925,-0.0089497179,1.0365875369,-0.3911295373,-0.077826642,0.5508385817,-0.3285681225,0.5657971033,-0.2066801958,0.1686295498,0.2070775065,0.9912375579,-0.4753575264,-0.2726487663,0.2664021737,1.3515632261,0.4126524074,0.1782886902,-0.0285599614,-0.0603748382,-0.0374828481,-0.0914111075,-0.268271809,0.0852087324,0.5126836126,0.2546340519,0.2746173051,2.817153954,0.7249724018,-0.6981285839,-0.4985126373,0.8604969349,0.2037431513,2.7264477402,1.2330380603,-0.0167592164,-0.144188394,-0.7301846959,0.4337688818,0.1298617096,-0.4470460904,-0.1921802707,-4.1597800015,-0.4025492346,0.0229041912,1.306807566,0.3525340482,0.6127139965,0.5393598738,0.6929584822,0.7056334137,1.1994301985,0.4496289046,0.2941309986,-0.2056291639,1.251880871,-0.010770356,-0.9810071383,0.0064674917,-0.0176386919,-0.048016511,-0.0107097517,0.0753283256,0.7404769463,-0.5929286192,-0.5078274499,0.4259262815,0.143754059,0.1359612229,0.2541697513,0.2510065262,0.6709506571,-0.148350937,1.1900545736,0.749742294,-0.6287601463,0.6900848995,-0.2690878318,0.0001816587,0.0455003469,0.0309224531,-0.0076091205,-1.0006871788,0.2681613331,0.1833383853,-0.5172206492,0.6892488375,-0.7451485095,0.4276582331,0.3742194161,0.4014722088,1.0994959368,0.847787554,0.7638290785,-0.7914432342,-0.4420336184,-0.9158573706,2.6389325839,0.0887550998,-0.0569580742,-0.0392462129,-0.0239489707,0.1418880057,1.0896267359,0.9904016473,-1.0607560941,1.6784823518,-0.9433720747,0.0257586137,0.4714302624,0.4166775721,-0.6066347851,1.6444284094,0.9257977982,-1.0381297318,-1.7311006544,1.1774799465,1.877862961,-0.2855399619,0.0755531811,0.0424919604,0.2678753077,0.1398908597,0.8214292233,0.9009702295,0.1469390864,0.4637993899,-0.2313547534,0.1448407563,1.0392025508,0.9881708464,-0.7406734936,0.4983164436,0.2939713585,-0.1343176987,-1.112507143,0.3883191946,0.1738987071,-0.07073642,0.050403588,0.0765184646,-0.0005174206,0.8847534729,0.7436968164,-1.0771556764,0.1100227689,-0.0614329979,-0.1622374928,-0.1525778093,-0.6077259941,-0.619774004,0.3603468196,-0.2009073954,-0.3733319916,-0.0788296869,0.6074303689,-0.4126291585,-0.9028665297,-0.0099997932,-0.0001654564,0.0148504563,-0.0231406351,0.5377204482,-0.5777914567,-0.1781865678,1.1415129984,0.2834037016,-0.5539063514,-0.4515762597,0.1404508438,0.1187112233,0.6532769069,-1.2829520787,-0.5799936288,-0.2116382272,0.2306003109,1.0953413393,-0.3323463029,0.0225181859,0.0589642565,0.0426297362,-0.040109034,0.3269351263,-0.5321326898,0.9175722208,-0.6271480872,-0.7459771441,0.0158692044,0.0311943145,-0.5266229614,-0.5225480734,-0.330023217,-0.5138457028,-0.0246451645,-0.2378591164,-0.8257893027,-0.1187365978,-0.8009636381,0.0004696224,0.0525540439,0.015912768,0.0008598419,-0.8381211463,-0.4135458004,0.0319505462,0.4168831222,-1.6027457971,-0.726313219,-0.3371409293,-1.942373089,-1.9681611004,0.3375429492,0.0455411676,0.2193321201,0.5576102386,0.2043673413,0.5864664222,-1.9789533345,0.0541247202,0.0236840469,-0.0086069514,-0.0407470423,-0.4357306331,1.9243856045,-1.3208328316,-0.0954191506,0.0808587123,0.3690795603,0.1095208965,-0.0382497008,-0.0336668021,-0.2167623054,0.0806856928,-0.6457893128,-0.6880786727,0.1245176541,-0.2603043844,-0.8839399611,-0.0815347261,-0.202848777,0.1924881481,-0.1531643391,-0.1819825028,-0.7487125691,-1.0617607253,-0.0682555272,0.1203110094,-0.0307641525,-0.6370047961,0.3619059939,0.3858454328,-0.1968004594,-0.4804011029,-0.3394977591,-0.0261051038,0.1742859157,0.5756254766,-2.5706321037,0.0797072295,0.3687297587,0.0907303422,-0.0206407321,0.3037126261,-0.9534375329,0.8626140311,0.2956554426,0.2553834042,-0.8520628339,-0.8594849437,-0.0600253851,-0.0390563524,-1.4805598205,-0.2333029476,-0.8993510452,-0.9759839461,0.0934807631,-1.6299950666,0.5668144115,0.0068186589,-0.0746488856,0.0060924025,-0.0051572226,1.0449041956
+biasActivation=0,1,1
+[BASIC:ACTIVATION]
+"org.encog.engine.network.activation.ActivationTANH"
+"org.encog.engine.network.activation.ActivationTANH"
+"org.encog.engine.network.activation.ActivationLinear"
diff --git a/feedbackloop.learner_backup/src/main/resources/2019-oct-28-activity_normalizer.json b/feedbackloop.learner_backup/src/main/resources/2019-oct-28-activity_normalizer.json
new file mode 100644
index 00000000..a3be23ca
--- /dev/null
+++ b/feedbackloop.learner_backup/src/main/resources/2019-oct-28-activity_normalizer.json
@@ -0,0 +1,238 @@
+{
+  "sourceColumns" : [ {
+    "name" : "Date",
+    "type" : "ignore",
+    "index" : 0
+  }, {
+    "name" : "time",
+    "type" : "ignore",
+    "index" : 1
+  }, {
+    "name" : "activity_label",
+    "type" : "nominal",
+    "index" : 2,
+    "classes" : [ "DoorClosedPOut", "DoorOpenedPOut", "DoorClosedPIn", "Working", "Reading", "TVWatching", "DoorOpenedPIn" ]
+  }, {
+    "name" : "grideye_room_window",
+    "type" : "ignore",
+    "index" : 3
+  }, {
+    "name" : "grideye_room_door",
+    "type" : "ignore",
+    "index" : 4
+  }, {
+    "name" : "Fibaro_2_work_motion",
+    "type" : "nominal",
+    "index" : 5,
+    "classes" : [ "ON", "OFF", "\\N" ]
+  }, {
+    "name" : "H2_weather_temp",
+    "type" : "ignore",
+    "index" : 6
+  }, {
+    "name" : "H2_weather_pressure",
+    "type" : "ignore",
+    "index" : 7
+  }, {
+    "name" : "H2_weather_hum",
+    "type" : "ignore",
+    "index" : 8
+  }, {
+    "name" : "Fibaro_4_TV_motion",
+    "type" : "nominal",
+    "index" : 9,
+    "classes" : [ "\\N", "ON", "OFF" ]
+  }, {
+    "name" : "Fibaro_3_door_motion",
+    "type" : "nominal",
+    "index" : 10,
+    "classes" : [ "OFF", "ON", "\\N" ]
+  }, {
+    "name" : "Fibaro_11_OpenClose",
+    "type" : "nominal",
+    "index" : 11,
+    "classes" : [ "CLOSED", "OPEN", "\\N" ]
+  }, {
+    "name" : "Aeotec_8_room_Temp",
+    "type" : "ignore",
+    "index" : 12
+  }, {
+    "name" : "Aeotec_8_room_lum",
+    "type" : "ignore",
+    "index" : 13
+  }, {
+    "name" : "Aeotec_8_room_Hum",
+    "type" : "ignore",
+    "index" : 14
+  }, {
+    "name" : "Aeotec_8_room_uv",
+    "type" : "ignore",
+    "index" : 15
+  }, {
+    "name" : "Fibaro_5_room_Temp",
+    "type" : "ignore",
+    "index" : 16
+  }, {
+    "name" : "Fibaro_5_room_lum",
+    "type" : "ignore",
+    "index" : 17
+  }, {
+    "name" : "Fibaro_3_door_lum",
+    "type" : "ignore",
+    "index" : 18
+  }, {
+    "name" : "Fibaro_3_door_Temp",
+    "type" : "ignore",
+    "index" : 19
+  }, {
+    "name" : "Fibaro_2_work_lum",
+    "type" : "ignore",
+    "index" : 20
+  }, {
+    "name" : "Fibaro_4_TV_Temp",
+    "type" : "ignore",
+    "index" : 21
+  }, {
+    "name" : "Fibaro_4_TV_lum",
+    "type" : "ignore",
+    "index" : 22
+  }, {
+    "name" : "Fibaro_2_work_Temp",
+    "type" : "ignore",
+    "index" : 23
+  }, {
+    "name" : "TV_OnOff",
+    "type" : "ignore",
+    "index" : 24
+  }, {
+    "name" : "DPS310_pressure_room",
+    "type" : "ignore",
+    "index" : 25
+  }, {
+    "name" : "DPS310_temp_room",
+    "type" : "ignore",
+    "index" : 26
+  }, {
+    "name" : "DPS310_altitude_room",
+    "type" : "ignore",
+    "index" : 27
+  }, {
+    "name" : "work_device_online_state",
+    "type" : "nominal",
+    "index" : 28,
+    "classes" : [ "ON", "OFF", "\\N" ]
+  }, {
+    "name" : "Fibaro_15_OpenClose",
+    "type" : "ignore",
+    "index" : 29
+  }, {
+    "name" : "GridEye_room_window_x",
+    "type" : "continuous",
+    "count" : 195108,
+    "index" : 30,
+    "low" : 0.0,
+    "high" : 255.0,
+    "mean" : 187.7175666810177,
+    "sd" : 84.78936592617109
+  }, {
+    "name" : "GridEye_room_door_y",
+    "type" : "continuous",
+    "count" : 195108,
+    "index" : 31,
+    "low" : 0.0,
+    "high" : 253.0,
+    "mean" : 180.47390163396682,
+    "sd" : 45.67432497413589
+  }, {
+    "name" : "GridEye_room_door_x",
+    "type" : "continuous",
+    "count" : 195108,
+    "index" : 32,
+    "low" : 0.0,
+    "high" : 255.0,
+    "mean" : 159.02686717100272,
+    "sd" : 53.62353439276967
+  }, {
+    "name" : "GridEye_room_window_y",
+    "type" : "continuous",
+    "count" : 195108,
+    "index" : 33,
+    "low" : 0.0,
+    "high" : 252.0,
+    "mean" : 123.94642966972138,
+    "sd" : 81.4972896448954
+  } ],
+  "inputColumns" : [ {
+    "name" : "Fibaro_2_work_motion",
+    "type" : "nominal",
+    "index" : 5,
+    "classes" : [ "ON", "OFF", "\\N" ]
+  }, {
+    "name" : "Fibaro_4_TV_motion",
+    "type" : "nominal",
+    "index" : 9,
+    "classes" : [ "\\N", "ON", "OFF" ]
+  }, {
+    "name" : "Fibaro_3_door_motion",
+    "type" : "nominal",
+    "index" : 10,
+    "classes" : [ "OFF", "ON", "\\N" ]
+  }, {
+    "name" : "Fibaro_11_OpenClose",
+    "type" : "nominal",
+    "index" : 11,
+    "classes" : [ "CLOSED", "OPEN", "\\N" ]
+  }, {
+    "name" : "work_device_online_state",
+    "type" : "nominal",
+    "index" : 28,
+    "classes" : [ "ON", "OFF", "\\N" ]
+  }, {
+    "name" : "GridEye_room_window_x",
+    "type" : "continuous",
+    "count" : 195108,
+    "index" : 30,
+    "low" : 0.0,
+    "high" : 255.0,
+    "mean" : 187.7175666810177,
+    "sd" : 84.78936592617109
+  }, {
+    "name" : "GridEye_room_door_y",
+    "type" : "continuous",
+    "count" : 195108,
+    "index" : 31,
+    "low" : 0.0,
+    "high" : 253.0,
+    "mean" : 180.47390163396682,
+    "sd" : 45.67432497413589
+  }, {
+    "name" : "GridEye_room_door_x",
+    "type" : "continuous",
+    "count" : 195108,
+    "index" : 32,
+    "low" : 0.0,
+    "high" : 255.0,
+    "mean" : 159.02686717100272,
+    "sd" : 53.62353439276967
+  }, {
+    "name" : "GridEye_room_window_y",
+    "type" : "continuous",
+    "count" : 195108,
+    "index" : 33,
+    "low" : 0.0,
+    "high" : 252.0,
+    "mean" : 123.94642966972138,
+    "sd" : 81.4972896448954
+  } ],
+  "outputColumns" : [ {
+    "name" : "activity_label",
+    "type" : "nominal",
+    "index" : 2,
+    "classes" : [ "DoorClosedPOut", "DoorOpenedPOut", "DoorClosedPIn", "Working", "Reading", "TVWatching", "DoorOpenedPIn" ]
+  } ],
+  "inputLow" : -1.0,
+  "inputHigh" : 1.0,
+  "outputLow" : -1.0,
+  "outputHigh" : 1.0,
+  "unknownValues" : [ null ]
+}
\ No newline at end of file
diff --git a/feedbackloop.learner_backup/src/main/resources/2019-oct-28-learner.json b/feedbackloop.learner_backup/src/main/resources/2019-oct-28-learner.json
new file mode 100644
index 00000000..2b7359f3
--- /dev/null
+++ b/feedbackloop.learner_backup/src/main/resources/2019-oct-28-learner.json
@@ -0,0 +1,25 @@
+{
+  "relevantItemNames": [
+    "Fibaro_2_work_motion",
+    "Fibaro_4_TV_motion",
+    "Fibaro_3_door_motion",
+    "Fibaro_11_OpenClose",
+    "work_device_online_state",
+    "GridEye_room_window_x",
+    "GridEye_room_door_y",
+    "GridEye_room_door_x",
+    "GridEye_room_window_y"
+  ],
+  "targetItemNames": [
+    "activity"
+  ],
+  "nonTrivialOutputMappings": {
+    "activity_label": "activity"
+  },
+  "definitionFile": "./2019-oct-28-activity_definition.json",
+  "dataFiles": [
+    "activity_data/28_08_2019_H14_14/result_all_items_EVERYTHING.csv"
+  ],
+  "csvFormat": "DECIMAL_COMMA",
+  "saveModels": true
+}
diff --git a/feedbackloop.learner_backup/src/main/resources/2019-oct-28-loaded_learner.json b/feedbackloop.learner_backup/src/main/resources/2019-oct-28-loaded_learner.json
new file mode 100644
index 00000000..f476f357
--- /dev/null
+++ b/feedbackloop.learner_backup/src/main/resources/2019-oct-28-loaded_learner.json
@@ -0,0 +1,26 @@
+{
+  "relevantItemNames": [
+    "Fibaro_2_work_motion",
+    "Fibaro_4_TV_motion",
+    "Fibaro_3_door_motion",
+    "Fibaro_11_OpenClose",
+    "work_device_online_state",
+    "GridEye_room_window_x",
+    "GridEye_room_door_y",
+    "GridEye_room_door_x",
+    "GridEye_room_window_y"
+  ],
+  "targetItemNames": [
+    "activity"
+  ],
+  "nonTrivialOutputMappings": {
+    "activity_label": "activity"
+  },
+  "definitionFile": "./2019-oct-28-activity_definition.json",
+  "dataFiles": [
+    "./2019-oct-28-activity_network.eg",
+    "./2019-oct-28-activity_normalizer.json"
+  ],
+  "csvFormat": "DECIMAL_COMMA",
+  "kind": "loaded"
+}
diff --git a/feedbackloop.learner_backup/src/main/resources/activity_definition.json b/feedbackloop.learner_backup/src/main/resources/activity_definition.json
index 49325fc7..9c4a9ea5 100644
--- a/feedbackloop.learner_backup/src/main/resources/activity_definition.json
+++ b/feedbackloop.learner_backup/src/main/resources/activity_definition.json
@@ -1,21 +1,18 @@
 {
   "name": "activity",
-  "inputColumns":  [
-    { "name": "m_accel_x", "type":"continuous" },
-    { "name": "m_accel_y", "type": "continuous" },
-    { "name": "m_accel_z", "type": "continuous" },
-    { "name": "m_rotation_x", "type": "continuous" },
-    { "name": "m_rotation_y", "type": "continuous" },
-    { "name": "m_rotation_z", "type": "continuous" },
-    { "name": "w_accel_x", "type": "continuous" },
-    { "name": "w_accel_y", "type": "continuous" },
-    { "name": "w_accel_z", "type": "continuous" },
-    { "name": "w_rotation_x", "type": "continuous" },
-    { "name": "w_rotation_y", "type": "continuous" },
-    { "name": "w_rotation_z", "type": "continuous" }
-  ],
-  "targetColumns": [
-    { "name": "labels", "type": "nominal" }
-  ],
-  "initialDataFile": "src/test/resources/activity_data.csv"
+  "columns": [
+    { "kind": "input", "name": "m_accel_x", "type":"continuous" },
+    { "kind": "input", "name": "m_accel_y", "type": "continuous" },
+    { "kind": "input", "name": "m_accel_z", "type": "continuous" },
+    { "kind": "input", "name": "m_rotation_x", "type": "continuous" },
+    { "kind": "input", "name": "m_rotation_y", "type": "continuous" },
+    { "kind": "input", "name": "m_rotation_z", "type": "continuous" },
+    { "kind": "input", "name": "w_accel_x", "type": "continuous" },
+    { "kind": "input", "name": "w_accel_y", "type": "continuous" },
+    { "kind": "input", "name": "w_accel_z", "type": "continuous" },
+    { "kind": "input", "name": "w_rotation_x", "type": "continuous" },
+    { "kind": "input", "name": "w_rotation_y", "type": "continuous" },
+    { "kind": "input", "name": "w_rotation_z", "type": "continuous" },
+    { "kind": "target", "name": "labels", "type": "nominal" }
+  ]
 }
diff --git a/feedbackloop.learner_backup/src/main/resources/learner_activity_phone_and_watch.json b/feedbackloop.learner_backup/src/main/resources/learner_activity_phone_and_watch.json
index acabbdd4..189a3c95 100644
--- a/feedbackloop.learner_backup/src/main/resources/learner_activity_phone_and_watch.json
+++ b/feedbackloop.learner_backup/src/main/resources/learner_activity_phone_and_watch.json
@@ -16,9 +16,12 @@
   "targetItemNames": [
     "activity"
   ],
-  "definitionFile": "src/test/resources/activity_definition.json",
+  "nonTrivialOutputMappings": {
+    "labels": "activity"
+  },
+  "definitionFile": "./activity_definition.json",
   "dataFiles": [
-    "src/test/resources/activity_data.csv"
+    "./activity_data.csv"
   ],
   "saveModels": false
 }
diff --git a/feedbackloop.learner_backup/src/main/resources/learner_preferences_brightness_iris.json b/feedbackloop.learner_backup/src/main/resources/learner_preferences_brightness_iris.json
index 8b2c9ad3..b035fe92 100644
--- a/feedbackloop.learner_backup/src/main/resources/learner_preferences_brightness_iris.json
+++ b/feedbackloop.learner_backup/src/main/resources/learner_preferences_brightness_iris.json
@@ -6,9 +6,9 @@
   "targetItemNames": [
     "iris1_item"
   ],
-  "definitionFile": "src/test/resources/preference_definition.json",
+  "definitionFile": "./preference_definition.json",
   "dataFiles": [
-    "src/test/resources/preference_data.csv"
+    "./preference_data.csv"
   ],
   "saveModels": false
 }
diff --git a/feedbackloop.learner_backup/src/main/resources/loaded_learner_activity_phone_and_watch.json b/feedbackloop.learner_backup/src/main/resources/loaded_learner_activity_phone_and_watch.json
index 3ce0a511..ea4d6263 100644
--- a/feedbackloop.learner_backup/src/main/resources/loaded_learner_activity_phone_and_watch.json
+++ b/feedbackloop.learner_backup/src/main/resources/loaded_learner_activity_phone_and_watch.json
@@ -16,10 +16,13 @@
   "targetItemNames": [
     "activity"
   ],
-  "definitionFile": "src/test/resources/activity_definition.json",
+  "nonTrivialOutputMappings": {
+    "labels": "activity"
+  },
+  "definitionFile": "./activity_definition.json",
   "dataFiles": [
-    "src/test/resources/activity_network.eg",
-    "src/test/resources/activity_normalizer.json"
+    "./activity_network.eg",
+    "./activity_normalizer.json"
   ],
   "kind": "loaded"
 }
diff --git a/feedbackloop.learner_backup/src/main/resources/loaded_learner_preferences_brightness_iris.json b/feedbackloop.learner_backup/src/main/resources/loaded_learner_preferences_brightness_iris.json
index 2ec4d871..f06dd1e5 100644
--- a/feedbackloop.learner_backup/src/main/resources/loaded_learner_preferences_brightness_iris.json
+++ b/feedbackloop.learner_backup/src/main/resources/loaded_learner_preferences_brightness_iris.json
@@ -6,10 +6,10 @@
   "targetItemNames": [
     "iris1_item"
   ],
-  "definitionFile": "src/test/resources/preference_definition.json",
+  "definitionFile": "./preference_definition.json",
   "dataFiles": [
-    "src/test/resources/preference_network.eg",
-    "src/test/resources/preference_normalizer.json"
+    "./preference_network.eg",
+    "./preference_normalizer.json"
   ],
   "kind": "loaded"
 }
diff --git a/feedbackloop.learner_backup/src/main/resources/preference_definition.json b/feedbackloop.learner_backup/src/main/resources/preference_definition.json
index 0d0d5011..6ac043cd 100644
--- a/feedbackloop.learner_backup/src/main/resources/preference_definition.json
+++ b/feedbackloop.learner_backup/src/main/resources/preference_definition.json
@@ -1,12 +1,9 @@
 {
   "name": "preference",
-  "inputColumns":  [
-    { "name": "activity", "type":"nominal" },
-    { "name": "w_brightness", "type": "nominal" }
-  ],
-  "targetColumns": [
-    { "name": "label1", "type": "continuous" },
-    { "name": "label2", "type": "continuous" }
-  ],
-  "initialDataFile": "src/test/resources/preference_data.csv"
+  "columns":  [
+    { "kind": "input", "name": "activity", "type":"nominal" },
+    { "kind": "input", "name": "w_brightness", "type": "nominal" },
+    { "kind": "target", "name": "label1", "type": "continuous" },
+    { "kind": "target", "name": "label2", "type": "continuous" }
+  ]
 }
diff --git a/feedbackloop.learner_backup/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/LearnerSubjectUnderTest.java b/feedbackloop.learner_backup/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/LearnerSubjectUnderTest.java
index 69ca8bd7..3a42297c 100644
--- a/feedbackloop.learner_backup/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/LearnerSubjectUnderTest.java
+++ b/feedbackloop.learner_backup/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/LearnerSubjectUnderTest.java
@@ -19,8 +19,8 @@ class LearnerSubjectUnderTest {
   Root root;
   private MachineLearningHandlerFactoryImpl factory;
 
-  void init() {
-    root = LearnerTestUtils.createKnowledgeBase();
+  void init(LearnerTestSettings settings) {
+    root = LearnerTestUtils.createKnowledgeBase(settings);
     factory = new MachineLearningHandlerFactoryImpl();
     factory.setKnowledgeBaseRoot(root);
   }
diff --git a/feedbackloop.learner_backup/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/LearnerTest.java b/feedbackloop.learner_backup/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/LearnerTest.java
index 75d162aa..7457ed7a 100644
--- a/feedbackloop.learner_backup/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/LearnerTest.java
+++ b/feedbackloop.learner_backup/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/LearnerTest.java
@@ -29,6 +29,9 @@ public class LearnerTest {
   private static URL PREFERENCE_CONFIG;
   private static URL LOADED_PREFERENCE_CONFIG;
   private static URL PREFERENCE_DATA;
+  private static URL OCT_ACTIVITY_CONFIG;
+  private static URL OCT_LOADED_ACTIVITY_CONFIG;
+  private static URL OCT_ACTIVITY_DATA;
   private LearnerSubjectUnderTest sut;
 
   @BeforeClass
@@ -39,6 +42,10 @@ public class LearnerTest {
     PREFERENCE_CONFIG = resolveFromBaseURL("learner_preferences_brightness_iris.json");
     LOADED_PREFERENCE_CONFIG = resolveFromBaseURL("loaded_learner_preferences_brightness_iris.json");
     PREFERENCE_DATA = resolveFromBaseURL("preference_data.csv");
+
+    OCT_ACTIVITY_CONFIG = resolveFromBaseURL("2019-oct-28-learner.json");
+    OCT_LOADED_ACTIVITY_CONFIG = resolveFromBaseURL("2019-oct-28-loaded_learner.json");
+    OCT_ACTIVITY_DATA = resolveFromBaseURL("activity_data/28_08_2019_H14_14/result_all_items_EVERYTHING.csv");
   }
 
   private static URL resolveFromBaseURL(String filename) throws MalformedURLException {
@@ -48,7 +55,6 @@ public class LearnerTest {
   @Before
   public void initLearner() {
     sut = new LearnerSubjectUnderTest();
-    sut.init();
   }
 
   @Test
@@ -56,6 +62,7 @@ public class LearnerTest {
     LearnerTestUtils.testLearner(sut, settingsActivities());
   }
 
+  @Ignore
   @Test
   public void testPreferences() throws IOException {
     LearnerTestUtils.testLearner(sut, settingsPreferences());
@@ -73,11 +80,27 @@ public class LearnerTest {
         .setConfigURL(LOADED_PREFERENCE_CONFIG));
   }
 
+  @Ignore // takes longer than 10min
+  @Test
+  public void testOctoberActivities() throws IOException {
+    LearnerTestUtils.testLearner(sut, settingsActivities()
+        .setConfigURL(OCT_ACTIVITY_CONFIG)
+        .setDataURL(OCT_ACTIVITY_DATA));
+  }
+
+  @Ignore // not working currently
+  @Test
+  public void testLoadedOctoberActivities() throws IOException {
+    LearnerTestUtils.testLearner(sut, settingsActivities()
+        .setConfigURL(OCT_LOADED_ACTIVITY_CONFIG)
+        .setDataURL(OCT_ACTIVITY_DATA)
+        .setVerbose(true));
+  }
+
   private LearnerTestSettings settingsActivities() throws IOException {
     return new LearnerTestSettings()
         .setConfigURL(ACTIVITY_CONFIG)
         .setDataURL(ACTIVITY_DATA)
-        .setExpectedOutput(line -> line[12])
         .setOutputItemProvider(() -> sut.root.getSmartHomeEntityModel().getActivityItem())
         .setStateOfOutputItem(item -> sut.root.currentActivityName())
         .setFactoryTarget(ACTIVITY_RECOGNITION)
diff --git a/feedbackloop.learner_backup/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/LearnerTestConstants.java b/feedbackloop.learner_backup/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/LearnerTestConstants.java
index 27785f59..5ba44038 100644
--- a/feedbackloop.learner_backup/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/LearnerTestConstants.java
+++ b/feedbackloop.learner_backup/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/LearnerTestConstants.java
@@ -12,19 +12,22 @@ public interface LearnerTestConstants {
   double MAX_COLOR_DIFFERENCE = 0.2;
   /** Weights for difference (in order: Hue, Saturation, Brightness) when comparing colors */
   double[] COLOR_WEIGHTS = new double[]{0.8/360, 0.1/100, 0.1/100};
-  /** Names of item names for activity recognition, in test data */
-  String[] ACTIVITY_INPUT_ITEM_NAMES = new String[]{"m_accel_x", "m_accel_y", "m_accel_z", "m_rotation_x", "m_rotation_y",
-      "m_rotation_z", "w_accel_x", "w_accel_y", "w_accel_z", "w_rotation_x", "w_rotation_y", "w_rotation_z"};
-  /** Names of item names for preference learning, in test data */
-  String[] PREFERENCE_INPUT_ITEM_NAMES = new String[]{"activity", "w_brightness"};
   /** Name of the item which is targeted by preference learning, in test data */
   String PREFERENCE_OUTPUT_ITEM_NAME = "iris1_item";
   /** Labels of activities, in test data */
-  String[] ACTIVITY_NAMES = new String[]{"working",
-          "walking",
-          "dancing",
-          "lying",
-          "getting up",
-          "reading"
-      };
+  String[] ACTIVITY_NAMES = new String[]{
+      "working",
+      "walking",
+      "dancing",
+      "lying",
+      "getting up",
+      "reading",
+      "DoorClosedPIn",
+      "DoorClosedPOut",
+      "DoorOpenedPIn",
+      "DoorOpenedPOut",
+      "Reading",
+      "TVWatching",
+      "Working",
+  };
 }
diff --git a/feedbackloop.learner_backup/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/LearnerTestSettings.java b/feedbackloop.learner_backup/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/LearnerTestSettings.java
index d1751330..c016f604 100644
--- a/feedbackloop.learner_backup/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/LearnerTestSettings.java
+++ b/feedbackloop.learner_backup/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/LearnerTestSettings.java
@@ -24,17 +24,18 @@ import java.util.function.Supplier;
 class LearnerTestSettings {
   private URL configURL;
   private URL dataURL;
-  private Function<String[], String> expectedOutput;
+  private Function<List<String>, String> expectedOutput = targetValues -> targetValues.get(0);
   private final Map<String, BiConsumer<Item, String>> specialInputHandler = new HashMap<>();
   private Supplier<Item> outputItemProvider;
   private Function<Item, String> stateOfOutputItem;
   private CheckUpdate checkUpdate = String::equals;
   private MachineLearningHandlerFactory.MachineLearningHandlerFactoryTarget factoryTarget;
   private boolean singleUpdateList;
+  private boolean verbose = false;
 
   @Getter(AccessLevel.NONE)
   @Setter(AccessLevel.NONE)
-  private transient LearnerScenarioDefinition settings;
+  private transient LearnerScenarioDefinition scenarioSettings;
 
   @SuppressWarnings("SameParameterValue")
   LearnerTestSettings putSpecialInputHandler(String itemName, BiConsumer<Item, String> handler) {
@@ -43,13 +44,12 @@ class LearnerTestSettings {
   }
 
   LearnerTestSettings setConfigURL(URL configURL) throws IOException {
-    settings = LearnerScenarioDefinition.loadFrom(configURL);
+    scenarioSettings = LearnerScenarioDefinition.loadFrom(configURL);
     this.configURL = configURL;
     return this;
   }
 
-  List<String> getInputItemNames() {
-    return settings.relevantItemNames;
+  LearnerScenarioDefinition getScenarioSettings() {
+    return scenarioSettings;
   }
-
 }
diff --git a/feedbackloop.learner_backup/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/LearnerTestUtils.java b/feedbackloop.learner_backup/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/LearnerTestUtils.java
index 0e864cbb..5f693e29 100644
--- a/feedbackloop.learner_backup/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/LearnerTestUtils.java
+++ b/feedbackloop.learner_backup/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/LearnerTestUtils.java
@@ -1,24 +1,30 @@
 package de.tudresden.inf.st.eraser.feedbackloop.learner_backup;
 
+import com.opencsv.CSVParserBuilder;
 import com.opencsv.CSVReader;
+import com.opencsv.CSVReaderBuilder;
+import de.tudresden.inf.st.eraser.feedbackloop.learner_backup.data.LearnerScenarioDefinition;
+import de.tudresden.inf.st.eraser.feedbackloop.learner_backup.data.LearnerSettings;
+import de.tudresden.inf.st.eraser.feedbackloop.learner_backup.data.SimpleColumnDefinition;
 import de.tudresden.inf.st.eraser.jastadd.model.*;
 import de.tudresden.inf.st.eraser.util.ParserUtils;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
-import org.junit.Test;
+import org.encog.ml.data.versatile.columns.ColumnType;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.Reader;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.stream.Stream;
 
 import static de.tudresden.inf.st.eraser.feedbackloop.learner_backup.LearnerTestConstants.COLOR_WEIGHTS;
 import static de.tudresden.inf.st.eraser.feedbackloop.learner_backup.LearnerTestConstants.MAX_COLOR_DIFFERENCE;
-import static org.hamcrest.Matchers.greaterThan;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
 
 /**
  * Utility methods to keep test code clean.
@@ -29,24 +35,17 @@ public class LearnerTestUtils {
 
   private static final Logger logger = LogManager.getLogger(LearnerTestUtils.class);
 
-  static Root createKnowledgeBase() {
+  static Root createKnowledgeBase(LearnerTestSettings settings) {
     Root result = Root.createEmptyRoot();
     Group group = new Group();
     result.getSmartHomeEntityModel().addGroup(group);
     // init items
-    Stream.concat(Arrays.stream(LearnerTestConstants.ACTIVITY_INPUT_ITEM_NAMES),
-        Stream.concat(Arrays.stream(LearnerTestConstants.PREFERENCE_INPUT_ITEM_NAMES), Stream.of(LearnerTestConstants.PREFERENCE_OUTPUT_ITEM_NAME)))
+    Stream.concat(settings.getScenarioSettings().relevantItemNames.stream(),
+                  settings.getScenarioSettings().targetItemNames.stream())
         .distinct().forEach(
         itemName -> {
           if (itemName.equals("activity")) return;
-          Item item;
-          switch(itemName) {
-            case LearnerTestConstants.PREFERENCE_OUTPUT_ITEM_NAME: item = new ColorItem(); break;
-            case "w_brightness": item = new StringItem(); break;
-            default:
-              item = new NumberItem();
-          }
-          item.setID(itemName);
+          Item item = createItem(itemName);
           ParserUtils.createMqttTopic(item, itemName, result);
           group.addItem(item);
         }
@@ -58,36 +57,90 @@ public class LearnerTestUtils {
     return result;
   }
 
+  private static Item createItem(String itemName) {
+    Item item;
+    if (itemName.contains("OpenClose")) {
+      // contact item
+      item = new ContactItem();
+    } else if (itemName.contains("Fibaro")) {
+      // boolean item
+      item = new SwitchItem();
+    } else {
+      switch (itemName) {
+        case "work_device_online_state":
+          item = new SwitchItem();
+          break;
+        case LearnerTestConstants.PREFERENCE_OUTPUT_ITEM_NAME:
+          item = new ColorItem();
+          break;
+        case "w_brightness":
+          item = new StringItem();
+          break;
+        default: item = new NumberItem();
+      }
+    }
+    item.setID(itemName);
+    return item;
+  }
+
   static void testLearner(LearnerSubjectUnderTest sut, LearnerTestSettings settings) {
+    sut.init(settings);
     // maybe use factory.createModel() here instead
     // go through same csv as for training and test some of the values
     int correct = 0, wrong = 0;
     try(InputStream is = settings.getDataURL().openStream();
         Reader reader = new InputStreamReader(is);
-        CSVReader csvreader = new CSVReader(reader)) {
+        CSVReader csvreader = createCSVReader(reader, settings)) {
       sut.initFor(settings.getFactoryTarget(), settings.getConfigURL());
       int index = 0;
+      LearnerScenarioDefinition scenarioSettings = settings.getScenarioSettings();
+      LearnerSettings definition = LearnerSettings.loadFrom(scenarioSettings.getDefinitionFileAsURL());
+      List<String> targetValues;
       for (String[] line : csvreader) {
+        // only check every 10th line, push an update for every input column
         if (++index % 10 == 0) {
-          // only check every 10th line, push an update for every 12 input columns
-          List<Item> itemsToUpdate = new ArrayList<>(settings.getInputItemNames().size());
-          for (int i = 0; i < settings.getInputItemNames().size(); i++) {
-            String itemName = settings.getInputItemNames().get(i);
+          // Attention: Not every column might be relevant
+          targetValues = new ArrayList<>();
+          int lineSize = line.length;
+          int inputSize = scenarioSettings.relevantItemNames.size();
+          List<Item> itemsToUpdate = new ArrayList<>(inputSize);
+          for (int i = 0; i < lineSize; i++) {
+            SimpleColumnDefinition column = definition.columns.get(i);
+            switch (column.kind) {
+              case input:
+                // do nothing
+                break;
+              case target:
+                targetValues.add(line[i]);
+                continue;
+              case ignored:
+                continue;
+            }
+            if (column.type == ColumnType.ignore) {
+              continue;
+            }
+            // use itemName == name of column (or a non-trivial mapping, if any)
+            String itemName = scenarioSettings.nonTrivialOutputMappings.getOrDefault(column.name, column.name);
             Item item = sut.root.getSmartHomeEntityModel().resolveItem(itemName)
                 .orElseThrow(() -> new AssertionError("Item " + itemName + " not found"));
             if (settings.getSpecialInputHandler().containsKey(itemName)) {
+              if (settings.isVerbose()) {
+                logger.debug("Setting {} {} using special handler and value '{}' (column {})",
+                    item, item.getID(), line[i], i);
+              }
               settings.getSpecialInputHandler().get(itemName).accept(item, line[i]);
             } else {
+              if (settings.isVerbose()) {
+                logger.debug("Setting {} {} using '{}' (column {})", item, item.getID(), line[i], i);
+              }
               item.setStateFromString(line[i]);
             }
-            if (settings.isSingleUpdateList()) {
-              itemsToUpdate.add(item);
-            } else {
-              sut.encoder.newData(Collections.singletonList(item));
-            }
+            itemsToUpdate.add(item);
           }
           if (settings.isSingleUpdateList()) {
             sut.encoder.newData(itemsToUpdate);
+          } else {
+            itemsToUpdate.forEach(item -> sut.encoder.newData(Collections.singletonList(item)));
           }
           MachineLearningResult result = sut.decoder.classify();
           // check if only one item is to be updated
@@ -97,16 +150,19 @@ public class LearnerTestUtils {
           assertEquals("Output item not to be updated!", settings.getOutputItemProvider().get(), update.getItem());
           update.apply();
           // check if the correct new state was set
-          String expected = settings.getExpectedOutput().apply(line);
+          assertThat("No target values found in this row!", targetValues, not(empty()));
+          String expected = settings.getExpectedOutput().apply(targetValues);
           String actual = settings.getStateOfOutputItem().apply(update.getItem());
           if (settings.getCheckUpdate().assertEquals(expected, actual)) {
             correct++;
           } else {
             wrong++;
-            logger.debug("Result not equal, expected '{}' but was '{}'", expected, actual);
+            if (settings.isVerbose()) {
+              logger.debug("Result not equal, expected '{}' but was '{}'", expected, actual);
+            }
           }
-        }
-      }
+        } // end if index % 10 == 0
+      } // end for
     } catch (IOException | ClassNotFoundException e) {
       throw new AssertionError(e);
     } finally {
@@ -118,14 +174,28 @@ public class LearnerTestUtils {
     assertThat(accuracy, greaterThan(LearnerTestConstants.MIN_ACCURACY));
   }
 
+  private static CSVReader createCSVReader(Reader reader, LearnerTestSettings settings) {
+    char separator;
+    switch(settings.getScenarioSettings().csvFormat) {
+      case "DECIMAL_POINT": separator=','; break;
+      case "DECIMAL_COMMA": separator=';'; break;
+      default:
+        logger.warn("Unknown CSV format, using default comma as separator");
+        separator=',';
+    }
+    return new CSVReaderBuilder(reader)
+        .withCSVParser(new CSVParserBuilder().withSeparator(separator).build())
+        .build();
+  }
+
   @FunctionalInterface
   public interface CheckUpdate {
     boolean assertEquals(String expected, String actual);
   }
 
-  static String decodeOutput(String[] line) {
-    int color = Integer.parseInt(line[2]);
-    int brightness = Integer.parseInt(line[3]);
+  static String decodeOutput(List<String> targetValues) {
+    int color = Integer.parseInt(targetValues.get(0));
+    int brightness = Integer.parseInt(targetValues.get(1));
     return TupleHSB.of(color, 100, brightness).toString();
   }
 
@@ -153,76 +223,4 @@ public class LearnerTestUtils {
 //        diffHue, diffSaturation, diffBrightness, total, MAX_COLOR_DIFFERENCE);
     return total < MAX_COLOR_DIFFERENCE;
   }
-
-  @Test
-  public void testColorSimilar() {
-    Map<String, TupleHSB> colors = new HashMap<>();
-
-    // reddish target colors
-    colors.put("pink", TupleHSB.of(350, 100, 82));
-    colors.put("orangeRed", TupleHSB.of(16, 100, 45));
-    colors.put("lightPink", TupleHSB.of(351, 100, 80));
-    colors.put("darkSalmon", TupleHSB.of(15, 71, 67));
-    colors.put("lightCoral", TupleHSB.of(0, 78, 63));
-    colors.put("darkRed", TupleHSB.of(0, 100, 16));
-    colors.put("indianRed", TupleHSB.of(0, 53, 49));
-    colors.put("lavenderBlush", TupleHSB.of(340, 100, 95));
-    colors.put("lavender", TupleHSB.of(240, 66, 90));
-    String[] targetColors = new String[]{"pink", "orangeRed", "lightPink", "darkSalmon", "lightCoral",
-        "darkRed", "indianRed", "lavenderBlush", "lavender"};
-
-    // reference colors
-    colors.put("blue", TupleHSB.of(240, 100, 11));
-    colors.put("blueViolet", TupleHSB.of(271, 75, 36));
-    colors.put("magenta", TupleHSB.of(300, 100, 41));
-    colors.put("purple", TupleHSB.of(300, 100, 20));
-    colors.put("red", TupleHSB.of(0, 100, 29));
-    colors.put("tomato", TupleHSB.of(9, 100, 55));
-    colors.put("orange", TupleHSB.of(39, 100, 67));
-    colors.put("yellow", TupleHSB.of(60, 100, 88));
-    colors.put("yellowGreen", TupleHSB.of(80, 60, 67));
-    colors.put("green", TupleHSB.of(120, 100, 29));
-    colors.put("springGreen", TupleHSB.of(150, 100, 64));
-    colors.put("cyan", TupleHSB.of(180, 100, 69));
-    colors.put("ivory", TupleHSB.of(60, 100, 98));
-
-    String[] referenceColors = new String[]{"blue", "blueViolet", "magenta", "purple", "red", "tomato",
-        "orange", "yellow", "yellowGreen", "green", "springGreen", "cyan", "ivory"};
-
-    /* Code to help producing similarity matrix */
-//    for (String target : targetColors) {
-//      String tmp = "";
-//      for (String reference : referenceColors) {
-//        tmp += assertColorSimilar(colors, target, reference) ? "x" : " ";
-//        tmp += ",";
-//      }
-//      System.out.println( "***" + target + ": " + tmp);
-//    }
-
-    String[] similarityMatrix = new String[]{
-        "blue, blueViolet, magenta, purple, red, tomato, orange, yellow, yellowGreen, green, springGreen, cyan, ivory", // <- reference colors
-        "    ,           ,    x   ,   x   ,  x ,    x  ,   x   ,   x   ,            ,      ,            ,     ,   x  ", // pink
-        "    ,           ,    x   ,   x   ,  x ,    x  ,   x   ,   x   ,            ,      ,            ,     ,   x  ", // orangeRed
-        "    ,           ,    x   ,   x   ,  x ,    x  ,   x   ,   x   ,            ,      ,            ,     ,   x  ", // lightPink
-        "    ,           ,        ,       ,  x ,    x  ,   x   ,   x   ,      x     ,      ,            ,     ,   x  ", // darkSalmon
-        "    ,           ,    x   ,   x   ,  x ,    x  ,   x   ,   x   ,      x     ,      ,            ,     ,   x  ", // lightCoral
-        "    ,           ,    x   ,   x   ,  x ,    x  ,   x   ,       ,            ,      ,            ,     ,      ", // darkRed
-        "    ,           ,    x   ,       ,  x ,    x  ,   x   ,       ,            ,      ,            ,     ,      ", // indianRed
-        "    ,           ,    x   ,   x   ,  x ,    x  ,   x   ,   x   ,            ,      ,            ,     ,   x  ", // lavenderBlush
-        " x  ,     x     ,        ,       ,    ,       ,       ,       ,            ,      ,            ,  x  ,      "}; // lavender
-
-    for (int targetIndex = 0; targetIndex < targetColors.length; targetIndex++) {
-      String target = targetColors[targetIndex];
-      String[] expectedValues = similarityMatrix[targetIndex + 1].split(",");
-      for (int referenceIndex = 0; referenceIndex < referenceColors.length; referenceIndex++) {
-        String reference = referenceColors[referenceIndex];
-        boolean expectedToBeSimilar = expectedValues[referenceIndex].contains("x");
-        String message = String.format("%s iss%s expected to be similar to %s, but %s!",
-            target, expectedToBeSimilar ? "" : " not", reference, expectedToBeSimilar ? "differs" : "it was");
-        assertEquals(message, expectedToBeSimilar,
-            colorSimilar(colors.get(reference).toString(), colors.get(target).toString()));
-      }
-    }
-  }
-
 }
diff --git a/feedbackloop.learner_backup/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/LearnerTestUtilsTest.java b/feedbackloop.learner_backup/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/LearnerTestUtilsTest.java
new file mode 100644
index 00000000..49209f2e
--- /dev/null
+++ b/feedbackloop.learner_backup/src/test/java/de/tudresden/inf/st/eraser/feedbackloop/learner_backup/LearnerTestUtilsTest.java
@@ -0,0 +1,88 @@
+package de.tudresden.inf.st.eraser.feedbackloop.learner_backup;
+
+import de.tudresden.inf.st.eraser.jastadd.model.TupleHSB;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests for the utility methods.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class LearnerTestUtilsTest {
+
+  @Test
+  public void testColorSimilar() {
+    Map<String, TupleHSB> colors = new HashMap<>();
+
+    // reddish target colors
+    colors.put("pink", TupleHSB.of(350, 100, 82));
+    colors.put("orangeRed", TupleHSB.of(16, 100, 45));
+    colors.put("lightPink", TupleHSB.of(351, 100, 80));
+    colors.put("darkSalmon", TupleHSB.of(15, 71, 67));
+    colors.put("lightCoral", TupleHSB.of(0, 78, 63));
+    colors.put("darkRed", TupleHSB.of(0, 100, 16));
+    colors.put("indianRed", TupleHSB.of(0, 53, 49));
+    colors.put("lavenderBlush", TupleHSB.of(340, 100, 95));
+    colors.put("lavender", TupleHSB.of(240, 66, 90));
+    String[] targetColors = new String[]{"pink", "orangeRed", "lightPink", "darkSalmon", "lightCoral",
+        "darkRed", "indianRed", "lavenderBlush", "lavender"};
+
+    // reference colors
+    colors.put("blue", TupleHSB.of(240, 100, 11));
+    colors.put("blueViolet", TupleHSB.of(271, 75, 36));
+    colors.put("magenta", TupleHSB.of(300, 100, 41));
+    colors.put("purple", TupleHSB.of(300, 100, 20));
+    colors.put("red", TupleHSB.of(0, 100, 29));
+    colors.put("tomato", TupleHSB.of(9, 100, 55));
+    colors.put("orange", TupleHSB.of(39, 100, 67));
+    colors.put("yellow", TupleHSB.of(60, 100, 88));
+    colors.put("yellowGreen", TupleHSB.of(80, 60, 67));
+    colors.put("green", TupleHSB.of(120, 100, 29));
+    colors.put("springGreen", TupleHSB.of(150, 100, 64));
+    colors.put("cyan", TupleHSB.of(180, 100, 69));
+    colors.put("ivory", TupleHSB.of(60, 100, 98));
+
+    String[] referenceColors = new String[]{"blue", "blueViolet", "magenta", "purple", "red", "tomato",
+        "orange", "yellow", "yellowGreen", "green", "springGreen", "cyan", "ivory"};
+
+    /* Code to help producing similarity matrix */
+//    for (String target : targetColors) {
+//      String tmp = "";
+//      for (String reference : referenceColors) {
+//        tmp += assertColorSimilar(colors, target, reference) ? "x" : " ";
+//        tmp += ",";
+//      }
+//      System.out.println( "***" + target + ": " + tmp);
+//    }
+
+    String[] similarityMatrix = new String[]{
+        "blue, blueViolet, magenta, purple, red, tomato, orange, yellow, yellowGreen, green, springGreen, cyan, ivory", // <- reference colors
+        "    ,           ,    x   ,   x   ,  x ,    x  ,   x   ,   x   ,            ,      ,            ,     ,   x  ", // pink
+        "    ,           ,    x   ,   x   ,  x ,    x  ,   x   ,   x   ,            ,      ,            ,     ,   x  ", // orangeRed
+        "    ,           ,    x   ,   x   ,  x ,    x  ,   x   ,   x   ,            ,      ,            ,     ,   x  ", // lightPink
+        "    ,           ,        ,       ,  x ,    x  ,   x   ,   x   ,      x     ,      ,            ,     ,   x  ", // darkSalmon
+        "    ,           ,    x   ,   x   ,  x ,    x  ,   x   ,   x   ,      x     ,      ,            ,     ,   x  ", // lightCoral
+        "    ,           ,    x   ,   x   ,  x ,    x  ,   x   ,       ,            ,      ,            ,     ,      ", // darkRed
+        "    ,           ,    x   ,       ,  x ,    x  ,   x   ,       ,            ,      ,            ,     ,      ", // indianRed
+        "    ,           ,    x   ,   x   ,  x ,    x  ,   x   ,   x   ,            ,      ,            ,     ,   x  ", // lavenderBlush
+        " x  ,     x     ,        ,       ,    ,       ,       ,       ,            ,      ,            ,  x  ,      "}; // lavender
+
+    for (int targetIndex = 0; targetIndex < targetColors.length; targetIndex++) {
+      String target = targetColors[targetIndex];
+      String[] expectedValues = similarityMatrix[targetIndex + 1].split(",");
+      for (int referenceIndex = 0; referenceIndex < referenceColors.length; referenceIndex++) {
+        String reference = referenceColors[referenceIndex];
+        boolean expectedToBeSimilar = expectedValues[referenceIndex].contains("x");
+        String message = String.format("%s iss%s expected to be similar to %s, but %s!",
+            target, expectedToBeSimilar ? "" : " not", reference, expectedToBeSimilar ? "differs" : "it was");
+        assertEquals(message, expectedToBeSimilar,
+            LearnerTestUtils.colorSimilar(colors.get(reference).toString(), colors.get(target).toString()));
+      }
+    }
+  }
+}
diff --git a/feedbackloop.learner_backup/src/test/resources/2019-oct-28-activity_definition.json b/feedbackloop.learner_backup/src/test/resources/2019-oct-28-activity_definition.json
new file mode 120000
index 00000000..de4c267d
--- /dev/null
+++ b/feedbackloop.learner_backup/src/test/resources/2019-oct-28-activity_definition.json
@@ -0,0 +1 @@
+../../../src/main/resources/2019-oct-28-activity_definition.json
\ No newline at end of file
diff --git a/feedbackloop.learner_backup/src/test/resources/2019-oct-28-activity_network.eg b/feedbackloop.learner_backup/src/test/resources/2019-oct-28-activity_network.eg
new file mode 120000
index 00000000..cc1061a8
--- /dev/null
+++ b/feedbackloop.learner_backup/src/test/resources/2019-oct-28-activity_network.eg
@@ -0,0 +1 @@
+../../../src/main/resources/2019-oct-28-activity_network.eg
\ No newline at end of file
diff --git a/feedbackloop.learner_backup/src/test/resources/2019-oct-28-activity_normalizer.json b/feedbackloop.learner_backup/src/test/resources/2019-oct-28-activity_normalizer.json
new file mode 120000
index 00000000..155f7b9f
--- /dev/null
+++ b/feedbackloop.learner_backup/src/test/resources/2019-oct-28-activity_normalizer.json
@@ -0,0 +1 @@
+../../../src/main/resources/2019-oct-28-activity_normalizer.json
\ No newline at end of file
diff --git a/feedbackloop.learner_backup/src/test/resources/2019-oct-28-learner.json b/feedbackloop.learner_backup/src/test/resources/2019-oct-28-learner.json
new file mode 120000
index 00000000..dff51597
--- /dev/null
+++ b/feedbackloop.learner_backup/src/test/resources/2019-oct-28-learner.json
@@ -0,0 +1 @@
+../../../src/main/resources/2019-oct-28-learner.json
\ No newline at end of file
diff --git a/feedbackloop.learner_backup/src/test/resources/2019-oct-28-loaded_learner.json b/feedbackloop.learner_backup/src/test/resources/2019-oct-28-loaded_learner.json
new file mode 120000
index 00000000..9fb3eb16
--- /dev/null
+++ b/feedbackloop.learner_backup/src/test/resources/2019-oct-28-loaded_learner.json
@@ -0,0 +1 @@
+../../../src/main/resources/2019-oct-28-loaded_learner.json
\ No newline at end of file
-- 
GitLab