Introdução
Se o entendi corretamente, você precisa identificar um usuário para quem não possui um identificador exclusivo, para descobrir quem eles são combinando dados aleatórios. Você não pode armazenar a identidade do usuário de maneira confiável porque:
- Cookies podem ser excluídos
- Endereço IP pode mudar
- O navegador pode mudar
- O cache do navegador pode ser excluído
Um Java Applet ou Com Object seria uma solução fácil usando um hash de informações de hardware, mas hoje em dia as pessoas têm tanta segurança que seria difícil convencer as pessoas a instalar esses tipos de programas em seu sistema. Isso deixa você com o uso de cookies e outras ferramentas semelhantes.
Cookies e outras ferramentas similares
Você pode considerar criar um perfil de dados e usar testes de probabilidade para identificar um usuário provável . Um perfil útil para isso pode ser gerado por alguma combinação do seguinte:
- Endereço de IP
- Endereço IP real
- Endereço IP do proxy (os usuários geralmente usam o mesmo proxy repetidamente)
- Biscoitos
- Web Bugs (menos confiável porque os bugs são corrigidos, mas ainda são úteis)
- Bug em PDF
- Flash Bug
- Java Bug
- Navegadores
- Acompanhamento de cliques (muitos usuários visitam a mesma série de páginas em cada visita)
- Impressão digital de navegadores - Plugins instalados (as pessoas geralmente têm conjuntos variados e únicos de plugins)
- Imagens em cache (às vezes as pessoas excluem seus cookies, mas deixam imagens em cache)
- Usando Blobs
- URL (s) (o histórico do navegador ou os cookies podem conter IDs de usuário únicos nos URLs, como https://stackoverflow.com/users/1226894 ou http://www.facebook.com/barackobama?fref=ts )
- Detecção de fontes do sistema (esta é uma assinatura de chave pouco conhecida, mas geralmente única)
- HTML5 e Javascript
- LocalStorage HTML5
- API de localização geográfica em HTML5 e geocodificação reversa
- Arquitetura, idioma do sistema operacional, hora do sistema, resolução de tela etc.
- API de informações de rede
- API de status da bateria
Os itens que listei são, obviamente, apenas algumas maneiras possíveis de identificar um usuário de forma exclusiva. Há muito mais.
Com esse conjunto de elementos de dados aleatórios dos quais construir um perfil de dados, o que vem a seguir?
O próximo passo é desenvolver algum Fuzzy Logic , ou, ainda melhor, uma Rede Neural Artificial (que usa a lógica fuzzy). Em qualquer um dos casos, a idéia é treinar seu sistema e, em seguida, combinar seu treinamento com a Inferência Bayesiana para aumentar a precisão de seus resultados.
A biblioteca NeuralMesh para PHP permite gerar redes neurais artificiais. Para implementar a inferência bayesiana, confira os seguintes links:
Neste ponto, você pode estar pensando:
Por que tanta matemática e lógica para uma tarefa aparentemente simples?
Basicamente, porque não é uma tarefa simples . O que você está tentando alcançar é, de fato, Probabilidade pura . Por exemplo, considerando os seguintes usuários conhecidos:
User1 = A + B + C + D + G + K
User2 = C + D + I + J + K + F
Quando você recebe os seguintes dados:
B + C + E + G + F + K
A pergunta que você está fazendo essencialmente é:
Qual é a probabilidade de os dados recebidos (B + C + E + G + F + K) serem realmente Usuário1 ou Usuário2? E qual dessas duas partidas é mais provável?
Para responder efetivamente a essa pergunta, é necessário entender o formato de frequência versus probabilidade e por que a probabilidade conjunta pode ser uma abordagem melhor. Os detalhes são muitos para entrar aqui (e é por isso que eu estou fornecendo links), mas um bom exemplo seria um Aplicativo para Assistente de Diagnóstico Médico , que usa uma combinação de sintomas para identificar possíveis doenças.
Pense por um momento na série de pontos de dados que compõem seu Perfil de Dados (B + C + E + G + F + K no exemplo acima) como Sintomas e Usuários Desconhecidos como Doenças . Ao identificar a doença, você pode identificar ainda um tratamento apropriado (tratar esse usuário como Usuário1).
Obviamente, é mais fácil identificar uma doença para a qual identificamos mais de 1 sintoma . De fato, quanto mais sintomas pudermos identificar, mais fácil e preciso será nosso diagnóstico.
Há alguma outra alternativa?
Claro. Como medida alternativa, você pode criar seu próprio algoritmo de pontuação simples e basear-se em correspondências exatas. Isso não é tão eficiente quanto a probabilidade, mas pode ser mais simples de implementar.
Como exemplo, considere este gráfico de pontuação simples:
+ ------------------------- + -------- + ------------ +
| Propriedade Peso Importância
+ ------------------------- + -------- + ------------ +
| Endereço IP real | 60 5
| Endereço IP do proxy usado | 40 4
| Cookies HTTP | 80 8
| Cookies de sessão | 80 6
| Cookies de terceiros | 60 4
| Cookies em Flash | 90 7
| Bug em PDF | 20 1 |
| Bug em Flash | 20 1 |
| Bug do Java | 20 1 |
| Páginas Frequentes | 40 1 |
| Impressão digital de navegadores | 35 2
| Plugins instalados | 25 1 |
| Imagens em cache | 40 3
| URL 60 4
| Detecção de fontes do sistema | 70 4
| Armazenamento local | 90 8
| Geolocalização | 70 6
| AOLTR 70 4
| API de informações de rede | 40 3
| API de status da bateria | 20 1 |
+ ------------------------- + -------- + ------------ +
Para cada informação que você pode reunir em uma determinada solicitação, conceda a pontuação associada e use a Importância para resolver conflitos quando as pontuações forem iguais.
Prova de conceito
Para uma simples prova de conceito, dê uma olhada no Perceptron . O Perceptron é um modelo de RNA geralmente usado em aplicações de reconhecimento de padrões. Existe até uma classe PHP antiga que a implementa perfeitamente, mas você provavelmente precisaria modificá-la para seus propósitos.
Apesar de ser uma ótima ferramenta, o Perceptron ainda pode retornar vários resultados (possíveis correspondências); portanto, o uso de uma comparação de Pontuação e Diferença ainda é útil para identificar a melhor dessas correspondências.
Premissas
- Armazene todas as informações possíveis sobre cada usuário (IP, cookies etc.)
- Onde o resultado é uma correspondência exata, aumente a pontuação em 1
- Onde o resultado não for uma correspondência exata, diminua a pontuação em 1
Expectativa
- Gere marcadores de RNA
- Gere usuários aleatórios emulando um banco de dados
- Gere um único usuário desconhecido
- Gerar RNA e valores de usuário desconhecido
- O sistema mesclará informações de RNA e ensinará o Perceptron
- Após treinar o Perceptron, o sistema terá um conjunto de pesos
- Agora você pode testar o padrão do usuário desconhecido e o Perceptron produzirá um conjunto de resultados.
- Armazenar todos os resultados positivos
- Classifique as partidas primeiro por Pontuação e depois por Diferença (conforme descrito acima)
- Gera as duas correspondências mais próximas ou, se nenhuma correspondência for encontrada, gera resultados vazios
Código de Prova de Conceito
$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 ";
}
Resultado:
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
)
Se Debug = true, você poderá ver Entrada (Sensor e Desejado), Pesos Iniciais, Saída (Sensor, Soma, Rede), Erro, Correção e Pesos Finais .
+----+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+-----+----+---------+---------+---------+---------+---------+---------+---------+---------+---------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------+
| 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 a x20 representam os recursos convertidos pelo código.
// Get RNA Labels
$labels = array();
$n = 1;
foreach ( $features as $k => $v ) {
$labels[$k] = "x" . $n;
$n ++;
}
Aqui está uma demonstração online
Classe usada:
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 modificada
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
Conclusão
Identificar um usuário sem um Identificador Único não é uma tarefa direta ou simples. depende da coleta de uma quantidade suficiente de dados aleatórios, que você pode coletar do usuário por vários métodos.
Mesmo se você optar por não usar uma rede neural artificial, sugiro pelo menos usar uma matriz de probabilidade simples com prioridades e probabilidades - e espero que o código e os exemplos fornecidos acima lhe dêem o suficiente para continuar.