Открытый курс по машинному обучению. Сессия №3

Автор материала: программист-исследователь Mail.Ru Group Юрий Кашницкий

Домашнее задание № 8

Vowpal Wabbit в задаче классификации тегов вопросов на Stackoverflow

План

1. Введение
2. Описание данных
3. Предобработка данных
4. Обучение и проверка моделей
5. Заключение

1. Введение

В этом задании вы будете делать примерно то же, что я каждую неделю – в Mail.Ru Group: обучать модели на выборке в несколько гигабайт. Задание можно выполнить и на Windows с Python, но я рекомендую поработать под *NIX-системой (например, через Docker) и активно использовать язык bash. Немного снобизма (простите, но правда): если вы захотите работать в лучших компаниях мира в области ML, вам все равно понадобится опыт работы с bash под UNIX.

Веб-форма для ответов.

Для выполнения задания понадобится установленный Vowpal Wabbit (уже есть в докер-контейнере курса, см. инструкцию в Wiki репозитория нашего курса) и примерно 70 Гб дискового пространства. Я тестировал решение не на каком-то суперкомпе, а на Macbook Pro 2015 (8 ядер, 16 Гб памяти), и самая тяжеловесная модель обучалась около 12 минут, так что задание реально выполнить и с простым железом. Но если вы планируете когда-либо арендовать сервера Amazon, можно попробовать это сделать уже сейчас.

Материалы в помощь:

  • интерактивный тьюториал CodeAcademy по утилитам командной строки UNIX (примерно на час-полтора)
  • статья про то, как арендовать на Amazon машину (еще раз: это не обязательно для выполнения задания, но будет хорошим опытом, если вы это делаете впервые)

2. Описание данных

Имеются 10 Гб вопросов со StackOverflow – скачайте и распакуйте архив.

Формат данных простой:

текст вопроса (слова через пробел) TAB теги вопроса (через пробел)

Здесь TAB – это символ табуляции. Пример первой записи в выборке:


In [1]:
!head -1 hw8_data/stackoverflow.10kk.tsv


 is there a way to apply a background color through css at the tr level i can apply it at the td level like this my td background color e8e8e8 background e8e8e8 however the background color doesn t seem to get applied when i attempt to apply the background color at the tr level like this my tr background color e8e8e8 background e8e8e8 is there a css trick to making this work or does css not natively support this for some reason 	css css3 css-selectors

In [1]:
!head -1 hw8_data/stackoverflow_10mln.tsv


 is there a way to apply a background color through css at the tr level i can apply it at the td level like this my td background color e8e8e8 background e8e8e8 however the background color doesn t seem to get applied when i attempt to apply the background color at the tr level like this my tr background color e8e8e8 background e8e8e8 is there a css trick to making this work or does css not natively support this for some reason 	css css3 css-selectors

Здесь у нас текст вопроса, затем табуляция и теги вопроса: css, css3 и css-selectors. Всего в выборке таких вопросов 10 миллионов.


In [2]:
%%time
!wc -l stackoverflow_10mln.tsv


 10000000 stackoverflow_10mln.tsv
CPU times: user 291 ms, sys: 122 ms, total: 412 ms
Wall time: 14.6 s

In [2]:
%%time
!wc -l hw8_data/stackoverflow.10kk.tsv


10000000 hw8_data/stackoverflow.10kk.tsv
CPU times: user 2.64 s, sys: 785 ms, total: 3.43 s
Wall time: 1min 53s

Обратите внимание на то, что такие данные я уже не хочу загружать в оперативную память и, пока можно, буду пользоваться эффективными утилитами UNIX – head, tail, wc, cat, cut и прочими.

3. Предобработка данных

Давайте выберем в наших данных все вопросы с тегами javascript, java, python, ruby, php, c++, c#, go, scala и swift и подготовим обучающую выборку в формате Vowpal Wabbit. Будем решать задачу 10-классовой классификации вопросов по перечисленным тегам.

Вообще, как мы видим, у каждого вопроса может быть несколько тегов, но мы упростим себе задачу и будем у каждого вопроса выбирать один из перечисленных тегов либо игнорировать вопрос, если таковых тегов нет. Но вообще VW поддерживает multilabel classification (аргумент --multilabel_oaa).

Реализуйте в виде отдельного файла preprocess.py код для подготовки данных. Он должен отобрать строки, в которых есть перечисленные теги, и переписать их в отдельный файл в формат Vowpal Wabbit. Детали:

  • скрипт должен работать с аргументами командной строки: с путями к файлам на входе и на выходе
  • строки обрабатываются по одной (можно использовать tqdm для подсчета числа итераций)
  • если табуляций в строке нет или их больше одной, считаем строку поврежденной и пропускаем
  • в противном случае смотрим, сколько в строке тегов из списка javascript, java, python, ruby, php, c++, c#, go, scala и swift. Если ровно один, то записываем строку в выходной файл в формате VW: label | text, где label – число от 1 до 10 (1 - javascript, ... 10 – swift). Пропускаем те строки, где интересующих тегов больше или меньше одного
  • из текста вопроса надо выкинуть двоеточия и вертикальные палки, если они есть – в VW это спецсимволы

In [9]:
import os
from tqdm import tqdm
from time import time
import numpy as np
from sklearn.metrics import accuracy_score

Должно получиться вот такое число строк – 4389054. Как видите, 10 Гб у меня обработались примерно за полторы минуты.


In [4]:
!python preprocess.py hw8_data/stackoverflow.10kk.tsv hw8_data/stackoverflow.vw


10000000it [01:06, 151047.91it/s]                                               

In [2]:
!wc -l hw8_data/stack.vw


wc: hw8_data/stack.vw: No such file or directory

In [4]:
!python preprocess.py stackoverflow_10mln.tsv stackoverflow.vw


10000000it [01:23, 119447.53it/s]
4389054 lines selected, 15 lines corrupted.

Поделите выборку на обучающую, проверочную и тестовую части в равной пропорции - по 1463018 в каждый файл. Перемешивать не надо, первые 1463018 строк должны пойти в обучающую часть stackoverflow_train.vw, последние 1463018 – в тестовую stackoverflow_test.vw, оставшиеся – в проверочную stackoverflow_valid.vw.

Также сохраните векторы ответов для проверочной и тестовой выборки в отдельные файлы stackoverflow_valid_labels.txt и stackoverflow_test_labels.txt.

Тут вам помогут утилиты head, tail, split, cat и cut.


In [3]:
#!head -1463018 hw8_data/stackoverflow.vw > hw8_data/stackoverflow_train.vw
#!tail -1463018 hw8_data/stackoverflow.vw > hw8_data/stackoverflow_test.vw
#!tail -n+1463018 hw8_data/stackoverflow.vw | head -n+1463018 > hw8_data/stackoverflow_valid.vw
#!split -l 1463018 hw8_data/stackoverflow.vw hw8_data/stack
!mv hw8_data/stackaa hw8_data/stack_train.vw 
!mv hw8_data/stackab hw8_data/stack_valid.vw 
!mv hw8_data/stackac hw8_data/stack_test.vw 
!cut -d '|' -f 1 hw8_data/stack_valid.vw > hw8_data/stack_valid_labels.txt
!cut -d '|' -f 1 hw8_data/stack_test.vw > hw8_data/stack_test_labels.txt

4. Обучение и проверка моделей

Обучите Vowpal Wabbit на выборке stackoverflow_train.vw 9 раз, перебирая параметры passes (1,3,5), ngram (1,2,3). Остальные параметры укажите следующие: bit_precision=28 и seed=17. Также скажите VW, что это 10-классовая задача.

Проверяйте долю правильных ответов на выборке stackoverflow_valid.vw. Выберите лучшую модель и проверьте качество на выборке stackoverflow_test.vw.


In [3]:
%%time
for p in [1,3,5]:
    for n in [1,2,3]:
        !vw --oaa 10 \
            -d hw8_data/stack_train.vw \
            --loss_function squared \
            --passes {p} \
            --ngram {n} \
            -f hw8_data/stack_model_{p}_{n}.vw \
            --bit_precision 28 \
            --random_seed 17 \
            --quiet \
            --c
        print ('stack_model_{}_{}.vw is ready'.format(p,n))


stack_model_1_1.vw is ready
stack_model_1_2.vw is ready
stack_model_1_3.vw is ready
stack_model_3_1.vw is ready
stack_model_3_2.vw is ready
stack_model_3_3.vw is ready
stack_model_5_1.vw is ready
stack_model_5_2.vw is ready
stack_model_5_3.vw is ready
CPU times: user 1min, sys: 14.8 s, total: 1min 15s
Wall time: 44min 24s

In [4]:
%%time
for p in [1,3,5]:
    for n in [1,2,3]:  
        !vw -i hw8_data/stack_model_{p}_{n}.vw \
            -t -d hw8_data/stack_valid.vw \
            -p hw8_data/stack_valid_pred_{p}_{n}.txt \
            --quiet
        print ('stack_valid_pred_{}_{}.txt is ready'.format(p,n))


CPU times: user 19.4 s, sys: 4.52 s, total: 23.9 s
Wall time: 13min 57s

In [10]:
%%time
with open('hw8_data/stack_valid_labels.txt') as valid_labels_file :
    valid_labels = [float(label) for label in valid_labels_file.readlines()]

scores=[]    
best_valid_score=0

for p in [1,3,5]:
    for n in [1,2,3]:
        with open('hw8_data/stack_valid_pred_'+str(p)+'_'+str(n)+'.txt') as pred_file:
            valid_pred = [float(label) for label in pred_file.readlines()]
            #if (n,p) in [(2,3),(3,5),(2,1),(1,1)]:
            acc_score=accuracy_score(valid_labels, valid_pred)
            scores.append(((n,p),acc_score))
            if acc_score>best_valid_score:
                best_valid_score=acc_score
            print(n,p,round(acc_score,4))


1 1 0.9152
2 1 0.931
3 1 0.9284
1 3 0.9139
2 3 0.9277
3 3 0.9263
1 5 0.9133
2 5 0.9291
3 5 0.926
CPU times: user 7.47 s, sys: 898 ms, total: 8.37 s
Wall time: 8.29 s

In [16]:
scores.sort(key=lambda tup: tup[1],reverse=True)
print(scores)


[((2, 1), 0.9309516355916332), ((2, 5), 0.9290938320649507), ((3, 1), 0.9283884408804266), ((2, 3), 0.9276693793241095), ((3, 3), 0.9262886717730062), ((3, 5), 0.9260138972999649), ((1, 1), 0.9151719254308559), ((1, 3), 0.9139306556720423), ((1, 5), 0.9133072867182769)]

In [18]:
best_valid_scoret_valid_score


Out[18]:
0.9309516355916332

Вопрос 1. Какое сочетание параметров дает наибольшую долю правильных ответов на проверочной выборке stackoverflow_valid.vw?

  • Биграммы и 3 прохода по выборке
  • Триграммы и 5 проходов по выборке
  • Биграммы и 1 проход по выборке <--
  • Униграммы и 1 проход по выборке

Проверьте лучшую (по доле правильных ответов на валидации) модель на тестовой выборке.


In [17]:
!vw -i hw8_data/stack_model_1_2.vw \
    -t -d hw8_data/stack_test.vw \
    -p hw8_data/stack_test_pred_1_2.txt \
    --quiet

In [19]:
%%time
with open('hw8_data/stack_test_labels.txt') as test_labels_file :
    test_labels = [float(label) for label in test_labels_file.readlines()]

with open('hw8_data/stack_test_pred_1_2.txt') as pred_file:
    test_pred = [float(label) for label in pred_file.readlines()]
    test_acc_score=accuracy_score(test_labels, test_pred)
    print(round(test_acc_score,4))


0.9311
CPU times: user 1.14 s, sys: 192 ms, total: 1.33 s
Wall time: 1.31 s

In [29]:
100*round(test_acc_score,4)-100*round(best_valid_score,4)


Out[29]:
0.009999999999990905

Вопрос 2. Как соотносятся доли правильных ответов лучшей (по доле правильных ответов на валидации) модели на проверочной и на тестовой выборках? (здесь % – это процентный пункт, т.е., скажем, снижение с 50% до 40% – это на 10%, а не 20%).

  • На тестовой ниже примерно на 2%
  • На тестовой ниже примерно на 3%
  • Результаты почти одинаковы – отличаются меньше чем на 0.5% <--

Обучите VW с параметрами, подобранными на проверочной выборке, теперь на объединении обучающей и проверочной выборок. Посчитайте долю правильных ответов на тестовой выборке.


In [30]:
!cat hw8_data/stack_train.vw hw8_data/stack_valid.vw > hw8_data/stack_merged.vw

In [31]:
%%time
!vw --oaa 10 \
    -d hw8_data/stack_merged.vw \
    --loss_function squared \
    --passes 1 \
    --ngram 2 \
    -f hw8_data/stack_model_merged.vw \
    --bit_precision 28 \
    --random_seed 17 \
    --quiet \
    -c

In [32]:
%%time
!vw -i hw8_data/stack_model_merged.vw \
    -t -d hw8_data/stack_test.vw \
    -p hw8_data/stack_test_pred_merged.txt \
    --quiet

In [33]:
%%time
with open('hw8_data/stack_test_labels.txt') as test_labels_file :
    test_labels = [float(label) for label in test_labels_file.readlines()]

with open('hw8_data/stack_test_pred_merged.txt') as pred_file:
    test_pred = [float(label) for label in pred_file.readlines()]
    merged_acc_score=accuracy_score(test_labels, test_pred)
    print(round(merged_acc_score,4))


0.935
CPU times: user 1.25 s, sys: 184 ms, total: 1.43 s
Wall time: 1.42 s

In [34]:
100*round(merged_acc_score,4)-100*round(test_acc_score,4)


Out[34]:
0.39000000000000057

Вопрос 3. На сколько процентных пунктов повысилась доля правильных ответов модели после обучения на вдвое большей выборке (обучающая stackoverflow_train.vw + проверочная stackoverflow_valid.vw) по сравнению с моделью, обученной только на stackoverflow_train.vw?

  • 0.1%
  • 0.4% <--
  • 0.8%
  • 1.2%

5. Заключение

В этом задании мы только познакомились с Vowpal Wabbit. Что еще можно попробовать:

  • Классификация с несколькими ответами (multilabel classification, аргумент multilabel_oaa) – формат данных тут как раз под такую задачу
  • Настройка параметров VW с hyperopt, авторы библиотеки утверждают, что качество должно сильно зависеть от параметров изменения шага градиентного спуска (initial_t и power_t). Также можно потестировать разные функции потерь – обучать логистическую регресиию или линейный SVM
  • Познакомиться с факторизационными машинами и их реализацией в VW (аргумент lrq)