Статьи

Создание механизма рекомендаций: машинное обучение с HDInsight, Mahout и Hadoop

Хочешь помочь кому-нибудь сегодня?

образДавайте поможем ребятам из Stack Exchange предложить вопросы, на которые он может ответить, исходя из его истории ответов, подобно тому, как Amazon предлагает вам продукты на основе вашей предыдущей истории покупок. Если вы не знаете, что делает Stack Exchange — они управляют несколькими сайтами вопросов и ответов, включая чрезвычайно популярное переполнение стека. 

Наша цель здесь — увидеть, как мы можем проанализировать прошлые ответы пользователя, предсказать вопросы, на которые он может ответить в будущем. Может, текущая логика рекомендаций Stack Exchange может работать лучше, чем наша, но это не помешает нам помогать им в наших собственных учебных целях  Подмигивающая улыбка.

Мы будем выполнять следующие задачи.

  • Извлечение необходимой информации из набора данных Stack Exchange
  • Использование необходимой информации для создания Рекомендатора

Но давайте начнем с основ. Если вы совершенно новичок в Apache Hadoop и Hadoop On Azure,  я рекомендую вам прочитать эти вводные статьи  перед началом работы, где я немного объясню модели HDInsight и Map Reduce.

За кулисами

Здесь мы пойдем, давайте сначала углубимся в «науку о данных». Прохладно!! Распределенное машинное обучение в основном используется для

  • Рекомендации — Помните рекомендации Amazon? Обычно используется для прогнозирования предпочтений на основе истории.
  • Кластеризация — для задач, таких как поиск, объединение связанных документов из набора документов или поиск единомышленников из сообщества
  • Классификация — для определения того, к какому набору категории относится новый элемент. Обычно это включает в себя сначала обучение системы, а затем запрос системы для обнаружения элемента.

Жаргон «Большие данные» часто используется, когда вам нужно выполнить операции с очень большим набором данных. В этой статье мы будем иметь дело с извлечением некоторых данных из большого набора данных и созданием Рекомендатора, используя наши извлеченные данные.

Что такое Рекомендатор?

Вообще говоря, мы можем построить рекомендацию либо

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

образПервый метод известен как рекомендации, основанные на пользователях, а второй метод известен как рекомендации, основанные на элементах.

В первом случае вкус можно определить по тому, как общие вопросы, на которые вы ответили, с этим пользователем (вопросы, на которые вы оба ответили). Например, подумайте о User1, User2, User3 и User4 — отвечая на несколько вопросов Q1, Q2, Q3 и Q4. На этой диаграмме показаны вопросы, на которые отвечают пользователи

Исходя из вышеприведенной диаграммы, Пользователь1 и Пользователь2 ответили на Q1, Q2 и Q3 — и если Пользователь2 ответил на Q2 и Q3, но не на Q1. Теперь, в некоторой степени, мы можем с уверенностью предположить, что User3 будет заинтересован в ответе на Q1 — потому что два пользователя, которые ответили на Q2 и Q3 вместе с ним, уже ответили на Q1. Здесь есть какое-то соответствие вкуса, не так ли? Итак, если у вас есть массив {UserId, QuestionId} — похоже, данных нам достаточно, чтобы создать рекомендацию.

Логическая сторона

Теперь, как именно мы собираемся сделать рекомендации по вопросу? На самом деле это довольно просто.

Во-первых, нам нужно узнать, сколько раз пара вопросов встречалась среди доступных пользователей. Обратите внимание, что эта матрица не имеет отношения с пользователем. Например, если Q1 и Q2 появляются вместе 2 раза (как на приведенной выше диаграмме), значение совместного вхождения в {Q1, Q2} будет равно 2. Вот матрица совместного вхождения (надеюсь, я понял это правильно).

  • Q1 и Q2 встречаются 2 раза (Пользователь1 и Пользователь2 ответили на Q1, Q2)
  • Q1 и Q3 встречаются 2 раза (Пользователь1 и Пользователь2 ответили на Q1, Q2)
  • Q2 и Q3 происходят 3 раза (Пользователь1, Пользователь2 и Пользователь3 ответили на вопрос Q2, Q3)
  • Как мудрый ..

образ

Приведенная выше матрица просто показывает, сколько раз пара вопросов встречалась (отвечала), как обсуждалось выше. Пока нет сопоставления с пользователями. Теперь, как мы будем связывать это, чтобы найти предпочтения пользователя? Чтобы выяснить, насколько близкий вопрос «соответствует» пользователю, нам просто нужно

  • Узнайте, как часто этот вопрос встречается с другими вопросами, на которые ответил этот пользователь
  • Устраните вопросы, на которые уже ответил пользователь.

Для первого шага нам нужно умножить вышеуказанную матрицу на матрицу предпочтений пользователя.

Например, давайте возьмем User3. Для пользователя 3 отображение предпочтений с вопросами [Q1, Q2, Q3, Q4] равно [0,1,1,0], потому что он уже ответил на Q2 и Q3, но не на Q1 и Q4. Итак, давайте умножим это на приведенную выше матрицу совместного использования. Помните, что это матричное произведение умножения / точка. Результат показывает, как часто Вопрос возникает вместе с другими вопросами, на которые отвечает пользователь (вес).

образ

Мы можем опустить Q2 и Q3 из результатов, так как мы знаем, что Пользователь 3 уже ответил на них. Теперь из оставшихся Q1 и Q4 — Q1 имеет более высокое значение (4) и, следовательно, более высокий вкус, совпадающий с User3. Интуитивно понятно, что это указывает на то, что Q1 совпал с вопросами, на которые уже ответил Пользователь 3 (Q2 и Q3), более чем Q4 совпало с Q2 и Q3 — так что Пользователю 3 будет интересно ответить на Q1 больше, чем на Q4. В реальной реализации обратите внимание, что вкусовая матрица пользователя будет разреженной (в основном нулями), поскольку в прошлом пользователь будет отвечать только на очень ограниченный набор вопросов. Преимущество вышеупомянутой логики в том, что мы можем использовать распределенную модель уменьшения карты для вычислений с несколькими задачами уменьшения карты — построение матрицы совместного использования, поиск точечного произведения для каждого пользователя и т. Д.

Теперь давайте начнем думать о реализации.

Реализация

С точки зрения реализации,

  1. Нам нужно подготовить кластер Hadoop
  2. Нам нужно загрузить и извлечь данные для анализа (данные переполнения стека)
  3. Задание 1 — Извлечение данных — Из каждой строки извлеките {UserId, QuestionId} для всех вопросов, на которые ответил пользователь.
  4. Задание 2. Построение рекомендаций. Используйте приведенный выше вывод Map Reduce для построения модели рекомендаций, в которой возможные элементы перечислены для каждого пользователя.

Давайте катиться !!

Шаг 1 — Подготовка вашего кластера

Теперь запомните, данные Stack Exchange огромны. Таким образом, мы должны иметь распределенную среду для того же процесса. Давайте перейдем к  Windows Azure . Если у вас нет учетной записи, подпишитесь на бесплатную пробную версию. Теперь  перейдите на страницу предварительного просмотра и запросите предварительный просмотр HDInsight (Hadoop на Azure).

Если у вас есть HD Insight, вы можете легко создать кластер Hadoop. Я создаю кластер с именем stackanalyzer.

образ

Когда кластер будет готов, вы увидите кнопки «Подключиться» и «Управление» на панели инструментов (здесь не показано). Подключитесь к головному узлу вашего кластера, нажав кнопку «Подключиться», которая должна открыть подключение к удаленному рабочему столу с головным узлом. Вы также можете нажать кнопку «Управление», чтобы открыть панель управления. (Если вы хотите, вы можете прочитать  больше о HD Insight здесь )

Шаг 2 — Получение ваших данных для анализа

После подключения к головному узлу кластера с использованием RDP вы можете загрузить данные Stack Exchange. Вы можете  загрузить данные сайтов Stack Exchange из Clear Bits в разделе Creative Commons. Я установил клиент Mu-Torrent в головном узле, а затем загрузил и извлек данные для  http://cooking.stackexchange.com/  — Извлеченные файлы выглядят следующим образом — набор файлов XML.

образ

Что нас интересует, так это XML-файл сообщений. Каждая строка представляет либо вопрос, либо ответ. Если это вопрос, PostTypeId = 1, а если это ответ, PostTypeId = 2.ParentId представляет идентификатор вопроса для ответа, а OwnerUserId представляет парня, который написал ответ на этот вопрос. 

<row Id = «16» PostTypeId = «2» ParentId = «2» CreationDate = «2010-07-09T19: 13: 37.540» Score = «3» 
  Body = «& lt; p & gt; … Shortutedforbrevity … & lt ; / р & GT; & # хА;» 
  OwnerUserId = «34» LastActivityDate = «2010-07-09T19: 13: 37.540» />

Таким образом, для нас нам нужно извлечь {OwnerUserId, ParentId} для всех постов, где PostTypeId = 2 (ответы), который является представлением {User, Question, Votes}. Задание Рекомендателя Mahout, которое мы будем использовать позже, получит эти данные и создаст результат Рекомендации.

Теперь само извлечение этих данных является огромной задачей, если учесть, что файл Posts огромен. Для сайта Cooking он не такой большой, но если вы анализируете весь переполнение стека, файл Posts может быть в ГБ. Для извлечения этих данных, давайте воспользуемся Hadoop и напишем специальное задание Map Reduce.

Шаг 3 — Извлечение необходимых данных из дампа (пользователь, вопрос)

Для извлечения данных мы будем использовать Hadoop для распространения. Давайте напишем простой Mapper. Как упоминалось ранее, нам нужно выяснить {OwnerUserId, ParentId} для всех сообщений с PostTypeId = 2. Это потому, что вход для задания рекомендации, которое мы можем запустить позже, — {user, item}. Для этого сначала загрузите Posts.XML в HDFS. Вы можете использовать команду  hadoop fs  для копирования локального файла по указанному пути ввода.

образ

Now, time to write a custom mapper to extract the data for us. We’ll be using Hadoop On Azure .NET SDK to write our Map Reduce job.  Not that we are specifying the input folder and output folder in the configuration section. Fire up Visual Studio, and create a C# Console application. If you remember from my previous articles, hadoop fs <yourcommand> is used to access HDFS file system, and it’ll help if you know some basic *nix commands like lscat etc.

Note: See my earlier posts regarding the first bits of HDInsight to understand more about Map Reduce Model and Hadoop on Azure

You need to install the Hadoop Map Reduce package from Hadoop SDK for .NET via Nuget package manager.

install-package Microsoft.Hadoop.MapReduce 

Now, here is some code where we

  • Create A Mapper
  • Create a Job
  • Submit the Job to the cluster

Here we go.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Microsoft.Hadoop.MapReduce;

namespace StackExtractor
{

    //Our Mapper that takes a line of XML input and spits out the {OwnerUserId,ParentId,Score} 
    //i.e, {User,Question,Weightage}
    public class UserQuestionsMapper : MapperBase
    {
        public override void Map(string inputLine, MapperContext context)
        {
            try
            {
                var obj = XElement.Parse(inputLine);
                var postType = obj.Attribute("PostTypeId");
                if (postType != null && postType.Value == "2")
                {
                    var owner = obj.Attribute("OwnerUserId");
                    var parent = obj.Attribute("ParentId");
		   
                    // Write output data. Ignore records will null values if any
                    if (owner != null && parent != null )
                    {
                        context.EmitLine(string.Format("{0},{1}", owner.Value, parent.Value));
                    }
                }
            }
            catch
            {
                //Ignore this line if we can't parse
            }
        }
    }
    //Our Extraction Job using our Mapper
    public class UserQuestionsExtractionJob : HadoopJob<UserQuestionsMapper>
    {
        public override HadoopJobConfiguration Configure(ExecutorContext context)
        {
            var config = new HadoopJobConfiguration();
            config.DeleteOutputFolder = true;
            config.InputPath = "/input/Cooking";
            config.OutputFolder = "/output/Cooking";
            return config;
        }

       
    }

    //Driver that submits this to the cluster in the cloud
    //And will wait for the result. This will push your executables to the Azure storage
    //and will execute the command line in the head node (HDFS for Hadoop on Azure uses Azure storage)
    public class Driver
    {
        public static void Main()
        {
            try
            {
                var azureCluster = new Uri("https://{yoururl}.azurehdinsight.net:563");
                const string clusterUserName = "admin";
                const string clusterPassword = "{yourpassword}";

                // This is the name of the account under which Hadoop will execute jobs.
                // Normally this is just "Hadoop".
                const string hadoopUserName = "Hadoop";

                // Azure Storage Information.
                const string azureStorageAccount = "{yourstorage}.blob.core.windows.net";
                const string azureStorageKey =
                    "{yourstoragekey}";
                const string azureStorageContainer = "{yourcontainer}";
                const bool createContinerIfNotExist = true;
                Console.WriteLine("Connecting : {0} ", DateTime.Now);

                var hadoop = Hadoop.Connect(azureCluster,
                                            clusterUserName,
                                            hadoopUserName,
                                            clusterPassword,
                                            azureStorageAccount,
                                            azureStorageKey,
                                            azureStorageContainer,
                                            createContinerIfNotExist);

                Console.WriteLine("Starting: {0} ", DateTime.Now);
                var result = hadoop.MapReduceJob.ExecuteJob<UserQuestionsExtractionJob>();
                var info = result.Info;

                Console.WriteLine("Done: {0} ", DateTime.Now);
                Console.WriteLine("\nInfo From Server\n----------------------");
                Console.WriteLine("StandardError: " + info.StandardError);
                Console.WriteLine("\n----------------------");
                Console.WriteLine("StandardOut: " + info.StandardOut);
                Console.WriteLine("\n----------------------");
                Console.WriteLine("ExitCode: " + info.ExitCode);
            }
            catch(Exception ex)
            {
                Console.WriteLine("Error: {0} ", ex.StackTrace.ToString(CultureInfo.InvariantCulture)); 
            }
            Console.WriteLine("Press Any Key To Exit..");
            Console.ReadLine();
        }
    }
}

Now, Compile and run the above program. The ExecuteJob will upload the required binaries to your cluster, and will initiate a Hadoop Streaming Job that’ll run our Mappers on the cluster, with input from the Posts file we stored earlier in the Input folder. Our console application will submit the Job to the cloud, and will wait for the result. The Hadoop SDK will upload the map reduce binaries to the blob, and will build the required command line to execute the job (See my previous posts to understand how to do this manually).  You can inspect the job by clicking Hadoop Map Reduce status tracker from the desktop short cut in the head node.

If everything goes well, you’ll see the results like this.

образ

As you see above, you can find the output in /output/Cooking folder. If you RDP to your cluster’s head node, and check the output folder now, you should see the files created by our Map Reduce Job.

образ

And as expected, the files contain the extracted data, which represents the UserId,QuestionId – For all questions answered by a user. If you want, you can load the data from HDFS to Hive, and then view the same with Microsoft Excel using the ODBC for Hive. See my previous articles.

Step 4 – Build the recommender And generate recommendations

As a next step, we need to build the co-occurrence matrix and run a recommender job, to convert our {UserId,QuestionId} data to recommendations. Fortunately, we don’t need to write a Map Reduce job for this. We could leverage Mahout library along with Hadoop. Read about Mahout Here

RDP to the head node of our cluster, as we need to install Mahout. Download the latest version of Mahout (0.7) as of this writing, and copy the same to the c:\app\dist folder in the head node of your cluster.

образ

Mahout’s Recommender Job has support for multiple algorithms to build recommendations – In this case, we’ll be using SIMILARITY_COOCCURRENCE. The Algorithms Page of Mahout website has lot more information about Recommendation, Clustering and Classification algorithms. We’ll be using the files we’ve in the /output/Cooking folder to build our recommendation.

Time to run the Recommender job. Create a users.txt file and place the IDs of the users for whom you need recommendations in that file, and copy the same to HDFS.

образ

Now, the following command should start the Recommendation Job. Remember, we’ll use the output files from our above Map Reduce job as input to the Recommender. Let us kick start the Recommendation job. This will generate output in the /recommend/ folder, for all users specified in the users.txt file. You can use the –numRecommendations switch to specify the number of recommendations you need against each user. If there is a preference relation with a user and and item, (like the number of times a user played a song), you could keep the input dataset for a recommender as {user,item,preferencevalue} – In this case, we are omitting the preference weightage.

Note: If the below command fails after re run complaining output directory already exists, just try removing the tmp folder and the output folder using hadoop fs –rmr temp and hadoop fs –rmr /recommend/

hadoop jar c:\Apps\dist\mahout-0.7\mahout-core-0.7-job.jar 
	org.apache.mahout.cf.taste.hadoop.item.RecommenderJob -s SIMILARITY_COOCCURRENCE 
	--input=/output/Cooking 
	--output=/recommend/ 
	--usersFile=/data/users.txt 

After the job is finished, examine the  /recommend/ folder, and try printing the content in the generated file. You may see the top recommendations, against the user Ids you had in the users.txt.

образ

So, the recommendation engine think User  1393 may answer the questions 641916897 etc if we suggest the same to him. You could experiment with other Similarity classes like SIMILARITY_LOGLIKELIHOOD, SIMILARITY_PEARSON_CORRELATION etc to find the best results. Iterate and optimize till you are happy.

For an though experiment here is another exercise — Examine the Stack Exchange data set, and find out how you may build a Recommender to show a ‘You may also like’ questions based on the questions a user favorite?

Conclusion

In this example, we were doing a lot of manual work to upload the required input files to HDFS, and triggering the Recommender Job manually. In fact, you could automate this entire work flow leveraging Hadoop For Azure SDK. But that is for another post, stay tuned. Real life analysis has much more to do, including writing map/reducers for extracting and dumping data to HDFS, automating creation of hive tables, perform operations using HiveQL or PIG, etc. However, we just examined the steps involved in doing something meaningful with Azure, Hadoop and Mahout.

You may also access this data in your Mobile App or ASP.NET Web application, either by using Sqoop to export this to SQL Server, or by loading it to a Hive table as I explained earlier. Happy Coding and Machine Learning!! Also, if you are interested in scenarios where you could tie your existing applications with HD Insight to build end to end workflows, get in touch with me.

I suggest you to read further.

— See more at: http://www.amazedsaint.com/2013/07/building-simple-recommender-engine.html#sthash.wQJlf2v9.dpuf