• 【机器学习】决策树原理及scikit-learn使用



    决策树详解

    决策树(Decision Tree)是一种非参数的有监督学习方法,它能够从一系列有特征和标签的数据中总结出决策规则,并用树状图的结构来呈现这些规则,以解决分类和回归问题。

    决策树由结点(node)和有向边(directed edge)组成。结点有两种:内部结点(internal node)和叶子结点(leaf node),其中内部结点表示一个特征或者属性,叶子结点表示一个类。

    下图为是否为哺乳动物的二分类决策树:体温和胎生为特征,哺乳动物和非哺乳动物为类别。【可以看成if-else的结够,根据给定的数据,一路走下去,最后得出结论】

    在这里插入图片描述原则上讲,任意一个数据集上的所有特征都可以被拿来分枝,特征上的任意节点又可以自由组合,所以一个数据集上可以发展出非常非常多棵决策树,其数量可达指数级。在这些树中,总有那么一棵树比其他的树分类效力都好,那样的树叫做”全局最优树“。

    暴力搜索方法肯定行不通,时间复杂度太高。
    机器学习研究者们开发了一些有效的算法,能够在合理的时间内构造出具有一定准确率的次最优决策树。这些算法基本都执行”贪心策略“,即通过局部的最优来达到我们相信是最接近全局最优的结果。

    在学习算法之前,先了解几个概念:

    1. 信息熵:熵度量了事物的不确定性,越不确定的事物,它的熵就越大。
      随机变量X的熵的表达式如下:
      在这里插入图片描述

    其中n代表X的n种不同的离散取值。而 p x i p_{x_i} pxi代表了X取值为i的概率,log为以2或者e为底的对数。

    1. 条件熵 (Conditional Entropy) H ( X ∣ Y ) H(X∣Y) H(XY) 表示在已知随机变量Y的条件下随机变量X的不确定性。

    H ( X ∣ Y ) = ∑ j = 1 n p ( y i ) H ( X ∣ y i ) H(X|Y)=\sum_{j=1}^np(y_i)H(X|y_i) H(XY)=j=1np(yi)H(Xyi)

    1. 信息增益:特征A对训练数据集D的信息增益 I ( D , A ) I(D,A) I(D,A),定义为集合D的经验熵 H ( D ) H(D) H(D)与给定特征A在给定条件下D的经验条件熵 H ( D ∣ A ) H(D|A) H(DA)之差,即:

    I ( D , A ) = H ( D ) − H ( D ∣ A ) I(D,A)=H(D)-H(D|A) I(D,A)=H(D)H(DA)

    H ( D ∣ A ) H(D|A) H(DA)表示在特征A在给定条件下对数据集D进行分类的不确定性,二者之差即为信息增益,表示特征A使得数据集D的分类不确定性减少的程度。
    显然,对于信息增益大的特征具有更强的分类能力。

    1. 具体计算示例:

    在这里插入图片描述
    构建最优树的算法:

    ID3 算法

    ID3算法的核心就是在决策树各个节点上应用信息增益准则选择特征,递归地构建决策树。

    具体过程:-

    1. 输入:训练数据集D、特征集A、,阈值ϵ
    2. 输出: 决策树T

    判断T是否需要选择特征生成决策树:

    • 若D中所有实例属于同一类,则T为单结点树,记录实例类别 C k C_k Ck,以此作为该结点的类标记,并返回T;
    • 若D中所有实例无任何特征(A=空集),则T为单结点树,记录D中实例个数最多类别 C k C_k Ck,以此作为该结点的类标记,并返回T ;

    否则,计算A中各特征的信息增益,并选择信息增益最大的特征 A g A_g Ag;

    • A g A_g Ag的信息增益小于ϵ,则T为单结点树,记录D中实例个数最多类别 C k C_k Ck ,以此作为该结点的类标记,并返回T ;
    • 否则,按照 A g A_g Ag的每个可能值 a i a_i ai,将D分为若干非空子集 D i D_i Di,将 D i D_i Di中实例个数最多的类别作为标记,构建子结点,以结点和其子节点构成T,并返回T ;

    i i i个子结点,以 D i D_i Di为训练集, A − A g A-A_g AAg为特征集合,递归地调用以上步骤,得到子树 T i T_i Ti并返回。

    C4.5算法

    D3算法有四个主要的不足,一是不能处理连续特征,第二个就是用信息增益作为标准容易偏向于取值较多的特征,最后两个是缺失值处理的问和过拟合问题。C4.5算法中改进了上述4个问题。

    ==信息增益作为标准容易偏向于取值较多的特征的问题。==我们引入一个信息增益比的变量 I R ( X , Y ) I_R(X,Y) IR(X,Y),它是信息增益和特征熵的比值。表达式如下:

    I R ( D , A ) = I ( A , D ) H A ( D ) I_R(D,A)=\frac{I(A,D)}{H_A(D)} IR(D,A)=HA(D)I(A,D)

    其中D为样本特征输出的集合,A为样本特征,对于特征熵 H A ( D H_A(D HA(D), 表达式如下:

    H A ( D ) = − ∑ i = 1 n ∣ D i ∣ ∣ D ∣ l o g 2 ∣ D i ∣ ∣ D ∣ H_A(D)=-\sum_{i=1}^n\frac{|D_i|}{|D|}log_2\frac{|D_i|}{|D|} HA(D)=i=1nDDilog2DDi
    其中n为特征A的类别数, D i D_i Di为特征A的第i个取值对应的样本个数。 ∣ D ∣ |D| D为样本个数。
    特征数越多的特征对应的特征熵越大,它作为分母,可以校正信息增益容易偏向于取值较多的特征的问题。

    算法流程同ID3,将信息增益换成信息增益比即可。

    CART 算法

    1. ID3算法中我们使用了信息增益来选择特征,信息增益大的优先选择。
    2. C4.5算法中,采用了信息增益比来选择特征,以减少信息增益容易选择特征值多的特征的问题。
    3. CART分类树算法使用基尼系数来代替信息增益比,基尼系数代表了模型的不纯度,基尼系数越小,则不纯度越低,特征越好。【简化了运算,没有那么多对数运算】

    具体的,在分类问题中,假设有K个类别,第k个类别的概率为 p k p_k pk, 则基尼系数的表达式为:
    G i n i ( p ) = ∑ k = 1 K p k ( 1 − p k ) = 1 − ∑ k = 1 K p k 2 Gini(p)=\sum_{k=1}^Kp_k(1-p_k)=1-\sum_{k=1}^Kp^2_k Gini(p)=k=1Kpk(1pk)=1k=1Kpk2
    对于个给定的样本D,假设有K个类别, 第k个类别的数量为 C k C_k Ck,则样本D的基尼系数表达式为:
    G i n i ( D ) = 1 − ∑ k = 1 K ∣ C k ∣ ∣ D ∣ Gini(D)=1-\sum_{k=1}^K\frac{|C_k|}{|D|} Gini(D)=1k=1KDCk

    二分类:
    G i n i ( D ) = 2 p ( 1 − p ) Gini(D)=2p(1-p) Gini(D)=2p(1p)

    对于样本D,如果根据特征A的某个值a,把D分成D1和D2两部分,则在特征A的条件下,D的基尼系数表达式为:

    G i n i ( D , A ) = ∣ D 1 ∣ ∣ D ∣ G i n i ( D 1 ) + ∣ D 2 ∣ ∣ D ∣ G i n i ( D 2 ) Gini(D,A)=\frac{|D_1|}{|D|}Gini(D_1)+\frac{|D_2|}{|D|}Gini(D_2) Gini(D,A)=DD1Gini(D1)+DD2Gini(D2)

    CART既可以用于分类又可以用来回归,其中对回归树用平方误差最小化准则,对分类树用基尼指数最小化原则,进行特征选择,生成二叉树。下表列出了三种不同决策树算法的差别:
    在这里插入图片描述CART生成分类树,是用基尼指数选择最优特征后,生成的二叉树:

    在这里插入图片描述具体流程:

    1. 输入:训练数据集D、特征集A、阈值
    2. 输出:CART决策树T

    从根节点出发,进行操作,构建二叉树;

    结点处的训练数据集为D,计算现有特征对该数据集的基尼指数,并选择最优特征。

    • 在特征 A g A_g Ag下,对其可能取的每个值 a g a_g ag,根据样本点对 A g = a g A_g= a_g Ag=ag的测试为“是”或“否”,将D分割成 D 1 D_1 D1 D 2 D_2 D2两部分,计算 A g = a g A_g= a_g Ag=ag时的基尼指数。
    • 选择基尼指数最小的那个值作为该特征下的最优切分点。
    • 计算每个特征下的最优切分点,并比较在最优切分下的每个特征的基尼指数,选择基尼指数最小的那个特征,即最优特征。

    根据最优特征与最优切分点,从现结点生成两个子结点,将训练数据集依特征分配到两个子结点中去。

    分别对两个子结点递归地调用上述步骤,直至满足停止条件(比如结点中的样本个数小于预定阈值,或样本集的基尼指数小于预定阈值(样本基本属于同一类),或者没有更多特征),即生成CART决策树。

    示例:
    在这里插入图片描述在这里插入图片描述在这里插入图片描述基尼指数最小的是青年和老年,都可以作为最优划分点,总的年龄这个特征的基尼指数是:0.44,划分点为青年/老年。

    在这里插入图片描述有工作这个特征的基尼指数是0.32,只有两个划分,不需要区分划分点了。

    在这里插入图片描述有自己房子这个特征的基尼指数:0.27

    在这里插入图片描述信贷情况的基尼指数是0.32,以一般为划分点

    基尼指数从小到大排序:有自己房子0.27,有工作0.32,信贷情况0.32(一般),年龄0.44(青年/老年),越小的越好分类:

    所以以有自己房子开始进行分类:
    在这里插入图片描述在没有房子的条件下计算其他特征的基尼指数:
    在这里插入图片描述很明显,有工作这个特征它的基尼指数肯定是等于0的,其他基尼指数都大于0,所以选择有工作进行划分:

    在这里插入图片描述

    CART生成回归树:
    在这里插入图片描述在这里插入图片描述

    CART决策树剪枝:

    在这里插入图片描述阿拉法到


    scikit-learn使用

    sklearn.tree介绍
    tree.DecisionTreeClassifier分类树
    tree.DecisionTreeRegressor回归树
    tree.export_graphviz将生成的决策树导出为DOT格式,画图专用
    tree.ExtraTreeClassifier高随机版本的分类树
    tree.ExtraTreeRegressor高随机版本的回归树

    分类树

    分类树核心代码:

    from sklearn import tree #导入需要的模块
    clf = tree.DecisionTreeClassifier()     #实例化
    clf = clf.fit(X_train,y_train) #用训练集数据训练模型
    result = clf.score(X_test,y_test) #导入测试集,从接口中调用需要的信息
    
    • 1
    • 2
    • 3
    • 4

    DecisionTreeClassifier参数详解:
    在这里插入图片描述简单示例:

    导入的库

    from sklearn import tree  # 决策树
    from sklearn.datasets import load_wine # 酒的数据集
    from sklearn.model_selection import train_test_split  # 把数据集分成训练集和测试集
    
    • 1
    • 2
    • 3

    打印看看白酒数据集:
    在这里插入图片描述在这里插入图片描述可以通过pandas把数据集拼接一起:
    在这里插入图片描述把数据集分成训练集和测试集,参数test_size表示测试集的比例

    Xtrain, Xtest, Ytrain, Ytest = train_test_split(wine.data, wine.target, test_size=0.3)
    
    • 1

    在这里插入图片描述
    定义模型,开始训练,测试:

    clf = tree.DecisionTreeClassifier(criterion='entropy')
    clf = clf.fit(Xtrain, Ytrain)
    score = clf.score(Xtest, Ytest) # 返回预测的准确度accuracy
    score
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述
    看一看模型给我们定义的决策树:

    feature_name = ['酒精','苹果酸','灰','灰的碱性','镁','总酚','类黄酮','非黄烷类酚类','花青素','颜色强度','色调','od280/od315稀释葡萄酒','脯氨酸']
    
    import graphviz
    dot_data = tree.export_graphviz(clf, feature_names=feature_name, class_names=['清酒', '雪梨', '贝尔摩德'], filled=True, rounded=True)
    graph = graphviz.Source(dot_data)
    graph  # graph.view()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    注意如果pycharm能显示图片,而jupyter不能,报错找不到graphviz模块,可以试一下下面操作:在jupyter中执行conda install python-graphviz
    在这里插入图片描述

    在这里插入图片描述

    • 第一行:划分条件,脯氨酸是否小于760,是往左边走,不是往右边走。
    • 第二行:entropy,信息增益比,越大分类越好,越靠前。当entropy=0时,可以选出类别了。
    • sample: 样本数量。
    • value:每个类别的数量。
    • class:类别

    决策树并不会选择全部特征可以通过feature_importances_来查看:
    在这里插入图片描述在这里插入图片描述

    scikit-learn在每次分枝时,不从使用全部特征,而是随机选取一部分特征,从中选取不纯度相关指标最优的作为分枝用的节点。这样,每次生成的树也就不同了,准确率也不一样,不希望每次结果不一样,需要添加参数random_state来控制随机性:

    random_state用来设置分枝中的随机模式的参数,默认None,在高维度时随机性会表现更明显,低维度的数据(比如鸢尾花数据集),随机性几乎不会显现。输入任意整数,会一直长出同一棵树,让模型稳定下来。

    clf = tree.DecisionTreeClassifier(criterion="entropy",random_state=30)
    clf = clf.fit(Xtrain, Ytrain)
    score = clf.score(Xtest, Ytest) #返回预测的准确度
    score
    
    • 1
    • 2
    • 3
    • 4

    splitter也是用来控制决策树中的随机选项的,有两种输入值,输入”best",决策树在分枝时虽然随机,但是还是会优先选择更重要的特征进行分枝(重要性可以通过属性feature_importances_查看),输入“random",决策树在分枝时会更加随机,树会因为含有更多的不必要信息而更深更大,并因这些不必要信息而降低对训练集的拟合。

    clf = tree.DecisionTreeClassifier(criterion="entropy"
                                      ,random_state=30
                                      ,splitter="random"
                                      )
    clf = clf.fit(Xtrain, Ytrain)
    score = clf.score(Xtest, Ytest)
    score
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    dot_data = tree.export_graphviz(clf
                                    ,feature_names= feature_name
                                    ,class_names=["琴酒","雪莉","贝尔摩德"]
                                    ,filled=True
                                    ,rounded=True
                                    )
    graph = graphviz.Source(dot_data)
    graph
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述随机性增加了,树变深了。

    剪枝参数

    在不加限制的情况下,一棵决策树会生长到衡量entropy的指标最优,或者没有更多的特征可用为止。这样的决策树往往会过拟合,这就是说,它会在训练集上表现很好,在测试集上却表现糟糕,我们需要使用剪枝参数来防止过拟合。

    剪枝策略对决策树的影响巨大,正确的剪枝策略是优化
    决策树算法的核心。sklearn为我们提供了不同的剪枝策略:

    • max_depth
      限制树的最大深度,超过设定深度的树枝全部剪掉
      这是用得最广泛的剪枝参数,在高维度低样本量时非常有效。决策树多生长一层,对样本量的需求会增加一倍,所以限制树深度能够有效地限制过拟合。在集成算法中也非常实用。实际使用时,建议从=3开始尝试,看看拟合的效果再决定是否增加设定深度。

    • min_samples_leaf & min_samples_split 限定叶子节点
      min_samples_leaf限定,一个节点在分枝后的每个子节点都必须包含至少min_samples_leaf个训练样本【对应图中simple值】,否则分枝就不会发生,或者,分枝会朝着满足每个子节点都包含min_samples_leaf个样本的方向去发生
      一般搭配max_depth使用,在回归树中有神奇的效果,可以让模型变得更加平滑。
      这个参数的数量设置得太小会引起过拟合,设置得太大就会阻止模型学习数据。
      一般来说,建议从=5开始使用。如果叶节点中含有的样本量变化很大,建议输入浮点数作为样本量的百分比来使用。同时,这个参数可以保证每个叶子的最小尺寸,可以在回归问题中避免低方差,过拟合的叶子节点出现。对于类别不多的分类问题,=1通常就是最佳选择
      min_samples_split限定,一个节点必须要包含至少min_samples_split个训练样本,这个节点才允许被分枝,否则分枝就不会发生。

    clf = tree.DecisionTreeClassifier(criterion='entropy'
                                      ,random_state=30
                                      # ,splitter='random'
                                      ,max_depth=3
                                      ,min_samples_leaf=10
                                      ,min_samples_split=10
                                      )
    clf = clf.fit(Xtrain, Ytrain)
    
    dot_data = tree.export_graphviz(clf
                                    ,feature_names= feature_name
                                    ,class_names=["琴酒","雪莉","贝尔摩德"]
                                    ,filled=True
                                    ,rounded=True
                                    )
    graph = graphviz.Source(dot_data)
    graph
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述我们对上面的几个参数进行调参,找准确率最高的:
    在这里插入图片描述在这里插入图片描述

    • max_features & min_impurity_decrease
      一般max_depth使用,用作树的”精修“
      max_features限制分枝时考虑的特征个数,超过限制个数的特征都会被舍弃。和max_depth异曲同工,max_features是用来限制高维度数据的过拟合的剪枝参数,但其方法比较暴力,是直接限制可以使用的特征数量而强行使决策树停下的参数,在不知道决策树中的各个特征的重要性的情况下,强行设定这个参数可能会导致模型学习不足。如果希望通过降维的方式防止过拟合,建议使用PCA,ICA或者特征选择模块中的降维算法。
      min_impurity_decrease限制信息增益的大小,信息增益小于设定数值的分枝不会发生。

    确认最优的剪枝参数
    使用确定超参数的曲线来进行判断了,继续使用我们已经训练好的决策树模型clf。超参数的学习曲线,是一条以超参数的取值为横坐标,模型的度量指标为纵坐标的曲线,它是用来衡量不同超参数取值下模型的表现的线。在我们建好的决策树里,我们的模型度量指标就是score。

    max_depth为例:

    import matplotlib.pyplot as plt
    test = []
    for i in range(10):
        clf = tree.DecisionTreeClassifier(max_depth=i+1
                                          ,criterion="entropy"
                                          ,random_state=30
                                          ,splitter="random"
                                          )
        clf = clf.fit(Xtrain, Ytrain)
        score = clf.score(Xtest, Ytest)
        test.append(score)
    plt.plot(range(1,11),test,color="red",label="max_depth")
    plt.legend()
    plt.show()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述可以看到max_depth=4时,准确度最高。

    • class_weight & min_weight_fraction_leaf 【控制目标权重】
      完成样本标签平衡的参数。样本不平衡是指在一组数据集中,标签的一类天生占有很大的比例。比如说,在银行要判断“一个办了信用卡的人是否会违约”,就是是vs否(1%:99%)的比例。这种分类状况下,即便模型什么也不做,全把结果预测成“否”,正确率也能有99%。因此我们要使用class_weight参数对样本标签进行一定的均衡,给少量的标签更多的权重,让模型更偏向少数类,向捕获少数类的方向建模。该参数默认None,此模式表示自动给与数据集中的所有标签相同的权重。
      有了权重之后,样本量就不再是单纯地记录数目,而是受输入的权重影响了,因此这时候剪枝,就需要搭配min_weight_fraction_leaf这个基于权重的剪枝参数来使用。另请注意,基于权重的剪枝参数(例如min_weight_fraction_leaf)将比不知道样本权重的标准(比如min_samples_leaf)更少偏向主导类。如果样本是加权的,则使用基于权重的预修剪标准来更容易优化树结构,这确保叶节点至少包含样本权重的总和的一小部分。

    重要属性和接口

    属性是在模型训练之后,能够调用查看的模型的各种性质。对决策树来说,最重要的是feature_importances_,能够查看各个特征对模型的重要性。

    决策树最常用的接口:

    • fit
    • score
    • apply
    • predict
    #apply返回每个测试样本所在的叶子节点的索引
    clf.apply(Xtest)
    #predict返回每个测试样本的分类/回归结果
    clf.predict(Xtest)
    
    • 1
    • 2
    • 3
    • 4

    回归树

    DecisionTreeRegressor几乎所有参数,属性及接口都和分类树一模一样。需要注意的是,在回归树种,没有标签分布是否均衡的问题,因此没有class_weight这样的参数。

    重要参数,属性及接口

    criterion
    回归树衡量分枝质量的指标,支持的标准有三种:
    1)输入"mse"使用均方误差mean squared error(MSE),父节点和叶子节点之间的均方误差的差额将被用来作为特征选择的标准,这种方法通过使用叶子节点的均值来最小化L2损失
    2)输入“friedman_mse”使用费尔德曼均方误差,这种指标使用弗里德曼针对潜在分枝中的问题改进后的均方误差
    3)输入"mae"使用绝对平均误差MAE(mean absolute error)
    这种指标使用叶节点的中值来最小化L1损失属性中最重要的依然是feature_importances_,
    接口依然是apply, fit, predict, score最核心。

    交叉验证

    交叉验证是用来观察模型的稳定性的一种方法,我们将数据划分为n份,依次使用其中一份作为测试集,其他n-1份作为训练集,多次计算模型的精确性来评估模型的平均准确程度。训练集和测试集的划分会干扰模型的结果,因此用交叉验证n次的结果求出的平均值,是对模型效果的一个更好的度量。在这里插入图片描述

    代码示例

    from sklearn.datasets import load_boston  # 波士顿房价数据集
    from sklearn.model_selection import cross_val_score # 交叉验证
    from sklearn.tree import DecisionTreeRegressor # 回归树
    boston = load_boston()
    regressor = DecisionTreeRegressor(random_state=0)
    cross_val_score(regressor, boston.data, boston.target, cv=10, 
                    scoring = "neg_mean_squared_error") # 均方误差
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    一维回归的图像绘制

    import numpy as np
    from sklearn.tree import DecisionTreeRegressor
    import matplotlib.pyplot as plt
    
    rng = np.random.RandomState(1)
    X = np.sort(5 * rng.rand(80,1), axis=0) # 创建80个(0,1)的随机数,扩大五倍,排序
    y = np.sin(X).ravel()  # 降维ravel
    y[::5] += 3 * (0.5 - rng.rand(16)) # 每个5个数,制造一个噪声
    
    regr_1 = DecisionTreeRegressor(max_depth=2) # 模型1
    regr_2 = DecisionTreeRegressor(max_depth=5) # 模型2
    regr_1.fit(X, y) # 模型1训练
    regr_2.fit(X, y) # 模型2训练
    
    X_test = np.arange(0.0, 5.0, 0.01)[:, np.newaxis] # 创建一组测试集 np.newaxis 增维
    y_1 = regr_1.predict(X_test) # 预测 模型1
    y_2 = regr_2.predict(X_test) # 预测 模型2
    
    plt.figure() # 画布
    plt.scatter(X, y, s=20, edgecolor="black",c="darkorange", label="data") # 散点图
    plt.plot(X_test, y_1, color="cornflowerblue",label="max_depth=2", linewidth=2) # 回归预测图1
    plt.plot(X_test, y_2, color="yellowgreen", label="max_depth=5", linewidth=2) # 回归预测图2
    plt.xlabel("data")
    plt.ylabel("target")
    plt.title("Decision Tree Regression")
    plt.legend()
    plt.show()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在这里插入图片描述如果树的最大深度(由max_depth参数控制)设置得太高,则决策树学习得太精细,它从训练数据中学了很多细节,包括噪声得呈现,从而使模型偏离真实的正弦曲线,形成过拟合。


  • 相关阅读:
    ddtrace 系列篇之 dd-trace-java 项目编译
    用npm 用jest测试
    Linux内存管理和源码分析——概述
    怕客户跑单?这套上市制造企业都在使的订单管理方案你一定要看看
    前端工程化
    Mac系统PR2022安装BeatEdit插件遇到各种问题解决
    mac git ssh
    阿里云服务器40G ESSD Entry系统盘99元e实例、3M带宽和性能测评
    Datax从mysql同步数据到mysql
    通过劫持线程arena实现任意地址分配 n1ctf2018_null
  • 原文地址:https://blog.csdn.net/qq_43466788/article/details/133623276