Смотрите в этой серии:
За помощь в организации свёрточной части спасибо Ирине Гольцман
Для работы этого семинара вам потреюуется nltk v3.2
Важно, что именно v3.2, чтобы правильно работал токенизатор
Устаовить/обновиться до неё можно командой
sudo pip install --upgrade nltk==3.2
sudo pip install --upgrade pip
Если у вас нет доступа к этой версии - просто убедитесь, что токены в token_counts включают русские слова.
In [ ]:
low_RAM_mode = True
very_low_RAM = False #если у вас меньше 3GB оперативки, включите оба флага
In [ ]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
Бывший kaggle-конкурс про выявление нежелательного контента.
Описание конкурса есть тут - https://www.kaggle.com/c/avito-prohibited-content
Если много RAM,
Много разных признаков:
Нужно предсказать всего 1 бинарный признак - есть ли в рекламе нежелательный контент.
In [ ]:
if not low_RAM_mode:
# Если у вас много оперативки
df = pd.read_csv("avito_train.tsv",sep='\t')
else:
#Если у вас меньше 4gb оперативки
df = pd.read_csv("avito_train_1kk.tsv",sep='\t')
In [ ]:
print df.shape, df.is_blocked.mean()
df[:5]
In [ ]:
print "Доля заблокированных объявлений",df.is_blocked.mean()
print "Всего объявлений:",len(df)
Если у вас слабый ПК и вы видите OutOfMemory, попробуйте уменьшить размер выборки до 100 000 примеров
Алсо если вы не хотите ждать чтения всех данных каждый раз - сохраните уменьшенную выборку и читайте её
In [ ]:
#downsample
< выдели подвыборку, в которой отрицательных примеров примерно столько же, сколько положительных>
df = <уменьшенная подвыборка>
print "Доля заблокированных объявлений:",df.is_blocked.mean()
print "Всего объявлений:",len(df)
In [ ]:
assert df.is_blocked.mean() < 0.51
assert df.is_blocked.mean() > 0.49
assert len(df) <= 560000
print "All tests passed"
In [ ]:
#прореживаем данные ещё в 2 раза, если памяти не хватает
if very_low_ram:
data = data[::2]
In [ ]:
from nltk.tokenize import RegexpTokenizer
from collections import Counter,defaultdict
tokenizer = RegexpTokenizer(r"\w+")
#словарь для всех токенов
token_counts = Counter()
#все заголовки и описания
all_texts = np.hstack([df.description.values,df.title.values])
#считаем частоты слов
for s in all_texts:
if type(s) is not str:
continue
s = s.decode('utf8').lower()
tokens = tokenizer.tokenize(s)
for token in tokens:
token_counts[token] +=1
In [ ]:
#распределение частот слов - большинство слов встречаются очень редко - для нас это мусор
_=plt.hist(token_counts.values(),range=[0,50],bins=50)
In [ ]:
#возьмём только те токены, которые встретились хотя бы 10 раз в обучающей выборке
#информацию о том, сколько раз встретился каждый токен, можно найти в словаре token_counts
min_count = 10
tokens = <список слов(ключей) из token_counts, которые встретились в выборке не менее min_count раз>
In [ ]:
token_to_id = {t:i+1 for i,t in enumerate(tokens)}
null_token = "NULL"
token_to_id[null_token] = 0
In [ ]:
print "Всего токенов:",len(token_to_id)
if len(token_to_id) < 30000:
print "Алярм! Мало токенов. Проверьте, есть ли в token_to_id юникодные символы, если нет - обновите nltk или возьмите другой токенизатор"
if len(token_to_id) < 1000000:
print "Алярм! Много токенов. Если вы знаете, что делаете - всё ок, если нет - возможно, вы слижком слабо обрезали токены по количеству"
In [ ]:
def vectorize(strings, token_to_id, max_len=150):
token_matrix = []
for s in strings:
if type(s) is not str:
token_matrix.append([0]*max_len)
continue
s = s.decode('utf8').lower()
tokens = tokenizer.tokenize(s)
token_ids = map(lambda token: token_to_id.get(token,0), tokens)[:max_len]
token_ids += [0]*(max_len - len(token_ids))
token_matrix.append(token_ids)
return np.array(token_matrix)
In [ ]:
desc_tokens = vectorize(df.description.values,token_to_id,max_len = 150)
title_tokens = vectorize(df.title.values,token_to_id,max_len = 15)
In [ ]:
print "Размер матрицы:",title_tokens.shape
for title, tokens in zip(df.title.values[:3],title_tokens[:3]):
print title,'->', tokens[:10],'...'
Как вы видите, всё довольно грязно. Посмотрим, сожрёт ли это нейронка
In [ ]:
#Возьмём числовые признаки
df_numerical_features = df[["phones_cnt","emails_cnt","urls_cnt","price"]]
In [ ]:
#Возьмём one-hot encoding категорий товара.
#Для этого можно использовать DictVectorizer (или другой ваш любимый препроцессор)
from sklearn.feature_extraction import DictVectorizer
categories = []
for cat_str, subcat_str in df[["category","subcategory"]].values:
cat_dict = {"category":cat_str,"subcategory":subcat_str}
categories.append(cat_dict)
vectorizer = DictVectorizer(sparse=False)
cat_one_hot = vectorizer.fit_transform(categories)
cat_one_hot = pd.DataFrame(cat_one_hot,columns=vectorizer.feature_names_)
In [ ]:
df_non_text = pd.merge(
df_numerical_features,cat_one_hot,on = np.arange(len(cat_one_hot))
)
del df_non_text["key_0"]
In [ ]:
#целевая переменная - есть заблокирован ли контент
target = df.is_blocked.values.astype('int32')
#закодированное название
title_tokens = title_tokens.astype('int32')
#закодированное описание
desc_tokens = desc_tokens.astype('int32')
#все нетекстовые признаки
df_non_text = df_non_text.astype('float32')
In [ ]:
#поделим всё это на обучение и тест
from sklearn.cross_validation import train_test_split
data_tuple = train_test_split(title_tokens,desc_tokens,df_non_text.values,target)
title_tr,title_ts,desc_tr,desc_ts,nontext_tr,nontext_ts,target_tr,target_ts = data_tuple
In [ ]:
save_prepared_data = True #сохранить
read_prepared_data = False #cчитать
#за 1 раз данные можно либо записать, либо прочитать, но не и то и другое вместе
assert not (save_prepared_data and read_prepared_data)
if save_prepared_data:
print "Сохраняем подготовленные данные... (может занять до 3 минут)"
import pickle
with open("preprocessed_data.pcl",'w') as fout:
pickle.dump(data_tuple,fout)
with open("token_to_id.pcl",'w') as fout:
pickle.dump(token_to_id,fout)
print "готово"
elif read_prepared_data:
print "Читаем сохранённые данные..."
import pickle
with open("preprocessed_data.pcl",'r') as fin:
data_tuple = pickle.load(fin)
title_tr,title_ts,desc_tr,desc_ts,nontext_tr,nontext_ts,target_tr,target_ts = data_tuple
with open("token_to_id.pcl",'r') as fin:
token_to_id = pickle.load(fin)
#повторно импортируем библиотеки, чтобы было удобно перезапускать тетрадку с этой клетки
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
print "готово"
Поскольку у нас есть несколько источников данных, наша нейронная сеть будет немного отличаться от тех, что вы тренировали раньше.
Всё это нужно как-то смешать - например, сконкатенировать
In [ ]:
#загрузим библиотеки
import lasagne
from theano import tensor as T
import theano
In [ ]:
#3 входа и 1 выход
title_token_ids = T.matrix("title_token_ids",dtype='int32')
desc_token_ids = T.matrix("desc_token_ids",dtype='int32')
categories = T.matrix("categories",dtype='float32')
target_y = T.ivector("is_blocked")
In [ ]:
title_inp = lasagne.layers.InputLayer((None,title_tr.shape[1]),input_var=title_token_ids)
descr_inp = lasagne.layers.InputLayer((None,desc_tr.shape[1]),input_var=desc_token_ids)
cat_inp = lasagne.layers.InputLayer((None,nontext_tr.shape[1]), input_var=categories)
In [ ]:
# Описание
descr_nn = lasagne.layers.EmbeddingLayer(descr_inp,input_size=len(token_to_id)+1,output_size=128)
#поменять порядок осей с [batch, time, unit] на [batch,unit,time], чтобы свёртки шли по оси времени, а не по нейронам
descr_nn = lasagne.layers.DimshuffleLayer(descr_nn, [0,2,1])
# 1D свёртка на ваш вкус
descr_nn = lasagne.layers.Conv1DLayer(descr_nn,num_filters=?,filter_size=?)
# максимум по времени для каждого нейрона
descr_nn = lasagne.layers.GlobalPoolLayer(descr_nn,pool_function=T.max)
#А ещё можно делать несколько параллельных свёрток разного размера или стандартный пайплайн
#1dconv -> 1d max pool ->1dconv и в конце global pool
# Заголовок
title_nn = <текстовая свёрточная сеть для заголовков (title_inp)>
# Нетекстовые признаки
cat_nn = <простая полносвязная сеть для нетекстовых признаков (cat_inp)>
In [ ]:
nn = <объединение всех 3 сетей в одну (например lasagne.layers.concat) >
nn = lasagne.layers.DenseLayer(nn,1024)
nn = lasagne.layers.DropoutLayer(nn,p=0.05)
nn = lasagne.layers.DenseLayer(nn,1,nonlinearity=lasagne.nonlinearities.linear)
binary = True
In [ ]:
#Все обучаемые параметры сети
weights = lasagne.layers.get_all_params(nn,trainable=True)
In [ ]:
#Обычное предсказание нейронки
prediction = lasagne.layers.get_output(nn)[:,0]
#функция потерь для prediction
loss = lasagne.objectives.binary_hinge_loss(prediction,target_y,delta = 1.0).mean()
In [ ]:
#Шаг оптимизации весов
updates = <Ваш любимый метод оптимизации весов>
In [ ]:
#Предсказание нейронки без учёта dropout и прочего шума - если он есть
det_prediction = lasagne.layers.get_output(nn,deterministic=True)[:,0]
#функция потерь для det_prediction
det_loss = lasagne.objectives.binary_hinge_loss(det_prediction,target_y,delta = 1.0).mean()
In [ ]:
train_fun = theano.function([desc_token_ids,title_token_ids,categories,target_y],[loss,prediction],updates = updates)
eval_fun = theano.function([desc_token_ids,title_token_ids,categories,target_y],[det_loss,det_prediction])
In [ ]:
#average precision at K
from oracle import APatK, score
In [ ]:
# наш старый знакомый - итератор по корзинкам - теперь умеет работать с произвольным числом каналов (название, описание, категории, таргет)
def iterate_minibatches(*arrays,**kwargs):
batchsize=kwargs.get("batchsize",100)
shuffle = kwargs.get("shuffle",True)
if shuffle:
indices = np.arange(len(arrays[0]))
np.random.shuffle(indices)
for start_idx in range(0, len(arrays[0]) - batchsize + 1, batchsize):
if shuffle:
excerpt = indices[start_idx:start_idx + batchsize]
else:
excerpt = slice(start_idx, start_idx + batchsize)
yield [arr[excerpt] for arr in arrays]
n_epochs = 10**10
и остановку процесса вручную по возвращению с дачи/из похода. Tips:
Если вы выставили небольшой minibatches_per_epoch, качество сети может сильно скакать возле 0.5 на первых итерациях, пока сеть почти ничему не научилась.
На первых этапах попытки стоит сравнивать в первую очередь по AUC, как по самой стабильной метрике.
Метрика Average Precision at top 2.5% (APatK) - сама по себе очень нестабильная на маленьких выборках, поэтому её имеет смысл оценивать на на всех примерах (см. код ниже). Для менее, чем 10000 примеров она вовсе неинформативна.
Для сравнения методов оптимизации и регуляризаторов будет очень полезно собирать метрики качества после каждой итерации и строить график по ним после обучения
Как только вы убедились, что сеть не упала - имеет смысл дать ей покрутиться - на стандартном ноутбуке хотя бы пару часов.
In [ ]:
from sklearn.metrics import roc_auc_score, accuracy_score
n_epochs = 100
batch_size = 100
minibatches_per_epoch = 100
for i in range(n_epochs):
#training
epoch_y_true = []
epoch_y_pred = []
b_c = b_loss = 0
for j, (b_desc,b_title,b_cat, b_y) in enumerate(
iterate_minibatches(desc_tr,title_tr,nontext_tr,target_tr,batchsize=batch_size,shuffle=True)):
if j > minibatches_per_epoch:break
loss,pred_probas = train_fun(b_desc,b_title,b_cat,b_y)
b_loss += loss
b_c +=1
epoch_y_true.append(b_y)
epoch_y_pred.append(pred_probas)
epoch_y_true = np.concatenate(epoch_y_true)
epoch_y_pred = np.concatenate(epoch_y_pred)
print "Train:"
print '\tloss:',b_loss/b_c
print '\tacc:',accuracy_score(epoch_y_true,epoch_y_pred>0.)
print '\tauc:',roc_auc_score(epoch_y_true,epoch_y_pred)
print '\tap@k:',APatK(epoch_y_true,epoch_y_pred,K = int(len(epoch_y_pred)*0.025)+1)
#evaluation
epoch_y_true = []
epoch_y_pred = []
b_c = b_loss = 0
for j, (b_desc,b_title,b_cat, b_y) in enumerate(
iterate_minibatches(desc_ts,title_ts,nontext_tr,target_ts,batchsize=batch_size,shuffle=True)):
if j > minibatches_per_epoch: break
loss,pred_probas = eval_fun(b_desc,b_title,b_cat,b_y)
b_loss += loss
b_c +=1
epoch_y_true.append(b_y)
epoch_y_pred.append(pred_probas)
epoch_y_true = np.concatenate(epoch_y_true)
epoch_y_pred = np.concatenate(epoch_y_pred)
print "Val:"
print '\tloss:',b_loss/b_c
print '\tacc:',accuracy_score(epoch_y_true,epoch_y_pred>0.)
print '\tauc:',roc_auc_score(epoch_y_true,epoch_y_pred)
print '\tap@k:',APatK(epoch_y_true,epoch_y_pred,K = int(len(epoch_y_pred)*0.025)+1)
In [ ]:
print "Если ты видишь это сообщение, самое время сделать резервную копию ноутбука. \nНет, честно, здесь очень легко всё сломать"
In [ ]:
#evaluation
epoch_y_true = []
epoch_y_pred = []
b_c = b_loss = 0
for j, (b_desc,b_title,b_cat, b_y) in enumerate(
iterate_minibatches(desc_ts,title_ts,nontext_tr,target_ts,batchsize=batch_size,shuffle=True)):
loss,pred_probas = eval_fun(b_desc,b_title,b_cat,b_y)
b_loss += loss
b_c +=1
epoch_y_true.append(b_y)
epoch_y_pred.append(pred_probas)
epoch_y_true = np.concatenate(epoch_y_true)
epoch_y_pred = np.concatenate(epoch_y_pred)
final_accuracy = accuracy_score(epoch_y_true,epoch_y_pred>0)
final_auc = roc_auc_score(epoch_y_true,epoch_y_pred)
final_apatk = APatK(epoch_y_true,epoch_y_pred,K = int(len(epoch_y_pred)*0.025)+1)
print "Scores:"
print '\tloss:',b_loss/b_c
print '\tacc:',final_accuracy
print '\tauc:',final_auc
print '\tap@k:',final_apatk
score(final_accuracy,final_auc,final_apatk)
Вспомните всё, чему вас учили
etc etc etc
Можно попробовать вспомнить NLP: лемматизация, улучшенная токенизация
{Как вы его создали?}
In [ ]: