K近邻算法(k-nearest neighbor,KNN)

经过一周昏天黑地的加班之后,终于到了周末,又感觉到生活如此美好,遂提笔写一写KNN,这个也许是机器学习众多算法中思想最为简单直白的算法了,其分类思想,总结起来就一句话:近朱者赤,近墨者黑。当然,KNN也可以用于回归任务,在回归任务中,采用“平均法”,即把离预测样本最近的K个样本的label(连续值)取平均作为预测结果,也可以根据距离远近进行加权平均。我们这篇博客主要关注分类任务,因此回归任务不再多提。我主要想从以下几个方面介绍K近邻算法(KNN):

  • KNN分类的基本思想
  • KNN中K值大小对预测结果的影响
  • KNN中距离度量方式
  • kd树的构造与搜索

Note: 个人认为对于初学者,KNN中最难也是最重要的知识点就是kd树了,构造kd树还好,搜索k近邻的时候对于初学者可能有些细节难以搞懂。

一、KNN分类的基本思想

首先,KNN是一种监督学习方法,其原理为:给定测试样本,基于某种距离度量找出训练集中与其最近的k个样本,然后基于这个k个样本(邻居)的label信息来进行预测。在分类任务中,通常采用“投票法”,少数服从多数原则,即选择这k个样本中出现最多的类别作为预测结果。当然也可以按照距离远近进行加权“投票”,距离近的样本权重大,“投票权”大。下面给出KNN分类器的示意图,来直观感受下,KNN是如何分类的。

上图给出了1-近邻,2-近邻,3-近邻的示意图,x 表示待预测样本。在1-近邻中,由于距离待预测样本最近的样本类别为负,因此预测待预测样本为负。3-近邻中,距离最近的3个样本中,两个为正,投票表决,少数服从多数,则把待预测样本标记为正。2-近邻中,距离最近的两个样本一正一负,此时可以随机选取类别。
       通过上面的例子也能够看出,KNN并不需要事先训练一个模型,而是直到需要分类测试样本时才进行。这种学习方法叫做 懒惰学习(lazy learning),这类技术在训练阶段仅仅把样本保存起来,训练时间开销为0,待收到测试样本时再处理。而且我们讲过的神经网络,决策树,logistic 回归,SVM等在训练阶段就学习模型的方法,称为急切学习(eager learning)。下面稍微形式化一点用伪代码来描述下KNN分类算法的流程:

上面 I(.)为指示函数,当yi=cjy_i = c_jyi​=cj​时I为1,否则为0。

这里加一句题外话,KNN虽然思想简单,但是性能是很不错的,能够理论证明KNN的泛化错误率不超过贝叶斯最优分类器的错误率的两倍。(相关理论证明,参见周志华《机器学习》p226)。从以上的基本原理中,我们大概能够得到KNN算法的三个关键点:

  1. K值的大小对预测结果的影响
  2. 距离度量方式
  3. 如何找到与测试样本距离最近的K个样本

下面会分别介绍这三方面的内容。

二、KNN中K值大小对预测结果的影响

从上面原理中,能够知道KNN中K值是需要自己指定的,关于K值大小对预测结果的影响如下:

  • 若K值太小,相当于用较小的邻域中的训练样本去预测,则KNN分类器容易受到由于训练数据中噪声的影响而产生过拟合。
  • 若K值太大,因为邻域比较大,则与测试样本距离较远的训练样本也会起作用,这样会导致误分类测试样本。
  • 若K=NK=NK=N,那么无论输入的测试样本是什么,输出都会是训练样本中样本数量最多的类,这种模型基本没什么用。

在实际应用中,k值一般选取一个比较小的值,可以通过交叉验证法来选取最优的K值。

三、KNN中距离度量方式

既然要算距离,也就那么几个距离度量方式,大家最熟悉的就是欧氏距离了,KNN中一般常用欧式距离(李航《统计学习方法》),weka和scikit-learn中均默认为欧式距离。空间中两个空间点的Minkowski distance可定义为:
Lp(xi,xj)=(∑l=1n∣xi(l)−xj(l)∣)1pL_p(x_i,x_j) = (\sum_{l=1}^n|x_i^{(l)} - x_j^{(l)}|)^{\frac{1}{p}} Lp​(xi​,xj​)=(l=1∑n​∣xi(l)​−xj(l)​∣)p1​
这里,p≥1p \geq 1p≥1。

  • 当 p=1p=1p=1时,即,Lp(xi,xj)=(∑l=1n∣xi(l)−xj(l)∣)L_p(x_i,x_j) = (\sum_{l=1}^n|x_i^{(l)} - x_j^{(l)}|)Lp​(xi​,xj​)=(∑l=1n​∣xi(l)​−xj(l)​∣),称为曼哈顿距离。
  • 当 p=2p=2p=2时,即,Lp(xi,xj)=(∑l=1n∣xi(l)−xj(l)∣)12L_p(x_i,x_j) = (\sum_{l=1}^n|x_i^{(l)} - x_j^{(l)}|)^{\frac{1}{2}}Lp​(xi​,xj​)=(∑l=1n​∣xi(l)​−xj(l)​∣)21​,也就是我们熟悉的欧氏距离。

当然还有其他距离度量的方式,在KNN里,这几个是比较常用的,尤其欧式距离。

四、kd树的构造与搜索

终于要降到KNN中的核心问题了,对,就是 kd树的构造与搜索。先来说说KNN中为什么需要kd树?在KNN中要想找到与测试样本最近的K个样本,传统方法肯定是遍历一遍训练集中所有样本,这个复杂是O(N)O(N)O(N)的,K近邻的话还需要O(K∗N)O(K*N)O(K∗N),当然,K取值一般比较小,因此复杂度是O(N)O(N)O(N),对于中小规模的数据集,这个复杂度基本没啥问题(weka中KNN默认搜索方式即为线性搜索),但是对于大规模数据而言,速度虽也能接受,但是略显有点慢。因此就需要kd树来解决这个问题。
       kd树是一种二叉树,实际上是一种存储结构,是一种对k维空间中的空间点进行存储以便对其进行快速检索的树形数据结构。我们已经知道了kd树就是一个二叉树,因此至少知道它长什么样子了,下面就是如何构造这课二叉树?构造kd树的步骤为(来自 李航《统计学习方法》):

上面的步骤总结下来其实就是每一层用样本特征的一个维度作为坐标轴,然后把所有样本按照此维度排序,选择中间的样本作为结点,然后前面的样本(在该维度上小于中间样本)进入左子树,大于的进入右子树。然后,递归。关于坐标轴的选择,比如第一层选取x(1)x^{(1)}x(1)维度,第二层选取x(2)x^{(2)}x(2)维度,第三层选择x(3)x^{(3)}x(3)维度,
为了更加清晰的展现上面的步骤,我们再用两个例子来讲述(例子来自wiki和李航《统计学习方法》),假定我们有个特征为两维的数据集为:
T={(2,3)T,(5,4)T,(9,6)T,(4,7)T,(8,1)T,(7,2)T}T=\{(2,3)^T, (5,4)^T, (9,6)^T, (4,7)^T, (8,1)^T, (7,2)^T\} T={(2,3)T,(5,4)T,(9,6)T,(4,7)T,(8,1)T,(7,2)T}
第一步:按照 x(1)x^{(1)}x(1) 轴排序,排序结果:[(2,3),(4,7),(5,4),(7,2),(8,1),(9,6)][(2, 3), (4, 7), (5, 4), (7, 2), (8, 1), (9, 6)][(2,3),(4,7),(5,4),(7,2),(8,1),(9,6)],选择中位数(中间样本),因此选择 (7,2)(7, 2)(7,2)这个样本作为根节点,这样样本[(2,3),(4,7),(5,4)][(2, 3), (4, 7), (5, 4)][(2,3),(4,7),(5,4)]被划分为左子区域,[(8,1),(9,6)][(8, 1), (9, 6)][(8,1),(9,6)]被划分为右子区域,如图所示:

第二步:对左子区域按照 x(2)x^{(2)}x(2) 轴排序,排序结果为:[(2,3),(5,4),(4,7)][(2, 3), (5, 4), (4, 7)][(2,3),(5,4),(4,7)],因此选择中间样本(5,4)(5, 4)(5,4) 作为划分点,[(2,3)[(2, 3)[(2,3)被分到左子区域,[(4,7)][(4,7)][(4,7)]被分到右子区域,如图所示:

第三步:对结点(5,4)(5,4)(5,4)的左子区域,按照 x(1)x^{(1)}x(1) 轴排序,排序结果:[(2,3)][(2,3)][(2,3)],因此选择样本[(2,3)][(2,3)][(2,3)]作为划分点,如图所示:

第四步:结点(2,3)(2,3)(2,3)的左孩子为空,递归返回,执行右孩子,右孩子也为空,继续返回到结点(5,4)(5,4)(5,4)的右孩子。

下面的步骤就不写了,就是个递归的过程,最终构造出来的一棵树如图所示:

这个构造的过程也是很简单,直接可以写个代码更为方便:

from operator import itemgetterdef kd_tree(instances, depth=0):if not instances:return Nonek = len(instances[0])axis = depth % kprint("============================")print("轴:%d" % (axis + 1))instances.sort(key=itemgetter(axis))print(instances)median = len(instances) // 2print(instances[median])kd_tree(instances[:median], depth + 1)kd_tree(instances[median + 1:], depth + 1)if __name__ == '__main__':# exampledata = [(2, 3), (5, 4), (9, 6), (4, 7), (8, 1), (7, 2)]# data = [(2,3,1), (5,4,6), (9,6,7), (4,7,2), (8,1,3), (7,2,9)]kd_tree(data)

输出结果:

# output result
============================
轴:1
[(2, 3), (4, 7), (5, 4), (7, 2), (8, 1), (9, 6)]
(7, 2)
============================
轴:2
[(2, 3), (5, 4), (4, 7)]
(5, 4)
============================
轴:1
[(2, 3)]
(2, 3)
============================
轴:1
[(4, 7)]
(4, 7)
============================
轴:2
[(8, 1), (9, 6)]
(9, 6)
============================
轴:1
[(8, 1)]
(8, 1)

同样,也可以画出与这棵树等价的空间划分图,由于特征就2维,因此可以画个平面图出来:

再举个样本特征为3维的例子,样本集为:[(2,3,1),(3,3,2),(5,4,6),(9,6,7),(4,7,2),(4,8,5),(8,1,3),(7,2,9)][(2,3,1),(3,3,2), (5,4,6), (9,6,7), (4,7,2), (4,8,5), (8,1,3), (7,2,9)][(2,3,1),(3,3,2),(5,4,6),(9,6,7),(4,7,2),(4,8,5),(8,1,3),(7,2,9)],构造出来的kd树为:

一棵kd树构造好后,新来一个测试样本后,怎么搜索距离其最近的k个邻居点呢?我们先来看搜索距离其最近的1个邻居点,也就是 k=1k=1k=1,其实也就是最近邻:

kd树的最近邻搜索算法:

  1. 从根节点出发,递归的向下访问kd树,若目标点xxx当前维的坐标小于切分点的坐标,则向左子树移动,否则向右子树移动。直到子结点为叶结点为止。
  2. 以此叶结点为“当前最近点”,此时以目标点为圆心(球心),目标点到此叶结点距离为半径,能够得到一个圆(球体)。
  3. 从叶结点向上回退到该叶节点的父节点:
    (a)计算目标点和父节点的距离,如果距离目标点更近,则以该父节点为“当前最近点”,距离为“当前最近距离”。
    (b)检查该父结点是否在(2)中的球体内(检查的方法见下面例子中红色字体),如若不在球内,则说明该父节点的另外一边子树区域不可能存在比当前点距离目标点更近的点,继续向上回退,继续判断。
    如若在球内,则说明该父结点另外子树对应的区域中可能存在距离目标点更近的点,则执行以该结点的兄弟结点(比如该结点为左节点,则兄弟为右结点)为根节点再次执行最近邻搜索算法 敲黑板: 这个红字部分是很多自称熟悉了kd树算法的人都会搞错的一个细节,李航的书中只说了 “递归的执行最近邻搜索” 这么一句话,但是可能很难深入理解到我上面说的。
  4. 当回退到根节点时,搜索结束。

其实从上面的算法中能够看出,搜索的复杂度就是树的高度,即O(logN)O(logN)O(logN)。

文字总归是空洞的,例子才是生动的,那我们下面来看两个例子,这个两个例子都是基于上面构造的KD树(特征维度为2维的),算了再贴下这个树吧:

例1:
假如,我们要寻找目标点 (3,6)(3,6)(3,6)的最近邻,第一步需要找到目标点所属区域,即找到某个叶结点。 首先从这棵树的根节点 (7,2)(7,2)(7,2)开始遍历,此时在x(1)x^{(1)}x(1)维度,3<73<73<7,所以往左走到点(5,4)(5,4)(5,4),此时在x(2)x^{(2)}x(2)维度,6>46>46>4,则往右移动到点(4,7)(4,7)(4,7),由于此时点(4,7)(4,7)(4,7)已经是叶结点,因此,认为此叶结点((4,7)(4,7)(4,7))为目标点 (3,6)(3,6)(3,6)的当前最近邻点,点(4,7)(4,7)(4,7)到目标点 (3,6)(3,6)(3,6)的距离为半径,半径为2\sqrt{2}2​,构成了一个圆(如下图所示)。
第二步开始回溯, 回退到点(4,7)(4,7)(4,7)的父结点(5,4)(5,4)(5,4),(a) 计算下点(5,4)(5,4)(5,4)与目标点 (3,6)(3,6)(3,6)的距离,距离为8>2\sqrt{8} > \sqrt{2}8​>2​,最短距离保持不变。(b) 由于此时圆与直线 x(2)=4x^{(2)}=4x(2)=4 (若是3维,则为平面)不相交,因此不用遍历点(5,4)(5,4)(5,4)的左子树了。然后直接回退到点(7,2)(7,2)(7,2),(a) 然后计算点(7,2)(7,2)(7,2)与目标点(3,6)(3,6)(3,6)的距离,距离为32>2\sqrt{32} > \sqrt{2}32​>2​,最短距离保持不变,(b)判断圆与直线 x(1)=7x^{(1)}=7x(1)=7 是否相交相交,因为不相交,因此也不用进入点(7,2)(7,2)(7,2)的右子树进行搜索。由于点(7,2)(7,2)(7,2)为树的根节点,因此搜索结束。此时目标点(3,6)(3,6)(3,6)的最近邻为点(4,7)(4,7)(4,7),最短距离为2\sqrt{2}2​。
Note:这与李航《统计学习方法》书中p44 例3.3给的例子有点不同,即若某一区域与圆不相交,则不用去搜素该区域的子区域了,而李航书中例子需要继续搜索,个人认为他的例子这样做是增加了搜索复杂度,是没必要的。可参照:K-D Trees and KNN Searches。
另,最重要的是,如何判断圆是否与某个结点对应的区域是否相交呢?上面的例子是二维空间下的直接判断直线与圆是否相交,三维空间则是判断平面是否与球体相交,因此我们可以知道,只要判断该结点划分维度(即构造kd树时的划分维度,比如是按x(1)x^{(1)}x(1)轴还是按x(2)x^{(2)}x(2)轴划分的)与目标点对应维度的直线距离是否小于半径即可,比如上面的例子中,我们只需用∣4−6∣=2>2|4-6|=2 > \sqrt{2}∣4−6∣=2>2​,即可知道直线 x(2)=4x^{(2)}=4x(2)=4 与圆不相交。三维,四维等高维一样的判断方法。

例2:
下面再举个例子,求目标点(2,5)(2,5)(2,5)的最近邻点。同样的步骤先找到叶结点,先从根节点(7,2)(7,2)(7,2)开始遍历,在x(1)x^{(1)}x(1)轴上,2<72 < 72<7,因此进入点(7,2)(7,2)(7,2)的左子树,到点(5,4)(5,4)(5,4),此时在x(2)x^{(2)}x(2)维度,5>45>45>4,则往右移动到点(4,7)(4,7)(4,7),由于此时点(4,7)(4,7)(4,7)已经是叶结点,因此,认为此叶结点((4,7)(4,7)(4,7))为目标点 (2,5)(2,5)(2,5)的当前最近邻点,点(4,7)(4,7)(4,7)到目标点 (2,5)(2,5)(2,5)的距离为半径,即8\sqrt{8}8​,构成了一个圆(如下图左1所示)。
然后开始回退,回退到点(4,7)(4,7)(4,7)的父节点(5,4)(5,4)(5,4),(a)计算点(5,4)(5,4)(5,4)与目标点(2,5)(2,5)(2,5)的距离为10>8\sqrt{10} > \sqrt{8}10​>8​,因此,最短距离保持不变,当前最近点保持不变。(b)判断圆是否和(5,4)(5,4)(5,4)相交,因为∣4−5∣=1<8|4-5| = 1 < \sqrt{8}∣4−5∣=1<8​,因此圆与直线 x(2)=4x^{(2)}=4x(2)=4 相交,所以进入到点(5,4)(5,4)(5,4)的左子树继续递归的调用最近邻搜索算法(即要以点(2,3)(2,3)(2,3)为根节点,搜索到包含目标点(2,5)(2,5)(2,5)的叶结点),搜索到的叶结点为(2,3)(2,3)(2,3),此时点(2,3)(2,3)(2,3)与目标点(2,5)(2,5)(2,5)的距离为2,小于8\sqrt{8}8​,因此,最短距离变为2,此时更新目标点的最近邻点为点(2,3)(2,3)(2,3),故,点(2,3)(2,3)(2,3)与目标点形成了新的圆,新半径为2。(如下图右1所示),然后继续回溯到点(5,4)(5,4)(5,4),因为点(5,4)(5,4)(5,4)属于递归搜索子树的根节点,因此继续回溯到点(7,2)(7,2)(7,2) ,(a)然后计算点(7,2)(7,2)(7,2)与目标点(2,5)(2,5)(2,5)的距离为34>2\sqrt{34} > 234​>2,因此不更新 “最短距离”和 “当前最近点”。(b)判断圆是否与直线 x(1)=7x^{(1)}=7x(1)=7 相交,此时直线 x(1)=7x^{(1)}=7x(1)=7 与圆不相交(因为∣2−7∣=5>2|2-7| = 5 > 2∣2−7∣=5>2),故也不用进入点(7,2)(7,2)(7,2)的右子树进行搜索,由于由于点(7,2)(7,2)(7,2)为树的根节点,因此搜索结束。此时目标点(2,5)(2,5)(2,5)的最近邻为点(2,3)(2,3)(2,3),最短距离为222。

注:这里举的两个例子都比较巧合,搜索到根节点都发现右子树不可能存在离目标点更近的点了,所以搜索过程都结束了,但是实际中,搜索到根节点发现右子树(or 左子树)是可能存在更近的点的,因此还要执行最近邻搜索算法的(在最近邻搜索算法中红字部分也讲了)。

关于求目标点的最近邻点的就讲完了,但是我们通常都是求k近邻啊,即要求目标点最近的kkk个点,那该怎么求呢。你看的资料可能会说 搜索的时候只要维护个最大堆即可,因为当堆的结点数量为K时,接下来如果有距离更近的,替换堆顶元素即可。

这不就是topk问题吗?(ps,关于求topk问题,正在实习的熊厂,和猫厂招聘蛮喜欢问的哦,鹅厂当初实习笔试面试时倒没遇到过,比如,给你100亿个数,内存装不下这么多数,让你求前k大的数,你怎么求?准备找工作的小伙伴可以去看看这题哦,额,扯远了,回到正题),topk问题嘛,当然是用个最大(小)堆去记录进来的k个数了,这里是求最近的k个点,因此搜索的时候只要维护个最大堆即可,因为当堆的结点数量为K时,接下来如果有距离更近的,替换堆顶元素即可。

如若在最大堆里按照离目标点最远距离的点放在堆顶去搜索,这样即是可以的。去看了下scipy中的kdtree即是这样实现的,源码如下:

这里关于自己实现kd树应该还会遇到个情形,即我们构造了如下一棵kd树,

假如我们要求目标点(2,5)(2,5)(2,5)的最近邻点,按照上面介绍的流程,发现(5,4)(5,4)(5,4)没有右孩子了。。叶结点找不到了,那该怎么办,莫慌,此时让其进入左孩子即可。

最后,关于kd树有一点要说的就是,kd树只适用于样本数远大于特征维数的情形,如果当样本数和特征维度差不多的时候,kd树搜索效率几乎和线性扫描差不多。

后记:原打算上周末写完的,结果又拖到了这周。。。

参考文献
[1]: 李航《统计学习方法》
[2]: kubicode博客《KNN算法中KD树的应用》
[3]: K-D Trees and KNN Searches

K近邻算法(k-nearest neighbor,KNN)相关推荐

  1. C++实现的简单k近邻算法(K-Nearest-Neighbour,K-NN)

    C++实现的简单的K近邻算法(K-Nearest Neighbor,K-NN) 前一段时间学习了K近邻算法,对K近邻算法有了一个初步的了解,也存在一定的问题,下面我来简单介绍一下K近邻算法.本博客将从 ...

  2. K 近邻法(K-Nearest Neighbor, K-NN)

    文章目录 1. k近邻算法 2. k近邻模型 2.1 模型 2.2 距离度量 2.2.1 距离计算代码 Python 2.3 kkk 值的选择 2.4 分类决策规则 3. 实现方法, kd树 3.1 ...

  3. 机器学习-监督学习之分类算法:K近邻法 (K-Nearest Neighbor,KNN)

    目录 KNN概述 举个例子: K值选取 距离计算 曼哈顿距离,切比雪夫距离关系(相互转化) k-近邻(KNN)算法步骤 相关代码实现 简单实例:判断电影类别 创建数据集 数据可视化 分类测试 运行结果 ...

  4. 机器学习算法系列(二十二)-近似k近邻算法-Annoy(Approximate Nearest Neighbor / ANN)

    阅读本文需要的背景知识点:k近邻算法.一丢丢编程知识 一.引言   前面一节我们学习了机器学习算法系列(二十一)-k近邻算法(k-Nearest Neighbor / kNN Algorithm),其 ...

  5. k近邻算法 (KNN)

    k近邻算法 k近邻算法(KNN,K-NearestNeighbor)是一种基本分类和回归方法,监督学习算法,本质上是基于一种数据统计的方法: 核心思想:给定一个训练数据集,对新的输入实例,在训练数据集 ...

  6. python k近邻算法_python中的k最近邻居算法示例

    python k近邻算法 K最近邻居(KNN) (K-Nearest Neighbors (KNN)) KNN is a supervised machine learning algorithm t ...

  7. 机器学习[k近邻算法]

    k近邻算法简称kNN算法,由Thomas等人在1967年提出[1].它基于以下思想:要确定一个样本的类别,可以计算它与所有训练样本的距离,然后找出和该样本最接近的k个样本,统计这些样本的类别进行投票, ...

  8. 机器学习算法系列之K近邻算法

    本系列机器学习的文章打算从机器学习算法的一些理论知识.python实现该算法和调一些该算法的相应包来实现. 目录 K近邻算法 一.K近邻算法原理 k近邻算法 通俗解释 近邻距离的度量 k值的选择 KN ...

  9. 机器学习——聚类之k近邻算法及python使用

    聚类算法之k近邻及python使用 什么是k近邻算法 k近邻算法流程 使用sklearn进行代码实现 数据集介绍 标准化 代码实现 写在开头,套用我的老师的一句话目前所有自然学科的前沿都是在研究数学, ...

最新文章

  1. 使用命令行创建AVD时的出错总结
  2. Mybatis实现物理分页
  3. 跟JBPM学设计模式之适配器模式
  4. MySQL高级 - 锁 - MyISAM表锁 - 查看锁争用情况
  5. mysql怎么存照片信息_mysql怎么存储图片信息?
  6. 在线支付巨头PayPal宣布退出Libra:继续就未来合作方式进行对话
  7. 拓端tecdat|R语言用线性回归模型预测空气质量臭氧数据
  8. OSChina 周二乱弹 ——深入浅出微信小程序
  9. 英文连写字体怎么练_“衡中体”英语书写视频受到英国媒体关注,现在开始练还不晚!...
  10. a标签去掉下划线以及字体颜色
  11. 设置新的路由器无线网络连接服务器,路由器连接新路由器怎么设置 路由器连接新路由器设置方法【详解】...
  12. 互联网+脑科学,中国脑计划的机会
  13. HTML+CSS初学者练习项目5:利用DIV+CSS制作个人CPS网站《汽车坐垫点评网》——首页
  14. string大小写转换
  15. php什么框架,php快速开发用什么框架
  16. Markdown 语法学习
  17. win7系统安装信息服务器不可用怎么办,Win7系统RPC服务器不可用怎么办?
  18. 年终总结——过去已逝,未来可期不可欺
  19. android倒计时dialog,Dialog中显示倒计时,到时自动关闭
  20. php引用公有类方法_php利用ReflectionClass反射机制获取类public公有方法

热门文章

  1. 大学计算机与应用软件,第5章 应用软件与常用办公软件 大学计算机基础简明教程[最新].doc...
  2. 实体店运用互联网思维进行客户裂变,不到8个月净赚2000万
  3. 大数据产业到底是机遇还是陷阱?
  4. 【随手记】有趣的面试题 —三人三鬼过河
  5. WAI-ARIA无障碍网页应用 HTML5 设计辅助功能
  6. 在美国OpenSky电商平台上使用MasterCard虚拟信用卡海淘购物攻略教程
  7. 唤醒手腕 - 人工智能 - 凸优化、损失函数、概率、激活函数、泛化拟合、回归分类 ···
  8. [DeeplearningAI笔记]序列模型3.3-3.5集束搜索
  9. sqlserver 默认日期格式转换为 yyyy-MM-dd
  10. 白鹭小游戏开发,并发布到微信平台