Статьи

Начало работы с машинным обучением

«Машинное обучение» — мистический термин. Большинство разработчиков вообще не нуждаются в этом в своей повседневной работе, и единственные подробности, о которых мы знаем, взяты из какого-то университетского курса 5 лет назад (который уже забыт). Я не специалист по машинному обучению, но мне довелось работать в компании, которая занимается этим, поэтому мне пришлось изучать основы. Я никогда не программировал реальные задачи машинного обучения, но получил хороший обзор.

Но что такое машинное обучение? Он инструктирует компьютер распознавать большие объемы данных (#bigdata hashtag — check). В каких случаях?

  • классификация новой записи в существующие классы — это спам в электронной почте, эта новостная статья о спорте или политике, является ли этот символ буквой «а», или «б», или «с», это объект перед собой за рулем автомобиля пешеход или дорожный знак.
  • прогнозирование значения новой записи (проблемы регрессии) — сколько будет стоить моя машина, сколько будет цена акций завтра.
  • группирование записей в классы, которые неизвестны заранее (кластеризация) — какие у вас сегменты рынка, какие сообщества в данной социальной сети (и многие другие приложения )

Как? Со многими различными  алгоритмами и структурами данных . К счастью, которые уже написаны учеными-программистами, разработчики могут просто использовать их (конечно, с достаточным пониманием).

Но если алгоритмы уже написаны, то должно быть легко использовать машинное обучение? По иронии судьбы, самая сложная часть машинного обучения — это та часть, где человек говорит машине, что важно в данных. Этот процесс называется  выбором функции, Что это за особенности, которые описывают данные таким образом, что компьютер может использовать их для идентификации значимых шаблонов. Я не эксперт по машинному обучению, но, как я понимаю, этот шаг — то, что большинство инженеров машинного обучения (или ученых-данных) делают ежедневно. Они не изобретают новые алгоритмы; они пытаются выяснить, какие комбинации функций для данных дают наилучшие результаты. И это процесс со многими «эвристиками», с которыми у меня нет опыта. (Это, конечно, упрощение, поскольку мои коллеги действительно занимались исследованиями и предлагали усовершенствования алгоритмов, но это научный аспект вещей)

Теперь я ограничусь только проблемами классификации, а остальное оставлю. И когда я говорю «лучшие результаты», как это измеряется? Есть метрики  «точность» и «отзыв» (их легче всего использовать для классификации на две группы, но есть способы применить их для классификации нескольких классов или нескольких меток). Если вам нужно классифицировать письмо как спам, а не как спам, ваша точность — это процент писем, помеченных как спам, от всех писем, помеченных как спам. И отзыв — это процент писем, помеченных как спам, от общего количества писем, помеченных как спам. Таким образом, если у вас есть 200 электронных писем, 100 из которых являются спамом, и ваша программа помечает 80 из них как спам правильно и 20 неправильно, вы получите 80% точности (80/80 + 20) и 80% отзыва (80/100 фактический спам). электронная почта). Хорошие результаты достигаются, когда вы набираете больше баллов по этим двум показателям. Т.е. ваш спам-фильтр хорош, если он правильно обнаруживает большинство спам-писем, а также не помечает не спам-письма как спам.

Процесс ввода данных в алгоритм прост. Обычно у вас есть два набора данных — обучающий набор и оценочный набор. Обычно вы начинаете с одного сета и делите его на два (тренировочный набор должен быть больше). Эти наборы содержат значения для всех функций, которые вы определили для рассматриваемых данных. Сначала вы «обучаете» свою статистическую модель классификатора с помощью обучающего набора (если вы хотите знать, как происходит обучение, читайте о различных алгоритмах), а затем запускаете оценочный набор, чтобы увидеть, сколько элементов было правильно классифицировано (оценочный набор имеет Правильный ответ в этом, так что вы сравните это с тем, что классификатор произвел в качестве выходной)

Позвольте мне проиллюстрировать это с моим первым реальным кодом машинного обучения (с большим отказом от ответственности, что задача, вероятно, не очень подходит для машинного обучения, поскольку существует очень маленький набор данных). Я являюсь членом (и в настоящее время председателем) проблемного комитета (и жюри)  Международной лингвистической олимпиады . Мы создаем  проблемы лингвистики , объединяем их в наборы задач и назначаем их на мероприятие каждый год. Но мы все еще не способны оценить, насколько сложна проблема для старшеклассников. Несмотря на то, что многие из нас когда-то были участниками таких олимпиад, мы теперь знаем «слишком много», чтобы оценить сложность. Поэтому я решил применить машинное обучение к этой проблеме.

Как уже упоминалось выше, я должен был начать с выбора правильных функций. После нескольких итераций я в конечном итоге использовал: количество примеров в задаче, среднюю длину примера, количество назначений, количество лингвистических компонентов, которые необходимо обнаружить как часть решения, и данные о проблеме. взбитый или нет. Сложность (легкая, средняя, ​​сложная) определяется фактическими результатами участников олимпиады (средняя оценка: 0–8 баллов = сложная, 8–12 — средняя,> 12 баллов). Я не уверен, связаны ли эти функции со сложностью проблемы, поэтому я экспериментировал с добавлением и удалением некоторых. Я помещаю данные  объектов в файл Weka arff , который выглядит следующим образом (attribute = features):

@RELATION problem-complexity
 
@ATTRIBUTE examples NUMERIC
@ATTRIBUTE avgExampleSize NUMERIC
@ATTRIBUTE components NUMERIC
@ATTRIBUTE assignments NUMERIC
@ATTRIBUTE scrambled {true,false}
@ATTRIBUTE complexity {easy,medium,hard}
 
@DATA
34,6,11,8,false,medium
12,21,7,17,false,medium
14,11,11,17,true,hard
13,16,9,14,false,hard
16,35,7,17,false,hard
20,9,7,10,false,hard
24,5,8,6,false,medium
9,14,13,4,false,easy
18,7,17,7,true,hard
18,7,12,10,false,easy
10,16,9,11,false,hard
11,3,17,13,true,easy
...

Оценочный набор выглядит именно так, но меньше (в моем случае всего 7 записей).

Weka  был рекомендован как хороший инструмент (по крайней мере, для запуска), и в нем много алгоритмов, которые можно просто использовать повторно.

Следуя  руководству по началу работы , я создал следующий простой код:

public static void main(String[] args) throws Exception {
    ArffLoader loader = new ArffLoader();
    loader.setFile(new File("problem_complexity_train_3.arff"));
    Instances trainingSet = loader.getDataSet();
    // this is the complexity, here we specify what are our classes, 
    // into which we want to classify the data
    int classIdx = 5;
        
    ArffLoader loader2 = new ArffLoader();
    loader2.setFile(new File("problem_complexity_test_3.arff"));
    Instances testSet = loader2.getDataSet();
        
    trainingSet.setClassIndex(classIdx);
    testSet.setClassIndex(classIdx);
        
     // using the LMT classification algorithm. Many more are available   
     Classifier classifier = new LMT();
     classifier.buildClassifier(trainingSet);
        
     Evaluation eval = new Evaluation(trainingSet);
     eval.evaluateModel(classifier, testSet);  
 
     System.out.println(eval.toSummaryString());
  
     // Get the confusion matrix
     double[][] confusionMatrix = eval.confusionMatrix();
     ....
}

A comment about the choice of the algorithm – having insufficient knowledge, I just tried a few and selected the one that produced the best result.

After performing the evaluation, you can get the so called “confusion matrix”, (eval.toConfusionMatrix) which you can use to see the quality of the result. When you are satisfied with the results, you can proceed to classify new entries, that you don’t know the complexity of. To do that, you have to provide a data set, and the only difference to the other two is that you put question mark instead of the class (easy, medium, hard). E.g.:

...
@DATA
34,6,11,8,false,?
12,21,7,17,false,?

Then you can run the classifier:

ArffLoader loader = new ArffLoader();
loader.setFile(new File("unclassified.arff"));
Instances dataSet = loader.getDataSet();
 
DecimalFormat df = new DecimalFormat("#.##");
for (Enumeration<Instance> en = dataSet.enumerateInstances(); en.hasMoreElements();) {
    double[] results = classifier.distributionForInstance(en.nextElement());
    for (double result : results) {
        System.out.print(df.format(result) + " ");
     }
     System.out.println();
};

This will print the probabilities for each of your entries to fall into each of the classes. As we are going to use this output only as a hint towards the complexity, and won’t use it as a final decision, it is fine to yield wrong results sometimes. But in many machine learning problems there isn’t a human evaluation of the result, so getting higher accuracy is the most important task.

How does this approach scale, however. Can I reuse the code above for a high volume production system? On the web you normally do not run machine learning tasks in real time (you run them as scheduled tasks instead), so probably the answer is “yes”.

I am still a novice in the field, but having done one actual task made me share my tiny experience and knowledge. Meanwhile I’m following the Stanford machine learning course on Coursera, which can give you way more details.

Can we, as developers, use machine learning in our work projects? If we have large amounts of data – yes. It’s not that hard to get started, and although probably we will be making stupid mistakes, it’s an interesting thing to explore and may bring value to the product we are building.