<jupyter_start><jupyter_text>Векторные представления слов ![w2v](https://cdn-images-1.medium.com/max/2600/1*sXNXYfAqfLUeiDXPCo130w.png) Есть три сценария работы с векторными представлениями:- Взять предобученную модель- Обучить свою модель - Взять предобученную модель и дообучить ее Предобученные модели: RusVectōrēsНа сайте [RusVectōrēs](https://rusvectores.org/ru/) собраны предобученные на различных данных модели для русского языка, а также можно поискать наиболее близкие слова к заданному, посчитать семантическую близость нескольких слов и порешать примеры с помощью «калькулятора семантической близости».Для других языков также можно найти предобученные модели — например, модели [fastText](https://fasttext.cc/docs/en/english-vectors.html) и [GloVe](https://nlp.stanford.edu/projects/glove/) (о них чуть дальше).  GensimИспользовать предобученную модель или обучить свою можно с помощью библиотеки `gensim`. Вот [ее документация](https://radimrehurek.com/gensim/models/word2vec.html). Как использовать готовую модельМодели word2vec бывают разных форматов:* .vec.gz — обычный текстовый файл * .bin.gz — бинарный файлЗагружаются они с помощью одного и того же класса `KeyedVectors`, меняется только параметр `binary` у функции `load_word2vec_format`. Если же векторы обучены **не** с помощью word2vec, то для загрузки нужно использовать функцию `load`. Т.е. для загрузки предобученных моделей *glove, fasttext, bpe* и любых других нужна именно она.Скачаем с RusVectōrēs модель для русского языка, обученную на НКРЯ : https://rusvectores.org/ru/models/<jupyter_code>import re
import gensim
import logging
import nltk.data 
import pandas as pd
from nltk.corpus import stopwords
from gensim.models import word2vec
from nltk.tokenize import sent_tokenize, RegexpTokenizer
nltk.download('punkt')<jupyter_output>[nltk_data] Downloading package punkt to /root/nltk_data...
[nltk_data]   Unzipping tokenizers/punkt.zip.<jupyter_text>mystem - модель для контекста<jupyter_code>!wget https://rusvectores.org/static/models/rusvectores2/ruscorpora_mystem_cbow_300_2_2015.bin.gz
model_path = 'ruscorpora_mystem_cbow_300_2_2015.bin.gz'

model_ru = gensim.models.KeyedVectors.load_word2vec_format(model_path, binary=True)
words = ['день_S', 'ночь_S', 'человек_S', 'семантика_S', 'биткоин_S']<jupyter_output><empty_output><jupyter_text>Частеречные тэги нужны, поскольку это специфика скачанной модели - она была натренирована на словах, аннотированных их частями речи (и лемматизированных). **Важно!** В названиях моделей на `rusvectores` указано, какой набор тегов они используют (mystem, upos и т.д.)Попросим у модели 10 ближайших соседей для каждого слова и коэффициент косинусной близости для каждого:<jupyter_code>model_ru['день_S']
for word in words:
    # есть ли слово в модели? 
    if word in model_ru:
        print(word)
        # выдаем 10 ближайших соседей слова:
        for word, sim in model_ru.most_similar(positive=[word], topn=10):
            # слово + коэффициент косинусной близости
            print(word, ': ', sim)
        print('\n')
    else:
        # Увы!
        print(f'Увы, слова "{word}" нет в модели!')
model_ru['биткоин_S']<jupyter_output><empty_output><jupyter_text>Находим косинусную близость пары слов:<jupyter_code>print(model_ru.similarity('человек_S', 'обезьяна_S'))
print(model_ru.similarity('добро_S', 'добродетель_S'))<jupyter_output>0.27628684<jupyter_text>Построим аналогии: * *король: мужчина = королева: женщина*  $\Rightarrow$ * *король - мужчина + женщина = королева*Что получится, если вычесть из пиццы Италию и прибавить Сибирь?* positive — вектора, которые мы складываем* negative — вектора, которые вычитаем<jupyter_code>print(model_ru.most_similar(positive=['пицца_S', 'сибирь_S'], negative=['италия_S'])[0][0])
model_ru.doesnt_match('пицца_S пельмень_S хот-дог_S ананас_S'.split())<jupyter_output>/usr/local/lib/python3.7/dist-packages/gensim/models/keyedvectors.py:895: FutureWarning: arrays to stack must be passed as a "sequence" type such as list or tuple. Support for non-sequence iterables such as generators is deprecated as of NumPy 1.16 and will raise an error in the future.
  vectors = vstack(self.word_vec(word, use_norm=True) for word in used_words).astype(REAL)<jupyter_text>Предобученные модели: GloVeСкачаем модель с сайта: https://nlp.stanford.edu/projects/glove/<jupyter_code>! wget https://nlp.stanford.edu/data/glove.6B.zip
!unzip glove.6B.zip
! head -n 1 glove.6B.100d.txt
import numpy as np

glove = {}
with open('glove.6B.200d.txt', 'r') as f:
    for line in f:
        word, embedding = line.split(' ',1)
        wordEmbedding = np.array([float(value) for value in embedding[1:].split(' ')])
        glove[word] = wordEmbedding

print(len(glove))
len(glove['hello'])<jupyter_output><empty_output><jupyter_text>это 200 элементов для вектора, который выдается по этому слово  Предобученные модели: fastTextпримерно - word2vec на символьных нграммахFastText использует не только векторы слов, но и векторы n-грам. В корпусе каждое слово автоматически представляется в виде набора символьных n-грамм. Скажем, если мы установим n=3, то вектор для слова "where" будет представлен суммой векторов следующих триграм: "" (где "" символы, обозначающие начало и конец слова). Благодаря этому мы можем также получать вектора для слов, отсутствуюших в словаре, а также эффективно работать с текстами, содержащими ошибки и опечатки.* [Статья](https://aclweb.org/anthology/Q17-1010)* [Сайт](https://fasttext.cc/)* [Руководство](https://fasttext.cc/docs/en/support.html)* [Репозиторий](https://github.com/facebookresearch/fasttext)Есть библиотека `fasttext` для питона (с готовыми моделями можно работать и через `gensim`).На сайте проекта можно найти предобученные модели для 157 языков (в том числе русского): https://fasttext.cc/docs/en/crawl-vectors.html<jupyter_code>!pip3 install fasttext==0.9.2
import fasttext

!wget https://dl.fbaipublicfiles.com/fasttext/vectors-crawl/cc.ru.300.bin.gz

!gzip -dk cc.ru.300.bin.gz
ft = fasttext.load_model('cc.ru.300.bin')
ft.get_dimension()<jupyter_output><empty_output><jupyter_text>для многоязычной  - MUSE fast text<jupyter_code>! ls
ft['модель']<jupyter_output><empty_output><jupyter_text>У fasttext есть все те же методы, что в gensim, но называются они иначе:<jupyter_code>ft.get_nearest_neighbors('чай')
ft.get_analogies("женщина", "мужчина", "актер")<jupyter_output><empty_output><jupyter_text>Важная особенность: так как модель обучена на символьных n-граммах, нет проблемы OOV (out of vocabulary) слов:<jupyter_code>ft.get_nearest_neighbors('книжонка')
ft.get_nearest_neighbors('компютер')<jupyter_output><empty_output><jupyter_text>Как обучить свою модель: word2vecВ качестве обучающих данных возьмем англоязычные отзывы о фильмах с сайта IMDB (данные взяты отсюда http://ai.stanford.edu/~amaas/data/sentiment/).<jupyter_code># если у вас линукс / мак / collab или ещё какая-то среда, в которой работает wget, можно так:
!wget --no-check-certificate 'https://docs.google.com/uc?export=download&id=1_QWwA3TmNu2T5tDqUUaCXsg9T78WADm-' -O imdb_data.csv
import pandas as pd

pd.set_option('display.max_columns', None)  
pd.set_option('display.expand_frame_repr', False)
pd.set_option('max_colwidth', 800)
data = pd.read_csv("imdb_data.csv")

len(data)
data.head()<jupyter_output><empty_output><jupyter_text>Убираем из данных ссылки, html-разметку и небуквенные символы, а затем приведем все к нижнему регистру и токенизируем. На выходе получается массив из предложений, каждое из которых представляет собой массив слов. Здесь используется токенизатор из библиотеки `nltk`.<jupyter_code>from bs4 import BeautifulSoup
from tqdm.notebook import tqdm
from multiprocessing import Pool
import warnings
warnings.filterwarnings("ignore")
tokenizer = nltk.data.load('tokenizers/punkt/english.pickle')
def review_to_wordlist(review, remove_stopwords=False ):
    # убираем ссылки вне тегов
    review = re.sub(r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+", " ", review)
    review_text = BeautifulSoup(review, "lxml").get_text()
    review_text = re.sub("[^a-zA-Z]"," ", review_text)
    words = review_text.lower().split()
    if remove_stopwords:
        stops = stopwords.words("english")
        words = [w for w in words if not w in stops]
    return(words)

def review_to_sentences(review, tokenizer=tokenizer, remove_stopwords=False):
    raw_sentences = tokenizer.tokenize(review.strip())
    sentences = []
    for raw_sentence in raw_sentences:
        if len(raw_sentence) > 0:
            sentences.append(review_to_wordlist(raw_sentence, remove_stopwords))
    return sentences
with Pool(4) as p:
    sentences = list(tqdm(p.imap(review_to_sentences, data["review"]), total=len(data)))

print(len(sentences))
print(*sentences[:3])
flat_sentences = [item for sublist in sentences for item in sublist]
len(flat_sentences)
# это понадобится нам позже для обучения модели fasttext

with open('clean_text.txt', 'w') as f:
    for s in flat_sentences:
        f.write(' '.join(s))
        f.write('\n')<jupyter_output><empty_output><jupyter_text>Обучаем и сохраняем модель. Основные параметры:* данные должны быть итерируемым объектом * size — размер вектора, * window — размер окна наблюдения,* min_count — мин. частотность слова в корпусе,* sg — используемый алгоритм обучения (0 — CBOW, 1 — Skip-gram),* sample — порог для downsampling'a высокочастотных слов,* workers — количество потоков,* alpha — learning rate,* iter — количество итераций,* max_vocab_size — позволяет выставить ограничение по памяти при создании словаря (т.е. если ограничение превышается, то низкочастотные слова будут выбрасываться). Для сравнения: 10 млн слов = 1Гб RAM.**Важно!** Обратите внимание, что тренировка модели не включает предобработку! Это значит, что избавляться от пунктуации, приводить слова к нижнему регистру, лемматизировать их, проставлять частеречные теги придется до тренировки модели (если, конечно, это необходимо для вашей задачи). Т.е. в каком виде слова будут в исходном тексте, в таком они будут и в модели.<jupyter_code>print("Training model...")

%time model_en = word2vec.Word2Vec(flat_sentences, workers=4, size=300, min_count=10, window=10, sample=1e-3)<jupyter_output>Training model...
CPU times: user 3min 51s, sys: 597 ms, total: 3min 51s
Wall time: 2min 3s<jupyter_text>Смотрим, сколько в модели слов.<jupyter_code>print(len(model_en.wv.vocab))<jupyter_output>27864<jupyter_text>Попробуем оценить модель вручную, аналогично тому, как проверяли предобученные модели.<jupyter_code>print(model_en.wv.most_similar(positive=["woman", "actor"], negative=["man"], topn=1))
print(model_en.wv.most_similar(positive=["dogs", "man"], negative=["dog"], topn=1))
print(model_en.wv.most_similar("usa", topn=3))
print(model_en.wv.doesnt_match("comedy thriller western novel".split()))<jupyter_output>novel<jupyter_text>Почему важно, на каких данных обучалась модель? Посмотрим на ближайшие по смыслу слова к слову "star":- в модели, обученной на обзорах фильмов- в модели, обученной на Википедии<jupyter_code>print(*model_en.wv.most_similar("star", topn=10), sep='\n')
model_en.similarity('star', 'celebrity')
model_en.similarity('star', 'sky')
model_en.similarity('star', 'shine')<jupyter_output><empty_output><jupyter_text>Скачаем предобученную модель fastText для английского:<jupyter_code>import fasttext.util
#fasttext.util.download_model('en', if_exists='ignore') 

# ft_eng = fasttext.load_model('cc.en.300.bin')
from sklearn.metrics.pairwise import cosine_similarity
cosine_similarity([ft['star']], [ft['celebrity']])
cosine_similarity([ft['star']], [ft['sky']])
cosine_similarity([ft['star']], [ft['shine']])<jupyter_output><empty_output><jupyter_text>Как дообучить существующую модельПри тренировке модели "с нуля" веса инициализируются случайно, однако можно использовать для инициализации векторов веса из предобученной модели, таким образом как бы дообучая ее.Сначала посмотрим близость какой-нибудь пары слов в имеющейся модели, чтобы потом сравнить результат с дообученной.<jupyter_code>model_en.wv.similarity('white', 'rabbit')<jupyter_output><empty_output><jupyter_text>В качестве дополнительных данных для обучения возьмем английский текст «Алисы в Зазеркалье».<jupyter_code>with open("alice.txt", 'r', encoding='utf-8') as f:
    text = f.read()

text = re.sub('\n', ' ', text)
sents = sent_tokenize(text)

punct = '!"#$%&()*+,-./:;<=>?@[\]^_`{|}~„“«»†*—/\-‘’'
clean_sents = []

for sent in sents:
    s = [w.lower().strip(punct) for w in sent.split()]
    clean_sents.append(s)
    
print(clean_sents[:2])<jupyter_output>[['through', 'the', 'looking-glass', 'by', 'lewis', 'carroll', 'chapter', 'i', 'looking-glass', 'house', 'one', 'thing', 'was', 'certain', 'that', 'the', 'white', 'kitten', 'had', 'had', 'nothing', 'to', 'do', 'with', 'it', '', 'it', 'was', 'the', 'black', 'kitten’s', 'fault', 'entirely'], ['for', 'the', 'white', 'kitten', 'had', 'been', 'having', 'its', 'face', 'washed', 'by', 'the', 'old', 'cat', 'for', 'the', 'last', 'quarter', 'of', 'an', 'hour', 'and', 'bearing', 'it', 'pretty', 'well', 'considering', 'so', 'you', 'see', 'that', 'it', 'couldn’t', 'have', 'had', 'any', 'hand', 'in', 'the', 'mischief']]<jupyter_text>Чтобы дообучить модель, надо сначала ее сохранить, а потом загрузить. Все параметры тренировки (размер вектора, мин. частота слова и т.п.) будут взяты из загруженной модели, т.е. задать их заново нельзя.**NB!** Дообучить можно только полную модель, а `KeyedVectors` — нельзя. Поэтому сохранять модель нужно в соотвествующем формате. Подробнее о разнице [вот тут](https://radimrehurek.com/gensim/models/keyedvectors.html).<jupyter_code>model_path = "movie_reviews.model"

print("Saving model...")
model_en.save(model_path)
model = word2vec.Word2Vec.load(model_path)

model.build_vocab(clean_sents, update=True)
model.train(clean_sents, total_examples=model.corpus_count, epochs=5)<jupyter_output><empty_output><jupyter_text>"Белый" и "кролик" стали ближе друг к другу!<jupyter_code>model.wv.similarity('white', 'rabbit')<jupyter_output><empty_output><jupyter_text>Как обучить свою модель: fastTextВыше мы записали предобработанные данные IMDB в текстовый файл, теперь мы можем использовать его для обучения модели fastText.<jupyter_code>import fasttext

# так можно обучить свою модель 
ft_model = fasttext.train_unsupervised('clean_text.txt', minn=3, maxn=4, dim=300)
ft_model.get_word_vector("movie")
ft_model.get_nearest_neighbors('actor')
ft_model.get_analogies("woman", "man", "actor")<jupyter_output><empty_output><jupyter_text>OOV:<jupyter_code>ft_model.get_nearest_neighbors('actr')
ft_model.get_nearest_neighbors('moviegeek')<jupyter_output><empty_output><jupyter_text>ОценкаМы научились обучать модели, научились загружать готовые, а как понять, какая модель лучше? Или вот, например, мы обучили модель, а как понять, насколько она хорошая? Рассмотрим два метода: с помощью метрик, основанных на подготовленных данных, и визуализации.Для этого существуют специальные наборы данных для оценки качества дистрибутивных моделей. Основных два: один измеряет точность решения задач на аналогии (про Сибирь и пельмени), а второй используется для оценки коэффициента семантической близости.  Word SimilarityЭтот метод заключается в том, чтобы оценить, насколько представления о семантической близости слов в модели соотносятся с "представлениями" людей.| слово 1    | слово 2    | близость | |------------|------------|----------|| кошка      | собака     | 0.7      |  | чашка      | кружка     | 0.9      |       Для каждой пары слов из заранее заданного набора данных мы можем посчитать косинусное расстояние, и получить список таких значений близости. При этом у нас уже есть список значений близостей, сделанный людьми. Мы можем сравнить эти два списка и понять, насколько они похожи (например, посчитав корреляцию). Эта мера схожести должна говорить о том, насколько модель хорошо моделирует расстояния до слова. АналогииДругая популярная задача для "внутренней" оценки называется задачей поиска аналогий. Как мы уже разбирали выше, с помощью простых арифметических операций мы можем модифицировать значение слова. Если заранее собрать набор слов-модификаторов, а также слов, которые мы хотим получить в результаты модификации, то на основе подсчёта количества "попаданий" в желаемое слово мы можем оценить, насколько хорошо работает модель.В качестве слов-модификаторов мы можем использовать семантические аналогии. Скажем, если у нас есть некоторое отношение "страна-столица", то для оценки модели мы можем использовать пары наподобие "Россия-Москва", "Норвегия-Осло", и т.д. Набор данных будет выглядеть следующм образом:| слово 1    | слово 2    | отношение     | |------------|------------|---------------|| Россия     | Москва     | страна-столица|  | Норвегия   | Осло       | страна-столица|Рассматривая случайные две пары из этого набора, мы хотим, имея триплет (Россия, Москва, Норвегия) хотим получить слово "Осло", т.е. найти такое слово, которое будет находиться в том же отношении со словом "Норвегия", как "Россия" находится с Москвой. Данные для русского языка можно скачать на странице с моделями на RusVectores. Посчитаем качество нашей модели НКРЯ на датасете про аналогии:<jupyter_code>res = model_ru.accuracy('ru_analogy_tagged.txt')
!less ru_analogy_tagged.txt
print(res[4]['incorrect'][:10])
print(model_ru.most_similar(positive=['мальчик_S', 'бабка_S'], negative=['девочка_S'])[0][0])
len(res[4]['incorrect']), len(res[4]['correct'])<jupyter_output><empty_output><jupyter_text>доп вещи  ворд 2 векна препроцессинге - убрать лучше лишние символы.можно задать - mincount - на уровне обучения. gensim сможет выкинуть.может  pymorpy и морфологический анализ  ВизуализацияНа полученную модель можно посмотреть, визуализировав ее, например, на плоскости. t-SNE**t-SNE**  (*t-distributed Stochastic Neighbor Embedding*) — техника нелинейного снижения размерности и визуализации многомерных переменных. Она разработана специально для данных высокой размерности Л. ван дер Маатеном и <NAME>ом, [вот их статья](http://jmlr.org/papers/volume9/vandermaaten08a/vandermaaten08a.pdf). t-SNE — это итеративный алгоритм, основанный на вычислении попарных расстояний между всеми объектами (в том числе поэтому он довольно медленный).Изобразим на плоскости 1000 самых частотных слов из коллекции текстов про кино:<jupyter_code>model_path = "movie_reviews.model"

model = word2vec.Word2Vec.load(model_path)
from nltk import FreqDist
from tqdm import tqdm_notebook as tqdm
from sklearn.manifold import TSNE

top_words = []


fd = FreqDist()
for s in tqdm(flat_sentences):
    fd.update(s)

for w in fd.most_common(1000):
    top_words.append(w[0])
    
print(top_words[:50:])
top_words_vec = model[top_words]
%%time
tsne = TSNE(n_components=2, random_state=0)
top_words_tsne = tsne.fit_transform(top_words_vec)
from bokeh.models import ColumnDataSource, LabelSet
from bokeh.plotting import figure, show, output_file
from bokeh.io import output_notebook
output_notebook()

p = figure(tools="pan,wheel_zoom,reset,save",
           toolbar_location="above",
           title="word2vec T-SNE (eng model, top1000 words)")

source = ColumnDataSource(data=dict(x1=top_words_tsne[:,0],
                                    x2=top_words_tsne[:,1],
                                    names=top_words))

p.scatter(x="x1", y="x2", size=8, source=source)

labels = LabelSet(x="x1", y="x2", text="names", y_offset=6,
                  text_font_size="8pt", text_color="#555555",
                  source=source, text_align='center')
p.add_layout(labels)

show(p)<jupyter_output><empty_output><jupyter_text>Чтобы вычислить преобразование t-SNE быстрее (и иногда еще и эффективнее), можно сперва снизить размерность исходных данных с помощью, например, SVD, и потом применять t-SNE:<jupyter_code>from sklearn.decomposition import TruncatedSVD

svd_50 = TruncatedSVD(n_components=50)
top_words_vec_50 = svd_50.fit_transform(top_words_vec)
top_words_tsne2 = TSNE(n_components=2, random_state=0).fit_transform(top_words_vec_50)
output_notebook()

p = figure(tools="pan,wheel_zoom,reset,save",
           toolbar_location="above",
           title="word2vec T-SNE (eng model, top1000 words, +SVD)")

source = ColumnDataSource(data=dict(x1=top_words_tsne2[:,0],
                                    x2=top_words_tsne2[:,1],
                                    names=top_words))

p.scatter(x="x1", y="x2", size=8, source=source)

labels = LabelSet(x="x1", y="x2", text="names", y_offset=6,
                  text_font_size="8pt", text_color="#555555",
                  source=source, text_align='center')
p.add_layout(labels)

show(p)<jupyter_output><empty_output><jupyter_text>word2vec - нужен и правый и левый контекста RNN - это может только по левому контексту работать.<jupyter_code><jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11881425963293937, 'fraction_numerical': 0.012779859709810704, 'mean_word_length': 5.5974643423137875, 'pattern_counts': {'":': 1, '<': 104, '<?xml version=': 0, '>': 105, 'https://': 17, 'lorem ipsum': 0, 'www.': 0, 'xml': 1}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '27', 'dir': 'jupyter-structured-clean-dedup', 'id': '324932', 'license': 'MIT', 'n_tokens_mistral': 8429, 'n_tokens_neox': 9014, 'n_words': 1866, 'path': '07 NLP/01/2_embeddings.ipynb', 'repo_name': 'ksetdekov/HSE_DS', 'size': '20817'}