Привью
Привью

Исходный код

Аннотация

В данной работе представлен процесс дообучения модели генерации текста на основе архитектуры GPT-2. Целью работы является демонстрация возможностей применения дообученной модели для генерации текстов, соответствующих определённым наименованиям заведений, рубрикам и оценкам, выставленных пользователями. Используя предварительно подготовленный датасет, который включал названия заведений, отношение к определённым рубрикам и рейтинг, мы обучили модель на генерацию описательных текстов, которые могли бы отражать характер и уровень заведений в зависимости от их оценочной характеристики.

В ходе работы особое внимание было уделено настройке параметров генерации текста, таких как температура, top_k и top_p, для улучшения качества и релевантности результатов. Тестирование модели проводилось на мощной вычислительной платформе с использованием видеокарты Tesla A-100 и QUDA, что позволило значительно ускорить процесс обучения до приблизительно 20 минут.

Результаты показали, что дообученная модель способна генерировать тексты, которые соответствуют заданным критериям, хотя иногда и выявлялись случаи потери контекста или странности в текстах.

Цель

Целью данной работы является демонстрация процесса дообучения предобученной модели GPT-2 для генерации релевантного текста на русском языке. В этой работе показан один из примеров, как с помощью дообучения можно адаптировать универсальную модель языковой генерации к конкретной задаче — созданию отзывов о различных заведениях. Эта задача включает в себя анализ и генерацию текста на основе трёх ключевых параметров: названия заведения, его рубрики и оценки, выставленной пользователем.

Несмотря на то, что прямое практическое применение данной модели в бизнес-процессах может показаться ограниченным, ценность проекта заключается в возможности обучения и демонстрации принципов работы с предобученными моделями.

Описание датасета

В качестве датасета в данной работе было принято решение использовать Geo Reviews Dataset 2023, доступный на платформе Яндекс Карт. Этот датасет включает 500,000 уникальных отзывов о различных организациях в России, собранных в период с января по июль 2023 года. Отличительной чертой данного набора данных является его высокое качество: все тексты очищены от персональных данных и не содержат коротких односложных отзывов.

Датасет представлен в формате tskv. Каждая запись содержит следующие поля:

  • Адрес организации (address): Предоставляет точное местоположение организации, что может быть использовано для анализа региональных особенностей отзывов.

  • Название организации (name_ru): Название на русском языке, критически важно для идентификации и анализа конкретного заведения.

  • Список рубрик, к которым относится организация (rubrics): Категоризация организаций помогает модели лучше адаптироваться к контексту и специфике генерируемых отзывов.

  • Оценка пользователя от 0 до 5 (rating): Отражает уровень удовлетворенности пользователя, что напрямую влияет на тональность и содержание сгенерированного текста.

  • Текст отзыва (text): Фактический отзыв, который служит обучающим материалом для модели, позволяя ей изучать различные стили и формы выражения пользовательских мнений.

Выбор данного датасета был обусловлен необходимостью обеспечения модели достаточным и качественным объемом текстовой информации для эффективного дообучения. Использование такого обширного и разнообразного набора данных позволяет достичь высокой степени релевантности и естественности сгенерированных текстов, делая их полезными в различных приложениях, включая системы автоматической обработки отзывов и управления репутацией.

В данной работе мы будем использовать ограниченный объем данных — всего 15,000 строк из общего количества датасета. Это решение было принято с целью ускорения процесса обучения и упрощения экспериментов при дообучении модели.

Для загрузки и подготовки датасета Geo Reviews Dataset 2023 из Hugging Face Datasets, можно следовать приведённому шагу за шагом процессу, описанному ниже. Этот процесс включает загрузку датасета, его кэширование и преобразование в формат DataFrame для последующего анализа и обработки.

Загрузка датасета

  1. Импорт необходимых библиотек:
    Для работы с датасетами и DataFrame необходимо импортировать соответствующие модули из библиотек datasets и pandas.

    from datasets import load_dataset
    import pandas as pd
    
  2. Загрузка датасета:
    Используя функцию load_dataset библиотеки datasets, загрузите датасет, указав идентификатор d0rj/geo-reviews-dataset-2023. Это действие загрузит датасет в локальное хранилище. Параметр cache_dir позволяет указать каталог для кэширования данных, что ускоряет последующие загрузки и снижает нагрузку на сеть.

    from pathlib import Path
    
     DATA_PATH = Path('data/finetune_gpt/')
     DATA_PATH.mkdir(parents=True, exist_ok=True)
    
    dataset = load_dataset("d0rj/geo-reviews-dataset-2023", cache_dir=DATA_PATH / 'model_cache')
    

    Убедитесь, что указали правильный путь к директории для хранения кэша, так как это повлияет на производительность загрузки данных.

  3. Преобразование данных в DataFrame:
    После успешной загрузки датасета, содержимое подмножества train (тренировочные данные) преобразуется в DataFrame с помощью библиотеки pandas. Это упрощает обработку данных, так как DataFrame предоставляет множество удобных функций для манипуляции и анализа данных.

    data_df = pd.DataFrame(dataset['train'])
    

address

name_ru

rating

rubrics

text

0

Екатеринбург, ул. Московская / ул. Волгоградск...

Московский квартал

3

Жилой комплекс

Московский квартал 2.\nШумно : летом по ночам ...

1

Московская область, Электросталь, проспект Лен...

Продукты Ермолино

5

Магазин продуктов;Продукты глубокой заморозки;...

Замечательная сеть магазинов в общем, хороший ...

2

Краснодар, Прикубанский внутригородской округ,...

LimeFit

1

Фитнес-клуб

Не знаю смутят ли кого-то данные правила, но я...

3

Санкт-Петербург, проспект Энгельса, 111, корп. 1

Snow-Express

4

Пункт проката;Прокат велосипедов;Сапсёрфинг

Хорошие условия аренды. \nДружелюбный персонал...

4

Тверь, Волоколамский проспект, 39

Студия Beauty Brow

5

Салон красоты;Визажисты, стилисты;Салон бровей...

Топ мастер Ангелина топ во всех смыслах ) Немн...

Для подготовки датасета к процессу дообучения модели GPT-2 был выполнен следующий предпроцессинг данных:

  1. Удаление пропущенных значений: В первую очередь, из датасета удаляются все записи, у которых отсутствуют данные в ключевых столбцах — text, name_ru, и rating. Это обеспечивает полноту информации для каждого случая и исключает потенциальные проблемы при обучении, связанные с отсутствием важных данных.

    work_data = data_df.dropna(subset=['text', 'name_ru', 'rating'])
    
  2. Удаление дубликатов: Далее из датасета удаляются дубликаты по столбцу text. Это предотвращает избыточное представление одинаковых отзывов, которое могло бы привести к переобучению модели на определённых фразах или выражениях.

    work_data = work_data.drop_duplicates(subset=['text']).reset_index(drop=True)
    
  3. Замена переносов строк: В текстах отзывов заменяются символы переноса строки (\\n) на пробелы. Это делает тексты более подходящими для обработки моделью, так как избавляется от лишних разрывов и делает текст более единообразным.

    work_data['text'] = work_data['text'].str.replace('\\n', ' ')
    
  4. Ограничение объема данных: Наконец, выбирается первые 15,000 записей из предобработанного датасета для ускорения процесса обучения. Это делает набор данных более управляемым и позволяет сосредоточиться на быстрой итерации и экспериментировании с моделью.

    work_data = work_data[:15000]
    

Описание модели

В данной работе используется модель ai-forever/rugpt3small_based_on_gpt2, адаптированная версия предобученной модели GPT-2, специально дообученная для работы с текстами на русском языке. Эта модель представляет собой итерацию известной серии моделей GPT (Generative Pre-trained Transformer), разработанных OpenAI. Основная особенность GPT моделей — это их способность генерировать текст, продолжая предложенный текстовый ввод. Это достигается благодаря архитектуре трансформера, которая позволяет модели учитывать контекст всего введенного текста.

Процесс дообучения

Дообучение модели на специфическом датасете позволяет адаптировать общие языковые модели к конкретным задачам. В нашем случае, дообучение проводилось на основе отзывов о различных заведениях в России. Для дообучения использовались не только тексты самих отзывов, но и сопутствующие данные, такие как название заведения, его рубрики и пользовательская оценка.

import pandas as pd
from transformers import GPT2LMHeadModel, GPT2Tokenizer, TextDataset, DataCollatorForLanguageModeling, Trainer, TrainingArguments
from pathlib import Path

class FineTuner:
    def __init__(self, 
                 model_name='ai-forever/rugpt3small_based_on_gpt2', 
                 cache_dir='model_cache',
                 data_path=DATA_PATH):
        self.data_path = Path(data_path)
        
        # Инициализация токенизатора и модели
        self.tokenizer = GPT2Tokenizer.from_pretrained(model_name, cache_dir=str(self.data_path / cache_dir))
        self.model = GPT2LMHeadModel.from_pretrained(model_name, cache_dir=str(self.data_path / cache_dir))

    def prepare_data(self, df):
        """
        Подготовка данных для обучения
        """
        # Объединение типа проблемы и исходного текста в одну строку входных данных
        df['input'] = df.apply(
            lambda row: f"<name_ru> {row['name_ru']} <rubrics> {row['rubrics']} <rating> {row['rating']} {self.tokenizer.eos_token}", axis=1
            )
        
        # Добавление к целевому тексту токена окончания строки
        df['output'] = df.apply(lambda row: f" <text> {row['text']} {self.tokenizer.eos_token}", axis=1)
        
        # Подготовка пути для сохранения данных
        dataset_path = self.data_path / 'train_dataset.txt'
        # Запись данных в файл
        with dataset_path.open('w', encoding='utf-8') as file:
            for input_text, target_text in zip(df['input'], df['output']):
                file.write(input_text + ' ' + target_text + '\n')
        return dataset_path

    def fine_tune(self, 
                  dataset_path, 
                  output_name='fine_tuned_model', 
                  num_train_epochs=4, 
                  per_device_train_batch_size=4, 
                  learning_rate=5e-5, 
                  save_steps=10_000):
        """
        Дообучение модели на заданном датасете.
        """
        train_dataset = TextDataset(
            tokenizer=self.tokenizer,
            file_path=str(dataset_path),
            block_size=256
        )

        data_collator = DataCollatorForLanguageModeling(
            tokenizer=self.tokenizer, mlm=False
        )

        training_args = TrainingArguments(
            output_dir=str(self.data_path / output_name),
            overwrite_output_dir=True,
            num_train_epochs=num_train_epochs,
            per_device_train_batch_size=per_device_train_batch_size,
            save_steps=save_steps,
            learning_rate=learning_rate,
            save_total_limit=2,
            logging_dir=str(self.data_path / 'logs'),
        )

        trainer = Trainer(
            model=self.model,
            args=training_args,
            data_collator=data_collator,
            train_dataset=train_dataset,
        )

        trainer.train()
        # Сохранение обученной модели и токенизатора
        self.model.save_pretrained(str(self.data_path / output_name))
        self.tokenizer.save_pretrained(str(self.data_path / output_name))

Методы генерации текста

Класс TextGenerator, который используется для генерации текста, позволяет настраивать процесс с помощью различных параметров:

  • max_length определяет максимальную длину генерируемого текста.

  • num_return_sequences задает количество вариантов текста, которое должно быть сгенерировано.

  • temperature контролирует разнообразие вывода, с более высокими значениями, создавая более случайные и разнообразные тексты.

  • top_k и top_p параметры используются для управления выборкой слов. top_k ограничивает выборку K наиболее вероятными словами, в то время как top_p применяет так называемый nucleus sampling, позволяя выбирать слова с наибольшей кумулятивной вероятностью, что обеспечивает более сбалансированный и естественный текст.

  • do_sample включает случайную выборку слов для увеличения разнообразия генерируемого текста.

from transformers import GPT2LMHeadModel, GPT2Tokenizer
from pathlib import Path

class TextGenerator:
    def __init__(self, model_name='fine_tuned_model', data_path=DATA_PATH):
        """
        Инициализация модели и токенизатора.
        Загружаем модель и токенизатор из указанного пути.
        """
        model_path = Path(data_path) / model_name
        self.tokenizer = GPT2Tokenizer.from_pretrained(str(model_path))
        self.model = GPT2LMHeadModel.from_pretrained(str(model_path))
        self.model.eval()

    def generate_text(self, 
                    name_ru: str, 
                    rubrics: str, 
                    rating: int,
                    max_length=100, 
                    num_return_sequences=1, 
                    temperature=1.0, 
                    top_k=0, 
                    top_p=1.0, 
                    do_sample=False):
        """
        Генерация текста на основе заданного начального текста (prompt) и параметров.
        
        Параметры:
        - name_ru: Название организации.
        - rubrics: Список рубрик, к которым относится организация.
        - rating: Оценка пользователя.
        - max_length: Максимальная длина сгенерированного текста.
        - num_return_sequences: Количество возвращаемых последовательностей.
        - temperature: Контролирует разнообразие вывода.
        - top_k: Если больше 0, ограничивает количество слов для выборки только k наиболее вероятными словами.
        - top_p: Если меньше 1.0, применяется nucleus sampling.
        - do_sample: Если True, включает случайную выборку для увеличения разнообразия.
        """
        # Формирование prompt
        prompt_text = f"<name_ru> {name_ru} <rubrics> {rubrics} <rating> {rating} {self.tokenizer.eos_token} <text> "
        
        # Кодирование текста в формате, пригодном для модели
        encoded_input = self.tokenizer.encode(prompt_text, return_tensors='pt')
        
        # Генерация текстов
        outputs = self.model.generate(
            encoded_input,
            max_length=max_length + len(encoded_input[0]),
            num_return_sequences=num_return_sequences,
            temperature=temperature,
            top_k=top_k,
            top_p=top_p,
            do_sample=do_sample,
            no_repeat_ngram_size=2
        )
        
        # Декодирование результатов
        all_texts = [self.tokenizer.decode(output, skip_special_tokens=True) for output in outputs]
        
        # Удаление входных данных из текстов
        prompt_length = len(self.tokenizer.decode(encoded_input[0], skip_special_tokens=True))
        trimmed_texts = [text[prompt_length:] for text in all_texts]
        
        # Возврат результатов в виде словаря
        return {
            "full_texts": all_texts,
            "generated_texts": trimmed_texts
        }
Подробнее о параметрах вариативности генерации текста

Параметр do_sample=True в методе generate() моделей трансформеров в библиотеке Transformers от Hugging Face контролирует, будет ли в процессе генерации использоваться случайная выборка слов. Этот параметр важен для управления стилем и разнообразием генерируемого текста.

Подробнее о параметре do_sample:

Когда do_sample установлен в False, генерация текста происходит путём выбора наиболее вероятного следующего слова в каждом шаге, что делает вывод детерминированным. Этот метод полезен, когда необходимо получить наиболее точный и когерентный текст.

Однако, если установить do_sample=True, модель начнёт выбирать следующие слова из вероятностного распределения, созданного на основе логитов модели. Это вносит элемент случайности в процесс генерации, что делает сгенерированный текст менее предсказуемым и более разнообразным.

Как работает do_sample=True:

  1. Без do_sample: Выборка слова происходит строго на основе наивысшего логита (или после преобразования логитов в вероятности, наибольшей вероятности).

  2. С do_sample:

    • Модель вычисляет вероятности каждого возможного следующего слова.

    • Следующее слово выбирается случайно, но с вероятностями, взвешенными по их расчётным вероятностям.

Взаимодействие do_sample с другими параметрами:

  • temperature: Изменяет распределение вероятностей, управляя "резкостью" или "плоскостью" распределения. Более высокая температура делает распределение более равномерным, тем самым увеличивая случайность.

  • top_k: Ограничивает выборку кандидатов, которые рассматриваются, до k наиболее вероятных слов.

  • top_p (nucleus sampling): Вместо фиксированного количества слов ограничивает выборку до набора слов, суммарная вероятность которых превышает заданный порог p.

Как работают temperature, top_k и top_p вместе:

  1. Применение temperature:

    • Независимо от других параметров, temperature модифицирует распределение вероятностей слов перед тем, как производится какая-либо выборка.

    • Если temperature равна 1, распределение не изменяется. Значение меньше 1 делает распределение более "пиковым" (предпочтение более вероятным словам), а значение больше 1 — более "равномерным" (повышение шансов менее вероятных слов).

  2. Применение top_k:

    • После применения temperature, если top_k больше 0, происходит фильтрация списка возможных слов до k наиболее вероятных. Это ограничивает выборку только самыми вероятными словами после применения temperature.

  3. Применение top_p (nucleus sampling):

    • top_p также применяется после модификации распределения через temperature. Он фильтрует наименьшее количество слов, суммарная вероятность которых превышает p. Это создает "ядерное" распределение, включающее только наиболее вероятные слова до достижения порога p.

Взаимодействие при одновременном использовании
Когда top_k и top_p используются одновременно, результат будет соответствовать наиболее строгому из ограничений. Например:

  • Если top_k=10 и top_p=0.5, и первые 10 слов покрывают 70% вероятностей, то применяется top_k, ограничивая выборку 10 словами.

  • Если top_k=50 и top_p=0.5, и первые 10 слов уже покрывают 55% вероятностей, то применяется top_p, ограничивая выборку первыми 10 словами, даже если top_k позволяет более широкий выбор.

Обучение модели:

Обучение модели проводилось на мощной машине с использованием видеокарты Tesla A-100, а также с поддержкой QUDA, что значительно ускорило процесс обучения. При этом длительность обучения составила приблизительно 20 минут.

Обучение на машинах без поддержки QUDA или аналогичных технологий ускорения, а также на более слабых видеокартах может занять значительно больше времени. Если вы рассматриваете возможность локального запуска, будьте готовы к тому, что длительность обучения может значительно увеличиться, что потребует больше ресурсов и времени.

Также рекомендуется рассмотреть возможность использования облачных сервисов с GPU для обучения модели, если локальные ресурсы ограничены или если требуется ускорить процесс обучения. Это может стать выгодным решением для ускорения разработки и тестирования моделей.

finetuner = FineTuner()
dataset_path = finetuner.prepare_data(work_data)
finetuner.fine_tune(dataset_path, output_name='fine_tuned_model_gpt_2')

Результаты

Для проверки эффективности и качества генерации текста мы использовали реальные данные из нашего датасета, включая уникальные наименования и рубрики. Выборка уникальных наименований и рубрик была выполнена следующим образом:

unique_name_ru = work_data['name_ru'].unique().tolist()
unique_rubrics = work_data['rubrics'].unique().tolist()
unique_name_ru[:10]
['Московский квартал',
 'Продукты Ермолино',
 'LimeFit',
 'Snow-Express',
 'Студия Beauty Brow',
 'Tele2',
 'У тещи',
 'Smoking Park',
 'Jinju',
 'Kari ГИПЕР']
unique_rubrics[:10]
['Жилой комплекс',
 'Магазин продуктов;Продукты глубокой заморозки;Магазин мяса, колбас',
 'Фитнес-клуб',
 'Пункт проката;Прокат велосипедов;Сапсёрфинг',
 'Салон красоты;Визажисты, стилисты;Салон бровей и ресниц',
 'Оператор сотовой связи;Интернет-провайдер',
 'Кафе',
 'Вейп-шоп;Магазин табака и курительных принадлежностей',
 'Кафе;Кофейня',
 'Магазин обуви;Ювелирный магазин;Детские игрушки и игры']

Для генерации текстов был использован экземпляр класса TextGenerator, инициализированный с дообученной моделью GPT-2. Параметры генерации были выбраны эмпирически для достижения оптимального баланса между разнообразием и релевантностью сгенерированных текстов:

name_ru = unique_name_ru[1]
rubrics = unique_rubrics[1]
rating = 1

generator = TextGenerator(
    model_name='fine_tuned_model_gpt_2',
    data_path=DATA_PATH
)
generated_texts = generator.generate_text(
    name_ru=name_ru,
    rubrics=rubrics,
    rating=rating,
    max_length=100,
    num_return_sequences=3,
    do_sample=True,
    temperature=0.95,  # Слегка уменьшаем уверенность
    top_k=10,         # Уменьшаем количество рассматриваемых верхних k слов
    top_p=0.95        # Уменьшаем "ядерность" распределения
)
for i, text in enumerate(generated_texts['generated_texts']):
    print(f"Generated Text {i+1}: {text}")

Вывод генераций с рейтингом 5:

Generated Text 1:  В магазине есть все необходимое для приготовления шашлыка, салатов и других блюд на углях.  Очень вкусная выпечка, есть выпечки на развес, которые можно взять с собой, так же можно приобрести кулинарные книги. Есть магазинчик, где можно купить сухофрукты. Продавцы вежливые, доброжелательные, очень приятно общаться! ??  Спасибо вам!!!  И не забудьте купить воду?  

Generated Text 2:  Очень удобно расположен магазин.  Персонал очень приветливый и вежливый.  

Generated Text 3:  В магазине чисто, персонал приветливый и внимательный. Есть доставка и самовывоз.  Цены приемлемые, всегда свежие продукты. Ассортимент всегда широкий. Всегда есть акции и акции. Спасибо за работу. Всем рекомендую. Рекомендую.  

Вывод генераций с рейтингом 1:

Generated Text 1:  На кассе мне сказали, что есть только один продукт, который есть у них в наличии.  Это не так.   В магазине нет ни одного продукта, на который я бы обратила внимание. А если бы он появился, то я уже была бы поражена, как мне повезло.   

Generated Text 2:  На прилавках лежат продукты питания и товары для дома.  Продавцы грубиянки, вечно ходят с недовольным лицом. Я бы сказала что они не знают что это такое.  

Generated Text 3:  1. Отвратительно отношение к покупателю. 2. Магазины, в которых отсутствует ассортимент товара. 3. Качество товаров оставляет желать лучшего. 4. Продавцы, которые работают в магазине, не знают свою работу.  4. Магазинам, у которых в собственности находится магазин, нужно провести инвентаризацию. Это сделано. 5. На полках нет ни одного товара, который был бы в наличии. Если бы я не знала что мне нужен товар, я бы его не купила.  

Результаты генерации были проанализированы визуально, без применения количественных метрик валидации. В рамках данного исследования наша цель заключалась не в создании точной модели, а в демонстрации возможностей дообучения модели на конкретной задаче. Анализ результатов позволил сделать следующие наблюдения:

  • Тексты с оценкой "5" отражает положительный контекст, соответствуя высокой оценке.

  • Тексты с оценкой "1" отражает негативный контекст, что соответствует низкой оценке.

  • В некоторых случаях тексты казались несколько отрывочными или странно сформулированными, что указывает на потерю смысла или кажущуюся несвязность предложений.

Полученные результаты демонстрируют успешное дообучение модели. Хотя имеются замечания к качеству некоторых генерированных текстов, в целом, модель показала способность генерировать содержательно разнообразные ответы, соответствующие заданной оценке и контексту. Этот эксперимент подчеркивает потенциал использования дообученных языковых моделей для специфических задач генерации текста.

Заключение

Мы относительно упрощённо выполнили генерацию текста, выбрав уже имеющиеся названия и категории из датасета. В качестве следующего шага стоит проверить генерацию текста для случайно выбранных названий и категорий. Однако, может возникнуть проблема связанная с ограниченностью датасета, включающего всего 15,000 записей. Малый размер датасета может не обеспечить достаточное разнообразие для генерации качественных и релевантных текстов для редких или уникальных названий и категорий.

Для улучшения качества и релевантности генерированных ответов возможно потребуется тестирование различных гиперпараметров, таких как температура, top_k и top_p в процессе генерации. Также, увеличение количества данных и продолжительности обучения (количество эпох) может значительно улучшить результаты. Эксперименты с различными настройками обучения и генерации могут помочь определить оптимальные параметры для достижения наилучшей производительности модели в специфических задачах генерации текста.

Цитирование

@misc{pisarenko2024fine-tuning,
  author = {Pisarenko, Anton},
  title = {Fine-tuning the {GPT-2} model for generating descriptions of establishments based on type and rating},
  year = {2024},
  howpublished = {\url{https://github.com/AntonSHBK/fine_tune_gpt_model}},
}

Контакты

Комментарии (3)


  1. anton_shbk Автор
    24.06.2024 13:12

    Область применения может быть разнообразной в зависимости от решаемых задач. Такие подходы и модели можно использовать во благо, например автоматизировать бизнес-процессы. Прогресс развивается семимильными шагами, нужно успевать за ним.


  1. Tanelxen
    24.06.2024 13:12

    Сама задача интересная, но вот область применения... Скам какой-то. И без того накруток отзывов полно, а тут еще и новый инструмент для автоматизации.


  1. aarmaageedoon
    24.06.2024 13:12

    В статье очень не хватает рассуждений на тему "А почему бы не взять ChatGPT/Saiga?". Оно с одной стороны понятно: дообучать и инференсить gpt2 гораздо проще, чем БЯМ. Однако, если выхлоп слабее, чем от БЯМ, то почему не они? В общем, было бы круто, если такое сравнение показали.