[关闭]
@tianxingjian 2020-11-21T15:31:21.000000Z 字数 14099 阅读 1829

机器学习

《Machine Learning in Action》—— Taoye给你讲讲决策树到底是支什么“鬼”

前面我们已经详细讲解了线性SVM以及SMO的初步优化过程,具体可看:

关于SVM非线性相关的内容,我们留着下个星期来撕

这篇文章我们先来看看决策树的内容,决策树相对于SVM来讲要简单不少,也没有多么复杂的公式。我理解的决策树,简单来说就是通过已有的数据集训练出一个树形结构的模型,以便我们能够依据该模型对不知分类结果的数据进行预测。简单的决策树就有点类似于我们在学习数据结构时候的二叉排序树。当然了,决策树除了能够进行分类之外,还能进行回归,这里主要讨论的是分类问题。

关于决策树相关的内容,可能会肝两篇文章来介绍。第一篇主要是讲解决策树先关的基本知识,及其决策训练的过程,也就是我们的这篇文章,重在理论及推导,也重在理解。而第二篇文章写的主要内容是决策树的代码实战,而代码实战是在熟悉理论基础的前提之下来进行的,所以对于决策树相关的理论知识还是有必要了解的。可能的话,还会有第三篇,主要是关羽防止决策树过拟合的剪枝操作的内容。

一、什么是决策树

关于决策树的定义,李航——《统计学习方法》(第二版)这本书中是这样描述的:

分类决策树模型是一种描述对实例进行分类的树形结构,决策树由节点(node)和有向边(directed edge)组成。节点有两种类型:内部节点(iternal node)和叶节点(leaf node)。内部节点表示一个特征或属性,叶节点表示一个类。

上述提到的内部节点可以理解成样本的属性特征,而叶节点可以理解成样本的标签,而有向边可以理解成不同的属性特征结果。一般我们在绘制决策树的时候,内部节点用方框或长方形表示,而叶节点用圆形或椭圆表示。(注意:这个并不是绝对的,在不同资料中可能有不同的表示方法,比如《统计学习方法》一书中用圆表示内部节点,用方形表示叶子节点,而在《机器学习实战》一书中表示方式正好相反

枯燥的文字说明总是会降低读者的阅读欲望,下面我们不妨通过一个实际的例子来说明下,从而加强读者对决策树的理解。

比如说,形容女生美与丑的时候一般会存在多个指标,这个例子感觉有点危险,还是换个吧。

例子来源:李航——《统计学习方法》(第二版)

比如说,我们没Money用了,想要去银行贷款,这个时候银行就会根据你自己的个人附带特征来决定是否给你放款。假设这里的属性特征有四个,分别是年纪、是否工作、是否有房子、信用情况,这些属性特征就相当于是内部节点,标签(结果)是否放款相当于叶子节点,而不同属性特征的结果表示有向边,像年纪中有青年、中年、老年,信用情况有好、一般、非常好等表示不同的有向边。

对此,我们可以根据自己的感觉来手动绘制一个简单的决策树模型:

我们知道,上述决策树仅仅是我们自己手动来实现的,不一定能够运用于解决实际问题。而决策树的任务,则是根据已有的数据样本集训练得到这么一颗树形结构模型,以此来对未知分类的数据进行类别预测。对此,我们要向得到这么一颗尽可能理想的决策树,则不得不考虑以下几个问题:

本篇文章,主要内容是属性特征的优先选取问题。

二、属性特征的选择

对于属性特征的选择,我们应当优先选取对训练数据集具有明显分类能力的特征,这样可以提高我们决策树的学习效率。假如说一个属性特征的分类结果与随机分类基本没什么差别,则我们可以说这个属性特征基本是没有分类能力的。

比如说,我们这里有12个关于女生美与丑的数据样本,其中六个是丑女,另外六个是美女,六个丑女身高有三个是165cm、三个是185cm,而另外六个美女身高同样如此,这样的话,我们仅仅通过身高来对女生美貌的判断基本是没有分类能力的。(仅仅是个例子,不代表任何观点

所以,我们理想情况下决策树的生成理应是这样的:随着决策过程的不断进行,我们希望决策树的分支节点所包含的样本尽可能的属于同一类别(标签相同),也就是节点的“纯度”越来越高。

所以,决策树的关键技术在于属性特征的选择。对于属性特征的选择,我们通常有三个准则:信息增益、信息增益比(增益率)和基尼指数。

2.1 信息增益

上面提到了样本浓度,比如说我这里有10个女生,10个都是美女,那就说此时的样本浓度高,假如美女和丑女五五分,那这个时候的浓度就比较低了。这里的“浓度”表达的就是这个意思,它主要针对的是标签(结果)

而表示样本“浓度”的指标,最常用的就是“信息熵”了。假定当前样本集合中第类(总共类)样本所占的比例,则此时样本的信息熵为:

通过上述公式,我们可以知道10个美女(一个类别)的信息熵为

美女丑女五五分(两类)的信息熵为:

通过上面信息熵的简单计算,我们可以知道,信息熵的值越小,则纯度越高。

既然我们已经了解了信息增益的计算公式及其所表达的意义,那么对于一个数据样本集,我们应当如何用代码来进行计算呢?下面我们来试试吧。

本次使用到的数据样本来自 李航——《统计学习方法》(第二版),数据是关于贷款申请的,具体如下:

那么现在我们来通过代码形式计算下这个数据样本集的信息熵吧:

首先创建一个establish_data方法来建立一个二维数组用于存储上述数据样本集的相关信息(关于属性特征所对于的值0,1,2之类的,大家可以参考上述表格对号入座,这里就不做过多解释了):

  1. """
  2. Author: Taoye
  3. 微信公众号: 玩世不恭的Coder
  4. Explain:创建训数据集
  5. """
  6. def establish_data():
  7. data = [[0, 0, 0, 0, 'N'], # 样本数据集相关信息,前面几项代表属性特征,最后一项表示是否放款
  8. [0, 0, 0, 1, 'N'],
  9. [0, 1, 0, 1, 'Y'],
  10. [0, 1, 1, 0, 'Y'],
  11. [0, 0, 0, 0, 'N'],
  12. [1, 0, 0, 0, 'N'],
  13. [1, 0, 0, 1, 'N'],
  14. [1, 1, 1, 1, 'Y'],
  15. [1, 0, 1, 2, 'Y'],
  16. [1, 0, 1, 2, 'Y'],
  17. [2, 0, 1, 2, 'Y'],
  18. [2, 0, 1, 1, 'Y'],
  19. [2, 1, 0, 1, 'Y'],
  20. [2, 1, 0, 2, 'Y'],
  21. [2, 0, 0, 0, 'N']]
  22. return np.array(data)

随后,我们创建一个calc_information_entropy方法用于计算信息熵,计算过程的代码可结合上述的公式来看。另外,需要说一点的是,为了方便对放款结果进行分类,我们使用pandas包进行处理,关于pandas的使用,大伙可以参考文档,Taoye后期也会整理一份。当然了,不用pandas模块也能实现这个功能,有兴趣的读者可自行尝试,也不难。calc_information_entropy具体代码如下:

  1. """
  2. Author: Taoye
  3. 微信公众号: 玩世不恭的Coder
  4. Explain:计算信息熵
  5. """
  6. def calc_information_entropy(data):
  7. data_number, _ = data.shape
  8. information_entropy = 0
  9. for item in pd.DataFrame(data).groupby(_ - 1):
  10. print(item[1])
  11. proportion = item[1].shape[0] / data_number
  12. information_entropy += - proportion * np.log2(proportion)
  13. return information_entropy

我们运行上述代码,来看看具体的信息熵结果:

相比大家都了解了信息熵的概念,并能够手动计算样本集的信息熵了,现在我们再来把信息增益搬出来吧。

假设一个属性特征个可能的取值,若使用来对样本集进行划分,这样的话就会产生个分支节点,其中第个 分支节点包含了中所有在属性上取值为的样本,记为。于是可计算出用属性特征对样本集进行划分所获得的“信息增益”(information gain)为:

以上是周志华——《机器学习》一书中对信息增益的相关说明。

枯燥的文字说明总是会降低读者的阅读欲望,上述符号也是有点多,我们不妨拿上面的贷款数据来进一步理解下上述信息增益的内容:

我们单独拿年龄这一个属性特征来计算其信息增益吧,其有青年、中年、老年三个可能的值,也就是说上述的,而数据样本集为上述15个数据样本,由于年龄有三个可能的值,所以此时会产生三个分支,即。之前我们已经计算得到,现在我们只要计算出后一项的值即可得到该属性特征所对应的信息增益

通过数据,我们观察得到如下信息:

对此,我们可以计算:

对此,我们的计算处年龄这个属性特征所对应的信息增益值为:

我们可以把后一项的内容理解成条件概率。另外,信息增益越大,则其所对应的属性特征的分类能力也就越强,也就是我们应当优先选取的特征。

同理,我们可以计算出工作、房子、信用属性特征所对应的信息增益:

我们比较哥哥属性特征的信息增益值,可以发现房子这个属性特征最大,所以它应该是我们优先选择的属性特征。

了解了信息增益的计算及其意义,下面我们来通过代码计算一下(代码含义见注释):

  1. import numpy as np
  2. import pandas as pd
  3. np.__version__
  4. pd.__version__
  5. """
  6. Author: Taoye
  7. 微信公众号: 玩世不恭的Coder
  8. Explain:创建训数据集
  9. """
  10. def establish_data():
  11. data = [[0, 0, 0, 0, 'N'], # 样本数据集相关信息,前面几项代表属性特征,最后一项表示是否放款
  12. [0, 0, 0, 1, 'N'],
  13. [0, 1, 0, 1, 'Y'],
  14. [0, 1, 1, 0, 'Y'],
  15. [0, 0, 0, 0, 'N'],
  16. [1, 0, 0, 0, 'N'],
  17. [1, 0, 0, 1, 'N'],
  18. [1, 1, 1, 1, 'Y'],
  19. [1, 0, 1, 2, 'Y'],
  20. [1, 0, 1, 2, 'Y'],
  21. [2, 0, 1, 2, 'Y'],
  22. [2, 0, 1, 1, 'Y'],
  23. [2, 1, 0, 1, 'Y'],
  24. [2, 1, 0, 2, 'Y'],
  25. [2, 0, 0, 0, 'N']]
  26. return np.array(data)
  27. """
  28. Author: Taoye
  29. 微信公众号: 玩世不恭的Coder
  30. Explain:计算信息熵
  31. """
  32. def calc_information_entropy(data):
  33. data_number, _ = data.shape
  34. information_entropy = 0
  35. for item in pd.DataFrame(data).groupby(_ - 1):
  36. proportion = item[1].shape[0] / data_number
  37. information_entropy += - proportion * np.log2(proportion)
  38. return information_entropy
  39. """
  40. Author: Taoye
  41. 微信公众号: 玩世不恭的Coder
  42. Explain:找出对应属性特征值的样本,比如找出所有年纪为青年的样本数据集
  43. """
  44. def handle_data(data, axis, value):
  45. result_data = list()
  46. for item in data:
  47. if item[axis] == value: result_data.append(item)
  48. return result_data
  49. """
  50. Author: Taoye
  51. 微信公众号: 玩世不恭的Coder
  52. Explain:计算最大的信息增益,并输出其所对应的特征索引
  53. """
  54. def calc_information_gain(data):
  55. feature_number = data.shape[1] - 1 # 属性特征的数量
  56. base_entropy = calc_information_entropy(data) # 计算总体数据集的信息熵
  57. max_information_gain, best_feature = 0.0, -1 # 初始化最大信息增益和对应的特征索引
  58. for index in range(feature_number):
  59. feat_list = [item[index] for item in data]
  60. feat_set = set(feat_list)
  61. new_entropy = 0.0
  62. for set_item in feat_set: # 计算属性特征划分后的信息增益
  63. sub_data = handle_data(data, index, set_item)
  64. proportion = len(sub_data) / float(data.shape[0]) # 计算子集的比例
  65. new_entropy += proportion * calc_information_entropy(np.array(sub_data))
  66. temp_information_gain = base_entropy - new_entropy # 计算信息增益
  67. print("第%d个属性特征所对应的的增益为%.3f" % (index + 1, temp_information_gain)) # 输出每个特征的信息增益
  68. if (temp_information_gain > max_information_gain):
  69. max_information_gain, best_feature = temp_information_gain, index # 更新信息增益,确定的最大的信息增益对应的索引
  70. return best_feature
  71. if __name__ == "__main__":
  72. data = establish_data()
  73. print("属性特征对应的最大的信息增益对应的索引为:%d" % calc_information_gain(data))

运行上述代码,可见输出的各个属性特征的信息增益,以及最大信息增益对应的特征索引:

可以发现,和我们手动计算的如出一辙,所以此时我们应当优先选取索引为2的属性特征作为决策标准,也就是房子。

2.2 信息增益比(增益率)

我们使用信息增益作为选取特征指标的时候,存在偏向于选择取值较多的特征的问题。

比如我们将id作为上述数据集的一个分类属性,通过计算可以发现该信息增益最大,但实际上该id对类别不具有什么分类能力,所以这样得到的决策树不具有泛化能力,无法对新样本进行有效预测。

为了解决信息增益存在的这个问题,我们就引入了信息增益比(增益率)的概念,著名的C4.5算法就是用“增益率”来作为选取最优划分属性。增益率定义为:

称为的“固有值”(intrinsic value)。通常的取值数目越多,则的值会越大。其中的的取值比如说:上述的年纪有青年、中年、老年三种取值。

对于上述的贷款数据集来说,信用情况有一般、好和非常好三种,比例分为是。毒刺,我们可以计算信用情况的“固有值”:

所以,对于信用属性来讲,其增益率为:

同理,我们可以计算出其他属性特征的增益率:

计算增益率的具体代码可参考如下:

  1. """
  2. Author: Taoye
  3. 微信公众号: 玩世不恭的Coder
  4. Explain:计算增益率
  5. """
  6. def calc_gain_ratio(data):
  7. feature_number = data.shape[1] - 1 # 属性特征的数量
  8. base_entropy = calc_information_entropy(data) # 计算总体数据集的信息熵
  9. for index in range(feature_number):
  10. feat_list = [item[index] for item in data]
  11. feat_set = set(feat_list)
  12. new_entropy, iv = 0.0, 0.0
  13. for set_item in feat_set: # 计算属性特征划分后的信息增益
  14. sub_data = handle_data(data, index, set_item)
  15. proportion = len(sub_data) / float(data.shape[0]) # 计算子集的比例
  16. new_entropy += proportion * calc_information_entropy(np.array(sub_data))
  17. iv += -proportion * np.log2(proportion)
  18. temp_information_gain = base_entropy - new_entropy # 计算信息增益
  19. gain_ratio = temp_information_gain / iv
  20. print("第%d个属性特征所对应的增益率为%.3f,信息增益为%.3f" % (index + 1, gain_ratio, temp_information_gain)) # 输出每个特征的信息增益

运行结果如下:

在C4.5算法中,并不是直接单一的使用增益率来对属性特征的选取进行决策,而是先在信息增益中选取高于平均水平的属性作为候选,然后从候选中选取增益率最高的。

关于C4.5算法,我们后期会讲到。

2.3 基尼指数

基尼指数是另外一种选取属性的指标。

前面我们提到了,信息熵是描述数据样本纯度一个标准,而在基尼指数中的基尼值同样可以体现数据样本的纯度。数据样本集的基尼值可以用来表示(其中表示有个标签结果):

基尼值越小,说明数据样本纯度越高。而属性对应的基尼指数可以定义为:

我们同样来分别计算下上述贷款数据集的基尼指数。

对于信用情况这一属性特征来讲,其基尼指数的手动计算过程如下所示:

对于其他属性的基尼指数,读者可自行根据上述过程进行计算(真的很简单)。关于基尼指数的计算代码,可参考如下:

  1. """
  2. Author: Taoye
  3. 微信公众号: 玩世不恭的Coder
  4. Explain:计算基尼指数
  5. """
  6. def calc_gini_index(data):
  7. feature_number = data.shape[1] - 1 # 属性特征的数量
  8. base_entropy = calc_information_entropy(data) # 计算总体数据集的信息熵
  9. for index in range(feature_number):
  10. feat_list = [item[index] for item in data]
  11. feat_set = set(feat_list)
  12. new_entropy, iv, gini_index = 0.0, 0.0, 0.0
  13. for set_item in feat_set: # 计算属性特征划分后的信息增益
  14. sub_data = handle_data(data, index, set_item)
  15. temp_df = pd.DataFrame(sub_data)
  16. yes_proportion = temp_df[temp_df[temp_df.shape[1]-1] == "Y"].shape[0] / len(sub_data)
  17. gini_value = 1 - (yes_proportion ** 2) - ((1- yes_proportion) ** 2)
  18. proportion = len(sub_data) / float(data.shape[0]) # 计算子集的比例
  19. new_entropy += proportion * calc_information_entropy(np.array(sub_data))
  20. iv += -proportion * np.log2(proportion)
  21. gini_index += gini_value * proportion # 计算基尼指数
  22. temp_information_gain = base_entropy - new_entropy # 计算信息增益
  23. gain_ratio = temp_information_gain / iv
  24. print("第%d个属性特征所对应的信息增益为%.3f,增益率为%.3f, 基尼指数为%.3f" % (index + 1, temp_information_gain, gain_ratio, gini_index))

运行结果:

以上就是基尼指数的计算及其相关代码,一般来讲,基尼指数越小就优先被划分。

上述内容的完整代码如下:

  1. import numpy as np
  2. import pandas as pd
  3. np.__version__
  4. pd.__version__
  5. """
  6. Author: Taoye
  7. 微信公众号: 玩世不恭的Coder
  8. Explain:创建训数据集
  9. """
  10. def establish_data():
  11. data = [[0, 0, 0, 0, 'N'], # 样本数据集相关信息,前面几项代表属性特征,最后一项表示是否放款
  12. [0, 0, 0, 1, 'N'],
  13. [0, 1, 0, 1, 'Y'],
  14. [0, 1, 1, 0, 'Y'],
  15. [0, 0, 0, 0, 'N'],
  16. [1, 0, 0, 0, 'N'],
  17. [1, 0, 0, 1, 'N'],
  18. [1, 1, 1, 1, 'Y'],
  19. [1, 0, 1, 2, 'Y'],
  20. [1, 0, 1, 2, 'Y'],
  21. [2, 0, 1, 2, 'Y'],
  22. [2, 0, 1, 1, 'Y'],
  23. [2, 1, 0, 1, 'Y'],
  24. [2, 1, 0, 2, 'Y'],
  25. [2, 0, 0, 0, 'N']]
  26. return np.array(data)
  27. """
  28. Author: Taoye
  29. 微信公众号: 玩世不恭的Coder
  30. Explain:计算信息熵
  31. """
  32. def calc_information_entropy(data):
  33. data_number, _ = data.shape
  34. information_entropy = 0
  35. for item in pd.DataFrame(data).groupby(_ - 1):
  36. proportion = item[1].shape[0] / data_number
  37. information_entropy += - proportion * np.log2(proportion)
  38. return information_entropy
  39. """
  40. Author: Taoye
  41. 微信公众号: 玩世不恭的Coder
  42. Explain:找出对应属性特征值的样本,比如找出所有年纪为青年的样本数据集
  43. """
  44. def handle_data(data, axis, value):
  45. result_data = list()
  46. for item in data:
  47. if item[axis] == value: result_data.append(item)
  48. return result_data
  49. """
  50. Author: Taoye
  51. 微信公众号: 玩世不恭的Coder
  52. Explain:计算最大的信息增益,并输出其所对应的特征索引
  53. """
  54. def calc_information_gain(data):
  55. feature_number = data.shape[1] - 1 # 属性特征的数量
  56. base_entropy = calc_information_entropy(data) # 计算总体数据集的信息熵
  57. max_information_gain, best_feature = 0.0, -1 # 初始化最大信息增益和对应的特征索引
  58. for index in range(feature_number):
  59. feat_list = [item[index] for item in data]
  60. feat_set = set(feat_list)
  61. new_entropy = 0.0
  62. for set_item in feat_set: # 计算属性特征划分后的信息增益
  63. sub_data = handle_data(data, index, set_item)
  64. proportion = len(sub_data) / float(data.shape[0]) # 计算子集的比例
  65. new_entropy += proportion * calc_information_entropy(np.array(sub_data))
  66. temp_information_gain = base_entropy - new_entropy # 计算信息增益
  67. print("第%d个属性特征所对应的的增益为%.3f" % (index + 1, temp_information_gain)) # 输出每个特征的信息增益
  68. if (temp_information_gain > max_information_gain):
  69. max_information_gain, best_feature = temp_information_gain, index # 更新信息增益,确定的最大的信息增益对应的索引
  70. return best_feature
  71. """
  72. Author: Taoye
  73. 微信公众号: 玩世不恭的Coder
  74. Explain:计算增益率
  75. """
  76. def calc_gain_ratio(data):
  77. feature_number = data.shape[1] - 1 # 属性特征的数量
  78. base_entropy = calc_information_entropy(data) # 计算总体数据集的信息熵
  79. for index in range(feature_number):
  80. feat_list = [item[index] for item in data]
  81. feat_set = set(feat_list)
  82. new_entropy, iv = 0.0, 0.0
  83. for set_item in feat_set: # 计算属性特征划分后的信息增益
  84. sub_data = handle_data(data, index, set_item)
  85. proportion = len(sub_data) / float(data.shape[0]) # 计算子集的比例
  86. new_entropy += proportion * calc_information_entropy(np.array(sub_data))
  87. iv += -proportion * np.log2(proportion)
  88. temp_information_gain = base_entropy - new_entropy # 计算信息增益
  89. gain_ratio = temp_information_gain / iv
  90. print("第%d个属性特征所对应的增益率为%.3f,信息增益为%.3f" % (index + 1, gain_ratio, temp_information_gain)) # 输出每个特征的信息增益
  91. """
  92. Author: Taoye
  93. 微信公众号: 玩世不恭的Coder
  94. Explain:计算基尼指数
  95. """
  96. def calc_gini_index(data):
  97. feature_number = data.shape[1] - 1 # 属性特征的数量
  98. base_entropy = calc_information_entropy(data) # 计算总体数据集的信息熵
  99. for index in range(feature_number):
  100. feat_list = [item[index] for item in data]
  101. feat_set = set(feat_list)
  102. new_entropy, iv, gini_index = 0.0, 0.0, 0.0
  103. for set_item in feat_set: # 计算属性特征划分后的信息增益
  104. sub_data = handle_data(data, index, set_item)
  105. temp_df = pd.DataFrame(sub_data)
  106. yes_proportion = temp_df[temp_df[temp_df.shape[1]-1] == "Y"].shape[0] / len(sub_data)
  107. gini_value = 1 - (yes_proportion ** 2) - ((1- yes_proportion) ** 2)
  108. proportion = len(sub_data) / float(data.shape[0]) # 计算子集的比例
  109. new_entropy += proportion * calc_information_entropy(np.array(sub_data))
  110. iv += -proportion * np.log2(proportion)
  111. gini_index += gini_value * proportion
  112. temp_information_gain = base_entropy - new_entropy # 计算信息增益
  113. gain_ratio = temp_information_gain / iv
  114. print("第%d个属性特征所对应的信息增益为%.3f,增益率为%.3f, 基尼指数为%.3f" % (index + 1, temp_information_gain, gain_ratio, gini_index))
  115. if __name__ == "__main__":
  116. data = establish_data()
  117. calc_gini_index(data)

优先选取属性特征的指标主要有三个,分别是信息增益、增益率、基尼指数。对上述内容做个简单的总结吧:

本文主要是决策树的理论部分内容,介绍了什么决策树,以及生成决策树时所需要优先选取的三种决策标准。有学习的过SVM,或阅读过Taoye之前写的几篇SVM内容的文章可以发现,决策树相对于SVM来讲要简单很多,没有太多且复杂的公式推导。关于决策树的其他内容,比如决策树的生成、可视化、剪枝等,我们放在后面几篇文章来写。

我是Taoye,爱专研,爱分享,热衷于各种技术,学习之余喜欢下象棋、听音乐、聊动漫,希望借此一亩三分地记录自己的成长过程以及生活点滴,也希望能结实更多志同道合的圈内朋友,更多内容欢迎来访微信公主号:玩世不恭的Coder

参考资料:

[1] 《机器学习实战》:Peter Harrington 人民邮电出版社
[2] 《统计学习方法》:李航 第二版 清华大学出版社
[3] 《机器学习》:周志华 清华大学出版社

推荐阅读

《Machine Learning in Action》—— 剖析支持向量机,优化SMO
《Machine Learning in Action》—— 剖析支持向量机,单手狂撕线性SVM
print( "Hello,NumPy!" )
干啥啥不行,吃饭第一名
Taoye渗透到一家黑平台总部,背后的真相细思极恐
《大话数据库》-SQL语句执行时,底层究竟做了什么小动作?
那些年,我们玩过的Git,真香
基于Ubuntu+Python+Tensorflow+Jupyter notebook搭建深度学习环境
网络爬虫之页面花式解析
手握手带你了解Docker容器技术
一文详解Hexo+Github小白建站
​打开ElasticSearch、kibana、logstash的正确方式

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注