!head -1 hw8_data/stackoverflow.10kk.tsv

!head -1 hw8_data/stackoverflow_10mln.tsv

"""
Explanation: <center>
<img src="../../img/ods_stickers.jpg">
Открытый курс по машинному обучению. Сессия №3
<center>Автор материала: программист-исследователь Mail.Ru Group Юрий Кашницкий
<center> Домашнее задание № 8
<center> 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 – скачайте и распакуйте архив. 
Формат данных простой:<br>
<center>текст вопроса (слова через пробел) TAB теги вопроса (через пробел)
Здесь TAB – это символ табуляции.
Пример первой записи в выборке:
End of explanation
"""


%%time
!wc -l stackoverflow_10mln.tsv

%%time
!wc -l hw8_data/stackoverflow.10kk.tsv

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


import os
from tqdm import tqdm
from time import time
import numpy as np
from sklearn.metrics import accuracy_score

"""
Explanation: Обратите внимание на то, что такие данные я уже не хочу загружать в оперативную память и, пока можно, буду пользоваться эффективными утилитами 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).
<br>
<br>
Реализуйте в виде отдельного файла 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 это спецсимволы
End of explanation
"""


!python preprocess.py hw8_data/stackoverflow.10kk.tsv hw8_data/stackoverflow.vw

!wc -l hw8_data/stack.vw

!python preprocess.py stackoverflow_10mln.tsv stackoverflow.vw

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


#!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

"""
Explanation: Поделите выборку на обучающую, проверочную и тестовую части в равной пропорции - по  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.
End of explanation
"""


%%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))

%%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))

%%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))

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

best_valid_scoret_valid_score

"""
Explanation: 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.
End of explanation
"""


!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

%%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))

100*round(test_acc_score,4)-100*round(best_valid_score,4)

"""
Explanation: <font color='red'> Вопрос 1.</font> Какое сочетание параметров дает наибольшую долю правильных ответов на проверочной выборке stackoverflow_valid.vw?
- Биграммы и 3 прохода по выборке
- Триграммы и 5 проходов по выборке
- Биграммы и 1 проход по выборке <--
- Униграммы и 1 проход по выборке 
Проверьте лучшую (по доле правильных ответов на валидации) модель на тестовой выборке.
End of explanation
"""


!cat hw8_data/stack_train.vw hw8_data/stack_valid.vw > hw8_data/stack_merged.vw

%%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

%%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

%%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))

100*round(merged_acc_score,4)-100*round(test_acc_score,4)

"""
Explanation: <font color='red'> Вопрос 2.</font> Как соотносятся доли правильных ответов лучшей (по доле правильных ответов на валидации) модели на проверочной и на тестовой выборках? (здесь % – это процентный пункт, т.е., скажем, снижение с 50% до 40% – это на 10%, а не 20%).
- На тестовой ниже примерно на 2%
- На тестовой ниже примерно на 3%
- Результаты почти одинаковы – отличаются меньше чем на 0.5% <--
Обучите VW с параметрами, подобранными на проверочной выборке, теперь на объединении обучающей и проверочной выборок. Посчитайте долю правильных ответов на тестовой выборке.
End of explanation
"""


