From: Daniel H. <dh...@us...> - 2008-04-07 17:20:01
|
Update of /cvsroot/jboost/jboost/src/jboost/booster In directory sc8-pr-cvs17.sourceforge.net:/tmp/cvs-serv22970/src/jboost/booster Modified Files: Tag: jboost_unstable Booster.java MixedBinaryPrediction.java AbstractBooster.java Log Message: clean-up Index: Booster.java =================================================================== RCS file: /cvsroot/jboost/jboost/src/jboost/booster/Booster.java,v retrieving revision 1.4 retrieving revision 1.4.2.1 diff -C2 -d -r1.4 -r1.4.2.1 *** Booster.java 25 Mar 2008 01:00:27 -0000 1.4 --- Booster.java 7 Apr 2008 17:19:55 -0000 1.4.2.1 *************** *** 71,79 **** ! public abstract double[][] getWeights(); ! public abstract double[][] getPotentials(); ! public abstract double getTotalWeight(); ! public int getNumExamples(); ! public abstract String getParamString(); --- 71,79 ---- ! public abstract double[][] getWeights(); ! public abstract double[][] getPotentials(); ! public abstract double getTotalWeight(); ! public int getNumExamples(); ! public abstract String getParamString(); *************** *** 93,96 **** --- 93,99 ---- * partition of the data. */ + // XXX DJH: What is 'partition'? Similar to 'elements' below? + // XXX YF: I think you are probably correct in your interpretation, need to check the inplementation, verify, and update the + // XXX Description of this method and change "examples" to "elements" in the javadoc for "update". public abstract Prediction[] getPredictions(Bag[] b, int[][] partition); Index: MixedBinaryPrediction.java =================================================================== RCS file: /cvsroot/jboost/jboost/src/jboost/booster/MixedBinaryPrediction.java,v retrieving revision 1.6 retrieving revision 1.6.2.1 diff -C2 -d -r1.6 -r1.6.2.1 *** MixedBinaryPrediction.java 25 Mar 2008 01:00:27 -0000 1.6 --- MixedBinaryPrediction.java 7 Apr 2008 17:19:55 -0000 1.6.2.1 *************** *** 1,5 **** package jboost.booster; - import jboost.examples.Label; import jboost.booster.NotNormalizedPredException; --- 1,4 ---- *************** *** 11,146 **** class MixedBinaryPrediction extends BinaryPrediction implements NormalizedPrediction{ ! protected double deltaT; ! protected double normalizingConstant; ! public MixedBinaryPrediction() {super();} ! public MixedBinaryPrediction(double p) ! throws NotNormalizedPredException { ! (new Exception("asdf")).printStackTrace(); ! throw new NotNormalizedPredException("MixedBinaryPrediction: Need to normalize based on time!"); ! } ! public static double updateMargin(double currentMargin, double stepSize, double step, double dt, double nc) { ! //System.out.println("cm: " + currentMargin + ", deltax: " + stepSize*step + ", dt: " + dt + ", nc: " + nc); ! //System.out.println(Math.exp(-dt*nc)*currentMargin + stepSize*step); ! return Math.exp(-dt*nc)*currentMargin + stepSize*step; ! } ! public MixedBinaryPrediction(double p, double dt, double nc) ! throws NotNormalizedPredException { ! super(); ! deltaT = dt; ! prediction=p; ! normalizingConstant = nc; ! } ! public MixedBinaryPrediction(double p, double dt) ! throws NotNormalizedPredException { ! super(); ! deltaT = dt; ! prediction=p; ! normalizingConstant = 1; ! } ! public Object clone(){ ! Object a = new MixedBinaryPrediction(prediction, deltaT); ! return a; ! } ! /** ! * Be very careful with how this is used. See NormalizedPrediction ! * for details. ! */ ! public Prediction add(Prediction p) throws NotNormalizedPredException{ ! if (! (p instanceof MixedBinaryPrediction)) { ! throw new NotNormalizedPredException("Must use MixedBinaryPrediction when adding to a MixedBinaryPrediction."); ! } ! // H_t = (1-alpha)H_{t-1} + alpha h_t ! // The prediction p is for h_t ! double alpha = ((MixedBinaryPrediction) p).prediction; ! prediction = Math.exp(-normalizingConstant*deltaT) * prediction + alpha; ! /* if (Math.abs(alpha) > 1 || Math.abs(prediction) > 1) { throw new NotNormalizedPredException("Prediction may result in unnormalized " + "prediction! p is: " + p); } - */ - return this; - } - - /** - * This is not well defined for normalized predictions. However, we - * do allow it. */ ! public Prediction scale(double w) throws NotNormalizedPredException{ ! if (Math.abs(w) > 1) { ! throw new NotNormalizedPredException("Scalar may result in unnormalized " ! + "prediction! w is: " + w); ! } ! prediction *= w; ! return this; ! } ! ! public double getDeltaT() { ! return deltaT; ! } ! ! public double getNormConstant() { ! return normalizingConstant; ! } ! ! public Prediction add(double w, Prediction p) { ! ((MixedBinaryPrediction) p).scale(w); ! this.add( (MixedBinaryPrediction) p ); ! return this; ! } ! ! public boolean equals(Prediction other) { ! MixedBinaryPrediction bp= (MixedBinaryPrediction) other; ! return (prediction == bp.prediction && deltaT == bp.deltaT && normalizingConstant == bp.normalizingConstant); ! } ! ! public String toString() { ! return "MixedBinaryPrediction. p(1)= " + prediction; ! } ! ! public String cPreamble() { ! System.out.println("Prediction::cPreamble not supported for 'mixed' or normalized predictions."); ! System.exit(2); ! return ! "typedef double Prediction_t;\n" + ! "#define reset_pred() {p = 0.0;}\n" + ! "#define add_pred(X) {p = (1-(X))*p + (X);}\n" + ! "#define finalize_pred()" + ! " ((r) ? (r[1] = p , r[0] = -p) : -p)\n"; ! } ! public String javaPreamble() { ! System.out.println("Prediction::javaPreamble not supported for 'mixed' or normalized predictions."); ! System.exit(2); ! return "" ! + " static private double p;\n" ! + " static private void reset_pred() { p = 0.0; }\n" ! + " static private void add_pred(double x) { p = (1-x)*p + x; }\n" ! + " static private double[] finalize_pred() {\n" ! + " return new double[] {-p, p};\n" ! + " }\n"; } ! public double[] toCodeArray() { ! return new double[] {prediction}; ! } ! } --- 10,138 ---- class MixedBinaryPrediction extends BinaryPrediction implements NormalizedPrediction{ ! protected double deltaT; ! protected double normalizingConstant; ! public MixedBinaryPrediction() {super();} ! public MixedBinaryPrediction(double p) ! throws NotNormalizedPredException { ! (new Exception("asdf")).printStackTrace(); ! throw new NotNormalizedPredException("MixedBinaryPrediction: Need to normalize based on time!"); ! } ! public static double updateMargin(double currentMargin, double stepSize, double step, double dt, double nc) { ! //System.out.println("cm: " + currentMargin + ", deltax: " + stepSize*step + ", dt: " + dt + ", nc: " + nc); ! //System.out.println(Math.exp(-dt*nc)*currentMargin + stepSize*step); ! return Math.exp(-dt*nc)*currentMargin + stepSize*step; ! } ! public MixedBinaryPrediction(double p, double dt, double nc) ! throws NotNormalizedPredException { ! super(); ! deltaT = dt; ! prediction=p; ! normalizingConstant = nc; ! } ! public MixedBinaryPrediction(double p, double dt) ! throws NotNormalizedPredException { ! super(); ! deltaT = dt; ! prediction=p; ! normalizingConstant = 1; ! } ! public Object clone(){ ! Object a = new MixedBinaryPrediction(prediction, deltaT); ! return a; ! } ! /** ! * Be very careful with how this is used. See NormalizedPrediction ! * for details. ! */ ! public Prediction add(Prediction p) throws NotNormalizedPredException{ ! if (! (p instanceof MixedBinaryPrediction)) { ! throw new NotNormalizedPredException("Must use MixedBinaryPrediction when adding to a MixedBinaryPrediction."); ! } ! // H_t = (1-alpha)H_{t-1} + alpha h_t ! // The prediction p is for h_t ! double alpha = ((MixedBinaryPrediction) p).prediction; ! prediction = Math.exp(-normalizingConstant*deltaT) * prediction + alpha; ! /* if (Math.abs(alpha) > 1 || Math.abs(prediction) > 1) { throw new NotNormalizedPredException("Prediction may result in unnormalized " + "prediction! p is: " + p); } */ ! return this; ! } ! /** ! * This is not well defined for normalized predictions. However, we ! * do allow it. ! */ ! public Prediction scale(double w) throws NotNormalizedPredException{ ! if (Math.abs(w) > 1) { ! throw new NotNormalizedPredException("Scalar may result in unnormalized " ! + "prediction! w is: " + w); } + prediction *= w; + return this; + } ! public double getDeltaT() { ! return deltaT; ! } ! public double getNormConstant() { ! return normalizingConstant; ! } + public Prediction add(double w, Prediction p) { + ((MixedBinaryPrediction) p).scale(w); + this.add( (MixedBinaryPrediction) p ); + return this; + } + public boolean equals(Prediction other) { + MixedBinaryPrediction bp= (MixedBinaryPrediction) other; + return (prediction == bp.prediction && deltaT == bp.deltaT && normalizingConstant == bp.normalizingConstant); + } + public String toString() { + return "MixedBinaryPrediction. p(1)= " + prediction; + } + public String cPreamble() { + System.out.println("Prediction::cPreamble not supported for 'mixed' or normalized predictions."); + System.exit(2); + return + "typedef double Prediction_t;\n" + + "#define reset_pred() {p = 0.0;}\n" + + "#define add_pred(X) {p = (1-(X))*p + (X);}\n" + + "#define finalize_pred()" + + " ((r) ? (r[1] = p , r[0] = -p) : -p)\n"; + } + public String javaPreamble() { + System.out.println("Prediction::javaPreamble not supported for 'mixed' or normalized predictions."); + System.exit(2); + return "" + + " static private double p;\n" + + " static private void reset_pred() { p = 0.0; }\n" + + " static private void add_pred(double x) { p = (1-x)*p + x; }\n" + + " static private double[] finalize_pred() {\n" + + " return new double[] {-p, p};\n" + + " }\n"; + } + public double[] toCodeArray() { + return new double[] {prediction}; + } + } Index: AbstractBooster.java =================================================================== RCS file: /cvsroot/jboost/jboost/src/jboost/booster/AbstractBooster.java,v retrieving revision 1.8 retrieving revision 1.8.2.1 diff -C2 -d -r1.8 -r1.8.2.1 *** AbstractBooster.java 25 Mar 2008 01:00:27 -0000 1.8 --- AbstractBooster.java 7 Apr 2008 17:19:55 -0000 1.8.2.1 *************** *** 21,85 **** public abstract class AbstractBooster implements Booster, Serializable { ! protected static final String PREFIX= "booster_"; ! /** ! * Factory method to build a booster instance according to ! * given configuration. Uses reflection to do this. ! * ! * @param c set of options for the booster ! * @param num_labels the number of m_labels in the data ! * @param isMultiLabel true if multilabled data ! * @return Booster ! */ ! public static Booster getInstance(Configuration c, int num_labels, ! boolean isMultiLabel) ! throws ClassNotFoundException, InstantiationException, ! IllegalAccessException, Exception { ! AbstractBooster result = null; ! // Get the booster type from configuration and ! // create a class of that type. ! String boosterType= c.getString(PREFIX + "type", ! "jboost.booster.AdaBoost"); ! System.out.println("Booster type: " + boosterType); ! Class boosterClass = Class.forName(boosterType); ! result = (AbstractBooster) boosterClass.newInstance(); ! result.init(c); ! // Get the runtime of the booster (if applicable). ! // If the booster is a discrete iterative scheme, the ! // number of iterations is dealt with elsewhere. ! if (result instanceof jboost.booster.BrownBoost) { ! double eps = 0.001; ! double runtime = Double.parseDouble(c.getString("boostingRuntime", ! "0.0")); ! if (runtime <= eps) { ! String str = "Need to specify runtime for m_booster " + result ! + ". Runtime must be larger than " + eps + "."; ! Monitor.log(str); ! throw new Exception(str); ! } ! jboost.booster.BrownBoost brown = (jboost.booster.BrownBoost) result; ! brown.setRuntime(runtime); ! result = brown; ! if (result instanceof jboost.booster.YabaBoost) { ! jboost.booster.YabaBoost yaba = (jboost.booster.YabaBoost) result; ! double c1=0, c2=0, theta=0, nc=0; ! double rpos=0, c1pos=0, c2pos=0, thetapos=0, ncpos=0; ! double rneg=0, c1neg=0, c2neg=0, thetaneg=0, ncneg=0; ! ControllerConfiguration conf = (ControllerConfiguration)c; ! try { ! c1 = Double.parseDouble(c.getString("c1", "Z1.0")); ! c2 = Double.parseDouble(c.getString("c2", "Z1.0")); ! theta = Double.parseDouble(c.getString("theta", "Z0.15")); ! nc = Double.parseDouble(c.getString("nc", "Z0.15")); ! yaba.setParams(c1,c2,theta,nc); ! if (conf.getCostSensitive()) { ! /* System.out.println(" runtime: " + rpos + "," + rneg + " c1: " + c1pos + "," + c1neg + --- 21,87 ---- public abstract class AbstractBooster implements Booster, Serializable { ! protected static final String PREFIX= "booster_"; ! /** ! * Factory method to build a booster instance according to ! * given configuration. Uses reflection to do this. ! * ! * @param c set of options for the booster ! * @param num_labels the number of m_labels in the data ! * @param isMultiLabel true if multilabled data ! * @return Booster ! */ ! public static Booster getInstance(Configuration c, int num_labels, ! boolean isMultiLabel) ! throws ClassNotFoundException, InstantiationException, ! IllegalAccessException, Exception { ! AbstractBooster result = null; ! // Get the booster type from configuration and ! // create a class of that type. ! String boosterType= c.getString(PREFIX + "type", ! "jboost.booster.AdaBoost"); ! System.out.println("Booster type: " + boosterType); ! Class boosterClass = Class.forName(boosterType); ! result = (AbstractBooster) boosterClass.newInstance(); ! result.init(c); ! // Get the runtime of the booster (if applicable). ! // If the booster is a discrete iterative scheme, the ! // number of iterations is dealt with elsewhere. ! // XXX DJH: Why isn't this done in BrownBoost/YABA.init()? ! // XXX YF: I also think this should not be done in AbstractBooster. ! if (result instanceof jboost.booster.BrownBoost) { ! double eps = 0.001; ! double runtime = Double.parseDouble(c.getString("boostingRuntime", ! "0.0")); ! if (runtime <= eps) { ! String str = "Need to specify runtime for m_booster " + result ! + ". Runtime must be larger than " + eps + "."; ! Monitor.log(str); ! throw new Exception(str); ! } ! jboost.booster.BrownBoost brown = (jboost.booster.BrownBoost) result; ! brown.setRuntime(runtime); ! result = brown; ! if (result instanceof jboost.booster.YabaBoost) { ! jboost.booster.YabaBoost yaba = (jboost.booster.YabaBoost) result; ! double c1=0, c2=0, theta=0, nc=0; ! double rpos=0, c1pos=0, c2pos=0, thetapos=0, ncpos=0; ! double rneg=0, c1neg=0, c2neg=0, thetaneg=0, ncneg=0; ! ControllerConfiguration conf = (ControllerConfiguration)c; ! try { ! c1 = Double.parseDouble(c.getString("c1", "Z1.0")); ! c2 = Double.parseDouble(c.getString("c2", "Z1.0")); ! theta = Double.parseDouble(c.getString("theta", "Z0.15")); ! nc = Double.parseDouble(c.getString("nc", "Z0.15")); ! yaba.setParams(c1,c2,theta,nc); ! if (conf.getCostSensitive()) { ! /* System.out.println(" runtime: " + rpos + "," + rneg + " c1: " + c1pos + "," + c1neg + *************** *** 88,97 **** " nc: " + ncpos + "," + ncneg ); ! */ ! rpos = Double.parseDouble(c.getString("pos_c","Z")); ! rneg = Double.parseDouble(c.getString("neg_c","Z")); ! c1pos = Double.parseDouble(c.getString("pos_c1","Z")); ! c1neg = Double.parseDouble(c.getString("neg_c1","Z")); ! /* System.out.println(" runtime: " + rpos + "," + rneg + " c1: " + c1pos + "," + c1neg + --- 90,99 ---- " nc: " + ncpos + "," + ncneg ); ! */ ! rpos = Double.parseDouble(c.getString("pos_c","Z")); ! rneg = Double.parseDouble(c.getString("neg_c","Z")); ! c1pos = Double.parseDouble(c.getString("pos_c1","Z")); ! c1neg = Double.parseDouble(c.getString("neg_c1","Z")); ! /* System.out.println(" runtime: " + rpos + "," + rneg + " c1: " + c1pos + "," + c1neg + *************** *** 100,111 **** " nc: " + ncpos + "," + ncneg ); ! */ ! c2pos = Double.parseDouble(c.getString("pos_c2","Z")); ! c2neg = Double.parseDouble(c.getString("neg_c2","Z")); ! thetapos = Double.parseDouble(c.getString("pos_theta","Z")); ! thetaneg = Double.parseDouble(c.getString("neg_theta","Z")); ! ncpos = Double.parseDouble(c.getString("pos_nc","Z")); ! ncneg = Double.parseDouble(c.getString("neg_nc","Z")); ! /* System.out.println(" runtime: " + rpos + "," + rneg + " c1: " + c1pos + "," + c1neg + --- 102,113 ---- " nc: " + ncpos + "," + ncneg ); ! */ ! c2pos = Double.parseDouble(c.getString("pos_c2","Z")); ! c2neg = Double.parseDouble(c.getString("neg_c2","Z")); ! thetapos = Double.parseDouble(c.getString("pos_theta","Z")); ! thetaneg = Double.parseDouble(c.getString("neg_theta","Z")); ! ncpos = Double.parseDouble(c.getString("pos_nc","Z")); ! ncneg = Double.parseDouble(c.getString("neg_nc","Z")); ! /* System.out.println(" runtime: " + rpos + "," + rneg + " c1: " + c1pos + "," + c1neg + *************** *** 114,235 **** " nc: " + ncpos + "," + ncneg ); ! */ ! yaba.setCostSensitiveParams(rpos, c1pos, c2pos, thetapos, ncpos, ! rneg, c1neg, c2neg, thetaneg, ncneg); ! } ! } catch (NumberFormatException e) { ! System.err.println("Need to supply appropriate parameters!"); ! System.err.println("For YabaBoost normal, we need r, c1, c2, nc, and theta!"); ! System.err.println("For YabaBoost cost sensitive, we need neg_c, pos_c, neg_c1, pos_c1, neg_c2, pos_c2, neg_theta, pos_theta, neg_nc, pos_nc"); ! throw new InstantiationException("Need more params for yaba"); ! } ! result = yaba; ! } ! } ! ! // If we have a multilable or multiclass problem, we need to wrap it. ! if (num_labels > 2 || isMultiLabel) { ! result= new MulticlassWrapMH(result, num_labels, isMultiLabel); ! } ! ! // If we are debugging, then wrap in paranoia ! boolean paranoid= c.getBool(PREFIX + "paranoid", false); ! if (paranoid) { ! result= new DebugWrap(result); } - return result; } ! public int getNumExamples(){ ! return 0; } ! public String getParamString() { ! return "No parameters defined"; } ! /** ! * Create and return a new Bag which initially contains the ! * elements in the list. ! * ! * @param list initial items to add to the Bag ! */ ! public Bag newBag(int[] list) { ! Bag bag= newBag(); ! bag.addExampleList(list); ! return bag; ! } - /** - * Clone a bag - * - * @param orig the bag to clone - * @return new bag - */ - public Bag newBag(Bag orig) { - Bag newbag= newBag(); - newbag.copyBag(orig); - return newbag; - } ! /** ! * Find the best binary split for a sorted list of example indices ! * with given split points. ! * @param l an array of example indices, sorted. ! * @param sp an array with true in position i when a split between ! * positions i-1 and i should be checked ! * @param b0 - a bag with all points below the best split (upon return) ! * @param b1 - a bag with all points at or above the best split (upon return) ! * @return the index in l where the best split occurred (possibly ! * 0 if the best split puts all points on one side) ! */ ! public int findBestSplit(Bag b0, Bag b1, int[] l, boolean[] sp) { ! Bag[] bags= new Bag[2]; - bags[0]= newBag(); // init an empty bag - bags[1]= newBag(l); // init a full bag ! b0.reset(); ! b1.copyBag(bags[1]); ! if (l.length == 0) ! return 0; ! double bestLoss= getLoss(bags); ! int bestIndex= 0; ! double loss; ! for (int i= 0; i < l.length - 1; i++) { ! bags[1].subtractExample(l[i]); ! bags[0].addExample(l[i]); ! if (sp[i + 1]) { // if this is a potential split point ! if ((loss= getLoss(bags)) < bestLoss) { ! bestLoss= loss; ! bestIndex= i + 1; ! b0.copyBag(bags[0]); ! b1.copyBag(bags[1]); ! } ! } } ! return bestIndex; } ! /** ! * Compute the loss associated with an array of bags where small ! * loss is considered "better". We assume that loss is additive ! * for a set of bags. ! * ! * @param bags array of bags whose losses will be added up and returned ! * @return loss the sum of the losses for all the bags ! */ ! public double getLoss(Bag[] bags) { ! double loss = 0; ! for (int i=0; i < bags.length; i++) { ! loss += bags[i].getLoss(); ! } ! return loss; } } --- 116,237 ---- " nc: " + ncpos + "," + ncneg ); ! */ ! yaba.setCostSensitiveParams(rpos, c1pos, c2pos, thetapos, ncpos, ! rneg, c1neg, c2neg, thetaneg, ncneg); ! } ! } catch (NumberFormatException e) { ! System.err.println("Need to supply appropriate parameters!"); ! System.err.println("For YabaBoost normal, we need r, c1, c2, nc, and theta!"); ! System.err.println("For YabaBoost cost sensitive, we need neg_c, pos_c, neg_c1, pos_c1, neg_c2, pos_c2, neg_theta, pos_theta, neg_nc, pos_nc"); ! throw new InstantiationException("Need more params for yaba"); ! } ! result = yaba; } } ! // If we have a multilable or multiclass problem, we need to wrap it. ! if (num_labels > 2 || isMultiLabel) { ! result= new MulticlassWrapMH(result, num_labels, isMultiLabel); } ! // If we are debugging, then wrap in paranoia ! boolean paranoid= c.getBool(PREFIX + "paranoid", false); ! if (paranoid) { ! result= new DebugWrap(result); } + return result; + } + public int getNumExamples(){ + return 0; + } ! public String getParamString() { ! return "No parameters defined"; ! } + /** + * Create and return a new Bag which initially contains the + * elements in the list. + * + * @param list initial items to add to the Bag + */ + public Bag newBag(int[] list) { + Bag bag= newBag(); + bag.addExampleList(list); + return bag; + } ! /** ! * Clone a bag ! * ! * @param orig the bag to clone ! * @return new bag ! */ ! public Bag newBag(Bag orig) { ! Bag newbag= newBag(); ! newbag.copyBag(orig); ! return newbag; ! } ! /** ! * Find the best binary split for a sorted list of example indices ! * with given split points. ! * @param l an array of example indices, sorted. ! * @param sp an array with true in position i when a split between ! * positions i-1 and i should be checked ! * @param b0 - a bag with all points below the best split (upon return) ! * @param b1 - a bag with all points at or above the best split (upon return) ! * @return the index in l where the best split occurred (possibly ! * 0 if the best split puts all points on one side) ! */ ! public int findBestSplit(Bag b0, Bag b1, int[] l, boolean[] sp) { ! Bag[] bags= new Bag[2]; ! bags[0]= newBag(); // init an empty bag ! bags[1]= newBag(l); // init a full bag ! b0.reset(); ! b1.copyBag(bags[1]); ! if (l.length == 0) ! return 0; ! ! double bestLoss= getLoss(bags); ! int bestIndex= 0; ! double loss; ! ! for (int i= 0; i < l.length - 1; i++) { ! bags[1].subtractExample(l[i]); ! bags[0].addExample(l[i]); ! if (sp[i + 1]) { // if this is a potential split point ! if ((loss= getLoss(bags)) < bestLoss) { ! bestLoss= loss; ! bestIndex= i + 1; ! b0.copyBag(bags[0]); ! b1.copyBag(bags[1]); } ! } } + return bestIndex; + } ! /** ! * Compute the loss associated with an array of bags where small ! * loss is considered "better". We assume that loss is additive ! * for a set of bags. ! * ! * @param bags array of bags whose losses will be added up and returned ! * @return loss the sum of the losses for all the bags ! */ ! public double getLoss(Bag[] bags) { ! double loss = 0; ! for (int i=0; i < bags.length; i++) { ! loss += bags[i].getLoss(); } + return loss; + } } |