Надо вывести формулы, где это просится (да, ручка и бумажка), заполнить код в клетках и выбрать ответы в веб-форме.
В этой домашней работе мы с вами изучим и запрограммируем модель для прогнозирования тегов по тексту вопроса на базе многоклассовой логистической регрессии. В отличие от обычной постановки задачи классификации (multiclass), в данном случае один пример может принадлежать одновременно к нескольким классам (multilabel). Мы будем реализовывать онлайн-версию алгоритма multilabel-классификации.
Мы будем использовать небольшую выборку из протеггированных вопросов с сайта StackOverflow размером в 125 тысяч примеров (около 150 Мб, скачайте по этой ссылке).
PS: Можно показать, что такая реализация совсем не эффективная и проще было бы использовать векторизированные вычисления. Для данного датасета так и есть. Но на самом деле подобные реализации используются в жизни, но естественно, написаны они не на Python. Например, в онлайн-моделях прогнозирования CTR юзеру показывается баннер, затем в зависимости от наличия клика происходит обновление параметров модели. В реальной жизни параметров модели может быть несколько сотен миллионов, а у юзера из этих ста миллионов от силы сто или тысяча параметров отличны от нуля, векторизировать такие вычисления не очень эффективно. Обычно все это хранится в огромных кластерах в in-memory базах данных, а обработка пользователей происходит распределенно.
PS2:
In [4]:
#pip install watermark
%load_ext watermark
In [9]:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style("dark")
plt.rcParams['figure.figsize'] = 16, 12
from tqdm import tqdm_notebook
import pandas as pd
from collections import defaultdict
# поменяйте на свой путь
DS_FILE_NAME = 'stackoverflow_sample_125k.tsv'
TAGS_FILE_NAME = 'top10_tags.tsv'
In [10]:
top_tags = []
with open(TAGS_FILE_NAME, 'r') as f:
for line in f:
top_tags.append(line.strip())
top_tags = set(top_tags)
print(top_tags)
Вспомним, как получается логистическая регрессия для двух классов $\left\{0, 1\right\}$, вероятность принадлежности объекта к классу $1$ выписывается по теореме Байеса:
$$\large \begin{array}{rcl} p\left(c = 1 \mid \vec{x}\right) &=& \dfrac{p\left(\vec{x} \mid c = 1\right)p\left(c = 1\right)}{p\left(\vec{x} \mid c = 1\right)p\left(c = 1\right) + p\left(\vec{x} \mid c = 0\right)p\left(c = 0\right)} \\ &=& \dfrac{1}{1 + e^{-a}} \\ &=& \sigma\left(a\right) \end{array}$$где:
Данное выражение легко обобщить до множества из $K$ классов, изменится только знаменатель в формуле Байеса. Запишем вероятность принадлежности объекта к классу $k$: $$\large \begin{array}{rcl} p\left(c = k \mid \vec{x}\right) &=& \dfrac{p\left(\vec{x} \mid c = k\right)p\left(c = k\right)}{\sum_{i=1}^K p\left(\vec{x} \mid c = i\right)p\left(c = i\right)} \\ &=& \dfrac{e^{z_k}}{\sum_{i=1}^{K}e^{z_i}} \\ &=& \sigma_k\left(\vec{z}\right) \end{array}$$ где:
Для моделирования полного правдоподобия примера мы используем категориальное распределение, а лучше его логарифм (для удобства):
$$\large \begin{array}{rcl} \mathcal{L} = \log p\left({\vec{x}}\right) &=& \log \prod_{i=1}^K \sigma_i\left(\vec{z}\right)^{y_i} \\ &=& \sum_{i=1}^K y_i \log \sigma_i\left(\vec{z}\right) \end{array}$$Получается хорошо знакомая нам функция cross entropy (если домножить на $-1$). Правдоподобие нужно максимизировать, а, соответственно, перекрестную энтропию нужно минимизировать. Продифференцировав по параметрам модели, мы легко получим правила обновления весов для градиентного спуска, проделайте этот вывод, если вы его не делали (если вы вдруг сдались, то на этом видео есть разбор вывода, понимание этого вам понадобится для дальнейшего выполнения задания; если предпочитаете текст, то и он есть тут и тут):
$$\large \begin{array}{rcl} \frac{\partial \mathcal{L}}{\partial w_{km}} &=& x_m \left(y_k - \sigma_k\left(\vec{z}\right)\right) \end{array}$$В стандартной формулировке получается, что вектор $\left(\sigma_1, \sigma_2, \ldots, \sigma_K\right)$ образует дискретное вероятностное распределение, т.е. $\sum_{i=1}^K \sigma_i = 1$. Но в нашей постановке задачи каждый пример может иметь несколько тегов или одновременно принадлежать к нескольким классам. Для этого мы немного изменим модель:
Вопрос 1. Ваше первое задание – записать упрощенное выражение логарифма правдоподобия примера с признаками $\vec{x}$. Как правило, многие алгоритмы оптимизации имеют интерфейс для минимизации функции, мы последуем этой же традиции и домножим полученное выражение на $-1$, а во второй части выведем формулы для минимизации полученного выражения.
Варианты ответа:
2. $\large -\mathcal{L} = -\sum_{i=1}^K y_i \log \sigma\left(z_i\right) + \left(1 - y_i\right) \log \left(1 - \sigma\left(z_i\right)\right)$
Варианты ответа::
2. $\large -\frac{\partial \mathcal{L}}{\partial w_{km}} = -x_m \left(y_k - \sigma\left(z_k\right)\right)$
Вам предлагается каркас класса модели, разберите его внимательно, обращайте внимание на комментарии. Затем заполните пропуски, запустите полученную модель и ответьте на проверочный вопрос.
Как вы могли уже заметить, при обновлении веса $w_{km}$ используется значение признака $x_m$, который равен $0$, если слова с индексом $m$ нет в предложении, и больше нуля, если такое слово есть. В нашем случае, чтобы не пересчитывать bag-of-words самим или с помощью sklearn.feature_extraction.text.CountVectorizer, мы будем идти по словам предложения в порядке их следования. Если какое-то слово встречается несколько раз, то мы добавляем его в аккумулятор со своим весом. В итоге получится то же самое, как если сначала посчитать количество одинаковых слов и домножить на соответствующий вес. Соответственно, при вычислении линейной комбинации $z$ весов модели и признаков примера необходимо учитывать только ненулевые признаки объекта.
Подсказка:
In [11]:
class LogRegressor():
"""Конструктор
Параметры
----------
tags_top : list of string, default=tags_top
список тегов
"""
def __init__(self, tags=top_tags):
# словарь который содержит мапинг слов предложений и тегов в индексы (для экономии памяти)
# пример: self._vocab['exception'] = 17 означает что у слова exception индекс равен 17
self._vocab = {}
# параметры модели: веса
# для каждого класса/тега нам необходимо хранить собственный вектор весов
# по умолчанию у нас все веса будут равны нулю
# мы заранее не знаем сколько весов нам понадобится
# поэтому для каждого класса мы сосздаем словарь изменяемого размера со значением по умолчанию 0
# пример: self._w['java'][self._vocab['exception']] содержит вес для слова exception тега java
self._w = dict([(t, defaultdict(int)) for t in tags])
# параметры модели: смещения или вес w_0
self._b = dict([(t, 0) for t in tags])
self._tags = set(tags)
"""Один прогон по датасету
Параметры
----------
fname : string, default=DS_FILE_NAME
имя файла с данными
top_n_train : int
первые top_n_train строк будут использоваться для обучения, остальные для тестирования
total : int, default=10000000
информация о количестве строк в файле для вывода прогресс бара
learning_rate : float, default=0.1
скорость обучения для градиентного спуска
tolerance : float, default=1e-16
используем для ограничения значений аргумента логарифмов
"""
def iterate_file(self,
fname=DS_FILE_NAME,
top_n_train=100000,
total=125000,
learning_rate=0.1,
tolerance=1e-16):
self._loss = []
n = 0
# откроем файл
with open(fname, 'r') as f:
# прогуляемся по строкам файла
for line in tqdm_notebook(f, total=total, mininterval=1):
pair = line.strip().split('\t')
if len(pair) != 2:
continue
sentence, tags = pair
# слова вопроса, это как раз признаки x
sentence = sentence.split(' ')
# теги вопроса, это y
tags = set(tags.split(' '))
# значение функции потерь для текущего примера
sample_loss = 0
# прокидываем градиенты для каждого тега
for tag in self._tags:
# целевая переменная равна 1 если текущий тег есть у текущего примера
y = int(tag in tags)
# расчитываем значение линейной комбинации весов и признаков объекта
# ЗАПОЛНИТЕ ПРОПУСКИ В КОДЕ
# z = ...
z = self._b[tag]
for word in sentence:
# если в режиме тестирования появляется слово которого нет в словаре, то мы его игнорируем
if n >= top_n_train and word not in self._vocab:
continue
if word not in self._vocab:
self._vocab[word] = len(self._vocab)
# z += ...
z += self._w[tag][self._vocab[word]]
# вычисляем вероятность наличия тега
# ЗАПОЛНИТЕ ПРОПУСКИ В КОДЕ
# sigma = ...
sigma = 1/(1 + np.exp(-z)) if z >= 0 else 1 - 1/(1 + np.exp(z))
# обновляем значение функции потерь для текущего примера
# ЗАПОЛНИТЕ ПРОПУСКИ В КОДЕ
# sample_loss += ...
sample_loss += -y*np.log(np.max([tolerance, sigma])) if y == 1 else \
-(1 - y)*np.log(1 - np.min([1 - tolerance, sigma]))
# если мы все еще в тренировочной части, то обновим параметры
if n < top_n_train:
# вычисляем производную логарифмического правдоподобия по весу
# ЗАПОЛНИТЕ ПРОПУСКИ В КОДЕ
# dLdw = ...
dLdw = y - sigma
# делаем градиентный шаг
# мы минимизируем отрицательное логарифмическое правдоподобие (второй знак минус)
# поэтому мы идем в обратную сторону градиента для минимизации (первый знак минус)
for word in sentence:
self._w[tag][self._vocab[word]] -= -learning_rate*dLdw
self._b[tag] -= -learning_rate*dLdw
n += 1
self._loss.append(sample_loss)
In [12]:
# создадим эксемпляр модели и пройдемся по датасету
model = LogRegressor()
model.iterate_file()
Проверим, действительно ли значение отрицательного логарифмического правдоподобия уменьшалось. Так как мы используем стохастический градентный спуск, не стоит ожидать плавного падения функции ошибки. Мы воспользуемся скользящим средним с окном в 10 тысяч примеров, чтобы хоть как-то сгладить график.
In [13]:
plt.plot(pd.Series(model._loss[:-25000]).rolling(10000).mean());
In [14]:
print('Mean of the loss function on the last 10k train samples: %0.2f' % np.mean(model._loss[-35000:-25000]))
Вопрос 3. Вычислите среднее значение функции стоимости на последних 10 000 примеров тренировочного набора, к какому из значений ваш ответ ближе всего?
Варианты ответа:
3. 19.74
В базовой модели первые 100 000 строк используются для обучения, а оставшиеся – для тестирования. Как вы можете заметить, значение отрицательного логарифмического правдоподобия не очень информативно, хоть и позволяет сравнивать разные модели. В качестве четвертого задания вам необходимо модифицировать базовую модель таким образом, чтобы метод iterate_file
возвращал значение точности на тестовой части набора данных.
Точность определим следующим образом:
iterate_file
возвращает среднюю точность на тестовом наборе данных
In [15]:
class LogRegressor():
def __init__(self, tags=top_tags):
self._vocab = {}
self._w = dict([(t, defaultdict(int)) for t in tags])
self._b = dict([(t, 0) for t in tags])
self._tags = set(tags)
def iterate_file(self,
fname=DS_FILE_NAME,
top_n_train=100000,
total=125000,
learning_rate=0.1,
tolerance=1e-16,
accuracy_level=0.9):
self._loss = []
n = 0
accuracy = []
with open(fname, 'r') as f:
for line in tqdm_notebook(f, total=total, mininterval=1):
pair = line.strip().split('\t')
if len(pair) != 2:
continue
sentence, tags = pair
sentence = sentence.split(' ')
tags = set(tags.split(' '))
sample_loss = 0
predicted_tags = None
for tag in self._tags:
y = int(tag in tags)
z = self._b[tag]
for word in sentence:
if n >= top_n_train and word not in self._vocab:
continue
if word not in self._vocab:
self._vocab[word] = len(self._vocab)
z += self._w[tag][self._vocab[word]]
sigma = 1/(1 + np.exp(-z)) if z >= 0 else 1 - 1/(1 + np.exp(z))
sample_loss += -y*np.log(np.max([tolerance, sigma])) if y == 1 else \
-(1 - y)*np.log(1 - np.min([1 - tolerance, sigma]))
if n < top_n_train:
dLdw = y - sigma
for word in sentence:
self._w[tag][self._vocab[word]] -= -learning_rate*dLdw
self._b[tag] -= -learning_rate*dLdw
else:
if predicted_tags is None:
predicted_tags = []
if sigma > accuracy_level:
predicted_tags.append(tag)
n += 1
self._loss.append(sample_loss)
if predicted_tags is not None:
accuracy.append(len(tags.intersection(predicted_tags))/len(tags.union(predicted_tags)))
return(np.mean(accuracy))
In [16]:
model = LogRegressor()
acc = model.iterate_file()
# выведем полученное значение с точностью до двух знаков
print('%0.2f' % acc)
Вопрос 4. К какому значению ближе всего полученное значение точности? Варианты ответа:
В качестве пятого задания вам необходимо добавить в класс LogRegressor
поддержку $L_2$-регуляризации. В методе iterate_file
должен появиться параметр lmbda=0.01
со значением по умолчанию. С учетом регуляризации новая функция стоимости примет вид:
Градиент первого члена суммы мы уже вывели, а для второго он имеет вид:
$$\large \begin{array}{rcl} \frac{\partial}{\partial w_{ki}} \frac{\lambda}{2} R\left(W\right) &=& \lambda w_{ki} \end{array}$$Если мы на каждом примере будем делать честное обновление всех весов, то все очень замедлится, ведь нам придется на каждой итерации пробегать по всем словам словаря. В ущерб теоретической корректности мы используем грязный трюк: будем регуляризировать только те слова, которые присутствуют в текущем предложении. Не забывайте, что смещение (bias) не регуляризируется. sample_loss
тоже должен остаться без изменений.
Замечание:
In [18]:
# Обновите определение класса LogRegressor
# Ваш код здесь
class LogRegressor():
def __init__(self, tags=top_tags):
self._vocab = {}
self._w = dict([(t, defaultdict(int)) for t in tags])
self._b = dict([(t, 0) for t in tags])
self._tags = set(tags)
def iterate_file(self,
fname=DS_FILE_NAME,
top_n_train=100000,
total=125000,
learning_rate=0.1,
tolerance=1e-16,
accuracy_level=0.9,
lmbda=0.01):
self._loss = []
n = 0
accuracy = []
with open(fname, 'r') as f:
for line in tqdm_notebook(f, total=total, mininterval=1):
pair = line.strip().split('\t')
if len(pair) != 2:
continue
sentence, tags = pair
sentence = sentence.split(' ')
tags = set(tags.split(' '))
sample_loss = 0
predicted_tags = None
for tag in self._tags:
y = int(tag in tags)
z = self._b[tag]
for word in sentence:
if n >= top_n_train and word not in self._vocab:
continue
if word not in self._vocab:
self._vocab[word] = len(self._vocab)
z += self._w[tag][self._vocab[word]]
sigma = 1/(1 + np.exp(-z)) if z >= 0 else 1 - 1/(1 + np.exp(z))
sample_loss += -y*np.log(np.max([tolerance, sigma])) if y == 1 else \
-(1 - y)*np.log(1 - np.min([1 - tolerance, sigma]))
if n < top_n_train:
dLdw = y - sigma
for word in sentence:
self._w[tag][self._vocab[word]] -= -learning_rate*dLdw \
+ learning_rate*lmbda*self._w[tag][self._vocab[word]]
self._b[tag] -= -learning_rate*dLdw
else:
if predicted_tags is None:
predicted_tags = []
if sigma > accuracy_level:
predicted_tags.append(tag)
n += 1
self._loss.append(sample_loss)
if predicted_tags is not None:
accuracy.append(len(tags.intersection(predicted_tags))/len(tags.union(predicted_tags)))
return(np.mean(accuracy))
In [19]:
model = LogRegressor()
acc = model.iterate_file()
print('%0.2f' % acc)
plt.plot(pd.Series(model._loss[:-25000]).rolling(10000).mean());
Вопрос 5. К какому значению ближе всего полученное значение точности? Варианты ответа:
Помимо $L_2$ регуляризации, часто используется $L_1$ регуляризация.
$$\large \begin{array}{rcl} L &=& -\mathcal{L} + \frac{\lambda}{2} R\left(W\right) \\ &=& -\mathcal{L} + \lambda \sum_{k=1}^K\sum_{i=1}^M \left|w_{ki}\right| \end{array}$$Если линейно объединить $L_1$ и $L_2$ регуляризацию, то полученный тип регуляризации называется ElasticNet:
$$\large \begin{array}{rcl} L &=& -\mathcal{L} + \lambda R\left(W\right) \\ &=& -\mathcal{L} + \lambda \left(\gamma \sum_{k=1}^K\sum_{i=1}^M w_{ki}^2 + \left(1 - \gamma\right) \sum_{k=1}^K\sum_{i=1}^M \left|w_{ki}\right| \right) \end{array}$$В качестве шестого вопроса вам предлагается вывести формулу градиента ElasticNet регуляризации (не учитывая $-\mathcal{L}$).
Варианты ответа::
2. $\large \frac{\partial}{\partial w_{ki}} \lambda R\left(W\right) = \lambda \left(2 \gamma \left|w_{ki}\right| + \left(1 - \gamma\right) \text{sign}\left(w_{ki}\right)\right)$
В качестве седьмой задачи вам предлается изменить класс LogRegressor
таким образом, чтобы метод iterate_file
принимал два параметра со значениями по умолчанию lmbda=0.0002
и gamma=0.1
. Сделайте один проход по датасету с включенной ElasticNet
-регуляризацией и заданными значениями по умолчанию и ответьте на вопрос.
In [20]:
class LogRegressor():
def __init__(self, tags=top_tags):
self._vocab = {}
self._w = dict([(t, defaultdict(int)) for t in tags])
self._b = dict([(t, 0) for t in tags])
self._tags = set(tags)
def iterate_file(self,
fname=DS_FILE_NAME,
top_n_train=100000,
total=125000,
learning_rate=0.1,
tolerance=1e-16,
accuracy_level=0.9,
lmbda=0.0002,
gamma=0.1):
self._loss = []
n = 0
accuracy = []
with open(fname, 'r') as f:
for line in tqdm_notebook(f, total=total, mininterval=1):
pair = line.strip().split('\t')
if len(pair) != 2:
continue
sentence, tags = pair
sentence = sentence.split(' ')
tags = set(tags.split(' '))
sample_loss = 0
predicted_tags = None
for tag in self._tags:
y = int(tag in tags)
z = self._b[tag]
for word in sentence:
if n >= top_n_train and word not in self._vocab:
continue
if word not in self._vocab:
self._vocab[word] = len(self._vocab)
z += self._w[tag][self._vocab[word]]
sigma = 1/(1 + np.exp(-z)) if z >= 0 else 1 - 1/(1 + np.exp(z))
sample_loss += -y * np.log(np.max([tolerance, sigma])) if y == 1 else \
-(1 - y) * np.log(1 - np.min([1 - tolerance, sigma]))
if n < top_n_train:
dLdw = y - sigma
for word in sentence:
self._w[tag][self._vocab[word]] -= -learning_rate * dLdw \
+ 2 * learning_rate * lmbda * gamma * self._w[tag][self._vocab[word]] \
+ learning_rate * lmbda*(1 - gamma) * np.sign(self._w[tag][self._vocab[word]])
self._b[tag] -= -learning_rate * dLdw
else:
if predicted_tags is None:
predicted_tags = []
if sigma > accuracy_level:
predicted_tags.append(tag)
n += 1
self._loss.append(sample_loss)
if predicted_tags is not None:
accuracy.append(len(tags.intersection(predicted_tags))/len(tags.union(predicted_tags)))
return(np.mean(accuracy))
In [21]:
%%time
model = LogRegressor()
acc = model.iterate_file()
print('%0.2f' % acc)
plt.plot(pd.Series(model._loss[:-25000]).rolling(10000).mean());
Вопрос 7. К какому значению ближе всего полученное значение точности: Варианты ответа:
In [22]:
model._vocab_inv = dict([(v, k) for (k, v) in model._vocab.items()])
for tag in model._tags:
print(tag, ':', ', '.join([model._vocab_inv[k] for (k, v) in
sorted(model._w[tag].items(),
key=lambda t: t[1],
reverse=True)[:5]]))
Вопрос 8. Для многих тегов наличие самого тега в предложении является важным сигналом, у многих сам тег является самым сильным сигналом, что не удивительно. Для каких из тегов само название тега не входит в топ-5 самых важных?
Варианты ответа:
Сейчас количество слов в словаре – 519290, если бы это была выборка из 10 миллионов вопросов с сайта StackOverflow, то размер словаря был бы миллионов 10. Регуляризировать модель можно не только изящно математически, но и топорно, например, ограничить размер словаря. Вам предоставляется возможность внести следующие изменения в класс LogRegressor
:
iterate_file
еще один аргумент со значением по умолчанию update_vocab=True
update_vocab=True
разрешать добавлять слова в словарь в режиме обученияupdate_vocab=False
игнорировать слова не из словаряfilter_vocab(n=10000)
, который оставит в словаре только топ-n самых популярных слов, используя данные из train
In [23]:
class LogRegressor():
def __init__(self, tags=top_tags):
self._vocab = {}
self._w = dict([(t, defaultdict(int)) for t in tags])
self._b = dict([(t, 0) for t in tags])
self._tags = set(tags)
self._word_stats = defaultdict(int)
def iterate_file(self,
fname=DS_FILE_NAME,
top_n_train=100000,
total=125000,
learning_rate=0.1,
tolerance=1e-16,
accuracy_level=0.9,
lmbda=0.0002,
gamma=0.1,
update_vocab=True):
self._loss = []
n = 0
accuracy = []
with open(fname, 'r') as f:
for line in tqdm_notebook(f, total=total, mininterval=1):
pair = line.strip().split('\t')
if len(pair) != 2:
continue
sentence, tags = pair
sentence = sentence.split(' ')
tags = set(tags.split(' '))
sample_loss = 0
predicted_tags = None
for tag in self._tags:
y = int(tag in tags)
z = self._b[tag]
for word in sentence:
if n >= top_n_train and word not in self._vocab:
continue
if word not in self._vocab and update_vocab:
self._vocab[word] = len(self._vocab)
if word not in self._vocab:
continue
if update_vocab:
self._word_stats[self._vocab[word]] += 1
z += self._w[tag][self._vocab[word]]
sigma = 1/(1 + np.exp(-z)) if z >= 0 else 1 - 1/(1 + np.exp(z))
sample_loss += -y*np.log(np.max([tolerance, sigma])) if y == 1 else \
-(1 - y)*np.log(1 - np.min([1 - tolerance, sigma]))
if n < top_n_train:
dLdw = y - sigma
for word in sentence:
if word not in self._vocab:
continue
self._w[tag][self._vocab[word]] -= -learning_rate * dLdw \
+ 2 * learning_rate * lmbda * gamma * self._w[tag][self._vocab[word]] \
+ learning_rate * lmbda *(1 - gamma) * np.sign(self._w[tag][self._vocab[word]])
self._b[tag] -= -learning_rate * dLdw
else:
if predicted_tags is None:
predicted_tags = []
if sigma > accuracy_level:
predicted_tags.append(tag)
n += 1
self._loss.append(sample_loss)
if predicted_tags is not None:
accuracy.append(len(tags.intersection(predicted_tags))/len(tags.union(predicted_tags)))
return(np.mean(accuracy))
def filter_vocab(self, n=10000):
keep_words = set([wid for (wid, wn) in sorted(self._word_stats.items(),
key=lambda t: t[1], reverse=True)[:n]])
self._vocab = dict([(k, v) for (k, v) in self._vocab.items() if v in keep_words])
for tag in self._tags:
self._w[tag] = dict([(k, v) for (k, v) in self._w[tag].items() if k in keep_words])
In [24]:
model = LogRegressor()
acc = model.iterate_file(update_vocab=True)
print('%0.2f' % acc)
plt.plot(pd.Series(model._loss[:-25000]).rolling(10000).mean());
In [25]:
# оставим только топ 10 000 слов
model.filter_vocab(n=10000)
In [26]:
# сделаем еще одну итерацию по датасету, уменьшив скорость обучения в 10 раз
acc = model.iterate_file(update_vocab=False, learning_rate=0.01)
print('%0.2f' % acc)
plt.plot(pd.Series(model._loss[:-25000]).rolling(10000).mean());
Вопрос 9. К какому значению ближе всего полученное значение точности: Варианты ответа:
In [27]:
class LogRegressor():
def __init__(self, tags=top_tags):
self._vocab = {}
self._w = dict([(t, defaultdict(int)) for t in tags])
self._b = dict([(t, 0) for t in tags])
self._tags = set(tags)
self._word_stats = defaultdict(int)
def iterate_file(self,
fname=DS_FILE_NAME,
top_n_train=100000,
total=125000,
learning_rate=0.1,
tolerance=1e-16,
accuracy_level=0.9,
lmbda=0.0002,
gamma=0.1,
update_vocab=True):
self._loss = []
n = 0
accuracy = []
with open(fname, 'r') as f:
for line in tqdm_notebook(f, total=total, mininterval=1):
pair = line.strip().split('\t')
if len(pair) != 2:
continue
sentence, tags = pair
sentence = sentence.split(' ')
tags = set(tags.split(' '))
sample_loss = 0
predicted_tags = None
for tag in self._tags:
y = int(tag in tags)
z = self._b[tag]
for word in sentence:
if n >= top_n_train and word not in self._vocab:
continue
if word not in self._vocab and update_vocab:
self._vocab[word] = len(self._vocab)
if word not in self._vocab:
continue
if update_vocab:
self._word_stats[self._vocab[word]] += 1
z += self._w[tag][self._vocab[word]]
sigma = 1/(1 + np.exp(-z)) if z >= 0 else 1 - 1/(1 + np.exp(z))
sample_loss += -y*np.log(np.max([tolerance, sigma])) if y == 1 else \
-(1 - y)*np.log(1 - np.min([1 - tolerance, sigma]))
if n < top_n_train:
dLdw = y - sigma
for word in sentence:
if word not in self._vocab:
continue
self._w[tag][self._vocab[word]] -= -learning_rate * dLdw \
+ 2 * learning_rate * lmbda * gamma * self._w[tag][self._vocab[word]] \
+ learning_rate * lmbda *(1 - gamma) * np.sign(self._w[tag][self._vocab[word]])
self._b[tag] -= -learning_rate * dLdw
else:
if predicted_tags is None:
predicted_tags = []
if sigma > accuracy_level:
predicted_tags.append(tag)
n += 1
self._loss.append(sample_loss)
if predicted_tags is not None:
accuracy.append(len(tags.intersection(predicted_tags))/len(tags.union(predicted_tags)))
return(np.mean(accuracy))
def filter_vocab(self, n=10000):
keep_words = set([wid for (wid, wn) in sorted(self._word_stats.items(),
key=lambda t: t[1], reverse=True)[:n]])
self._vocab = dict([(k, v) for (k, v) in self._vocab.items() if v in keep_words])
for tag in self._tags:
self._w[tag] = dict([(k, v) for (k, v) in self._w[tag].items() if k in keep_words])
def predict_proba(self, sentence):
p = {}
sentence = sentence.split(' ')
for tag in self._tags:
z = self._b[tag]
for word in sentence:
if word not in self._vocab:
continue
z += self._w[tag][self._vocab[word]]
sigma = 1 / (1 + np.exp(-z)) if z >= 0 else 1 - 1 / (1 + np.exp(z))
p[tag] = sigma
return p
In [28]:
model = LogRegressor()
acc = model.iterate_file(update_vocab=True)
print('%0.2f' % acc)
model.filter_vocab(n=10000)
acc = model.iterate_file(update_vocab=False, learning_rate=0.01)
print('%0.2f' % acc)
In [29]:
sentence = ("I want to improve my coding skills, so I have planned write " +
"a Mobile Application.need to choose between Apple's iOS or Google's Android." +
" my background: I have done basic programming in .Net,C/C++,Python and PHP " +
"in college, so got OOP concepts covered. about my skill level, I just know " +
"concepts and basic syntax. But can't write complex applications, if asked :(" +
" So decided to hone my skills, And I wanted to know which is easier to " +
"learn for a programming n00b. A) iOS which uses Objective C B) Android " +
"which uses Java. I want to decide based on difficulty " +
"level").lower().replace(',', '')
In [30]:
sorted(model.predict_proba(sentence).items(),
key=lambda t: t[1], reverse=True)
Out[30]:
Вопрос 10. Отметьте все теги, ассоциирующиеся с данным вопросом, если порог принятия равен $0.9$. То есть считаем, что вопросу надо поставить некоторый тег, если вероятность его появления, предсказанная моделью, больше или равна 0.9.
Варианты ответа: