AresEkb commited on
Commit
4ed59da
1 Parent(s): f153b6a

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +154 -0
app.py ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+
4
+ import pandas as pd
5
+ import torch
6
+
7
+ from datasets import load_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').add_faiss_index(column='embeddings')
46
+ generalized_functions_ds = load_dataset(ds_name, 'generalized_functions').add_faiss_index(column='embeddings')
47
+ jobs_ds = load_dataset(ds_name, 'jobs').add_faiss_index(column='embeddings')
48
+ particular_functions_ds = load_dataset(ds_name, 'particular_functions').add_faiss_index(column='embeddings')
49
+ actions_ds = load_dataset(ds_name, 'actions').add_faiss_index(column='embeddings')
50
+ skills_ds = load_dataset(ds_name, 'skills').add_faiss_index(column='embeddings')
51
+ knowledges_ds = load_dataset(ds_name, 'knowledges').add_faiss_index(column='embeddings')
52
+
53
+ indices = {'reg_number', 'generalized_function_id', 'particular_function_id'}
54
+
55
+ entity_kinds = {
56
+ 'Предметная область': domains_ds,
57
+ 'Процесс': generalized_functions_ds,
58
+ 'Подпроцесс': particular_functions_ds,
59
+ 'Функция': actions_ds,
60
+ 'Должность': jobs_ds,
61
+ 'Навык': skills_ds,
62
+ 'Знание': knowledges_ds,
63
+ }
64
+
65
+ # Main search logic
66
+ def search(context_entity_kind, context_entity_name, context_entity_count,
67
+ target_entity_kind, target_entity_name, target_entity_count):
68
+ # Find similar context entities
69
+ start_time = time.perf_counter_ns()
70
+ context_ds = entity_kinds[context_entity_kind]
71
+ context_embedding = get_embeddings(context_entity_name)
72
+ scores, samples = context_ds.get_nearest_examples(
73
+ 'embeddings', context_embedding, k=context_entity_count
74
+ )
75
+ cos_scores = util.cos_sim(context_embedding, samples['embeddings'])[0]
76
+ cos_scores = [round(x, 4) for x in cos_scores.tolist()]
77
+ results = pd.DataFrame({'name': samples['name'], 'score': cos_scores}).sort_values('score', ascending=False)
78
+ search_time = round((time.perf_counter_ns() - start_time) / 10**6)
79
+
80
+ # Get related entities
81
+ start_time = time.perf_counter_ns()
82
+ context_df = pd.DataFrame(samples).drop(columns=['embeddings']).rename(columns={'name': 'context_name'})
83
+ context_df['context_score'] = cos_scores
84
+ target_df = entity_kinds[target_entity_kind].to_pandas()
85
+ common_indices = list(indices.intersection(context_df.columns).intersection(target_df.columns))
86
+ target_ds = Dataset.from_pandas(context_df.merge(target_df, on=common_indices))
87
+ target_ds.add_faiss_index(column='embeddings')
88
+
89
+ # Find similar target entities
90
+ target_embedding = get_embeddings(target_entity_name)
91
+ scores, samples = target_ds.get_nearest_examples(
92
+ 'embeddings', target_embedding, k=target_entity_count
93
+ )
94
+ cos_scores = util.cos_sim(target_embedding, samples['embeddings'])[0]
95
+ cos_scores = (cos_scores + torch.tensor(samples['context_score'])) / 2
96
+ cos_scores = [round(x, 4) for x in cos_scores.tolist()]
97
+ results2 = pd.DataFrame({'name': samples['name'], 'context_name': samples['context_name'], 'score': cos_scores}).sort_values('score', ascending=False)
98
+ search_time2 = round((time.perf_counter_ns() - start_time) / 10**6)
99
+
100
+ return [results.to_numpy(), search_time, results2.to_numpy(), search_time2]
101
+
102
+ # User Interface
103
+ ui = gr.Interface(
104
+ search,
105
+ [
106
+ gr.Radio(label='Тип объекта', choices=list(entity_kinds.keys()), value='Функция'),
107
+ gr.Textbox(label='Название объекта'),
108
+ gr.Slider(1, 20, 10, step=1, label='Кол-во объектов'),
109
+ gr.Radio(label='Тип связанного объекта', choices=list(entity_kinds.keys()), value='Должность'),
110
+ gr.Textbox(label='Название связанного объекта'),
111
+ gr.Slider(1, 20, 10, step=1, label='Кол-во связанных объектов'),
112
+ ],
113
+ [
114
+ gr.Dataframe(label='Похожие объекты', headers=['Название', 'Сходство'], datatype=['str', 'number']),
115
+ gr.Textbox(label='Время поиска, миллисекунды'),
116
+ gr.Dataframe(label='Похожие связанные объекты', headers=['Название', 'Контекст', 'Сходство']),
117
+ gr.Textbox(label='Время поиска, миллисекунды'),
118
+ ],
119
+ allow_flagging='never',
120
+ live=True,
121
+ examples=[
122
+ ['Функция', 'проектирование базы данных', 7, 'Должность', '', 7],
123
+ ['Функция', 'написать руководство пользователя', 7, 'Должность', '', 7],
124
+ ['Должность', 'программист', 12, 'Процесс', '', 7],
125
+ ],
126
+ title='Поиск по профстандартам',
127
+ description='''Выберите тип объектов, который вы хотите найти, введите его название.
128
+ Опционально укажите какие связанные объекты вы хотите найти.''',
129
+ article=f'''<p>Поиск выполняется по
130
+ <a href="https://profstandart.rosmintrud.ru/obshchiy-informatsionnyy-blok/natsionalnyy-reestr-professionalnykh-standartov/reestr-professionalnykh-standartov/">реестру</a>
131
+ профессиональных стандартов минтруда.</p>
132
+ <p>В базе есть следующие данные:</p>
133
+ <table>
134
+ <tr><th>Тип объектов</th><th>Кол-во</th></tr>
135
+ <tr><td>Предметные области</td><td>{domains_ds.num_rows}</td></tr>
136
+ <tr><td>Процессы</td><td>{generalized_functions_ds.num_rows}</td></tr>
137
+ <tr><td>Подпроцессы</td><td>{particular_functions_ds.num_rows}</td></tr>
138
+ <tr><td>Функции</td><td>{actions_ds.num_rows}</td></tr>
139
+ <tr><td>Должности</td><td>{jobs_ds.num_rows}</td></tr>
140
+ <tr><td>Навыки</td><td>{skills_ds.num_rows}</td></tr>
141
+ <tr><td>Знания</td><td>{knowledges_ds.num_rows}</td></tr>
142
+ </table>
143
+ <p>Для вычисления векторных представлений используется следующая модель:</p>
144
+ <table>
145
+ <tr><th>Характеристика модели</th><th>Значение</th></tr>
146
+ <tr><td>Модель</td><td><a href="https://huggingface.co/{checkpoint}">{checkpoint}</a></td></tr>
147
+ <tr><td>Размер, Мб</td><td>{round(get_model_size(model))}</td></tr>
148
+ <tr><td>Количество параметров, миллионы</td><td>{round(model.num_parameters()/10**6)}</td></tr>
149
+ <tr><td>Размерность векторных представлений</td><td>{get_embeddings('').shape[1]}</td></tr>
150
+ </table>
151
+ ''',
152
+ css='.w-full .col:nth-child(2) { flex-grow: 2 !important; }')
153
+
154
+ ui.launch()