可能你对经常使用的统计分类包中的功能不满足你的需求而感到不爽,或者你已经有了一个新的数据处理方法。所以,你决定改动现有封装好的算法,开始编写你自己的机器学习方法。
无论如何,你对数据结构和算法的了解越多,编写代码时就越容易。
我不认为在机器学习中使用的数据结构与软件开发的其他领域使用的数据结构有明显的不同。但是,由于开发中碰到的许多问题类型和难度的不同,真正掌握数据结构的基本知识是非常有必要的。
此外,由于机器学习是一个非常需要数学知识的领域,我们应该记住数据结构如何被用来解决数学问题,以及数据结构是如何成为数学对象的。
有两种方式来分类数据结构:通过它们的实现和它们的操作方式。
在实现上,指的是它们是如何编程的,以及实际的存储模式。他们看外面的样子,不像引擎盖下面发生的事情那么重要。对于按操作方式或抽象数据类型分类的数据结构,则恰恰相反--它们的外部外观和操作方式比它们的实现方式更重要,实际上,一个数据结构通常可以使用许多不同的内部表示来实现。
当我说基本数组是机器学习中最重要的数据结构时,我并不是在开玩笑。这个类型使用的频率比你想象的要多。数组是非常重要的,因为它们被用在线性代数中 - 这是最有用和最强大的数学工具。
因此,最常见的类型是一维和二维的变化,分别对应于向量和矩阵,但偶尔会遇到三维或四维数组,或者用于更高级的张量或前者的例子。
在进行矩阵算术时,将不得不从令人眼花缭乱的各种库,数据类型甚至语言中进行选择。许多科学编程语言,例如Matlab,交互数据语言(IDL)和带有Numpy扩展的Python,主要用于处理向量和矩阵。
但是这些数据结构的好处是,即使在更通用的编程语言中,实现向量和矩阵公式就在上面图中,假设语言中有任何Fortran DNA,那么实现向量和矩阵就很简单。考虑矩阵向量乘法的转变:
例如C ++:
for(int i = 0 ; i < n ; i ++){
y [ i ] = 0 ;
for(int j = 0 ; j < n ; j ++)y [ i ] + = a [ i ] [ j ] * x [ j ]
}
在大多数情况下,数组可以在运行时分配到固定大小,或者可以计算可靠的上限。在那些需要数组无限扩展的情况下,可以使用可扩展的数组,例如C ++标准模板库(STL)中的vector类。Matlab中的数组规则具有相似的可扩展性,可扩展数组也是整个Python语言的基础。
在这个数据结构中,有两块元数据与实际的数据值一起存储。这些是分配给数据结构的存储空间和数组的实际大小。只要数组的大小超出存储空间,就会分配一个新的空间,其大小是现在的两倍,值被复制到该空间中,旧数组被删除。
这是一个O(n)的操作,其中n是数组的大小,但是由于它只是偶尔发生,所以将一个新值添加到结尾的时间实际上是分配到常量时间O(1)。这是一个非常灵活的数据结构,具有快速插入和快速访问。
可扩展数组非常适合于组成其他更复杂的数据结构,并使其可扩展。例如,要存储稀疏矩阵,可以在结尾添加任意数量的新元素,然后按位置排序以更快定位。更多关于这个稀疏矩阵问题,可以查看下面链接!
稀疏矩阵用于文本分类问题。
链表由几个单独分配的节点组成。每个节点包含一个数据值和指向列表中下一个节点的指针。插入节点,是常量时间,非常高效,但访问一个值,是缓慢的,往往需要扫描大部分的列表。
链表很容易拼接和拆分。有很多变化 - 例如,插入可以在头部或尾部完成; 该列表可以双向链接,并且基于相同原理的许多类似的数据结构,比如下节的二叉树。
主要来说,我发现链表可用于解析不确定长度的列表。之后,可以将它们转换为固定长度的数组以便快速访问。出于这个原因,我使用一个链接列表类,其中包括转换为数组的方法。
二叉树类似于链表,除了每个节点有两个指向后续节点的指针而不是一个。左侧子项的值总是小于父节点的值,而父节点的值又小于右侧子元素的值。因此,二叉树中的数据会自动排序。插入和访问平均效率是O(log n)。就像链表一样,它们很容易转换成数组,这就是二叉树排序的基本图。
如果数据已经被排序,那么在最坏的情况下,二叉树的效率就会降低,因为数据将被线性地排列,就好像它是一个链表。虽然二叉树中的排序是受限的,但它绝不是唯一的,同一列表可以根据插入顺序,有着不同的结构排列。
为了使其更加平衡,可以将一些转换应用于树。自平衡树自动执行这些操作,以保持访问和插入的时间是最佳平均值。
机器学习中普遍存在的一个问题是找到最近的某个特定点的邻居。这是神经网络算法需要解决的问题。KD树是一种二叉树,它提供了一个有效的解决方案。
堆是另一个层次结构,有序的数据结构类似于一棵树,除了水平排序,它有一个垂直排序。这个顺序应用在层次结构中,但不能违背的是:父项总是大于其子项,但是更高级别的节点值不一定比它子节点同一层次的节点值大。
插入和检索都是通过提升进行的。首先将元素插入到可用的最高位置。然后将其与其父母进行比较,并提升至正确的等级。当从堆中取下一个元素时,两个子元素中越大的子元素被提升到缺失的位置,那么这两个子元素中的更大的子元素就会被提升等等,直到所有的元素都排到了正确的位置上。
通常情况下,顶部排名最高的值将从堆中取出,以便对列表进行排序。与树不同,大多数堆只是简单地存储在一个数组中,元素之间的关系也只是隐含的。
一个堆栈被定义为“先进后出”。一个元素被压入堆栈顶部,覆盖前一个元素。顶部的元素必须先弹出才能访问任何其他元素。
堆栈主要用于解析语法和实现计算机语言。
有许多机器学习应用程序,其中栈在领域特定语言(DSL)是完美的解决方案。例如,libAGF库使用递归控制语言将二进制分类概括为多类。一个特殊的字符用于重复前面的选项,但是由于该语言是递归的,所以必须从相同的层次或更高的层次中提取该选项。这是由堆栈实现的。
一个队列被定义为“先入先出”。想一下银行柜员的线路(对于我们这些人来说,还记得一次在网上银行之前)。队列在实时编程中非常有用,因此程序可以维护一个要处理的作业列表。
考虑一个记录运动员分裂时间的应用程序。你输入的是远动员号码,然后按回车,除非你花了很长的时间,而下一个运动员也已经过去了。所以你可以输入最近的运动员的运动员号码的列表,然后按一个单独的键,在队列中登记下一个已经通过的号码。
一个集合包含一个非重复元素的无序列表。如果添加已经在该集合中的元素,则不会有任何更改。由于机器学习的大部分数学涉及集合,它们是非常有用的数据结构。
在一个关联数组中,有两种类型的数据被成对存储:关键字及其关联的值。数据结构本质上是关系型的:值是通过其关键字查找。由于许多训练数据也是关系型的,这种类型的数据结构看起来非常适合机器学习问题。
在实践中,它并没有太多用处,部分原因是大多数只是一维的,而机器学习数据通常是多维的。
关联数组有助于构建字典。
假设你正在建立一个DSL,想存储一个函数和变量列表,并且需要区分这两个。
查询“sqrt”数组将返回“function”。
当你处理更多的问题时,你肯定会遇到那些标准框架不能很好的解决你的需求。你将需要设计自己的数据结构。
考虑一个多类分类器,它将一个二元分类器推广到具有两个以上类的分类问题。一个明显的解决方案是一个二分法:递归地将这些类分成两组。除了分层解决方案不是解决多类问题的唯一方法之外,可以使用类似二叉树的方法来组织二进制分类器。
考虑几个分区,然后用来同时解决所有类的概率。
最通用的解决方案将两者结合起来,因此每个分层分区不需要是二进制分类的,但是可以通过非分层多类分类器来解决。这是在libAGF库中采取的方法。
更复杂的数据结构也可以由基本结构组成。考虑一个稀疏矩阵类。在稀疏矩阵中,大部分元素都是零,只有非零元素被存储。我们可以将每个元素的位置和值存储为一个三元组,并将它们的列表存储在一个可扩展数组中。
3X3的数组:
数据结构本身只是偶尔有趣的。真正有趣的是你能用它们解决的问题
对于我所做的大部分工作,我使用了很多基本的固定长度数组。我主要使用更复杂的数据结构来使程序在运行和与外界交互方面更加流畅,并且更方便用户。不像之前的Fortran程序那样,为了改变网格大小,我不得不忍受接近半个小时的编译周期(我实际上是在这样的程序上工作的!)。
即使你不能提出一个应用程序,我仍然认为知道诸如堆栈和队列之类的东西是很好的。你永远不知道什么时候可以派上用场。
真正复杂的人工智能应用程序可能会使用诸如定向和无向图之类的东西,它们实际上只是树和链表的一般化。如果你不能应付后者,你将如何建立像前者一样的东西?
如果你想自己练习和实现ML算法的数据结构,试着解决下面的一些问题: