{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Домашнее задание 6: классификация текстов","metadata":{}},{"cell_type":"markdown","source":"В этом домашнем задании вам предстоит построить классификатор текстов и поучаствовать в соревновании на Kaggle!\nПервым делом вам предстоит построить хороший бейзлайн, а дальше пытаться улучшать, соревнуясь с другими участниками.\n\n\nСсылка на соревнование: https://www.kaggle.com/c/avito-category-prediction/overview\nДанные для домашнего задания можно скачать на странице соревнования.\nЧтобы ваше участие было засчитано, убедитесь, что имя в Leaderboard имеет вид: «Имя Фамилия номер_группы».\n\nОценивание:\n\nДомашнее задание оценивается как обычно. Баллы указаны напротив заданий.\n\nЗа соревнование даются бонусные баллы следующим образом.\nЕсли вы пересекли baseline_2 на приватном лидерборде, ваша оценка равна \n\n10 - 10 * (i - 1) / M\n\nгде M — количество студентов, принявших участие в соревновании;\n\ni — место (начиная с 1) студента в приватном лидерборде среди всех таких студентов.\n\nПравила:\n\n* Домашнее задание оценивается в 10 баллов.\n\n* Плагиат не допускается. При обнаружении случаев списывания, 0 за работу выставляется всем участникам нарушения, даже если можно установить, кто у кого списал.\n\n* Старайтесь сделать код как можно более оптимальным. В частности, будет штрафоваться использование циклов в тех случаях, когда операцию можно совершить при помощи инструментов библиотек, о которых рассказывалось в курсе.  \n\n* В течение 3 суток после окончания соревнования в соответствующее задание на anytask необходимо прислать код, воспроизводящий ответы для посылки, фигурирующей в приватном лидерборде. В случае отсутствия кода, воспроизводящего результат, в установленный срок студенту выставляется 0 в качестве оценки за соревнование. Если не оговорено иное, использовать любые внешние данные в соревнованиях '''запрещено'''. Под внешними данными понимаются размеченные данные, где разметка имеет прямое отношение к решаемой задаче. Грубо говоря, сборник текстов с википедии не считается внешними данными.\n","metadata":{}},{"cell_type":"markdown","source":"Мы будем работать с датасетом объявлений Avito. Нам предстоит по заголовку и тексту объявления предсказывать категорию объявления.","metadata":{}},{"cell_type":"code","source":"import pandas as pd","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os\n\ndata_dir = '/kaggle/input/avito-category-prediction'\nos.listdir(data_dir)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train = pd.read_csv(os.path.join(data_dir, 'train.csv'))\ntest = pd.read_csv(os.path.join(data_dir, 'test.csv'))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train.shape","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Задание 1 (0.5 балла)\n\nВыведете на экран информацию о пропусках в данных. Если пропуски присутствуют заполните их пустой строкой.","metadata":{}},{"cell_type":"code","source":"train.isna().sum().to_frame('n/a').T","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train = train.fillna('')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Задание 2 (0.5 балла)\nСконкатенируйте заголовок и описание в единую строку и поместите результат в отдельный столбец. Таким образом мы будет работать с одним текстом, а не с двумя.","metadata":{}},{"cell_type":"code","source":"train['title_description'] = train['title'] + train['description']\ntrain.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Задание 3 (0.5 балла)\nДавайте немного посмотрим на наши данные. Визуализируйте (где явно просят) или выведете информацию о следующем:\n\n1. Сколько всего уникальных классов необходимо предсказать?\n2. Постройте столбчатую диаграмму распределения по классам. Нормализуйте график, чтобы видеть доли, а не абсолютные значения.\n3. На сколько большой дисбаланс? Какой класс имеет самую большую долю в выборке (укажите класс и долю), какой класс наименьшую (укажите класс и долю).","metadata":{}},{"cell_type":"code","source":"y = train['Category']","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"y.nunique()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ratios = y.value_counts(normalize=True).to_frame('class ratio')\nax = ratios.sort_index().plot.bar(figsize=(10, 4))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ratios.iloc[[0, -1]]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Задание 4 (0.5 балла)\n\nДалее мы будем пока работать только с train частью. Для test части нам неизвестны истинные значения, и они понадобятся только тогда, когда мы будем отправлять решение на Kaggle.\n\n1. Предобработайте данные (train часть) с помощью CountVectorizer.\n2. Какого размера получилась матрица?\n","metadata":{}},{"cell_type":"code","source":"from sklearn.feature_extraction.text import CountVectorizer\n\ntxt = train['title_description']\n\ncvec = CountVectorizer()\ntxtv = cvec.fit_transform(txt)\ntxtv","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Задание 5 (0.5 балла)\n\nВ предыдущем пункте у вас должная была получится очень большая матрица. Это не дело.\nЕсли вы взгляните на текст, то увидете, что там есть множество специальных символов.\n\nДавайте также посмотрим на словарь, который получился в результате построения CountVectorizer, его можно найти в поле _vocabulary инстанса этого класса.\n\n1. Найдите в этом словаре все слова, которые начинаются на цифру. Сколько таких слов нашлось?\n\n2. Найдите все слова, которые начинаются на символы пунктуации. Сколько таких слов нашлось? ","metadata":{}},{"cell_type":"code","source":"vocab = pd.DataFrame(cvec.vocabulary_.items(), columns=['word', 'count'])\nvocab","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"first_chars = vocab['word'].str[0].str","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"first_chars.isdigit().sum()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"first_chars.contains(r'[!\"#$%&\\'()*+,-.\\/:;<=>?@\\[\\\\\\]^_`{|}~]').sum()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Задание 6 (2.5 балла)\n\nВ scikit-learn мы можем оценивать процесс подсчета матрицы через CountVectorizer. У CountVectorizer, как и у других наследников \\_VectorizerMixin, есть аргумент tokenizer и preprocessor. preprocessor применится в самом начале к каждой строке вашего датасета, tokenizer же должен принять строку и вернуть токены.\nДавайте напишем кастомный токенайзер, которые сделает все, что нам нужно: \n\n0. Приведет все буквы к нижнему регистру\n1. Разобьет текст на токены с помощью word_tokenizer из пакета nltk\n2. Удалит все токены содержащие числа и пунктуацию\n3. Удалит все токены, которые перечислены в nltk.corpus.stopwords('russian')\n4. Проведет стемминг с помощью SnowballStemmer\n\nПродемонстрируйте работу вашей функии на самом первом описании товара в датасете.\n\nВажно: так как функию будет необходимо запускать на большом корпусе, подумайте об эффективной реализации. Постарайтесь решить задачу в минимальное количество циклов и копирований.\n\nРеференсная реализация на компьютере автора работала 230 секунд на 100к строчек.","metadata":{}},{"cell_type":"code","source":"import re\nfrom nltk import word_tokenize\nfrom nltk.corpus import stopwords\nfrom nltk.stem.snowball import SnowballStemmer\n\nlang = 'russian'\n\nnonword = re.compile(r'[\\W\\d]')\nstopwords_set = set(stopwords.words(lang))\nstemmer = SnowballStemmer(lang)\n\n\ndef is_clean(word):\n    return not (nonword.search(word) or word in stopwords_set)\n\n\ndef tokenize(doc):\n    words = filter(is_clean, word_tokenize(doc.lower()))\n    return map(stemmer.stem, words)\n\n\nprint(list(tokenize(txt[0])))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Задание 7 (1 балл)\n\n1. Возьмите случайные 500к строк из датасета. Постройте по ним CountVectorizer с применением вашего токенизатора.\n2. Разбейте полученную матрицу на train, test в отношении 4 к 1. Не забудьте про target переменную.\n3. Обучите SGDClassifier на полученной выборке.\n4. Посчитайте метрику accuracy","metadata":{}},{"cell_type":"code","source":"txt_sample = txt.sample(500_000, random_state=42)\n\ncvec = CountVectorizer(tokenizer=tokenize)\nx = cvec.fit_transform(txt_sample)\nx","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Задание 8 (1 балл)\n\n1. Повторите 7 задание, но с tf-idf векторизатором. Как изменилось качество.\n2. Мы можем еще сильнее уменьшите размер нашей матрицы, если отбросим значения df близкие к единице. Скорее всего такие слова не несут много информации о категории, так как встречаются достаточно часто. Ограничьте максимальный df в параметрах TfIdfVectorizer, поставьте верхнюю границу равную 0.9. Как изменился размер матрицы, как изменилось качество?\n","metadata":{}},{"cell_type":"code","source":"from sklearn.feature_extraction.text import TfidfVectorizer\n\ntfidf = TfidfVectorizer(tokenizer=tokenize)\nx = tfidf.fit_transform(txt_sample)\nx","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Задание 9 (1 балл)\nЕще один популяпный трюк, который позволит уменьшить количество признаков называется hashing trick. Его суть в том, то мы случайно группируем признаки ииии  ..... складываем их! А потом удаляем исходные признаки. В итоге все наши признаки это просто суммы исходных. Звучит странно, но это отлично работает. Давайте проверим этот трюк в нашем сеттинге.\nТакже при таком подходе вам не нужно хранить словарь token->index, что тоже иногда полезно.\n\n1. Повторите задание 7 с HashingVectorizer, укажите количество фичей равное 30000.\n2. Какой из трех подходов показал самый высокий результат?","metadata":{}},{"cell_type":"code","source":"from sklearn.feature_extraction.text import HashingVectorizer\n\nhasher = HashingVectorizer(tokenizer=tokenize)\nx = hasher.fit_transform(txt_sample, n_features=30000)\nx","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Задание 10 (1.5 балла)\n\nПришло время выйти в мир. Отправляйтесь на Kaggle Inclass и сделайте первую посылку. Там вы найдете инструкцию как сформировать файл с предсказаниями и отправить его.","metadata":{}},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}