introduction
Si je vous comprends bien, vous devez identifier un utilisateur pour lequel vous n'avez pas d'identifiant unique, vous voulez donc savoir qui il est en faisant correspondre des données aléatoires. Vous ne pouvez pas stocker l'identité de l'utilisateur de manière fiable car:
- Les cookies peuvent être supprimés
- L'adresse IP peut changer
- Le navigateur peut changer
- Le cache du navigateur peut être supprimé
Une applet Java ou un objet Com aurait été une solution simple en utilisant un hachage d'informations matérielles, mais de nos jours, les gens sont tellement conscients de la sécurité qu'il serait difficile d'amener les gens à installer ce type de programmes sur leur système. Cela vous laisse coincé avec l'utilisation de cookies et d'autres outils similaires.
Cookies et autres outils similaires
Vous pouvez envisager de créer un profil de données, puis d'utiliser des tests de probabilité pour identifier un utilisateur probable . Un profil utile pour cela peut être généré par une combinaison des éléments suivants:
- Adresse IP
- Adresse IP réelle
- Adresse IP du proxy (les utilisateurs utilisent souvent le même proxy à plusieurs reprises)
- Biscuits
- Bogues Web (moins fiables car les bogues sont corrigés, mais toujours utiles)
- Bug PDF
- Bug Flash
- Bogue Java
- Navigateurs
- Suivi des clics (de nombreux utilisateurs visitent la même série de pages à chaque visite)
- Browsers Finger Print - Plugins installés (les gens ont souvent des ensembles de plugins variés et quelque peu uniques)
- Images en cache (les gens suppriment parfois leurs cookies mais laissent des images en cache)
- Utilisation de blobs
- URL (s) (l'historique du navigateur ou les cookies peuvent contenir des identifiants d'utilisateur uniques dans les URL, telles que https://stackoverflow.com/users/1226894 ou http://www.facebook.com/barackobama?fref=ts )
- Détection des polices système (il s'agit d'une signature de clé peu connue mais souvent unique)
- HTML5 et Javascript
- HTML5 LocalStorage
- API de géolocalisation HTML5 et géocodage inversé
- Architecture, langue du système d'exploitation, heure du système, résolution de l'écran, etc.
- API d'informations sur le réseau
- API d'état de la batterie
Les éléments que j'ai énumérés ne sont, bien sûr, que quelques façons possibles d'identifier un utilisateur de manière unique. Il y en a beaucoup plus.
Avec cet ensemble d'éléments de données aléatoires à partir desquels créer un profil de données, quelle est la prochaine étape?
L'étape suivante consiste à développer une logique floue , ou, mieux encore, un réseau neuronal artificiel (qui utilise la logique floue). Dans les deux cas, l'idée est de former votre système, puis de combiner son entraînement avec l'inférence bayésienne pour augmenter la précision de vos résultats.
La bibliothèque NeuralMesh pour PHP vous permet de générer des réseaux de neurones artificiels. Pour implémenter l'inférence bayésienne, consultez les liens suivants:
À ce stade, vous pensez peut-être:
Pourquoi tant de mathématiques et de logique pour une tâche apparemment simple?
Fondamentalement, parce que ce n'est pas une tâche simple . Ce que vous essayez d'atteindre, c'est en fait la pure probabilité . Par exemple, compte tenu des utilisateurs connus suivants:
User1 = A + B + C + D + G + K
User2 = C + D + I + J + K + F
Lorsque vous recevez les données suivantes:
B + C + E + G + F + K
La question que vous vous posez essentiellement est:
Quelle est la probabilité que les données reçues (B + C + E + G + F + K) soient réellement User1 ou User2? Et laquelle de ces deux correspondances est la plus probable?
Afin de répondre efficacement à cette question, vous devez comprendre le format Fréquence vs Probabilité et pourquoi la probabilité conjointe pourrait être une meilleure approche. Les détails sont trop difficiles à entrer ici (c'est pourquoi je vous donne des liens), mais un bon exemple serait une application d'assistant de diagnostic médical , qui utilise une combinaison de symptômes pour identifier les maladies possibles.
Pensez un instant à la série de points de données qui composent votre profil de données (B + C + E + G + F + K dans l'exemple ci-dessus) en tant que symptômes et Utilisateurs inconnus en tant que maladies . En identifiant la maladie, vous pouvez en outre identifier un traitement approprié (traiter cet utilisateur comme Utilisateur1).
De toute évidence, une maladie pour laquelle nous avons identifié plus d'un symptôme est plus facile à identifier. En fait, plus nous pouvons identifier de symptômes , plus notre diagnostic est presque certain et précis.
Y a-t-il d'autres alternatives?
Bien sûr. Comme mesure alternative, vous pouvez créer votre propre algorithme de notation simple et le baser sur des correspondances exactes. Ce n'est pas aussi efficace que la probabilité, mais peut être plus simple à mettre en œuvre.
À titre d'exemple, considérons ce graphique de score simple:
+ ------------------------- + -------- + ------------ +
| Propriété | Poids | Importance |
+ ------------------------- + -------- + ------------ +
| Adresse IP réelle | 60 | 5 |
| Adresse IP proxy utilisée | 40 | 4 |
| Cookies HTTP | 80 | 8 |
| Cookies de session | 80 | 6 |
| Cookies tiers | 60 | 4 |
| Cookies Flash | 90 | 7 |
| Bug PDF | 20 | 1 |
| Flash Bug | 20 | 1 |
| Bug Java | 20 | 1 |
| Pages fréquentes | 40 | 1 |
| Navigateurs Empreinte digitale | 35 | 2 |
| Plugins installés | 25 | 1 |
| Images en cache | 40 | 3 |
| URL | 60 | 4 |
| Détection des polices système | 70 | 4 |
| Localstorage | 90 | 8 |
| Géolocalisation | 70 | 6 |
| AOLTR | 70 | 4 |
| API d'informations réseau | 40 | 3 |
| API d'état de la batterie | 20 | 1 |
+ ------------------------- + -------- + ------------ +
Pour chaque information que vous pouvez collecter sur une demande donnée, attribuez le score associé, puis utilisez Importance pour résoudre les conflits lorsque les scores sont identiques.
Preuve de concept
Pour une simple preuve de concept, jetez un œil à Perceptron . Perceptron est un modèle d'ARN qui est généralement utilisé dans les applications de reconnaissance de formes. Il existe même une ancienne classe PHP qui l'implémente parfaitement, mais vous devrez probablement la modifier pour vos besoins.
Bien qu'il s'agisse d'un excellent outil, Perceptron peut toujours renvoyer plusieurs résultats (correspondances possibles), il est donc toujours utile d'utiliser une comparaison de score et de différence pour identifier le meilleur de ces correspondances.
Hypothèses
- Stocker toutes les informations possibles sur chaque utilisateur (IP, cookies, etc.)
- Lorsque le résultat est une correspondance exacte, augmentez le score de 1
- Lorsque le résultat n'est pas une correspondance exacte, diminuez le score de 1
Attente
- Générer des étiquettes ARN
- Générer des utilisateurs aléatoires émulant une base de données
- Générer un seul utilisateur inconnu
- Générer un ARN et des valeurs utilisateur inconnus
- Le système fusionnera les informations d'ARN et apprendra au Perceptron
- Après avoir entraîné le Perceptron, le système aura un ensemble de pondérations
- Vous pouvez maintenant tester le modèle de l'utilisateur inconnu et le Perceptron produira un jeu de résultats.
- Stocker toutes les correspondances positives
- Trier les correspondances d'abord par score, puis par différence (comme décrit ci-dessus)
- Affiche les deux correspondances les plus proches ou, si aucune correspondance n'est trouvée, affiche des résultats vides
Code de preuve de concept
$features = array(
'Real IP address' => .5,
'Used proxy IP address' => .4,
'HTTP Cookies' => .9,
'Session Cookies' => .6,
'3rd Party Cookies' => .6,
'Flash Cookies' => .7,
'PDF Bug' => .2,
'Flash Bug' => .2,
'Java Bug' => .2,
'Frequent Pages' => .3,
'Browsers Finger Print' => .3,
'Installed Plugins' => .2,
'URL' => .5,
'Cached PNG' => .4,
'System Fonts Detection' => .6,
'Localstorage' => .8,
'Geolocation' => .6,
'AOLTR' => .4,
'Network Information API' => .3,
'Battery Status API' => .2
);
// Get RNA Lables
$labels = array();
$n = 1;
foreach ($features as $k => $v) {
$labels[$k] = "x" . $n;
$n ++;
}
// Create Users
$users = array();
for($i = 0, $name = "A"; $i < 5; $i ++, $name ++) {
$users[] = new Profile($name, $features);
}
// Generate Unknown User
$unknown = new Profile("Unknown", $features);
// Generate Unknown RNA
$unknownRNA = array(
0 => array("o" => 1),
1 => array("o" => - 1)
);
// Create RNA Values
foreach ($unknown->data as $item => $point) {
$unknownRNA[0][$labels[$item]] = $point;
$unknownRNA[1][$labels[$item]] = (- 1 * $point);
}
// Start Perception Class
$perceptron = new Perceptron();
// Train Results
$trainResult = $perceptron->train($unknownRNA, 1, 1);
// Find matches
foreach ($users as $name => &$profile) {
// Use shorter labels
$data = array_combine($labels, $profile->data);
if ($perceptron->testCase($data, $trainResult) == true) {
$score = $diff = 0;
// Determing the score and diffrennce
foreach ($unknown->data as $item => $found) {
if ($unknown->data[$item] === $profile->data[$item]) {
if ($profile->data[$item] > 0) {
$score += $features[$item];
} else {
$diff += $features[$item];
}
}
}
// Ser score and diff
$profile->setScore($score, $diff);
$matchs[] = $profile;
}
}
// Sort bases on score and Output
if (count($matchs) > 1) {
usort($matchs, function ($a, $b) {
// If score is the same use diffrence
if ($a->score == $b->score) {
// Lower the diffrence the better
return $a->diff == $b->diff ? 0 : ($a->diff > $b->diff ? 1 : - 1);
}
// The higher the score the better
return $a->score > $b->score ? - 1 : 1;
});
echo "<br />Possible Match ", implode(",", array_slice(array_map(function ($v) {
return sprintf(" %s (%0.4f|%0.4f) ", $v->name, $v->score,$v->diff);
}, $matchs), 0, 2));
} else {
echo "<br />No match Found ";
}
Production:
Possible Match D (0.7416|0.16853),C (0.5393|0.2809)
Print_r de "D":
echo "<pre>";
print_r($matchs[0]);
Profile Object(
[name] => D
[data] => Array (
[Real IP address] => -1
[Used proxy IP address] => -1
[HTTP Cookies] => 1
[Session Cookies] => 1
[3rd Party Cookies] => 1
[Flash Cookies] => 1
[PDF Bug] => 1
[Flash Bug] => 1
[Java Bug] => -1
[Frequent Pages] => 1
[Browsers Finger Print] => -1
[Installed Plugins] => 1
[URL] => -1
[Cached PNG] => 1
[System Fonts Detection] => 1
[Localstorage] => -1
[Geolocation] => -1
[AOLTR] => 1
[Network Information API] => -1
[Battery Status API] => -1
)
[score] => 0.74157303370787
[diff] => 0.1685393258427
[base] => 8.9
)
Si Debug = true, vous pourrez voir l' entrée (capteur et souhaité), les poids initiaux, la sortie (capteur, somme, réseau), l'erreur, la correction et les poids finaux .
+----+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+-----+----+---------+---------+---------+---------+---------+---------+---------+---------+---------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------+
| o | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | x10 | x11 | x12 | x13 | x14 | x15 | x16 | x17 | x18 | x19 | x20 | Bias | Yin | Y | deltaW1 | deltaW2 | deltaW3 | deltaW4 | deltaW5 | deltaW6 | deltaW7 | deltaW8 | deltaW9 | deltaW10 | deltaW11 | deltaW12 | deltaW13 | deltaW14 | deltaW15 | deltaW16 | deltaW17 | deltaW18 | deltaW19 | deltaW20 | W1 | W2 | W3 | W4 | W5 | W6 | W7 | W8 | W9 | W10 | W11 | W12 | W13 | W14 | W15 | W16 | W17 | W18 | W19 | W20 | deltaBias |
+----+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+-----+----+---------+---------+---------+---------+---------+---------+---------+---------+---------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------+
| 1 | 1 | -1 | -1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 0 | -1 | 0 | -1 | -1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | -1 | -1 | -1 | -1 | 1 | 1 | 0 | -1 | -1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 |
| -1 | -1 | 1 | 1 | 1 | 1 | 1 | 1 | -1 | -1 | -1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 1 | -1 | -1 | 1 | -19 | -1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -1 | -1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 |
| -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |
| 1 | 1 | -1 | -1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 19 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -1 | -1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 |
| -1 | -1 | 1 | 1 | 1 | 1 | 1 | 1 | -1 | -1 | -1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 1 | -1 | -1 | 1 | -19 | -1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -1 | -1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 |
| -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |
+----+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+-----+----+---------+---------+---------+---------+---------+---------+---------+---------+---------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------+
x1 à x20 représentent les caractéristiques converties par le code.
// Get RNA Labels
$labels = array();
$n = 1;
foreach ( $features as $k => $v ) {
$labels[$k] = "x" . $n;
$n ++;
}
Voici une démo en ligne
Classe utilisée:
class Profile {
public $name, $data = array(), $score, $diff, $base;
function __construct($name, array $importance) {
$values = array(-1, 1); // Perception values
$this->name = $name;
foreach ($importance as $item => $point) {
// Generate Random true/false for real Items
$this->data[$item] = $values[mt_rand(0, 1)];
}
$this->base = array_sum($importance);
}
public function setScore($score, $diff) {
$this->score = $score / $this->base;
$this->diff = $diff / $this->base;
}
}
Classe Perceptron modifiée
class Perceptron {
private $w = array();
private $dw = array();
public $debug = false;
private function initialize($colums) {
// Initialize perceptron vars
for($i = 1; $i <= $colums; $i ++) {
// weighting vars
$this->w[$i] = 0;
$this->dw[$i] = 0;
}
}
function train($input, $alpha, $teta) {
$colums = count($input[0]) - 1;
$weightCache = array_fill(1, $colums, 0);
$checkpoints = array();
$keepTrainning = true;
// Initialize RNA vars
$this->initialize(count($input[0]) - 1);
$just_started = true;
$totalRun = 0;
$yin = 0;
// Trains RNA until it gets stable
while ($keepTrainning == true) {
// Sweeps each row of the input subject
foreach ($input as $row_counter => $row_data) {
// Finds out the number of columns the input has
$n_columns = count($row_data) - 1;
// Calculates Yin
$yin = 0;
for($i = 1; $i <= $n_columns; $i ++) {
$yin += $row_data["x" . $i] * $weightCache[$i];
}
// Calculates Real Output
$Y = ($yin <= 1) ? - 1 : 1;
// Sweeps columns ...
$checkpoints[$row_counter] = 0;
for($i = 1; $i <= $n_columns; $i ++) {
/** DELTAS **/
// Is it the first row?
if ($just_started == true) {
$this->dw[$i] = $weightCache[$i];
$just_started = false;
// Found desired output?
} elseif ($Y == $row_data["o"]) {
$this->dw[$i] = 0;
// Calculates Delta Ws
} else {
$this->dw[$i] = $row_data["x" . $i] * $row_data["o"];
}
/** WEIGHTS **/
// Calculate Weights
$this->w[$i] = $this->dw[$i] + $weightCache[$i];
$weightCache[$i] = $this->w[$i];
/** CHECK-POINT **/
$checkpoints[$row_counter] += $this->w[$i];
} // END - for
foreach ($this->w as $index => $w_item) {
$debug_w["W" . $index] = $w_item;
$debug_dw["deltaW" . $index] = $this->dw[$index];
}
// Special for script debugging
$debug_vars[] = array_merge($row_data, array(
"Bias" => 1,
"Yin" => $yin,
"Y" => $Y
), $debug_dw, $debug_w, array(
"deltaBias" => 1
));
} // END - foreach
// Special for script debugging
$empty_data_row = array();
for($i = 1; $i <= $n_columns; $i ++) {
$empty_data_row["x" . $i] = "--";
$empty_data_row["W" . $i] = "--";
$empty_data_row["deltaW" . $i] = "--";
}
$debug_vars[] = array_merge($empty_data_row, array(
"o" => "--",
"Bias" => "--",
"Yin" => "--",
"Y" => "--",
"deltaBias" => "--"
));
// Counts training times
$totalRun ++;
// Now checks if the RNA is stable already
$referer_value = end($checkpoints);
// if all rows match the desired output ...
$sum = array_sum($checkpoints);
$n_rows = count($checkpoints);
if ($totalRun > 1 && ($sum / $n_rows) == $referer_value) {
$keepTrainning = false;
}
} // END - while
// Prepares the final result
$result = array();
for($i = 1; $i <= $n_columns; $i ++) {
$result["w" . $i] = $this->w[$i];
}
$this->debug($this->print_html_table($debug_vars));
return $result;
} // END - train
function testCase($input, $results) {
// Sweeps input columns
$result = 0;
$i = 1;
foreach ($input as $column_value) {
// Calculates teste Y
$result += $results["w" . $i] * $column_value;
$i ++;
}
// Checks in each class the test fits
return ($result > 0) ? true : false;
} // END - test_class
// Returns the html code of a html table base on a hash array
function print_html_table($array) {
$html = "";
$inner_html = "";
$table_header_composed = false;
$table_header = array();
// Builds table contents
foreach ($array as $array_item) {
$inner_html .= "<tr>\n";
foreach ( $array_item as $array_col_label => $array_col ) {
$inner_html .= "<td>\n";
$inner_html .= $array_col;
$inner_html .= "</td>\n";
if ($table_header_composed == false) {
$table_header[] = $array_col_label;
}
}
$table_header_composed = true;
$inner_html .= "</tr>\n";
}
// Builds full table
$html = "<table border=1>\n";
$html .= "<tr>\n";
foreach ($table_header as $table_header_item) {
$html .= "<td>\n";
$html .= "<b>" . $table_header_item . "</b>";
$html .= "</td>\n";
}
$html .= "</tr>\n";
$html .= $inner_html . "</table>";
return $html;
} // END - print_html_table
// Debug function
function debug($message) {
if ($this->debug == true) {
echo "<b>DEBUG:</b> $message";
}
} // END - debug
} // END - class
Conclusion
Identifier un utilisateur sans identifiant unique n'est pas une tâche simple ou directe. il dépend de la collecte d'une quantité suffisante de données aléatoires que vous pouvez collecter auprès de l'utilisateur par diverses méthodes.
Même si vous choisissez de ne pas utiliser de réseau neuronal artificiel, je suggère au moins d'utiliser une matrice de probabilité simple avec des priorités et des probabilités - et j'espère que le code et les exemples fournis ci-dessus vous en donneront assez pour continuer.