aurelianvolturi commited on
Commit
ae8fcc2
·
verified ·
1 Parent(s): 7db058e

Publish reproducible multi-task toxicity model

Browse files
README.md CHANGED
@@ -3,32 +3,101 @@ language: ru
3
  pipeline_tag: text-classification
4
  base_model: cointegrated/rubert-tiny2
5
  license: cc-by-nc-sa-4.0
 
 
 
 
 
6
  ---
7
 
8
  # RuBERT Tiny 2 Multi-Task Toxicity
9
 
10
- Multi-label модель с общим замороженным энкодером и тремя независимыми
11
- линейными головами: profanity, threat, illegal.
 
 
 
 
 
 
 
 
 
12
 
13
  ## Test metrics
14
 
15
- | class | support | threshold | precision | recall | f1 | pr_auc |
16
- |:----------|----------:|------------:|------------:|---------:|-------:|---------:|
17
- | profanity | 181 | 0.5 | 0.6795 | 0.8785 | 0.7663 | 0.849 |
18
- | threat | 155 | 0.64 | 0.6571 | 0.7419 | 0.697 | 0.7605 |
19
- | illegal | 73 | 0.78 | 0.679 | 0.7534 | 0.7143 | 0.7813 |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
- Macro F1: 0.7258; Micro F1: 0.7319.
22
- Пороги выбраны только по validation.
 
 
 
 
23
 
24
  ## Использование
25
 
26
- Архитектура MultiTaskToxicityEncoder и функция predict_toxicity(text)
27
- приведены в сопровождающем notebook. В репозитории сохраняются
28
- токенизатор, model_state.pt и model_config.json.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
  ## Ограничения
31
 
32
- Модель выполняет предварительную модерацию, а не юридическую
33
- квалификацию. Редкие и завуалированные формулировки требуют
34
- ручной проверки.
 
 
 
 
3
  pipeline_tag: text-classification
4
  base_model: cointegrated/rubert-tiny2
5
  license: cc-by-nc-sa-4.0
6
+ tags:
7
+ - multi-label
8
+ - toxicity
9
+ - russian
10
+ - custom-code
11
  ---
12
 
13
  # RuBERT Tiny 2 Multi-Task Toxicity
14
 
15
+ End-to-end multi-label encoder с общим RuBERT Tiny 2 и тремя
16
+ независимыми линейными головами: `profanity`, `threat`, `illegal`.
17
+
18
+ ## Данные
19
+
20
+ Нативные русские Toxic Russian Comments и RUCriminal после
21
+ fail-closed удаления политических высказываний, политически
22
+ чувствительных тем и явных персональных идентификаторов.
23
+ Для `illegal` используются законные hard negatives по тем же
24
+ статьям УК РФ. Переводные данные отсутствуют. Legal train/test
25
+ разделены по непересекающимся статьям.
26
 
27
  ## Test metrics
28
 
29
+ | class | support | threshold | precision | recall | f1 | f1_ci_low | f1_ci_high | pr_auc |
30
+ |:----------|----------:|------------:|------------:|---------:|-------:|------------:|-------------:|---------:|
31
+ | profanity | 141 | 0.75 | 0.9452 | 0.9787 | 0.9617 | 0.9342 | 0.9817 | 0.9806 |
32
+ | threat | 91 | 0.83 | 0.8298 | 0.8571 | 0.8432 | 0.7858 | 0.8984 | 0.9167 |
33
+ | illegal | 103 | 0.5 | 0.9717 | 1 | 0.9856 | 0.9679 | 1 | 1 |
34
+
35
+ Macro F1: 0.9302; Micro F1: 0.9369.
36
+
37
+ Пороги выбраны только на validation:
38
+
39
+ ```json
40
+ {
41
+ "profanity": 0.75,
42
+ "threat": 0.83,
43
+ "illegal": 0.5
44
+ }
45
+ ```
46
+
47
+ ## Метрики по доменам
48
 
49
+ | domain | class | support | f1 | recall |
50
+ |:----------------|:----------|----------:|-------:|---------:|
51
+ | legal_questions | illegal | 102 | 1 | 1 |
52
+ | social_comments | profanity | 141 | 0.9617 | 0.9787 |
53
+ | social_comments | threat | 91 | 0.8432 | 0.8571 |
54
+ | social_comments | illegal | 1 | 0.4 | 1 |
55
 
56
  ## Использование
57
 
58
+ ```python
59
+ import torch
60
+ from transformers import (
61
+ AutoModelForSequenceClassification,
62
+ AutoTokenizer,
63
+ )
64
+
65
+ repo = "aurelianvolturi/rubert-tiny2-multitask-toxicity"
66
+ tokenizer = AutoTokenizer.from_pretrained(repo)
67
+ model = AutoModelForSequenceClassification.from_pretrained(
68
+ repo,
69
+ trust_remote_code=True,
70
+ low_cpu_mem_usage=False,
71
+ device_map=None,
72
+ ).eval()
73
+
74
+ text = "Как проверить подлинность паспорта?"
75
+ batch = tokenizer(
76
+ text,
77
+ return_tensors="pt",
78
+ truncation=True,
79
+ max_length=model.config.max_length,
80
+ )
81
+
82
+ with torch.inference_mode():
83
+ probabilities = torch.sigmoid(
84
+ model(**batch).logits
85
+ )[0].tolist()
86
+
87
+ result = {
88
+ label: probability >= model.config.thresholds[label]
89
+ for label, probability in zip(
90
+ model.config.labels,
91
+ probabilities,
92
+ )
93
+ }
94
+ ```
95
 
96
  ## Ограничения
97
 
98
+ Модель предназначена для предварительной модерации и не выполняет
99
+ юридическую квалификацию. Завуалированные формулировки, цитаты,
100
+ ирония и новый сленг могут требовать ручной проверки.
101
+
102
+ Custom code следует загружать только из доверенного репозитория и
103
+ фиксировать `revision` на commit hash.
__init__.py ADDED
File without changes
config.json ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "MultiTaskToxicityEncoder"
4
+ ],
5
+ "auto_map": {
6
+ "AutoConfig": "configuration_multitask_toxicity.MultiTaskToxicityConfig",
7
+ "AutoModelForSequenceClassification": "modeling_multitask_toxicity.MultiTaskToxicityEncoder"
8
+ },
9
+ "dropout": 0.15,
10
+ "dtype": "float32",
11
+ "encoder_config": {
12
+ "_name_or_path": "cointegrated/rubert-tiny2",
13
+ "add_cross_attention": false,
14
+ "architectures": [
15
+ "BertForPreTraining"
16
+ ],
17
+ "attention_probs_dropout_prob": 0.1,
18
+ "bos_token_id": null,
19
+ "chunk_size_feed_forward": 0,
20
+ "classifier_dropout": null,
21
+ "dtype": "float32",
22
+ "emb_size": 312,
23
+ "eos_token_id": null,
24
+ "gradient_checkpointing": false,
25
+ "hidden_act": "gelu",
26
+ "hidden_dropout_prob": 0.1,
27
+ "hidden_size": 312,
28
+ "id2label": {
29
+ "0": "LABEL_0",
30
+ "1": "LABEL_1"
31
+ },
32
+ "initializer_range": 0.02,
33
+ "intermediate_size": 600,
34
+ "is_decoder": false,
35
+ "is_encoder_decoder": false,
36
+ "label2id": {
37
+ "LABEL_0": 0,
38
+ "LABEL_1": 1
39
+ },
40
+ "layer_norm_eps": 1e-12,
41
+ "max_position_embeddings": 2048,
42
+ "model_type": "bert",
43
+ "num_attention_heads": 12,
44
+ "num_hidden_layers": 3,
45
+ "output_attentions": false,
46
+ "output_hidden_states": false,
47
+ "pad_token_id": 0,
48
+ "position_embedding_type": "absolute",
49
+ "problem_type": null,
50
+ "return_dict": true,
51
+ "tie_word_embeddings": true,
52
+ "transformers_version": "5.13.1",
53
+ "type_vocab_size": 2,
54
+ "use_cache": true,
55
+ "vocab_size": 83828
56
+ },
57
+ "encoder_name": "cointegrated/rubert-tiny2",
58
+ "id2label": {
59
+ "0": "profanity",
60
+ "1": "threat",
61
+ "2": "illegal"
62
+ },
63
+ "label2id": {
64
+ "illegal": 2,
65
+ "profanity": 0,
66
+ "threat": 1
67
+ },
68
+ "labels": [
69
+ "profanity",
70
+ "threat",
71
+ "illegal"
72
+ ],
73
+ "max_length": 128,
74
+ "model_type": "multitask_toxicity",
75
+ "thresholds": {
76
+ "illegal": 0.5,
77
+ "profanity": 0.75,
78
+ "threat": 0.83
79
+ },
80
+ "transformers_version": "5.13.1"
81
+ }
configuration_multitask_toxicity.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers import PreTrainedConfig
2
+
3
+
4
+ class MultiTaskToxicityConfig(PreTrainedConfig):
5
+ model_type = "multitask_toxicity"
6
+
7
+ def __init__(
8
+ self,
9
+ encoder_name="cointegrated/rubert-tiny2",
10
+ encoder_config=None,
11
+ labels=None,
12
+ thresholds=None,
13
+ max_length=128,
14
+ dropout=0.15,
15
+ **kwargs,
16
+ ):
17
+ super().__init__(**kwargs)
18
+ self.encoder_name = encoder_name
19
+ self.encoder_config = dict(encoder_config or {})
20
+ self.labels = list(
21
+ labels or ["profanity", "threat", "illegal"]
22
+ )
23
+ self.thresholds = thresholds or {
24
+ label: 0.5 for label in self.labels
25
+ }
26
+ self.max_length = int(max_length)
27
+ self.dropout = float(dropout)
28
+ self.num_labels = len(self.labels)
29
+ self.id2label = {
30
+ index: label for index, label in enumerate(self.labels)
31
+ }
32
+ self.label2id = {
33
+ label: index for index, label in enumerate(self.labels)
34
+ }
inference.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from transformers import (
3
+ AutoModelForSequenceClassification,
4
+ AutoTokenizer,
5
+ )
6
+
7
+ REPO_ID = "aurelianvolturi/rubert-tiny2-multitask-toxicity"
8
+
9
+ tokenizer = AutoTokenizer.from_pretrained(REPO_ID)
10
+ model = AutoModelForSequenceClassification.from_pretrained(
11
+ REPO_ID,
12
+ trust_remote_code=True,
13
+ low_cpu_mem_usage=False,
14
+ device_map=None,
15
+ ).eval()
16
+
17
+
18
+ def predict(text):
19
+ batch = tokenizer(
20
+ text,
21
+ truncation=True,
22
+ max_length=model.config.max_length,
23
+ return_tensors="pt",
24
+ )
25
+ with torch.inference_mode():
26
+ probabilities = torch.sigmoid(
27
+ model(**batch).logits
28
+ )[0].tolist()
29
+
30
+ return {
31
+ label: {
32
+ "detected":
33
+ probability >= model.config.thresholds[label],
34
+ "probability": probability,
35
+ }
36
+ for label, probability in zip(
37
+ model.config.labels,
38
+ probabilities,
39
+ )
40
+ }
model_state.pt → model.safetensors RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:d9756ef248659d3d7fba2ebf7bae2a66a0e054ccb70979e9d0bbf42be6de173e
3
- size 116802296
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:379356c6a512c9cbca9687747dfa8e3a1cd0610f6e50a8494e9b44a36876561d
3
+ size 116785916
model_config.json DELETED
@@ -1,15 +0,0 @@
1
- {
2
- "encoder_name": "cointegrated/rubert-tiny2",
3
- "labels": [
4
- "profanity",
5
- "threat",
6
- "illegal"
7
- ],
8
- "thresholds": {
9
- "profanity": 0.4999999999999998,
10
- "threat": 0.6399999999999997,
11
- "illegal": 0.7799999999999997
12
- },
13
- "max_length": 128,
14
- "seed": 42
15
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
modeling_multitask_toxicity.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ from transformers import BertConfig, BertModel, PreTrainedModel
4
+ from transformers.modeling_outputs import SequenceClassifierOutput
5
+
6
+ from .configuration_multitask_toxicity import MultiTaskToxicityConfig
7
+
8
+
9
+ class MultiTaskToxicityEncoder(PreTrainedModel):
10
+ config_class = MultiTaskToxicityConfig
11
+ base_model_prefix = "encoder"
12
+
13
+ def __init__(self, config):
14
+ super().__init__(config)
15
+
16
+ if not config.encoder_config:
17
+ raise ValueError(
18
+ "encoder_config отсутствует в config.json"
19
+ )
20
+
21
+ # Не вызываем from_pretrained() внутри __init__. Внешний
22
+ # from_pretrained() сам создаёт модель и загружает все веса.
23
+ encoder_config = BertConfig.from_dict(
24
+ dict(config.encoder_config)
25
+ )
26
+ self.encoder = BertModel(encoder_config)
27
+ self.dropout = nn.Dropout(config.dropout)
28
+ self.heads = nn.ModuleDict({
29
+ label: nn.Linear(encoder_config.hidden_size, 1)
30
+ for label in config.labels
31
+ })
32
+ self.post_init()
33
+
34
+ def forward(
35
+ self,
36
+ input_ids,
37
+ attention_mask,
38
+ labels=None,
39
+ **kwargs,
40
+ ):
41
+ cls_embedding = self.encoder(
42
+ input_ids=input_ids,
43
+ attention_mask=attention_mask,
44
+ ).last_hidden_state[:, 0]
45
+
46
+ hidden = self.dropout(cls_embedding)
47
+ logits = torch.cat([
48
+ self.heads[label](hidden)
49
+ for label in self.config.labels
50
+ ], dim=1)
51
+
52
+ loss = None
53
+ if labels is not None:
54
+ loss = nn.functional.binary_cross_entropy_with_logits(
55
+ logits,
56
+ labels,
57
+ )
58
+
59
+ return SequenceClassifierOutput(
60
+ loss=loss,
61
+ logits=logits,
62
+ )