Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
weka.gui.beans.TimeSeriesForecasting Maven / Gradle / Ivy
Go to download
Provides a time series forecasting environment for Weka. Includes a wrapper for Weka regression schemes that automates the process of creating lagged variables and date-derived periodic variables and provides the ability to do closed-loop forecasting. New evaluation routines are provided by a special evaluation module and graphing of predictions/forecasts are provided via the JFreeChart library. Includes both command-line and GUI user interfaces. Sample time series data can be found in ${WEKA_HOME}/packages/timeseriesForecasting/sample-data.
/*
* 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 .
*/
/*
* TimeSeriesForecasting.java
* Copyright (C) 2010-2016 University of Waikato, Hamilton, New Zealand
*/
package weka.gui.beans;
import java.awt.BorderLayout;
import java.beans.EventSetDescriptor;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import javax.swing.JPanel;
import org.apache.commons.codec.binary.Base64;
import weka.classifiers.evaluation.NumericPrediction;
import weka.classifiers.timeseries.AbstractForecaster;
import weka.classifiers.timeseries.WekaForecaster;
import weka.core.SerializationHelper;
import weka.filters.supervised.attribute.TSLagMaker;
import weka.core.Attribute;
import weka.core.DenseInstance;
import weka.core.Environment;
import weka.core.EnvironmentHandler;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.Utils;
import weka.gui.Logger;
/**
* KnowledgeFlow component for producing a forecast using a time series
* forecasting model.
*
* @author Mark hall (mhall{[at]}pentaho{[dot]}com)
* @version $Revision: 52593 $
*
*/
@KFStep(category = "Time Series", toolTipText = "Forecasting using a time series model")
public class TimeSeriesForecasting extends JPanel implements BeanCommon,
Visible, EventConstraints, EnvironmentHandler, InstanceListener,
DataSourceListener {
/**
* For serialization
*/
private static final long serialVersionUID = -7826178727365267059L;
/** The structure of the data used to train the forecaster */
protected transient Instances m_header;
/** The forecaster to use for forecasting */
protected transient WekaForecaster m_forecaster;
/**
* The output instances structure - typically the same as the input structure.
* Will have additional attributes for upper and lower confidence intervals if
* the forecaster produces them
*/
protected transient Instances m_outgoingStructure;
/**
* The filename to load from - takes precendence over an encoded forecaster if
* not null and not equal to "-NONE-"
*/
protected String m_fileName = "-NONE-";
/**
* The file name to save the updated forecaster to if the user has opted to
* rebuild the forecasting model on the incoming data
*/
protected String m_saveFileName = "";
/**
* Base 64 encoded forecasting model - this allows the model to be embeded in
* the XML knowledge flow file format rather than loaded from a file at
* execution time.
*/
protected String m_encodedForecaster = "-NONE-";
/**
* Number of future time steps to forecast - will be ignored if overlay data
* is being used since the number of instances containing overlay values will
* dictate the number of forecasted values that can be produced
*/
protected String m_numberOfStepsToForecast = "1";
/** True if the forecaster should be rebuilt on incoming data */
protected boolean m_rebuildForecaster = false;
/**
* The number of time units beyond the end of the training data used to train
* the forecaster that the most recent incoming priming instance is. This is
* used to adjust the artificial time stamp (if one is being used) to the
* right value before a forecast is produced
*/
protected String m_artificialTimeStartOffset = "0";
/** Logging object */
protected transient Logger m_log;
/** Environment variables */
protected transient Environment m_env;
/** Upstream component sending us data */
protected Object m_listenee = null;
/** Incoming connection type */
protected String m_incomingConnection = "";
/** Components listening to our output */
protected ArrayList m_instanceListeners = new ArrayList();
private static enum Status {
BUSY, IDLE;
}
protected Status m_forecastingStatus = Status.IDLE;
/**
* Global about information for this component.
*
* @return global information for this component
*/
public String globalInfo() {
return "Encapsulates a time series forecasting model and uses it to"
+ " produce forecasts given incoming historical data. Forecaster "
+ "can optionally be rebuilt using the incoming data before a "
+ "forecast is generated.";
}
/** Visual representation */
protected BeanVisual m_visual = new BeanVisual("TimeSeriesForecasting",
BeanVisual.ICON_PATH + "DefaultClassifier.gif", BeanVisual.ICON_PATH
+ "DefaultClassifier_animated.gif");
/**
* Constructor
*/
public TimeSeriesForecasting() {
setLayout(new BorderLayout());
useDefaultVisual();
add(m_visual, BorderLayout.CENTER);
}
/**
* Use the default images for a data source
*
*/
public void useDefaultVisual() {
m_visual.loadIcons(BeanVisual.ICON_PATH + "DefaultClassifier.gif",
BeanVisual.ICON_PATH + "DefaultClassifier_animated.gif");
}
/**
* Set the logging object to use
*
* @param logger the logging object to use
*/
public void setLog(Logger logger) {
m_log = logger;
}
/**
* Get the forecaster. Loads the forecaster from a file (if necessary).
*
* @return the forecasting model
* @throws Exception if there is a problem loading the forecaster
*/
public WekaForecaster getForecaster() throws Exception {
if (m_forecaster != null) {
return m_forecaster;
} else {
// try and decode the base64 string (if set)
List model = getForecaster(m_encodedForecaster);
if (model != null) {
m_forecaster = (WekaForecaster) model.get(0);
m_header = (Instances) model.get(1);
return m_forecaster;
}
}
return null;
}
/**
* Decodes and returns a forecasting model (list containing the forecaster and
* Instances object containing the structure of the data used to train the
* forecaster) from a base 64 string.
*
* @param base64encoded a List containing forecaster and header
* encoded as a base 64 string
*
* @return the decoded List containing forecaster and header
* @throws Exception if there is a problem decoding
*/
public static List getForecaster(String base64encoded)
throws Exception {
if (base64encoded != null && base64encoded.length() > 0
&& !base64encoded.equals("-NONE-")) {
byte[] decoded = decodeFromBase64(base64encoded);
ByteArrayInputStream bis = new ByteArrayInputStream(decoded);
ObjectInputStream ois = SerializationHelper.getObjectInputStream(bis);
List model = (List) ois.readObject();
ois.close();
return model;
}
return null;
}
/**
* Set the base 64 encoded forecaster.
*
* @param encodedForecaster a base 64 encoded List containing the
* forecaster and header
*/
public void setEncodedForecaster(String encodedForecaster) {
m_encodedForecaster = encodedForecaster;
}
/**
* Gets the base 64 encoded forecaster
*
* @return a base 64 string encoding a List that contains the
* forecasting model and the header
*/
public String getEncodedForecaster() {
return m_encodedForecaster;
}
/**
* Set the filename to load from.
*
* @param filename the filename to load from
*/
public void setFilename(String filename) {
m_fileName = filename;
}
/**
* Get the filename to load from.
*
* @return the filename to load from.
*/
public String getFilename() {
return m_fileName;
}
/**
* Set the name of the file to save the forecasting model out to if the user
* has opted to rebuild the forecaster using the incoming data.
*
* @param fileName the file name to save to.
*/
public void setSaveFilename(String fileName) {
m_saveFileName = fileName;
}
/**
* Get the name of the file to save the forecasting model to if the user has
* opted to rebuild the forecaster using the incoming data.
*
* @return the name of the file to save the forecaster to.
*/
public String getSaveFilename() {
return m_saveFileName;
}
/**
* Set whether the forecaster should be rebuilt/re-estimated on the incoming
* data.
*
* @param rebuild true if the forecaster should be rebuilt using the incoming
* data
*/
public void setRebuildForecaster(boolean rebuild) {
m_rebuildForecaster = rebuild;
}
/**
* Get whether the forecaster will be rebuilt/re-estimated on the incoming
* data.
*
* @return true if the forecaster is to be rebuilt on the incoming data
*/
public boolean getRebuildForecaster() {
return m_rebuildForecaster;
}
/**
* Returns true if, at this time, the object will accept a connection
* according to the supplied EventSetDescriptor
*
* @param esd the EventSetDescriptor
* @return true if the object will accept a connection
*/
public boolean connectionAllowed(EventSetDescriptor esd) {
return connectionAllowed(esd.getName());
}
/**
* Returns true if, at this time, the object will accept a connection with
* respect to the named event
*
* @param eventName the event
* @return true if the object will accept a connection
*/
public boolean connectionAllowed(String eventName) {
if (m_listenee != null) {
return false;
}
return true;
}
/**
* Set the number of time steps to forecast beyond the end of the incoming
* priming data. This will be ignored if the forecaster is using overlay data
* as the number of instances for which overlay data is present (and targets
* are missing) in the incoming data will determine how many forecasted values
* are produced.
*
* @param n the number of steps to forecast.
*/
public void setNumStepsToForecast(String n) {
m_numberOfStepsToForecast = n;
}
/**
* Get the number of time steps to forecast beyond the end of the incoming
* priming data. This will be ignored if the forecaster is using overlay data
* as the number of instances for which overlay data is present (and targets
* are missing) in the incoming data will determine how many forecasted values
* are produced.
*
* @return the number of steps to forecast.
*/
public String getNumStepsToForecast() {
return m_numberOfStepsToForecast;
}
/**
* Set the offset, from the value associated with the last training instance,
* for the artificial time stamp. Has no effect if an artificial time stamp is
* not in use by the forecaster. If in use, this needs to be set so that the
* forecaster knows what time stamp value corresponds to the first requested
* forecast (i.e. it should be equal to the number of recent historical
* priming instances that occur after the last training instance in time).
*
* @param art the offset from the last artificial time value in the training
* data for which the forecast is requested.
*/
public void setArtificialTimeStartOffset(String art) {
m_artificialTimeStartOffset = art;
}
/**
* Get the offset, from the value associated with the last training instance,
* for the artificial time stamp. Has no effect if an artificial time stamp is
* not in use by the forecaster. If in use, this needs to be set so that the
* forecaster knows what time stamp value corresponds to the first requested
* forecast (i.e. it should be equal to the number of recent historical
* priming instances that occur after the last training instance in time).
*
* @return the offset from the last artificial time value in the training data
* for which the forecast is requested.
*/
public String getArtificialTimeStartOffset() {
return m_artificialTimeStartOffset;
}
/**
* Notify this object that it has been registered as a listener with a source
* with respect to the named event
*
* @param eventName the event
* @param source the source with which this object has been registered as a
* listener
*/
public void connectionNotification(String eventName, Object source) {
if (connectionAllowed(eventName)) {
m_listenee = source;
m_incomingConnection = eventName;
}
}
/**
* Notify this object that it has been deregistered as a listener with a
* source with respect to the supplied event name
*
* @param eventName the event
* @param source the source with which this object has been registered as a
* listener
*/
public void disconnectionNotification(String eventName, Object source) {
if (m_listenee == source) {
m_listenee = null;
m_incomingConnection = "";
}
}
/**
* Get the custom (descriptive) name for this bean (if one has been set)
*
* @return the custom name (or the default name)
*/
public String getCustomName() {
return m_visual.getText();
}
/**
* Returns true if. at this time, the bean is busy with some task.
*
* @return true if the bean is busy.
*/
public boolean isBusy() {
return (m_forecastingStatus == Status.BUSY);
}
/**
* Set a custom (descriptive) name for this bean
*
* @param name the name to use
*/
public void setCustomName(String name) {
m_visual.setText(name);
}
/**
* Stop the component from executing. Also attempts to tell the immediate
* upstream component to stop.
*/
public void stop() {
m_forecastingStatus = Status.IDLE;
// tell upstream component to stop
if (m_listenee != null) {
if (m_listenee instanceof BeanCommon) {
((BeanCommon) m_listenee).stop();
}
}
}
/**
* Set environment variables to use.
*
* @param env the environment variables to use
*/
public void setEnvironment(Environment env) {
m_env = env;
}
/**
* Gets the visual appearance of this wrapper bean
*/
public BeanVisual getVisual() {
return m_visual;
}
/**
* Sets the visual appearance of this wrapper bean
*
* @param newVisual a BeanVisual
value
*/
public void setVisual(BeanVisual newVisual) {
m_visual = newVisual;
}
/**
* Encode the model and header into a base 64 string. A List
* containing first the model and then the header is encoded.
*
* @param model the forecasting model to encode
* @param header empty instances object containing just the structure of the
* data used to train the forecaster
* @return a base 64 encoded String
* @throws Exception if a problem occurs.
*/
public static String encodeForecasterToBase64(WekaForecaster model,
Instances header) throws Exception {
if (model != null && header != null) {
List modelAndHeader = new ArrayList();
modelAndHeader.add(model);
modelAndHeader.add(header);
ByteArrayOutputStream bao = new ByteArrayOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(bao);
ObjectOutputStream oo = new ObjectOutputStream(bos);
oo.writeObject(modelAndHeader);
oo.flush();
byte[] modelBytes = bao.toByteArray();
return encodeToBase64(modelBytes);
} else {
throw new Exception("[TimeSeriesForecasting] unable to encode model!");
}
}
protected List loadModel(String filename) {
List loaded = new ArrayList();
try {
if (!isEmpty(filename) && !filename.equals("-NONE-")) {
// replace any environment variables
if (m_env == null) {
m_env = Environment.getSystemWide();
}
String filenameN = filename;
try {
filenameN = m_env.substitute(filename);
} catch (Exception e) {
// quietly ignore
}
InputStream is = new FileInputStream(filenameN);
if (filenameN.toLowerCase().endsWith(".gz")) {
is = new GZIPInputStream(is);
}
ObjectInputStream ois = SerializationHelper.getObjectInputStream(is);
WekaForecaster forecaster = (WekaForecaster) ois.readObject();
Instances header = (Instances) ois.readObject();
is.close();
loaded.add(forecaster);
loaded.add(header);
return loaded;
} else {
logMessage("Model is null or no filename specified to load from!");
return null;
}
} catch (Exception ex) {
ex.printStackTrace();
logMessage(ex.getMessage());
return null;
}
}
/**
* Utility method to check if a String is null or empty ("").
*
* @param aString the String to check.
* @return true if the supplied String is null or empty.
*/
protected static boolean isEmpty(String aString) {
if (aString == null || aString.length() == 0) {
return true;
}
return false;
}
/**
* Logs message to the log or System.out if the log object is not set.
*
* @param message the message to log
*/
protected void logMessage(String message) {
message = "[TimeSeriesForecasting] " + statusMessagePrefix() + message;
if (m_log != null) {
m_log.logMessage(message);
} else {
System.out.println(message);
}
}
/**
* Logs an error message to the log or System.err if the log object is not set
*
* @param message the message to log
* @param ex the exception that generated the error (may be null)
*/
protected void logError(String message, Exception ex) {
String exceptionMessage = (ex != null) ? ex.getMessage() : "";
message = "[TimeSeriesForecasting] " + statusMessagePrefix() + " "
+ message + exceptionMessage;
if (m_log != null) {
m_log.logMessage(message);
} else {
System.err.println(message);
}
}
/**
* Prints a message to the status area of the log or to System.out if the log
* object is not set.
*
* @param message the message to output
*/
protected void statusMessage(String message) {
message = statusMessagePrefix() + message;
if (m_log != null) {
m_log.statusMessage(message);
} else {
System.out.println(message);
}
}
/**
* Prints a fixed error message to the status area of the log
*/
protected void statusError() {
String message = statusMessagePrefix() + "ERROR: see log for details";
if (m_log != null) {
m_log.statusMessage(message);
} else {
System.err.println(message);
}
}
/**
* Prints a warning message to the status area of the log
*
* @param message the message to log
*/
protected void statusWarning(String message) {
message = statusMessagePrefix() + "WARNING: " + message;
if (m_log != null) {
m_log.statusMessage(message);
} else {
System.out.println(message);
}
}
protected static final String encodeToBase64(byte[] val) throws IOException {
String string;
if (val == null) {
string = null;
} else {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
GZIPOutputStream gzos = new GZIPOutputStream(baos);
BufferedOutputStream bos = new BufferedOutputStream(gzos);
bos.write(val);
bos.flush();
bos.close();
string = new String(Base64.encodeBase64(baos.toByteArray()));
}
return string;
}
protected static final byte[] decodeFromBase64(String string)
throws Exception {
byte[] bytes;
if (string == null) {
bytes = new byte[] {};
} else {
bytes = Base64.decodeBase64(string.getBytes());
}
if (bytes.length > 0) {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
GZIPInputStream gzip = new GZIPInputStream(bais);
BufferedInputStream bi = new BufferedInputStream(gzip);
byte[] result = new byte[] {};
byte[] extra = new byte[1000000];
int nrExtra = bi.read(extra);
while (nrExtra >= 0) {
// add it to bytes...
//
int newSize = result.length + nrExtra;
byte[] tmp = new byte[newSize];
for (int i = 0; i < result.length; i++)
tmp[i] = result[i];
for (int i = 0; i < nrExtra; i++)
tmp[result.length + i] = extra[i];
// change the result
result = tmp;
nrExtra = bi.read(extra);
}
bytes = result;
gzip.close();
}
return bytes;
}
/**
* Returns true, if at the current time, the named event could be generated.
* Assumes that the supplied event name is an event that could be generated by
* this bean
*
* @param eventName the name of the event in question
* @return true if the named event could be generated at this point in time
*/
public boolean eventGeneratable(String eventName) {
if (eventName.equals("instance") || eventName.equals("dataset")) {
if (m_listenee == null) {
return false;
}
}
return true;
}
/** holds overlay data (if present in the incoming data) */
protected transient Instances m_overlayData;
/**
* holds the incoming data for either priming the forecaster or for rebuilding
* the forecaster
*/
protected transient Instances m_bufferedPrimeData;
/** true if the forecaster is using overlay data */
protected transient boolean m_isUsingOverlayData;
/** the lag maker in use by the forecaster */
protected transient TSLagMaker m_modelLagMaker;
/** name of the time stamp attribute */
protected transient String m_timeStampName = "";
/** the fields that the forecaster is predicting */
protected transient List m_fieldsToForecast;
private void loadOrDecodeForecaster() {
// filename takes precedence over encoded forecaster (if any)
if (!isEmpty(m_fileName) && !m_fileName.equals("-NONE-")) {
List loaded = loadModel(m_fileName);
if (loaded == null) {
statusError();
logError("problem loading forecasting model.", null);
stop();
return;
} else {
m_forecaster = (WekaForecaster) loaded.get(0);
m_header = (Instances) loaded.get(1);
}
} else if (m_encodedForecaster != null && m_encodedForecaster.length() > 0
&& !m_encodedForecaster.equals("-NONE-")) {
try {
getForecaster();
} catch (Exception ex) {
ex.printStackTrace();
statusError();
logError("a problem occurred while decoding the model. See the "
+ "log for details.", ex);
stop();
return;
}
} else {
statusError();
logError("unable to obtain a forecasting model to use.", null);
stop();
return;
}
}
/**
* Accept an incoming instance
*
* @param e incoming InstanceEvent encapsulating the instance to process.
*/
public void acceptInstance(InstanceEvent e) {
int status = e.getStatus();
if (status == InstanceEvent.FORMAT_AVAILABLE) {
Instances dataset = e.getStructure();
loadOrDecodeForecaster();
// check the structure
if (!m_header.equalHeaders(dataset)) {
statusError();
logError("incoming instances structure does not match the structure "
+ "of the data used to train the forecaster.", null);
stop();
return;
}
m_forecastingStatus = Status.BUSY;
processInstance(null, true);
return;
}
// get the instance, process it
processInstance(e.getInstance(), false);
if (status == InstanceEvent.BATCH_FINISHED) {
processInstance(null, false); // finished
// generate forecast
generateForecast();
m_forecastingStatus = Status.IDLE;
}
}
protected void processInstance(Instance toProcess, boolean first) {
if (first) {
m_overlayData = null;
m_bufferedPrimeData = null;
statusMessage("configuring forecaster...");
m_modelLagMaker = m_forecaster.getTSLagMaker();
if (!m_modelLagMaker.isUsingAnArtificialTimeIndex()
&& m_modelLagMaker.getAdjustForTrends()) {
m_timeStampName = m_modelLagMaker.getTimeStampField();
}
m_isUsingOverlayData = m_forecaster.isUsingOverlayData();
if (!m_rebuildForecaster) {
// m_isIncrementallyPrimeable = true;
logMessage("forecaster will be primed " + "incrementally.");
// first reset lag histories
try {
m_forecaster.primeForecaster(new Instances(m_header, 0));
} catch (Exception ex) {
ex.printStackTrace();
statusError();
logError("problem during initialization of the " + "priming data.",
ex);
stop();
return;
}
} else {
logMessage("forecaster will be "
+ "rebuilt/re-estimated on incoming data");
}
if (m_isUsingOverlayData) {
logMessage("forecaster is using "
+ "overlay data. We expect to see overlay attribute "
+ "values for the forecasting period.");
m_overlayData = new Instances(m_header, 0);
}
if (m_rebuildForecaster) {
m_bufferedPrimeData = new Instances(m_header, 0);
}
m_fieldsToForecast = AbstractForecaster.stringToList(m_forecaster
.getFieldsToForecast());
// notify instance listeners of the structure (same as incoming
// but may have confidence bounds attributes added)
m_outgoingStructure = new Instances(m_header);
if (m_forecaster.isProducingConfidenceIntervals()) {
ArrayList atts = new ArrayList();
for (int i = 0; i < m_header.numAttributes(); i++) {
atts.add((Attribute) m_header.attribute(i).copy());
}
for (String f : m_fieldsToForecast) {
Attribute lb = new Attribute(f + "_lowerBound");
Attribute ub = new Attribute(f + "_upperBound");
atts.add(lb);
atts.add(ub);
}
m_outgoingStructure = new Instances(m_header.relationName() + "_"
+ "plus_forecast", atts, 0);
}
InstanceEvent ie = new InstanceEvent(this, m_outgoingStructure);
// notify all listeners
notifyInstanceListeners(ie);
} else if (toProcess == null) {
// no more input. rebuild forecaster if necessary
if (m_rebuildForecaster && m_bufferedPrimeData.numInstances() > 0) {
// push out the historical data first
for (int i = 0; i < m_bufferedPrimeData.numInstances(); i++) {
InstanceEvent ie = new InstanceEvent(this,
m_bufferedPrimeData.instance(i), InstanceEvent.INSTANCE_AVAILABLE);
// notify listeners here
notifyInstanceListeners(ie);
}
// rebuild the forecaster
try {
statusMessage("rebuilding the forecasting model...");
logMessage("rebuilding the forecasting model.");
m_forecaster.buildForecaster(m_bufferedPrimeData);
statusMessage("priming the forecasting model...");
logMessage("priming the forecasting model.");
// prime the forecaster
m_forecaster.primeForecaster(m_bufferedPrimeData);
} catch (Exception e) {
e.printStackTrace();
statusError();
logError("a problem occurred when rebuilding the forecaster", e);
stop();
}
}
if (m_rebuildForecaster && !isEmpty(m_saveFileName)) {
// save the forecaster
String saveName = m_saveFileName;
if (m_env == null) {
m_env = Environment.getSystemWide();
}
try {
saveName = m_env.substitute(saveName);
} catch (Exception ex) {
// quietly ignore
}
statusMessage("Saving rebuilt forecasting model...");
logMessage("Saving rebuild forecasting model to \"" + saveName + "\"");
try {
OutputStream os = new FileOutputStream(saveName);
if (saveName.toLowerCase().endsWith(".gz")) {
os = new GZIPOutputStream(os);
}
ObjectOutputStream oos = new ObjectOutputStream(
new BufferedOutputStream(os));
oos.writeObject(m_forecaster);
oos.writeObject(m_header);
oos.close();
} catch (IOException e) {
statusError();
logError("a problem occurred when trying to save rebuilt model.", e);
stop();
}
}
} else {
// if we are expecting overlay data, then check this instance to see if
// all
// target values predicted by the forecaster are missing. If so, then this
// *might* indicate the start of the overlay data. We will start buffering
// instances into the overlay buffer. If we get an instace with all
// non-missing targets
// at some future point then we will flush the overlay buffer either into
// the
// forecaster as priming instances (if forecaster is incrementally
// primeable)
// or into the buffered prime/training data if forecaster is not
// incrementally
// primeable or we are rebuilding/re-estimating the model
if (m_isUsingOverlayData) {
boolean allMissing = true;
for (String field : m_fieldsToForecast) {
if (!toProcess.isMissing(m_header.attribute(field))) {
allMissing = false;
break;
}
}
if (allMissing) {
// add it to the overlay buffer
m_overlayData.add(toProcess);
statusMessage("buffering overlay instance");
} else {
// check the overlay buffer - if it's not empty then flush it
// into either the forecaster directly (if incrementally primeable)
// or into the priming buffer
if (m_overlayData.numInstances() > 0) {
// first buffer this one (will get flushed anyway)
m_overlayData.add(toProcess);
logMessage("encountered a supposed "
+ "overlay instance with non-missing target values - "
+ "converting buffered overlay data into "
+ (m_rebuildForecaster ? "training" : "priming") + " data...");
statusMessage("flushing overlay buffer.");
for (int i = 0; i < m_overlayData.numInstances(); i++) {
if (!m_rebuildForecaster) {
try {
m_forecaster.primeForecasterIncremental(m_overlayData
.instance(i));
// output this instance immediately (make sure that we include
// any attributes for confidence intervals - these will be
// necessarily missing for historical instances)
Instance outgoing = convertToOutputFormat(m_overlayData
.instance(i));
InstanceEvent ie = new InstanceEvent(this, outgoing,
InstanceEvent.INSTANCE_AVAILABLE);
// notify listeners here
notifyInstanceListeners(ie);
} catch (Exception e) {
e.printStackTrace();
statusError();
logError("problem occurred during priming.", e);
stop();
return;
}
} else {
// transfer to the priming buffer
m_bufferedPrimeData.add(m_overlayData.instance(i));
}
}
// clear out the overlay data
m_overlayData = new Instances(m_header, 0);
} else {
// not all missing and overlay buffer is empty then it's a priming
// instance
// either buffer it or send it directly to the forecaster (if
// incrementally
// primeable
if (!m_rebuildForecaster) {
try {
m_forecaster.primeForecasterIncremental(toProcess);
// output this instance immediately (make sure that we include
// any attributes for confidence intervals - these will be
// necessarily missing for historical instances)
Instance outgoing = convertToOutputFormat(toProcess);
InstanceEvent ie = new InstanceEvent(this, outgoing,
InstanceEvent.INSTANCE_AVAILABLE);
// tell listeners
notifyInstanceListeners(ie);
} catch (Exception ex) {
ex.printStackTrace();
statusError();
logError("problem occurred during priming.", ex);
stop();
return;
}
} else {
// buffer
m_bufferedPrimeData.add(toProcess);
}
}
}
} else {
// not using overlay data
if (!m_rebuildForecaster) {
try {
m_forecaster.primeForecasterIncremental(toProcess);
// output this instance immediately (make sure that we include
// any attributes for confidence intervals - these will be
// necessarily missing for historical instances)
Instance outgoing = convertToOutputFormat(toProcess);
InstanceEvent ie = new InstanceEvent(this, outgoing,
InstanceEvent.INSTANCE_AVAILABLE);
notifyInstanceListeners(ie);
} catch (Exception ex) {
ex.printStackTrace();
statusError();
logError("problem occurred during priming.", ex);
stop();
return;
}
} else {
// buffer
m_bufferedPrimeData.add(toProcess);
}
}
}
}
/**
* Accept an incoming data set
*
* @param dse incoming DataSetEvent encapsulating the instances to process
*/
public void acceptDataSet(DataSetEvent dse) {
loadOrDecodeForecaster();
Instances data = dse.getDataSet();
if (!m_header.equalHeaders(data)) {
statusError();
logError(
"Incoming instance structure does not match what the forecaster "
+ "was trained with", null);
return;
}
if (dse.isStructureOnly()) {
return;
}
m_forecastingStatus = Status.BUSY;
processInstance(null, true);
for (int i = 0; i < data.numInstances(); i++) {
processInstance(data.instance(i), false);
}
processInstance(null, false); // finished
// generate forecast
generateForecast();
m_forecastingStatus = Status.IDLE;
}
private void generateForecast() {
// doesn't matter if we're not using a time stamp
double lastTimeFromPrime = -1;
if (m_modelLagMaker.getAdjustForTrends()
&& m_modelLagMaker.getTimeStampField() != null
&& m_modelLagMaker.getTimeStampField().length() > 0
&& !m_modelLagMaker.isUsingAnArtificialTimeIndex()) {
try {
lastTimeFromPrime = m_modelLagMaker.getCurrentTimeStampValue();
} catch (Exception ex) {
statusError();
logError("a problem occurred while establishing the current "
+ "time stamp value", ex);
stop();
return;
}
} else if (m_modelLagMaker.getAdjustForTrends()
&& m_modelLagMaker.isUsingAnArtificialTimeIndex()) {
// If an artificial time stamp is in use then we need to set the
// initial value to whatever offset from training that the user has
// indicated to be the first forecasted point.
try {
String artOff = m_artificialTimeStartOffset;
if (m_env != null) {
artOff = m_env.substitute(artOff);
}
double artificialStartValue = m_modelLagMaker
.getArtificialTimeStartValue();
artificialStartValue += Integer.parseInt(artOff);
m_modelLagMaker.setArtificialTimeStartValue(artificialStartValue);
} catch (Exception ex) {
ex.printStackTrace();
statusError();
logError("unable to set the value of the artificial time stamp.", ex);
stop();
return;
}
}
boolean overlay = (m_overlayData != null
&& m_overlayData.numInstances() > 0 && m_isUsingOverlayData);
String numS = m_numberOfStepsToForecast;
if (m_env != null) {
try {
numS = m_env.substitute(numS);
} catch (Exception e) {
// quietly ignore
// e.printStackTrace();
}
}
int numSteps = (overlay) ? m_overlayData.numInstances() : Integer
.parseInt(numS);
List> forecast = null;
// TODO adapt the log to PrintStream for the forecasting methods
try {
if (overlay) {
forecast = m_forecaster.forecast(numSteps, m_overlayData);
} else {
forecast = m_forecaster.forecast(numSteps);
}
} catch (Exception ex) {
ex.printStackTrace();
statusError();
logError("unable to generate a forecast.", ex);
stop();
return;
}
// now convert the forecast into instances. If we have overlay
// data then we can just fill in the forecasted values (and
// potentially add for confidence intervals)
double time = lastTimeFromPrime;
int timeStampIndex = -1;
if (m_timeStampName.length() > 0) {
Attribute timeStampAtt = m_outgoingStructure.attribute(m_timeStampName);
if (timeStampAtt == null) {
statusError();
logError("couldn't find time stamp: " + m_timeStampName
+ "in the input data", null);
stop();
return;
}
timeStampIndex = timeStampAtt.index();
}
statusMessage("Generating forecast...");
logMessage("Generating forecast.");
for (int i = 0; i < numSteps; i++) {
Instance outputI = null;
double[] outVals = new double[m_outgoingStructure.numAttributes()];
for (int j = 0; j < outVals.length; j++) {
if (overlay) {
outVals[j] = m_overlayData.instance(i).value(j);
} else {
outVals[j] = Utils.missingValue();
}
}
List predsForStep = forecast.get(i);
if (timeStampIndex != -1) {
// set time value
time = m_modelLagMaker.advanceSuppliedTimeValue(time);
outVals[timeStampIndex] = time;
}
for (int j = 0; j < m_fieldsToForecast.size(); j++) {
String target = m_fieldsToForecast.get(j);
int targetI = m_outgoingStructure.attribute(target).index();
NumericPrediction predForTargetAtStep = predsForStep.get(j);
double y = predForTargetAtStep.predicted();
double yHigh = y;
double yLow = y;
double[][] conf = predForTargetAtStep.predictionIntervals();
if (!Utils.isMissingValue(y)) {
outVals[targetI] = y;
}
// any confidence bounds?
if (conf.length > 0) {
yLow = conf[0][0];
yHigh = conf[0][1];
int indexOfLow = m_outgoingStructure
.attribute(target + "_lowerBound").index();
int indexOfHigh = m_outgoingStructure.attribute(
target + "_upperBound").index();
outVals[indexOfLow] = yLow;
outVals[indexOfHigh] = yHigh;
}
}
outputI = new DenseInstance(1.0, outVals);
outputI.setDataset(m_outgoingStructure);
// notify listeners of output instance
InstanceEvent ie = new InstanceEvent(this, outputI,
InstanceEvent.INSTANCE_AVAILABLE);
if (i == (numSteps - 1)) {
ie.setStatus(InstanceEvent.BATCH_FINISHED);
}
notifyInstanceListeners(ie);
}
statusMessage("Finished.");
logMessage("Finished. Generated " + numSteps + " forecasted values.");
}
private String statusMessagePrefix() {
return getCustomName()
+ "$"
+ hashCode()
+ "|"
+ ((Utils.joinOptions(m_forecaster.getOptions()).length() > 0) ? Utils
.joinOptions(m_forecaster.getOptions()) + "|" : "");
}
private Instance convertToOutputFormat(Instance incoming) {
Instance output = (Instance) incoming.copy();
if (m_forecaster.isProducingConfidenceIntervals()) {
double[] values = new double[incoming.numAttributes()
+ (m_fieldsToForecast.size() * 2)];
for (int i = 0; i < incoming.numAttributes(); i++) {
values[i] = incoming.value(i);
}
// set all bounds to missing (initially)
for (int i = incoming.numAttributes(); i < incoming.numAttributes()
+ (m_fieldsToForecast.size() * 2); i++) {
values[i] = Utils.missingValue();
}
output = new DenseInstance(1.0, values);
}
output.setDataset(m_outgoingStructure);
return output;
}
private void notifyInstanceListeners(InstanceEvent e) {
ArrayList l = null;
synchronized (this) {
l = (ArrayList) m_instanceListeners.clone();
}
if (l.size() > 0) {
for (int i = 0; i < l.size(); i++) {
l.get(i).acceptInstance(e);
}
}
}
/**
* Add an listener to be notified of outgoing InstanceEvents
*
* @param l the listener to register to receive outging instance events
*/
public synchronized void addInstanceListener(InstanceListener l) {
m_instanceListeners.add(l);
}
/**
* Deregister and remove a listener of InstanceEvents
*
* @param l the listener to remove.
*/
public synchronized void removeInstanceListener(InstanceListener l) {
m_instanceListeners.remove(l);
}
}