/*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
* TabuSearch.java
* Copyright (C) 2009 Adrian Pino
* Copyright (C) 2009 University of Waikato, Hamilton, NZ
*
*/
package weka.attributeSelection;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Enumeration;
import java.util.List;
import java.util.Random;
import java.util.Vector;
import weka.core.Instances;
import weka.core.Option;
import weka.core.OptionHandler;
import weka.core.RevisionUtils;
import weka.core.TechnicalInformation;
import weka.core.TechnicalInformationHandler;
import weka.core.Utils;
import weka.core.TechnicalInformation.Field;
import weka.core.TechnicalInformation.Type;
/**
* Class for performing the TabuSearch method.
*
* Tabu Search :
*
* Performs a search through the space of attribute subsets. Evading local maximums by accepting bad and diverse solutions and make further search in the best soluions. Stops when there's not more improvement in n iterations
* ;For more information see:
*
* Abdel-Rahman Hedar, Jue Wangy,, Masao Fukushima (2006). Tabu Search for Attribute Reduction in Rough Set Theory. .
*
*
* Valid options are:
*
* -Z <numInitialSolution>
* Specify the number of attributes
* in the initial Solution..
*
* -P <diversificationProb>
* Specify the diversification probabilities,
* if this value is near to 0 then the best attributes
* will have more probabilities of keeping in the new diverse solution
*
* -S <seed>
* Set the random number seed.
* (default = 1)
*
* -N <number of neighbors>
* Set the number of neighbors to generate.
*
*
* BibTeX:
*
* @book{Abdel-RahmanHedar2006,
* author = {Abdel-Rahman Hedar, Jue Wangy, and Masao Fukushima},
* month = {July},
* title = {Tabu Search for Attribute Reduction in Rough Set Theory},
* year = {2006},
* language = {English}
* }
*
*
*
* from the Book: Tabu Search for Attribute Reduction in Rough Set Theory, Abdel-Rahman Hedar, Jue Wangy, and Masao Fukushima.
*
* @author Adrian Pino (apinoa@facinf.uho.edu.cu)
* @version $Revision: 4976 $
*
*/
public class TabuSearch extends ASSearch
implements OptionHandler, TechnicalInformationHandler{
/** for serialization */
static final long serialVersionUID = -8812132617585120414L;
/** number of attributes in the data */
private int m_numAttribs;
/** holds the class index */
private int m_classIndex;
/** random number generation */
private Random m_random;
/** seed for random number generation */
private int m_seed;
/** probabilities of diversification */
private double m_diversificationProb;
/** number of iterations for getting the best subset*/
private int m_numIterations;
/** total number of subsets evaluated during a search */
private int m_totalEvals;
/** holds the best Subset found */
protected Subset m_Sbest;
/** holds the number of neighborhood to generate from a Subset*/
private int m_numNeighborhood;
/** time for procesing the search method */
private long m_processinTime;
/** holds the solution size*/
private int m_initialSize;
/**Subset holding the initial solution*/
private Subset m_initialSolution;
/**attribute ranking*/
private List m_rankedAttribs;
/**tabu List*/
private List m_vectorTabu;
/**Evaluator used to know the significance of a subset (for guiding the search)*/
private SubsetEvaluator ASEvaluator =null;
/**
* Searches the attribute subset space using Tabu Search.
*
* @param ASEvaluator the attribute evaluator to guide the search
* @param data the training instances.
* @return an array of selected attribute indexes
* @exception Exception if the search can't be completed
*/
public int[] search(ASEvaluation ASEval, Instances data)
throws Exception{
m_totalEvals = 0;
m_processinTime =System.currentTimeMillis ();
m_numAttribs =data.numAttributes ();
m_classIndex =data.classIndex ();
ASEvaluator =(SubsetEvaluator)ASEval;
m_random = new Random(m_seed);
int numN = m_numNeighborhood;
numN = (m_numNeighborhood <= 0) ? 3*m_numAttribs/4 : m_numNeighborhood;
if(m_numAttribs <= 14) m_numIterations = m_numAttribs;
else if(m_numAttribs > 14) m_numIterations = m_numAttribs/3;
m_rankedAttribs = RankEachAttribute ();
/** generating an initial Solution based in SFS*/
if(m_initialSize < 0)m_initialSize = m_numAttribs;
m_initialSolution = GenerateInitialSolution(new Subset(new BitSet(m_numAttribs), 0), m_initialSize);
/**Initializing Vector Tabu*/
m_vectorTabu =new ArrayList();
BitSet tabu1 = new BitSet(m_numAttribs);
BitSet tabu2 = new BitSet(m_numAttribs);
tabu2.set (0,m_numAttribs-1);
if (m_classIndex >= 0) tabu2.set (m_classIndex, false);
m_vectorTabu.add (tabu1);
m_vectorTabu.add (tabu2);
/**For Controlling the Search*/
int iterationCounter = 0;
int numGenerationNeighborForDiv = (m_numAttribs/m_numIterations >= 2) ? m_numAttribs/m_numIterations : 3;
int numTotalWImp = 0;
int numTotalWImpForFinishing = m_numIterations/2;
BitSet S = m_initialSolution.subset;
m_Sbest = m_initialSolution;
List RedSet = new ArrayList();
RedSet.add (m_Sbest.clone ());
while(iterationCounter < m_numIterations && numTotalWImp < numTotalWImpForFinishing){
iterationCounter ++;
List neighborhood = null;
int counterGenerationNeighborWImp = 0;
while (counterGenerationNeighborWImp < numGenerationNeighborForDiv) {
neighborhood = generateNeighborhood(S, numN);
if(neighborhood != null){
S =((Subset)neighborhood.get (0)).subset;
double Smerit = ASEvaluator.evaluateSubset (S);
m_totalEvals ++;
RedSet.add (new Subset((BitSet)S.clone (), Smerit));
m_vectorTabu.add ((BitSet)S.clone ());
if(Smerit > m_Sbest.merit){
m_Sbest = new Subset((BitSet)S.clone (), Smerit);
Subset aux = shake ();
if(aux != null)
m_Sbest = aux.clone ();
}
else{
counterGenerationNeighborWImp ++;
numTotalWImp ++;
}
}
else break;
}
S = diversify (neighborhood);
}
Subset elite = eliteReducts(RedSet, RedSet.size ());
if(elite.merit >= m_Sbest.merit)
return attributeList (elite.subset);
m_processinTime = System.currentTimeMillis () - m_processinTime;
return attributeList (m_Sbest.subset);
}
/**
* Generates a list of neighborhood given a Solution, flipping randomly the state of many attributes.
*
* @param S the Solution from which we are going to find the neighborhood
* @param numNeighborhood number of attributes for flipping
* @return a Subset list containing the neighborhood
* @exception Exception if the evaluation can not be complete
*/
private List generateNeighborhood(BitSet S, int numNeighborhood)
throws Exception{
int counter = 0;
List neighborhood = new ArrayList();
int numAttribs = (m_classIndex == -1) ? m_numAttribs : m_numAttribs - 1;
if(numNeighborhood >= numAttribs){
for (int i = 0; i < m_numAttribs; i++) {
if(i == m_classIndex)continue;
BitSet aux = (BitSet)S.clone ();
aux.flip (i);
if(!m_vectorTabu.contains (aux)){
neighborhood.add (new Subset((BitSet)aux.clone (), ASEvaluator.evaluateSubset (aux)));
m_totalEvals ++;
}
}
}
else{
while (counter < numNeighborhood) {
BitSet aux = (BitSet)S.clone ();
int randomNumber = m_random.nextInt (m_numAttribs);
if(randomNumber == m_classIndex)
continue;
aux.flip (randomNumber);
if(!m_vectorTabu.contains (aux)){
neighborhood.add (new Subset((BitSet)aux.clone (), ASEvaluator.evaluateSubset (aux)));
m_totalEvals ++;
counter ++;
}
}
}
if(neighborhood.isEmpty ())
return null;
return bubbleSubsetSort (neighborhood);
}
/**
* Generate a diverse Bitset given a List of Subset
*
* @param neighborhood the list from which are going to be generate the diverse solution
* @return a diverse Bitset
*/
public BitSet diversify(List neighborhood){
if(neighborhood == null)
return m_Sbest.subset;
BitSet result = new BitSet(m_numAttribs);
double [] counts = new double[m_numAttribs];
int numNeighborhood = neighborhood.size ();
for (int i = 0; i < m_numAttribs; i++) {
if(i == m_classIndex)
continue;
int counter = 0;
for (int j = 0; j < numNeighborhood; j++) {
if(((Subset)neighborhood.get (j)).subset.get (i))
counter ++;
}
counts [i] = counter/(double)numNeighborhood;
}
for (int i = 0; i < m_numAttribs; i++) {
double randomNumber = m_random.nextDouble ();
double ocurrenceAndRank = counts [i] * (m_diversificationProb) + doubleRank (i) * (1 - m_diversificationProb);
if(randomNumber > ocurrenceAndRank)
result.set (i);
}
return result;
}
/**
* Try to improve the best Solution obtained till now in the way of Sequential Backward Selection(SBS)
*
* @param ASEvaluator the attribute evaluator to guide the search
* @return null if there is not improvement otherwise a Subset better than the obtained till now.
*/
private Subset shake()
throws Exception{
Subset bestCopy = m_Sbest.clone ();
boolean anyImprovement = false;
for (int i = m_rankedAttribs.size ()-1; i >= 0; i --) {
Subset ranking = m_rankedAttribs.get (i);
int attributeIndex = ranking.subset.nextSetBit (0);
BitSet aux = (BitSet)bestCopy.subset.clone ();
if(aux.get (attributeIndex)){
aux.set (attributeIndex, false);
if(m_vectorTabu.contains (aux))
continue;
double tempMerit = ASEvaluator.evaluateSubset (aux);
m_totalEvals ++;
if(tempMerit >= bestCopy.merit){
bestCopy = new Subset((BitSet)aux.clone (), tempMerit);
anyImprovement = true;
}
}
}
if(anyImprovement)
return bestCopy;
return null;
}
/**
* Find a better solution by intersecting and SFS of the best elite Reducts found till that moment
*
* @param RedSet the Subset List of the Elite Reducted found till that moment
* @param numSubset number of elites to intersect
* @return a the resulting Subset
* @exception Exception if any evaluation can't be completed
*/
public Subset eliteReducts(ListRedSet, int numSubset)
throws Exception{
if(numSubset <= 0)
return null;
ListorderedRedSet = bubbleSubsetSort (RedSet);
int numAttribsOfBest = m_Sbest.cardinality ();
BitSet result =new BitSet(m_numAttribs);
result.set (0,m_numAttribs-1,true);
BitSet aux;
for (int i = 0; i < numSubset; i++) {
aux = ((Subset)orderedRedSet.get (i)).subset;
result.and (aux);
}
int diff = numAttribsOfBest - result.cardinality ();
Subset resultSet = GenerateInitialSolution (new Subset(result,ASEvaluator.evaluateSubset (result)),result.cardinality () + diff-1);
m_totalEvals ++;
if(resultSet == null)
return null;
return resultSet;
}
/**
* Generate an initial solution based in a Sequential Forward Selection (SFS)
*
* @param size number of posible attributes in the initial Solution
* @return an initial Subset containing the initial Solution
* @exception Exception if the evaluation can not be completed
*/
public Subset GenerateInitialSolution(Subset initial, int size)
throws Exception{
List rankedAttribsCopy = new ArrayList();
for (int i = 0; i bestMerit){
bestMerit = tempSubset.merit;
bestIndex = i;
}
}
if(bestIndex == -1) break;
solution =joinSubsets (solution, rankedAttribsCopy.get (bestIndex));
rankedAttribsCopy.remove (bestIndex);
}
return solution;
}
//--------Auxiliar methods****
/**
* get the importance of an individual attribute according to his position in m_rankedAttribs List.
*
* @param idAttrib the index attribute of the attribute that we want to get the doubleRank
* @return a double giving the result. If is a good attribute the result will be a number near of 0(inclusive),
* If is a bad one the result will be near of 1(exclusive).
*/
private double doubleRank(int idAttrib){
int rankSize = m_rankedAttribs.size ();
int rankAttribute = -1;
if(idAttrib == m_classIndex) return -1;
for (int i = 0; i < rankSize; i++) {
if(((Subset)m_rankedAttribs.get (i)).subset.nextSetBit (0) == idAttrib){
rankAttribute = i;
break;
}
}
return rankAttribute/(double)rankSize;
}
/**
* Rank all the attributes individually acording to their merits
*
* @return an ordered List of Subsets with just one attribute
* @exception Exception if the evaluation can not be completed
*/
private List RankEachAttribute()
throws Exception{
List result =new ArrayList();
for (int i = 0; i bubbleSubsetSort(List subsetList){
List result =new ArrayList();
for (int i = 0; i merit1){
Subset temp =subset1;
subsetList.set (i,subset2);
subsetList.set (j,temp);
subset1 =subset2;
merit1 =subset1.merit;
}
}
}
return subsetList;
}
/**
* Join two subsets
*
* @param subset1 one of the subsets
* @param subset2 the other subset
* @return a new Subset that is te result of the Join
* @exception Exception if the evaluation of the subsets can not be completed
*/
public Subset joinSubsets(Subset subset1, Subset subset2)
throws Exception{
BitSet b1 =(BitSet)subset1.subset.clone ();
BitSet b2 =(BitSet)subset2.subset.clone ();
b1.or (b2);
double newMerit =ASEvaluator.evaluateSubset (b1);
m_totalEvals++;
return new Subset((BitSet)b1, newMerit);
}
/**
* converts a BitSet into a list of attribute indexes
*
* @param group the BitSet to convert
* @return an array of attribute indexes
*/
public int[] attributeList (BitSet group) {
int count = 0;
// count how many were selected
for (int i = 0; i < m_numAttribs; i++) {
if (group.get(i)) {
count++;
}
}
int[] list = new int[count];
count = 0;
for (int i = 0; i < m_numAttribs; i++) {
if (group.get(i)) {
list[count++] = i;
}
}
return list;
}
// Auxiliar Class for handling solutions and its respective merit
public class Subset implements Serializable{
double merit;
BitSet subset;
public Subset(BitSet subset, double merit){
this.subset = (BitSet)subset.clone ();
this.merit = merit;
}
public Subset(){
this.subset =null;
this.merit =-1.0;
}
public boolean isEqual(Subset otherSubset){
if(subset.equals (otherSubset.subset))return true;
return false;
}
public Subset clone(){
return new Subset((BitSet)subset.clone (), this.merit);
}
public int cardinality(){
if(subset == null) return 0;
return subset.cardinality ();
}
public void flip(int index)
throws Exception{
subset.flip (index);
merit = ASEvaluator.evaluateSubset (subset);
m_totalEvals ++;
}
public boolean contains(int indexAttribute){
return subset.get (indexAttribute);
}
}
//..........
// Test Methods
public String printSubset(Subset subset){
StringBuffer bufferString = new StringBuffer();
if(subset == null){
//System.out.println ("null");
return "";
}
BitSet bits =subset.subset;
double merit =subset.merit;
List indexes =new ArrayList();
for (int i = 0; i"));
newVector.addElement(new Option("\tSpecify the diversification probabilities,"
+ "\n\tif this value is near to 0 then the best attributes"
+ "\n\t will have more probabilities of keeping in the new diverse solution"
, "P", 1
, "-P "));
newVector.addElement(new Option("\tSet the random number seed."
+"\n\t(default = 1)"
, "S", 1, "-S "));
newVector.addElement(new Option("\tSet the number of neighbors to generate."
, "N", 1, "-N "));
return newVector.elements();
}
/**
*
* Valid options are:
*
* -Z <numInitialSolution>
* Specify the number of attributes
* in the initial Solution..
*
* -P <diversificationProb>
* Specify the diversification probabilities,
* if this value is near to 0 then the best attributes
* will have more probabilities of keeping in the new diverse solution
*
* -S <seed>
* Set the random number seed.
* (default = 1)
*
* -N <number of neighbors>
* Set the number of neighbors to generate.
*
*
* @param options the list of options as an array of strings
* @exception Exception if an option is not supported
*
**/
public void setOptions (String[] options)
throws Exception {
String optionString;
resetOptions();
optionString = Utils.getOption('Z', options);
if (optionString.length() != 0) {
setInitialSize (Integer.parseInt(optionString));
}
optionString = Utils.getOption('P', options);
if (optionString.length() != 0) {
setDiversificationProb (Double.parseDouble(optionString));
}
optionString = Utils.getOption('S', options);
if (optionString.length() != 0) {
setSeed(Integer.parseInt(optionString));
}
optionString = Utils.getOption('N', options);
if (optionString.length() != 0) {
setNumNeighborhood (Integer.parseInt(optionString));
}
}
/**
* Gets the current settings of TabuSearch.
*
* @return an array of strings suitable for passing to setOptions()
*/
public String[] getOptions () {
String[] options = new String[8];
int current = 0;
options[current++] = "-Z";
options[current++] = "" + getInitialSize ();
options[current++] = "-P";
options[current++] = ""+getDiversificationProb ();
options[current++] = "-S";
options[current++] = "" + getSeed();
options[current++] = "-N";
options[current++] = "" + getNumNeighborhood ();
while (current < options.length)
options[current++] = "";
return options;
}
/**
* returns a description of the search.
* @return a description of the search as a String.
*/
public String toString() {
StringBuffer FString = new StringBuffer();
FString.append("\nTabu Search "
+ "\n\tInitial Size: "+getInitialSize ());
if(getInitialSize () > 0){
FString.append("\n\tInitial Solution (Generated by SFS):");
FString.append("\n\tmerit:"+"\t\t"+" subset");
FString.append("\n\t"+ printSubset (m_initialSolution));
}
FString.append("\tdiversificationProb: "
+Utils.doubleToString(Math.abs(getDiversificationProb ()),8,3)+"\n");
FString.append("\tTotal number of subsets evaluated: "
+ m_totalEvals + "\n");
FString.append("\tMerit of best subset found: "
+Utils.doubleToString(Math.abs(m_Sbest.merit),8,3)+"\n");
/*FString.append("\tTime procesing the search space: "
+(double)m_processinTime/1000+" seconds\n"); */
return FString.toString();
}
protected void resetOptions () {
m_initialSize = -1;
m_numNeighborhood = -1;
m_seed = 1;
m_diversificationProb = 1.0;
m_totalEvals = 0;
m_processinTime = 0;
}
}