{"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":{"id":"-cn66Wl-QHEk"}},{"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":{"id":"GfMglCX3QHEu"}},{"cell_type":"markdown","source":"Мы будем работать с датасетом объявлений Avito. Нам предстоит по заголовку и тексту объявления предсказывать категорию объявления.","metadata":{"id":"tgQacCDpQHEw"}},{"cell_type":"code","source":"import pandas as pd","metadata":{"id":"siqy7sYIQHEw","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train = pd.read_csv('avito-text-classification/train.csv')\ntest = pd.read_csv('avito-text-classification/test.csv')","metadata":{"id":"X2p4NnrvKdQQ"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train = pd.read_csv('../input/avito-category-prediction/train.csv')\ntest = pd.read_csv('../input/avito-category-prediction/test.csv')","metadata":{"id":"HTYuV2LuQHEx","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train.head()","metadata":{"id":"ZBsk7eATQHEy","outputId":"639eda44-8eae-4ca1-8fce-1ad5fd8733b2","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"id":"hGJbv171QHEz","outputId":"310d6aa8-a3a4-4f0b-ccec-ff8b4a72e44a"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Задание 1 (0.5 балла)\n\nВыведете на экран информацию о пропусках в данных. Если пропуски присутствуют заполните их пустой строкой.","metadata":{"id":"Vhs81o8kQHEz"}},{"cell_type":"code","source":"train.isna().sum()","metadata":{"id":"MYl4PVYuQHE0","outputId":"145a98d6-c0d5-4f3e-cdb7-196e29e508f4"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test.isna().sum()","metadata":{"id":"iBb-m-0XQHE0","outputId":"cc9200ed-259d-47e8-c71c-58569100020c"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train.description.fillna('', inplace=True)\ntest.description.fillna('', inplace=True)","metadata":{"id":"DGtNm8UcMyIg","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Задание 2 (0.5 балла)\nСконкатенируйте заголовок и описание в единую строку и поместите результат в отдельный столбец. Таким образом мы будет работать с одним текстом, а не с двумя.","metadata":{"id":"ohYxqF2eQHE0"}},{"cell_type":"code","source":"train.title += ' ' + train.description\ntest.title += ' ' + test.description","metadata":{"id":"V-GF2teGQHE1","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train.drop('description', axis=1, inplace=True)\ntest.drop('description', axis=1, inplace=True)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Задание 3 (0.5 балла)\nДавайте немного посмотрим на наши данные. Визуализируйте (где явно просят) или выведете информацию о следующем:\n\n1. Сколько всего уникальных классов необходимо предсказать?\n2. Постройте столбчатую диаграмму распределения по классам. Нормализуйте график, чтобы видеть доли, а не абсолютные значения.\n3. На сколько большой дисбаланс? Какой класс имеет самую большую долю в выборке (укажите класс и долю), какой класс наименьшую (укажите класс и долю).","metadata":{"id":"bDp1oX-fQHE1"}},{"cell_type":"code","source":"len(train.Category.value_counts())","metadata":{"id":"MI69KhgnQHE2","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import seaborn as sns\nimport matplotlib.pyplot as plt\n%matplotlib inline\nfig = plt.figure(figsize=(20,6))\nsns.barplot(x=\"Category\", y=\"Category\", data=train, estimator=lambda x: len(x) / len(train) * 100).set(ylabel=\"Percent\")\nfig.tight_layout()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Задание 4 (0.5 балла)\n\nДалее мы будем пока работать только с train частью. Для test части нам неизвестны истинные значения, и они понадобятся только тогда, когда мы будем отправлять решение на Kaggle.\n\n1. Предобработайте данные (train часть) с помощью CountVectorizer.\n2. Какого размера получилась матрица?\n","metadata":{"id":"Op0CLeyPQHE2"}},{"cell_type":"code","source":"!pip install nltk","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.feature_extraction.text import CountVectorizer\ncnt_vec = CountVectorizer()\nX = cnt_vec.fit_transform(train.title)\nX.shape","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":{"id":"sbBM3Fn0QHE3"}},{"cell_type":"code","source":"len([x for x in cnt_vec.vocabulary_.keys() if x[0].isdigit()])","metadata":{"id":"ASCTChPuQHE3","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from string import punctuation\nlen([x for x in cnt_vec.vocabulary_.keys() if x[0] in punctuation])","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":{"id":"JIm9iau5QHE4"}},{"cell_type":"code","source":"from nltk.tokenize import word_tokenize\nfrom nltk.corpus import stopwords\nfrom nltk.stem.snowball import SnowballStemmer\ndef my_tokenizer(txt):\n    stop_words = stopwords.words('russian')\n    stem = SnowballStemmer('russian')\n    lst = [stem.stem(str.lower(x)) for x in word_tokenize(txt) if x.isalpha() and x not in stop_words]\n    return lst","metadata":{"id":"zq9cKScoQHE4","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%time\nmy_tokenizer(train.title[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":{"id":"OQWhPbwPQHE4"}},{"cell_type":"code","source":"import numpy as np\nrandom_indices = np.random.choice(train.shape[0], size=500000, replace=False)\ncnt_vec = CountVectorizer(tokenizer=my_tokenizer)\nX = cnt_vec.fit_transform(train.title[random_indices])\ny = train.Category[random_indices].to_numpy()","metadata":{"id":"AIQSIPI-QHE5","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=123)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.linear_model import SGDClassifier\nfrom sklearn.metrics import accuracy_score\nclf = SGDClassifier(loss='modified_huber', penalty='l2',alpha=0.00005, random_state=42, max_iter=100, tol=None)\nclf.fit(X_train, y_train)\naccuracy_score(y_test, clf.predict(X_test))","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":{"id":"a2yp7vhZQHE5"}},{"cell_type":"code","source":"from sklearn.feature_extraction.text import TfidfVectorizer\ntfidf_vec = TfidfVectorizer(tokenizer=my_tokenizer, max_df=0.9)\nprint(\"For CountVectorizer:\", X.shape)\nX = tfidf_vec.fit_transform(train.title[random_indices])\nprint(\"For TF-IDF:\", X.shape)\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=123)\n","metadata":{"id":"90njuhF_QHE5","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"clf = SGDClassifier(loss='modified_huber', penalty='l2', alpha=1e-5, random_state=42, max_iter=150, tol=None)\nclf.fit(X_train, y_train)\naccuracy_score(y_test, clf.predict(X_test))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","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":{"id":"vZdaONiTQHE6"}},{"cell_type":"code","source":"from sklearn.feature_extraction.text import HashingVectorizer\nhash_vec = HashingVectorizer(n_features=30000, tokenizer=my_tokenizer)\nX = hash_vec.fit_transform(train.title[random_indices])\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=123)","metadata":{"id":"8nm5AKEDQHE6","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"clf = SGDClassifier(loss='modified_huber', penalty='l2', random_state=42, max_iter=100, tol=None)\nclf.fit(X_train, y_train)\naccuracy_score(y_test, clf.predict(X_test))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Задание 10 (1.5 балла)\n\nПришло время выйти в мир. Отправляйтесь на Kaggle Inclass и сделайте первую посылку. Там вы найдете инструкцию как сформировать файл с предсказаниями и отправить его.","metadata":{"id":"VXdcE-kTQHE-"}},{"cell_type":"code","source":"","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"clf.predict()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X_kaggle = tfidf_vec.transform(test.title)","metadata":{"id":"OLfzXgk-QHE-","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X_kaggle.shape","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"y_kaggle = clf.predict(X_kaggle)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df = pd.DataFrame(y_kaggle, columns=['Category'])\ndf['Id'] = test.itemid\ndf = df.set_index('Id')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df.to_csv('./result.csv')","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}