MIT-D4M-数据库信号处理笔记-全-

MIT D4M 数据库信号处理笔记(全)

001:大数据与机器学习的数学基础

以下内容采用知识共享许可协议提供。您的支持将帮助麻省理工开放课程持续提供高质量的免费教育资源。如需捐款或查看来自数百门MIT课程的其他材料,请访问 ocw.mit.edu

在本节课中,我们将探讨大数据与机器学习背后的核心数学思想。我们将从基本的数学概念出发,理解如何将这些强大的工具应用于看似非传统的数据类型,如图像和文本。

理想与现实:数学的桥梁

我们从一个非常基础的数学概念开始:圆。圆可以被定义为所有到一个固定点距离相等的点的集合。这是一个完美的、理想的数学概念。

然而,我们知道自然界中并不存在完美的圆。即使是我们绘制的圆,放大后也会由像素块组成。这个近似过程——我们拥有一个理想的数学概念,但知道它在现实中并不完美存在——是我们思考问题的一种高效方式。这种对理想形状的思考与操作,并将结果带回现实世界的方法,是许多科学和工程领域的基础,其思想根源可追溯到约2500年前的柏拉图哲学。

这种数学概念能如此有效地描述自然世界(以及虚拟世界),有时被称为“数学的不可思议的有效性”。

线性模型的力量

在大多数科学和工程领域的入门课程中,核心的理论基础通常是线性模型。例如,物理学中的 F=ma,或化学中的反应速率方程。

我们喜欢线性模型,是因为它们允许我们进行外推。如果我们有证据支持某个线性关系(如图中的实线),我们就可以相对有信心地预测没有数据区域的趋势,或将其应用到新领域。线性模型使我们能够进行这种推理。

当然,存在许多重要的非线性现象。但分析和计算非线性通常更复杂,需要更多数据和计算资源,并且外推能力非常有限。

线性与大数据

我们希望能在新兴的大数据领域,做到在其他领域已成功做到的事情。大数据通常涉及文本、图像等非传统科学数据。问题在于:如何将强大的线性概念引入这个感觉完全不同的领域?

首先,我们需要准确理解“线性”的数学含义。线性不仅仅指一条直线。其核心是分配律,例如:2*(3+4) = 2*3 + 2*4。这个性质是数学在其最基本层面上“有效”的原因。

关键在于,不仅传统的算术加法和乘法满足分配律。存在其他运算对也满足此性质,从而允许我们为不同类型的数据构建线性模型。

以下是几个重要的运算对示例:

  • 传统算术 为普通加法 (+), 为普通乘法 (×)。
  • Max-Plus代数 为取最大值 (max), 为普通加法 (+)。这在机器学习和神经网络(如ReLU激活函数)以及金融领域中非常重要。
  • 集合代数 为并集 (), 为交集 ()。这是关系型数据库查询优化的数学基础。

通过使用不同的运算对,我们可以线性地推理各种类型的数据。这是本课程的核心:揭示允许我们以线性方式思考那些原本不具明显线性特征的数据的数学原理。

从数字到复杂对象

我们可以进一步扩展这个概念。在分配律方程中,变量 A、B、C 不仅可以是简单的数字(标量),还可以是更复杂的对象:

  • 电子表格:A、B、C 可以是整个数据表。这是大数据处理的关键——将数据作为整体集合进行推理和转换。
  • 数据库表:与电子表格类似,结合并集/交集运算对,可以对整个数据库表进行线性推理。
  • 矩阵:对于学过线性代数的人来说,这是最熟悉的例子。矩阵运算与线性性紧密相连。
  • 图与网络:通过矩阵与图之间的对偶关系,我们可以用矩阵表示图或神经网络,并同样应用这些线性方程。

机器学习的突破与挑战

现在,让我们将话题转向机器学习,这是过去十年最令人兴奋的突破之一。深度学习神经网络自2015年左右兴起,实现了诸如计算机视觉和自然语言理解等曾被认为近乎不可能的任务。

机器学习领域的核心技术和问题早已有之。早在20世纪50年代中期的首次机器学习会议上,讨论的主题就包括神经网络、语言处理和视觉识别,其中多篇开创性论文来自MIT林肯实验室。当时,拥有4096字节内存的计算机已被认为能力超强,人们乐观地预计相关问题将在十年内解决。

然而,实际用了大约五十年。如今,我们知道了如何让深度学习工作,但仍不完全理解为何它工作得如此之好。历史上,从“如何”到“为何”的理解通常需要约二十年。本课程旨在提供更深的数学基础,以帮助获得这种理解,或许你们这一代能更快地找到答案。

神经网络揭秘

一个典型的神经网络结构如下:左侧是输入向量 y₀(例如,一张猫图片的像素值)。中间是多个隐藏层,每个圆圈代表一个神经元。连接神经元的线具有权重 W。每个神经元还有一个阈值或偏置 b。右侧是输出层,每个神经元代表一个可能的类别(如“猫”、“狗”)。

整个网络的前向传播过程可以用一个相对简洁的方程描述:
yᵢ₊₁ = H( yᵢ W + b )

其中:

  • yᵢ 是第 i 层的向量。
  • W 是连接第 i 层到第 i+1 层的权重矩阵。
  • b 是偏置向量。
  • H 是一个非线性函数,通常是线性整流单元 (ReLU),其规则很简单:如果输入值大于0则输出该值,否则输出0。

这个非线性函数 H 至关重要。如果没有它,整个多层网络可以合并为单一的矩阵乘法,就无法学习复杂模式。

机器学习的挑战在于:我们不知道权重 W、偏置 b、层数以及每层神经元数量应该是多少。解决方法是训练:使用大量已知标签的数据(如图片和对应的“猫”、“狗”标签),通过反向传播算法,根据网络预测与真实标签的误差,一点点调整所有 Wb。使用训练好的网络对新数据进行预测的过程称为推理

理解这个核心方程,就能在数学原理层面理解大多数机器学习工作。

理解原理的重要性

为什么理解“为何”有效很重要?主要有两个原因:

  1. 推广到新领域:如果你想将机器学习应用到语言、视觉以外的领域,需要理论指导来评估某个方法是否可能奏效,减少盲目试错。
  2. 安全与鲁棒性:研究表明,可以对图像进行人眼难以察觉的微小改动,使神经网络做出完全错误的判断(例如将贵宾犬识别为鸵鸟)。构建鲁棒的、抗欺骗的机器学习系统至关重要,而这离不开对其理论基础的深入理解。

本课程的核心思想

那么,本课程将如何帮助我们更深入地理解数据呢?核心思想体现在一种称为声明式数学数据的概念中。

我们引入一个名为关联数组的数学概念及其对应的代数。它统一了数据库、图和矩阵中使用的数据,并将其全部纳入一个线性系统。其关键运算包括我们之前讨论的加法 ()、乘法 (),以及最重要的矩阵乘法(或数组乘法),我们将其表示为 A ⊕.⊗ B。这个运算将是本课程后续内容的主要工具。

本节课中,我们一起学习了数学理想化与线性思维的力量,探讨了如何通过不同的代数运算对将线性模型应用于大数据,并概述了深度学习神经网络的基本原理及其面临的理论挑战。我们从基础的圆和分配律出发,最终指向了一个能够统一多种数据形式的强大数学框架,为后续深入探讨数据库上的信号处理奠定了概念基础。

002:引言 🎓

在本节课中,我们将要学习一门名为“数据库信号处理”的课程。这门课程旨在将信号处理、检测理论和线性代数的数学工具,应用于处理由文本、字符串和关系构成的新型数据领域。我们将探索如何利用这些强大的数学框架来分析图数据、社交网络、网络流量等非传统数值数据。


以下内容在知识共享许可协议下提供。您的支持将帮助麻省理工开放课程持续提供高质量的免费教育资源。如需捐款或查看来自数百门麻省理工课程的其他材料,请访问 ocw.mit.edu。

欢迎各位。很高兴今天在这里见到大家,这是九讲课程的第一讲。这门课程名为“数据库信号处理”。今天对我来说是非常特殊的一天,因为这标志着我过去几十年、特别是近三四年深入研究的一项技术迈出了重要一步。我们希望这门课程所介绍的技术未来能被广泛使用。

这是一门关于全新技术的独特课程。因此,我要感谢你们成为这门课程的“首批体验者”。开设一门现有主题的新课程已属不易,而开设一门全新主题的课程则更具挑战。我们将尝试一些不同的方法,某些主题会从多个角度进行讲解,我们会观察哪些方式对你们最有效。

首先,我想谈谈课程标题,它有些与众不同。标题是“数据库信号处理”,这通常不是两个会联系在一起的短语。信号处理,众所周知,是我们在林肯实验室所做技术的核心之一。数学上,信号处理的核心是检测理论:如何在给定数据集中寻找目标,如何排除非目标并找到目标。而检测理论的数学核心是线性代数。这门技术正是为你们设计的,因为它假定你们具备线性代数基础,而这并非如想象中那么普遍。

第二个短语是“数据库”。我们想到数据库时,通常会想到网络搜索、文本等。这类数据与我们思考信号处理、检测理论和线性代数时想到的数学(如实数、矩阵)并不完全一致。我们在这里所做的,正是将这套数学工具与一个全新的数据分支(单词、字符串和关系)连接起来。这是本课程的核心创新点:我们将运用我们熟知的强大工具,尝试应用于这个对我们日益重要的新领域。这就是标题的由来。


有众多人士为这项工作做出了贡献。这张幻灯片列出了我能记起的人员名单,无疑我可能遗漏了某些重要人物,我为此表示歉意。但毫无疑问,这个团队的所有成员都对推动这项技术发展起到了重要作用。


以下是本讲的提纲,这也将是大多数课程的基本节奏:首先是通过PPT讲解介绍概念(抱歉,我们知道大家已经看了很多PPT),然后会进行演示,展示实际可用的代码(这些代码现在已可在你们的Lgrid账户中找到,部分代码甚至会成为作业的一部分)。因此,课程风格将是先讲解幻灯片,短暂休息后,再进行示例演示和讨论。


让我谈谈我们今天将要介绍的技术能够处理的一些数据类型。它使我们能够处理与关系相关的数据,特别是那些由图表示的关系。例如,车辆轨迹代表了位置之间的关系;社交网络代表了人与人之间的关系;网络领域则代表了计算机、网络服务器之间的通信关系。这些都是对我们非常重要的领域。

为了让大家了解我们为何关注此技术以及它能做什么,我将介绍一些我们使用该技术完成的、令人兴奋的实际案例。课程中也会有类似的示例,展示这项技术如何产生实际影响。我们首先要讨论的是在网络领域所做的一些工作。


这是一个数据集的示例,它是一个表示网络流量的图。这里可以看到源IP地址(可以想象为某个域内部的计算机)和目的IP地址(它们所访问的网络服务器)。图中绿色的点,在图论中有时被称为二分图,基本上是两组互相关联但组内无连接的顶点。图中这些淡蓝色的线就是实际的连接,显示了哪些计算机连接到哪些计算机。显然,网络流量数据量巨大,这张图显示了90分钟的数据。在这个特定案例中,数据集中实际上存在我们试图检测的恶意活动。


在处理此类数据集时,最大的挑战之一是数据表示。在数据库术语中,这有时被称为模式。如何表示数据通常是解决问题的关键。鉴于我们想使用图技术,我们发现数据几乎从不会以适合图的格式直接提供,因此我们必须经历许多步骤。使用这项技术时,它几乎总是从原始数据到数据分析再到查询的流水线中的一个环节。你们将希望为流水线的不同阶段使用不同的技术和工具。我们绝不试图暗示我们所讨论的技术是唯一的工具,它们只是重要的一环,但你们始终是在一个流水线的背景下工作。

这张图展示了我们通常在此类工作中利用的技术栈。我们的最终目标是进行图分析。本课程将重点讨论高级语言,它们能让你轻松地进行图分析,并桥接数据库本身和你所需的高性能计算。因此,不仅软件和数据有流水线,技术也有一个栈。解决此类问题时,通常从一个点开始,但在构建系统时,几乎总会涉及其他类型的技术。

以上只是一个快速概述和示例,我不期望大家完全理解。这个初步示例可能引发了更多疑问:到底如何实现?现在,我将稍微转变方向,真正谈谈这门课程的内容。我将从宏观角度谈谈这门课程。我一直认为,每门课程都应该告诉你,为什么它是你职业生涯中最重要的课程。当然,每门课你都能找到理由,但人们通常不会这么说。所以我要告诉你为什么这门课是最重要的。当然,每门课都是最重要的,但我要说说这门课的特殊之处。

退一步讲,我们都是麻省理工的一部分。麻省理工是地球上排名第一的理工科大学,这是任何合理衡量标准下的客观事实。麻省理工在过去60年取得了令人难以置信的成功,根本原因在于它认识到,做出发现的公式是理论与实验的结合。作为一个组织,我们实际上就是围绕这个公式组织的:理论部分由各院系负责,通常涉及数学;实验部分则是我们进行的研究,其核心是测量,也就是数据,通常以比特的形式存在。因此,实施麻省理工的这个公式,通常归结为在计算机中将软件和比特结合起来。这就是我们将这两个想法融合的地方,我认为这非常重要。

那么,我们用什么工具、什么机器来将它们结合起来呢?是计算机。如今,计算机比我们过去想象的要复杂得多。我们过去通常认为它们是冯·诺依曼机器,意味着有数据和操作(字节和软件),字节进入计算机的一部分,被操作,然后输出。这个模型在高层面上仍然成立,但我们现在处理的计算机要复杂得多。左边是一个标准的并行计算机,如今几乎每台计算机都类似这样:有处理器、内存、某种持久存储,以及连接它们的网络。这形成了一个非常复杂的内存层次结构,从顶部的寄存器开始,然后是缓存、本地内存、远程内存,最后是存储。这个层次结构有很多含义:带宽朝这个方向增加,延迟降低;可编程性通常增加(越近越无需担心);数据容量则朝这个方向减少。所有现代计算机都是具有多级内存层次的冯·诺依曼架构。从根本上说,你必须知道,这种架构决定了你使用的算法。如果算法不能在这种架构上良好运行,你可能就不会实现它们。因此,必须非常清楚算法相对于此架构的适应性。


本课程的目标是教授能让你更快完成工作的技术。这是一张相当复杂的图,但它展示了:给定一个你想解决的问题,如果使用不同的技术来实现,这些技术的性能如何。在某种意义上,你可以将此视为解决问题所需编写的代码量,我们以实现C语言程序作为参考点。如果你使用汇编或Java MapReduce等,最终会编写更多代码;如果你使用C++、Java或Matlab等,编写的代码则更少。同时,这张图也显示了相对性能:在汇编等低级环境中,可以获得更高性能;在Matlab等高级环境中,性能则较低。这是一个相当普遍的权衡空间。我们在这里添加了并行编程模型(有多种类型:直接内存访问、消息传递、管理者-工作者或MapReduce风格等)。我们希望达到的目标是:付出更少努力,获得更高性能。因此,本课程将主要关注Matlab和我们称为D4M的技术。D4M是一种软件组合,能让你以相对较少的努力获得较好的性能。这就是本课程的全部内容:如果你处理这类问题,有合适的工具,使用合适的工具将花费更少时间。我们将尝试教授这些工具及其工作原理。

这里有一点“诱饵调包”的意味:课程名为“数据库信号处理”,但我们要到最后才真正讲到数据库。原因是,数据库只擅长解决一类问题,而且通常是在最后阶段才真正需要用到数据库。为了理解这一点,我们有多种不同的方式用算法解决问题:我们可以只使用单台计算机的内存;可以使用多台计算机的内存;可以使用磁盘或存储,甚至使用多个磁盘。随着我们朝这个方向推进,我们可以处理越来越多、越来越多的数据。但真正重要的是,回到之前那张图:当我处理这些数据时,所需的块大小是多少?如果我需要以非常小的块获取数据,那么将数据放在内存中显然是最有效的方式。同样,如果我们决定需要处理更多数据,可以转向并行程序以获得更多内存,或者使用文件中的数据。我们可以将数据写入文件,读入、处理再写出,这种模型非常有效。特别是如果我们以大数据块访问数据,这种模型效果很好。对于非常大的数据集,我们甚至可以并行写入文件来处理极大问题。最后,我们才会回到需要数据库的情况:当我们有大量数据,但只想访问其中一小部分时(例如查找某些内容),这时才真正需要数据库。除非数据只能通过数据库访问,否则你应遵循这条路径:从串行程序内存开始,可能转向使用文件的并行程序,数据库几乎是最后的手段,因为它增加了大量复杂性。虽然它能更快地查找内容,但如果你需要遍历数据的很大一部分,你会发现它实际上比使用文件要慢得多。我们将向你展示技术,遵循这条路径,希望到最后你会明白:是的,我知道在这些情况下会使用数据库,但我意识到我可以用其他技术完成大部分工作,而且效果很好。因此,你将学到我们所谓的“快速路径”。

如果你想达到一定的性能水平,但没有或只有很少相关培训,通常会走这条路:开始使用一项技术,程序会暂时变慢,你可能会在网上苦苦挣扎几天,最终回到起点,然后慢慢爬升到某个性能水平,这可能需要几周时间。我们一次又一次地观察到这种现象。通常,人们会在这里放弃,并说这项技术对他们无效。本课程的目的就是给你专家所掌握的技术。专家可以使用这项技术,知道如何正确使用,然后走上这条路径:在几小时内就能获得良好性能,如果他们真的想达到更高水平,可以再花几天时间。这就是我们的整体理念。


本课程将引入一些新概念,这些概念你可能在其他课程中未曾遇到过。我们将大量讨论图。标准的图论或计算机科学课程中讨论的图,通常侧重于无向、无权图。图是一组顶点和边。无向无权意味着所有边都是相同的,边要么存在要么不存在,类似于0或1,并且连接没有方向性。大多数图论课程都基于此。然而,在现实世界中,我从未遇到过这种图。事实上,另一个常见概念是Erdős–Rényi图(随机连接图),我在现实世界中也从未遇到过。因此,当我们现在拥有所有这些数据并将理论与数据对比时,我们发现现有理论有所欠缺,它并不是进入数据领域的良好基石。我们看到真实数据并非随机,通常遵循幂律分布(某些顶点非常受欢迎,而大多数顶点连接很少)。边通常是有向的(例如,我加你为好友与你加我为好友意义不同)。边本身通常有权重(可以是数值、单词等)。通常可以有多个边(例如,我可以发送多个好友请求)。可能最重要的是,边通常是“超边”,这个概念在图论中几乎从未涉及,但非常重要:一条边可以同时连接多个顶点。一个典型例子是发送给多个人的电子邮件。实际上,如今大多数电子邮件都有多个收件人。一封发送给全班所有人的电子邮件就是一条超边,它连接了我与所有收件人。这与我单独给每个人发送电子邮件(标准边)非常不同。因此,我们将超越图的标准定义,这将是本课程的难点。我们还将处理线性代数的更广泛定义。传统线性代数处理具有行索引和列索引的矩阵,其值为实数、整数或复数。但我们将处理字符串等,将线性代数应用于单词等领域。这将是不同的。实际上,这可能是我最喜欢的部分,尽管可能会让你们昏昏欲睡。我们还将扩展处理的定义。在这个领域,你会听到很多流行技术,比如Hadoop及其MapReduce并行编程范式。这是一项让初学者入门的好技术。但对于具备我们这种数学素养的人来说,我们可以使用更好的编程模型。如果你理解数学到我们这个程度,我们可以为你提供并行计算机的编程工具,这些工具比那些为不具备线性代数背景的人设计的技术更高效(编写更少代码,获得更好性能)。这将是本课程的基础。


让我继续介绍课程大纲。这基本上是九讲课程的安排。我们现在是引言部分,回顾课程和目标。下一讲将真正讨论“关联数组”这个概念,这是将所有内容整合在一起的核心技术。当我谈到将线性代数扩展到单词时,会使用一种称为模糊代数的数学概念,这涉及到称为群论的抽象代数概念。它并不像听起来那么可怕,实际上相当优雅,了解它很有好处。了解当我们使用关联数组技术时,我们已经思考了这些事物如何协同工作的数学原理,这很重要。当我们添加一个功能时,通常是因为它在数学上合理;当我们不添加时,通常是因为某些请求可能导致不良后果。然后,我们将进入课程的核心部分,展示如何分析非结构化数据。在分析非结构化数据时,我们会讨论幂律,并有一整讲关于幂律数据建模。我们还有一讲关于互相关。最后两讲将真正深入并行处理和数据库。最后两讲甚至不会完全是讲座,而主要是演示,因为我们将把许多内容整合在一起,并带你浏览代码示例。我们需要花些时间带你浏览,因为你会看到我们讨论的所有想法在这些代码示例中汇聚。软件中已有所有讲座材料(在你的Lgrid账户中),我认为有第0讲到第7讲(第7讲是第八讲),然后第9讲将主要是浏览示例。


我们坚信从线性代数角度看待图这一理念,甚至为此写了一本书。你们都已获赠这本书的副本。这本书阐述了如果你使用这些技术思考图所带来的理念和力量。我强烈鼓励你翻阅它,本课程的某些部分几乎直接取自这本书,但很多是补充材料。你会发现,本课程实际上是通往这些技术的桥梁:它让你能够从混乱的非结构化单词和其他类型的数据出发,然后将它们放入这种良好的线性代数格式,从而应用书中描述的技术。因此,这几乎是一座桥梁,我当然鼓励你阅读它,并随时向我提问。


现在,我将回到最初的网络示例,更详细地讲解,并可能强调一些我已经谈过的事情。这是一个非常标准的数据处理流水线:我们有原始数据(本例中是网络数据),这是一系列记录。我们需要将其转换为一组顶点和边,然后从中进行图分析和图算法。通常,你需要编写解析器,将数据从某种原始格式转换为处理链下一步的格式,然后将这些边列表转换为邻接矩阵,这是我们看待图的方式。关于邻接矩阵,让我回到这里。

对于不了解的人,这是一个图:一组顶点和边。这是该图的邻接矩阵。基本上,每一行是一个顶点,每一列是一个顶点。如果边存在,我们就在这里放一个点。图论与线性代数之间存在形式上的对偶性。图论的基本操作是广度优先搜索(BFS):给定一个起始顶点,遍历其边到邻居。这里也有同样的操作:给定一个起始顶点向量,我们进行矩阵乘法来识别邻居。因此,在最根本的层面上,图论和线性代数是相连的,因为图论的基本操作是广度优先搜索,而线性代数的基本操作是向量-矩阵乘法。


我们希望形成这些邻接矩阵,以便利用这种联系。这里更详细地展示了我们将如何使用D4M技术来实现:我们有原始数据,将其转换为CSV文件(逗号分隔值文件,是电子表格和表格的默认格式,近年来变得非常流行)。然后,我们使用D4M技术读取这些CSV文件,将其插入分布式数据库。接着,我们可以查询该分布式数据库以获取关联数组。现在,从关联数组出发,我们拥有进行图算法的全部能力。因此,D4M真正帮助你在准备阶段(为进行图算法铺平道路)完成这些步骤。你甚至可以在完全不使用D4M的情况下进行图算法,只需在Matlab中使用常规线性代数操作即可。但D4M是连接器。事实上,我强烈建议人们只在D4M擅长的部分使用它,而在其他部分使用其他技术(如Matlab普通矩阵等)。这样你会获得更好的性能,对我来说也很好,因为如果你有问题,会去麻烦别人而不是我们,这对大家都有好处。


这是一个示例,展示了这看起来像什么。这是我们从数据中获取的代理日志,本质上是一个网络日志。我们首先要做的就是将其转换为CSV。基本上,每个条目获得一列,我们有一个实际条目的ID(本例中是源IP、服务器IP、时间戳),以及与之关联的实际文本行。这可以有很多列。如果你是标准的数据库人员(使用SQL,这是标准数据库语言),你可能会直接将这些数据制成具有这些列的表并插入。但我们发现,当你想快速查找数据,或者想以极高速度插入大量数据并仍能快速查找任何IP地址或时间戳时,挑战就出现了。SQL可能提供良好性能,但有些数据库和技术能提供更好的性能。这些数据库被称为三元组存储,因为它们将所有数据存储为三元组(行、列和关联值)。因此,我们做的是:获取我们的密集数据,并创建一个我们称之为“展开表”的结构。本质上,我们获取日志ID,附加列名及其值。我们称之为展开表。如果你像我一样是SQL老手,这种模式会立即让你感到不适甚至过敏,因为你创建了数量巨大的列。在SQL中,你可以动态创建列,但数据库保留在需要时重新复制和重新格式化整个表的权利。而三元组存储没有这个要求,它们只存储非零条目,因此无论你有多少列(十亿行、百亿列)都完全没问题。这是一个强大的特性。

但仅凭这个模式本身,对你绝对没有好处,你只是让事情变得更难了。因为大多数数据库系统(包括三元组存储)都有其取向:行取向或列取向。我们讨论的数据库几乎总是行取向的,这意味着它们可以在恒定时间内获取一行(给定行键),并快速返回给你。它们被设计为即使以每秒数百万条的速度插入数据,仍能在毫秒级查找内容,这是非常强大的技术。但到目前为止,我们只是让数据的输出格式变得奇怪,增加了难度。那么,回报在哪里呢?我们还存储了表的转置。对于熟悉稀疏线性代数的人来说,这是一个古老的技术:我们几乎总是存储矩阵A和它的转置A^T,因为某些操作在转置上更快,某些操作在原矩阵上更快。我们在这里做完全相同的事情。结合我们的展开模式,现在每个列都成了一行。通过一个简单的两表模式,我们索引了数据库中每个字符串。这并不是说你所有的数据库都只有两个表,但可能是三个或四个表,这相比标准数据库模式(可能有几十甚至上百个表)是巨大的简化。本质上,这是一个“一统天下”的模式:一个表及其配对表,你就索引了所有内容。代价是存储空间翻倍。但如果你问任何数据库人员:用存储空间翻倍的代价,换取对数据库中每个字符串的快速索引访问,他们百分之百会接受这个交易。这就是这项技术的威力:你可以引用巨大的数据集。随着我们深入学习关联数组,我们将反复回到这个模式,但这就是其背后的逻辑。到最后,你会明白我们这样做有充分的理由。


现在,我们已经将数据摄取到数据库中,可以进行查询了。如果我有一个表的绑定,D4M技术中一个很好的特性是:如果你使用一个表及其转置,我们会完全向你隐藏这一点。如果你向表中插入数据,它会自动插入转置;如果你按行查找,它会知道使用一个表;如果你按列查找,它会知道使用另一个表。这里我们进行了一个范围查询:给我这个时间范围内的所有数据(大约三小时窗口)。这是一个数据库查询,它返回一个关联数组的键。然后,我获取这些键对应的行(基本上我找到了特定时间戳内的每一行),现在我想获取整行数据,所以我将行键传回表中以获取实际数据。现在我得到了整行数据:服务器IP等完整列表。接下来,我要做一点代数运算:我想创建源IP和服务器IP的图。我通过这个相关性操作来实现,本质上是一个矩阵乘法。就这样,我从这个时间窗口的所有数据中构建了整个源IP-服务器IP图。这就是D4M的威力:它允许你对此类数据进行相关性分析,就像我们在线性代数中使用传统技术进行相关性分析一样。你可能会想:哇,这太有启发性了。在示例中,我们将深入探讨具体如何操作以及其工作原理,但这确实是人们使用D4M的目的:达到能够进行此类操作的阶段。检测理论的关键在于进行相关性分析。相关性让我们能够确定数据的背景和杂波,然后我们就可以运用我们熟知和喜爱的传统检测理论、信号处理技术等。这就是连接的具体体现。现在,我们有了一个关联数组或邻接矩阵,表示源IP及其服务器IP。我们实际上可以绘制它,这就是我们得到那张图的方式。如果我们获取这个图G的邻接矩阵,这就是它的样子,这就是我们构建它的方式,这是我们实际用来构建该图的代码。


继续前进,这张图展示了整个过程:我们有一整周的代理数据。这里显示了时间:大约花了两个小时进行数据摄取,大约三个小时进行这种处理。一亿条代理日志,445亿个三元组。这是大数据。这是真正的大数据。这是你可以做的事情,无论别人在做什么,你都应该能够使用这项技术在更大规模上完成。


这带我们来到本讲的结尾。总结一下:这类大数据存在于广泛的领域(如网络分析、DNA测序)。在人们传统上用来解决这类问题的工具与D4M这项技术之间存在差距。D4M填补了这个空白。


本节课中,我们一起学习了“数据库信号处理”课程的引言,了解了课程的目标、核心思想以及将要涵盖的技术概览。我们探讨了如何将线性代数和信号处理技术应用于图、文本和关系型数据,并初步了解了D4M技术在处理大规模、非结构化数据流水线中的桥梁作用。下一讲,我们将深入探讨“关联数组”这一核心概念。

003:示例演示 🚀

在本节课中,我们将通过一系列示例,学习D4M(数据库、数据、数据挖掘)的基本概念和操作。我们将从设置环境开始,逐步介绍如何创建、查询和操作D4M的核心数据结构——关联数组。

概述

D4M的核心思想是将线性代数与字符串操作相结合,以便高效处理大规模数据。本节课将演示如何设置D4M环境,创建关联数组,进行数据查询,以及执行基本的数学运算。


1. 环境设置与验证

首先,我们需要确保D4M软件已正确安装并配置。所有软件和示例都位于您的Eleggrid账户的toolsol目录下。

在Matlab中,最简单的验证方法是输入help D4M。如果命令返回D4M所有函数的列表,说明路径设置正确。

以下是验证步骤的代码示例:

help D4M

如果遇到任何问题,可以发送邮件至gridhelp@Ldonmt.u寻求帮助。


2. 创建关联数组

关联数组是D4M中的基本数据结构,它允许我们将行键、列键和字符串值关联起来。

在D4M中,字符串列表是一个字符行向量,其最后一个字符是分隔符。分隔符可以是逗号、分号、空格或换行符等。

以下是创建关联数组的步骤:

  1. 定义行键、列键和值:使用字符行向量定义行键、列键和对应的值。
  2. 使用构造函数:调用Assoc函数,传入行键、列键和值列表来创建关联数组。

以下是创建关联数组的代码示例:

% 定义行键、列键和值(逗号为分隔符)
R = 'a,a,a,a,a,a,'; % 行键列表
C = 'A,A,A,A,A,A,'; % 列键列表
V = 'A-A,A-A,A-A,A-A,A-A,A-A,'; % 值列表

% 创建关联数组
A = Assoc(R, C, V);

创建后,可以使用displayFull(A)命令以表格形式查看关联数组的内容。


3. 查询关联数组

关联数组支持多种查询方式,这些查询语法与操作内存中的数组或数据库中的表基本一致。

以下是几种常见的查询操作:

  • 按行键和列键查询:指定具体的行键和列键来获取数据。
  • 使用通配符查询:使用通配符(如*)来匹配包含特定字符串的行或列。
  • 范围查询:使用冒号(:)指定行或列的范围。
  • 按值查询:根据值的字符串比较结果来筛选数据。

以下是查询操作的代码示例:

% 获取行键为'a'和'b'的所有列
A('a,b', :);

% 获取所有包含'a'的行,以及第1到3列
A('*a*', 1:3);

% 获取所有值小于'B'的条目
A(Val < 'B');

需要注意的是,某些高级查询功能(如数值索引和值比较查询)可能并非在所有数据库后端都支持。


4. 对关联数组进行数学运算

关联数组不仅可以存储字符串,还可以转换为数值进行数学运算,这为数据分析提供了便利。

以下是进行数学运算的步骤:

  1. 转换为数值矩阵:使用double(logical(A))或简写dblLog(A)将关联数组转换为0-1矩阵。
  2. 执行运算:之后便可以像操作普通Matlab矩阵一样进行求和、转置、乘法等运算。

以下是数学运算的代码示例:

% 将关联数组转换为数值矩阵
A_num = dblLog(A);

% 对行求和(压缩行维度)
row_sum = sum(A_num, 1);

% 对列求和(压缩列维度)
col_sum = sum(A_num, 2);

% 计算A的转置乘以A,得到共现矩阵(相关性矩阵)
corr_matrix = sqIn(A_num);

对于更复杂的、D4M未直接支持的运算,可以使用adj(A)提取内部的稀疏矩阵进行操作,完成后再用putAdj放回关联数组结构中。


5. 其他构造方法

除了标准的三列表构造法,关联数组还支持多种便捷的构造方式,以处理各种边界情况和快速创建特定结构。

以下是一些其他构造方法的示例:

  • 处理空输入:如果输入为空,将返回一个空的关联数组。
  • 创建行/列向量:通过将行键或列键指定为标量字符串,可以快速创建行向量或列向量。
  • 统一赋值:可以为所有条目指定一个相同的数值或字符串值。

以下是其他构造方法的代码示例:

% 快速创建行向量:所有条目属于同一行'r1',不同列,不同数值
A_row = Assoc('r1,', 'c1,c2,c3,', [1 2 3]);

% 创建列向量:所有条目属于同一列'c1',不同行,值统一为字符串'a'
A_col = Assoc('r1,r2,r3,', 'c1,', 'a');

总结

本节课我们一起学习了D4M的入门操作。我们首先完成了环境设置与验证,然后掌握了关联数组的创建方法,包括使用字符串列表定义数据。接着,我们探索了多种查询关联数组的技巧,例如按键查询、通配符匹配和范围查询。最后,我们学习了如何将关联数组转换为数值矩阵以进行数学运算,并了解了一些便捷的数组构造方法。

请将示例代码复制到您的本地目录进行练习,确保所有功能正常运行,为后续更深入的学习和实践做好准备。

004:使用关联数组分析引文数据 📊

在本节课中,我们将学习如何利用D4M技术分析引文数据。我们将从原始数据出发,通过一系列步骤将其转换为便于分析的矩阵形式,并探讨如何从中提取有意义的关联信息。课程将涵盖数据处理流程、核心数据结构(关联数组与矩阵)以及从复杂数据中构建图的基本方法。


数据导入与模式设计 📥

上一节我们概述了课程目标,本节中我们来看看如何处理具体的引文数据。引文数据是学术分析中的常见例子,它结构相对清晰且易于获取。

原始数据通常以记录和列的表格形式(如SQL表)存在。我们采用一种称为“展开模式”的方法将其导入。在这种模式下,行键是原始记录,列键则由列标签和列值拼接而成。这样,每个唯一的字符串值都会拥有自己的列,从而形成一个大型稀疏结构。

将这种结构的转置也存入数据库后,我们就能以恒定时间快速查找任何字符串(如作者名、文档ID)。数据库中的值通常设为1,因为我们一般不会基于数值进行搜索(这需要遍历整个表,成本很高)。

对于数据集中较长的字段(如完整参考文献、标题、摘要),我们建议将其存储在单独的表中。这些“原始数据表”仅用于快速查阅完整内容,而不会干扰基于关键词的搜索索引。

为了支持文本分析(如关键词搜索),我们还会创建另一个表来索引标题和摘要中的所有单词及其N元语法(N-gram)。例如,单词“A”在标题中出现的位置信息会被记录。这样,我们既可以高效搜索,也可以在需要时重建原始文本。

以下是处理此类数据时常见的表结构:

  • 主表(展开模式):存储所有可用于搜索的字段及其关系,包含其转置以实现快速查找。
  • 原始数据表:存储完整的、未经处理的文本字段(如摘要全文),仅用于展示。
  • N元语法表:存储文档中所有单词及其位置信息,用于高效的文本分析。

数据处理流程 ⚙️

将原始数据转化为可分析的数据库,需要经过一个多步骤的管道。D4M技术是这个管道中的关键一环,但并非全部。

对于一个约150GB的XML压缩数据文件,我们的处理流程如下:

  1. 接收与解压:获取硬盘数据,解压缩XML文件(约1小时)。
  2. 解析为三元组:使用C++等高性能语言编写解析器,将XML转换为(行键,列键,值)形式的三元组(约2小时)。对于大规模数据解析,低级语言如C/C++通常比Python或D4M本身更高效。
  3. 构建关联数组:使用D4M将三元组读入,并在MATLAB中构建为“关联数组”数据结构(约1天)。关联数组是本节课的核心分析工具。
  4. 保存为文件:在将数据插入数据库之前,先将关联数组保存到文件。这作为一个检查点,方便在需要重新插入数据时(例如数据库损坏)快速进行,避免了重复耗时的解析过程。
  5. 插入数据库:将文件数据插入到我们设计好的三组数据库表中:主键表及其转置、N元语法表及其转置、原始文本表。在单个节点上,我们实现了每秒1万到10万条的插入速率。

值得注意的是,数据库擅长快速查询小部分数据,但遍历整个数据集的速度与插入速度相当。因此,如果需要对整个数据集进行分析,直接从文件系统读取保存的关联数组文件通常更快,也更容易实现并行处理。甚至对于需要反复查询同一部分数据(如3-4%的数据集)的任务,将其从数据库中查询出来并保存为文件进行后续分析,也是隔离数据库延迟、提升效率的好方法。


从关联数据到图分析 📈

我们存储数据的“展开模式”在数学上称为“关联矩阵”。在图论中,图通常可以用两种矩阵表示:邻接矩阵和关联矩阵。

  • 邻接矩阵:行和列都代表顶点。如果两个顶点间有边,则对应矩阵元素有值。它非常适合表示简单有向图,但难以处理多边图或超边(连接多个顶点的边)。
  • 关联矩阵:每一行代表一条边,每一列代表一个顶点。矩阵中的值表示这条边连接了哪些顶点。我们的引文数据包含丰富的连接关系(一篇文献连接着作者、机构、关键词等多个顶点),本质上是一种超边,因此更适合用关联矩阵无损地存储。

然而,我们通常的分析(如寻找合作作者)需要在顶点(如作者与作者)之间进行,这就需要从关联矩阵投影到邻接矩阵。这可以通过矩阵乘法实现。

给定关联矩阵 E,如果我们想分析作者之间的合作关系(即哪些作者共同出现在同一篇文献中),可以执行以下操作:

% 假设 E 是完整的关联矩阵
E_author = E(startsWith('author::'), :); % 提取所有作者相关的行
A_coauthor = E_author' * E_author;       % 内积,得到作者-作者邻接矩阵

结果矩阵 A_coauthor 是一个对称方阵。对角线上的值表示某位作者出现的次数,非对角线上的值则表示两位作者共同出现的次数。这便构成了经典的“合著者关系图”。

同样,我们也可以分析文献之间的关联(例如,哪些文献共享相同的关键词),这通过外积形式的乘法实现:

A_doc_shared_keyword = E_keyword * E_keyword'; % 外积,得到文档-文档邻接矩阵

这些“内平方”和“外平方”操作是进行分析的基础工具,它们将丰富的关联数据投影到更易处理的子空间上,尽管在此过程中会丢失一些原始信息(如具体的合作文献是哪一篇),但这是进行分析所必需的步骤。


超越传统图:处理复杂关系 🎨

传统的图论课程主要研究无向、无权重的简单图。然而,现实世界的数据关系要复杂得多。

考虑一幅由彩色线条构成的画作:

  • 它包含多种颜色的边(信息维度)。
  • 存在多重边(相同顶点间有多条边)。
  • 包含超边(一条边连接多个顶点)。
  • 线条的绘制顺序也可能包含信息。

如果使用传统简单图来表示,我们需要将超边分解为多条简单边,从而丢失了“这些边本属于同一整体”的信息。同时,边的颜色、顺序等信息也会被丢弃。

关联矩阵方法为解决此问题提供了方案。我们可以为每条边分配唯一的标签(如颜色、顺序ID),并在关联矩阵中记录每条边连接的所有顶点。这样,所有原始信息都得以保留。当需要进行特定分析时(例如,“只分析蓝色边构成的网络”),我们可以通过矩阵操作轻松地投影出相应的子图,而不会在数据存储阶段就丢失任何信息。


总结与核心要点 ✅

本节课我们一起学习了利用D4M技术分析引文数据的完整流程。

我们首先介绍了如何将原始的表格数据通过“展开模式”导入,并设计多张表来分别处理可搜索字段、原始文本和文本索引。接着,我们详细阐述了从原始XML文件到最终数据库的数据处理管道,强调了在适当环节使用文件缓存和选择合适工具(如C++用于解析)的重要性。

课程的核心是理解关联数组和两种矩阵表示法:关联矩阵(无损存储复杂关系)和邻接矩阵(用于实际分析)。我们通过矩阵乘法(如 E' * E)可以从关联矩阵投影出各种有意义的邻接矩阵(如合著者网络、文献相似度网络)。

最后,我们探讨了现实世界数据的复杂性,指出传统简单图表示的局限性,并展示了关联矩阵方法在保留多维度、多关系信息方面的强大能力。这种方法为我们处理和分析大规模、非结构化数据提供了坚实的数学和工程基础。

005:示例演示 🎨

在本节课中,我们将通过具体的代码示例,学习如何使用D4M(数据库、矩阵、数学)库进行图数据的处理和分析。我们将从一个艺术作品的关联数据入手,演示如何构建关联数组、计算邻接矩阵,并进行一些基本的图分析操作。


概述

我们将使用一个包含线条(边)和颜色(值)的艺术作品数据集。核心任务是:

  1. 从CSV文件读取数据并构建关联数组(Associative Array)。
  2. 从关联数据中提取顶点信息,构建关联矩阵。
  3. 使用矩阵运算计算顶点邻接矩阵和边邻接矩阵。
  4. 演示如何使用查询(如startsWith)和逻辑操作筛选数据。
  5. 介绍一种特殊的矩阵乘法catkeymul,用于在关联操作中保留连接键信息。

读取数据与构建关联数组

首先,我们需要将数据加载到MATLAB环境中。我们使用readCSV函数来读取一个CSV格式的数据文件。这个文件包含了艺术作品中每条边(例如线条)与各个属性(如顶点、颜色、顺序)的关联关系。

E = readCSV('art.csv');

执行上述代码后,变量E就成为了一个关联数组。我们可以使用displayFull(E)来查看它的完整结构。这个数组的行键(row keys)代表边(例如E1, E2),列键(column keys)代表属性(例如顶点V1, V2,颜色color,顺序order),而数组中的值则是对应的属性值(如颜色名称green,顺序数字1)。

关联数组的内部结构是经过字典序排序的,这保证了高效的数据检索和操作。


提取顶点并计算邻接矩阵

上一节我们介绍了如何构建完整的关联数组。本节中,我们来看看如何从中提取我们关心的部分——顶点数据,并进行图分析。

我们的目标是分析顶点之间的连接关系。因此,首先需要从完整的关联数组E中筛选出只包含顶点信息的子集。

以下是筛选顶点列并转换为数值矩阵的步骤:

% 1. 筛选所有以‘V’开头的列(即所有顶点)
EV = E(:, startsWith('V,'));

% 2. 将逻辑值转换为双精度数值(有值处为1,无值处为0)
EV = double(logical(EV));

现在,EV是一个只包含0和1的矩阵,表示了每条边(行)包含了哪些顶点(列)。

接下来,我们可以利用这个顶点-边关联矩阵来计算顶点之间的邻接关系。两个顶点如果被同一条边连接,那么它们就是相邻的。这可以通过矩阵的“内积”运算来实现。

% 计算顶点邻接矩阵:A = EV^T * EV
A_vertex = sqIn(EV);
displayFull(A_vertex);

运算结果A_vertex是一个对称矩阵。矩阵中(i, j)位置的值表示顶点i和顶点j共同出现在同一条边上的次数。值为0表示两个顶点没有直接连接。由于超边(一条边连接多个顶点)的存在,这个操作会在顶点之间形成“团”(clique)结构。

类似地,我们也可以计算边之间的邻接关系,即哪些边共享相同的顶点。

% 计算边邻接矩阵:A_edge = EV * EV^T
A_edge = sqOut(EV);
displayFull(A_edge);

矩阵A_edge(i, j)位置的值表示边i和边j共享的顶点数量。


数据查询与筛选操作

除了基本的矩阵运算,D4M还提供了灵活的数据查询功能。以下是两种常用的数据筛选方法。

第一种方法是基于值的精确匹配。例如,我们想找出所有颜色为“橙色”(orange)的边。

% 找出颜色列的值等于‘orange’的所有行
E_orange_rows = Row(E(:, ‘color‘) == ‘orange‘);
% 用这些行索引回原始数组,得到所有橙色边的完整信息
E_orange = E(E_orange_rows, :);
displayFull(E_orange);

第二种方法是基于键(key)的模式匹配,使用startsWith函数。这通常比正则表达式更高效,尤其是在处理数据库绑定时。

% 找出所有行键以‘O‘(橙色)或‘G‘(绿色)开头的边
E_og = E(startsWith(‘O,G‘, ‘,‘), :);
displayFull(E_og);

startsWith函数接受一个字符串列表,可以同时进行多个前缀查询,非常方便。


交叉关联与CatKeyMul操作

在实际分析中,我们经常需要比较两个不同集合的关联关系,这称为交叉关联(cross-correlation)。

假设我们有两个顶点-边矩阵,分别对应橙色边(EV_orange)和绿色边(EV_green)。我们想知道橙色边和绿色边通过顶点产生了怎样的关联。

% 计算橙色边和绿色边的交叉关联(内积)
cross_inner = transpose(EV_orange) * EV_green;
% 计算交叉关联(外积)
cross_outer = EV_orange * transpose(EV_green);

内积cross_inner的结果可能为空,这表示没有顶点同时属于一个橙色边和一个绿色边。而外积cross_outer则显示了每对橙色边和绿色边共享的顶点数量。

然而,外积操作丢失了具体的顶点信息,我们只知道连接的数量,而不知道是哪些顶点起到了连接作用。为了解决这个问题,D4M引入了CatKeyMul(连接键乘法)操作。

CatKeyMul在矩阵乘法过程中,不是对数值进行累加,而是将匹配上的公共键(这里是顶点)连接成一个列表,并保存在结果矩阵的值中。

% 使用CatKeyMul计算关联,并保留连接的顶点信息
correlation_with_keys = catkeymul(transpose(EV_orange), EV_green);
displayFull(correlation_with_keys);

结果不仅显示了哪些边是相连的,值字段还列出了连接它们的顶点列表。这是一个非常强大的功能,可以用于追溯数据关联的“谱系”。

需要注意的是:当对同一个矩阵使用CatKeyMul时,由于矩阵与自身相乘会产生密集的对角线(每条边与自身共享所有顶点),可能会生成极其冗长的列表,导致性能问题。因此,此操作更适用于两个不同集合的关联分析。


总结

本节课中我们一起学习了D4M库在图数据处理中的基本应用流程:

  1. 数据载入与表示:使用readCSV和关联数组来灵活表示图数据。
  2. 图分析基础:通过提取顶点-边矩阵(EV),并利用sqInsqOut计算顶点和边的邻接矩阵,从而分析图的连接结构。
  3. 灵活查询:掌握了基于值(==)和基于键模式(startsWith)的数据筛选方法。
  4. 高级关联分析:学习了交叉关联的概念,并认识了强大的CatKeyMul操作,它能在计算关联的同时保留具体的连接信息,为深入的数据溯源提供了工具。

通过本讲的示例,你应该已经具备了使用D4M处理简单图数据并进行分析的基础能力。建议你尝试完成课程作业:选择一幅图片,将其转化为线条图,标记顶点和边,构建关联矩阵,并计算其邻接矩阵,以加深理解。

006:群论 📚

在本节课中,我们将深入探讨关联数组的数学基础,特别是其背后的群论概念。我们将了解关联数组如何为电子表格和大表提供形式化的数学结构,并探索其与线性代数和集合论的联系。

概述 📋

关联数组是一种强大的数据结构,它将键(通常是字符串)映射到值。这种结构自然地统一了四种思考数据的方式:关联数组、三元组数据库、图以及矩阵。本节课的目标是建立关联数组的正式数学基础,使我们能够将信号处理和线性代数中的成熟工具应用于数据库领域。

什么是电子表格与大表? 📊

电子表格(如 Microsoft Excel)是地球上最常用的分析工具之一,每天有数亿人使用。大表则是指用于存储海量分析数据的大型三元组数据库,例如 Google 和 Amazon 所使用的系统。它们可以被视为巨大的电子表格,能够同时存储多样化的数据(字符串、日期、整数、实数等)。

然而,尽管这些结构被广泛使用,我们却缺乏一个正式的数学基础来描述它们。关联数组的概念恰好为此提供了一个优雅的数学结构。

关联数组的数学定义 🧮

关联数组可以被视为从一组键到值的部分函数。更正式地说,设 S 为一个无限、严格全序的集合(例如,按字典序排序的字符串集合,或实数集)。一个 D 维关联数组 A 是一个部分函数:

A: K₁ × K₂ × ... × K_D → V

其中 K_i ⊆ S 是键的集合,V ⊆ S 是值的集合。对于大多数应用,D=2,即行键和列键。关联数组只存储定义了值的键值对,其余部分视为“未定义”或“空”。

这与传统线性代数中的矩阵不同,矩阵的索引是整数,并且明确包含零值。在关联数组的实现中,零值通常被视为等同于“空”。

二元运算与碰撞函数 ⚙️

对两个关联数组进行二元运算(如加法、逻辑与)时,涉及两个关键函数:

  1. 键处理函数 (g):决定如何处理两个数组的键集。通常是取并集 (∪)交集 (∩)。例如,加法通常对应并集,而逻辑与通常对应交集。
  2. 值碰撞函数 (f):当两个数组在同一个键上都有定义(即发生“碰撞”)时,此函数决定如何合并两个值 v1v2

运算过程如下:

  • 首先,根据函数 g 确定结果数组的键集(K₃ = g(K₁, K₂))。
  • 对于结果键集中的每个键:
    • 如果该键只在其中一个输入数组中有定义,则直接采用该值(若 g 是并集)或忽略(若 g 是交集)。
    • 如果该键在两个输入数组中都有定义(碰撞),则使用碰撞函数 f 计算新值:v₃ = f(v₁, v₂)

这种设计保证了运算的封闭性:对关联数组的任何操作都返回另一个关联数组,这使得我们可以组合多个操作,形成易于理解和推理的运算链。

从群论到半环 🧬

上一节我们介绍了关联数组的基本运算,本节中我们来看看这些运算如何形成更高级的代数结构。通过为运算 fg 选择特定的函数,我们可以让关联数组具备不同的代数性质。

我们首先关注那些构成半群的运算。半群要求运算满足结合律,即 (a ∘ b) ∘ c = a ∘ (b ∘ c)。在众多可能的碰撞函数中,有18种能形成结合运算。

进一步,如果运算还满足交换律(即 a ∘ b = b ∘ a),则构成阿贝尔半群(或称交换半群)。在这18种中,有14种满足交换律。

当我们为一对运算(一个充当“加法”,一个充当“乘法”)分配好角色,并且它们满足分配律时,就形成了一个半环。半环是环的推广,它不要求存在加法逆元(即没有“减法”)。在196对可能的运算组合中,有74对构成半环。

对于数据分析而言,半环尤为重要,因为许多图算法(如广度优先搜索)都可以归结为在半环上进行的矩阵乘法。常见的例子包括:

  • 传统矩阵乘法:(+, ×) 半环。
  • 最短路径算法:(min, +) 半环。
  • 最大容量路径算法:(max, min) 半环。

向量空间与“域” 🚀

为了应用完整的线性代数工具,我们最终希望构建向量空间。传统的向量空间定义在“域”上(如实数域、复数域),域要求存在加法逆元。

然而,在关联数组的世界里,许多值(如字符串)没有自然的“负”概念。因此,我们转向一种称为 “域” 的结构——这是一种具备域的大部分性质(如分配律、存在零元和幺元),但不要求加法逆元的代数结构。

在满足条件的半环中,我们可以进一步定义标量乘法(将标量作用于关联数组的每一个值),从而构建出定义在“域”上的向量空间(或称“半向量空间”)。这使我们能够在关联数组上应用许多线性代数的概念,如子空间、张成空间等,尽管像线性无关性这类依赖于逆元的概念需要重新审视。

关联数组的一个巨大优势是维度通用性:任何两个关联数组都可以进行加法和乘法,无论它们的行键和列键是否匹配。系统会根据键处理函数自动处理维度对齐问题,这极大地简化了编程。

矩阵乘法的实现与应用 ✨

矩阵乘法是关联数组能力的“皇冠上的明珠”。有两种等价的视角:

  1. 内积形式:计算输出矩阵的每个元素 (i, j) 时,取第一个矩阵的第 i 行与第二个矩阵的第 j 列进行内积。这是计算效率较高的实现方式。
  2. 外积形式:将第一个矩阵的每一列视为一个列向量,第二个矩阵的每一行视为一个行向量,计算所有列向量与行向量的外积,然后将结果矩阵相加。这种形式在理论分析时更清晰,能确保运算始终保持在定义的代数结构内。

无论采用哪种视角,关联数组的矩阵乘法都天然支持维度通用性,并且运算结果自动封装为一个新的关联数组。

总结与展望 🎯

本节课我们一起学习了关联数组背后的群论基础。我们看到了如何通过定义键处理函数和值碰撞函数,为电子表格和大表这类通用数据结构建立一个丰富而严谨的数学框架。

这个框架的核心价值在于统一性:它将数据库查询(三元组)、图分析、线性代数(矩阵)和编程中的关联结构(哈希表)统一在同一个数学对象下。这使得我们能够将信号处理和线性代数中强大的数学工具直接应用于数据库和大数据分析领域,同时通过维度通用性等新特性,解决传统方法中棘手的对齐和维度匹配问题。

虽然仍有一些开放性问题(如广义逆、特征值),但关联数组代数已经为处理现代海量、异构数据提供了一个极其强大且优雅的数学基础。

007:示例演示 🧪

在本节课中,我们将通过一个具体的示例演示,学习如何利用D4M(数据库与矩阵的数学)工具进行群论相关概念的探索性计算。我们将看到如何将抽象的函数定义和测试过程,通过类似电子表格的直观方式来实现。


上一节我们介绍了D4M的基本概念,本节中我们来看看一个名为“群论一”的具体示例。该示例演示了如何定义函数、组合它们,并进行简单的代数性质测试。

首先,我们启动示例程序。程序会读取一个CSV文件,该文件以类似电子表格的结构定义了不同的“关键函数”。

以下是该CSV文件包含的核心内容:

  • 集合操作:定义了并集(union)和交集(intersection)操作。
  • 值比较条件:定义了三种比较条件,即 V1 < V2V1 == V2V1 > V2
  • 操作符结果:定义了基于上述条件可能产生的不同操作结果。

这个CSV文件被作为关联数组读入。其强大之处在于,列名(如V1 < V2)本身可以是可执行的代码字符串。我们可以利用MATLAB的eval函数来执行这些列名,从而动态地生成或应用函数。这得益于D4M允许在集合中使用字符串的特性。

同样,MATLAB能够自动解析特殊值,如NaN(非数字)、Inf(无穷大)和-Inf(负无穷大)。在本示例中,NaN被用作一种具有“空值”或“失败状态”属性的特殊标识符,它在许多运算中能表现出符合预期的行为。

程序接着会进行一系列测试。需要说明的是,这些测试主要是快速的数值验证,而非严谨的形式化证明。例如,“半环测试”会花费一些时间运行,期间程序可能看起来没有响应,但这是在正常计算。

测试完成后,程序会输出结果。它会生成并展示所有可能的函数对组合。例如,通过代码可以自动生成200对不同的组合,并将结果写入一个新的CSV文件。这展示了D4M在自动化探索和生成数据方面的便捷性。

最后,我们读取并显示完整的函数组合结果。通过display full(A_funk)命令,可以看到所有值组合的完整表格。

本示例的核心目的,并非期望大家使用D4M进行深入的群论研究,而是为了展示当计算环境能够处理字符串等灵活数据类型时,解决问题的思路将大大拓宽。D4M使得以直观、灵活的方式组织和操作这类非传统数值数据变得非常简单。


本节课中我们一起学习了如何通过D4M工具进行一个探索性的群论示例演示。我们看到了如何用电子表格定义函数和条件,如何利用字符串列名实现动态代码执行,以及如何自动化地生成和测试函数组合。这体现了D4M在处理复杂、非结构化数据问题时的强大灵活性和便捷性。

008:非结构化数据中的实体分析 📊

在本节课中,我们将要学习如何利用D4M技术对非结构化数据(如新闻文本)进行实体分析。我们将回顾Web技术的发展历程,理解D4M技术诞生的背景,并通过一个具体的案例——分析路透社新闻语料库中的实体关系——来演示其强大的分析能力。

课程概述与背景 🌐

上一节我们介绍了关联数组和线性代数的基础理论。本节中,我们来看看如何将这些理论应用于真实世界的数据分析。

以下内容采用知识共享许可协议提供。您的支持将帮助MIT OpenCourseWare继续免费提供高质量的教育资源。如需捐款或查看来自数百门MIT课程的其他材料,请访问 ocw.mit.edu

我们现在开始上课。我知道今天实验室有很多活动,我非常高兴你们选择了来上这节课。对于那些提交了第一次作业的同学,我已经提供了反馈。我对完成作业的同学印象深刻,你们很好地把握了作业的精神。我知道刚布置的第二次作业是建立在第一次基础上的,对于那些没做第一次作业的同学可能会有些吃力。但我还是要祝贺那些挑战了第二次作业的同学,它确实需要相当多的思考。

我们将进入课程三个部分中的第二个阶段。前几讲我们做了很多动机阐述和理论介绍,现在我们将更多地接触那些你实际工作中可能会做的事情。我们正进入课程的下一个阶段。

这是第03讲。我们将使用一些真实数据作为例子进行讲解。

课程大纲

对于那些刚加入我们或通过网络观看并跳到此讲的同学,本课程名为“数据库信号处理”。这通常不放在一起的两个领域:信号处理(主要指检测理论及其线性代数基础)和数据库(主要指处理字符串、非结构化数据和图)。本课程讨论的D4M技术,正是将这两种视角融合在一起。

以下是大纲:我将简要介绍历史,谈谈Web如何演变成今天的样子,这很大程度上解释了为何我们发明了D4M技术。然后会谈谈D4M填补的具体空白,介绍一些相关成果。希望今天的演示能更深入,我们会花比上次更多的时间。

Web的演变与D4M的诞生 💡

回顾Web的早期,即90年代初,硬件方面非常简单。都是Sun工作站,它们既是客户端也是服务器和数据库。当时的数据库主要是SQL数据库,Oracle刚刚起步,Sybase是当时的主导者。

这是一个早期现代网页的例子。我和朋友Don Bory为一家公司工作,我们有一个SQL数据库数据集,想把它发布出来。我们觉得当时的客户端工具很笨重。我的朋友Don下载了一个叫NCSA Mosaic的软件测试版,这是第一个浏览器。它改变了一切。在此之前,HTTP和HTML(1989年出现)并不被看好,另一个叫Gopher的技术更受欢迎。但Mosaic为HTTP创建了图形界面并支持图片,这改变了一切。我们当时用Perl语言连接Sybase数据库,通过Gopher服务器与Mosaic浏览器通信,生成HTML。我们当时的结论是:这个浏览器是个糟糕的GUI,HTTP是个糟糕的文件系统,Perl不适合分析,SQL也不适合数据进出。这太费劲了,不会流行起来。

然而,之后互联网经历了“寒武纪大爆发”。硬件方面,出现了大型数据中心、笔记本电脑、平板电脑等。浏览器技术也飞速发展。Mosaic的作者创立了Netscape,后来开源成为Mozilla和Firefox。微软收购了Spyglass公司的Mosaic代码,发展出Internet Explorer。Chrome则是完全重写的。SQL数据库方面,Oracle占主导,但也有MySQL等。服务器方面,Apache(源于Mosaic服务器)和Windows IIS是主流。连接它们的语言也大量涌现,如Java等。

重要的是要认识到,整个架构并非因为我们认为它们是最优工具而构建,它们只是当时可用的、能被重新用于这项工作的技术。因此,我们的结论必须修正:它确实流行起来了,因为分享信息的强烈愿望克服了高门槛。

如今情况已大不相同。我们不是第一个注意到原始架构存在缺陷的人。今天的Web看起来更像这样:我们仍有强大的客户端硬件;服务器和数据库已移至数据中心外的集装箱中;界面越来越像视频游戏(如iPad应用);Google等公司认识到,如果只是呈现数据而非处理交易,SQL的许多功能可能并不需要,于是出现了更简单的、可扩展的“三元组存储”(Triple Stores),如Google Bigtable、Accumulo、Cassandra、HBase等。

但在中间层,我们很大程度上仍在使用相同的技术,即连接前后端的“胶水”层。我们的新结论是:查看大量数据仍然费劲,但视图很棒。中间层的问题,即这个“空白”,依然存在。D4M正是为了填补这个空白而设计的。

D4M(动态分布式数据维度数据模型)从底层设计,旨在处理存储在现代数据库中的三元组数据,并以更少的代码进行分析。它将三元组存储(可视为巨大的三元组稀疏矩阵)与我们在上一讲讨论的关联数组(Associative Array)数学概念连接起来。

技术生态与平台介绍 🖥️

我想谈谈我们带来这些技术的方式,介绍一下LLGrid以及我们在那里所做的工作,因为你们将使用LLGrid账户。

LLGrid是林肯实验室的核心超级计算机,我们在这里帮助满足您的所有计算需求。同样,如果您有项目并认为使用D4M有用,我们也可以提供帮助。LLGrid约有500名用户,超过2000个处理器。据我们所知,它是世界上唯一一个桌面交互式超级计算机,比任何其他超级计算机都更容易使用。

LLGrid的“面包和黄油”是我们的并行Matlab技术,今年将庆祝其10周年。它让人们能相对轻松地进行并行编程。D4M与此技术无缝交互,因此在本课程结束时,你们也将能用它进行并行数据库操作。我们使用“分布式阵列”并行编程模型,这被广泛认为是处理多维数组的最佳模型。MIT的每个人都熟悉矩阵思维,但这并非普遍技能。D4M使用矩阵视角看待数据,因此这两项技术结合得非常好。

在2012年,不谈“云”就不算完整的计算课程。我们将云简单分为两半:效用云计算(如Gmail、企业服务、日历、数据共享)和数据密集型云计算(基于Hadoop等技术)。后者是我们主要关注的。

从实施者的角度看,如果你拥有大量计算硬件,很可能在做四件事之一:传统超级计算(类似LLGrid所做的)、传统数据库管理系统(如信用卡交易)、企业计算(如今多在VMware上运行),或大数据(使用Java和MapReduce等)。这每一个都是数百亿美元的产业,硬件甚至芯片都出现了专业化。有时你的任务可以完全在一个领域内完成,有时则需要跨领域,这可能很有挑战性。

LLGrid使传统超级计算变得交互式、按需和弹性。在Hadoop社区,为了提供高效搜索,在Hadoop之上开发了许多数据库,HBase是一个,Accumulo是另一个(据我所知是性能最高的开源三元组存储)。我们创建了与这些数据库的绑定,即D4M技术。我们还有LLGrid MapReduce,这是Hadoop社区的核心编程模型,是一个非常简单的模型(每个程序在文件列表上独立运行)。对于使用Python或Java(而非Matlab)的用户,MapReduce是可用的。

我们的大愿景是结合大计算大数据。随着我们处理新应用(文本、网络、生物等),新的API和分布式数据库类型正在影响一切。

Hadoop简介

简单介绍一下Hadoop架构:你提交一个Hadoop MapReduce作业,它进入作业跟踪器,被分解为子任务,每个任务有自己的跟踪器,然后被发送到架构中的数据节点。名称节点负责跟踪实际名称。这是一个简单的Hadoop集群。

Hadoop的优势和劣势:它允许你在大量数据上分布式处理。最佳用例是:如果你有海量日志文件,并且你决定只对它们进行一次搜索(例如搜索一个字符串)。如果你决定进行不止一次搜索,那它就不总是最合适的。它基本上是为在大量文件上运行grep而设计的。整个数据库社区对此可能会感到不安,因为我们已经通过创建索引解决了快速搜索的问题,这也是人们发明HBase、Accumulo等数据库的原因。

Hadoop具有高度可扩展性,其设计能够容忍极不可靠的硬件,但这是有代价的——它依赖于大量复制(通常标准复制因子是3)。这对于高性能存储领域来说意味着成本是3倍。Hadoop的调度器还很不成熟,容易发生资源冲突,不是一个轻松的多用户环境。它根本上依赖于JVM存在于每个节点上,因为当你向每个节点发送程序时,该程序的解释器必须存在于每个节点上。默认情况下,Hadoop集群每个节点都有的语言只有Java。

LLGrid MapReduce架构大大简化了这一点。你调用egan_mapreduce,它启动一批映射器任务,在不同的输入文件上运行它们,完成后创建输出文件,然后如果你指定了,再运行一个归约程序来合并输出。基本模型就是:一个映射程序处理文件列表,每个生成一个输出;一个归约程序将它们全部合并。

这就是关于Hadoop的简要介绍。它非常简单和流行,预计将长期保持其流行度。对于地球上绝大多数人来说,这是他们能接触到的最易用的并行计算技术。

D4M核心概念回顾与基础分析 📈

回到D4M,我之前提到过很多。D4M的核心概念是多维关联数组。D4M旨在让我们这些具有更多数学专业知识的人能够做比在Hadoop中更复杂的事情。它允许你同时以四种方式查看数据:可以将其视为二维矩阵,用字符串引用行和列,值也可以是字符串;它与三元组存储一一对应,因此可以轻松连接数据库;它看起来像矩阵,因此可以进行线性代数运算;利用邻接矩阵和图之间的对偶性,你也可以将数据视为图。

这是可组合的:几乎所有对关联数组执行的操作都会返回另一个关联数组。我们可以对它们进行加、减、与、或、乘等操作,可以非常轻松地进行复杂查询。这些操作既适用于关联数组,也适用于绑定的数据库表。

说到表,我已经谈过我们在这门课中一直使用的模式。如果你的标准数据看起来像这样(例如一个网络记录:源IP、域名、目的IP),我们通过将源与值连接来“展开”它,从而创建一个非常大的稀疏表,这自然会进入我们的三元组存储。当然,仅此本身并没有带来什么,因为大多数表要么是行存储要么是列存储。我们使用的数据库是行存储的,允许快速查找行键。然而,一旦我们展开了模式,如果我们同时存储其转置,我们现在就可以快速有效地索引这里的所有内容。对用户来说,就像我们用一种模式索引了数据库中的每个字符串,这是非常强大的一步。

实体分析实战演示 🎯

现在,我将展示一些我们做过的基本分析,并演示更多代码。

例如,这是我们的表。可以将其视为稀疏矩阵,这里有各种源IP。我只想对这些数据计算一些非常基本的统计量。需要说明的是,计算总和和平均值听起来可能不是非常复杂的统计,但它仍然异常强大。我们惊讶于它的价值,因为它通常能立即显示出你数据中的坏数据。对于许多客户来说,这通常是我们使用D4M提供的第一个增值:这是第一次有人从整体上查看他们的数据并能够做这类事情。通常我们做的第一件事就是:加载他们的数据,进行一些基本的求和,然后告诉他们:“你知道你有8%的数据在这个列中都有这个值吗?这绝对不可能是正确的。” 8%可能低到你不会在常规检查中遇到,但在最基本的直方图中就会凸显出来。这通常非常有价值,客户通常会去修复它。

因此,我鼓励你们在拿到数据后,首先进行基本统计,看看哪些地方正常,哪些地方有问题。

以下是基本实现。我们给它一组要查看的行(可以非常大)。我们有一个表绑定T。我们请求获取所有这些行,返回一个关联数组。由于表总是包含字符串并返回字符串值,我们首先需要将它们转换为逻辑值以便进行数学运算,然后再转换回双精度浮点数。

你可以传递正则表达式,也可以使用startsWith命令。假设我们只对源IP域名感兴趣。我们做的第一件事是查找热门列,只需输入sum即可。我们还想查找热门配对,计算协方差矩阵,或者平方后查找具有许多目的IP的域名。

在这个数据集中,最热门的是这些。你可以看到这些都是相当合理的内容,比如“patriots”(新英格兰爱国者队球迷很多)、“ads”、“staples”等。这立刻向你展示了数据情况。

这是数据的协方差矩阵。如你所见,它是对称的,显然有对角线,并且具有二分图结构(没有目的IP到目的IP的链接,只有源IP到目的域名等)。协方差矩阵通常非常有帮助,因为它第一次向你展示了数据的完整互连结构。你可以快速识别密集行、密集列、块、组,以及那些因为太密集或太稀疏而可能不感兴趣的块。这是一个非常基础的调查工具。

这里要提一下我们另一个小组的同事开发的“结构化知识空间”(Structured Knowledge Space, SKS)。SKS是一个非常强大的分析数据集。它有点像Google搜索的联想功能,但更深层。SKS维护一个文档集合的数据库,当你输入“阿富汗”时,它会去统计这些文档中不同类型实体的出现次数,然后显示可能的下一个关键词选择,这是基于数据本身,而非缓存的查询。这是一个相当复杂的分析。

D4M走在正确轨道上的一个重大胜利是:这个功能原本用Java和SQL实现了数百或数千行代码,而我们将展示如何用一行代码实现它。

SKS算法示例

让我们回顾一下那个算法。假设我的数据看起来像这样:有一批文档,一批实体,以及实体出现在文档中的关系。我的关联数组将字符串映射到行,我的两个“面”(facet)只是这里的两个列名。我选择面Y1和Y2。基本操作是:获取Y1和Y2。我使用这里的竖线符号表示去掉面名,以便对它们进行“与”操作。这得到了所有同时包含“UN”和“Carl”的文档。然后,我可以通过矩阵乘法计算计数:将它们“与”起来,转置,然后进行矩阵乘法。就这样完成了。

演示:路透社语料库实体分析 📰

现在我将进入演示环节。我会先进行设置,然后我们短暂休息一下,再开始演示。演示将真正展示如何在真实数据上使用D4M。

我们将要处理的数据是路透社语料库(Reuters Corpus)。这是2000年发布的一个语料库,旨在促进该领域的研究。我们非常感谢路透社这样做。他们将数据交给了NIST(美国国家标准与技术研究院),由NIST管理这个数据集。这是一组90年代中后期的路透社新闻报道,总计约80万篇。我们实际使用的并非原始路透社数据,而是通过各种解析器运行后提取出的实体(人物、地点、组织)。因此,这是数据的一个非常简洁的摘要,是一个衍生品。

数据符合幂律分布。如果我们查看每个实体出现的文档数,会发现某些人物、地点和组织出现在许多文档中,而大多数只出现在少数文档中。查看每个文档包含的实体数也会看到同样的现象。本质上,你可以将此视为计算这个二分图的入度和出度,它具有我们期望在数据中看到的经典幂律形状。

我们将在演示中完成这个分析。

总结与预告

回顾一下,Web的演变催生了一类新技术。我们看到Web正朝着游戏化界面、三元组存储数据库和D4M这类分析技术发展。这是一项非常新的技术。

顺便说明,本周没有作业。对于那些还在做上次作业的同学,可以放松一下了。

示例代码在示例目录中,我们现在已经进入到第二个子文件夹 apps/entity_analysis。鼓励大家运行这些示例,我们稍后就会开始做。现在我们将休息五分钟,然后继续演示。


本节课中我们一起学习了Web技术栈的演变、D4M技术出现的背景及其核心价值。我们回顾了关联数组在数据分析中的基础应用,并预览了如何利用D4M对路透社新闻语料库进行实体关系分析。在接下来的演示中,我们将亲眼见证如何用简洁的代码实现强大的分析功能。

009:示例演示 🧪

在本节课中,我们将通过一个具体的示例演示,学习如何使用D4M(数据库、数据、数据挖掘)工具包对从路透社文档中提取的实体数据进行分析。我们将涵盖数据读取、格式转换、基本统计、相关性分析以及图结构探索等核心操作。


数据读取与格式转换

上一节我们介绍了D4M的基本概念,本节中我们来看看如何将原始数据读入并转换为适合分析的关联数组格式。

首先,我们有一个包含约2.4MB数据的CSV文件。该文件包含了从路透社文档中提取的实体信息,每一行代表一个实体在一个文档中出现的位置。

% 读取CSV文件数据到关联数组
E = Assoc('','','');
E = readCSV('entity_data.csv');

读取后,数据以“边列表”的形式存储在关联数组 E 中。其行键是自动生成的索引,列包括文档名、实体名、位置和实体类型。为了进行后续分析,我们需要将其转换为“展开模式”,即以文档为行、以“类型:实体”组合为列的矩阵。

以下是转换步骤:

  1. 获取构成关联数组的三元组(行、列、值)。
  2. 将实体类型和实体名称字符串拼接,形成新的列键。
  3. 用文档作为行键,新的“类型:实体”作为列键,位置作为值,构建新的关联数组。
% 获取三元组
[docStr, entityStr, posStr] = find(E);
% 拼接类型和实体,形成新的列键
colStr = CatStr(entityTypeStr, ':', entityStr);
% 构建展开模式的关联数组
A = Assoc(docStr, colStr, posStr);

转换完成后,我们可以保存这个二进制文件以便快速后续加载。这个新矩阵 A 的行是文档,列是唯一的“类型:实体”对,值表示该实体在文档中出现的位置。


基本统计与直方图

在将数据成功转换为展开模式后,我们可以开始计算一些基本的统计数据,以了解数据的整体分布情况。

首先,我们查看数据的基本维度:文档数量、唯一实体数量以及非零条目总数。

% 显示关联数组的尺寸(行数 x 列数)
size(A)
% 计算非零条目的数量
nnz(A)

接着,我们可能想知道数据集中每种类型的实体(如地点、人物、组织)各有多少。这需要先将展开的列键拆分开,然后进行统计。

以下是计算实体类型分布的步骤:

  1. 使用 Col2Type 类函数将拼接的列键拆分开。
  2. 将关联数组转换为仅包含1的二进制形式(使用 double(logical(A)))。
  3. 沿行方向求和,得到每个实体出现的总文档数。
  4. 进一步处理,得到每个实体的出现频次。
% 拆分列键,获取实体类型和名称
[typeList, entityList] = Col2Type(colStr);
% 转换为二进制并沿行求和,得到每个实体的文档频次
entityCount = sum(double(logical(A)), 1);
% 获取实体及其频次的三元组
[~, entity, count] = find(entityCount);

最后,我们可以针对特定类型的实体(例如所有“地点”)绘制其出现频次的分布直方图。这通常呈现出典型的“幂律分布”,即少数实体出现非常频繁,而大多数实体只出现几次。

% 选取所有‘地点’实体
locationEntities = entityStartsWith(entity, 'LOCATION:');
locationCounts = count(locationEntities);
% 绘制对数-对数坐标下的直方图
loglog(sort(full(locationCounts), 'descend'));
xlabel('Rank'); ylabel('Frequency');
title('Location Entity Degree Distribution (Power Law)');

这种分布分析是信号处理中的基础,有助于我们理解数据特性,并为后续的过滤(如去除高频和低频噪声)提供依据。


相关性分析:Facet查询

掌握了数据的基本分布后,我们可以探索实体之间的关联。一种强大的方法是进行“Facet”查询,即分析当某些实体共同出现时,其他实体出现的模式。

例如,我们想找出所有同时包含“纽约”(地点)和“张德培”(人物)的文档,并查看这些文档中还有哪些其他实体频繁出现。

其核心操作是矩阵乘法,公式为:
相关性向量 = (A(:, 实体1) + A(:, 实体2))’ * A

% 选取‘纽约’和‘张德培’对应的列向量
nyCol = A(:, ‘LOCATION:New York’);
mcCol = A(:, ‘PERSON:Michael Chang’);
% 合并两个查询(确保列名一致后相加)
combinedQuery = noCol(nyCol) + noCol(mcCol);
% 转置为行向量,并与原矩阵相乘,得到其他实体的共现频次
facetVector = combinedQuery’ * A;

这样得到的 facetVector 是一个行向量,其中的值表示其他实体在与“纽约”和“张德培”共同出现的文档中出现的次数。为了识别出有意义的关联(而不仅仅是普遍常见的实体),我们需要对结果进行归一化,即用每个实体的共现次数除以它在整个数据集中的总出现次数。

% 计算每个实体在整个数据集中的总出现次数
entityTotal = sum(A, 1);
% 归一化Facet结果
normalizedFacet = facetVector ./ entityTotal;

归一化后,值接近1的实体(如“弗吉尼亚·韦德”)表示它们几乎只在与查询实体相关的文档中出现,暗示着更强的特定关联;而值很小的实体(如“美国”)虽然也出现在这些文档中,但它们在许多其他文档中也出现,因此关联性较弱。


图构建与分析

之前的分析聚焦于实体与文档的关系。现在,我们通过矩阵运算将数据投影到实体-实体或文档-文档的关系空间,从而构建图并进行探索。

首先,我们计算实体-实体关联矩阵(也称为共现矩阵)。这通过将文档-实体矩阵与其转置相乘来实现:
实体关联矩阵 G_entity = A’ * A

% 计算实体-实体关联矩阵(使用平方函数优化自相关)
G_entity = sqIn(A);
% 可视化该稀疏矩阵的结构
spy(G_entity);

生成的 G_entity 是一个对称方阵,其行和列都是实体。矩阵中的非零值表示两个实体共同出现在至少一个文档中。通过可视化,我们可以清晰看到数据中的区块结构,例如地点-地点、人物-人物、时间-时间之间的密集连接块。

除了简单的共现计数,有时我们还需要知道是哪些文档连接了两个实体。这时需要使用“谱系保留”矩阵乘法,它在结果中保留被消去的维度(即文档)的标识符作为值。

% 使用谱系保留乘法,结果值是被共享的文档ID
G_entity_pedigree = A’ * A; % 使用重载的特殊乘法语义
% 例如,查询‘Jennifer Hurley’和‘James Harvey’的连接
connectionDoc = G_entity_pedigree(‘PERSON:Jennifer Hurley’, ‘PERSON:James Harvey’);

类似地,我们可以构建文档-文档关联矩阵,以发现内容相似的文档:
文档关联矩阵 G_doc = A * A’

% 计算文档-文档关联矩阵
G_doc = sqOut(A);
spy(G_doc);

这个矩阵揭示了哪些文档共享大量相同的实体。通过对角线操作(移除文档与自身的关联),并对关联强度进行归一化(考虑文档自身的大小),我们可以进行更复杂的多面体查询和社区发现。例如,找出与“纽约”强相关且出现超过一定次数的人物,然后进一步分析这些人物的关系网络(邻域和三角关系)。

% 1. 移除关联矩阵的对角线(自连接)
D = diag(diag(G_entity));
G_entity_no_self = G_entity - D;
% 2. 归一化关联强度(Jaccard相似度的一种形式)
entityDegrees = diag(G_entity); % 每个实体的度
normMatrix = 1 ./ max(entityDegrees’, entityDegrees); % 示例归一化
G_norm = G_entity_no_self .* normMatrix;
% 3. 执行复杂查询:找出与“纽约”共现超过4次且归一化强度>0.3的人物
target = ‘LOCATION:New York’;
[relatedEntities, strength] = find(G_norm(target, :) > 4 & G_norm(target, :) > 0.3);
% 4. 分析其中一个关键人物(如‘John Kennedy’)的邻域网络
neighborhood = G_norm(‘PERSON:John Kennedy’, :);
% 找出该邻域中的三角关系(即与John Kennedy相连的实体之间也相互连接)
triangles = neighborhood’ * neighborhood;


本节课中我们一起学习了如何利用D4M工具包对实体数据进行端到端的分析。我们从读取原始CSV数据开始,将其转换为强大的关联数组格式。随后,我们计算了基本的统计信息和分布直方图,以理解数据特征。接着,我们深入探讨了Facet查询,用于分析实体间的条件相关性。最后,我们通过矩阵乘法构建了实体和文档的关系图,并演示了如何进行图上的查询、归一化和社区结构探索。这些技术为在大型数据库上进行高效的信号处理和数据挖掘奠定了坚实的基础。

010:结构化数据分析 📊

在本节课中,我们将学习如何对数据库中的结构化数据进行更复杂的分析。我们将超越简单的查询和基本关联,探索一些更高级的分析技术,并了解D4M技术在处理这些复杂任务时的优势。


数据模式与并行化考量

上一节我们介绍了D4M的基本概念,本节中我们来看看处理数据时的一个关键设计模式。

我们使用一种称为“展开转置对”的模式将数据读入三元组存储(如Acumulo)。这种模式允许我们动态添加任意数量的列,而无需额外开销。具体做法是将原始表的列名和值拼接在一起,形成新的列。

公式新列键 = 列名 + 分隔符 + 值

然而,在设计行键时,我们需要考虑并行化问题。如果行键是基于时间的小端序(例如递增的时间戳),在并行数据库系统中,所有新数据可能会持续写入同一个处理器节点,造成热点瓶颈。

解决方案:将行键设计为大端序的时间格式,或者使用哈希取模等策略进行分发。同时,建议将实际的时间值存储在一个名为“time”的列中,以便直接查询。


基础统计分析 📈

在开始复杂分析前,获取数据的基本统计信息至关重要。这有助于快速发现数据质量问题。

以下是获取基础统计信息的关键步骤:

  1. 查询数据:获取指定行键范围内的所有数据。
  2. 转换为数值:使用 double(logical(...)) 函数将字符串值转换为数值1,便于数学运算。
  3. 计算列计数:对行维度进行求和,得到每类列的出现次数。
  4. 计算协方差:通过 A’ * AsqIn 计算列与列之间的协方差。
  5. 恢复原始模式:使用 CatVal 函数将“展开”的列键拆分开,恢复成“列-值”对的密集格式。

这些操作在D4M中通常只需一行代码即可完成,能快速揭示数据中的异常模式,例如某些列从不共现或出现频率异常。


构建数据图与图遍历 🕸️

接下来,我们探讨如何在数据中构建和遍历图结构。

目标是:给定一组起始列,找出通过特定类型的列与它们相连的所有其他列,同时排除一些已知的“干扰”列。

算法核心步骤

  1. 根据起始列 C0,找到包含这些列的所有行。
  2. 从这些行中,仅筛选出我们关心的列类型 Ct
  3. 从结果中减去我们想要排除的干扰列 Cclutter

代码示例

% 步骤1 & 2: 找到包含C0的行,并筛选出Ct类型的列
A = T(C0, :); % 查询包含C0的所有行
rows = Row(A); % 提取行键
A = T(rows, :); % 用行键获取完整行数据
A = double(logical(A)); % 转换为数值
A = A(:, Ct); % 筛选特定列类型

% 步骤3: 排除干扰列
C1 = Col(A - A(:, Cclutter)); % 获取最终的列集合C1

图遍历的局限性:需要注意的是,数据的自然拓扑结构可能限制遍历。例如,在环状图中,从某个节点出发的单向遍历可能无法到达所有其他节点。设计分析时必须考虑边的方向性。


空间范围查询 🌍

许多数据集包含空间坐标信息。我们将学习如何高效地查询落在特定地理多边形内的数据。

基本思路是:

  1. 查询一个初始的行范围。
  2. 从中提取出存储坐标的列(如 x, y)。
  3. 将坐标值从字符串转换为数值。
  4. 使用MATLAB的 inpolygon 函数判断哪些坐标点位于给定的多边形内。
  5. 返回落在多边形内的所有数据列。

为了提高大规模空间查询的效率,可以使用 莫顿编码 技术。它将二维坐标的字符串交错排列,形成一维的Z序曲线编码,从而可以将二维范围查询转换为高效的一维前缀查询,之后再进行精确的坐标过滤。


高级关联分析:类型对查找 🔗

现在,我们进行更复杂的“二阶”分析,例如查找同时包含两种特定类型数据的行。

目标是:找出所有恰好包含一个 类型1 值和一个 类型2 值的行。

实现步骤

  1. 查询数据并转换为数值矩阵。
  2. 分别对 类型1类型2 的列进行行求和。
  3. 找到行和等于1的 类型1 行。
  4. 在这些行中,进一步找到行和等于2的行(这意味着该行恰好包含一个 类型1 和一个 类型2)。
  5. 提取出这些行中的 类型1类型2 的具体值,构建它们之间的关联关系。

D4M的一个便利特性是,查询函数可以通过输出参数的数量来返回不同形式的结果。指定三个输出参数(行、列、值)可以直接获得三元组列表,这在处理大型数据或只关心部分信息时更高效。


列对集合与语义扩展

我们还可以分析列对之间的集合关系。例如,给定两组列 C1C2,找出所有同时出现在同一行中的 (C1_i, C2_j) 对。

这可以通过字符串拼接函数 CatStr 来实现,它将两个字符串数组合并,用分隔符连接,从而创建出代表配对的唯一新键。

此外,基于已有的配对关系,可以进行语义扩展推理。例如:

  • 反向对:如果存在 (列1, 列3),可能意味着也存在 (列3, 列1)
  • 衍生对:如果存在 (列1, 值A),可能意味着也应该存在 (列2, 值A)

这些模式在构建知识图谱或完善数据关系时非常有用。


总结

本节课中我们一起学习了多种针对结构化数据的分析技术:

  1. 基础统计:快速获取数据概览,发现质量问题。
  2. 图遍历:在数据中构建和探索关系网络,并注意方向性限制。
  3. 空间查询:利用坐标数据进行地理范围筛选,了解莫顿编码优化。
  4. 高级关联:通过矩阵运算和集合操作,查找复杂的类型共现关系。
  5. 关系扩展:基于现有数据对,进行逻辑推理和语义扩展。

通过“展开转置对”模式,我们可以将许多复杂的图分析和关联分析,转化为一系列高效的行列查询和矩阵乘法。随着熟练度的提升,你会发现D4M能够以简洁的代码,优雅地解决大量复杂的数据分析问题。

011:示例演示 🧭

在本节课中,我们将通过一系列具体的代码示例,学习如何使用D4M(数据库、数据、数据挖掘)工具包对路透社数据集进行高级分析。我们将重点学习如何构建和查询“轨迹”,并探索更复杂的分析模式。


数据概览与准备

首先,我们加载并查看数据集。这个数据集包含了近10000个文档和3600个实体(如地点、人物、组织、时间)。

load entity.mat; % 加载名为 E 的关联数组
spy(E'); % 可视化数据矩阵的稀疏结构

通过可视化,我们可以看到数据中不同实体类别的分布。例如,地点“United States”和“New York”出现频率很高,组织“International Red Cross”也很突出。

我们可以使用 startsWith 等查询语法快速筛选数据。例如,查找所有以“person/”开头的列(即所有人物实体):

personRows = startsWith(E, 'person/');

构建简单轨迹

上一节我们介绍了数据分析的基本概念,本节中我们来看看如何从文档中提取“轨迹”。一个轨迹可以定义为在同一文档中同时出现的人物、时间和地点。

以下是构建轨迹的关键步骤:

  1. 定义轨迹元素:我们指定人物、时间和地点作为轨迹的三个要素。

    obj = startsWith(E, 'person/');
    tim = startsWith(E, 'time/');
    loc = startsWith(E, 'location/');
    
  2. 筛选有效文档:只保留同时包含人物、时间和地点的文档行。

    % 计算每行是否同时包含三类实体
    hasAll = sum(obj,2) & sum(tim,2) & sum(loc,2);
    E_reduced = E(hasAll, :);
    
  3. 创建边列表:将文档、时间、地点信息转换为边列表格式。

    % 提取文档-时间边和文档-地点边
    [~,~,ET] = find(E_reduced(:, tim));
    [~,~,ES] = find(E_reduced(:, loc));
    
  4. 通过矩阵乘法构建轨迹:这是核心步骤,通过关联数组的矩阵乘法,高效地关联起人物、时间和地点。

    % 构建“时间轨迹”:人物 -> 时间 -> 地点
    Ttrack = (obj' * tim) * loc';
    % 构建“空间轨迹”:人物 -> 地点 -> 时间
    Strack = (obj' * loc) * tim';
    

生成的 Ttrack 矩阵中,行是人,列是时间,值是与该人、该时间相关的地点集合。Strack 矩阵则展示了人物与地点的关联及其对应的时间。通过点击矩阵中的单元格,可以查看具体的关联详情。


轨迹查询与分析

构建轨迹后,我们可以进行各种查询。以下是几种常见的查询操作:

  • 查询特定人物的轨迹

    personTrack = Ttrack('person/Michael Chang', :);
    
  • 在特定时间和地点窗口内查询轨迹

    % 查找在1996年11月期间出现在澳大利亚的轨迹
    timeRange = startsWith(tim, 'time/199611');
    locationFilter = (loc == 'location/Australia');
    windowedTracks = Ttrack(:, timeRange) & Strack(:, locationFilter);
    
  • 比较不同人物的轨迹重合度

    p1 = Ttrack('person/Michael Chang', :);
    p2 = Ttrack('person/Javier Sanchez', :);
    % 检查是否有共同的时间或地点
    overlap = p1 & p2;
    

构建轨迹图

我们可以将轨迹进一步抽象为图结构。例如,将一个轨迹序列(地点A -> 地点B -> 地点C)转换为地点之间的转移图。

以下是构建轨迹图的基本思路:

  1. 对每个轨迹,提取其按时间排序的地点序列。
  2. 将序列中相邻的地点对(如“地点A -> 地点B”)作为图中的一条边。
  3. 统计所有轨迹中,相同地点对出现的次数作为边的权重。
% 这是一个简化的逻辑示意
for each track
    locations = get_locations_in_time_order(track);
    for i = 1:length(locations)-1
        graph(locations(i), locations(i+1)) += 1;
    end
end

生成的地点-地点图(例如220x220的矩阵)可以揭示数据中隐藏的移动模式或关联模式。例如,我们可能发现从“Belgium”到“Albania”或从“Australia”到“Colombia”的频繁转移。


高级分析示例:多假设跟踪

对于更复杂的场景,如一个文档中提及一个人物和多个地点、时间,简单的轨迹模型可能不够。我们可以进行“多假设跟踪”分析。

此分析会列出特定人物(如“Michael Chang”)在所有文档中出现的所有可能的地点-时间对,形成一个可能轨迹的集合。此外,它还可以记录实体在文档文本中的字符距离等信息,为后续基于上下文的精确筛选提供依据。

% 分析 Michael Chang 的所有可能关联
hypotheses = findMultipleHypothesisTracks(E, 'person/Michael Chang');

该函数会返回一个结构,包含人物每次出现时,文档中所有邻近的时间和地点,以及它们之间的字符偏移量,从而帮助判断哪个地点-时间对最可能是真实所指。


总结

本节课中我们一起学习了如何利用D4M对实体数据进行复杂的轨迹分析。我们从加载和探索数据开始,逐步实现了:

  1. 基础轨迹构建:通过矩阵乘法关联人物、时间、地点。
  2. 灵活轨迹查询:支持按人物、时间范围、地点进行筛选和对比。
  3. 轨迹图生成:将序列数据转换为地点转移图,发现宏观模式。
  4. 高级分析模式:如多假设跟踪,处理数据中的歧义以探索更精确的关联。

这些示例展示了D4M在探索性数据分析方面的强大能力,它允许研究者快速迭代和发现新的分析模式,然后将成熟的方法部署到生产系统中。

012:完美幂律图——生成、采样、构建与拟合 📊

在本节课中,我们将学习如何为图数据构建一个背景模型,特别是基于“完美幂律”的模型。我们将探讨如何生成、采样、拟合这种幂律分布,并利用它来检测数据中的异常模式。这对于理解社交网络、通信网络等由人工过程产生的数据至关重要。

概述

传统的信号处理依赖于高斯噪声作为背景模型。然而,许多现代数据集(如社交网络图)呈现出幂律分布,而非高斯分布。因此,我们需要为这些数据开发新的背景模型。本节课将介绍一种构建“完美幂律”图的线性模型方法,并研究采样、数据清理等操作对观测分布的影响,最终将其应用于实际数据(如路透社新闻数据集)的分析。

构建完美幂律图

上一节我们概述了背景模型的重要性。本节中,我们来看看如何具体构建一个完美的幂律分布图。

我们将图表示为一个随机矩阵 A。该矩阵有 N_out 个行顶点和 N_in 个列顶点,矩阵中的非零值代表边,所有值的总和 M 代表边的总数。一个“完美幂律”要求顶点的出度分布和入度分布都遵循幂律,即度数 d 与拥有该度数的顶点数 N(d) 满足关系:N(d) ∝ d^{-α},其中 α 是幂律指数。

我们可以用以下简单的MATLAB函数来生成这样的度分布:

function [d, N_d] = perfect_power_law(alpha, d_max, N_d)
    % 生成完美幂律度分布
    % alpha: 幂律指数
    % d_max: 最大度数
    % N_d: 控制分布中“段”的数量
    ...
end

该函数会生成一个度分布,其在对数坐标下呈直线。它巧妙地处理了从“整数区间”(每个整数度数一个桶)到“对数区间”的过渡,这与真实数据中观察到的现象一致。此外,我们可以用“简易斜率估计法”来估算 α,即直接用最大度数点 d_max 和度数为1的点进行拟合:α = log(N(1)/N(d_max)) / log(d_max)

生成了度分布后,我们需要将其分配给具体的边。以下代码可以将度分布实例化为一个具体的边列表:

edges = assign_edges_from_degree_distribution(d, N_d);

将顶点连接成边的方式有很多种(例如,全自环、随机连接),这对应着不同的二阶统计特性。通常,我们模拟随机标记顶点并随机配对的情况,这更接近真实世界的数据。

模型参数与拟合

我们构建模型的三个参数是:幂律指数 α、最大度数 d_max 和粗略代表区间数量的 N_d。然而,在实际分析中,我们更常从数据中获取顶点数 N 和边数 M

给定 α,我们可以反推出能构成幂律的 NM 的允许范围。并非所有的 (N, M) 组合都能产生幂律,它们通常落在一个带状区域内,其边顶点比常在10左右,这与许多实际数据集的经验值相符。通过数值方法,我们可以根据给定的 αM 来拟合出最佳的 d_maxN_d

以下是应用此模型的一个实例(以万圣节糖果分布为例):

  • 边数(糖果总数) M = 77
  • 顶点数(糖果种类) N = 19
  • 边顶点比 M/N ≈ 4
  • 最大度数(某种糖果的数量) d_max = 15
  • 简易斜率估计 α ≈ 1.7

通过模型拟合,我们得到了参数,并可以用模型定义的区间对原始数据进行重分桶。重分桶后的数据点能更清晰地揭示其背后的幂律趋势。

采样与数据清理的影响

拥有了生成完美幂律模型的能力后,我们现在可以研究常见的操作——如采样和数据清理——会如何影响我们观测到的分布。

在传统图论中,我们常将数据简化为无向、无权、无自环的图,以便应用现有理论。但当我们对一个完美幂律图施加这些“清理”操作时(例如,A = triu(A + A') > 0),其度分布会发生显著扭曲,可能产生弯曲或扇状尾部(我们称之为“女巫的扫帚”分布)。这种非线性失真可能被误认为是数据本身的特性。

然而,即使数据被清理后看起来不像幂律,我们仍可以通过模型拟合和重分桶来恢复其潜在的幂律结构。反之,有些操作(如计算关联矩阵 A * A')可能产生一个看起来像幂律的分布,但经过重分桶分析后,可能会发现其存在“女巫的鼻子”状的隆起。这表明,线性幂律模型仍然是一个良好的一阶近似,而隆起部分可以作为二阶修正来研究。

另一个重要现象是“致密化”,即随着时间推移,图的边顶点比会增加。这不仅是潜在物理过程的产物,也直接源于采样方式:

  • 随机采样边:随着采样比例增加,累积样本的边顶点比会持续上升(致密化),因为是在向固定数量的顶点中添加边。
  • 线性采样(整行或整列):每个样本本身就能保持与整体相似的密度,因此累积样本的密度保持恒定。

幂律指数 α 的估计也受采样影响。随机采样初始会高估 α,而线性采样则会低估,但随着数据量增加,两者都会收敛到真实值。

子采样与联合分布分析

对于大规模数据集,我们通常需要通过子采样来高效估计背景模型。我们可以分析从完美幂律中采样子集时,度分布的变化,并推导出相应的校正公式。

此外,我们可以通过顶点的度来标记它们,进而研究联合分布,即查看从度为 d_out 的顶点到度为 d_in 的顶点的边数分布。对于完美幂律,这个联合分布在用模型定义的区间重分桶后,应该呈现相对均匀的分布。

通过计算观测值与模型期望值的比值 观测值 / 期望值,我们可以识别异常模式。原始数据由于泊松采样噪声(在小计数区域波动大),比值图可能很杂乱。但经过重分桶后,大部分数据会集中在1附近,使得真正的异常值(显著大于或小于1)更容易被识别。我们可以据此定位最典型、最过剩和最不足的连接模式。

应用于路透社数据集

现在,我们将上述理论应用于真实的路透社新闻数据集。该数据集包含文档与实体(如地点、组织、人物、时间)之间的关联。

以下是针对不同实体类型的分析结果:

  • 地点与组织:其度分布在重分桶后大致符合幂律,但仍存在一些弯曲,暗示数据中可能存在真实的结构性特征。
  • 人物:其度分布在原始数据中呈现“弯曲扫帚”状,但经过模型拟合和重分桶后,显示出非常漂亮的幂律分布,表明人物关联性具有很强的幂律特性。
  • 时间:重分桶后显示出明显的尖峰,这与数据中特定时间戳(如新闻发布时间)的集中出现有关,验证了重分桶对于揭示真实特征的有效性。

我们也分析了实体间的共现(关联)关系。例如,人物-人物关联的原始图看起来像幂律,但重分桶后揭示了数据中真实存在的相关性隆起。

在采样分析中,按文档(行)采样表现为线性采样特性,而按实体(列)采样则更接近随机采样特性,其致密化和幂律指数收敛行为都与完美幂律模型的预测一致。

最后,通过计算联合分布的观测值与期望值之比,我们可以系统地找出最具代表性的文档、最异常的连接等,为数据摘要和异常检测提供有力工具。

总结

本节课中,我们一起学习了为图数据构建基于“完美幂律”的背景模型。我们掌握了生成完美幂律图的方法,并深入探讨了采样、数据清理等操作如何非线性地扭曲观测分布。通过模型拟合和重分桶技术,我们可以从扭曲的数据中恢复潜在的幂律结构,或验证其是否存在高阶特征。我们将这套方法应用于路透社数据集,展示了如何利用背景模型来量化数据、识别异常,并理解不同实体类型的统计特性差异。这为在图数据上应用经典的检测理论奠定了坚实的基础。

013:示例演示 🎬

在本节课中,我们将通过一系列MATLAB代码示例,学习如何生成、操作和分析符合幂律分布的图数据。我们将看到如何创建完美的幂律图,如何通过不同的排列方式改变其视觉表现,以及如何处理和校正采样数据。


生成完美幂律图

首先,我们学习如何根据给定的参数生成一个完美的幂律分布图。核心参数包括指数 alpha、最大度数 Dmax 和分箱数量 ND

以下是生成和绘制初始幂律分布的步骤:

  1. 设置参数并生成分布:我们设定 alpha = 1.3Dmax = 1000ND ≈ 30。使用 power_law_distribution 函数根据这些参数创建度数分布。
  2. 绘制分布图:生成的完美幂律分布如图1所示。
  3. 计算图的基本属性:通过求和计算总顶点数 N 和总边数 M。在本例中,得到 N = 18,187M = 84,000,边顶点比约为 4.6
  4. 生成边列表:使用 edges_from_distribution 函数,根据度数分布生成一组符合该分布的顶点和边。

上一节我们介绍了如何生成一个基础的幂律图,本节中我们来看看如何通过不同的排列方式,改变这个图的邻接矩阵视觉表现,而不改变其度数分布。

探索不同的排列方式

通过随机排列边和顶点标签,我们可以创建视觉上不同但度数分布完全相同的图。以下是四种排列组合:

  • 不进行任何排列:直接使用生成器输出的顶点和边。生成的邻接矩阵(图2)显示为完全的自环,这虽然无趣,但严格符合幂律分布。
  • 仅排列边:随机打乱边的连接关系,但不改变顶点标签的顺序。生成的邻接矩阵(图3)看起来随机,但高度数顶点仍集中在矩阵的左上角(因为生成器默认按度数降序输出顶点)。
  • 仅排列顶点标签:随机打乱顶点标签,但不改变边的连接。生成的邻接矩阵(图4)看起来稀疏且随机,因为每个顶点的边集被整体移动了位置。
  • 同时排列边和顶点标签:这是创建随机外观图的典型方法。生成的邻接矩阵(图5)中,高度数顶点对应的行和列会显得格外密集,这是幂律图的标志性特征。

通过邻接矩阵可视化是分析大型图的有效方法,它能让我们在单个视图中观察数万条边,而传统的节点-连接线图在此尺度下会变得难以辨认。

接下来,我们将学习如何对生成的图数据进行常见的清理和转换操作。

数据清理与转换

在实际分析中,我们经常需要对图数据进行标准化处理。以下操作展示了不同处理方式对数据的影响:

  • 转换为无权图:将多重边合并为单边。这会丢失连接强度的信息,例如在社交网络中,将频繁联系和单次联系等同看待。
  • 转换为无向图:忽略边的方向。这也会丢失信息,例如在引用网络中,A频繁引用B与B频繁引用A的意义不同。
  • 消除自环:移除顶点连接自身的边。在本例中,自环很少,因此此操作对数据扭曲很小。
  • 计算上三角关联矩阵:通过邻接矩阵与其转置相乘得到,可以揭示其他结构。

这些转换都会以不同方式扭曲原始的理想幂律分布,图6至图9展示了这些扭曲效果。

在分析真实数据时,我们常常无法获得全量数据,而只能进行采样。下面我们看看采样对幂律分布估计的影响。

采样与校正

我们创建一个更大的完美幂律图(N=50,000M=329,000),然后随机抽取 1/40 的顶点作为样本。

  • 朴素采样估计的问题:如果简单地用样本度数乘以采样率(40倍)来估计总体度数,对于高度数顶点估计效果很好,但对于低度数(稀有)顶点,会严重高估其出现概率(图10与图11对比)。这印证了幂律世界中“长尾”效应:有大量稀有事件,其中一些注定会发生。
  • 改进的校正方法:我们提供了 compute_degree_correctionapply_degree_correction 函数。该方法基于中位数(50分位数)进行校正,而不是简单的平均值。校正后(图12),对低度数顶点的估计变得更加合理,从而在高频和低频部分都能得到更好的总体估计。

最后,我们将学习如何对实际处理过的图数据进行幂律拟合,以恢复其原始分布参数。

幂律分布拟合

本部分演示如何对一个已经过转换(无向、无权、无自环)的图数据进行幂律拟合,试图恢复其原始参数。

  1. 准备数据:使用之前的参数创建完美幂律图,然后对其进行无向、无权、消除自环的标准处理。
  2. 计算经验分布:计算处理后图的度数分布。
  3. 估算“简易斜率”:通过计算度数为1的顶点数与最大度数顶点数之比,快速估算alpha值(poor man‘s alpha)。在本例中,该估计值与真实值非常接近。
  4. 进行幂律拟合:使用 power_law_fit 函数进行拟合。该函数结合了三种优化技术来应对拟合问题的复杂性:
    • 采样搜索:在参数空间中随机采样。
    • 启发式搜索:一种模拟退火类搜索。
    • 邦加莱搜索:一种改进的牛顿法非线性优化。
      函数会评估这些方法的结果,并选择最佳拟合参数(本例中选择了采样搜索的结果)。
  5. 评估拟合效果:图13展示了拟合过程在 N(顶点数)与 M(边数)参数空间中的搜索路径。图14则对比了原始模型、转换后的数据、拟合出的新模型以及将数据重映射到模型分箱后的结果。可以看到,即使数据经过转换,拟合过程也能很好地恢复原始的幂律分布形态。

本节课中我们一起学习了从生成、可视化、转换到分析和拟合幂律图数据的完整流程。我们看到了如何通过邻接矩阵有效观察大图,理解了常见数据清理操作带来的影响,掌握了处理采样偏差的校正方法,并最终实现了对幂律分布参数的稳健拟合。这些工具和方法为分析真实世界中的复杂网络数据奠定了基础。

014: 生物序列互相关 🧬

在本节课中,我们将学习如何利用D4M技术和关联数组的数学原理,高效地进行生物基因序列的匹配与分析。我们将从一个具体的应用案例——快速识别病原体DNA序列——入手,了解整个数据处理流程,并展示如何通过结合数据库技术实现性能的显著提升。

概述:基因序列分析的挑战与机遇

随着DNA测序成本的急剧下降,获取基因数据变得前所未有的容易。一台桌面测序仪每天可产生数百GB的数据。这为医疗诊断、疫情追踪等领域带来了巨大机遇,但同时也对数据处理速度提出了严峻挑战。例如,在2011年德国大肠杆菌疫情中,快速、准确地识别病原体序列对于控制疫情和减少经济损失至关重要。传统的序列比对方法计算量大、耗时长,因此我们需要更高效的算法。

核心算法:基于关联数组的序列匹配

上一节我们提到了海量基因数据带来的挑战,本节中我们来看看如何使用D4M的核心数学工具——关联数组——来简化并加速序列匹配。

基因序列由A、T、C、G四种碱基(称为“基对”)组成。标准的匹配算法是将长序列切割成更短的片段(例如10个碱基的片段,称为“10-mer”),然后比较这些片段。

算法步骤如下:

  1. 构建参考序列矩阵:将已知的参考数据库中的每条序列(ID)作为行,将该序列中出现的所有唯一10-mer作为列,形成一个稀疏关联数组 A1

    • A1(序列ID, 10-mer) = 1 (表示该序列包含此10-mer)
  2. 构建待测序列矩阵:对待测的未知样本序列进行同样操作,得到矩阵 A2

  3. 计算互相关(匹配度):通过矩阵乘法计算两个集合的匹配程度。

    • 匹配结果 = A1’ * A2
    • 结果矩阵中的每个元素 (i, j) 的值,代表参考序列 i 与未知序列 j 所共有的10-mer数量。数值越高,表明两条序列越相似。

这个算法将复杂的生物序列比对问题,转化为了简洁的线性代数运算。

性能优化:利用统计特性减少计算量

直接进行全量序列比对计算量巨大。我们可以利用数据本身的统计特性来大幅优化。

在将参考数据存入数据库(如Accumulo)时,我们可以利用其“累加器”功能,自动统计每个10-mer在所有序列中出现的总次数,即它的“度”。分析发现,10-mer的度分布遵循一个长尾分布:少数10-mer出现在绝大多数序列中(常见片段),而大多数10-mer只出现在少量序列中。

核心洞察:常见片段(度很高的10-mer)就像常见词汇“的”、“是”,对于区分不同文档(序列)作用很小,反而是噪声。真正的匹配信息隐藏在那些不常见的片段中。

因此,我们可以设置一个阈值,只选择那些度低于特定值的10-mer进行比对。例如,仅使用度最低的0.5%的10-mer,就能过滤掉99.5%的数据量。

实验结果证明

  • 检测率:使用0.5%的数据,几乎能检测出所有的强匹配序列。
  • 误报率:在降采样后的数据中,如果匹配数超过10,则几乎100%是真实匹配。
    这种基于统计的降采样方法,在保证精度的同时,极大地提升了比对速度。

深入分析:追溯匹配的具体证据

有时我们不仅想知道匹配的数量,还想知道具体是哪些片段匹配了。这就需要“追溯谱系”的关联。

D4M提供了一种特殊的矩阵乘法(如 catKeyMul),它可以在计算结果中保留匹配的键(即具体的10-mer),而不仅仅是计数。

代码示例

% 执行保留具体匹配片段的矩阵乘法
detailed_matches = catKeyMul(A1', A2);
% 筛选出匹配数大于6的结果
strong_detailed_matches = detailed_matches(detailed_matches.val > 6, :);

结果将显示:序列ID i 与序列ID j 匹配,并且列出它们共同拥有的具体10-mer列表。这允许研究人员进一步分析匹配片段的连续性和生物学意义。

系统集成:从数据到应用的完整流程

一个完整的应用系统不仅仅是算法。本节我们来看看如何将D4M分析与数据库等技术集成到一个可用的数据处理管道中。

典型数据处理管道如下:

  1. 原始数据:接收FASTA格式的原始基因序列文件。
  2. 数据解析:将序列解析成“三元组”格式(行键:序列ID,列键:10-mer,值:位置)。
  3. 并行入库与本地缓存
    • 使用并行程序将三元组高速插入Accumulo数据库。为了获得最佳插入性能,需要注意数据的“分片”策略,将数据均匀分布到不同的数据库节点上。
    • 同时,将数据也转换为关联数组并保存为MAT文件。数据库擅长随机查询,而本地文件格式更适合对整个数据集进行全扫描或复杂分析,也便于并行计算。
  4. 分析计算:从数据库查询参考数据,从本地文件加载样本数据,执行高效的降采样互相关计算。
  5. 结果展示:可以通过Web界面等方式,为用户提供交互式分析结果。

这种架构分离了算法开发(在MATLAB/D4M环境中快速原型化)和系统部署(可能用Java等语言重写核心模块),兼顾了开发效率和运行效率。

成果总结

通过结合D4M的关联数组数学和Accumulo数据库的高性能特性,我们在基因序列分析上取得了显著成果:

  • 开发效率:用约150行D4M代码实现了核心比对功能,而传统工具(如BLAST)有近百万行代码。
  • 运行效率:相比传统方法,实现了100倍的速度提升,能够在几小时内处理数十亿的序列数据。

本节课中我们一起学习了如何将抽象的关联数组运算应用于具体的生物信息学问题,构建了一个从高效算法到完整系统管道的解决方案。这种方法不仅适用于基因序列分析,同样可应用于文档比对、网络分析等任何需要快速进行集合匹配和关联发现的领域。

015:示例演示 🧬

在本节课中,我们将通过两个具体的示例,演示如何利用D4M(数据库、矩阵、数学)框架对生物信息学中的DNA序列数据进行关联数组操作和交叉相关分析。我们将学习如何从原始数据构建关联数组,进行矩阵乘法以发现序列间的匹配,并利用谱系追踪功能深入分析匹配细节。


概述与数据介绍

上一节我们介绍了关联数组和矩阵乘法的核心概念。本节中,我们来看看如何将这些理论应用于实际的DNA序列数据分析。

我们将使用两个数据集:

  1. 一个已知的参考细菌数据集。
  2. 一个从人手表面采集的DNA序列样本(开源数据)。

我们的目标是分析样本数据中的序列与参考数据集之间的匹配情况。以下是两个数据集的直观展示,可以看到序列长度差异很大。


示例一:基础匹配分析

首先,我们运行第一个示例程序 B1。该程序的核心是读取数据并构建关联数组。

构建关联数组

我们设定k-mer(即连续碱基片段)的长度为10,也就是分析10-mer。程序会读取CSV格式的FASTA文件,并根据指定的k-mer长度创建关联数组。

  • : 代表一个DNA序列(Sequence ID)。
  • : 代表一个唯一的10-mer。
  • : 表示该10-mer在该序列中是否出现(通常为1)。

我们分别为参考数据集(A1)和样本数据集(A2)构建了关联数组。

% 伪代码示意:构建关联数组
A1 = Assoc([], [], [], seqIDs1, kmers1);
A2 = Assoc([], [], [], seqIDs2, kmers2);

执行矩阵乘法

接下来,我们通过矩阵乘法计算两个数据集之间的交叉相关(匹配)情况。

  • 标准矩阵乘法 (A1 * A2.‘): 结果矩阵 A1A2 中的每个元素值,表示对应的参考序列和样本序列所共享的唯一10-mer的数量。
  • 谱系矩阵乘法 (catKeyAll(A1, A2.‘)): 结果 A1A2key 不仅包含数量,还通过值(value)记录了具体是哪些10-mer促成了这次匹配。
% 计算匹配
A1A2 = A1 * A2.‘;          % 标准乘法,得到匹配计数
A1A2key = catKeyAll(A1, A2.‘); % 谱系乘法,得到匹配详情

运算结果显示:

  • A1(参考集)有198条序列,包含801个唯一10-mer。
  • A2(样本集)有999条序列,包含2365个唯一10-mer。
  • 矩阵乘法后,198条参考序列中的85条,与999条样本序列中的415条,至少有一个共同的10-mer。

结果可视化与分析

我们可以通过可视化工具查看这些矩阵。

  1. 查看A1(参考数据集): 矩阵非常稀疏。放大后可以看到,某些10-mer(列)出现在很多序列中,形成垂直的“亮线”;某些序列(行)包含很多10-mer,形成水平的“亮线”。

  1. 查看A2(样本数据集): 结构类似,但模式略有不同,可能存在更多密集的区块。

  2. 查看交叉相关矩阵 (A1A2): 该图显示了匹配计数。由于大部分匹配只是偶然共享一个10-mer,所以图中绝大多数点的值都是1。

  3. 查看谱系结果 (A1A2key): 这是最关键的一步。图中每个点代表一个具体的匹配关系。例如,一个点表示“参考序列ID-X”与“样本序列ID-Y”在“10-mer-Z”上匹配。点击该点,程序可以打印出完整的10-mer字符串,方便我们进一步核查。

通过这个简单的例子,我们演示了从数据到关联数组,再到发现并可视化序列间基本关联的全过程。


示例二:进阶分析与统计

在第一个示例中,关联数组的值仅是0或1(表示出现与否)。现在,我们进行更深入的分析,记录每个10-mer在序列中出现的具体位置和次数。

构建包含位置信息的关联数组

我们再次读取数据,但这次生成两个关联数组:

  • A1c: 存储每个10-mer在每条序列中出现的次数(计数)。
  • A1p: 存储每个10-mer在序列中出现的位置列表

这为后续分析(如判断匹配是否是连续区域)提供了可能。虽然构建这样的数组更耗时,但信息量更大。

分析度分布

在深入匹配细节前,先了解数据的整体统计特性总是有益的。我们计算并绘制两个数据集的“出度”分布(即每条序列包含的唯一10-mer数量)。

以下是具体方法:

  1. 使用 adj 函数从关联数组 A1c 中提取其内部的稀疏矩阵。
  2. 使用 outdegree 函数对行(序列)进行求和,得到每条序列的10-mer数量。
  3. 在双对数坐标轴上绘制频率分布图。
% 计算并绘制出度分布
M1 = adj(A1c); % 获取内部稀疏矩阵
d1 = outdegree(M1); % 计算行和(出度)
loglog(sort(full(d1), ‘descend‘)); % 绘制双对数图

结果分析:两个数据集的分布都近似遵循幂律分布(长尾分布),即大多数序列只包含少量独特10-mer,少数序列包含大量独特10-mer。这与许多真实世界网络数据特征相似。

一个深入思考:为何之前某些数据显示类似高斯(钟形)分布?这可能与采样充分性有关。10-mer的总可能组合约有4^10 ≈ 100万种。当前数据集仅包含约27万个条目,未充分采样整个空间,故呈幂律分布。当一个数据集足够大(如5亿条目),能更充分地采样整个可能空间时,分布可能更接近对数正态分布(钟形)。这只是一个假设,说明了数据规模对统计特征的影响。

执行有阈值的匹配分析

现在,我们进行类似于示例一的交叉相关分析,但目标更明确:找到那些共享多个独特10-mer的序列对,这比偶然匹配更有意义。

步骤如下:

  1. 二值化: 将存储计数的 A1cA2c 通过 double logical 函数转换回0/1矩阵(A1b, A2b),因为我们只关心是否出现,不关心出现次数。
    A1b = double(logical(A1c));
    A2b = double(logical(A2c));
    
  2. 计算匹配: 计算 A1bA2b 的交叉相关。
    Corr = A1b * A2b.‘;
    
  3. 设定阈值并提取: 我们只关注匹配数大于8的序列对。这里使用了一个巧妙的技巧:
    HighMatch = (Corr > 8); % 提取匹配数>8的子数组
    HighMatch = floor(HighMatch); % 将所有值设为1
    HighMatch = putVal(HighMatch, ‘~‘); % 将所有值赋为字符串‘~‘
    
    技巧解释: 我们将HighMatch的值设为‘~‘,然后与谱系矩阵catKeyAll(A1b, A2b.‘)进行“与”操作。D4M默认的字符串碰撞函数是min(取字典序最小值)。‘~‘的ASCII码很大,而谱系信息字符串通常以字母开头(ASCII码较小)。因此,min(‘~‘, ‘具体谱系‘)的结果永远是谱系字符串本身。这样我们就巧妙地利用碰撞函数规则,从谱系矩阵中“筛选”出了我们感兴趣的高匹配对的具体匹配信息。
  4. 查看结果: 最终,我们可以得到类似这样的信息:参考序列ID_REF与样本序列ID_SAMP匹配了至少8个不同的10-mer,并列出这些10-mer的具体列表。进一步,我们可以根据A1pA2p中的位置信息,检查这些匹配的10-mer在原始序列中是否是连续区域,从而判断是否可能来自同源DNA片段。

总结与课程预告

本节课中,我们一起学习了两个完整的D4M应用示例:

  1. 基础匹配: 从FASTA数据构建关联数组,利用矩阵乘法快速发现序列间的共享k-mer,并通过可视化工具探索结果。
  2. 进阶分析: 在关联数组中存储更丰富的信息(计数、位置),通过度分布了解数据整体特征,并利用阈值筛选和谱系追踪功能,深入挖掘具有显著意义的序列匹配对。

通过这些操作,我们展示了D4M如何将数据库查询(通过关联数组)转化为线性代数运算,从而高效处理大规模、稀疏的生物信息学数据。

下节课预告:接下来的课程将是整个系列中最关键的一讲。大家将有机会在Lincoln实验室的网格计算账户上,亲自运行这些示例程序,操作真实的测试数据库。这对于理解和掌握D4M技术至关重要,也帮助我们共同测试和完善D4M工具。期待大家的参与!

谢谢。

016:演示7 🚀

在本节课中,我们将通过一系列实际演示,学习如何使用D4M与Accumulo数据库进行交互。我们将从生成测试数据开始,逐步进行数据插入、查询、迭代器使用以及更复杂的连接操作,最后还会展示如何处理更大规模的数据集和真实的Twitter数据。通过这些步骤,你将掌握利用D4M进行高效数据库操作的核心技能。


概述

本节课是课程系列的倒数第二讲。之前的所有内容都在为实际使用数据库打下基础。今天我们将不依赖幻灯片,而是通过实际操作演示与现有技术栈的交互。所有演示内容都是你可以实际使用的。我们将从使用已设置的Accumulo数据库开始。

你可以通过访问网页 dbstatus.lincoln.mit.edu 来查看你有权访问的数据库列表。登录后,你会看到为课程设置的五个独立的Accumulo数据库实例。

一个正在运行的Accumulo实例的管理页面会显示磁盘使用情况、表数量、历史摄入速率和扫描速率等信息。这些信息对于监控数据库性能非常有用。

今天的演示代码位于 examples/scaling/two_parallel_database 目录中。我们将涵盖大量内容,展示如何利用D4M和Accumulo协同工作。


第一步:生成测试数据 📊

为了进行数据库工作和测试,我们首先需要生成一些数据。我们将使用一个内置的数据生成器,称为Kronecker图生成器。它源自Graph 500基准测试,可以生成具有幂律分布的大规模图。

以下是生成数据的关键参数和步骤:

  • scale参数:决定顶点数量,顶点数大约为 2^scale
  • 边数:每个顶点的平均边数乘以最大顶点数 nmax
  • 生成结果:返回两个向量,分别代表起始顶点和结束顶点的列表。

我们首先生成一个较小的图来观察其结构。

scale = 12;
edgeFactor = 16;
nmax = 2^scale;
m = edgeFactor * nmax;
[src, dst] = KronGraph500NoPerm(scale, edgeFactor);
A = sparse(src, dst, 1, nmax, nmax);
spy(A);
title('Kronecker Graph Adjacency Matrix');

生成的邻接矩阵具有递归的分形结构。我们还可以绘制其度分布图,它呈现出典型的幂律特征,即少数顶点(超级节点)拥有大量连接,而大多数顶点连接数很少。


第二步:创建更大的数据集并保存到文件 💾

接下来,我们将创建一个更大的数据集。通过多次调用生成器,我们可以获得同一图分布的多个独立样本,从而创建一个边数更多的图。

我们将生成8组数据,并将每组数据的(行、列、值)三元组分别保存到三个独立的文本文件中(*_row.txt, *_col.txt, *_val.txt)。在生成过程中,我们会记录每秒生成的边数,这是评估数据处理流水线性能的重要指标。

numFiles = 8;
for i = 1:numFiles
    fileName = sprintf('data/test_%02d', i);
    rng(i); % 设置随机种子以保证可重复性
    [src, dst] = KronGraph500NoPerm(scale, edgeFactor);
    % 将顶点索引转换为字符串并写入文件
    dlmwrite([fileName '_row.txt'], num2str(src), '');
    dlmwrite([fileName '_col.txt'], num2str(dst), '');
    dlmwrite([fileName '_val.txt'], ones(size(src)), ''); % 值全部设为1
end

生成的文件包含由逗号分隔的字符串序列。现在,我们有了8组数据文件。


第三步:将文件数据读入并构建关联数组 🔄

直接处理文本文件效率较低。因此,我们将这些三元组读入,构建成D4M的关联数组(Associative Array),然后保存为MATLAB的二进制 .mat 文件。这样,后续操作会快得多。

在构建关联数组时,如果遇到相同的行和列键(即碰撞),我们可以指定处理方式。默认是取最小值,但我们也可以指定为求和操作 @sum,这样同一个单元格的值就会累加起来。

for i = 1:numFiles
    fileName = sprintf('data/test_%02d', i);
    % 读取三元组文件
    rowStr = dlmread([fileName '_row.txt'], ',');
    colStr = dlmread([fileName '_col.txt'], ',');
    val = dlmread([fileName '_val.txt'], ',');
    % 构建关联数组,碰撞时值相加
    A = Assoc(rowStr, colStr, val, @sum);
    save([fileName '.mat'], 'A');
end

保存后的 .mat 文件通常比原始的三个文本文件更小,因为关联数组内部对行键和列键进行了压缩存储。


第四步:在文件上进行计算(入度/出度分布)📈

现在,我们可以直接基于 .mat 文件进行计算,而无需数据库。例如,我们可以读取所有文件,累加计算整个图的入度分布和出度分布。

一种简单的方法是循环读取每个文件,并将关联数组直接相加。然而,对于大规模数据,这种方法可能导致性能随着循环次数增加而下降,因为每次相加都涉及重建操作。

更高效的方法是先分别对每个文件计算行和或列和,将结果保存到列表或预分配的缓冲区中,最后再进行一次性的总和计算。

inDegreeTally = [];
outDegreeTally = [];
for i = 1:numFiles
    fileName = sprintf('data/test_%02d.mat', i);
    load(fileName, 'A');
    % 计算并累加行和(出度)与列和(入度)
    outDegreeTally = [outDegreeTally; sum(A,2)]; % 追加到列表
    inDegreeTally = [inDegreeTally; sum(A,1)']; % 追加到列表
end
% 最后进行总和计算(更高效的方式是使用预分配缓冲区)
totalOutDegree = sum(outDegreeTally);
totalInDegree = sum(inDegreeTally);
% 绘制度分布图
figure;
loglog(full(totalOutDegree), full(totalInDegree), '.');

这种基于文件的计算方式非常适合需要遍历全部数据的分析任务。如果后续需要反复使用某次查询的结果,将其保存到文件中是更佳选择。


第五步:设置数据库与创建表 🗃️

现在,我们开始真正的数据库操作。首先,需要在Accumulo中设置并创建表。

我们使用 DBsetup 命令来连接数据库。在林肯实验室网格(LLGrid)系统上,有一个便捷函数 DBsetupLLGrid,它利用挂载的文件系统来获取认证密钥,简化了连接过程。

我们将创建以下几组表:

  1. 邻接矩阵表对:一个表存储 (行, 列),其转置表存储 (列, 行),以实现快速的行查找和列查找。
  2. 度统计表:利用Accumulo的聚合器(Combiner)功能,在插入时实时计算并存储每个顶点的入度和出度。这对于快速了解数据规模非常有用。
  3. 边表(关联矩阵):以边为行、顶点为列,保留每条边的原始信息,支持超边(连接多个顶点的边)。

在创建表时,我们可以为特定列(如 'out_degree,''in_degree,')启用 SUM 聚合器,这样在插入碰撞时值会自动相加。

% 连接数据库(LLGrid特定方式)
db = DBsetupLLGrid('classdb01');
% 创建邻接矩阵表对
Tadj = DB([myName 'Tadj'], [myName 'TadjT']);
% 创建度统计表,并为度数列启用SUM聚合器
TadjDeg = DB([myName 'TadjDeg']);
TadjDeg = addColCombiner(TadjDeg, 'out_degree,', 'sum');
TadjDeg = addColCombiner(TadjDeg, 'in_degree,', 'sum');
% 创建边表及度统计表
Tedge = DB([myName 'Tedge'], [myName 'TedgeT']);
TedgeDeg = DB([myName 'TedgeDeg']);
TedgeDeg = addColCombiner(TedgeDeg, 'degree,', 'sum');

创建成功后,可以在Accumulo的Web管理界面中看到新创建的空表。请注意,在生产环境中,需要合理设置表的读写权限。


第六步:向数据库插入数据 ⬆️

表创建好后,我们就可以向其中插入数据了。首先插入邻接矩阵数据。

我们从 .mat 文件中读取关联数组,但需要注意,Accumulo只能存储字符串。因此,我们需要使用 Num2Str 函数将关联数组中的数值转换为字符串。

插入操作非常简单,只需使用 put 方法即可。对于表对 Tadj,D4M会自动处理正向和转置的插入。对于度统计表 TadjDeg,我们先在D4M中计算好每个顶点的行和(出度)与列和(入度),然后再插入。这种“先聚合,后插入”的方式可以显著减少数据库的插入操作次数,提升性能。

for i = 1:numFiles
    fileName = sprintf('data/test_%02d.mat', i);
    load(fileName, 'A');
    % 将数值转换为字符串以便数据库存储
    Astr = Num2Str(A);
    % 插入邻接矩阵
    put(Tadj, Astr);
    % 计算并插入出度
    outDeg = sum(A, 2);
    put(TadjDeg, Assoc(row(outDeg), 'out_degree,', Num2Str(outDeg)));
    % 计算并插入入度
    inDeg = sum(A, 1)';
    put(TadjDeg, Assoc(row(inDeg), 'in_degree,', Num2Str(inDeg)));
end

即使是插入15万条记录,Accumulo也能在瞬间完成,这展示了其强大的插入性能。我们可以通过管理界面监控实时的插入速率。


第七步:执行数据库查询 🔍

数据插入后,我们就可以执行查询了。一个常见的场景是:随机选择一些顶点,先查看它们的度(数量),然后根据度的范围(例如,过滤掉超级节点和噪声节点)筛选出感兴趣的顶点,最后获取这些顶点的完整邻接行信息。

这类似于信号处理中的“滤除杂波(clutter)”和“抑制噪声(noise)”步骤,在图分析中非常实用。

% 随机选择100个顶点
numVertices = 100;
randomVertices = randi([1, 1000], numVertices, 1);
vertexStr = Num2Str(randomVertices);
% 查询这些顶点的出度
degreeInfo = TadjDeg(vertexStr, 'out_degree,');
% 设置度的阈值,筛选出“信号”顶点
degMin = 5;
degMax = 10;
selectedVertices = degreeInfo(:, 'out_degree,') > degMin & degreeInfo(:, 'out_degree,') < degMax;
% 获取筛选后顶点的完整邻接行
resultRows = Tadj(row(selectedVertices), :);
% 可视化结果
spy(resultRows);

通过这种查询,我们可以快速聚焦于图中具有特定连接模式的顶点。


第八步:使用迭代器处理大量结果 🔄

当查询可能返回大量数据(超过内存容量)时,我们需要使用迭代器(Iterator)。迭代器允许我们以可控的块大小(chunk size)逐步获取查询结果。

在MATLAB中,我们通过创建迭代器对象来实现。尽管MATLAB本质上是无状态的,但D4M通过底层Java代码实现了有状态的迭代器,语法上依然保持MATLAB风格。

% 创建迭代器,设置每次返回最多1000条条目
maxElements = 1000;
iterator = Tadj.iterator('element', maxElements);
% 初始化查询,获取随机顶点的数据
queryVertices = Num2Str(randi([1, 1000], 100, 1));
A1 = iterator(queryVertices);
% 循环处理结果块
inDegreeTally = [];
while nnz(A1) > 0
    inDegreeTally = [inDegreeTally; sum(A1, 1)'];
    A1 = iterator(); % 继续获取下一块结果
end
% 计算最大入度
maxInDegree = max(full(inDegreeTally));

迭代器使得处理海量结果成为可能,并且可以在获取一定数量后暂停,等待用户确认,防止意外的大查询拖垮系统。


第九步:在数据库上执行连接操作 ⛓️

连接(Join)是数据库的核心操作之一。例如,我们想找出同时包含特定两个顶点的所有记录。

一种方法是先分别获取包含顶点A和顶点B的所有记录列,在内存中进行逻辑“与”操作,找出同时包含两者的行,然后再根据这些行键去获取完整的记录。

col1 = '1,';
col2 = '100,';
% 获取包含col1或col2的所有列
cols = Tadj(:, {col1, col2});
% 将值转换为逻辑值并求和,和为2的行即同时包含两者
jointRows = sum(logical(cols), 2) == 2;
% 获取这些行的完整信息
result = Tadj(row(jointRows), :);

如果单个列的结果太大无法装入内存,可以使用两个迭代器分别遍历,并在遍历过程中逐步完成连接操作。这模拟了传统SQL查询规划器在后台的工作方式。


第十步:插入并查询边数据(关联矩阵) 📝

之前我们存储的是邻接矩阵,它合并了重复边。有时我们需要保留每条边的原始信息,这时就需要使用关联矩阵(Incidence Matrix),其中每行代表一条边,每列代表一个顶点。

插入边数据的过程与之前类似,但需要为每条边生成唯一的行键,并在列键中体现方向(如 'out/‘’in/')。

for i = 1:numFiles
    % 读取原始的三元组文件
    src = dlmread(sprintf('data/test_%02d_row.txt', i), ',');
    dst = dlmread(sprintf('data/test_%02d_col.txt', i), ',');
    numEdges = length(src);
    for e = 1:numEdges
        % 为每条边创建唯一ID
        edgeId = sprintf('edge%06d_%02d', e, i);
        % 插入出边和入边信息
        put(Tedge, Assoc(edgeId, ['out/' num2str(src(e))], '1'));
        put(Tedge, Assoc(edgeId, ['in/' num2str(dst(e))], '1'));
    end
end

对边表的查询同样可以利用度统计表进行预筛选,避免触及连接数过多的顶点,从而执行复杂的图分析查询。


第十一步:处理更大规模的数据与并行计算 ⚡

为了展示D4M和Accumulo处理更大数据量的能力,我们可以在更强大的服务器节点上运行上述步骤。通过增加生成图的规模(例如 scale=18),我们可以创建包含数百万条边的数据集。

在这些多核服务器上,我们可以轻松地将文件读取、关联数组构建等计算任务并行化。D4M与MATLAB并行计算工具箱集成良好,通常只需取消注释代码中的 parfor 循环或使用 pRUN 命令即可。

% 示例:并行读取文件并构建关联数组
if exist('parpool','file') && isempty(gcp('nocreate'))
    parpool; % 启动并行池
end
parfor i = 1:numFiles
    % 每个工作进程处理不同的文件
    fileName = sprintf('data/big_test_%02d', i);
    % ... 读取和构建关联数组的代码 ...
    save([fileName '.mat'], 'A');
end

在并行插入数据库时,多个进程同时写入可能会使单个数据库实例的插入速率达到每秒数十万甚至更高,充分体现了Accumulo的高吞吐量能力。


第十二步:分析真实数据——Twitter示例 🐦

最后,我们使用一个真实的Twitter数据集进行演示。该数据集包含了大量推文,经过解析后转换成了(实体,类型,值)形式的三元组。

我们首先将数据读入并构建关联数组,计算各类实体(如单词、@提及、#标签、地理位置)的出现频率(度分布)。这个过程能迅速揭示数据中的问题,例如:“New York City”和“New York City ”(多一个空格)会被视为两个不同的键。这种数据清洗工作正是D4M的用武之地。

% 加载Twitter关联数组
load('twitter_data.mat', 'A');
% 计算列和(每个实体的出现频率)
degreeDist = sum(A,1);
% 找出高频实体
highFreqLocations = degreeDist(:, 'location,') > 100;
highFreqMentions = degreeDist(:, 'mention,') > 100;
highFreqHashtags = degreeDist(:, 'hashtag,') > 50;
% 显示结果
disp('高频地点:'); disp(row(highFreqLocations));
disp('高频@提及:'); disp(row(highFreqMentions));
disp('高频#标签:'); disp(row(highFreqHashtags));

通过这种分析,我们可以快速理解数据集的概况,发现数据质量问题,并聚焦于重要的信号。对于真正海量的数据,则可以将其入库,利用数据库的计数和迭代器功能进行更深入、更高效的查询。


总结 🎯

本节课中,我们一起学习了D4M与Accumulo数据库协同工作的完整流程:

  1. 生成与处理测试数据:使用Kronecker图生成器创建具有幂律特征的图数据,并保存为文件和关联数组。
  2. 文件级计算:直接在MATLAB文件中进行高效的度分布等分析,适用于全数据遍历场景。
  3. 数据库设置与连接:配置Accumulo数据库,创建用于邻接矩阵、度统计和边存储的表对。
  4. 数据插入:将关联数组数据高效插入数据库,利用聚合器在插入时进行统计。
  5. 基本与高级查询:执行基于键的查询,利用度信息进行过滤,实现类似信号处理的“杂波滤除”。
  6. 迭代器使用:通过迭代器分块处理可能返回大量结果的查询,避免内存溢出。
  7. 连接操作:在数据库上实现类似SQL的连接查询,包括内存连接和迭代器连接两种策略。
  8. 边数据管理:使用关联矩阵存储原始边信息,支持更丰富的图分析。
  9. 并行与大规模处理:在强大硬件上并行执行数据生成、处理和插入,展示系统的高扩展性。
  10. 真实数据分析:将所学应用于Twitter数据集,演示了快速的数据概览、质量检查和洞察发现。

核心在于理解:D4M提供了线性代数和关联数组的抽象,使得数据库操作变得直观;而Accumulo则提供了海量数据的高性能存储与检索能力。两者结合,为大规模图数据和社会网络分析提供了强大的工具链。记住,对于需要全扫描的分析,优先使用文件;对于需要随机访问、复杂过滤和迭代查询的任务,则使用数据库。

017:克罗内克图、数据生成与性能 🚀

在本节课中,我们将要学习如何生成用于大规模图计算基准测试的数据,特别是克罗内克图。我们还将探讨影响数据库和D4M性能的关键因素,并理解在不同场景下选择合适计算工具的策略。

克罗内克图与Graph 500基准测试

上一节我们介绍了人工生成数据集的重要性。本节中,我们来看看一个用于生成大规模图数据的强大工具:克罗内克图。

Graph 500基准测试旨在为世界上最强大的计算机提供一个衡量其在图类型操作上性能的机制。该基准测试生成数据,构建成图,然后执行其他操作。我们发现它作为一个高性能数据生成器非常有用,尤其适合测试对幂律数据的插入性能。

该基准测试生成的数据被称为克罗内克图。其基本思想是取一个小的种子矩阵 G,然后通过多次克罗内克积运算来创建一个巨大的邻接矩阵,从而生成图结构。这种方法能自然地产生类似幂律的度分布图。

克罗内克积的一个强大优势在于,它无需实际构建庞大的邻接矩阵就能生成图的边。只要多个进程设置不同的随机数种子,它们就能并行地生成一个更大图中完全一致的边,这使得生成超大规模图成为可能。

然而,克罗内克图生成的数据会带有一些人工结构(如图中的“凸起”),这是其人工特征。因此,如果目标是模拟真实图,我们开发的“完美幂律”方法可能是更好的选择。

克罗内克积详解

为了更好地理解克罗内克图,让我们回顾一下克罗内克积的定义。

如果有一个矩阵 B(大小为 Nb x Nb)和另一个矩阵 C(大小为 Nc x Nc),它们的克罗内克积 B ⊗ C 是通过将 C 乘以 B 中的每个元素,并将结果按块排列而形成的一个更大矩阵。这是一种在两个维度上扩展矩阵的方法。

克罗内克图主要有三种类型:

  • 显式图:将克罗内克图的系数设置为0和1。这样得到的结构非常清晰,便于进行理论分析。
  • 随机图:矩阵中的元素是0到1之间的概率值,表示顶点间存在边的概率。通过多次克罗内克积得到一个巨大的概率矩阵。
  • 实例图:从上述概率矩阵中随机采样,生成图的具体实例。在进行模拟时,通常得到的是这种类型的图。

克罗内克图的理论分析

克罗内克图为图论分析提供了强大的理论框架。一个很好的分析对象是二分图。

二分图包含两组顶点,每组内部的顶点互不连接,但都与另一组的所有顶点相连。其邻接矩阵具有特定的块结构。

如果我们对两个二分图进行克罗内克积,结果(在某种置换下)等于另外两个二分图的并集。这个结果最早由Weisel教授在1962年证明。通过不断对二分图进行克罗内克积,可以自然地构建出幂律图结构:生成超级节点、低度数组件和单例顶点。

我们可以解析地计算克罗内克积二分图的度分布。例如,对一个 (5,1) 二分图进行10次克罗内克积,得到的度分布在双对数坐标下会呈现斜率为-1的直线段,这体现了其内在的幂律特性。

纯二分图没有组内连接。为了在社区之间创建连接,我们需要在对角线上添加单位矩阵 I。这样,图 B + I 的克罗内克积就能生成既包含密集社区(来自 B),又包含社区间连接(来自 I)的图。通过解析计算和适当的置换,我们可以清晰地看到图中的核心二分图块以及它们之间的互联结构。

我们甚至可以计算这种图的度分布、直径、特征值、等周率等指标。这为了解幂律图的子结构及其相互作用提供了深刻见解。

数据库性能考量

现在,让我们从理论转向实践,探讨一些性能基准测试。

在并行计算中,通常关注一个参数:同时运行的并行进程数。然而,当涉及并行数据库时,需要考虑更多参数。

以下是影响并行数据库插入性能的关键参数:

  • 并行进程数:同时向数据库插入数据的工作进程数量。
  • Tablet服务器数:在 Accumulo 等数据库中,数据被分片存储在多个 Tablet 服务器上。服务器数量会影响整体吞吐量。
  • 表的分片(Splits):表在数据库中被分割成多个区域,分布在不同服务器上。分片的数量和数据分布的均衡性对性能有巨大影响。分片过少或数据倾斜都会限制性能。

另一个重要因素是数据块大小,即每次提交给数据库的数据量。通常存在一个最优块大小,能使插入性能达到峰值。这是因为合适大小的数据块可以更好地利用CPU缓存。

对于查询性能,需要记住 Accumulo 是一个基于行的存储系统。按行键查询可以近乎恒定时间内返回结果。然而,如果按列查询且没有使用特殊的“转置”模式,则需要对整个表进行扫描,性能会随着数据量增长而线性下降。这正是D4M采用特殊模式来同时支持高效行、列查询的主要原因。

D4M 与硬件性能极限

最后,我们来谈谈 D4M 本身的性能。

如果以最优方式编写 D4M 程序(通常是将复杂分析转化为一系列稀疏矩阵乘法),就能最大限度地利用底层高度优化的数学库(如稀疏BLAS)。D4M 关联数组的运算性能非常接近原生 MATLAB 稀疏矩阵运算。

然而,必须认识到稀疏矩阵乘法存在根本性的硬件限制。现代CPU的缓存和向量化单元是为密集矩阵乘法设计的,因此密集运算能达到接近95%的硬件峰值效率。相比之下,稀疏矩阵乘法的效率可能只有0.1%左右,这是因为其内存访问模式不规则,无法有效利用缓存。

此外,D4M中那些需要拼接字符串键的特殊乘法操作(例如在生物信息学例子中保留DNA序列信息),还会引入额外的性能开销,主要受限于字符串排序的速度。

因此,在优化程序时,了解这些理论性能极限至关重要。它帮助我们判断优化空间:如果当前性能距离理论极限有1000倍差距,通常很容易通过 profiling 找到问题并大幅改进;但如果已经接近极限(例如10%),那么进一步优化的收益可能就非常有限了。

总结:选择正确的工具

本节课中我们一起学习了克罗内克图的生成与理论,以及数据库和D4M的性能特性。现在,我们可以更好地理解解决数据库信号处理问题的“轻松路径”:

根据数据量和访问粒度来选择工具:

  • 数据量小,访问粒度小:直接在单机内存中处理(使用D4M关联数组)。
  • 数据请求量大,但可放入内存:单机内存处理仍然是最佳选择。
  • 数据量大,请求粒度大(如全表扫描):使用文件系统,分批读入内存处理。或者使用并行计算和并行文件系统。
  • 数据量大,且需要随机访问小部分数据:这是数据库的典型应用场景。

如今,许多被认为是“大数据”的数据集(如数百万甚至数亿条目),实际上可以轻松放入单机内存中处理。真正的数据库用武之地通常在需要随机访问超大规模数据(如数十亿条目)时。当然,如果数据已经存在于某个数据库中,并且是唯一的数据源,那么集成需求可能会凌驾于纯性能考量之上。

关键在于为工作选择正确的工具,并根据需要灵活组合它们,这是高效完成工作而不至于陷入困境的捷径。

018:示例演示 🎬

在本节课中,我们将通过一个具体的示例演示,来学习如何将信号处理的概念应用于数据库操作。我们将看到如何利用矩阵运算来处理和分析存储在数据库中的关联数据。


上一节我们介绍了数据库与线性代数结合的基本概念。本节中,我们来看看一个具体的应用实例。

以下是一个简单的关联数据示例,它描述了人物(行)与地点(列)之间的访问关系。矩阵中的“1”表示某人访问过某地。

       地点A  地点B  地点C
人物X    1      0      1
人物Y    0      1      1
人物Z    1      1      0

这个矩阵可以表示为 A。我们可以通过矩阵乘法来挖掘数据中的关系。例如,计算 A^T * A 可以得到地点之间的关联矩阵,揭示哪些地点常被相同的人访问。

计算过程如下:
关联矩阵 = A^T * A

这个运算的结果是一个对称矩阵,其对角线元素表示访问每个地点的总人数,非对角线元素表示同时访问两个地点的人数。


为了更清晰地展示,以下是计算步骤的分解:

  1. 转置矩阵 A:将行(人物)和列(地点)互换。
  2. 执行矩阵乘法:将转置后的矩阵与原始矩阵 A 相乘。

通过这个简单的操作,我们就能从原始的“人物-地点”数据中,提炼出“地点-地点”的共现模式。这正是信号处理中相关分析思想在数据库查询中的体现。


本节课中我们一起学习了如何将一个代表关联数据的矩阵,通过矩阵转置矩阵乘法运算,转换为一个揭示元素间共同出现关系的关联矩阵。这个演示清晰地展示了将数据库操作转化为线性代数运算的核心思路,为处理大规模图数据提供了高效的计算基础。


版权声明: 本课程内容采用知识共享许可协议提供。您的支持将帮助MIT开放课程持续免费提供高质量教育资源。如需捐款或查看来自数百门MIT课程的其他材料,请访问 MIT OpenCourseWare 网站 ocw.mit.edu。

posted @ 2026-03-29 09:22  绝不原创的飞龙  阅读(1)  评论(0)    收藏  举报