Обучаем Word2vec: практикум по созданию векторных моделей языка |
||
МЕНЮ Искусственный интеллект Поиск Регистрация на сайте Помощь проекту ТЕМЫ Новости ИИ Искусственный интеллект Разработка ИИГолосовой помощник Городские сумасшедшие ИИ в медицине ИИ проекты Искусственные нейросети Слежка за людьми Угроза ИИ ИИ теория Внедрение ИИКомпьютерные науки Машинное обуч. (Ошибки) Машинное обучение Машинный перевод Нейронные сети начинающим Реализация ИИ Реализация нейросетей Создание беспилотных авто Трезво про ИИ Философия ИИ Big data Работа разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика
Генетические алгоритмы Капсульные нейросети Основы нейронных сетей Распознавание лиц Распознавание образов Распознавание речи Техническое зрение Чат-боты Авторизация |
2020-10-02 20:00 Введение Word2vec — библиотека для получения векторных представлений слов на основе их совместной встречаемости в текстах. Системный Блокъ уже писал ранее о том, как работают эти модели, и вы можете освежить в памяти механизмы работы Word2vec, прочитав эту статью. Сейчас мы займемся более практичными и приземленными вещами: научимся использовать Word2vec в своей повседневной работе. Мы будем использовать реализацию Word2vec в библиотеке Gensim для языка программирования Python. Тьюториал состоит из двух частей:
Для прохождения тьюториала мы рекомендуем использовать Python3. Работоспособность кода для Python2 не гарантируется. Код из этого тьюториала также доступен в формате jupyter-тетрадки. Предобработка текстовых данных и тренировка модели Прежде чем переходить к тренировке моделей, бывает необходимо привести данные в формат, удобный для работы. Тексты обычно очищаются от пунктуации, приводятся к нижнему регистру. Этот процесс называется предобработкой текстовых данных. Для того чтобы начать тренировать векторную модель при помощи Word2vec, предобработку текста выполнять не обязательно. Для того чтобы алгоритм мог выучить вектора слов, достаточно всего лишь разделить текст на предложения. Однако в нашем тьюториале мы разберём, как выполнять более глубокую предобработку текста: лемматизацию и частеречный анализ. Это нужно для того, чтобы вы сами могли создавать модели, совместимые с уже готовыми моделями RusVect?r?s. Ну и просто знание о том, как устроена предобработка текста, может пригодиться во многих задачах обработки языка. Обработка текста Предобработка текстов для тренировки моделей выглядит следующим образом:
Давайте попробуем воссоздать процесс предобработки текста на примере рассказа О. Генри «Русские соболя». Для предобработки можно использовать различные тэггеры, мы сейчас будем использовать UDPipe, чтобы сразу получить частеречную разметку в виде Universal POS-tags. Сначала установим обертку UDPipe для Python с помощью питоновского пакет-менеджера pip: pip install ufal.udpipe UDPipe использует предобученные модели для лемматизации и тэггинга. Вы можете использовать уже готовую модель или обучить свою. Чтобы загружать файлы, можно использовать питоновскую библиотеку wget: pip install wget Кусок кода ниже скачает рассказ О’Генри и модель UDPipe для лингвистической предобработки. Модель весит 40 мегабайт, поэтому ячейка может выполнятся некоторое время, особенно если у вас небыстрый интернет. import wget import sys udpipe_url = 'https://rusvectores.org/static/models/udpipe_syntagrus.model' text_url = 'https://rusvectores.org/static/henry_sobolya.txt' modelfile = wget.download(udpipe_url) textfile = wget.download(text_url) Приступим к собственно предобработке текста. Попробуем лемматизировать текст и добавить частеречные тэги при помощи этой функции: def process(pipeline, text='Строка', keep_pos=True, keep_punct=False): entities = {'PROPN'} named = False # переменная для запоминания того, что нам встретилось имя собственное memory = [] mem_case = None mem_number = None tagged_propn = [] # обрабатываем текст, получаем результат в формате conllu: processed = pipeline.process(text) # пропускаем строки со служебной информацией: content = [l for l in processed.split(' ') if not l.startswith('#')] # извлекаем из обработанного текста леммы, тэги и морфологические характеристики tagged = [w.split(' ') for w in content if w] for t in tagged: if len(t) != 10: # если список короткий — строчка не содержит разбора, пропускаем continue (word_id,token,lemma,pos,xpos,feats,head,deprel,deps,misc) = t if not lemma or not token: # если слово пустое — пропускаем continue if pos in entities: # здесь отдельно обрабатываем имена собственные — они требуют особого обращения if '|' not in feats: tagged_propn.append('%s_%s' % (lemma, pos)) continue morph = {el.split('=')[0]: el.split('=')[1] for el in feats.split('|')} if 'Case' not in morph or 'Number' not in morph: tagged_propn.append('%s_%s' % (lemma, pos)) continue if not named: named = True mem_case = morph['Case'] mem_number = morph['Number'] if morph['Case'] == mem_case and morph['Number'] == mem_number: memory.append(lemma) if 'SpacesAfter= ' in misc or 'SpacesAfter=s ' in misc: named = False past_lemma = '::'.join(memory) memory = [] tagged_propn.append(past_lemma + '_PROPN ') else: named = False past_lemma = '::'.join(memory) memory = [] tagged_propn.append(past_lemma + '_PROPN ') tagged_propn.append('%s_%s' % (lemma, pos)) else: if not named: if pos == 'NUM' and token.isdigit(): # Заменяем числа на xxxxx той же длины lemma = num_replace(token) tagged_propn.append('%s_%s' % (lemma, pos)) else: named = False past_lemma = '::'.join(memory) memory = [] tagged_propn.append(past_lemma + '_PROPN ') tagged_propn.append('%s_%s' % (lemma, pos)) if not keep_punct: # обрабатываем случай, когда пользователь попросил не сохранять пунктуацию (по умолчанию она сохраняется) tagged_propn = [word for word in tagged_propn if word.split('_')[1] != 'PUNCT'] if not keep_pos: tagged_propn = [word.split('_')[0] for word in tagged_propn] return tagged_propn Эту функцию можно также изменить под конкретную задачу. Например, если частеречные тэги нам не нужны, в функции ниже выставим keep_pos=False. Если необходимо сохранить знаки пунктуации, можно выставить keep_punct=True. Теперь загружаем модель UDPipe, читаем текстовый файл и обрабатываем его при помощи нашей функции. В файле должен содержаться необработанный текст (одно предложение на строку или один абзац на строку). На выход мы получаем последовательность разделенных пробелами лемм с частями речи («зеленый_NOUN трамвай_NOUN»). from ufal.udpipe import Model, Pipeline import os import re def tag_ud(text='Текст нужно передать функции в виде строки!', modelfile='udpipe_syntagrus.model'): udpipe_model_url = 'https://rusvectores.org/static/models/udpipe_syntagrus.model' udpipe_filename = udpipe_model_url.split('/')[-1] if not os.path.isfile(modelfile): print('UDPipe model not found. Downloading...', file=sys.stderr) wget.download(udpipe_model_url) print(' Loading the model...', file=sys.stderr) model = Model.load(modelfile) process_pipeline = Pipeline(model, 'tokenize', Pipeline.DEFAULT, Pipeline.DEFAULT, 'conllu') print('Processing input...', file=sys.stderr) lines = text.split(' ') tagged = [] for line in lines: # line = unify_sym(line.strip()) # здесь могла бы быть ваша функция очистки текста output = process(process_pipeline, text=line) tagged_line = ' '.join(output) tagged.append(tagged_line) return ' '.join(tagged) text = open(textfile, 'r', encoding='utf-8').read() processed_text = tag_ud(text=text, modelfile=modelfile) print(processed_text[:350]) with open('my_text.txt', 'w', encoding='utf-8') as out: out.write(processed_text) > русский_PROPN соболь_NOUN о.::генри_PROPN Наша функция напечатает обработанный текст, который мы теперь можем также сохранить в файл. Итак, в ходе этой части тьюториала мы научились от «сырого текста» приходить к лемматизированному тексту с частеречными тэгами, который уже можно подавать на вход модели! Теперь теперь попробуем натренировать векторную модель. Тренировка модели Для работы с эмбеддингами слов существуют различные библиотеки: gensim, keras, tensorflow, pytorch. Мы будем работать с библиотекой gensim. Gensim — изначально библиотека для тематического моделирования текстов. Однако помимо различных алгоритмов для topic modeling в ней реализованы на python и алгоритмы из тулкита word2vec (который в оригинале был написан на C++). Прежде всего, если gensim у вас на компьютере не установлен, нужно его установить: pip install gensim Gensim регулярно обновляется, так что не будет лишним удостовериться, что у вас установлена последняя версия, а при необходимости проапдейтить библиотеку: pip install gensim --upgrade При подготовке этого тьюториала использовался gensim версии 3.7.0. Поскольку обучение и загрузка моделей могут занимать продолжительное время, иногда бывает полезно вести лог событий. Для этого используется стандартная питоновская библиотека logging. import sys import gensim, logging logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO) На вход модели мы даем наш обработанный текстовый файл (либо любой другой текст, важно лишь, что каждое предложение должно быть на отдельной строчке). f = 'my_text.txt' data = gensim.models.word2vec.LineSentence(f) Инициализируем модель. Параметры в скобочках:
model = gensim.models.Word2Vec(data, size=500, window=10, min_count=2, sg=0) Мы создаем модель, в которой размерность векторов — 500, размер окна наблюдения — 10 слов, алгоритм обучения — CBOW, слова, встретившиеся в корпусе только 1 раз, не используются. После тренировки модели можно нормализовать вектора, тогда модель будет занимать меньше RAM. Однако после этого её нельзя дотренировать. model.init_sims(replace=True) Смотрим, сколько в модели слов (корпус у нас был небольшой): print(len(model.wv.vocab)) > 252 И сохраняем нашу модель! model.save('my.model') Работа с векторными моделями при помощи библиотеки Gensim Для своих индивидуальных нужд и экспериментов бывает полезно самому натренировать модель на нужных данных и с нужными параметрами. Но для каких-то общих целей модели уже есть как для русского языка, так и для английского. Модели для русского скачать можно здесь. Существуют несколько форматов, в которых могут храниться модели. Во-первых, данные могут храниться в нативном формате word2vec, при этом модель может быть бинарной или не бинарной. Для загрузки модели в формате word2vec в классе KeyedVectors (в котором хранится большинство относящихся к дистрибутивным моделям функций) существует функция load_word2vec_format, а бинарность модели можно указать в аргументе binary (внизу будет пример). Помимо этого, модель можно хранить и в собственном формате gensim, для этого существует класс Word2Vec с функцией load. Поскольку модели бывают разных форматов, то для них написаны разные функции загрузки; бывает полезно учитывать это в своем скрипте. Наш код определяет тип модели по её расширению, но вообще файл с моделью может называться как угодно, жестких ограничений для расширения нет. Давайте скачаем новейшую модель для русского языка, созданную на основе Национального корпуса русского языка (НКРЯ), и загрузим в её в память (поскольку zip-архив с моделью весит почти 500 мегабайт, следующая ячейка выполнится у вас не сразу!). Распаковывать скачанный архив для обычных моделей не нужно, так как его содержимое прочитается при помощи специальной инструкции: import zipfile model_url = 'http://vectors.nlpl.eu/repository/20/180.zip' m = wget.download(model_url) model_file = model_url.split('/')[-1] with zipfile.ZipFile(model_file, 'r') as archive: stream = archive.open('model.bin') model = gensim.models.KeyedVectors.load_word2vec_format(stream, binary=True) Ищем семантическую близость Допустим, нам интересны такие слова (пример для русского языка): words = [’день_NOUN’, ’ночь_NOUN’, ’человек_NOUN’, ’семантика_NOUN’, ’студент_NOUN’, ’студент_ADJ’] Попросим у модели 10 ближайших соседей для каждого слова и коэффициент косинусной близости для каждого: for word in words: # есть ли слово в модели? Может быть, и нет if word in model: print(word) # выдаем 10 ближайших соседей слова: for i in model.most_similar(positive=[word], topn=10): # слово + коэффициент косинусной близости print(i[0], i[1]) print(' ') else: # Увы! print(word + ' is not present in the model') > день_NOUN ночь_NOUN человек_NOUN семантика_NOUN студент_NOUN студент_ADJ is not present in the model Как видим, модель выдала по 10 ближайших семантических «соседей» для каждого слова. Кроме неизвестного ей (и нам тоже) прилагательного студент (’студент_ADJ’). Прилагательное студенческий она, разумеется, знает: model.most_similar(positive=['студенческий_ADJ'], topn=10) [(’университетский_ADJ’, 0.6642225384712219), Теперь научимся находить косинусную близость пары слов. Это легко: print(model.similarity('человек_NOUN', 'обезьяна_NOUN')) > 0.22025344 Более сложные операции над векторами Помимо более простых операций над векторами (нахождение косинусной близости между двумя векторами и ближайших соседей вектора) gensim позволяет выполнять и более сложные операции над несколькими векторами. Так, например, мы можем найти лишнее слово в группе. Лишним словом является то, вектор которого наиболее удален от других векторов слов. print(model.doesnt_match('яблоко_NOUN груша_NOUN виноград_NOUN банан_NOUN лимон_NOUN картофель_NOUN'.split())) > картофель_NOUN Также можно складывать и вычитать вектора нескольких слов. Например, сложив два вектора и вычтя из них третий вектор, мы можем решить своеобразную пропорцию. Подробнее о семантических пропорциях вы можете прочитать в материале Системного Блока. print(model.most_similar(positive=['пицца_NOUN', 'россия_NOUN'], negative=['италия_NOUN'])[0][0]) > гамбургер_NOUN Заключение В этом тьюториале мы постарались разобраться с тем, как работать с семантическими векторными моделями и библиотекой gensim. Теперь вы можете:
Мы надеемся, что этот тьюториал поможет нашим читателям поглубже окунуться в мир дистрибутивной семантики и использовать эти инструменты в своей работе! Код этого тьюториала в формате тетрадки Jupyter ?Елизавета Кузьменко? Источники
Источник: m.vk.com Комментарии: |
|