Slide 1

Slide 1 text

Мамаев Павел Алексеевич Ведущий инженер по разработке. Брокерский бизнес

Slide 2

Slide 2 text

• Сбер • Ведущий инженер по разработке Июнь 2021 – н.в. Виртуальный ассистент. Платежи и Переводы: Переводы клиенту Сбербанка Переводы между своими счетами Оплата сотовой Переводы по СБП … Виртуальный ассистент. Брокерский бизнес: Пополнение брокерского счета Курс акций Московской биржи … • SAS Russia • Аналитик Data Science Декабрь 2018 – Июнь 2021 Мамаев Павел Алексеевич

Slide 3

Slide 3 text

Архитектура Взаимодействие продуктов с платформой Устройство ввода Платформа Нормализация ввода/вывода Маршрутизатор Платформа заказчика 1 Продукт 1 Продукт 2 Продукт 3 Продукт 4 Общий классификатор

Slide 4

Slide 4 text

Архитектура Взаимодействие продуктов с платформой Устройство ввода Платформа Нормализация ввода/вывода Общий классификатор Маршрутизатор Платформа заказчика 1 Продукт 1 Продукт 2 Продукт 3 Продукт 4 Платформа заказчика 2 Продукт 1 Продукт 2 Продукт 3 Платформа заказчика 3 Продукт 1 Продукт 2 Продукт 3 Платформа заказчика 4 Продукт 1 Продукт 2 Продукт 3

Slide 5

Slide 5 text

Архитектура Взаимодействие продуктов с платформой Устройство ввода Платформа Нормализация ввода/вывода Общий классификатор Маршрутизатор Платформа заказчика 1 Продукт 1 Продукт 2 Продукт 3 Продукт 4 Платформа заказчика 2 Продукт 1 Продукт 2 Продукт 3 Платформа заказчика 3 Продукт 1 Продукт 2 Продукт 3 Платформа заказчика 4 Продукт 1 Продукт 2 Продукт 3

Slide 6

Slide 6 text

Архитектура Свой классификатор Платформа заказчика 1 Продукт 1 Продукт 2 Продукт 3 Продукт 4 Что-то умное Продукт 5 Продукт 6 Устройство ввода Платформа Нормализация ввода/вывода Общий классификатор Маршрутизатор Платформа заказчика 2 Продукт 1 Продукт 2 Продукт 3 Платформа заказчика 3 Продукт 1 Продукт 2 Продукт 3 Платформа заказчика 4 Продукт 1 Продукт 2 Продукт 3

Slide 7

Slide 7 text

Почему свой сервис Возможные варианты: • Переиспользовать целевое внутренне решение • Переиспользовать OpenSource решение • Создать свое собственное решение

Slide 8

Slide 8 text

Почему свой сервис Возможные варианты: • Переиспользовать целевое внутренне решение • Переиспользовать OpenSource решение • Создать свое собственное решение Но: • Необходимо затратить большой ресурс времени, а у нас сроки • Возникнуть проблемы с безопасностью, и мы в банкинге • Нужны дополнительные ресурсы

Slide 9

Slide 9 text

Почему свой сервис Возможные варианты: • Переиспользовать целевое внутренне решение • Переиспользовать OpenSource решение • Создать свое собственное решение Но: • Необходимо затратить большой ресурс времени, а у нас сроки • Возникнуть проблемы с безопасностью, и мы в банкинге • Нужны дополнительные ресурсы

Slide 10

Slide 10 text

Когда речь заходит о ресурсах …

Slide 11

Slide 11 text

Почему свой сервис Возможные варианты: • Переиспользовать целевое внутренне решение • Переиспользовать OpenSource решение • Создать свое собственное решение Но: • Необходимо затратить большой ресурс времени (после MVP) • Возникнуть проблемы с безопасностью, и мы в банкинге • Нужны дополнительные ресурсы (в меру возможностей – MVP)

Slide 12

Slide 12 text

MVP кругами Эйлера Быстро Качественно Дешево Долго Дорого Криво Утопия

Slide 13

Slide 13 text

MVP кругами Эйлера Быстро Качественно Дешево Долго Дорого Криво Утопия

Slide 14

Slide 14 text

Какое качество нам нужно? Рассматриваем MVP (CR – целевая метрика) 20% 25% 28% 35% 40% 55% 60% 70% 85% 87% 90% 92% 94% 96% 98% 99% 0% 10% 20% 30% 40% 50% 60% 70% 80% 90% 100%

Slide 15

Slide 15 text

Какое качество нам нужно? Рассматриваем MVP (план минимум) 20% 25% 28% 35% 40% 55% 60% 70% 85% 87% 90% 92% 94% 96% 98% 99% 0% 10% 20% 30% 40% 50% 60% 70% 80% 90% 100%

Slide 16

Slide 16 text

Какое качество нам нужно? Рассматриваем MVP (план максимум) 20% 25% 28% 35% 40% 55% 60% 70% 85% 87% 90% 92% 94% 96% 98% 99% 0% 10% 20% 30% 40% 50% 60% 70% 80% 90% 100%

Slide 17

Slide 17 text

Какое качество нам нужно? Рассматриваем MVP (технический стек) 20% 25% 28% 35% 40% 55% 60% 70% 85% 87% 90% 92% 94% 96% 98% 99% 0% 10% 20% 30% 40% 50% 60% 70% 80% 90% 100% ??? AI, DL, NN, LLM, BERT Внутренне решение

Slide 18

Slide 18 text

Какое качество нам нужно? Рассматриваем MVP (технический стек) 20% 25% 28% 35% 40% 55% 60% 70% 85% 87% 90% 92% 94% 96% 98% 99% 0% 10% 20% 30% 40% 50% 60% 70% 80% 90% 100% PyMorphy2, sklearn AI, DL, NN, LLM, BERT If/else Внутренне решение

Slide 19

Slide 19 text

MVP Классификатора Пример реализации (процесс) Входная фраза Токенизатор (pymorphy2) Извлечение сущностей Условия Пополни мой брокерский счет на 500 рублей Нормализация пополнить мой брокерский счет на 500 rur [ {‘text’: …, ‘lemma’: …, …}, … ] { ’action’: ‘refill’, ‘amount’: { ‘value’: 500.0, ‘curr’: ‘RUB’ }, … } if/elif/elif/.../else

Slide 20

Slide 20 text

MVP Классификатора Пример реализации (процесс) Входная фраза Токенизатор (pymorphy2) Извлечение сущностей Условия Пополни мой брокерский счет на 500 рублей Нормализация пополнить мой брокерский счет на 500 rur [ {‘text’: …, ‘lemma’: …, …}, … ] { ’action’: ‘refill’, ‘amount’: { ‘value’: 500.0, ‘curr’: ‘RUB’ }, … } if/elif/elif/.../else

Slide 21

Slide 21 text

Нормализатор + Токенизатор Пример кода class LocalTextNormalizer: def __init__(self): self.__ready_to_use = False self._morph = None @lazy def morph(self): self._morph = Pymorphy2MorphWrapper() return self._morph def __load_everything(self): self.converter_pipeline = { 'Конверсия юникодовых символов’: …, 'Цифры и буквы отдельно’: …, 'Номера телефонов’: …, 'Номера карт’: …, 'Объединение сумм’: …, 'Символы валют’: …, "Претокенизация математических операций": … } self.tokens_processor = { "Synonyms": …, "Text2Num": …, "CurrencyMoney": …, "Grammemes": self.morph, } self.__ready_to_use = True def load_everything(self): if not self.__ready_to_use: self.__load_everything() def __call__(self, text, message_type="text"): self.load_everything() normalized_text = text convert_processor = self.convert_plan[message_type] for converter_name in convert_processor: converter = self.converter_pipeline[converter_name] normalized_text = converter(normalized_text) token_list = self.get_token_list(normalized_text) self.extend_raw_token_list(token_list) for converter_step in self.processor_pipeline: converter_name = converter_step["name"] converter = self.tokens_processor[converter_name] token_list = converter(token_list) return { "original_text": text, "normalized_text": return_lemmas_only(token_list), "tokenized_elements_list": token_list }

Slide 22

Slide 22 text

Пополни мой брокерский счет на 500 рублей { 'original_text': 'Пополни мой брокерский счет на 500 рублей', 'normalized_text': 'пополнить мой брокерский счет на MONEY_TOKEN .', 'tokenized_elements_list': [ {'text': 'Пополни', 'raw_text': 'Пополни', 'grammem_info’: … , …}, {'text': 'мой', 'raw_text': 'мой', 'grammem_info’: …, …}, {'text': 'брокерский', 'raw_text': 'брокерский', 'grammem_info’: …, …}, {'text': 'счет', 'raw_text': 'счет', 'grammem_info’: …, …}, {'text': 'на', 'raw_text': 'на', 'grammem_info’: …, …}, {'text': '500', 'raw_text': '500', 'lemma': '500', 'original_text': '500', 'token_type': 'NUM_TOKEN’, …}, {'text': 'rur', 'raw_text': 'рублей', 'grammem_info’: …, …}, {'raw_text': '.', 'text': '.', 'lemma': '.', 'token_type': 'SENTENCE_ENDPOINT_TOKEN’, …} ], 'entities': { 'NUM_TOKEN': [{'value': 500, 'adjectival_number': False}], 'MONEY_TOKEN': [{'amount': 500, 'currency': 'rur'}], 'CCY_TOKEN': [{'value': 'rur'}] }, 'original_message_name': 'MESSAGE_FROM_USER', 'human_normalized_text': 'пополнить мой брокерский счет на 500 rur', 'asr_normalized_message': None, 'human_normalized_text_with_anaphora': 'пополнить мой брокерский счет на 500 rur' }, Нормализатор + Токенизатор Пример ответа

Slide 23

Slide 23 text

{ 'original_text': 'Пополни мой брокерский счет на 500 рублей', 'normalized_text': 'пополнить мой брокерский счет на MONEY_TOKEN .', 'tokenized_elements_list': [ {'text': 'Пополни', 'raw_text': 'Пополни', 'grammem_info’: … , …}, {'text': 'мой', 'raw_text': 'мой', 'grammem_info’: …, …}, {'text': 'брокерский', 'raw_text': 'брокерский', 'grammem_info’: …, …}, {'text': 'счет', 'raw_text': 'счет', 'grammem_info’: …, …}, {'text': 'на', 'raw_text': 'на', 'grammem_info’: …, …}, {'text': '500', 'raw_text': '500', 'lemma': '500', 'original_text': '500', 'token_type': 'NUM_TOKEN’, …}, {'text': 'rur', 'raw_text': 'рублей', 'grammem_info’: …, …}, {'raw_text': '.', 'text': '.', 'lemma': '.', 'token_type': 'SENTENCE_ENDPOINT_TOKEN’, …} ], 'entities': { 'NUM_TOKEN': [{'value': 500, 'adjectival_number': False}], 'MONEY_TOKEN': [{'amount': 500, 'currency': 'rur'}], 'CCY_TOKEN': [{'value': 'rur'}] }, 'original_message_name': 'MESSAGE_FROM_USER', 'human_normalized_text': 'пополнить мой брокерский счет на 500 rur', 'asr_normalized_message': None, 'human_normalized_text_with_anaphora': 'пополнить мой брокерский счет на 500 rur' }, Нормализатор + Токенизатор Разбор ответа original_text Оригинальная фраза normalized_text Нормализованный текст tokenized_elements_list Список токенов запроса entities Базовые сущности

Slide 24

Slide 24 text

{ 'text': 'Пополни', 'raw_text': 'Пополни', 'grammem_info': { 'aspect': 'perf', 'mood': 'imp', 'number': 'sing', 'person': '2', 'transitivity': 'tran','verbform': 'fin', 'voice': 'act', 'raw_gram_info': 'aspect=perf|mood=imp|number=sing|person=2|transitivity=tran|verbform=fin|voice=act', 'part_of_speech': 'VERB' }, 'lemma': 'пополнить', 'is_stop_word': False, 'list_of_dependents': [4, 7], 'dependency_type': 'root', 'head': 0 } { 'raw_text': '.', 'text': '.', 'lemma': '.', 'token_type': 'SENTENCE_ENDPOINT_TOKEN', 'token_value': {'value': '.'}, 'list_of_token_types_data': [ { 'token_type': 'SENTENCE_ENDPOINT_TOKEN', 'token_value': {'value': '.'} } ] } Нормализатор + Токенизатор Пример ответа

Slide 25

Slide 25 text

{ 'text': 'Пополни', 'raw_text': 'Пополни', 'grammem_info': { 'aspect': 'perf', 'mood': 'imp', 'number': 'sing', 'person': '2', 'transitivity': 'tran','verbform': 'fin', 'voice': 'act', 'raw_gram_info': 'aspect=perf|mood=imp|number=sing|person=2|transitivity=tran|verbform=fin|voice=act', 'part_of_speech': 'VERB' }, 'lemma': 'пополнить', 'is_stop_word': False, 'list_of_dependents': [4, 7], 'dependency_type': 'root', 'head': 0 } Нормализатор + Токенизатор Разбор ответа text Оригинальный текст raw_text Сырой текст, как пришел grammem_info Информация о составе слова (падеж, род, число и пр.) is_stop_word Стоп-слово

Slide 26

Slide 26 text

{ 'text': '500', 'raw_text': '500', 'lemma': '500', 'original_text': '500', 'token_type': 'NUM_TOKEN', 'token_value': { 'value': 500, 'adjectival_number': False }, 'list_of_token_types_data': [ { 'token_type': 'NUM_TOKEN', 'token_value': { 'value': 500, 'adjectival_number': False } } ], 'grammem_info': { 'numform': 'digit', 'raw_gram_info': 'numform=digit’, 'part_of_speech': 'NUM' }, 'is_stop_word': False, 'list_of_dependents': [5], 'dependency_type': 'nummod', 'head': 4, 'is_beginning_of_composite': True, 'composite_token_type': 'MONEY_TOKEN', 'composite_token_length': 2, 'composite_token_value': { 'amount': 500, 'currency': 'rur' } } Нормализатор + Токенизатор Пример ответа

Slide 27

Slide 27 text

{ 'text': '500', 'raw_text': '500', 'lemma': '500', 'original_text': '500', 'token_type': 'NUM_TOKEN', 'token_value': { 'value': 500, 'adjectival_number': False }, 'list_of_token_types_data': [ { 'token_type': 'NUM_TOKEN', 'token_value': { 'value': 500, 'adjectival_number': False } } ], 'grammem_info': { 'numform': 'digit', 'raw_gram_info': 'numform=digit’, 'part_of_speech': 'NUM' }, 'is_stop_word': False, 'list_of_dependents': [5], 'dependency_type': 'nummod', 'head': 4, 'is_beginning_of_composite': True, 'composite_token_type': 'MONEY_TOKEN', 'composite_token_length': 2, 'composite_token_value': { 'amount': 500, 'currency': 'rur' } } Нормализатор + Токенизатор Разбор ответа token_type Тип токена (число, валюта и пр.) token_value Значение токена composite_token_type Тип составного токена из нескольких таких (сумма и пр.) composite_token_value Значение составного токена

Slide 28

Slide 28 text

Извлечение сущности Пример кода @dataclass class Entity: value: Optional[Any] elements_indexes: Optional[List[int]] name: Optional[str] = None @property def is_composite(self) -> bool: return isinstance(self.elements_indexes, list) and len(self.elements_indexes) > 1 @property def is_empty(self) -> bool: return not (isinstance(self.elements_indexes, list) and len(self.elements_indexes) > 0)

Slide 29

Slide 29 text

class EntityExtractor: name: Optional[str] = None do_remove: bool = True def get_empty_entity(self) -> Entity: return Entity( name=self.name, value=None, elements_indexes=None ) def get_entity(self, elements_list: ElementsList, external_data: Optional[Dict[str, Any]]) -> Optional[Entity]: raise NotImplementedError def extract(self, data: Dict[str, Union[Dict, List, ElementsList]], external_data: Optional[Dict[str, Any]]) -> Tuple[Entity, bool]: entity = self.get_entity(elements_list=data["elements_list"], external_data=external_data) … return entity, self.do_remove Извлечение сущности Пример кода

Slide 30

Slide 30 text

Извлечение сущности Пример кода class AmountExtractor(EntityExtractor): name: str = "amount" do_remove: bool = True optional_lemmas_before_entity: Set[str] = {"на"} hard_return_lemmas: Set[str] = {"минус"} default_currency: str = "rur" @staticmethod def _is_acceptable_element_after_number_token(element: Element) -> bool: pass def get_empty_entity(self) -> Entity: return Entity( name=self.name, value={ "amount": None, "currency": None }, elements_indexes=None ) def get_entity(self, elements_list: ElementsList, external_data: Optional[Dict[str, Any]]) -> Optional[Entity]: … return entity

Slide 31

Slide 31 text

{ 'original_text': 'Пополни мой брокерский счет на 500 рублей', 'normalized_text': 'пополнить мой брокерский счет на MONEY_TOKEN .', 'tokenized_elements_list': [ {'text': 'Пополни', 'raw_text': 'Пополни', 'grammem_info’: … , …}, {'text': 'мой', 'raw_text': 'мой', 'grammem_info’: …, …}, {'text': 'брокерский', 'raw_text': 'брокерский', 'grammem_info’: …, …}, {'text': 'счет', 'raw_text': 'счет', 'grammem_info’: …, …}, {'text': 'на', 'raw_text': 'на', 'grammem_info’: …, …}, {'text': '500', 'raw_text': '500', 'lemma': '500', 'original_text': '500', 'token_type': 'NUM_TOKEN’, …}, {'text': 'rur', 'raw_text': 'рублей', 'grammem_info’: …, …}, {'raw_text': '.', 'text': '.', 'lemma': '.', 'token_type': 'SENTENCE_ENDPOINT_TOKEN’, …} ], 'entities': { 'NUM_TOKEN': [{'value': 500, 'adjectival_number': False}], 'MONEY_TOKEN': [{'amount': 500, 'currency': 'rur'}], 'CCY_TOKEN': [{'value': 'rur'}] }, 'original_message_name': 'MESSAGE_FROM_USER', 'human_normalized_text': 'пополнить мой брокерский счет на 500 rur', 'asr_normalized_message': None, 'human_normalized_text_with_anaphora': 'пополнить мой брокерский счет на 500 rur' }, { "action": "refill", "brokerage_contract": "BA", "amount": { "value": 500.0, "curr": "RUB" }, "card": None } Извлечение сущности Пример ответа

Slide 32

Slide 32 text

{ "action": "refill", "brokerage_contract": "BA", "amount": { "value": 500.0, "curr": "RUB" }, "card": None } Извлечение сущности Разбор ответа action – class ActionExtractor(EntityExtractor): Тип действия, которое хочет сделать клиент: пополнить, снять, спросить и пр. brokerage_contract – class BrokerageContractExtractor(EntityExtractor): Тип брокерского инструмента: Брокерский счет, ИИС, ИнвестКопилка amount – class AmountExtractor(EntityExtractor): Сумма, с которой клиент хочет что-то сделать card – class PaymentToolsExtractor(EntityExtractor): Платежный инструмент, который назвал клиент: платежная система, номер

Slide 33

Slide 33 text

Условия Пример кода is_sbp_transfer = entities.get(e.TransferKeywordExtractor.name) \ or entities.get(e.PaymentKeywordExtractor.name) \ or self.is_main_intents \ or entities.get(e.PTLiteGenericCardFromExtractor.name) \ or entities.get(e.PTLiteGenericCardToExtractor.name) \ or entities.get(e.AccountAmbiguousExtractor.name, {}).get('account', False) \ or entities.get(e.ClassificationRecipientExtractor.name) if entities.get(e.QuickPaymentSystemExtractor.name) or entities.get(e.LightBankExtractor.name) or entities.get( "to_other"): if entities.get(e.CancelRefundExtractor.name): return ['A0532.Cancel_or_refund_payment'] if entities.get(e.DoNotPayExtractor.name): return ['A04.01665.Do_not_pay_and_transfer'] if entities.get(e.SpasiboExtractor.name): return ['A0501.How_to_check_your_SPASIBO_bonus_balance'] if entities.get(e.QuestionMarkerExtractor.name) \ or entities.get(e.LimitsExtractor.name) \ or entities.get(e.PurchaseServiceKeywordExtractor.name) \ or entities.get("is_credit") \ or entities.get(e.TransferAbroadExtractor.name): return ["A04.01589.Quick_payments_system"] if any(entities.get(e.NoComissionExtractor.name).values()): if is_sbp_transfer and self.is_sbp and entities.get("is_without_comission"): if entities.get("to_sber"): return ["p2p"] return ["sbp"] return ["A04.01589.Quick_payments_system"] if is_sbp_transfer and self.is_sbp: if entities.get("to_sber"): return ["p2p"] return ["sbp"] else: return ["A04.01589.Quick_payments_system"] if entities.get(e.NoComissionExtractor.name, {}).get('comission', False) and not entities.get( "is_without_comission", True): return ['A0545.Comission_for_payments_and_transfers'] if entities.get(e.CancelRefundExtractor.name): return ['A0532.Cancel_or_refund_payment'] if entities.get(e.LimitsExtractor.name): return ['A04.01598.How_to_change_daily_limit'] if len(intents) > 1: return full_card

Slide 34

Slide 34 text

Результат Первая итерация • Средний прирост по метрикам продуктов ~10-15% • Клиенты чаще попадают в нужный продукт за счет разветвляющего навыка

Slide 35

Slide 35 text

MVP Классификатора Пример реализации (процесс) Входная фраза Токенизатор (pymorphy2) Извлечение сущностей Пополни мой брокерский счет на 500 рублей Нормализация пополнить мой брокерский счет на 500 rur [ {‘text’: …, ‘lemma’: …, …}, … ] { ’action’: ‘refill’, ‘amount’: { ‘value’: 500.0, ‘curr’: ‘RUB’ }, … }

Slide 36

Slide 36 text

MVP Классификатора Пример реализации (улучшение) Входная фраза Токенизатор (pymorphy2) Извлечение сущностей Бинаризация Модель (sklearn) Пополни мой брокерский счет на 500 рублей Нормализация пополнить мой брокерский счет на 500 rur [ {‘text’: …, ‘lemma’: …, …}, … ] { ’action’: ‘refill’, ‘amount’: { ‘value’: 500.0, ‘curr’: ‘RUB’ }, … } { ’action_refill’: 1, ‘action_is_faq’: 0, ’has_amount’: 1, … } refill_ba

Slide 37

Slide 37 text

Основные поинты • Иногда нужно создавать MVP в меру своих возможностей и ресурсов, если вы не можете потянуть целевое решение в ближайшие сроки • Обрабатывать запросы клиентов можно простыми инструментами, если у вас нет подходящих компетенций. Это вопрос инструментария • Не стесняйтесь писать костыльные решения, если горят сроки

Slide 38

Slide 38 text

No content