# Обучение

Встроенная модель работает сразу. Модель, обученная на вашей собственной почте, работает лучше, потому что узнаёт, как выглядит ваш ham: ваши рассылки, манеру письма ваших коллег, языки, на которых вам пишут.


## Обучение модели

Укажите `train` папки со спамом и ham:

```sh
spamscanner train --spam ~/Maildir/.Junk --ham ~/Maildir/cur --ham ~/Maildir/.Archive --out my-model.json
```

Источниками могут быть:

* файлы **mbox**, в том числе сжатые gzip (`.mbox.gz`),
* каталог **Maildir** (читаются его папки `cur` и `new`, `tmp` пропускается),
* **папка** с файлами `.eml`, читаемая рекурсивно,
* **набор данных**: файл CSV или JSON Lines со столбцом текста и столбцом метки (`--dataset`). Столбцы с именами `text`, `message`, `body`, `email` или `content` и `label`, `category`, `class`, `spam` или `is_spam` находятся автоматически; иначе используйте `--text-column` и `--label-column`. Распознаются такие метки, как `spam`, `1`, `phishing` и `ham`, `0`, `not_spam`, `legitimate`.

Повторяющиеся письма учитываются один раз. Чтобы дообучить встроенную модель, а не начинать с пустой, добавьте `--merge`.

Использование модели:

```sh
spamscanner scan message.eml --model my-model.json
SPAMSCANNER_MODEL=/var/lib/spamscanner/model.json spamscanner milter
```

```js
const scanner = new SpamScanner({classifier: '/var/lib/spamscanner/model.json'});
```

Сколько почты достаточно: несколько сотен писем каждого вида дают полезную модель, несколько тысяч — хорошую. Держите оба класса примерно в равновесии и включайте в ham почту, которую не нужно фильтровать (сброс пароля, счета от ваших поставщиков).


## Измерение качества

Отложите часть почты при обучении и измеряйте на ней:

```sh
spamscanner eval --model my-model.json --spam held-out/spam --ham held-out/ham
```

Встроенная модель на SMS-сообщениях на 21 языке, которых она никогда не видела, причём большинство — на языках, которые она почти не знает:

```text
Messages: 13028 spam, 92406 ham
Precision: 89.72%   Recall: 11.18%   F1: 19.89%
False positives: 167 (0.18% of ham)   False negatives: 132
Unsure: 91247 (86.54%)
```

Точность (precision) показывает, какая доля того, что модель называет спамом, действительно спам; полнота (recall) — какую долю спама она ловит. Неуверенные письма здесь считаются пропущенным спамом, хотя при проверке их всё ещё могут поймать остальные проверки и языковая модель. Следить нужно за ложными срабатываниями: ham, помеченным как спам. В прогоне выше модель не уверена в большинстве этих писем, а не ошибается в них, и это задуманное поведение для языков, на которых у неё мало почты.

`--json` выдаёт те же числа для скриптов.


## Обучение по жалобам

Когда пользователи перемещают почту в папку Junk или из неё, обучайте модель по одному письму:

```sh
spamscanner learn spam message.eml --model /var/lib/spamscanner/model.json
spamscanner learn ham message.eml --model /var/lib/spamscanner/model.json
```

Первый `learn` создаёт файл из встроенной модели. По HTTP то же делают `POST /learn/spam` и `/learn/ham` в [HTTP API](https://spamscanner.net/ru/docs/http-api/), а `spamc -L spam` работает с [сервером spamd](https://spamscanner.net/ru/docs/mail-servers/#a-drop-in-for-spamassassins-spamd) при `--allow-tell`. [IMAPSieve в Dovecot](https://spamscanner.net/ru/docs/mail-servers/#dovecot-junk-folder-and-learning) может вызывать любой из них при перемещении письма.

Из Node.js:

```js
await scanner.learn(raw, 'spam');
await scanner.unlearn(raw, 'spam'); // undo, before learning it as ham
scanner.saveModel('/var/lib/spamscanner/model.json');
```

Если письмо, о котором сообщили как о неверно классифицированном, уже было выучено, его нужно сначала разучить из неверного класса, а затем выучить в правильном.


## Встроенная модель

`model/classifier.json` собирается командой `npm run model:train` из этих публичных наборов данных на Hugging Face, все под открытыми лицензиями:

| Набор данных                                                                                                                                                                                                                                                                                                               | Лицензия                           | Содержимое                      |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | ------------------------------- |
| [FredZhang7/all-scam-spam](https://huggingface.co/datasets/FredZhang7/all-scam-spam)                                                                                                                                                                                                                                       | Apache-2.0                         | Сообщения и письма на 43 языках |
| [SetFit/enron_spam](https://huggingface.co/datasets/SetFit/enron_spam)                                                                                                                                                                                                                                                     | Публичный исследовательский корпус | Корпус Enron-Spam               |
| [alt-gnome/telegram-spam](https://huggingface.co/datasets/alt-gnome/telegram-spam)                                                                                                                                                                                                                                         | CC0-1.0                            | Русские сообщения из Telegram   |
| [tanaos/synthetic-spam-detection-dataset-german](https://huggingface.co/datasets/tanaos/synthetic-spam-detection-dataset-german), [-italian](https://huggingface.co/datasets/tanaos/synthetic-spam-detection-dataset-italian), [-spanish](https://huggingface.co/datasets/tanaos/synthetic-spam-detection-dataset-spanish) | MIT                                | Синтетические сообщения         |

Она обучена на 62 480 письмах со спамом и 76 489 письмах ham. Скрипт откладывает каждое десятое письмо, обучается на остальных и измеряет только классификатор, без остальных проверок:

| Отложенный тест | Письма | Точность | Полнота | Ложные срабатывания | Unsure |
| --------------- | -----: | -------: | ------: | ------------------: | -----: |
| Английский      |  6 564 |  100,0 % |  97,0 % |               0,0 % |  2,4 % |
| Русский         |  1 682 |  100,0 % |  97,4 % |               0,0 % |  2,2 % |
| Итальянский     |  1 389 |   98,1 % |  85,3 % |               1,8 % | 10,9 % |
| Немецкий        |  1 309 |   97,7 % |  76,1 % |               2,2 % | 20,7 % |
| Испанский       |  1 281 |   97,5 % |  82,5 % |               2,6 % | 16,8 % |
| Enron-Spam      |  2 888 |  100,0 % |  93,1 % |               0,0 % |  4,5 % |
| all-scam-spam   |  4 236 |  100,0 % |  88,8 % |               0,0 % | 11,2 % |
| Всего           | 13 840 |   99,2 % |  85,1 % |               0,5 % | 12,4 % |

Спамом здесь считается вероятность по оценке классификатора 99 % и выше — точка, в которой один классификатор достигает порога спама. При проверке спам, в котором он менее уверен, всё равно получает баллы, а остальные проверки добавляют свои.

Результаты для немецкого, испанского и итальянского получены на синтетических наборах данных, где есть почти одинаковые письма, размеченные и как спам, и как ham: часть ошибки заключена в разметке, а не в модели. Лучшее решение — почта на ваших собственных языках. Числа для каждого языка и набора данных находятся в `metadata.metrics` модели.

### Больше языков

`npm run model:train -- --with multilingual-sms` добавляет [SMS Spam Multilingual Collection](https://huggingface.co/datasets/dbarbedillo/SMS_Spam_Multilingual_Collection_Dataset): коллекцию SMS Spam Collection, машинно переведённую на 21 язык. Она не входит во встроенную модель, потому что в её карточке указана лицензия GPL; проверьте, подходит ли это для того, как вы распространяете модель. С ней результаты на отложенных письмах для языков, которые встроенная модель почти не знает, были такими:

| Язык        | Письма | Точность | Полнота | Ложные срабатывания |
| ----------- | -----: | -------: | ------: | ------------------: |
| Китайский   |    430 |  100,0 % |  82,3 % |               0,0 % |
| Арабский    |    430 |  100,0 % |  84,6 % |               0,0 % |
| Корейский   |    412 |  100,0 % |  80,4 % |               0,0 % |
| Японский    |    486 |   96,0 % |  85,7 % |               0,5 % |
| Хинди       |    412 |  100,0 % |  63,9 % |               0,0 % |
| Французский |    480 |   98,6 % |  94,2 % |               0,6 % |
| Турецкий    |    220 |  100,0 % |  73,1 % |               0,0 % |

### Переобучение

```sh
npm run model:train                       # downloads the datasets into data/ the first time
npm run model:train -- --no-download      # reuse data/
npm run model:train -- --out /tmp/model.json --max-features 200000
```


## Файл модели

Модель — это JSON-файл: количество выученных писем со спамом и ham, а для каждого хешированного признака — сколько писем со спамом и ham его содержали, в отсортированном виде и в кодировке base64. В нём нет ни слов, ни текста писем. `--max-features` оставляет только самые частые признаки, а `--min-count` отбрасывает редкие, жертвуя точностью ради размера; встроенная модель хранит 400 000 признаков примерно в 6 МБ.

Модели из Spam Scanner 6 и более ранних версий загрузить нельзя: они хешировали другие признаки. Обучите новую на той же почте.
