目录

  • 一、决策树算法简介
  • 二、决策树分类原理
    • 1、熵
      • 1.1 概念
      • 1.2 案例
    • 2、决策树的划分依据一:信息增益
      • 2.1 概念
      • 2.2 案例:
    • 3、决策树的划分依据二:信息增益率
      • 3.1 概念
      • 3.2 案例
        • 3.2.1 案例一:
        • 3.2.2 案例二:
      • 3.3 为什么使用C4.5要好:
    • 4、决策树的划分依据三:基尼值和基尼指数
      • 4.1 概念
      • 4.2 案例
    • 5、小结:
      • 5.1 常见决策树的启发函数比较
        • 5.1.1 ID3 算法
        • 5.1.2 C4.5算法
        • 5.1.3 CART算法
        • 5.1.4 多变量决策树(multi-variate decision tree)
      • 5.2 决策树变量的两种类型:
      • 5.3 如何评估分割点的好坏?
  • 三、cart剪枝
    • 1、为什么要剪枝
    • 2、常用的减枝方法
      • 2.1 预剪枝
      • 2.2 后剪枝:
      • 小结:
  • 四、特征工程-特征提取
    • 1、特征提取
      • 1.1 定义
      • 1.2 特征提取API
    • 2、字典特征提取
      • 2.1 应用
      • 2.2 流程分析
      • 2.3 总结
    • 3、文本特征提取
      • 3.1 应用
      • 3.2 流程分析
      • 3.3 jieba分词处理
      • 3.4 案例分析
      • 3.5 Tf-idf文本特征提取
        • 3.5.1 公式
        • 3.5.2 案例
      • 3.6 Tf-idf的重要性
    • 4、总结:
  • 五、决策树算法api
  • 六、案例:泰坦尼克号乘客生存预测
    • 1、案例背景
    • 2、步骤分析
    • 3、代码实现
    • 4、决策树可视化
      • 4.1 保存树的结构到dot文件
      • 4.2 网站显示结构
    • 5、决策树总结

一、决策树算法简介

决策树思想的来源非常朴素,程序设计中的条件分支结构就是if-else结构,最早的决策树就是利用这类结构分割数据的一种分类学习方法。

决策树:是一种树形结构,其中每个内部节点表示一个属性上的判断,每个分支代表一个判断结果的输出,最后每个叶节点代表一种分类结果,本质是一颗由多个判断节点组成的树。

怎么理解这句话?通过一个对话例子


想一想这个女生为什么把年龄放在最上面判断!!!!!!!!!

上面案例是女生通过定性的主观意识,把年龄放到最上面,那么如果需要对这一过程进行量化,该如何处理呢?

此时需要用到信息论中的知识:信息熵,信息增益

下面接着看:

二、决策树分类原理

1、熵

1.1 概念

物理学上,熵 Entropy 是“混乱”程度的量度。


系统越有序,熵值越低;系统越混乱或者分散,熵值越高。

1948年香农提出了信息熵(Entropy)的概念。

  • 信息理论

1、从信息的完整性上进行的描述:

当系统的有序状态一致时,数据越集中的地方熵值越小,数据越分散的地方熵值越大。

2、从信息的有序性上进行的描述:

当数据量一致时,系统越有序,熵值越低;系统越混乱或者分散,熵值越高。

“信息熵” (information entropy)是度量样本集合纯度最常用的一种指标

假定当前样本集合 DDD 中第 kkk 类样本所占的比例为pkp_kpk​ (k = 1, 2,. . . , |y|) ,​​ pk=Ck/Dp_k=C^k/Dpk​=Ck/D ​​ , DDD为样本的所有数量,CkC^kCk为第kkk类样本的数量。
则 D的信息熵定义为((log是以2为底,lg是以10为底):

其中:Ent(D) 的值越小,则 D 的纯度越高。

1.2 案例

课堂案例:
假设我们没有看世界杯的比赛,但是想知道哪支球队会是冠军,
我们只能猜测某支球队是或不是冠军,然后观众用对或不对来回答,
我们想要猜测次数尽可能少,你会用什么方法?答案:
二分法:
假如有 16 支球队,分别编号,先问是否在 1-8 之间,如果是就继续问是否在 1-4 之间,
以此类推,直到最后判断出冠军球队是哪支。
如果球队数量是 16,我们需要问 4 次来得到最后的答案。那么世界冠军这条消息的信息熵就是 4。那么信息熵等于4,是如何进行计算的呢?
Ent(D) = -(p1 * logp1 + p2 * logp2 + ... + p16 * logp16),
其中 p1, ..., p16 分别是这 16 支球队夺冠的概率。
当每支球队夺冠概率相等都是 1/16 的时:Ent(D) = -(16 * 1/16 * log1/16) = 4
每个事件概率相同时,熵最大,这件事越不确定。

再看一个例子:

篮球比赛里,有4个球队 {A,B,C,D} ,获胜概率分别为{1/2, 1/4, 1/8, 1/8}
求Ent(D)答案:

2、决策树的划分依据一:信息增益

2.1 概念

信息增益:以某特征划分数据集前后的熵的差值。熵可以表示样本集合的不确定性,熵越大,样本的不确定性就越大。因此可以使用划分前后集合熵的差值来衡量使用当前特征对于样本集合D划分效果的好坏

信息增益 = entroy(前) - entroy(后)

注:信息增益表示得知特征X的信息而使得类Y的信息熵减少的程度

  • 定义与公式

假定离散属性a有 V 个可能的取值:

假设离散属性性别有2(男,女)个可能的取值

若使用a来对样本集 DDD 进行划分,则会产生 VVV 个分支结点,

其中第vvv个分支结点包含了 D 中所有在属性a上取值为ava^vav的样本,记DvD^vDv我们可根据前面给出的信息熵公式计算出DvD^vDv​的信息熵,再考虑到不同的分支结点所包含的样本数不同,给分支结点赋予权重∣Dv∣∣D∣\frac{|D^v|}{|D|}∣D∣∣Dv∣​
即样本数越多的分支结点的影响越大,于是可计算出用属性a对样本集 DDD 进行划分所获得的"信息增益" (information gain)

其中:
特征a对训练数据集DDD的信息增益Gain(D,a),定义为集合DDD的信息熵Ent(D)Ent(D)Ent(D)与给定特征a条件下DDD的信息条件熵Ent(D∣a)Ent(D|a)Ent(D∣a)之差,即公式为:

公式的详细解释:

  • 信息熵的计算:

  • 条件熵的计算:

    其中:

    • DvD^vDv表示a属性中第vvv个分支节点包含的样本数
    • CkvC^{kv}Ckv表示a属性中第vvv个分支节点包含的样本数中,第kkk个类别下包含的样本数

一般而言,信息增益越大,则意味着使用属性 a 来进行划分所获得的"纯度提升"越大。因此,我们可用信息增益来进行决策树的划分属性选择,著名的 ID3 决策树学习算法 [Quinlan, 1986] 就是以信息增益为准则来选择划分属性。

其中,ID3 名字中的 ID 是 Iterative Dichotomiser (迭代二分器)的简称

下面我就用一个例子说明怎么使用信息增益来划分决策树。

2.2 案例:

如下图,第一列为论坛号码,第二列为性别,第三列为活跃度,最后一列用户是否流失。

我们要解决一个问题:性别和活跃度两个特征,哪个对用户流失影响更大?

通过计算信息增益可以解决这个问题,统计上右表信息

其中Positive为正样本(已流失),Negative为负样本(未流失),下面的数值为不同划分下对应的人数。

可得到三个熵:

  • 计算类别信息熵
    整体熵:

    (1)性别属性:
  • 计算性别属性的信息熵(a=“性别”)
  • 计算性别的信息增益(a=“性别”)

    (2)活跃度属性:
  • 计算活跃度属性的信息熵(a=“活跃度”)
  • 计算活跃度的信息增益(a=“活跃度”)

    活跃度的信息增益比性别的信息增益大,也就是说,活跃度对用户流失的影响比性别大。在做特征选择或者数据分析的时候,我们应该重点考察活跃度这个指标。

3、决策树的划分依据二:信息增益率

3.1 概念

在上面的介绍中,我们有意忽略了"编号"这一列.若把"编号"也作为一个候选划分属性,则根据信息增益公式可计算出它的信息增益为 0.9182,远大于其他候选划分属性。

计算每个属性的信息熵过程中,我们发现,该属性的值为0, 也就是其信息增益为0.9182. 但是很明显这么分类,最后出现的结果不具有泛化效果.无法对新样本进行有效预测.

实际上,信息增益准则对可取值数目较多的属性有所偏好,为减少这种偏好可能带来的不利影响,著名的 C4.5 决策树算法 [Quinlan, 1993J 不直接使用信息增益,而是使用"增益率" (gain ratio) 来选择最优划分属性.

增益率:增益率是用前面的信息增益Gain(D, a)和属性a对应的"固有值"(intrinsic value) [Quinlan , 1993J的比值来共同定义的。

属性 a 的可能取值数目越多(即 V 越大),则 IV(a) 的值通常会越大.

3.2 案例

3.2.1 案例一:

  • a.计算类别信息熵

  • b.计算性别属性的信息熵(性别、活跃度)

  • c.计算活跃度的信息增益(性别、活跃度)

以上三个前面已经计算过了,这里就不再重复,可去前面看看怎么计算的,下面直接开始怎么计算信息增益率

  • d.计算属性分裂信息度量
    用分裂信息度量来考虑某种属性进行分裂时分支的数量信息和尺寸信息,我们把这些信息称为属性的内在信息(instrisic information)。信息增益率用信息增益/内在信息,会导致属性的重要性随着内在信息的增大而减小(也就是说,如果这个属性本身不确定性就很大,那我就越不倾向于选取它),这样算是对单纯用信息增益有所补偿。
  • e.计算信息增益率

活跃度的信息增益率更高一些,所以在构建决策树的时候,优先选择

通过这种方式,在选取节点的过程中,我们可以降低取值较多的属性的选取偏好。

3.2.2 案例二:

如下图,第一列为天气,第二列为温度,第三列为湿度,第四列为风速,最后一列该活动是否进行。

我们要解决:根据下面表格数据,判断在对应天气下,活动是否会进行?


该数据集有四个属性,属性集合A={ 天气,温度,湿度,风速}, 类别标签有两个,类别集合L={进行,取消}。

  • a.计算类别信息熵
    类别信息熵表示的是所有样本中各种类别出现的不确定性之和。根据熵的概念,熵越大,不确定性就越大,把事情搞清楚所需要的信息量就越多。
  • b.计算每个属性的信息熵
    每个属性的信息熵相当于一种条件熵。他表示的是在某种属性的条件下,各种类别出现的不确定性之和。属性的信息熵越大,表示这个属性中拥有的样本类别越不“纯”。
  • c.计算信息增益
    信息增益的 = 熵 - 条件熵,在这里就是 类别信息熵 - 属性信息熵,它表示的是信息不确定性减少的程度。如果一个属性的信息增益越大,就表示用这个属性进行样本划分可以更好的减少划分后样本的不确定性,当然,选择该属性就可以更快更好地完成我们的分类目标。

信息增益就是ID3算法的特征选择指标。

假设我们把上面表格1的数据前面添加一列为"编号",取值(1–14). 若把"编号"也作为一个候选划分属性,则根据前面步骤: 计算每个属性的信息熵过程中,我们发现,该属性的值为0, 也就是其信息增益为0.940. 但是很明显这么分类,最后出现的结果不具有泛化效果.此时根据信息增益就无法选择出有效分类特征。所以,C4.5选择使用信息增益率对ID3进行改进。

  • d.计算属性分裂信息度量

用分裂信息度量来考虑某种属性进行分裂时分支的数量信息和尺寸信息,我们把这些信息称为属性的内在信息(instrisic information)。信息增益率用信息增益/内在信息,会导致属性的重要性随着内在信息的增大而减小(也就是说,如果这个属性本身不确定性就很大,那我就越不倾向于选取它),这样算是对单纯用信息增益有所补偿。

  • e.计算信息增益率

    天气的信息增益率最高,选择天气为分裂属性。发现分裂了之后,天气是“阴”的条件下,类别是”纯“的,所以把它定义为叶子节点,选择不“纯”的结点继续分裂。

在子结点当中重复过程1~5,直到所有的叶子结点足够"纯"。

现在我们来总结一下C4.5的算法流程:

while(当前节点"不纯"):1.计算当前节点的类别熵(以类别取值计算)2.计算当前阶段的属性熵(按照属性取值吓得类别取值计算)3.计算信息增益4.计算各个属性的分裂信息度量5.计算各个属性的信息增益率
end while
当前阶段设置为叶子节点

3.3 为什么使用C4.5要好:

1.用信息增益率来选择属性

克服了用信息增益来选择属性时偏向选择值多的属性的不足。

2.采用了一种后剪枝方法

避免树的高度无节制的增长,避免过度拟合数据

3.对于缺失值的处理

在某些情况下,可供使用的数据可能缺少某些属性的值。假如〈x,c(x)〉是样本集S中的一个训练实例,但是其属性A的值A(x)未知。

处理缺少属性值的一种策略是赋给它结点n所对应的训练实例中该属性的最常见值;

另外一种更复杂的策略是为A的每个可能值赋予一个概率。

例如,给定一个布尔属性A,如果结点n包含6个已知A=1和4个A=0的实例,那么A(x)=1的概率是0.6,而A(x)=0的概率是0.4。于是,实例x的60%60\%60%被分配到A=1的分支,40%40\%40%被分配到另一个分支。

C4.5就是使用这种方法处理缺少的属性值。

4、决策树的划分依据三:基尼值和基尼指数

4.1 概念

CART 决策树 [Breiman et al., 1984] 使用"基尼指数" (Gini index)来选择划分属性.

CART 是Classification and Regression Tree的简称,这是一种著名的决策树学习算法,分类和回归任务都可用

基尼值Gini(D):从数据集D中随机抽取两个样本,其类别标记不一致的概率。故,Gini(D)值越小,数据集D的纯度越高。

数据集 D 的纯度可用基尼值来度量:

pk=Ck/Dp_k=C^k/Dpk​=Ck/D,DDD为样本的所有数量,CkC^kCk​​ 为第kkk类样本的数量。

基尼指数Gini_index(D):一般,选择使划分后基尼系数最小的属性作为最优化分属性。

4.2 案例

请根据下图列表,按照基尼指数的划分依据,做出决策树。

序号 是否有房 婚姻状况 年收入 是否拖欠贷款
1 yes single 125k no
2 no married 100k no
3 no single 70k no
4 yes married 120k no
5 no divorced 95k yes
6 no married 60k no
7 yes divorced 220k no
8 no single 85k yes
9 no married 75k no
10 No Single 90k Yes
  • 1,对数据集非序列标号属性{是否有房,婚姻状况,年收入}分别计算它们的Gini指数,取Gini指数最小的属性作为决策树的根节点属性

第一次大循环

  • 2,根节点的Gini值为:
  • 3,当根据是否有房来进行划分时,Gini指数计算过程为:


  • 4,若按婚姻状况属性来划分,属性婚姻状况有三个可能的取值{married,single,divorced},分别计算划分后的Gini系数增益。
    ​ {married} | {single,divorced}
    ​ {single} | {married,divorced}
    ​ {divorced} | {single,married}

    对比计算结果,根据婚姻状况属性来划分根节点时取Gini指数最小的分组作为划分结果,即:
    {married} | {single,divorced}
  • 5,同理可得年收入Gini:
    对于年收入属性为数值型属性,首先需要对数据按升序排序,然后从小到大依次用相邻值的中间值作为分隔将样本划分为两组。例如当面对年收入为60和70这两个值时,我们算得其中间值为65。以中间值65作为分割点求出Gini指数。


    根据计算知道,三个属性划分根节点的指数最小的有两个:年收入属性婚姻状况,他们的指数都为0.3。此时,选取首先出现的属性【married】作为第一次划分。

第二次大循环

  • 6,接下来,采用同样的方法,分别计算剩下属性,其中根节点的Gini系数为(此时是否拖欠贷款的各有3个records):

    注意:因为结婚了4个,所以在D中把这4个给除掉了,所以D=6,即此时的集合里元素数量为6.

  • 7,对于是否有房属性,可得:

  • 8,对于年收入属性则有:

  • 9,0.25<0.343,所以选择是否有房作为第二部分划分,然后D=4,再次按这个D=4计算年收入的基尼指数,计算出基尼指数最小对应的年收入为77.5。

到此划分结束!!!

经过如上流程,构建的决策树,如下图:

现在我们来总结一下CART算法的流程:

while(当前节点"不纯"):1.遍历每个变量的每一种分割方式,找到最好的分割点2.分割成两个节点N1和N2
end while
每个节点足够“纯”为止

5、小结:

5.1 常见决策树的启发函数比较

名称 提出时间 分支方式 备注
ID3 1975 信息增益 ID3只能对离散属性的数据集构成决策树
C4.5 1993 信息增益率 优化后解决了ID3分支过程中总喜欢偏向选择值较多的 属性
CART 1984 Gini系数 可以进行分类和回归,可以处理离散属性,也可以处理连续属性

5.1.1 ID3 算法

存在的缺点

​ (1) ID3算法在选择根节点和各内部节点中的分支属性时,采用信息增益作为评价标准。信息增益的缺点是倾向于选择取值较多的属性,在有些情况下这类属性可能不会提供太多有价值的信息.

​ (2) ID3算法只能对描述属性为离散型属性的数据集构造决策树

5.1.2 C4.5算法

做出的改进(为什么使用C4.5要好)

​ (1) 用信息增益率来选择属性

​ (2) 可以处理连续数值型属性

​ (3)采用了一种后剪枝方法

​ (4)对于缺失值的处理

C4.5算法的优缺点

​ 优点:

​ 产生的分类规则易于理解,准确率较高。

​ 缺点:

​ 在构造树的过程中,需要对数据集进行多次的顺序扫描和排序,因而导致算法的低效。

​ 此外,C4.5只适合于能够驻留于内存的数据集,当训练集大得无法在内存容纳时程序无法运行。

5.1.3 CART算法

CART算法相比C4.5算法的分类方法,采用了简化的二叉树模型,同时特征选择采用了近似的基尼系数来简化计算。

C4.5不一定是二叉树,但CART一定是二叉树。

5.1.4 多变量决策树(multi-variate decision tree)

同时,无论是ID3, C4.5还是CART,在做特征选择的时候都是选择最优的一个特征来做分类决策,但是大多数,分类决策不应该是由某一个特征决定的,而是应该由一组特征决定的。这样决策得到的决策树更加准确。这个决策树叫做多变量决策树(multi-variate decision tree)。在选择最优特征的时候,多变量决策树不是选择某一个最优特征,而是选择最优的一个特征线性组合来做决策。这个算法的代表是OC1,这里不多介绍。

如果样本发生一点点的改动,就会导致树结构的剧烈改变。这个可以通过集成学习里面的随机森林之类的方法解决。

5.2 决策树变量的两种类型:

  • 1.数字型(Numeric):变量类型是整数或浮点数,如前面例子中的“年收入”。用“>=”,“>”,“<”或“<=”作为分割条件(排序后,利用已有的分割情况,可以优化分割算法的时间复杂度)。
  • 2.名称型(Nominal):类似编程语言中的枚举类型,变量只能从有限的选项中选取,比如前面例子中的“婚姻情况”,只能是“单身”,“已婚”或“离婚”,使用“=”来分割。

5.3 如何评估分割点的好坏?

如果一个分割点可以将当前的所有节点分为两类,使得每一类都很“纯”,也就是同一类的记录较多,那么就是一个好分割点。

比如上面的例子,“拥有房产”,可以将记录分成了两类,“是”的节点全部都可以偿还债务,非常“纯”;“否”的节点,可以偿还贷款和无法偿还贷款的人都有,不是很“纯”,但是两个节点加起来的纯度之和与原始节点的纯度之差最大,所以按照这种方法分割。

构建决策树采用贪心算法,只考虑当前纯度差最大的情况作为分割点。

三、cart剪枝

1、为什么要剪枝

  • 图形描述

    • 横轴表示在决策树创建过程中树的结点总数,纵轴表示决策树的预测精度。
    • 实线显示的是决策树在训练集上的精度,虚线显示的则是在一个独立的测试集上测量出来的精度。
    • 随着树的增长,在训练样集上的精度是单调上升的, 然而在独立的测试样例上测出的精度先上升后下降。
  • 出现这种情况的原因:
    • 原因1:噪声、样本冲突,即错误的样本数据。
    • 原因2:特征即属性不能完全作为分类标准。
    • 原因3:巧合的规律性,数据量不够大。

2、常用的减枝方法

2.1 预剪枝

(1)每一个结点所包含的最小样本数目,例如10,则该结点总样本数小于10时,则不再分;

(2)指定树的高度或者深度,例如树的最大深度为4;

(3)指定结点的熵小于某个值,不再划分。随着树的增长, 在训练样集上的精度是单调上升的, 然而在独立的测试样例上测出的精度先上升后下降。

2.2 后剪枝:

后剪枝,在已生成过拟合决策树上进行剪枝,可以得到简化版的剪枝决策树。


小结:

  • 剪枝原因

    • 噪声、样本冲突,即错误的样本数据
    • 特征即属性不能完全作为分类标准
    • 巧合的规律性,数据量不够大。
  • 常用剪枝方法
    • 预剪枝

      • 在构建树的过程中,同时剪枝

        • 限制节点最小样本数
        • 指定数据高度
        • 指定熵值的最小值
    • 后剪枝
      • 把一棵树,构建完成之后,再进行从下往上的剪枝

四、特征工程-特征提取

什么是特征提取呢?

1、特征提取

1.1 定义

将任意数据(如文本或图像)转换为可用于机器学习的数字特征

注:特征值化是为了计算机更好的去理解数据

  • 特征提取分类:

    • 字典特征提取(特征离散化)
    • 文本特征提取
    • 图像特征提取(深度学习将介绍)

1.2 特征提取API

sklearn.feature_extraction

2、字典特征提取

作用:对字典数据进行特征值化

sklearn.feature_extraction.DictVectorizer(sparse=True,…)
  • DictVectorizer.fit_transform(X)

    • X:字典或者包含字典的迭代器返回值
    • 返回sparse矩阵
  • DictVectorizer.get_feature_names() 返回类别名称

2.1 应用

我们对以下数据进行特征提取:

[{'city': '北京','temperature':100},
{'city': '上海','temperature':60},
{'city': '深圳','temperature':30}]

2.2 流程分析

  • 实例化类DictVectorizer
  • 调用fit_transform方法输入数据并转换(注意返回格式)
from sklearn.feature_extraction import DictVectorizerdef dict_demo():"""字典特征提取:return:None"""# 1.获取数据data = [{'city': '北京', 'temperature': 100},{'city': '上海', 'temperature': 60},{'city': '深圳', 'temperature': 30}]# 2.字典特征提取# 2.1实例化transfer = DictVectorizer(sparse=True) # sparse好处是节省内存,提高访问效率# 2.2转换new_data = transfer.fit_transform(data)print(new_data)# 2.3获取具体属性名names = transfer.get_feature_names()print(names)if __name__ == "__main__":dict_demo()

注意观察sparse=False参数的结果:

返回的结果:(0, 1)    1.0(0, 3)    100.0(1, 0)    1.0(1, 3)    60.0(2, 2)    1.0(2, 3)    30.0
特征名字:['city=上海', 'city=北京', 'city=深圳', 'temperature']

这个结果和我们平时的不一样,这个是稀疏矩阵的存储,那再看看sparse=True参数的结果:

返回的结果:[[   0.    1.    0.  100.][   1.    0.    0.   60.][   0.    0.    1.   30.]]
特征名字:['city=上海', 'city=北京', 'city=深圳', 'temperature']

我们把这个处理数据的技巧叫做”one-hot“编码:

转化为:

2.3 总结

对于特征当中存在类别信息的我们都会做one-hot编码处理

3、文本特征提取

作用:对文本数据进行特征值化

API:
第一个:

sklearn.feature_extraction.text.CountVectorizer(stop_words=[])
  • stop_words:表示哪些词不用特征值化
  • 返回词频矩阵

然后:

  • CountVectorizer.fit_transform(X)

    • X:文本或者包含文本字符串的可迭代对象
    • 返回值:返回sparse矩阵
  • CountVectorizer.get_feature_names() 返回值:单词列表

第二个:

sklearn.feature_extraction.text.TfidfVectorizer

3.1 应用

我们对以下数据进行特征提取:

["life is short,i like python",
"life is too long,i dislike python"]


那怎么实现上面的编码呢?接着看:

3.2 流程分析

  • 实例化类CountVectorizer
  • 调用fit_transform方法输入数据并转换 (注意返回格式,利用toarray()进行sparse矩阵转换array数组)
from sklearn.feature_extraction.text import CountVectorizerdef text_count_demo():"""对文本进行特征抽取,countvetorizer:return: None"""data = ["life is short,i like like python", "life is too long,i dislike python"]# 1、实例化一个转换器类# transfer = CountVectorizer(sparse=False) # 注意,没有sparse这个参数transfer = CountVectorizer() # 注意:有stop_words=[]这个参数,# 2、调用fit_transformdata = transfer.fit_transform(data)print("文本特征抽取的结果:\n", data.toarray())print("返回特征名字:\n", transfer.get_feature_names())return None

返回结果:

文本特征抽取的结果:[[0 1 1 2 0 1 1 0][1 1 1 0 1 1 0 1]]
返回特征名字:['dislike', 'is', 'life', 'like', 'long', 'python', 'short', 'too']

注意:返回的是特征名字出现的次数

问题:如果我们将数据替换成中文?

"人生苦短,我喜欢Python","生活太长久,我不喜欢Python"

那么最终得到的结果是:


为什么会得到这样的结果呢,仔细分析之后会发现英文默认是以空格分开的。其实就达到了一个分词的效果,所以我们要对中文进行分词处理。

3.3 jieba分词处理

  • jieba.cut()

    • 返回词语组成的生成器

需要安装下jieba库

pip3 install jieba

3.4 案例分析

对以下三句话进行特征值化

今天很残酷,明天更残酷,后天很美好,
但绝对大部分是死在明天晚上,所以每个人不要放弃今天。我们看到的从很远星系来的光是在几百万年之前发出的,
这样当我们看到宇宙时,我们是在看它的过去。如果只用一种方式了解某样事物,你就不会真正了解它。
了解事物真正含义的秘密取决于如何将其与我们所了解的事物相联系。

分析:

  • 准备句子,利用jieba.cut进行分词
  • 实例化CountVectorizer
  • 将分词结果变成字符串当作fit_transform的输入值

from sklearn.feature_extraction.text import CountVectorizer
import jiebadef cut_word(text):"""对中文进行分词"我爱北京天安门"————>"我 爱 北京 天安门":param text::return: text"""# 用jieba对中文字符串进行分词text = " ".join(list(jieba.cut(text)))return textdef text_chinese_count_demo2():"""对中文进行特征抽取:return: None"""data = ["一种还是一种今天很残酷,明天更残酷,后天很美好,但绝对大部分是死在明天晚上,所以每个人不要放弃今天。","我们看到的从很远星系来的光是在几百万年之前发出的,这样当我们看到宇宙时,我们是在看它的过去。","如果只用一种方式了解某样事物,你就不会真正了解它。了解事物真正含义的秘密取决于如何将其与我们所了解的事物相联系。"]# 将原始数据转换成分好词的形式text_list = []for sent in data:text_list.append(cut_word(sent))print(text_list)# 1、实例化一个转换器类# transfer = CountVectorizer(sparse=False)transfer = CountVectorizer()# 2、调用fit_transformdata = transfer.fit_transform(text_list)print("文本特征抽取的结果:\n", data.toarray())print("返回特征名字:\n", transfer.get_feature_names())return None

返回结果:

['一种 还是 一种 今天 很 残酷 , 明天 更 残酷 , 后天 很 美好 , 但 绝对 大部分 是 死 在 明天 晚上 , 所以 每个 人 不要 放弃 今天 。', '我们 看到 的 从 很 远 星系 来 的 光是在 几百万年 之前 发出 的 , 这样 当 我们 看到 宇宙 时 , 我们 是 在 看 它 的 过去 。', '如果 只用 一种 方式 了解 某样 事物 , 你 就 不会 真正 了解 它 。 了解 事物 真正 含义 的 秘密 取决于 如何 将 其 与 我们 所 了解 的 事物 相 联系 。']
Prefix dict has been built succesfully.
文本特征抽取的结果:[[2 0 1 0 0 0 2 0 0 0 0 0 1 0 1 0 0 0 0 1 1 0 2 0 1 0 2 1 0 0 0 1 1 0 0 1 0][0 0 0 1 0 0 0 1 1 1 0 0 0 0 0 0 0 1 3 0 0 0 0 1 0 0 0 0 2 0 0 0 0 0 1 0 1][1 1 0 0 4 3 0 0 0 0 1 1 0 1 0 1 1 0 1 0 0 1 0 0 0 1 0 0 0 2 1 0 0 1 0 0 0]]
返回特征名字:['一种', '不会', '不要', '之前', '了解', '事物', '今天', '光是在', '几百万年', '发出', '取决于', '只用', '后天', '含义', '大部分', '如何', '如果', '宇宙', '我们', '所以', '放弃', '方式', '明天', '星系', '晚上', '某样', '残酷', '每个', '看到', '真正', '秘密', '绝对', '美好', '联系', '过去', '还是', '这样']

但如果把这样的词语特征用于分类,会出现什么问题?

请看问题:

该如何处理某个词或短语在多篇文章中出现的次数高这种情况

3.5 Tf-idf文本特征提取

  • TF-IDF的主要思想是:如果某个词或短语在一篇文章中出现的概率高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来分类
  • TF-IDF作用:用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度

3.5.1 公式

  • 词频(term frequency,tf)指的是某一个给定的词语在该文件中出现的频率
  • 逆向文档频率(inverse document frequency,idf)是一个词语普遍重要性的度量。某一特定词语的idf,可以由总文件数目除以包含该词语之文件的数目,再将得到的商取以10为底的对数得到

最终得出结果可以理解为重要程度。

举例:
假如一篇文章的总词语数是100个,而词语"非常"出现了5次,那么"非常"一词在该文件中的词频就是5/100=0.05。
而计算文件频率(IDF)的方法是以文件集的文件总数,除以出现"非常"一词的文件数。
所以,如果"非常"一词在1,0000份文件出现过,而文件总数是10,000,000份的话,
其逆向文件频率就是lg(10,000,000 / 1,0000)=3。
最后"非常"对于这篇文档的tf-idf的分数为0.05 * 3=0.15

3.5.2 案例

from sklearn.feature_extraction.text import TfidfVectorizer
import jiebadef cut_word(text):"""对中文进行分词"我爱北京天安门"————>"我 爱 北京 天安门":param text::return: text"""# 用结巴对中文字符串进行分词text = " ".join(list(jieba.cut(text)))return textdef text_chinese_tfidf_demo():"""对中文进行特征抽取:return: None"""data = ["一种还是一种今天很残酷,明天更残酷,后天很美好,但绝对大部分是死在明天晚上,所以每个人不要放弃今天。","我们看到的从很远星系来的光是在几百万年之前发出的,这样当我们看到宇宙时,我们是在看它的过去。","如果只用一种方式了解某样事物,你就不会真正了解它。了解事物真正含义的秘密取决于如何将其与我们所了解的事物相联系。"]# 将原始数据转换成分好词的形式text_list = []for sent in data:text_list.append(cut_word(sent))print(text_list)# 1、实例化一个转换器类# transfer = CountVectorizer(sparse=False)transfer = TfidfVectorizer(stop_words=['一种', '不会', '不要'])# 2、调用fit_transformdata = transfer.fit_transform(text_list)print("文本特征抽取的结果:\n", data.toarray())print("返回特征名字:\n", transfer.get_feature_names())return None

返回结果:

['一种 还是 一种 今天 很 残酷 , 明天 更 残酷 , 后天 很 美好 , 但 绝对 大部分 是 死 在 明天 晚上 , 所以 每个 人 不要 放弃 今天 。', '我们 看到 的 从 很 远 星系 来 的 光是在 几百万年 之前 发出 的 , 这样 当 我们 看到 宇宙 时 , 我们 是 在 看 它 的 过去 。', '如果 只用 一种 方式 了解 某样 事物 , 你 就 不会 真正 了解 它 。 了解 事物 真正 含义 的 秘密 取决于 如何 将 其 与 我们 所 了解 的 事物 相 联系 。']
文本特征抽取的结果:[[ 0.          0.          0.          0.43643578  0.          0.          0.0.          0.          0.21821789  0.          0.21821789  0.          0.0.          0.          0.21821789  0.21821789  0.          0.436435780.          0.21821789  0.          0.43643578  0.21821789  0.          0.0.          0.21821789  0.21821789  0.          0.          0.218217890.        ][ 0.2410822   0.          0.          0.          0.2410822   0.24108220.2410822   0.          0.          0.          0.          0.          0.0.          0.2410822   0.55004769  0.          0.          0.          0.0.2410822   0.          0.          0.          0.          0.482164410.          0.          0.          0.          0.          0.24108220.          0.2410822 ][ 0.          0.644003    0.48300225  0.          0.          0.          0.0.16100075  0.16100075  0.          0.16100075  0.          0.161000750.16100075  0.          0.12244522  0.          0.          0.161000750.          0.          0.          0.16100075  0.          0.          0.0.3220015   0.16100075  0.          0.          0.16100075  0.          0.0.        ]]
返回特征名字:['之前', '了解', '事物', '今天', '光是在', '几百万年', '发出', '取决于', '只用', '后天', '含义', '大部分', '如何', '如果', '宇宙', '我们', '所以', '放弃', '方式', '明天', '星系', '晚上', '某样', '残酷', '每个', '看到', '真正', '秘密', '绝对', '美好', '联系', '过去', '还是', '这样']

这里再解释一下文本特征提取的结果:返回的是tf-idf分数,也就是特征在每篇文章中的tf-idf分数,比如说:‘之前’这个特征在三篇文章中的tf-idf分数分别为0、0.2410822、0,那就说明在第二篇中的重要程度比其他两篇大。

3.6 Tf-idf的重要性

分类机器学习算法进行文章分类中前期数据处理方式

4、总结:

五、决策树算法api

这个只是最重要的几个参数,接下来的案例中会用到的,还有一些参数可以到官网去查看。

六、案例:泰坦尼克号乘客生存预测

1、案例背景

泰坦尼克号沉没是历史上最臭名昭着的沉船之一。1912年4月15日,在她的处女航中,泰坦尼克号在与冰山相撞后沉没,在2224名乘客和机组人员中造成1502人死亡。这场耸人听闻的悲剧震惊了国际社会,并为船舶制定了更好的安全规定。 造成海难失事的原因之一是乘客和机组人员没有足够的救生艇。尽管幸存下沉有一些运气因素,但有些人比其他人更容易生存,例如妇女,儿童和上流社会。 在这个案例中,我们要求您完成对哪些人可能存活的分析。特别是,我们要求您运用机器学习工具来预测哪些乘客幸免于悲剧。

案例:https://www.kaggle.com/c/titanic/overview

我们提取到的数据集中的特征包括票的类别,是否存活,乘坐班次,年龄,登陆home.dest,房间,船和性别等。

数据:https://www.kaggle.com/c/titanic/data.去这里把训练集和测试集下载下来。

经过观察数据得到:

  • 1 乘坐班是指乘客班(1,2,3),是社会经济阶层的代表。

  • 2 其中age数据存在缺失。

2、步骤分析

  • 1.获取数据
  • 2.数据基本处理
    • 2.1 确定特征值,目标值
    • 2.2 缺失值处理
    • 2.3 数据集划分
  • 3.特征工程(字典特征抽取)
  • 4.机器学习(决策树)
  • 5.模型评估

3、代码实现

  • 导入需要的模块
import pandas as pd
import numpy as np
from sklearn.feature_extraction import DictVectorizer
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier, export_graphviz
  • 1.获取数据
# 1、获取数据
titan = pd.read_csv("data/train.csv") # 数据我从官网下载下来了
  • 2.数据基本处理

  • 2.1 确定特征值,目标值

x = titan[["pclass", "age", "sex"]]
y = titan["survived"]
  • 2.2 缺失值处理
# 缺失值需要处理,将特征当中有类别的这些特征进行字典特征抽取
x['age'].fillna(x['age'].mean(), inplace=True)
  • 2.3 数据集划分
x_train, x_test, y_train, y_test = train_test_split(x, y, random_state=22, test_size=0.2)
  • 3.特征工程(字典特征抽取)
    特征中出现类别符号,需要进行one-hot编码处理(DictVectorizer);
    x.to_dict(orient=“records”) 需要将数组特征转换成字典数据
# 对于x转换成字典数据x.to_dict(orient="records")
# ['Age', 'Pclass', 'Sex=female', 'Sex=male']transfer = DictVectorizer(sparse=False)x_train = transfer.fit_transform(x_train.to_dict(orient="records"))
x_test = transfer.fit_transform(x_test.to_dict(orient="records"))# 以下是查看字典特征提取后的代码
x_train.toarray() # 查看字典特征提取后的结果
输出:
array([[29.69911765,  1.        ,  0.        ,  1.        ],[30.5       ,  3.        ,  0.        ,  1.        ],[ 3.        ,  2.        ,  1.        ,  0.        ],...,[35.        ,  2.        ,  0.        ,  1.        ],[47.        ,  3.        ,  1.        ,  0.        ],[39.        ,  3.        ,  1.        ,  0.        ]])
# 有点懵逼,不用怕,看看每一行的特征名不就清楚了嘛:
transfer.get_feature_names()
输出:
['Age', 'Pclass', 'Sex=female', 'Sex=male']
  • 4.决策树模型训练和模型评估
    决策树API当中,如果没有指定max_depth那么会根据信息熵的条件直到最终结束。这里我们可以指定树的深度来进行限制树的大小
# 4.机器学习(决策树)
estimator = DecisionTreeClassifier(criterion="entropy", max_depth=5)
estimator.fit(x_train, y_train)# 5.模型评估
estimator.score(x_test, y_test) # accestimator.predict(x_test)

决策树的结构是可以直接显示

4、决策树可视化

4.1 保存树的结构到dot文件

  • sklearn.tree.export_graphviz() 该函数能够导出DOT格式

    • tree.export_graphviz(estimator,out_file='tree.dot’,feature_names=[‘’,’’])

在上面泰坦尼克号乘客生存预测例子上继续:

from sklearn.tree import export_graphvizexport_graphviz(estimator, out_file="./data/tree.dot", feature_names=['age', 'pclass=1st', 'pclass=2nd', 'pclass=3rd', '女性', '男性'])

上面代码需要注意的是:feature_names里面的特征名字需要把每一个特征的每一个对应值都写出来。

dot文件当中的内容如下:

digraph Tree {node [shape=box] ;
0 [label="女性 <= 0.5\ngini = 0.473\nsamples = 712\nvalue = [439, 273]"] ;
1 [label="age <= 13.0\ngini = 0.298\nsamples = 462\nvalue = [378, 84]"] ;
0 -> 1 [labeldistance=2.5, labelangle=45, headlabel="True"] ;
2 [label="pclass <= 2.5\ngini = 0.464\nsamples = 30\nvalue = [11, 19]"] ;
1 -> 2 ;
3 [label="gini = 0.0\nsamples = 11\nvalue = [0, 11]"] ;
2 -> 3 ;
4 [label="age <= 0.71\ngini = 0.488\nsamples = 19\nvalue = [11, 8]"] ;
2 -> 4 ;
5 [label="gini = 0.0\nsamples = 1\nvalue = [0, 1]"] ;
4 -> 5 ;
6 [label="age <= 11.5\ngini = 0.475\nsamples = 18\nvalue = [11, 7]"] ;
4 -> 6 ;
7 [label="gini = 0.457\nsamples = 17\nvalue = [11, 6]"] ;
6 -> 7 ;
8 [label="gini = 0.0\nsamples = 1\nvalue = [0, 1]"] ;
6 -> 8 ;
9 [label="pclass <= 1.5\ngini = 0.256\nsamples = 432\nvalue = [367, 65]"] ;
1 -> 9 ;
10 [label="age <= 51.5\ngini = 0.424\nsamples = 95\nvalue = [66, 29]"] ;
9 -> 10 ;
11 [label="age <= 47.5\ngini = 0.456\nsamples = 74\nvalue = [48, 26]"] ;
10 -> 11 ;
12 [label="gini = 0.415\nsamples = 68\nvalue = [48, 20]"] ;
11 -> 12 ;
13 [label="gini = 0.0\nsamples = 6\nvalue = [0, 6]"] ;
11 -> 13 ;
14 [label="age <= 75.5\ngini = 0.245\nsamples = 21\nvalue = [18, 3]"] ;
10 -> 14 ;
15 [label="gini = 0.18\nsamples = 20\nvalue = [18, 2]"] ;
14 -> 15 ;
16 [label="gini = 0.0\nsamples = 1\nvalue = [0, 1]"] ;
14 -> 16 ;
17 [label="age <= 32.25\ngini = 0.191\nsamples = 337\nvalue = [301, 36]"] ;
9 -> 17 ;
18 [label="age <= 31.5\ngini = 0.214\nsamples = 254\nvalue = [223, 31]"] ;
17 -> 18 ;
19 [label="gini = 0.191\nsamples = 243\nvalue = [217, 26]"] ;
18 -> 19 ;
20 [label="gini = 0.496\nsamples = 11\nvalue = [6, 5]"] ;
18 -> 20 ;
21 [label="age <= 41.5\ngini = 0.113\nsamples = 83\nvalue = [78, 5]"] ;
17 -> 21 ;
22 [label="gini = 0.044\nsamples = 44\nvalue = [43, 1]"] ;
21 -> 22 ;
23 [label="gini = 0.184\nsamples = 39\nvalue = [35, 4]"] ;
21 -> 23 ;
24 [label="pclass <= 2.5\ngini = 0.369\nsamples = 250\nvalue = [61, 189]"] ;
0 -> 24 [labeldistance=2.5, labelangle=-45, headlabel="False"] ;
25 [label="age <= 2.5\ngini = 0.086\nsamples = 134\nvalue = [6, 128]"] ;
24 -> 25 ;
26 [label="pclass <= 1.5\ngini = 0.5\nsamples = 2\nvalue = [1, 1]"] ;
25 -> 26 ;
27 [label="gini = 0.0\nsamples = 1\nvalue = [1, 0]"] ;
26 -> 27 ;
28 [label="gini = 0.0\nsamples = 1\nvalue = [0, 1]"] ;
26 -> 28 ;
29 [label="age <= 27.5\ngini = 0.073\nsamples = 132\nvalue = [5, 127]"] ;
25 -> 29 ;
30 [label="age <= 24.5\ngini = 0.165\nsamples = 44\nvalue = [4, 40]"] ;
29 -> 30 ;
31 [label="gini = 0.054\nsamples = 36\nvalue = [1, 35]"] ;
30 -> 31 ;
32 [label="gini = 0.469\nsamples = 8\nvalue = [3, 5]"] ;
30 -> 32 ;
33 [label="age <= 56.5\ngini = 0.022\nsamples = 88\nvalue = [1, 87]"] ;
29 -> 33 ;
34 [label="gini = 0.0\nsamples = 82\nvalue = [0, 82]"] ;
33 -> 34 ;
35 [label="gini = 0.278\nsamples = 6\nvalue = [1, 5]"] ;
33 -> 35 ;
36 [label="age <= 38.5\ngini = 0.499\nsamples = 116\nvalue = [55, 61]"] ;
24 -> 36 ;
37 [label="age <= 1.5\ngini = 0.492\nsamples = 108\nvalue = [47, 61]"] ;
36 -> 37 ;
38 [label="gini = 0.0\nsamples = 4\nvalue = [0, 4]"] ;
37 -> 38 ;
39 [label="age <= 32.5\ngini = 0.495\nsamples = 104\nvalue = [47, 57]"] ;
37 -> 39 ;
40 [label="gini = 0.498\nsamples = 100\nvalue = [47, 53]"] ;
39 -> 40 ;
41 [label="gini = 0.0\nsamples = 4\nvalue = [0, 4]"] ;
39 -> 41 ;
42 [label="gini = 0.0\nsamples = 8\nvalue = [8, 0]"] ;
36 -> 42 ;
}

这个结构不能看清结构,所以可以在一个网站上显示:

4.2 网站显示结构

http://webgraphviz.com/

将dot文件内容复制到该网站当中显示:

5、决策树总结

  • 优点:
    简单的理解和解释,树木可视化。
  • 缺点:
    决策树学习者可以创建不能很好地推广数据的过于复杂的树,容易发生过拟合。
  • 改进:
    减枝cart算法
    随机森林(集成学习的一种)

注:企业重要决策,由于决策树很好的分析能力,在决策过程应用较多, 可以选择特征

机器学习算法——决策树算法详细介绍,并使用sklearn实现案例预测,可视化决策树相关推荐

  1. 机器学习算法——线性回归的详细介绍 及 利用sklearn包实现线性回归模型

    目录 1.线性回归简介 1.1 线性回归应用场景 1.2 什么是线性回归 1.2.1 定义与公式 1.2.2 线性回归的特征与目标的关系分析 2.线性回归api初步使用 2.1 线性回归API 2.2 ...

  2. 决策树算法和CART决策树算法详细介绍及其原理详解

    相关文章 K近邻算法和KD树详细介绍及其原理详解 朴素贝叶斯算法和拉普拉斯平滑详细介绍及其原理详解 决策树算法和CART决策树算法详细介绍及其原理详解 线性回归算法和逻辑斯谛回归算法详细介绍及其原理详 ...

  3. 机器学习算法原理:详细介绍各种机器学习算法的原理、优缺点和适用场景

    目录 引言 二.线性回归 三.逻辑回归 四.支持向量机 五.决策树 六.随机森林 七.K-均值聚类 八.主成分分析(PCA) 九.K近邻算法 十.朴素贝叶斯分类器 十一.神经网络 十二.AdaBoos ...

  4. 机器学习算法:决策树算法

    1.基本定义   决策树(Decision Tree)是一种基本的分类和回归算法.该算法模型呈树形结构,主要由结点和有向边组成.结点又分为两种类型:内部结点和叶子结点.内部结点表示在一个属性或特征上的 ...

  5. Python机器学习--算法--决策树算法

    决策树算法 决策树算法类型: 决策树是一系列算法,而不是一个算法. 决策树包含了 ID3分类算法,C4.5分类算法,Cart分类树算法,Cart回归树算法. 决策树既可以做分类算法,也可以做回归算法. ...

  6. 机器学习:决策树算法(ID3算法)的理解与实现

    机器学习:决策树算法(ID3算法)的理解与实现 文章目录 机器学习:决策树算法(ID3算法)的理解与实现 1.对决策树算法的理解 1.概述 2.算法难点 选择最优划分属性 1.信息熵 2.信息增益 2 ...

  7. python决策树实例_Python机器学习之决策树算法实例详解

    本文实例讲述了Python机器学习之决策树算法.分享给大家供大家参考,具体如下: 决策树学习是应用最广泛的归纳推理算法之一,是一种逼近离散值目标函数的方法,在这种方法中学习到的函数被表示为一棵决策树. ...

  8. 垃圾收集概述和垃圾收集算法(超详细介绍)

    文章目录 垃圾收集概述和垃圾收集算法(超详细介绍) 为什么我们还要去了解垃圾收集和内存分配 哪些内存需要回收 不需要回收的 需要回收的 方法区的回收 回收废弃常量 回收"不再被使用的类&qu ...

  9. ML之分类预测:基于sklearn库的七八种机器学习算法利用糖尿病(diabetes)数据集(8→1)实现二分类预测

    ML之分类预测:基于sklearn库的七八种机器学习算法利用糖尿病(diabetes)数据集(8→1)实现二分类预测 目录 输出结果 数据集展示 输出结果 1.k-NN 2.LoR 4.DT 5.RF ...

最新文章

  1. window for jdk install
  2. python 进阶:修饰器的介绍
  3. 找新房子需要考虑的因素
  4. ubuntu系统安装socket服务器,Ubuntu上进行socket编程,并且实现通信功能
  5. C# GDI+ 文字操作
  6. [洛谷P2073] 送花
  7. PyTorch-Transformers:一款可处理最先进NLP的惊人模型库
  8. for循环 与 while循环
  9. 童鞋们,颜色采色器,实用工具
  10. 计算机文档我的文档丢失,我的文档不见了怎么办?我的文档图标不见了找回方案...
  11. iOS第三方开源库的吐槽和备忘 - 王培
  12. 拼多多商家如何运营好店铺?商家如何一键打单?
  13. 用计算机可以定位到手机吗,如何使用计算机定位手机的位置?
  14. MP3音频编解码芯片 VS1053B-L
  15. 【转】源nat和目的nat的区别
  16. java银行新核心业务有哪些_银行核心业务系统性能测试
  17. php项目宝塔搭建实战前后端Niushop开源商城系统
  18. r语言kmeans聚类(真实案例完整流程)
  19. 基于cocos2d-x简易泡泡龙游戏二
  20. 深圳大数据学习:Scala系列之文件以及正则表达式

热门文章

  1. 点集凸包算法python实现(二)
  2. JDK1.8中英文官方文档
  3. h5加java棋牌_Html5斗地主棋牌架设Canvas实现斗地主游戏代码解析
  4. java graphics2d 画圆_java Graphics2D 画图
  5. java绘图- 绘图用法(基于Graphics2D)
  6. 操作系统的功能和意义
  7. 阿里云的ACA认证到底是个啥?有用吗?
  8. MBA回乡卖家电,谁给了他年销600万的底气?
  9. 信签纸有虚线怎么写_写观后感的信签纸格式
  10. 如何策划设计作品 设计的本质 所谓设计