Игра Яндекс Практикума
Игра Яндекс Практикума
Игра Яндекс Практикума

Тестим Марка: как происходит генерация новостей

Марк — это языковая модель, которая умеет придумывать новости без помощи человека. Рассказываем, как развернуть нейросеть самому.

2К открытий2К показов

Сейчас Марк — это генеративная языковая модель, которая умеет придумывать новостные статьи без помощи человека. Марк может писать свободно, опираясь на свой опыт и выбирая каждое слово самостоятельно. Либо же мы можем помочь ему с темой новости и дать начало заголовка или заголовок, тогда Марк продолжит нашу мысль.

Что такое языковые модели?

Говоря просто, языковые модели — это когда мы пытаемся найти закономерности и правильные речевые последовательности в человеческом языке, а потом на основе этого пробуем создать “язык” независимо от самого человека.

Как это можно сделать?

Можно оценить вероятность появления какого-то словосочетания в языке. Иными словами, каждый раз мы отвечаем себе на вопрос: корректно ли использовать такую последовательность слов (формулировку) в русском языке? Если это допустимо, то вероятность употребления такого словосочетания достаточно велика, а значит мы используем языковую конструкцию, которую могут понять остальные, знающие русский.

В противном случае появляются конструкции вроде “лето наступила”, по правилам нашего языка составленные некорректно, а значит, вероятность появления такой конструкции в речи довольно мала.

Главная идея: правильные языковые конструкции могут быть созданы не только человеком, но и искусственным интеллектом (или роботом). Соответственно между хорошей языковой моделью (которая хорошо понимает правила нашего языка) и человеком можно наладить продуктивную взаимную коммуникацию. И вот это уже идея на миллион!

Ты сам можешь генерировать новости!

Начнём с простого: сейчас мы покажем тебе, как генерировать новости. Целиком код для генерации лежит на COLAB — можно запустить всё и посмотреть, какую новость Марк сгенерирует для тебя!

Нашу обученную модель мы выложили на Hugging Face — технологический стартап с активным open source сообществом, который способствовал внедрению моделей на основе трансформеров (к примеру, предшественников и самой ChatGPT) во всем мире.

Благодаря стартапу к большому количеству тяжелых (и умных) моделей можно подключиться просто по API и самому творить магию, не имея огромных вычислительных машин.

Например, вот так мы получим доступ к нашей модели:

			from transformers import GPT2LMHeadModel, GPT2Tokenizer

path = 'AnyaSchen/news_gpt-3'
tokenizer = GPT2Tokenizer.from_pretrained(path)
model = GPT2LMHeadModel.from_pretrained(path).to(DEVICE)
		

Возможно, нужно будет установить библиотеку transformers. Например, вот так:

			!pip install transformers
		

Ну, а новости ты можешь сгенерировать вот этим кодом:

			input = input('Введи начало заголовка или просто нажми Enter:')
input = input if len(input) > 0 else tokenizer.bos_token #токен начала предложения
input_ids = tokenizer.encode(input, return_tensors="pt").to(DEVICE)

out = model.generate(input_ids,
                     do_sample = True,
                     num_beams = 3,
                     temperature = 2.0,
                     top_p = 0.9,
                     max_length = 200,
                     **stopping_criteria = StoppingCriteriaList([stop_criteria])**,
                     eos_token_id = tokenizer.eos_token_id,
                     bos_token_id = tokenizer.bos_token_id).to(DEVICE)
print(tokenizer.batch_decode(out, skip_special_tokens=False)[0])
		

Что тут происходит? Сейчас расскажем.

Шаг 1: Мы задаём начало заголовка нашей новости в переменную input. Если вдруг нам нечего дать в начало, и мы нажимаем Enter, то подставляется специальный символ, обозначающий для нашей модели начало предложения (мол, генерируй давай с самого начала).

			input = input('Введи начало заголовка или просто нажми Enter:')
input = input if input else tokenizer.bos_token #токен начала предложения
		

Шаг 2: Дальше мы берём наш входной текст и говорим (дословно): токенайзер, тебе нужно заэнкодить наше начало.

			input_ids = tokenizer.encode(input, return_tensors="pt").to(DEVICE)
		

По факту мы берем наш кусочек заголовка, пилим его на слова (это делает токенайзер) и преобразуем каждое слово в большие наборы чисел (это и делает энкодер), к которым можно получить доступ по уникальным, назначенным им номерам.

Зачем так?

Цель: Мы пытаемся научить компьютер “понимать” наш язык.

Компьютер хранит наши буковки числами, например, в виде ASCII-кода, где каждому символу присваивается уникальный номер (1, 2, 3…), а потом оно конвертируется в нолики и единички. Так вот слова в такой системе просто набор чисел, например:

котик → 1000011101010000111110100010000101000011100010000111010 (попробуй сам).

Но в нашем языке у каждого слова есть смысловой окрас и значение: для нас грусть и радость имеют противоположные смыслы, а в целом — это чувства.
Компьютер должен ощущать эту разницу между семантикой слов, чтобы хорошо “говорить” на нашем языке, но для него это всё равно набор ноликов и единичек, не имеющих смысла. Например, вот тебе послание:

10000011010100010000001000011000010001000001100001100001000011001010001000111100001110001000011101010110010000010001000010100010010111000001000100000110001000011100001111111000011010110001000000100001

Понятно, что тут ничего не понятно: ни сколько тут слов, ни что они собой значат. Ты можешь прочитать эти 1 и 0, но в чем смысл, это просто цифры, да?)
Ещё одно послание (как у тебя дела с китайским?) — 你會成功的! Но ты не поймешь, пока кто-то не переведёт это на понятный тебе язык.

Энкодер делает именно это: переводит с человеческого на понятный компьютеру.

Проблема: как объяснить компьютеру, что это за слова и что они значат?

Умные ученые нашли решение:

  1. разбиваем предложения на кусочки, из которых могут быть составлены родственные слова (именно это и делает токенайзер в нашей модели)
  2. превращаем каждый кусочек в вектор из чисел, который будет отвечать за семантику.
  3. Каждому кусочку присваивается уникальный номер, и энкодер нам возвращает именно его. Почему так? Чтобы общаться, мы используем слова в определенной последовательности и не сообщаем собеседнику все трактовки каждого сказанного нами слова, надеясь, что наше понимание слов идентично. Так и с компьютером: наборы уникальных номеров — это его слова. Например, слово “нейросеть”, переведенное на язык нашей модели, выглядит так:

Почему кусочки, а не слова? Потому что кот и котик — это про одно и тоже, но настроение меняется)

Можно сравнить с настройкой робота из фильма Интерстеллар: 70% честности, 80% позитивности и т.д. Мы задаем какие-то параметры, по которым можно “почувствовать” каждое слово, но не нам, а компьютеру. В реальности, конечно, все сильно сложнее… Но идея понятна, да?

Шаг 3: Генерация на понятном компьютеру языке

			out = model.generate(input_ids,
                     do_sample = True,
                     num_beams = 3,
                     temperature = 2.0,
                     top_p = 0.9,
                     max_length = 200,
                     **stopping_criteria = StoppingCriteriaList([stop_criteria])**,
                     eos_token_id = tokenizer.eos_token_id,
                     bos_token_id = tokenizer.bos_token_id).to(DEVICE)
		

Мы научили языковую модель генерировать новости (как? Потом расскажем), но создаёт она их, подбирая семантически каждый кусочек по своим выученным закономерностям и правилам. Чтобы новости получались разнообразнее и умнее, чем тексты у T9, мы использовали самые разные параметры генерации: температура, количество звеньев и т.д. Если будет интересно — расскажем, как они влияют на процесс (можешь поменять значения у себя в коде — увидишь, как всё ломается).

Из интересного тут — критерий остановки (код ниже тоже тебе нужен).

			class KeywordsStoppingCriteria(StoppingCriteria):
    def __init__(self, keywords_ids:list):
      self.keywords = keywords_ids

    def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor, **kwargs) -> bool:
      if input_ids[0][-1] in self.keywords:
        print(input_ids)
        return True
      return False

stop_criteria = KeywordsStoppingCriteria(tokenizer.encode(tokenizer.eos_token, return_tensors="pt").to(DEVICE))
		

Что на выходе генерации? Чтобы модель не выдавала бесконечные потоки слов, мы ограничили длину предложения в 200 слов (смотри параметры генерации). Но это не всё, ей нужно помочь с окончанием процесса в момент, когда она сгенерировала свою логическую мысль и напечатала специальный токен конца предложения <eos>

После выполнения этого шага генерации в переменной out будут лежать числа — уникальные значения кусочков, которые наша языковая модель нагенерировал (aka новости, только на языке, понятном компьютеру).

Шаг 4: Перевести это обратно на человеческий

			print(tokenizer.batch_decode(out, skip_special_tokens=False)[0])
		

Мы перевели человеческий в машинный, машина нагененерировала нам новости на своем языке, а теперь что? Правильно, машинный на человеческий, пожалуйста, чтобы нам понять. → Это и делает декодер.

И вот нам выводится новость, которая поделена на специальные токены: начала, отделения заголовка от описания и конца предложения. Чтобы эти токены нормально сработали, нужно вначале их показать токенайзеру (смотри, у нас и такие специальные “слова” есть).

			SPECIAL_TOKENS = {'bos_token':'<bos>','eos_token' :'<eos>', 'pad_token':'<pad>', 'sep_token': '<sep>'}
tokenizer.add_special_tokens(SPECIAL_TOKENS)
		

Целиком код для генерации можно найти тут: COLAB.

Что же ты сегодня узнал:

  • Что такое языковая модель.
  • Как происходит генерация кода в языковых моделях.
  • Энкодер переводит с человеческого на машинный, а декодер — наоборот.
  • Токенайзер пилит слова на кусочки, а модель — обрабатывает семантику этих кусочков.
Следите за новыми постами
Следите за новыми постами по любимым темам
2К открытий2К показов