/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.mahout.math.hadoop.stochasticsvd; import java.io.IOException; import java.util.Iterator; import org.apache.commons.lang.Validate; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.SequenceFile.CompressionType; import org.apache.hadoop.io.Writable; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.Mapper; import org.apache.hadoop.mapreduce.Reducer; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.input.SequenceFileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; import org.apache.hadoop.mapreduce.lib.output.SequenceFileOutputFormat; import org.apache.mahout.math.DenseVector; import org.apache.mahout.math.Vector; import org.apache.mahout.math.VectorWritable; /** * Job that accumulates Y'Y output * */ public class YtYJob { public static final String PROP_OMEGA_SEED = "ssvd.omegaseed"; public static final String PROP_K = "ssvd.k"; public static final String PROP_P = "ssvd.p"; // we have single output, so we use standard output public static final String OUTPUT_YtY = "part-"; private YtYJob() { } public static class YtYMapper extends Mapper<Writable, VectorWritable, IntWritable, VectorWritable> { private int kp; private Omega omega; private UpperTriangular mYtY; /* * we keep yRow in a dense form here but keep an eye not to dense up while * doing YtY products. I am not sure that sparse vector would create much * performance benefits since we must to assume that y would be more often * dense than sparse, so for bulk dense operations that would perform * somewhat better than a RandomAccessSparse vector frequent updates. */ private Vector yRow; @Override protected void setup(Context context) throws IOException, InterruptedException { int k = context.getConfiguration().getInt(PROP_K, -1); int p = context.getConfiguration().getInt(PROP_P, -1); Validate.isTrue(k > 0, "invalid k parameter"); Validate.isTrue(p > 0, "invalid p parameter"); kp = k + p; long omegaSeed = Long.parseLong(context.getConfiguration() .get(PROP_OMEGA_SEED)); omega = new Omega(omegaSeed, k, p); mYtY = new UpperTriangular(kp); // see which one works better! // yRow = new RandomAccessSparseVector(kp); yRow = new DenseVector(kp); } @Override protected void map(Writable key, VectorWritable value, Context context) throws IOException, InterruptedException { omega.computeYRow(value.get(), yRow); // compute outer product update for YtY if (yRow.isDense()) { for (int i = 0; i < kp; i++) { double yi; if ((yi = yRow.getQuick(i)) == 0.0) { continue; // avoid densing up here unnecessarily } for (int j = i; j < kp; j++) { double yj; if ((yj = yRow.getQuick(j)) != 0.0) { mYtY.setQuick(i, j, mYtY.getQuick(i, j) + yi * yj); } } } } else { /* * the disadvantage of using sparse vector (aside from the fact that we * are creating some short-lived references) here is that we obviously * do two times more iterations then necessary if y row is pretty dense. */ for (Iterator<Vector.Element> iterI = yRow.iterateNonZero(); iterI .hasNext();) { Vector.Element eli = iterI.next(); int i = eli.index(); for (Iterator<Vector.Element> iterJ = yRow.iterateNonZero(); iterJ .hasNext();) { Vector.Element elj = iterJ.next(); int j = elj.index(); if (j < i) { continue; } mYtY.setQuick(i, j, mYtY.getQuick(i, j) + eli.get() * elj.get()); } } } } @Override protected void cleanup(Context context) throws IOException, InterruptedException { context.write(new IntWritable(context.getTaskAttemptID().getTaskID() .getId()), new VectorWritable(new DenseVector(mYtY.getData()))); } } public static class YtYReducer extends Reducer<IntWritable, VectorWritable, IntWritable, VectorWritable> { private final VectorWritable accum = new VectorWritable(); private DenseVector acc; @Override protected void setup(Context context) throws IOException, InterruptedException { int k = context.getConfiguration().getInt(PROP_K, -1); int p = context.getConfiguration().getInt(PROP_P, -1); Validate.isTrue(k > 0, "invalid k parameter"); Validate.isTrue(p > 0, "invalid p parameter"); accum.set(acc = new DenseVector(k + p)); } @Override protected void cleanup(Context context) throws IOException, InterruptedException { context.write(new IntWritable(), accum); } @Override protected void reduce(IntWritable key, Iterable<VectorWritable> values, Context arg2) throws IOException, InterruptedException { for (VectorWritable vw : values) { acc.addAll(vw.get()); } } } public static void run(Configuration conf, Path[] inputPaths, Path outputPath, int k, int p, long seed) throws ClassNotFoundException, InterruptedException, IOException { Job job = new Job(conf); job.setJobName("YtY-job"); job.setJarByClass(YtYJob.class); job.setInputFormatClass(SequenceFileInputFormat.class); FileInputFormat.setInputPaths(job, inputPaths); FileOutputFormat.setOutputPath(job, outputPath); SequenceFileOutputFormat.setOutputCompressionType(job, CompressionType.BLOCK); job.setMapOutputKeyClass(IntWritable.class); job.setMapOutputValueClass(VectorWritable.class); job.setOutputKeyClass(IntWritable.class); job.setOutputValueClass(VectorWritable.class); job.setMapperClass(YtYMapper.class); job.getConfiguration().setLong(PROP_OMEGA_SEED, seed); job.getConfiguration().setInt(PROP_K, k); job.getConfiguration().setInt(PROP_P, p); /* * we must reduce to just one matrix which means we need only one reducer. * But it's ok since each mapper outputs only one vector (a packed * UpperTriangular) so even if there're thousands of mappers, one reducer * should cope just fine. */ job.setNumReduceTasks(1); job.submit(); job.waitForCompletion(false); if (!job.isSuccessful()) { throw new IOException("YtY job unsuccessful."); } } }