AresEkb commited on
Commit
bea4046
·
1 Parent(s): 6a0c1de

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +146 -0
app.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+
4
+ import pandas as pd
5
+ import torch
6
+
7
+ from datasets import Dataset
8
+ from sentence_transformers import util
9
+ from transformers import AutoTokenizer, AutoModel
10
+
11
+ import gradio as gr
12
+
13
+ device = torch.device('cpu')
14
+
15
+ # Helpers
16
+ def get_model_size(model):
17
+ param_size = 0
18
+ for param in model.parameters():
19
+ param_size += param.nelement() * param.element_size()
20
+ buffer_size = 0
21
+ for buffer in model.buffers():
22
+ buffer_size += buffer.nelement() * buffer.element_size()
23
+ return (param_size + buffer_size) / 1024**2
24
+
25
+ # Load model
26
+ checkpoint = 'sberbank-ai/sbert_large_mt_nlu_ru'
27
+ tokenizer = AutoTokenizer.from_pretrained(checkpoint)
28
+ model = AutoModel.from_pretrained(checkpoint)
29
+ model = model.to(device)
30
+
31
+ def mean_pooling(token_embeddings, attention_mask):
32
+ input_mask_expanded = attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float()
33
+ sum_embeddings = torch.sum(token_embeddings * input_mask_expanded, 1)
34
+ sum_mask = torch.clamp(input_mask_expanded.sum(1), min=1e-9)
35
+ return sum_embeddings / sum_mask
36
+
37
+ def get_embeddings(input):
38
+ encoded_input = tokenizer(input, padding=True, truncation=True, max_length=50, return_tensors='pt').to(device)
39
+ with torch.no_grad():
40
+ model_output = model(**encoded_input)
41
+ return mean_pooling(model_output[0], encoded_input['attention_mask']).cpu().numpy()
42
+
43
+ # Load data
44
+ ds_name = 'AresEkb/prof_standards_sbert_large_mt_nlu_ru'
45
+ domains_ds = load_dataset(ds_name, 'domains', split='train').add_faiss_index(column='embeddings')
46
+ generalized_functions_ds = load_dataset(ds_name, 'generalized_functions', split='train').add_faiss_index(column='embeddings')
47
+ jobs_ds = load_dataset(ds_name, 'jobs', split='train').add_faiss_index(column='embeddings')
48
+ particular_functions_ds = load_dataset(ds_name, 'particular_functions', split='train').add_faiss_index(column='embeddings')
49
+ actions_ds = load_dataset(ds_name, 'actions', split='train').add_faiss_index(column='embeddings')
50
+ skills_ds = load_dataset(ds_name, 'skills', split='train').add_faiss_index(column='embeddings')
51
+ knowledges_ds = load_dataset(ds_name, 'knowledges', split='train').add_faiss_index(column='embeddings')
52
+
53
+ entity_kinds = {
54
+ 'Предметная область': domains_ds,
55
+ 'Процесс': generalized_functions_ds,
56
+ 'Подпроцесс': particular_functions_ds,
57
+ 'Функция': actions_ds,
58
+ 'Должность': jobs_ds,
59
+ 'Навык': skills_ds,
60
+ 'Знание': knowledges_ds,
61
+ }
62
+
63
+ # Main logic
64
+ def calc(entity_kind, entity1_name, entity2_name, entity3_name, entity_count):
65
+ start_time = time.perf_counter_ns()
66
+
67
+ if not entity1_name or not entity2_name and not entity3_name:
68
+ return [None, 0]
69
+
70
+ embedding1 = get_embeddings(entity1_name)
71
+ embedding2 = get_embeddings(entity2_name)
72
+ embedding3 = get_embeddings(entity3_name)
73
+
74
+ if entity2_name and entity3_name:
75
+ embedding = embedding1 - embedding2 + embedding3
76
+ elif entity2_name:
77
+ embedding = (embedding1 + embedding2) / 2
78
+ else:
79
+ embedding = (embedding1 + embedding3) / 2
80
+
81
+ context_ds = entity_kinds[entity_kind]
82
+ scores, samples = context_ds.get_nearest_examples(
83
+ 'embeddings', embedding, k=entity_count
84
+ )
85
+ cos_scores = util.cos_sim(embedding, samples['embeddings'])[0]
86
+ cos_scores = [round(x, 4) for x in cos_scores.tolist()]
87
+ results = pd.DataFrame({'name': samples['name'], 'score': cos_scores}).sort_values('score', ascending=False)
88
+ search_time = round((time.perf_counter_ns() - start_time) / 10**6)
89
+
90
+ return [results.to_numpy(), search_time]
91
+
92
+ # User interface
93
+ ui = gr.Interface(
94
+ calc,
95
+ [
96
+ gr.Radio(label='Тип искомого объекта', choices=list(entity_kinds.keys()), value='Функция'),
97
+ gr.Textbox(label='Исходный объект'),
98
+ gr.Textbox(label='Вычитаемый объект'),
99
+ gr.Textbox(label='Добавляемый объект'),
100
+ gr.Slider(1, 20, 10, step=1, label='Кол-во результатов'),
101
+ ],
102
+ [
103
+ gr.Dataframe(label='Результат вычисления', headers=['Название', 'Сходство'], datatype=['str', 'number'], wrap=True),
104
+ gr.Textbox(label='Время поиска, миллисекунды'),
105
+ ],
106
+ allow_flagging='never',
107
+ live=True,
108
+ examples=[
109
+ ['Функция', 'проектирование баз данных', 'база данных', 'интерфейс', 10],
110
+ ['Знание', 'управление проектами', 'проекты', 'производство', 10],
111
+ ['Функция', 'проектирование баз данных', 'проектирование баз данных', 'диагностика оборудования', 10],
112
+ ['Функция', 'проектирование автомобиля', 'управление автомобилем', 'управление данными', 10],
113
+ ['Функция', 'оценка данных', 'управление данными', 'управление персоналом', 10],
114
+ ['Функция', 'проектирование баз данных', 'диагностика оборудования', '', 10],
115
+ ],
116
+ title='Калькулятор терминов',
117
+ description='''Вычисляет векторные представления для объектов, выполняет над ними арифметические операции
118
+ и ищет наиболее близкий к полученному вектору объект (с указанным типом).
119
+ Если указать только два объекта (т.е. не указывать вычитаемый или добавляемый объект),
120
+ то вычисляется среднее арифметическое между указанными объектами.''',
121
+ article=f'''<p>Поиск выполняется по
122
+ <a href="https://profstandart.rosmintrud.ru/obshchiy-informatsionnyy-blok/natsionalnyy-reestr-professionalnykh-standartov/reestr-professionalnykh-standartov/">реестру</a>
123
+ профессиональных стандартов минтруда.</p>
124
+ <p>В базе есть следующие данные:</p>
125
+ <table>
126
+ <tr><th>Тип объектов</th><th>Кол-во</th></tr>
127
+ <tr><td>Предметные области</td><td>{domains_ds.num_rows}</td></tr>
128
+ <tr><td>Процессы</td><td>{generalized_functions_ds.num_rows}</td></tr>
129
+ <tr><td>Подпроцессы</td><td>{particular_functions_ds.num_rows}</td></tr>
130
+ <tr><td>Функции</td><td>{actions_ds.num_rows}</td></tr>
131
+ <tr><td>Должности</td><td>{jobs_ds.num_rows}</td></tr>
132
+ <tr><td>Навыки</td><td>{skills_ds.num_rows}</td></tr>
133
+ <tr><td>Знания</td><td>{knowledges_ds.num_rows}</td></tr>
134
+ </table>
135
+ <p>Для вычисления векторных представлений используется следующая модель:</p>
136
+ <table>
137
+ <tr><th>Характеристика модели</th><th>Значение</th></tr>
138
+ <tr><td>Модель</td><td><a href="https://huggingface.co/{checkpoint}">{checkpoint}</a></td></tr>
139
+ <tr><td>Размер, Мб</td><td>{round(get_model_size(model))}</td></tr>
140
+ <tr><td>Количество параметров, миллионы</td><td>{round(model.num_parameters()/10**6)}</td></tr>
141
+ <tr><td>Размерность векторных представлений</td><td>{get_embeddings('').shape[1]}</td></tr>
142
+ </table>
143
+ ''',
144
+ css='.w-full .col:nth-child(2) { flex-grow: 2 !important; }')
145
+
146
+ ui.launch()