HardWorkingStation commited on
Commit
22169da
·
0 Parent(s):

Initial commit

Browse files
.gitattributes ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ data/* filter=lfs diff=lfs merge=lfs -text
2
+ src/model/* filter=lfs diff=lfs merge=lfs -text
.github/workflows/main.yaml ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Sync to HuggingFace hub
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ pull_request:
6
+ branches: [main]
7
+ # to run this workflows manually from the Actions tab
8
+ workflow_dispatch:
9
+
10
+ jobs:
11
+
12
+ check_files:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - name: Check large files
16
+ uses: ActionsDesk/lfs-warning@v2.0
17
+ with:
18
+ filesizelimit: 10485760 # this is 10MB so we can sync to HF Spaces
19
+
20
+ sync-to-hub:
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v2
24
+ with:
25
+ fetch-depth: 0
26
+ - name: Push to hub
27
+ env:
28
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
29
+ run: git lfs fetch --all && git push --force https://HF_USERNAME:$HF_TOKEN@huggingface.co/spaces/versus666/ml_message_moderation main
30
+ needs: check_files
.gitignore ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Service
10
+ venv
11
+ original_data
12
+ catboost_info
13
+ test
14
+
15
+ # Distribution / packaging
16
+ .Python
17
+ build/
18
+ develop-eggs/
19
+ dist/
20
+ downloads/
21
+ eggs/
22
+ .eggs/
23
+ lib/
24
+ lib64/
25
+ parts/
26
+ sdist/
27
+ var/
28
+ wheels/
29
+ pip-wheel-metadata/
30
+ share/python-wheels/
31
+ *.egg-info/
32
+ .installed.cfg
33
+ *.egg
34
+ MANIFEST
35
+
36
+ # PyInstaller
37
+ # Usually these files are written by a python script from a template
38
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
39
+ *.manifest
40
+ *.spec
41
+
42
+ # Installer logs
43
+ pip-log.txt
44
+ pip-delete-this-directory.txt
45
+
46
+ # Unit test / coverage reports
47
+ htmlcov/
48
+ .tox/
49
+ .nox/
50
+ .coverage
51
+ .coverage.*
52
+ .cache
53
+ nosetests.xml
54
+ coverage.xml
55
+ *.cover
56
+ *.py,cover
57
+ .hypothesis/
58
+ .pytest_cache/
59
+
60
+ # Translations
61
+ *.mo
62
+ *.pot
63
+
64
+ # Django stuff:
65
+ *.log
66
+ local_settings.py
67
+ db.sqlite3
68
+ db.sqlite3-journal
69
+
70
+ # Flask stuff:
71
+ instance/
72
+ .webassets-cache
73
+
74
+ # Scrapy stuff:
75
+ .scrapy
76
+
77
+ # Sphinx documentation
78
+ docs/_build/
79
+
80
+ # PyBuilder
81
+ target/
82
+
83
+ # Jupyter Notebook
84
+ .ipynb_checkpoints
85
+
86
+ # IPython
87
+ profile_default/
88
+ ipython_config.py
89
+
90
+ # pyenv
91
+ .python-version
92
+
93
+ # pipenv
94
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
95
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
96
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
97
+ # install all needed dependencies.
98
+ #Pipfile.lock
99
+
100
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
101
+ __pypackages__/
102
+
103
+ # Celery stuff
104
+ celerybeat-schedule
105
+ celerybeat.pid
106
+
107
+ # SageMath parsed files
108
+ *.sage.py
109
+
110
+ # Environments
111
+ .env
112
+ .venv
113
+ env/
114
+ venv/
115
+ ENV/
116
+ env.bak/
117
+ venv.bak/
118
+
119
+ # Spyder project settings
120
+ .spyderproject
121
+ .spyproject
122
+
123
+ # Rope project settings
124
+ .ropeproject
125
+
126
+ # mkdocs documentation
127
+ /site
128
+
129
+ # mypy
130
+ .mypy_cache/
131
+ .dmypy.json
132
+ dmypy.json
133
+
134
+ # Pyre type checker
135
+ .pyre/
README.md ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: ML message moderation
3
+ emoji: 🤳 📨 → ✅
4
+ colorFrom: indigo
5
+ colorTo: red
6
+ sdk: streamlit
7
+ sdk_version: 1.10.0
8
+ python_version: 3.9
9
+ app_file: src/app.py
10
+ pinned: false
11
+ ---
data/catboost_preds.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8110fe3b1a51cfa0a58395ef1e820442155210b11e75d9031638d04a1acf821e
3
+ size 9003
data/data.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ca00668aaf1f8e1e3b78f77ab09c770be082665a4730a24a152cf73f9c84eef5
3
+ size 3318792
data/features_emb.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e2dad8888db5bfa4f3365a0d567c7fe0e94b404a7a3b5958f0d7a6de1d550a5b
3
+ size 25755653
data/tf_idf_vocab.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ff9f91761bd164059e2c3b502e827ca93266761d500245ca0e2392e012c336ee
3
+ size 30119044
images/idf_formula.jpg ADDED
images/re.jpeg ADDED
images/tf_formula.jpg ADDED
images/tf_idf_formula.jpg ADDED
requirements.txt ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ altair==4.2.0
2
+ argon2-cffi==21.3.0
3
+ argon2-cffi-bindings==21.2.0
4
+ asttokens==2.0.8
5
+ attrs==22.1.0
6
+ backcall==0.2.0
7
+ beautifulsoup4==4.11.1
8
+ bleach==5.0.1
9
+ blinker==1.5
10
+ cachetools==5.2.0
11
+ catboost==1.0.6
12
+ certifi==2022.6.15
13
+ cffi==1.15.1
14
+ charset-normalizer==2.1.0
15
+ click==8.1.3
16
+ colorama==0.4.5
17
+ commonmark==0.9.1
18
+ cycler==0.11.0
19
+ debugpy==1.6.2
20
+ decorator==5.1.1
21
+ defusedxml==0.7.1
22
+ entrypoints==0.4
23
+ executing==0.10.0
24
+ fastjsonschema==2.16.1
25
+ filelock==3.8.0
26
+ fonttools==4.34.4
27
+ gitdb==4.0.9
28
+ GitPython==3.1.27
29
+ graphviz==0.20.1
30
+ huggingface-hub==0.8.1
31
+ idna==3.3
32
+ importlib-metadata==4.12.0
33
+ ipykernel==6.15.1
34
+ ipython==8.4.0
35
+ ipython-genutils==0.2.0
36
+ ipywidgets==7.7.1
37
+ jedi==0.18.1
38
+ Jinja2==3.1.2
39
+ joblib==1.1.0
40
+ jsonschema==4.9.1
41
+ jupyter-client==7.3.4
42
+ jupyter-core==4.11.1
43
+ jupyterlab-pygments==0.2.2
44
+ jupyterlab-widgets==1.1.1
45
+ kiwisolver==1.4.4
46
+ lxml==4.9.1
47
+ MarkupSafe==2.1.1
48
+ matplotlib==3.5.3
49
+ matplotlib-inline==0.1.3
50
+ mistune==0.8.4
51
+ nbclient==0.6.6
52
+ nbconvert==6.5.3
53
+ nbformat==5.4.0
54
+ nest-asyncio==1.5.5
55
+ nltk==3.7
56
+ notebook==6.4.12
57
+ numpy==1.23.2
58
+ packaging==21.3
59
+ pandas==1.4.3
60
+ pandocfilters==1.5.0
61
+ parso==0.8.3
62
+ pickleshare==0.7.5
63
+ Pillow==9.2.0
64
+ plotly==5.10.0
65
+ prometheus-client==0.14.1
66
+ prompt-toolkit==3.0.30
67
+ protobuf==3.20.1
68
+ psutil==5.9.1
69
+ pure-eval==0.2.2
70
+ pyarrow==9.0.0
71
+ pycparser==2.21
72
+ pydeck==0.7.1
73
+ Pygments==2.12.0
74
+ Pympler==1.0.1
75
+ pyparsing==3.0.9
76
+ pyrsistent==0.18.1
77
+ python-dateutil==2.8.2
78
+ pytz==2022.2.1
79
+ pytz-deprecation-shim==0.1.0.post0
80
+ pywin32==304
81
+ pywinpty==2.0.7
82
+ PyYAML==6.0
83
+ pyzmq==23.2.1
84
+ regex==2022.7.25
85
+ requests==2.28.1
86
+ rich==12.5.1
87
+ scikit-learn==1.1.2
88
+ scipy==1.9.0
89
+ semver==2.13.0
90
+ Send2Trash==1.8.0
91
+ six==1.16.0
92
+ smmap==5.0.0
93
+ soupsieve==2.3.2.post1
94
+ stack-data==0.4.0
95
+ streamlit==1.12.0
96
+ tenacity==8.0.1
97
+ terminado==0.15.0
98
+ threadpoolctl==3.1.0
99
+ tinycss2==1.1.1
100
+ tokenizers==0.12.1
101
+ toml==0.10.2
102
+ toolz==0.12.0
103
+ torch==1.12.1
104
+ tornado==6.2
105
+ tqdm==4.64.0
106
+ traitlets==5.3.0
107
+ transformers==4.21.1
108
+ typing_extensions==4.3.0
109
+ tzdata==2022.2
110
+ tzlocal==4.2
111
+ urllib3==1.26.11
112
+ validators==0.20.0
113
+ watchdog==2.1.9
114
+ wcwidth==0.2.5
115
+ webencodings==0.5.1
116
+ widgetsnbextension==3.6.1
117
+ zipp==3.8.1
src/app.py ADDED
@@ -0,0 +1,377 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import tools
3
+
4
+
5
+ st.title('Message moderation lab')
6
+
7
+
8
+ st.write(
9
+ """
10
+ Термин «модерация» происходит от латинского «moderor», что значит «умерять, сдерживать».
11
+ Суть задачи модерации состоит в контроле за выполнением законов, правил, требований и ограничений в
12
+ любых сообществах и сервисах — будь то простое общение в социальных сетях или деловые переговоры на онлайн площадке.
13
+
14
+ Автоматические системы модерации внедряются в веб-сервисы и приложения, где необходимо обрабатывать большое
15
+ количество сообщений пользователей. Такие системы позволяют сократить издержки на ручную модерацию, ускорить её и
16
+ обрабатывать все сообщения пользователей в real-time.
17
+
18
+ Со временем пользователи подстраиваются и учатся обманывать такие системы, например пользователи:
19
+ - генерируют опечатки: you are stupit asswhol, fack u
20
+ - заменяют буквенные символы на цифры, похожие по описанию: n1gga, b0ll0cks,
21
+ - вставляют дополнительные пробелы: i d i o t,
22
+ - удаляют пробелы между словами: dieyoustupid
23
+ - указывают контактные данные: восем-906-три единицы-два раза по две единицы
24
+ и многое другое.
25
+
26
+ Для того, чтобы обучить классификатор устойчивый к таким подменам, нужно поступить так, как поступают пользователи:
27
+ сгенерировать такие же изменения в сообщениях и добавить их в обучающую выборку к основным данным.
28
+
29
+ В целом, эта борьба неизбежна: пользователи всегда будут пытаться находить уязвимости и хаки,
30
+ а модераторы реализовывать новые алгоритмы.
31
+
32
+ В примере ниже можно ознакомиться с работой разных алгоритмов по выявлению наличия контактных данных в сообщениях
33
+ пользователей. Это актуально в первую очередь для торговых площадок и других онлайн площадок по продаже и
34
+ рекомендации товаров и услуг. Актуально это потому, что пользователи не всегда желают платить комиссию за работу
35
+ сервиса и пытаются осуществлять сделки напрямую, минуя сервис.
36
+
37
+ В данном примере сообщения пользователей подвергаются проверке тремя алгоритмами по поиску контактных данных:
38
+ - регулярные выражения (regex)
39
+ - TF-IDF, на основе частотности слов
40
+ - нейросеть BERT
41
+
42
+ 1. Регулярные выражения
43
+ Регулярные выражения представляют собой похожий, но гораздо более сильный инструмент для поиска строк, проверки их
44
+ на соответствие какому-либо шаблону и другой подобной работы. Англоязычное название этого
45
+ инструмента — Regular Expressions или просто RegExp.
46
+ """
47
+ )
48
+
49
+ with st.expander(
50
+ label='Блок теории про регулярные выражения'
51
+ ):
52
+ st.write(
53
+ """
54
+ В самом общем смысле регулярные выражения — это последовательности символов для поиска соответствий шаблону.
55
+ Они являются экземплярами регулярного языка и широко применяются для парсинга текста или валидации входных строк.
56
+
57
+ Представьте лист картона, в котором вырезаны определенные фигуры. И только фигуры, точно соответствующие вырезам,
58
+ смогут через них пройти. В данном случае лист картона аналогичен строке регулярного выражения.
59
+ """
60
+ )
61
+ st.image(
62
+ image='images/re.jpeg',
63
+ caption='Суть работы регулярных выражений',
64
+ use_column_width=True
65
+ )
66
+
67
+ st.write(
68
+ """
69
+ Несколько случаев применения регулярных выражений:
70
+
71
+ - парсинг входных данных, например текста, логов, веб-информации и т.д.;
72
+ - валидация пользовательского ввода;
73
+ - тестирование результатов вывода;
74
+ - точный поиск текста;
75
+ - реструктуризация данных.
76
+
77
+ Регулярные выражения отлично подходят, когда есть четкий формат и структура данных. В нашем же случае пользователям
78
+ легко будет обмануть систему модерации сообщений, если она будет построена только на регулярных выражениях.
79
+ Нужно что-то посложнее.
80
+ """
81
+ )
82
+
83
+ st.write(
84
+ """
85
+ 2. TF-IDF (TF — term frequency, IDF — inverse document frequency).
86
+ Мера TF-IDF является произведением двух сомножителей TF и IDF.
87
+
88
+ TF - частота слова - отношение числа вхождений некоторого слова к общему числу слов документа.
89
+ Таким образом, оценивается важность слова в пределах отдельного документа.
90
+
91
+ IDF - обратная частота документа - инверсия частоты, с которой некоторое слово встречается в документах коллекции.
92
+ Учёт IDF уменьшает вес широкоупотребительных слов. Для каждого уникального слова в пределах конкретной коллекции
93
+ документов существует только одно значение IDF.
94
+ """
95
+ )
96
+
97
+ with st.expander(
98
+ label='Блок теории про TF-IDF'
99
+ ):
100
+
101
+ st.image(
102
+ image='images/tf_idf_formula.jpg',
103
+ caption='Формула TF-IDF',
104
+ use_column_width=True
105
+ )
106
+
107
+ st.write(
108
+ """
109
+ TF рассчитывается по следующей формуле:
110
+ """
111
+ )
112
+
113
+ st.image(
114
+ image='images/tf_formula.jpg'
115
+ )
116
+
117
+ st.write(
118
+ """
119
+ где t (от англ. term) — количество употребления слова, а n — общее число слов в тексте.
120
+ """
121
+ )
122
+
123
+ st.image(
124
+ image='images/idf_formula.jpg'
125
+ )
126
+
127
+ st.write(
128
+ """
129
+ где D - общее число текстов в корпусе, d - количество текстов, в которых это слово встречается.
130
+
131
+ IDF нужна в формуле, чтобы уменьшить вес слов, наиболее распространённых в любом другом тексте заданного корпуса.
132
+ """
133
+ )
134
+
135
+ st.write(
136
+ """
137
+ TF-IDF оценивает значимость слова в документе, на основе данных о всей коллекции документов. Данная мера
138
+ определяет вес слова за величину пропорциональную частоте его вхождения в документ и обратно пропорциональную
139
+ частоте его вхождения во всех документах коллекции.
140
+
141
+ Большая величина TF-IDF говорит об уникальности слова в тексте по отношению к корпусу.
142
+ Чем чаще оно встречается в конкретном тексте и реже в остальных, тем выше значение TF-IDF.
143
+ """
144
+ )
145
+
146
+ st.write(
147
+ """
148
+ 3. Нейросеть BERT.
149
+
150
+ BERT — это нейронная сеть от Google, показавшая с большим отрывом state-of-the-art результаты на целом ряде задач.
151
+ С помощью BERT можно создавать программы с ИИ для обработки естественного языка: отвечать на вопросы, заданные
152
+ в произвольной форме, создавать чат-ботов, автоматические переводчики, анализировать текст и так далее.
153
+ """
154
+ )
155
+
156
+ with st.expander(
157
+ label='Блок теории про BERT'
158
+ ):
159
+ st.write(
160
+ """
161
+ Чтобы подавать на вход нейронной сети текст, нужно его как-то представить в виде чисел. Проще всего это делать
162
+ побуквенно, подавая на каждый вход нейросети по одной букве. Тогда каждая букв�� будет кодироваться числом
163
+ от 0 до 32 (плюс какой-то запас на знаки препинания). Это так называемый character-level.
164
+
165
+ Но гораздо лучше результаты получаются, если мы предложения будем представлять не по одной букве, а подавая на
166
+ каждый вход нейросети сразу по целому слову (или хотя бы слогами). Это уже будет word-level. Самый простой
167
+ вариант — составить словарь со всеми существующими словами, и скармливать сети номер слова в этом словаре.
168
+ Например, если слово "собака" стоит в этом словаре на 1678 месте, то на вход нейросети для этого слова
169
+ подаем число 1678.
170
+
171
+ Вот только в естественном языке при слове "собака" у человека всплывает сразу множество
172
+ ассоциаций: "пушистая", "злая", "друг человека". Нельзя ли как-то закодировать эту особенность нашего мышления
173
+ в представлении для нейросети? Оказывается, можно. Для этого достаточно так пересортировать номера слов, чтобы
174
+ близкие по смыслу слова стояли рядом. Пусть будет, например, для "собака" число 1678, а для слова "пушистая"
175
+ число 1680. А для слова "чайник" число 9000. Как видите, цифры 1678 и 1680 находятся намного ближе друг к другу,
176
+ чем цифра 9000.
177
+
178
+ На практике, каждому слову назначают не одно число, а несколько — вектор, скажем, из 32 чисел. И расстояния
179
+ измеряют как расстояния между точками, на которые указывают эти вектора в пространстве соответствущей
180
+ размерности (для вектора длиной в 32 числа, это пространство с 32 размерностями, или с 32 осями).
181
+ Это позволяет сопоставлять одному слову сразу несколько близких по смыслу слов (смотря по какой оси считать).
182
+ Более того, с векторами можно производить арифметические операции. Классический пример: если из вектора,
183
+ обозначающего слово "король", вычесть вектор "мужчина" и прибавить вектор для слова "женщина", то получится
184
+ некий вектор-результат. И он чудесным образом будет соответствовать слову "королева". И действительно,
185
+ "король — мужчина + женщина = королева". Магия! И это не абстрактный пример, а
186
+ [реально так происходит](https://blog.acolyer.org/2016/04/21/the-amazing-power-of-word-vectors/). Учитывая,
187
+ что нейронные сети хорошо приспособлены для математических преобразований над своими входами, видимо это и
188
+ обеспечивает такую высокую эффективность этого метода.
189
+
190
+ Идея в основе BERT лежит очень простая: давайте на вход нейросети будем подавать фразы, в которых 15% слов
191
+ заменим на [MASK], и обучим нейронную сеть предсказывать эти закрытые маской слова.
192
+
193
+ Например, если подаем на вход нейросети фразу "Я пришел в [MASK] и купил [MASK]", она должна на выходе показать
194
+ слова "магазин" и "молоко". Это упрощенный пример с официальной страницы BERT, на более длинных предложениях
195
+ разброс возможных вариантов становится меньше, а ответ нейросети однозначнее.
196
+
197
+ А для того, чтобы нейросеть научилась понимать соотношения между разными предложениями, дополнительно обучим
198
+ ее предсказывать, является ли вторая фраза логичным продолжением первой. Или это какая-то случайная фраза, не
199
+ имеющая никакого отношения к первой.
200
+
201
+ Так, для двух предложений: "Я пошел в магазин." и "И купил там молоко.", нейросеть должна ответить,
202
+ что это логично. А если вторая фраза будет "Карась небо Плутон", то должна ответить, что это предложение никак
203
+ не связано с первым. Ниже мы поиграемся с обоими этими режимами работы BERT.
204
+
205
+ Обучив таким образом нейронную сеть на корпусе текстов из Wikipedia и сборнике книг BookCorpus
206
+ в течении 4 дней на 16 TPU, получили BERT.
207
+ """
208
+ )
209
+
210
+ if st.checkbox('Сгенерировать рандомное сообщение'):
211
+ user_text = st.text_area(
212
+ label='Введите сообщение',
213
+ height=200,
214
+ value=tools.get_random_message(),
215
+ help='Попробуйте указать ссылки на vk, twich, twitter и др. каналы связи а также почту')
216
+ else:
217
+ user_text = st.text_area(
218
+ label='Введите сообщение',
219
+ height=200,
220
+ help='Попробуйте указать ссылки на vk, twich, twitter и др. каналы связи а также почту'
221
+ )
222
+
223
+ with st.expander(
224
+ label='Показать примеры сообщений со скрытыми контактными данными'
225
+ ):
226
+ st.write(
227
+ """
228
+ Ма8ш9и9н9а6 в 0хо0ро4ш4е2м9 состоянии
229
+
230
+ Новый велосипед Работает всё Звонить на 8 девятьсот восемь 1976829
231
+
232
+ Беспроводная точка доступа маршрутизатор Моя Почта xopkin317 mailru
233
+
234
+ My Отличный телефон TW практически новый ich хороший экран, без трещин lork не падал ing92
235
+ """
236
+ )
237
+
238
+ re_res = tools.get_re_pred(user_text)
239
+
240
+ if 'Есть контактная информация' in re_res:
241
+ st.success(f'Regex: {re_res}')
242
+ else:
243
+ st.error(f'Regex : {re_res}')
244
+
245
+ tf_idf_res = tools.get_tf_idf_pred(user_text)
246
+
247
+ if 'Есть контактная информация' in tf_idf_res:
248
+ st.success(f'TF_IDF: {tf_idf_res}')
249
+ else:
250
+ st.error(f'TF_IDF: {tf_idf_res}')
251
+
252
+ bert_res = tools.get_bert_prediction(user_text)
253
+
254
+ if 'Есть контактная информация' in bert_res:
255
+ st.success(f'BERT: {bert_res}')
256
+ else:
257
+ st.error(f'BERT: {bert_res}')
258
+
259
+ with st.form(key='quiz'):
260
+ right_answers_count = 0
261
+
262
+ st.write('QUIZ')
263
+
264
+ answer = st.radio(
265
+ label='Что такое регулярные выражения?',
266
+ options=[
267
+ 'Модель машинного обучения',
268
+ 'Аналог TF-IDF',
269
+ 'Инструмент проверки строк на соответствие какому-либо шаблону',
270
+ 'Инструмент для классификации сообщений пользователя',
271
+ 'Выражения, которые регулярно используются разработчиками',
272
+ 'WEB фреймворк',
273
+ ]
274
+ )
275
+
276
+ if answer == 'Инструмент проверки строк на соответствие какому-либо шаблону':
277
+ right_answers_count += 1
278
+
279
+ answer = st.radio(
280
+ label='Как пользователи обходят правила модерации сервиса?',
281
+ options=[
282
+ 'Пишут в поддержку',
283
+ 'Изменяют сообщения, маскируя запрещенный контент',
284
+ 'Записывают голосовые сообщения',
285
+ 'Пользуются другими сервисами, без модерации'
286
+ ]
287
+ )
288
+
289
+ if answer == 'Изменяют сообщения, маскируя запрещенный контент':
290
+ right_answers_count += 1
291
+
292
+ answer = st.radio(
293
+ label='Что такое TF-IDF?',
294
+ options=[
295
+ 'Вид регулярных выражения',
296
+ 'Система модерации текстовых сообщений',
297
+ 'Запчасть автомобиля',
298
+ 'Мера оценки значимости слова в документе',
299
+ 'Модель машинного обучения',
300
+ 'Корпус текстов',
301
+ ]
302
+ )
303
+
304
+ if answer == 'Мера оценки значимости слова в документе':
305
+ right_answers_count += 1
306
+
307
+ answer = st.radio(
308
+ label='Что оценивает TF-IDF?',
309
+ options=[
310
+ 'Нужно ли отправлять сообщение на модерацию или нет',
311
+ 'Значимость слова в документе',
312
+ 'Частоту слова',
313
+ 'Обратную частоту слова в документе'
314
+ ]
315
+ )
316
+
317
+ if answer == 'Значимость слова в документе':
318
+ right_answers_count += 1
319
+
320
+ answer = st.radio(
321
+ label='Что такое BERT?',
322
+ options=[
323
+ 'Персонаж из мультика "Улица Сезам"',
324
+ 'Нейронная сеть от Google',
325
+ 'Система модерации сообщений',
326
+ 'Система оценки соответствия сообщений правилам организации и законам',
327
+ 'Вид регулярных выражений'
328
+ ]
329
+ )
330
+
331
+ if answer == 'Нейронная сеть от Google':
332
+ right_answers_count += 1
333
+
334
+ answer = st.radio(
335
+ label='Как обучается BERT?',
336
+ options=[
337
+ 'На GPU',
338
+ 'Никак, Google уже обучила ее, нам остается только пользоваться готовой',
339
+ 'Маскируя 15% слов символом [MASK] и пытаясь предсказать спрятанные слова'
340
+ ]
341
+ )
342
+
343
+ if answer == 'Маскируя 15% слов символом [MASK] и пытаясь предсказать спрятанные слова':
344
+ right_answers_count += 1
345
+
346
+ answer = st.radio(
347
+ label='В каком виде подается информация на вход нейросети BERT?',
348
+ options=[
349
+ 'Как есть без изменений',
350
+ 'В виде векторов с числами, обозначающими целевое слово и близких к нему по смыслу из словаря',
351
+ 'В виде сконкатенированных строк всего обучающего датасета',
352
+ 'В виде списка текстов'
353
+ ]
354
+ )
355
+
356
+ if answer == 'В виде векторов с числами, обозначающими целевое слово и близких к нему по смыслу из словаря':
357
+ right_answers_count += 1
358
+
359
+ answer = st.radio(
360
+ label='BERT учитывает контекст в предложениях?',
361
+ options=[
362
+ 'Нет',
363
+ 'Да'
364
+ ]
365
+ )
366
+
367
+ if answer == 'Да':
368
+ right_answers_count += 1
369
+
370
+ res = st.form_submit_button()
371
+
372
+ if res:
373
+ st.info(f'Количество правильных ответов {right_answers_count} из 8.')
374
+ if right_answers_count <= 6:
375
+ st.warning('Для прохождения блока необходимо правильно ответить хотя бы на 7 вопросов.')
376
+ else:
377
+ st.success('Отлично! Блок пройден.')
src/model/catboost.cbm ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:61965d81b3fd4a75cdd6d1b1fdbfbd65b8f4b8d6466c483012ff40a357ef56d2
3
+ size 20983520
src/model/tf_idf.pk ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:35f46199019d8dbd3f5473b65e359db42c206b41f5c8f1dc22b36aff7cbc831b
3
+ size 38728470
src/model/tf_idf_catboost.cbm ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a457ea9813775fde8c96540bb418b6aa749e00819afcc14c7331974f6756fa93
3
+ size 13968080
src/tools.py ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import pickle
3
+ import re
4
+ import string
5
+
6
+ import streamlit as st
7
+
8
+ import numpy as np
9
+ import pandas as pd
10
+
11
+ import catboost
12
+
13
+ import torch
14
+ from transformers import AutoTokenizer, AutoModel
15
+
16
+ from sklearn.feature_extraction.text import TfidfVectorizer
17
+
18
+ import nltk
19
+ from nltk import WordNetLemmatizer
20
+
21
+
22
+ def check_nltk():
23
+ for path in nltk.data.path:
24
+ try:
25
+ if len(os.listdir(path)) > 0:
26
+ return
27
+ except:
28
+ continue
29
+
30
+ nltk.download("omw-1.4")
31
+ nltk.download("wordnet")
32
+
33
+
34
+ with st.spinner('🌀 Загружаю данные...'):
35
+ check_nltk()
36
+ data = pd.read_csv('data/data.csv')
37
+ embeddings = pd.read_csv('data/features_emb.csv')
38
+ preds = pd.read_csv('data/catboost_preds.csv')
39
+ catboost_bert_model = catboost.CatBoostClassifier(random_state=25).load_model('src/model/catboost.cbm')
40
+ catboost_tf_idf_model = catboost.CatBoostClassifier(random_state=25).load_model('src/model/tf_idf_catboost.cbm')
41
+ bert_tokenizer = AutoTokenizer.from_pretrained("DeepPavlov/rubert-base-cased-conversational")
42
+ bert_model = AutoModel.from_pretrained("DeepPavlov/rubert-base-cased-conversational")
43
+ tf_idf_vocab = pd.read_csv('data/tf_idf_vocab.csv', index_col='Unnamed: 0')
44
+
45
+
46
+ def get_random_message() -> str:
47
+ return data.sample(1)['description'].values[0]
48
+
49
+
50
+ def get_bert_prediction(
51
+ text: str
52
+ ) -> str:
53
+
54
+ res_mapper = {
55
+ 0: 'Контактная информация отсутствует',
56
+ 1: 'Есть контактная информация'
57
+ }
58
+
59
+ tokens = bert_tokenizer.encode(
60
+ text,
61
+ add_special_tokens=True,
62
+ truncation=True,
63
+ max_length=512
64
+ )
65
+
66
+ n = 512 # max длина вектора
67
+
68
+ padded = torch.LongTensor(
69
+ [
70
+ np.array(tokens + [0] * (n - len(tokens)))
71
+ ]
72
+ )
73
+
74
+ attention_mask = torch.LongTensor(
75
+ np.where(
76
+ padded != 0, 1, 0
77
+ )
78
+ )
79
+
80
+ with torch.no_grad():
81
+ batch_embeddings = bert_model(padded, attention_mask=attention_mask)[0][:, 0, :].numpy()
82
+
83
+ return res_mapper.get(int(catboost_bert_model.predict(batch_embeddings)))
84
+
85
+
86
+ def get_tf_idf_pred(text: str) -> str:
87
+
88
+ res_mapper = {
89
+ 0: 'Контактная информация отсутствует',
90
+ 1: 'Есть контактная информация'
91
+ }
92
+
93
+ if len(text) == 0:
94
+ return res_mapper.get(0)
95
+
96
+ def remove_symbols(data):
97
+ return re.sub('[/*,;-]', '', data)
98
+
99
+ def remove_punc(data):
100
+ trans = str.maketrans('', '', string.punctuation)
101
+ return data.translate(trans)
102
+
103
+ def white_space(data):
104
+ return ' '.join(data.split())
105
+
106
+ def lemmatization(data):
107
+ return ' '.join([WordNetLemmatizer().lemmatize(word) for word in data.split()])
108
+
109
+ def complete_noise(data):
110
+ new_data = remove_symbols(data)
111
+ new_data = remove_punc(new_data)
112
+ new_data = white_space(new_data)
113
+ new_data = lemmatization(new_data)
114
+ return new_data
115
+
116
+ text = complete_noise(text)
117
+ with open('src/model/tf_idf.pk', 'rb') as fin:
118
+ tf_idf = pickle.load(fin)
119
+ tf_idf.vocabulary_ = tf_idf_vocab.to_dict()['0']
120
+ # tf_idf_new = TfidfVectorizer(ngram_range=(1, 5), vocabulary=tf_idf_vocab.to_dict()['0'])
121
+ # st.write(tf_idf.get_params())
122
+ bag_of_words = tf_idf.transform([text])
123
+
124
+ try:
125
+ return res_mapper.get(int(catboost_tf_idf_model.predict(bag_of_words)))
126
+ except:
127
+ return 'В сообщении встречаются слова, отсутствующие в вокабуляре TF-IDF.'
128
+
129
+
130
+ def get_re_pred(text: str) -> str:
131
+
132
+ url_pattern = re.compile(r'\b((?:https?://)?(?:(?:www\.)?(?:[\da-z\.-]+)\.(?:[a-z]{2,6})|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:(?:[0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(?::[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(?:ffff(?::0{1,4}){0,1}:){0,1}(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])|(?:[0-9a-fA-F]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])))(?::[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])?(?:/[\w\.-]*)*/?)\b')
133
+ phone_pattern = re.compile(r'(\d{3}[-\.\s]??\d{3}[-\.\s]??\d{4}|\(\d{3}\)\s*\d{3}[-\.\s]??\d{4}|\d{3}[-\.\s]??\d{4})')
134
+ if len(re.findall(url_pattern, text)) > 0:
135
+ return 'Есть контактная информация (url)'
136
+ elif len(re.findall(r'[\w\.-]+@[\w\.-]+(\.[\w]+)+', text)) > 0:
137
+ return 'Есть контактная информация (mail)'
138
+ elif len(re.findall(phone_pattern, text)) > 0:
139
+ return 'Есть контактная информация (phone)'
140
+ else:
141
+ return 'Контактная информация отсутствует'
142
+