/* * Copyright (C) 2013 Serdar * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package de.fub.maps.project.detector.model.inference; import de.fub.maps.project.detector.factories.nodes.inference.InferenceModelNode; import de.fub.maps.project.detector.model.Detector; import static de.fub.maps.project.detector.model.inference.InferenceMode.CROSS_VALIDATION_MODE; import de.fub.maps.project.detector.model.inference.features.FeatureProcess; import de.fub.maps.project.detector.model.inference.processhandler.CrossValidationProcessHandler; import de.fub.maps.project.detector.model.inference.processhandler.InferenceDataProcessHandler; import de.fub.maps.project.detector.model.inference.processhandler.InferenceModelProcessHandler; import de.fub.maps.project.detector.model.inference.processhandler.TrainingsDataProcessHandler; import de.fub.maps.project.detector.model.inference.ui.InferenceModelSettingForm; import de.fub.maps.project.detector.model.process.DetectorProcess; import de.fub.maps.project.detector.model.xmls.Features; import de.fub.maps.project.detector.model.xmls.InferenceModelDescriptor; import de.fub.maps.project.detector.model.xmls.ProcessDescriptor; import de.fub.maps.project.detector.model.xmls.ProcessHandlerDescriptor; import de.fub.maps.project.detector.model.xmls.ProcessHandlers; import de.fub.maps.project.detector.model.xmls.TransportMode; import de.fub.utilsmodule.icons.IconRegister; import de.fub.utilsmodule.synchronizer.ModelSynchronizer; import java.awt.Image; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JComponent; import javax.swing.JToolBar; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.openide.nodes.Node; import org.openide.util.Exceptions; import org.openide.util.Lookup; import weka.classifiers.Classifier; import weka.core.Attribute; /** * comment 2 3 * * @author Serdar */ public abstract class AbstractInferenceModel extends DetectorProcess<InferenceModelInputDataSet, InferenceModelResultDataSet> { private static final Logger LOG = Logger.getLogger(AbstractInferenceModel.class.getName()); public static final String OPTIONS_PROPERTY_SECTION = "inference.model.option"; /** * constant for the classes Weka Atttribute creation. */ public static final String CLASSES_ATTRIBUTE_NAME = "classes"; /** * * Property name for propergating feature list changes. */ public static final String PROP_NAME_FEATURE_LIST = "abstractInferenceMode.featureList"; /** * Property name for propergating inference mode changes. */ public static final String PROP_NAME_INFERENCE_MODE = "abstractInferenceMode.inferenceMode"; /** * The name of the icon for the ui representation, which will be access via * the utility class IconRegister. */ private static final String ICON_NAME = "inferenceModelIcon.png"; /** * MUTEX to synchronize. */ private final Object INFERENCE_MODEL_LISTENER_MUTEX = new Object(); /** * The result value of the classification. */ private final InferenceModelResultDataSet outputDataSet = new InferenceModelResultDataSet(); /** * Holds the processhandlers for the three inference modes. * */ private final EnumMap<InferenceMode, Class<? extends InferenceModelProcessHandler>> processHandlerMap = new EnumMap<InferenceMode, Class<? extends InferenceModelProcessHandler>>(InferenceMode.class); /** * */ private final EnumMap<InferenceMode, InferenceModelProcessHandler> processHandlerInstanceMap = new EnumMap<InferenceMode, InferenceModelProcessHandler>(InferenceMode.class); /** * */ private final HashSet<InferenceModelListener> listenerSet = new HashSet<InferenceModelListener>(); /** * */ private final HashSet<FeatureProcess> featureList = new HashSet<FeatureProcess>(); /** * */ private final List<Attribute> attributeList = new ArrayList<Attribute>(); /** * */ private final Map<String, Attribute> attributeMap = new HashMap<String, Attribute>(); /** * */ protected ModelSynchronizer.ModelSynchronizerClient modelSynchronizerClient; /** * */ private InferenceModelInputDataSet inputDataSet; /** * */ private InferenceMode inferenceMode = InferenceMode.ALL_MODE; /** * */ private Classifier classifier; /** * */ private InferenceModelDescriptor inferenceModelDescriptor = null; public AbstractInferenceModel() { } @Override protected void setDetector(Detector detector) { super.setDetector(detector); if (detector != null) { modelSynchronizerClient = detector.create(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { reinit(); } }); } reinit(); } /** * initialises this inference model. */ private void reinit() { if (getInferenceModelDescriptor() != null) { featureList.clear(); processHandlerMap.clear(); // get all features description and instanciate the features. Features features = getInferenceModelDescriptor().getFeatures(); if (features != null) { FeatureProcess feature = null; HashSet<String> featureType = new HashSet<String>(); for (ProcessDescriptor featureDescriptor : features.getFeatureList()) { if (featureDescriptor != null && !featureType.contains(featureDescriptor.getName())) { featureType.add(featureDescriptor.getName()); try { feature = FeatureProcess.find(featureDescriptor, getDetector()); if (feature != null) { addFeature(feature); } } catch (DetectorProcessNotFoundException ex) { Exceptions.printStackTrace(ex); } } } featureType.clear(); } // get all processHandler descriptions and instanciate the processhandlers ProcessHandlers inferenceModelProcessHandlers = getInferenceModelDescriptor().getInferenceModelProcessHandlers(); if (inferenceModelProcessHandlers != null) { InferenceModelProcessHandler processHandler = null; for (ProcessHandlerDescriptor processHandlerDescriptor : inferenceModelProcessHandlers.getProcessHandlerList()) { try { processHandler = InferenceModelProcessHandler.find(processHandlerDescriptor, AbstractInferenceModel.this); processHandlerInstanceMap.put(processHandlerDescriptor.getInferenceMode(), processHandler); } catch (InstantiationException ex) { LOG.log(Level.SEVERE, ex.getMessage(), ex); } } } initAttributes(); } } /** * Returns the data synchronizer client. Via this synchornizer visual * components can use this client to receive change events of the inference * model descriptor. * * @return ModuleSynchronizerClient */ public ModelSynchronizer.ModelSynchronizerClient getModelSynchronizerClient() { return modelSynchronizerClient; } /** * Returns the inference model descriptor if this inference model. If this * model has a parent Detector, then the inferenceModelDescriptor will be * the one specified in the DetectorDescriptor, otherwise a specified * default descriptor will be created via the * <code>createDefaultDescriptor</code>. * * @return InferenceModelDescriptor or null if the creation failed. */ public InferenceModelDescriptor getInferenceModelDescriptor() { if (inferenceModelDescriptor == null) { if (getDetector() == null) { inferenceModelDescriptor = createDefaultDescriptor(); } else { inferenceModelDescriptor = getDetector().getDetectorDescriptor().getInferenceModel(); } } return inferenceModelDescriptor; } /** * Starts the process depending on the current inference mode. * */ @Override protected void start() { switch (inferenceMode) { case INFERENCE_MODE: startInference(); break; case TRAININGS_MODE: startTraining(); break; case ALL_MODE: startTraining(); startInference(); break; case CROSS_VALIDATION_MODE: // TODO not supported anymore break; default: throw new IllegalArgumentException(MessageFormat.format("{0} not supported", inferenceMode)); //NO18N } } /** * * @return */ public Collection<Attribute> getAttributes() { return attributeList; } /** * * @return */ public Map<String, Attribute> getAttributeMap() { return attributeMap; } /** * Initialised the attributeList and attributeMap of this inference model. */ private void initAttributes() { if (getDetector() != null && getDetector().getDetectorDescriptor() != null && getDetector().getDetectorDescriptor().getDatasets() != null && getDetector().getDetectorDescriptor().getDatasets().getTrainingSet() != null && getDetector().getDetectorDescriptor().getDatasets().getTrainingSet().getTransportModeList() != null) { attributeList.clear(); attributeMap.clear(); HashSet<String> classes = new HashSet<String>(); List<TransportMode> transportModeList = getDetector().getDetectorDescriptor().getDatasets().getTrainingSet().getTransportModeList(); for (TransportMode transportMode : transportModeList) { classes.add(transportMode.getName()); } // the class label attribute will be the first attribute // in the list of attributes. // this is more like a convention ArrayList<String> classLabels = new ArrayList<String>(classes); Collections.sort(classLabels); Attribute attribute = new Attribute(CLASSES_ATTRIBUTE_NAME, classLabels); if (!attributeMap.containsKey(CLASSES_ATTRIBUTE_NAME)) { attributeList.add(attribute); attributeMap.put(CLASSES_ATTRIBUTE_NAME, attribute); for (FeatureProcess feature : getFeatureList()) { if (feature != null) { attribute = new Attribute(feature.getName()); // make sure there was no attribute overwritten if (!attributeMap.containsKey(feature.getName())) { attributeMap.put(feature.getName(), attribute); attributeList.add(attribute); } else { LOG.log(Level.SEVERE, "attribute {0} was overwritten. this should never happen and is a bug", feature.getName()); } } } } } } /** * Note : The classifier[, , ] should not be trained when handed over to the * crossValidateModel method. Why? If the classifier does not abide to the * Weka convention that a classifier must be re-initialized every time the * buildClassifier method is called (in other words: subsequent calls to the * buildClassifier method always return the same results), you will get * inconsistent and worthless results. The crossValidateModel takes care of * training and evaluating the classifier. (It creates a copy of the * original classifier that you hand over to the crossValidateModel for each * run of the cross-validation.) * * @see {@link http://weka.wikispaces.com/Use+WEKA+in+your+Java+code} * * Starts the training process of this inference model. throws an * IllegalStateException if this model does not contain any features. * */ private void startTraining() { if (!getAttributes().isEmpty()) { InferenceModelProcessHandler processHandler = null; // first start cross validation of classifier. Important see comment above fireStartEvent(CROSS_VALIDATION_MODE); processHandler = getProcessHandlerInstance(CROSS_VALIDATION_MODE); processHandler.start(); fireFinishedEvent(CROSS_VALIDATION_MODE); // then training with test of classifier fireStartEvent(InferenceMode.TRAININGS_MODE); processHandler = getProcessHandlerInstance(InferenceMode.TRAININGS_MODE); processHandler.start(); fireFinishedEvent(InferenceMode.TRAININGS_MODE); } else { throw new IllegalStateException("There are no attributes/features registered to use for the classifier!"); // NO18N } } /** * Starts the inference process of this inference model. */ private void startInference() { if (getClassifier() != null) { InferenceModelProcessHandler processHandler = null; // third phase inference fireStartEvent(InferenceMode.INFERENCE_MODE); processHandler = getProcessHandlerInstance(InferenceMode.INFERENCE_MODE); processHandler.start(); fireFinishedEvent(InferenceMode.INFERENCE_MODE); } } /** * * @param inferenceMode1 * @param inferenceModelProcessHandleClass */ public final void putInferenceProcessHandler(InferenceMode inferenceMode1, Class<? extends InferenceModelProcessHandler> inferenceModelProcessHandleClass) { if (inferenceMode1 != null && inferenceModelProcessHandleClass != null) { processHandlerMap.put(inferenceMode, inferenceModelProcessHandleClass); } } /** * Returns a list with all InferenceModelProcessHander instances of this * inference model. * * @return a list of InferenceModelProcessHandlers */ public List<InferenceModelProcessHandler> getProcessHandlers() { List<InferenceModelProcessHandler> list = new ArrayList<InferenceModelProcessHandler>(processHandlerInstanceMap.size()); for (InferenceMode mode : InferenceMode.values()) { InferenceModelProcessHandler processHandlerInstance = getProcessHandlerInstance(mode); if (processHandlerInstance != null) { list.add(processHandlerInstance); } } return list; } /** * Returns the ProcessHandler instance for the specified InferenceMode. If * there is no ProcessHandler registered for the specified InferenceMode, * then a default instance for the InferenceMode will be created if * possible. * * @param infMode1 The InferenceMode for which a ProcessHandler should be * returned. * @return a InferenceModelProcessHander or null if there is no instance * could be created for the specified InferenceMode. */ public InferenceModelProcessHandler getProcessHandlerInstance(InferenceMode infMode1) { InferenceModelProcessHandler processHandler = processHandlerInstanceMap.get(infMode1); if (processHandler == null) { Class<? extends InferenceModelProcessHandler> clazz = processHandlerMap.get(infMode1); if (clazz != null) { try { processHandler = InferenceModelProcessHandler.find(clazz.getName(), AbstractInferenceModel.this); } catch (InstantiationException ex) { Exceptions.printStackTrace(ex); } } // fall back to default process handlers if (processHandler == null) { switch (infMode1) { case TRAININGS_MODE: processHandler = new TrainingsDataProcessHandler(AbstractInferenceModel.this); break; case CROSS_VALIDATION_MODE: processHandler = new CrossValidationProcessHandler(AbstractInferenceModel.this); break; case INFERENCE_MODE: processHandler = new InferenceDataProcessHandler(AbstractInferenceModel.this); break; case ALL_MODE: // do nothing break; default: throw new IllegalArgumentException(MessageFormat.format("{0} not supported!", infMode1)); // NO18N } processHandlerInstanceMap.put(infMode1, processHandler); } else { processHandlerInstanceMap.put(infMode1, processHandler); } } return processHandler; } /** * * @param mode */ protected void fireStartEvent(InferenceMode mode) { synchronized (INFERENCE_MODEL_LISTENER_MUTEX) { for (InferenceModelListener listener : listenerSet) { switch (mode) { case TRAININGS_MODE: listener.startedTraining(); break; case CROSS_VALIDATION_MODE: listener.startedCrossValidation(); break; case INFERENCE_MODE: listener.startedClustering(); break; case ALL_MODE: // do nothing break; default: throw new IllegalArgumentException("Event not supported"); //NO18N } } } } /** * * @param mode */ protected void fireFinishedEvent(InferenceMode mode) { synchronized (INFERENCE_MODEL_LISTENER_MUTEX) { for (InferenceModelListener listener : listenerSet) { switch (mode) { case TRAININGS_MODE: listener.finishedTraining(); break; case CROSS_VALIDATION_MODE: listener.finishedCrossValidation(); break; case INFERENCE_MODE: listener.finishedClustering(); break; case ALL_MODE: // do nothing break; default: throw new IllegalArgumentException("Event not supported"); //NO18N } } } } /** * Returns the current inference mode of this model. * * @return InferenceMode instance. */ public InferenceMode getInferenceMode() { return inferenceMode; } /** * * @param inferenceMode */ public void setInferenceMode(InferenceMode inferenceMode) { Object oldValue = this.inferenceMode; this.inferenceMode = inferenceMode; pcs.firePropertyChange(PROP_NAME_INFERENCE_MODE, oldValue, this.inferenceMode); } /** * * @param inputDataset */ @Override public void setInput(InferenceModelInputDataSet inputDataset) { this.inputDataSet = inputDataset; } /** * Returns the input data model, which is used to train the classifier and * classify the provided instances. * * @return InferenceModelInputData instance or null. */ public InferenceModelInputDataSet getInput() { return inputDataSet; } /** * Returns the result of the training and inference processes of this * inference model. * * * @return always an InferenceModelResultDataSet */ @Override public InferenceModelResultDataSet getResult() { return this.outputDataSet; } /** * * * @return */ @Override protected Node createNodeDelegate() { return getDetector() == null ? new InferenceModelNode(AbstractInferenceModel.this) : new InferenceModelNode(getDetector()); } /** * Returns the default icon, which represents this model. * * @return an Image instance or null. */ @Override protected Image getDefaultImage() { return IconRegister.findRegisteredIcon(ICON_NAME); } /** * Returns the used weka classifier. * * @return a weka classifier. */ public final Classifier getClassifier() { if (classifier == null) { classifier = createClassifier(); } return classifier; } /** * * @param listener */ public final void addInferenceModelListener(final InferenceModelListener listener) { synchronized (INFERENCE_MODEL_LISTENER_MUTEX) { listenerSet.add(listener); } } /** * * @param listener */ public final void removeInferenceModelListener(final InferenceModelListener listener) { synchronized (INFERENCE_MODEL_LISTENER_MUTEX) { listenerSet.remove(listener); } } /** * * @return */ public int featureListSize() { return featureList.size(); } /** * * @return */ public boolean isfeatureListEmpty() { return featureList.isEmpty(); } /** * Behaves like the List interface's add method except it fires a * PropertyChangeEvent. * * @param e * @return */ public boolean addFeature(FeatureProcess e) { boolean result = featureList.add(e); if (result) { pcs.firePropertyChange(PROP_NAME_FEATURE_LIST, null, featureList); } return result; } /** * Removes the specified feature from the feature list of this model and * fires a PropertyChangeEvent. * * @param o FeatureProcess * @return */ public boolean removeFeature(FeatureProcess o) { boolean result = featureList.remove(o); if (result) { pcs.firePropertyChange(PROP_NAME_FEATURE_LIST, null, featureList); } return result; } /** * Clears the feature list of this model. */ public void clearFeatureList() { featureList.clear(); pcs.firePropertyChange(PROP_NAME_FEATURE_LIST, null, featureList); } /** * Removes the specified features from the feature list of this model and * fires a propertyChangeEvent. * * @param c * @return * */ public boolean removeAllFeatures(Collection<?> c) { boolean result = featureList.removeAll(c); if (result) { pcs.firePropertyChange(PROP_NAME_FEATURE_LIST, null, featureList); } return result; } /** * Adds the collection of features to the list of features of this model and * fires a propertyChangeEvent. * * @param c * @return */ public boolean addAllFeatures(Collection<? extends FeatureProcess> c) { boolean result = featureList.addAll(c); if (result) { pcs.firePropertyChange(PROP_NAME_FEATURE_LIST, null, featureList); } return result; } /** * Returns the list of used features. * * @return a copy of the list of all features. */ public Collection<FeatureProcess> getFeatureList() { return Collections.unmodifiableCollection(featureList); } /** * Attempts to cance the process of this inference model. * * @return default always false and has no effect on the process. */ @Override public boolean cancel() { return false; } /** * Returns a toolbar which contains can contain commands and other ui * controllers to control this inference model. * * @return JToolbar, default null. */ public JToolBar getToolbarRepresenter() { return null; } /** * Returns the default settings from the configure this inference model * implementation, which gets used by the project settings component. * * @return a JCompoent. */ @Override public JComponent getSettingsView() { InferenceModelSettingForm inferenceModelSettingForm = new InferenceModelSettingForm(AbstractInferenceModel.this); return inferenceModelSettingForm; } /** * Reinitializes the used weka classifier. */ public void resetClassifier() { classifier = createClassifier(); } /** * Factory method, which creates for the specified detector and qualified * name that should be instanciated. This methods lookps up via * <code>findAll</code> all registered AbstractInferenceModel * implementations * * @param qualifiedInstanceName the full qualified name of the to be * instanciated AbstractInferenceModel type. * @param detector the parent detector of the to be instanciated * AbstractInferenceMode. * @return an AbstractInferenceModel implementation. * @throws * de.fub.maps.project.detector.model.process.DetectorProcess.DetectorProcessNotFoundException * if there is no registered type with the specified full qualified name. */ public static synchronized AbstractInferenceModel find(String qualifiedInstanceName, Detector detector) throws DetectorProcessNotFoundException { AbstractInferenceModel abstractInferenceModel = null; try { Class<?> clazz = null; ClassLoader classLoader = Lookup.getDefault().lookup(ClassLoader.class); // prefer netbeans classloader if (classLoader != null) { clazz = classLoader.loadClass(qualifiedInstanceName); } else { // fall back clazz = Class.forName(qualifiedInstanceName); } if (AbstractInferenceModel.class.isAssignableFrom(clazz)) { @SuppressWarnings("unchecked") Class<AbstractInferenceModel> abstractInferenceModelClass = (Class<AbstractInferenceModel>) clazz; abstractInferenceModel = DetectorProcess.find(abstractInferenceModelClass, detector); } else { throw new DetectorProcessNotFoundException(MessageFormat.format("{0} is not type of {1}", clazz.getSimpleName(), AbstractInferenceModel.class.getSimpleName())); } } catch (ClassNotFoundException ex) { throw new DetectorProcessNotFoundException(ex); } catch (DetectorProcessNotFoundException ex) { throw new DetectorProcessNotFoundException(ex); } return abstractInferenceModel; } /** * Creates a weka classifier for this inference model. * * @return Classifier - a weka classifier. null not permitted. */ protected abstract Classifier createClassifier(); /** * Creates the default descriptor of this inference model implementation. * * @return */ protected abstract InferenceModelDescriptor createDefaultDescriptor(); }