Javascript 无Cookie或本地存储的用户识别

Javascript 无Cookie或本地存储的用户识别,javascript,php,http-headers,fingerprinting,Javascript,Php,Http Headers,Fingerprinting,我正在构建一个分析工具,目前我可以从用户代理获取用户的IP地址、浏览器和操作系统 我想知道是否有可能在不使用cookie或本地存储的情况下检测到相同的用户?我不期望这里有代码示例;这只是一个简单的提示,说明在哪里可以进一步查看 忘了提到,如果是同一台计算机/设备,则需要跨浏览器兼容。基本上,我追求的是设备识别,而不是真正的用户。这种在没有cookie甚至没有ip地址的情况下检测相同用户的技术称为浏览器指纹识别。基本上,您可以尽可能地抓取有关浏览器的信息—使用javascript、flash或ja

我正在构建一个分析工具,目前我可以从用户代理获取用户的IP地址、浏览器和操作系统

我想知道是否有可能在不使用cookie或本地存储的情况下检测到相同的用户?我不期望这里有代码示例;这只是一个简单的提示,说明在哪里可以进一步查看


忘了提到,如果是同一台计算机/设备,则需要跨浏览器兼容。基本上,我追求的是设备识别,而不是真正的用户。

这种在没有cookie甚至没有ip地址的情况下检测相同用户的技术称为浏览器指纹识别。基本上,您可以尽可能地抓取有关浏览器的信息—使用javascript、flash或java f.ex可以获得更好的结果。已安装的扩展名、字体等。。之后,如果需要,可以存储散列的结果

这不是绝对正确的,但是:

83.6%的浏览者有独特的指纹;在那些启用Flash或Java的用户中,94.2%。这不包括饼干

更多信息:

导言

如果我理解正确,您需要识别一个没有唯一标识符的用户,因此您希望通过匹配随机数据来确定他们是谁。您无法可靠地存储用户的身份,因为:

可以删除Cookies IP地址可以更改 浏览器可以更改 浏览器缓存可能会被删除 Java小程序或Com对象本来是一个使用硬件信息散列的简单解决方案,但是现在人们非常安全,很难让人们在他们的系统上安装这些类型的程序。这让你不得不使用cookie和其他类似的工具

Cookie和其他类似工具

<>你可以考虑建立一个数据配置文件,然后使用概率测试来识别一个可能的用户。可通过以下组合生成对此有用的配置文件:

IP地址 真实IP地址 代理IP地址用户经常重复使用同一个代理 曲奇饼 HTTP Cookies 会话Cookies 第三方Cookies 闪光饼干 Web bug不太可靠,因为bug得到了修复,但仍然有用 PDF错误 闪光虫 Java错误 浏览器 单击“跟踪”可跟踪每次访问同一系列页面的多个用户 浏览器指纹 -安装的插件人们通常有各种各样的、有些独特的插件集 缓存图像人们有时删除cookie,但保留缓存图像 使用水滴 URL浏览器历史记录或cookie可能在URL中包含唯一的用户id,例如或 这是一个鲜为人知但通常是唯一的密钥签名 HTML5和Javascript HTML5地理定位API和反向地理编码 体系结构、操作系统语言、系统时间、屏幕分辨率等。 网络信息API 电池状态API 当然,我列出的项目只是唯一识别用户的几种可能方式。还有很多

使用这组随机数据元素来构建数据概要文件,下一步是什么

下一步是开发一些,或者更好的是,一个使用模糊逻辑的系统。在任何一种情况下,我们的想法都是训练您的系统,然后将其训练与提高结果的准确性相结合

PHP库允许您生成人工神经网络。要实现贝叶斯推断,请查看以下链接:

此时,您可能会想:

为什么一个看似简单的任务需要这么多数学和逻辑

基本上,因为这不是一项简单的任务。事实上,你想要达到的是纯粹的概率。例如,给定以下已知用户:

User1 = A + B + C + D + G + K
User2 = C + D + I + J + K + F
当您收到以下数据时:

B + C + E + G + F + K
你基本上要问的问题是:

接收到的数据B+C+E+G+F+K实际上是User1或User2的概率是多少?这两个匹配中哪一个最有可能

为了有效地回答这个问题,您需要理解为什么可能是更好的方法。这里的细节太多了,所以我给大家提供了链接,但一个很好的例子是a,它使用症状的组合来识别可能的疾病

在上面的示例中,将一系列数据点作为症状,将未知用户视为疾病,这些数据点构成您的数据配置文件B+C+E+G+F+K。通过识别疾病,您可以进一步确定适当的治疗方法,将该用户视为用户1

显然,一种我们已经确定了不止一种症状的疾病更容易识别。事实上,我们能识别的症状越多,我们的诊断就越容易和准确

还有其他选择吗

当然。作为一种替代措施,您可以创建自己的简单评分算法,并基于精确匹配。这不如概率效率高,但对您来说实现起来可能更简单

作为一个例子,考虑这个简单的得分图表:

+-------------------------+--------+------------+ | Property | Weight | Importance | +-------------------------+--------+------------+ | Real IP address | 60 | 5 | | Used proxy IP address | 40 | 4 | | HTTP Cookies | 80 | 8 | | Session Cookies | 80 | 6 | | 3rd Party Cookies | 60 | 4 | | Flash Cookies | 90 | 7 | | PDF Bug | 20 | 1 | | Flash Bug | 20 | 1 | | Java Bug | 20 | 1 | | Frequent Pages | 40 | 1 | | Browsers Finger Print | 35 | 2 | | Installed Plugins | 25 | 1 | | Cached Images | 40 | 3 | | URL | 60 | 4 | | System Fonts Detection | 70 | 4 | | Localstorage | 90 | 8 | | Geolocation | 70 | 6 | | AOLTR | 70 | 4 | | Network Information API | 40 | 3 | | Battery Status API | 20 | 1 | +-------------------------+--------+------------+ D的打印:

x1到x20表示由代码转换的特征

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;
    }
}
这是一个

使用的类别:

改进感知器类

结论

识别一个没有唯一标识符的用户不是一项直接或简单的任务。它取决于收集足够数量的随机数据,您可以通过各种方法从用户那里收集这些数据


即使您选择不使用人工神经网络,我建议您至少使用一个简单的概率矩阵,其中包含优先级和可能性-我希望上面提供的代码和示例足以让您继续下去。

上述指纹打印工作正常,但仍可能出现结肠炎

一种方法是将UID添加到与用户的每次交互的url中


网站中的每个链接都使用此修改器进行调整。这类似于ASP.Net在页面之间使用表单数据的工作方式。

您可以使用ETag来完成。虽然我不确定这是否是一系列法律诉讼


如果您正确地警告您的用户,或者如果您有类似于intranet网站的东西,这可能是可以的

效率低下,但可能会给您期望的结果,这将是轮询您这边的API。在客户端有一个后台进程,每隔一段时间发送用户数据。您需要一个用户标识符才能发送到API。一旦你有了它,你就可以发送任何与该唯一标识符相关的信息


这就消除了对cookie和本地存储的需要。

您可以使用缓存的png来实现这一点,不同浏览器的行为会有所不同,如果用户清除缓存,则会失败,但这是一个选项

1:设置将唯一用户id存储为十六进制字符串的数据库

2:创建一个genUser.php或任何生成用户id的语言文件,将其存储在数据库中,然后创建一个真彩色.png。根据该十六进制字符串的值,每个像素将为4个字节,并将其返回到浏览器。确保设置内容类型和缓存头

3:在HTML或JS中创建一个像

4:将该图像绘制到画布ctx.drawImagedocument.getElementById'user_id',0,0

5:使用ctx.getImageData读取该图像的字节,并将整数转换为十六进制字符串

6:这是您的唯一用户id,现在缓存在您的用户计算机上

你调查过吗? 它可以跨浏览器工作,也可以不跨浏览器工作。他们网站的摘录

如果用户在一个浏览器上被烧焦并切换到另一个浏览器, 只要他们还有本地共享对象cookie,cookie
将在两种浏览器中复制。

您可能会创建一个blob来存储设备标识符

缺点是用户需要下载blob, 因为浏览器无法访问文件系统直接保存文件

参考:


根据你所说的:

基本上,我是在追求设备识别,而不是真正的用户

最好的方法是发送mac地址,即NIC ID

你可以看看这篇文章:

创建一个跨平台的虚拟nsapiplugin,并在用户下载插件时(如登录后)为插件名称或版本生成一个唯一的名称。 为插件提供安装程序/根据策略进行安装 这将要求用户自愿安装标识符

一旦安装了插件,任何启用插件的浏览器的页面都将包含此特定插件。要将信息返回到服务器,需要一种算法来有效地检测客户端的插件,否则IE和Firefox>=28用户将需要一个可能的有效标识表

这需要对一项可能被浏览器供应商关闭的技术进行相对较高的投资。当您能够说服用户安装插件时,还可以选择安装、使用vpn或修补网络驱动程序

不想被识别的用户或他们的机器总会找到一种方法来阻止它。

我不敢相信,这里还没有提到! 该网站描述了模式识别方面的许多功能,这些功能可用于构建分类器


当然,为了评估这些特性,我建议使用支持向量机,尤其是。

在会话期间或跨会话跟踪它们


如果您的站点到处都是HTTPS,那么您可以使用TLS会话ID来跟踪用户的会话

实际上不是这样的-至少不是您可以依赖的任何准确的方式。也许这三者加起来就是一个杂凑,但是如果一个房子里有多个人使用同一个浏览器和操作系统,它仍然不起作用。此外,大多数ISP都提供动态IP地址,这意味着它们经常更改,也无法用于识别目的。这样你就不知道会话是什么了。您的用例正是会话设计的目的。会话与登录或aut无关

hentication。您的web服务器将告诉客户端发送带有会话标识符的cookie。您使用他们发送给您的会话id识别该客户端。Cookie是否仍然有效?为什么你要避免使用cookies?它非常简单,我一直都在使用它,请用户输入用户名和密码!!!在这种情况下,这里有一个最小的javascript解决方案—非跨浏览器:我提到它,因为它让我意识到许多插件默认情况下是跨浏览器安装的,用户没有任何选择。仔细地整理这些不是一个小任务,但仍然。。。可能会导致一个更大的基于设备的指纹的有形浏览器不可知属性。我认为,这仍然是答案。如果你需要识别一个设备,你只需要获取那些数据-f.ex。操作系统、通用扩展及其版本、已安装的字体等等……这些都不会很好地工作。每个浏览器都支持会话和cookie。对作业使用正确的工具。@slash197文件缓存如何?我的意思是,使用1px 1px透明闪存媒体以及包含唯一生成id的xml文件,应该在服务器上创建一次,然后以这种方式下载到用户本地HD,即使用户删除cookie或注销,您仍然可以使用操作脚本sendAndLoad方法进行桥接。最小的更改将影响哈希结果。例如冲击波播放器的版本。可能的解决方案是使用本地存储的xml缓存文件生成唯一密钥+在浏览器上隐藏1px x 1px闪存介质操作脚本,这样可以消除cookie、会话过期问题(如果这是主要问题)。您仍然可以在sql数据库和用户本地计算机上的密钥之间建立桥梁。@Mbarry我不太喜欢flash,但如果在浏览器中有一个类似于我的flash阻止插件,1x1像素flash介质将被禁用,我是rught吗?我想到了这一点,但这是用户修改它的最简单的方法,而不是id是一个自引用散列。使其加密安全。此外,当有人浏览网站时,这种方法也可以,但是当一个返回的用户在一周后回来,只需键入网站地址而没有id时,您建议如何处理这种情况?@slash197在这种情况下,您为什么不告诉用户登录,即使用户删除Cookie,也会发生这种情况。ETag不支持跨浏览器。ETag是HTTP/1.1规范的一部分。所有流行的浏览器都支持ETag,您几乎需要编写自己的浏览器来不支持ETag/如果没有匹配的头。我没有说它不支持,我说它不支持跨浏览器兼容。如果一个标签保存在Firefox中,那么它在chrome中不可用,所以内容将再次下载,因为没有缓存。现在我明白你的意思了。你说得对。每个浏览器都有自己的缓存存储,因此有不同的ETag。我想知道它是否可以在禁用JavaScript的情况下工作。你有经验吗?它叫evercookie是有原因的,不管怎样它都能用。他们几乎不可能把饼干拿走,不管怎样都不行。从描述的第一行开始:“evercookie是一个javascript API…”。如果禁用javascript,它将不起作用。甚至不必禁用js。鬼魂和乌布洛克掉落evercookie@Baba你说用BLOB给浏览器添加指纹是什么意思?@billmarky@Baba你怎么用它来给浏览器添加指纹?只要在任何给定的时间检查一下其中当前有什么?@Baba做得很好,我一直尝试使用一些多级策略来识别用户,但正如你所说的,缓存可以被清除,IP可以被更改,代理或NAT后面的用户-尤其是那些人-cookies可以被删除,等等。。但是,即使付出了这么多的努力,这也是一个概率问题,例如,如果坏用户正在使用Tor浏览器,那么上述大多数(如果不是全部的话)检测策略都不会起作用。我喜欢Tor,但所有Tor都没有定义或不知道。这只是一个注释,旨在清除出版物中的一些灰尘:截至2017年9月7日的断开链接列表:-使用PHP实现贝叶斯推断,所有3部分。-频率与概率-联合概率-输入传感器和期望值、初始权重、输出传感器、和、网络、错误、校正和最终权重欢迎使用堆栈溢出。请注意;这将要求用户自愿安装标识符。这可能不是海报的原意。对不起,NIC ID很容易搞笑。这绝对不是最好的方法。@asgs浏览器指纹识别可能会更好,或者你认为什么是最好的方法?没有最好的方法,这是最悲哀的部分。然而,在我看来,浏览器指纹识别和Baba在上面介绍的概率研究相结合是最好的。他想要的东西可以跨浏览器跟踪用户,但在这里不起作用。每个浏览器都有自己的缓存数据库。你在哪里看到的,他的问题只是要求忘记提到它会起作用
uld需要跨浏览器兼容,即在任何浏览器中工作。他的问题写得很差。我想说的是,设备识别是他想要的东西的赠品,他在这里详细阐述了:这里的问题是如何?
Possible Match D (0.7416|0.16853),C (0.5393|0.2809)
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
)
+----+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+-----+----+---------+---------+---------+---------+---------+---------+---------+---------+---------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------+
| 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         |
| -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | --  | --  | --  | --  | --  | --  | --  | --  | --  | --  | --  | --   | --  | -- | --      | --      | --      | --      | --      | --      | --      | --      | --      | --       | --       | --       | --       | --       | --       | --       | --       | --       | --       | --       | -- | -- | -- | -- | -- | -- | -- | -- | -- | --  | --  | --  | --  | --  | --  | --  | --  | --  | --  | --  | --        |
+----+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+-----+----+---------+---------+---------+---------+---------+---------+---------+---------+---------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------+
// Get RNA Labels
$labels = array();
$n = 1;
foreach ( $features as $k => $v ) {
    $labels[$k] = "x" . $n;
    $n ++;
}
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;
    }
}
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