Найти среднее число чисел с помощью MapReduce

Я пытался написать код, чтобы найти среднее число чисел с помощью MapReduce.

Я пытаюсь использовать глобальные счетчики для достижения своей цели, но я не могу установить значение счетчика в map метод моего картографа, и я также не могу повторить значение счетчика в reduce метод моего редуктора.

должен ли я использовать глобальный счетчик в map в любом случае (например, с помощью incrCounter(key, amount) предоставленной Reporter)? Или вы предложите другую логику? чтобы получить средние цифры?

4 ответов


логика довольно проста: Если все число имеет такой же ключ, затем сопоставитель отправил все значения, которые вы хотите найти в среднем с тем же ключом. Из-за этого в редукторе вы можете суммировать значения в итераторе. Затем вы можете сохранить счетчик по времени номера работает итератор, который решает вопрос о том, сколько элементов должно быть усреднено. Наконец, после итератора вы можете найти среднее значение, разделив сумму на число предметы.

будьте осторожны эта логика не будет работать если класс объединителя установлен как тот же класс, что и редуктор...


используйте все 3 Mapper / Combiner / Reducer для того чтобы разрешить вопрос. См. ниже ссылку для полного кода и объяснения

http://alchemistviews.blogspot.com/2013/08/calculate-average-in-map-reduce-using.html


средняя сумма / размер. Если sum - это что-то вроде sum = k1 + k2 + k3 + ... , вы можете разделить по размеру после или во время суммирования. Таким образом, среднее значение также k1 / size + k2 / size + k3 / size + ...

код Java 8 прост:

    public double average(List<Valuable> list) {
      final int size = list.size();
      return list
            .stream()
            .mapToDouble(element->element.someValue())
            .reduce(0,(sum,x)->sum+x/size);
    }

таким образом, вы сначала сопоставляете каждое значение ваших элементов в списке, чтобы удвоить, а затем суммировать с помощью функции reduce.


среднее арифметическое является агрегатной функцией, которая не является дистрибутивной, но алгебраической. Согласно Han et al. агрегатная функция является дистрибутивной, если:

[...] он может быть вычислен [...] следующим образом. Предполагать.[ .] данные разделяются на n наборы. Мы применяем функцию к каждому разделу, в результате чего n статистические значения. Если результат получен путем применения функции к n совокупность значений так же, как и при применении функции ко всему набору данных (без секционирования), функция может быть вычислена распределенным образом.

или другими словами, он должен быть ассоциативным и коммутативным. Однако агрегатная функция алгебраична в соответствии с Han et al. если:

[...] он может быть вычислен алгебраической функцией с M аргументами (где m-ограниченное положительное целое число), каждый из которых получен применение статистической функции распределения.

для среднего арифметического это просто avg = sum / count. Очевидно, что вы должны нести счет дополнительно. Но использование глобального счетчика для этого кажется неправильным. The API описание org.apache.hadoop.mapreduce.Counter следующим образом:

именованный счетчик, который отслеживает ход выполнения задания map / reduce.

счетчики должны обычно использоваться для статистики о заданиях в любом случае, но не в рамках расчетов в ходе данных обработки.

таким образом, все, что вам нужно сделать в разделе, это добавить свои номера и отслеживать их количество вместе с суммой (сумма, количество); простой подход может быть строкой типа <sum><separator><count>.

в картографе счетчик всегда будет 1, а сумма-это само исходное значение. Чтобы уменьшить файлы карт уже можно использовать объединитель и обрабатывать агрегаты, как (sum_1 + ... + sum_n, count_1 + ... + count_n). Это должно быть повторено в редукторе и закончено окончательным расчетом sum/count. имейте в виду, что этот подход не зависит от используемого ключа!

наконец, вот простой пример использования raw статистика преступности Лос-Анджелеса который должен вычислять "среднее время преступления"в Лос-Анджелесе:

public class Driver extends Configured implements Tool {
    enum Counters {
        DISCARDED_ENTRY
    }

    public static void main(String[] args) throws Exception {
        ToolRunner.run(new Driver(), args);
    }

    public int run(String[] args) throws Exception {
        Configuration configuration = getConf();

        Job job = Job.getInstance(configuration);
        job.setJarByClass(Driver.class);

        job.setMapperClass(Mapper.class);
        job.setMapOutputKeyClass(LongWritable.class);
        job.setMapOutputValueClass(Text.class);

        job.setCombinerClass(Combiner.class);
        job.setReducerClass(Reducer.class);
        job.setOutputKeyClass(LongWritable.class);
        job.setOutputValueClass(Text.class);

        FileInputFormat.addInputPath(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, new Path(args[1]));

        return job.waitForCompletion(true) ? 0 : -1;
    }
}

public class Mapper extends org.apache.hadoop.mapreduce.Mapper<
    LongWritable,
    Text,
    LongWritable,
    Text
> {

    @Override
    protected void map(
        LongWritable key,
        Text value,
        org.apache.hadoop.mapreduce.Mapper<
            LongWritable,
            Text,
            LongWritable,
            Text
        >.Context context
    ) throws IOException, InterruptedException {
            // parse the CSV line
            ArrayList<String> values = this.parse(value.toString());

            // validate the parsed values
            if (this.isValid(values)) {

                // fetch the third and the fourth column
                String time = values.get(3);
                String year = values.get(2)
                    .substring(values.get(2).length() - 4);

                // convert time to minutes (e.g. 1542 -> 942)
                int minutes = Integer.parseInt(time.substring(0, 2))
                    * 60 + Integer.parseInt(time.substring(2,4));

                // create the aggregate atom (a/n)
                // with a = time in minutes and n = 1
                context.write(
                    new LongWritable(Integer.parseInt(year)),
                    new Text(Integer.toString(minutes) + ":1")
                );
            } else {
                // invalid line format, so we increment a counter
                context.getCounter(Driver.Counters.DISCARDED_ENTRY)
                    .increment(1);
            }
    }

    protected boolean isValid(ArrayList<String> values) {
        return values.size() > 3 
            && values.get(2).length() == 10 
            && values.get(3).length() == 4;
    }

    protected ArrayList<String> parse(String line) {
        ArrayList<String> values = new ArrayList<>();
        String current = "";
        boolean escaping = false;

        for (int i = 0; i < line.length(); i++){
            char c = line.charAt(i);

            if (c == '"') {
                escaping = !escaping;
            } else if (c == ',' && !escaping) {
                values.add(current);
                current = "";
            } else {
                current += c;
            }
        }

        values.add(current);

        return values;
    }
}

public class Combiner extends org.apache.hadoop.mapreduce.Reducer<
    LongWritable,
    Text,
    LongWritable,
    Text
> {

    @Override
    protected void reduce(
        LongWritable key,
        Iterable<Text> values,
        Context context
    ) throws IOException, InterruptedException {
        Long n = 0l;
        Long a = 0l;
        Iterator<Text> iterator = values.iterator();

        // calculate intermediate aggregates
        while (iterator.hasNext()) {
            String[] atom = iterator.next().toString().split(":");
            a += Long.parseLong(atom[0]);
            n += Long.parseLong(atom[1]);
        }

        context.write(key, new Text(Long.toString(a) + ":" + Long.toString(n)));
    }
}

public class Reducer extends org.apache.hadoop.mapreduce.Reducer<
    LongWritable,
    Text,
    LongWritable,
    Text
> {

    @Override
    protected void reduce(
        LongWritable key, 
        Iterable<Text> values, 
        Context context
    ) throws IOException, InterruptedException {
        Long n = 0l;
        Long a = 0l;
        Iterator<Text> iterator = values.iterator();

        // calculate the finale aggregate
        while (iterator.hasNext()) {
            String[] atom = iterator.next().toString().split(":");
            a += Long.parseLong(atom[0]);
            n += Long.parseLong(atom[1]);
        }

        // cut of seconds
        int average = Math.round(a / n);

        // convert the average minutes back to time
        context.write(
            key,
            new Text(
                Integer.toString(average / 60) 
                    + ":" + Integer.toString(average % 60)
            )
        );
    }
}