MapReduce-KNN для Hadoop - запускать несколько тестовых случаев из одного файла данных - PullRequest
0 голосов
/ 28 августа 2018

Фон: [ Перейдите к следующему разделу для точной проблемы ]

В настоящее время я работаю над Hadoop как небольшим проектом в моем университете (это не обязательный проект, я делаю это, потому что хочу).

Мой план состоял в том, чтобы использовать 5 компьютеров в одной из лабораторий (Master + 4 Slaves) для запуска алгоритма KNN на большом наборе данных, чтобы выяснить время работы и т. Д.

Я знал, что могу найти основной код в Интернете, и я нашел его (https://github.com/matt-hicks/MapReduce-KNN). Он хорошо работает для одного тестового случая, но у меня есть очень большой с сотнями тестовых случаев. Поэтому мне нужно было повторять один и тот же бит кода для каждого теста.

Проблема :

tl; dr: у меня есть программа KNN, которая принимает только один тестовый случай за раз, но я хочу сделать его итеративным, чтобы он мог работать с несколькими тестовыми случаями.

Мое решение:

Я не очень разбираюсь в этом, и из основ, которые я знаю, я решил превратить переменные и карты в массивы переменных и массивы карт.

Так вот:

    public static class KnnMapper extends Mapper<Object, Text, NullWritable, DoubleString>
    {
        DoubleString distanceAndModel = new DoubleString();
        TreeMap<Double, String> KnnMap = new TreeMap<Double, String>();

        // Declaring some variables which will be used throughout the mapper
        int K;

        double normalisedSAge;
        double normalisedSIncome;
        String sStatus;
        String sGender;
double normalisedSChildren;

стало так:

DoubleString distanceAndModel = new DoubleString();
    TreeMap<Double, String>[] KnnMap = new TreeMap<Double, String>[1000];



    // Declaring some variables which will be used throughout the mapper
    int[] K = new int[1000];

    double[] normalisedSAge = new double[1000];
    double[] normalisedSIncome = new double[1000];
    String[] sStatus = new String[1000];
    String[] sGender = new String[1000];
    double[] normalisedSChildren = new double[1000];
    int n = 0;

А это:

        protected void setup(Context context) throws IOException, InterruptedException
    {
        if (context.getCacheFiles() != null && context.getCacheFiles().length > 0)
        {
            // Read parameter file using alias established in main()
            String knnParams = FileUtils.readFileToString(new File("./knnParamFile"));
            StringTokenizer st = new StringTokenizer(knnParams, ",");

            // Using the variables declared earlier, values are assigned to K and to the test dataset, S.
            // These values will remain unchanged throughout the mapper
            K = Integer.parseInt(st.nextToken());
            normalisedSAge = normalisedDouble(st.nextToken(), minAge, maxAge);
            normalisedSIncome = normalisedDouble(st.nextToken(), minIncome, maxIncome);
            sStatus = st.nextToken();
            sGender = st.nextToken();
            normalisedSChildren = normalisedDouble(st.nextToken(), minChildren, maxChildren);
        }

}

стало так:

protected void setup(Context context) throws IOException, InterruptedException
    {
        if (context.getCacheFiles() != null && context.getCacheFiles().length > 0)
        {
            // Read parameter file using alias established in main()
            String knnParams = FileUtils.readFileToString(new File("./knnParamFile"));
            //Splitting input File if we hit a newline character or return carriage i.e., Windown Return Key as input
            StringTokenizer lineSt = new StringTokenizer(knnParams, "\n\r");

            //Running a loop to tokennize each line of inputs or test cases
            while(lineSt.hasMoreTokens()){
            String nextLine = lineSt.nextToken();   //Converting current line to a string
            StringTokenizer st = new StringTokenizer(nextLine, ","); // Tokenizing the current string or singular data

            // Using the variables declared earlier, values are assigned to K and to the test dataset, S.
            // These values will remain unchanged throughout the mapper
            K[n] = Integer.parseInt(st.nextToken());
            normalisedSAge[n] = normalisedDouble(st.nextToken(), minAge, maxAge);
            normalisedSIncome[n] = normalisedDouble(st.nextToken(), minIncome, maxIncome);
            sStatus[n] = st.nextToken();
            sGender[n] = st.nextToken();
            normalisedSChildren[n] = normalisedDouble(st.nextToken(), minChildren, maxChildren);
            n++;
        }}
    }

И так далее для класса редуктора.

Впервые я работаю с TreeMaps. Я изучал и использовал деревья раньше, но не Карты и TreeMaps. Я все еще пытался сделать это и массив, который оказался неправильным:

/ home / hduser / Desktop / knn / KnnPattern.java: 81: ошибка: создание универсального массива TreeMap [] KnnMap = new TreeMap [1000]; ^

/ home / hduser / Desktop / knn / KnnPattern.java: 198: ошибка: несовместимая типы: double [] не может быть преобразовано в double нормализованный Rhildren, нормализованный возраст, нормализованный доход, sStatus, sGender, нормализованный ребенок); ^

/ home / hduser / Desktop / knn / KnnPattern.java: 238: ошибка: универсальный массив создание TreeMap [] KnnMap = новый TreeMap [1000]; ^

/ home / hduser / Desktop / knn / KnnPattern.java: 283: ошибка: неверные типы операндов для бинарного оператора '>' if (KnnMap [num] .size ()> K) ^ первый тип: int второй тип: int []

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

Но я до сих пор в основном работал с C / C ++ и Python в Uni. ООП здесь, кажется, облегчает жизнь людям, но я не уверен на 100%, как им пользоваться.

Мой вопрос:

Можно ли создать связанный список TreeMaps?

Есть ли связанный список для замены:

TreeMap<Double, String>[] KnnMap = new TreeMap<Double, String>[1000];

И правильный ли мой подход к проблеме? Создание итеративного кода должно помочь пройти через все тесты, верно?

Я попытаюсь заставить его работать оттуда с попытками и ошибками. Но это то, что я застрял несколько дней назад.

Приношу свои извинения, если кто-то уже спрашивал об этом раньше, но я ничего не мог найти, и поэтому мне пришлось написать вопрос. Пожалуйста, поделитесь ссылкой на любой связанный ответ, если вы считаете, что на него уже был дан ответ.

Спасибо! И, на заметку: все, что я должен иметь в виду при работе с TreeMaps и, в частности, связанный список TreeMaps.

1 Ответ

0 голосов
/ 28 августа 2018

По поводу сообщений об ошибках

/home/hduser/Desktop/knn/KnnPattern.java:81: error: generic array creation TreeMap[] KnnMap = new TreeMap[1000]; ^

и

/home/hduser/Desktop/knn/KnnPattern.java:238: error: generic array creation TreeMap[] KnnMap = new TreeMap[1000]; ^

Эти ошибки возникают из-за того, что вы пытались создать экземпляр из универсального типа компонента, который не поддерживается Java, поскольку универсальные типы теряются во время выполнения. Обходной путь (если вам действительно нужен массив) - создать List из TreeMap объектов и затем преобразовать его в массив:

// TreeMap<Double, String>[] KnnMap = new TreeMap<Double, String>[1000];
List<TreeMap<Double, String>> KnnMapList = new LinkedList<>();
TreeMap<Double, String>[] KnnMap = (TreeMap<Double, String>[]) KnnMapList.toArray();

См. этот вопрос для получения дополнительной информации.


/home/hduser/Desktop/knn/KnnPattern.java:198: error: incompatible types: double[] cannot be converted to double normalisedRChildren, normalisedSAge, normalisedSIncome, sStatus, sGender, normalisedSChildren); ^

Глядя на исходный код на GitHub, я понял, что вы, вероятно, не модифицировали следующий вызов метода в методе KnnMapper#map(Object, Text, Context):

double tDist = totalSquaredDistance(normalisedRAge, normalisedRIncome, rStatus, rGender,
                    normalisedRChildren, normalisedSAge, normalisedSIncome, sStatus, sGender, normalisedSChildren);

должно быть

double tDist = totalSquaredDistance(normalisedRAge, normalisedRIncome, rStatus, rGender,
                    normalisedRChildren, normalisedSAge[n], normalisedSIncome[n], sStatus[n], sGender[n], normalisedSChildren[n]);

Но я предполагаю, что эти модификации не дадут вам нужную функцию, потому что KnnMapper#map(Object, Text, Context) вызывается только один раз для пары ключ / значение, как указано здесь , и вы, вероятно, захотите вызвать ее n раз.


Конкретная задача

Во избежание дальнейших проблем я предлагаю оставить верхний код класса GitHub без изменений и модифицировать метод KnnPattern#main(String[]) только таким образом, чтобы он вызывал задание n раз, как описано в this ответ.


Редактировать: Пример

Это модифицированный метод KnnPattern#main(String[]), который читает строку файла данных построчно, создает временный файл с текущей строкой в ​​качестве содержимого и запускает задание с временным файлом в качестве файла кэша.
(при условии, что вы используете хотя бы Java 7)

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
...
public class KnnPattern
{
  ...
    public static void main(String[] args) throws Exception {
        // Create configuration
        Configuration conf = new Configuration();

        if (args.length != 3) {
            System.err.println("Usage: KnnPattern <in> <out> <parameter file>");
            System.exit(2);
        }

        try (final BufferedReader br = new BufferedReader(new FileReader(args[2]))) {
            int n = 1;
            String line;
            while ((line = br.readLine()) != null) {
                // create temporary file with content of current line
                final File tmpDataFile = File.createTempFile("hadoop-test-", null);
                try (BufferedWriter tmpDataWriter = new BufferedWriter(new FileWriter(tmpDataFile))) {
                    tmpDataWriter.write(line);
                    tmpDataWriter.flush();
                }

                // Create job
                Job job = Job.getInstance(conf, "Find K-Nearest Neighbour #" + n);
                job.setJarByClass(KnnPattern.class);
                // Set the third parameter when running the job to be the parameter file and give it an alias
                job.addCacheFile(new URI(tmpDataFile.getAbsolutePath() + "#knnParamFile")); // Parameter file containing test data

                // Setup MapReduce job
                job.setMapperClass(KnnMapper.class);
                job.setReducerClass(KnnReducer.class);
                job.setNumReduceTasks(1); // Only one reducer in this design

                // Specify key / value
                job.setMapOutputKeyClass(NullWritable.class);
                job.setMapOutputValueClass(DoubleString.class);
                job.setOutputKeyClass(NullWritable.class);
                job.setOutputValueClass(Text.class);

                // Input (the data file) and Output (the resulting classification)
                FileInputFormat.addInputPath(job, new Path(args[0]));
                FileOutputFormat.setOutputPath(job, new Path(args[1] + "_" + n));

                // Execute job
                final boolean jobSucceeded = job.waitForCompletion(true);

                // clean up
                tmpDataFile.delete();

                if (!jobSucceeded) {
                    // return error status if job failed
                    System.exit(1);
                }

                ++n;
            }
        }
    }

}
...