Skip to content

Add MLP trainer based on DB data - #1

Merged
ChristophWurst merged 1 commit into
masterfrom
feature/mlp-trainer
Dec 13, 2018
Merged

Add MLP trainer based on DB data#1
ChristophWurst merged 1 commit into
masterfrom
feature/mlp-trainer

Conversation

@ChristophWurst

Copy link
Copy Markdown
Member

This is my very first implementation of a neural net classifier based on the multilayer perceptron classifier from php-ml.

In order to get okay-ish results I had to

  • hash the UIDs, convert to binary representation but use just a few bits to keep the feature vector smaller
  • convert the IPs to binary representation in order to allow the net learn patterns of IP addresses like their hierarchy (rather than learning the decimal representation)
  • generate negatives samples ...
    • ... by mixing known UIDs with unrelated IPs
    • ... by generating random IPs

All that is now available as a occ command with various optional parameters:

occ suspiciouslogin:train:mlp --help
Usage:
  suspiciouslogin:train:mlp [options]

Options:
      --shuffled[=SHUFFLED]                ratio of shuffled negative samples [default: 1]
      --random[=RANDOM]                    ratio of random negative samples [default: 1]
  -e, --epochs[=EPOCHS]                    number of epochs to train [default: 5000]
  -l, --layers[=LAYERS]                    number of hidden layers [default: 6]
      --learn-rate[=LEARN-RATE]            learning rate [default: 0.050000000000000003]
      --validation-rate[=VALIDATION-RATE]  relative size of the validation data set [default: 0.14999999999999999]
  -h, --help                               Display this help message

Some of the most recent training runs yield results like this:

Got 1375 samples for training: 573 positive, 401 random negative and 401 shuffled negative
Got 102 positive and 102 negative samples for validation (rate: 0.15)
Number of epochs: 1000
Number of hidden layers: 6
Learning rate: 0.01
Vecor dimensions: 48
Start training
Training finished after 118s
Prescision(y): 0.8141592920354
Prescision(n): 0.89010989010989
Recall(y): 0.90196078431373
Recall(n): 0.79411764705882
Average(precision): 0.85213459107264
Average(recall): 0.84803921568627
Average(f1score): 0.84759609591517

Roughly translated to our problem this means 80 to 90% of the addresses classified as 'n' (negative) are really from suspicious logins. On the other hand, of all logins from unknown IPs we detect ~80%.

Ref https://en.wikipedia.org/wiki/Precision_and_recall

Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
@ChristophWurst ChristophWurst added the enhancement New feature or request label Dec 12, 2018
@ChristophWurst ChristophWurst self-assigned this Dec 12, 2018
@ChristophWurst

Copy link
Copy Markdown
Member Author

Got 1375 samples for training: 573 positive

FYI: This 573 unique (uid, ip) combinations are the result of roughly two weeks of data collection of two dozen users.

@ChristophWurst
ChristophWurst merged commit 442aae7 into master Dec 13, 2018
@ChristophWurst
ChristophWurst deleted the feature/mlp-trainer branch December 13, 2018 07:54
@YoSiJo YoSiJo mentioned this pull request Sep 16, 2019
ChristophWurst added a commit that referenced this pull request Feb 14, 2024
fix #745 ValueError: random_int(): Argument #1 ($min) must be less than or equal to argument #2 ($max)
ChristophWurst added a commit that referenced this pull request Feb 15, 2024
[stable26] fix #745 ValueError: random_int(): Argument #1 ($min) must be less than or equal to argument #2 ($max)
ChristophWurst added a commit that referenced this pull request Feb 15, 2024
[stable28] fix #745 ValueError: random_int(): Argument #1 ($min) must be less than or equal to argument #2 ($max)
ChristophWurst added a commit that referenced this pull request Feb 15, 2024
[stable27] fix #745 ValueError: random_int(): Argument #1 ($min) must be less than or equal to argument #2 ($max)
ChristophWurst added a commit that referenced this pull request Jun 29, 2026
#1113)

rubix/ml and rubix/tensor expose free functions and constants through
Composer's "files" autoloader (PHP does not autoload functions). That loader
dedupes process-wide on a content-blind identifier, md5("<package>:<path>").
Another app shipping a php-scoped copy of rubix (recognize) registers the very
same identifier - php-scoper rewrites the namespace inside the file but not the
package name or path - so whichever app boots first loads its copy and the
other is skipped.

When the scoped copy wins, our un-scoped 'Rubix\ML\sigmoid' is never defined and
login crashes with "Tensor\Matrix::map(): Argument #1 ($callback) must be of
type callable, string given".

Load the rubix files explicitly in Application::register() via RubixBootstrap.php;
require_once dedupes by realpath, so our copies are always defined and it is a
no-op when our own autoloader won the race. Wire the regression test up to the
bootstrap so it now passes.

Fixes #1113

Assisted-by: ClaudeCode:claude-opus-4-8
Signed-off-by: Christoph Wurst <1374172+ChristophWurst@users.noreply.github.com>
ChristophWurst added a commit that referenced this pull request Jun 29, 2026
rubix/ml and rubix/tensor expose free functions and constants through
Composer's "files" autoloader, which dedupes process-wide on a content-blind
identifier md5("<package>:<path>"). Several apps bundle rubix and register the
same identifier, so only the first to boot loads its copy:

  - recognize php-scopes rubix, so when it wins only prefixed symbols exist and
    our un-scoped Rubix\ML\sigmoid is missing -> login crashes with
    "Tensor\Matrix::map(): Argument #1 ($callback) must be of type callable";
  - mail ships rubix un-scoped, so it defines the same Rubix\ML\* symbols we do.

Load our rubix files from Application::register() via RubixBootstrap.php, but
only when the symbols are actually missing, guarding on a representative symbol
per file. Requiring unconditionally would fatal with "Cannot redeclare function
Rubix\ML\argmin()" whenever another un-scoped copy already loaded them
(require_once dedupes by realpath, not by symbol).

Fixes #1113

Assisted-by: ClaudeCode:claude-opus-4-8
Signed-off-by: Christoph Wurst <1374172+ChristophWurst@users.noreply.github.com>
ChristophWurst added a commit that referenced this pull request Jun 29, 2026
rubix/ml and rubix/tensor expose free functions and constants through
Composer's "files" autoloader, which dedupes process-wide on a content-blind
identifier md5("<package>:<path>"). Several apps bundle rubix and register the
same identifier, so only the first to boot loads its copy:

  - recognize php-scopes rubix, so when it wins only prefixed symbols exist and
    our un-scoped Rubix\ML\sigmoid is missing -> login crashes with
    "Tensor\Matrix::map(): Argument #1 ($callback) must be of type callable";
  - mail ships rubix un-scoped, so it defines the same Rubix\ML\* symbols we do.

Load our rubix files from Application::register() via RubixBootstrap.php, but
only when the symbols are actually missing, guarding on a representative symbol
per file. Requiring unconditionally would fatal with "Cannot redeclare function
Rubix\ML\argmin()" whenever another un-scoped copy already loaded them
(require_once dedupes by realpath, not by symbol).

Fixes #1113

Assisted-by: ClaudeCode:claude-opus-4-8
Signed-off-by: Christoph Wurst <1374172+ChristophWurst@users.noreply.github.com>
ChristophWurst added a commit that referenced this pull request Jul 3, 2026
Add a regression test that reproduces #1113 in a fresh subprocess: it
simulates another app (e.g. recognize, which ships a php-scoped copy of
rubix) winning Composer's "files" autoload dedupe race by pre-marking
rubix's file identifiers as loaded, then asserts that sigmoid activation -
which relies on the string callable 'Rubix\ML\sigmoid' - still works.

Without the fix the un-scoped rubix functions.php is skipped, so
'Rubix\ML\sigmoid' is never defined and inference crashes with
"Tensor\Matrix::map(): Argument #1 ($callback) must be of type callable,
string given". This test currently fails on purpose to demonstrate the
bug; the fix follows in a separate commit.

Assisted-by: ClaudeCode:claude-opus-4-8
Signed-off-by: Christoph Wurst <1374172+ChristophWurst@users.noreply.github.com>
ChristophWurst added a commit that referenced this pull request Jul 3, 2026
rubix/ml and rubix/tensor expose free functions and constants through
Composer's "files" autoloader, which dedupes process-wide on a content-blind
identifier md5("<package>:<path>"). Several apps bundle rubix and register the
same identifier, so only the first to boot loads its copy:

  - recognize php-scopes rubix, so when it wins only prefixed symbols exist and
    our un-scoped Rubix\ML\sigmoid is missing -> login crashes with
    "Tensor\Matrix::map(): Argument #1 ($callback) must be of type callable";
  - mail ships rubix un-scoped, so it defines the same Rubix\ML\* symbols we do.

Load our rubix files from Application::register() via RubixBootstrap.php, but
only when the symbols are actually missing, guarding on a representative symbol
per file. Requiring unconditionally would fatal with "Cannot redeclare function
Rubix\ML\argmin()" whenever another un-scoped copy already loaded them
(require_once dedupes by realpath, not by symbol).

Fixes #1113

Assisted-by: ClaudeCode:claude-opus-4-8
Signed-off-by: Christoph Wurst <1374172+ChristophWurst@users.noreply.github.com>
ChristophWurst added a commit that referenced this pull request Jul 3, 2026
rubix/ml and rubix/tensor expose free functions and constants through
Composer's "files" autoloader, which dedupes process-wide on a content-blind
identifier md5("<package>:<path>"). Several apps bundle rubix and register the
same identifier, so only the first to boot loads its copy:

  - recognize php-scopes rubix, so when it wins only prefixed symbols exist and
    our un-scoped Rubix\ML\sigmoid is missing -> login crashes with
    "Tensor\Matrix::map(): Argument #1 ($callback) must be of type callable";
  - mail ships rubix un-scoped, so it defines the same Rubix\ML\* symbols we do.

Load our rubix files from Application::register() via RubixBootstrap.php, but
only when the symbols are actually missing, guarding on a representative symbol
per file. Requiring unconditionally would fatal with "Cannot redeclare function
Rubix\ML\argmin()" whenever another un-scoped copy already loaded them
(require_once dedupes by realpath, not by symbol).

Fixes #1113

Assisted-by: ClaudeCode:claude-opus-4-8
Signed-off-by: Christoph Wurst <1374172+ChristophWurst@users.noreply.github.com>
ChristophWurst added a commit that referenced this pull request Jul 15, 2026
Add a regression test that reproduces #1113 in a fresh subprocess: it
simulates another app (e.g. recognize, which ships a php-scoped copy of
rubix) winning Composer's "files" autoload dedupe race by pre-marking
rubix's file identifiers as loaded, then asserts that sigmoid activation -
which relies on the string callable 'Rubix\ML\sigmoid' - still works.

Without the fix the un-scoped rubix functions.php is skipped, so
'Rubix\ML\sigmoid' is never defined and inference crashes with
"Tensor\Matrix::map(): Argument #1 ($callback) must be of type callable,
string given". This test currently fails on purpose to demonstrate the
bug; the fix follows in a separate commit.

Assisted-by: ClaudeCode:claude-opus-4-8
Signed-off-by: Christoph Wurst <1374172+ChristophWurst@users.noreply.github.com>
ChristophWurst added a commit that referenced this pull request Jul 15, 2026
rubix/ml and rubix/tensor expose free functions and constants through
Composer's "files" autoloader, which dedupes process-wide on a content-blind
identifier md5("<package>:<path>"). Several apps bundle rubix and register the
same identifier, so only the first to boot loads its copy:

  - recognize php-scopes rubix, so when it wins only prefixed symbols exist and
    our un-scoped Rubix\ML\sigmoid is missing -> login crashes with
    "Tensor\Matrix::map(): Argument #1 ($callback) must be of type callable";
  - mail ships rubix un-scoped, so it defines the same Rubix\ML\* symbols we do.

Load our rubix files from Application::register() via RubixBootstrap.php, but
only when the symbols are actually missing, guarding on a representative symbol
per file. Requiring unconditionally would fatal with "Cannot redeclare function
Rubix\ML\argmin()" whenever another un-scoped copy already loaded them
(require_once dedupes by realpath, not by symbol).

Fixes #1113

Assisted-by: ClaudeCode:claude-opus-4-8
Signed-off-by: Christoph Wurst <1374172+ChristophWurst@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant