Day_61-62 决策树

news2024/11/24 8:56:47

目录

Day_61-62决策树(准备工作)

一. 算法的基本概念

        1. 决策树的定义

        2. 如何构建决策树?

        2.1 熵

        2.2 信息增益原则

        2.3 计算步骤

 二. 示例演示

        1. 第一次节点决策分类:

        2. 后续节点的决策分类

        3. 决策分类的结束条件

 三. 代码实现

        1. 主函数

        2. 两个构造函数

        3. 打标签函数getMajorityClass和判定纯度函数pureJudge

        4. 核心代码建立决策树

        4.1 判定是否结束子树构造

        4.2 根据信息增益原则寻找最优属性

        4.3 根据最优属性进行分类

        4.4 构造孩子节点和更新节点信息

        5. 输出函数

        6. 准确性检验

四. 运行结果


Day_61-62决策树(准备工作)

一. 算法的基本概念

        1. 决策树的定义

        决策树是一种机器学习的方法(参考这篇文章),决策树的生成算法有ID3, C4.5和C5.0等(这篇文章只讨论ID3)。决策树是一种树形结构,其中每个内部节点表示一个属性上的判断,每个分支代表一个判断结果的输出,最后每个叶节点代表一种分类结果。

        举一个简单的例子:

        假设我要买一部手机,只考虑手机的两个方面:颜色和价格。我的心路历程是这样的:首先要看颜色,不是白色的我不喜欢,不买;然后看价格,本人价格敏感,太贵的不买。这个解决方案可以用一个流程图来描述,如图1所示。具体来说,这是一个树。方形就是我要判断的一个指标;有向边就是一个指标的取值;沿着有向边走到树的末端,就到了叶子节点——叶子节点就是我最终的决定。来一个手机,我按这个树描述的规则,进行判断,就可以知道我能不能买。

图1

        2. 如何构建决策树?

        上面我们知道了上面是决策树,回顾一下目标,我们的目标是根据数据输出它的标签对不对?所以这里问题的关键是我们如何构建一个决策树。这里我们就开始介绍算法的基本概念

        2.1 熵

        学过信息论的读者应该都知道熵的概念(熵在其他的领域计算公式略有差异,这里以信息领域为准),1948年,香农将统计物理中熵的概念,引申到信道通信的过程中,香农定义的“熵”又被称为“香农熵”或“信息熵”。对于属性P_{1}的熵即:

         其中i标记概率空间中所有可能的样本,表示p_{i}该样本的出现几率,K是和单位选取相关的任意常数(这里我取K为1),S表示这个属性的最终熵值。 这个概念是用于衡量信息的混乱程度的量,熵的值越高,表示数据集的混乱程度越高(纯度越低);熵的值越低,表示数据集的混轮程度越高(纯度越低)。

        条件熵表示在属性P_{i}条件下判定结果的熵值,对于属性P_{1}条件下判定结果的熵值

        2.2 信息增益原则

        对于某一个数据集,它可能有诸多属性,对于每一个属性,是否以它分类呢?这里引入信息增益的概念。对于某一个数据集D,它的某一个属性为P_{i},那么在P_{i}条件下的信息增益为g(D,P_{i}),定义g(D,P_{i})的计算公式如下所示,

        除此之外,对于每一个属性由于S(D)都相等,之前是计算max\ g(D,P_{i}),现在

        max(g(D,P_{i}))=max(S(D)-S(D|P_{i})) =max(-S(D|P_{i}))=min(S(D|P_{i}))

        2.3 计算步骤

        ①从根节点开始,计算所有可能的特征的信息增益,选择信息增益最大的特征作为节点的划分特征;

        ②由该特征的不同取值建立子节点;

        ③再对子节点递归1-2步,构建决策树;

        ④直到没有特征可以选择或类别完全相同为止,得到最终的决策树。

 二. 示例演示

        对于数据weather

@relation weather
@attribute Outlook {Sunny, Overcast, Rain}
@attribute Temperature {Hot, Mild, Cool}
@attribute Humidity {High, Normal, Low}
@attribute Windy {FALSE, TRUE}
@attribute Play {N, P}
@data
Sunny,Hot,High,FALSE,N
Sunny,Hot,High,TRUE,N
Overcast,Hot,High,FALSE,P
Rain,Mild,High,FALSE,P
Rain,Cool,Normal,FALSE,P
Rain,Cool,Normal,TRUE,N
Overcast,Cool,Normal,TRUE,P
Sunny,Mild,High,FALSE,N
Sunny,Cool,Normal,FALSE,P
Rain,Mild,Normal,FALSE,P
Sunny,Mild,Normal,TRUE,P
Overcast,Mild,High,TRUE,P
Overcast,Hot,Normal,FALSE,P
Rain,Mild,High,TRUE,N

        1. 第一次节点决策分类:

        计算属性Outlook下的信息增益:

        S(play|Sunny)=-3/5\cdot log(3/5)-2/5\cdot log(2/5)=0.673

        S(play|Overcast)=0-1\cdot log1=0

        S(play|Rain)=-2/5\cdot log(2/5)-3/5\cdot log(3/5)=0.673

        故最终的条件熵为:

        S(play|Outlok)=-5/14\cdot 0.673-0-5/14\cdot 0.673=0.4807

        同理计算另外三个属性的条件熵:

        S(play|Temperature)=0.6315

        S(play|Humidity)=0.6315

        S(play|Windy)=0.6183

        由上述的公式可知,根据最大化信息增益准则,用Outlook属性作为第一个节点的分类标准最为合适。

        2. 后续节点的决策分类

        同样的道理,上述过程完成了对于第一个节点的决策分类,对于第二个节点也需要进行上述的决策分类,需要注意的是,这里的条件熵已经改变了部分数据集,需要重新判定,例如对于sunny下述数据,hot有2个,mild有2个,cool有1个,现在的条件熵为

        S(play|Hot)=-1\cdot log(1)-0\cdot log(0)=0

        S(play|Mild)=-1/2\cdot log(1/2)-1/2\cdot log(1/2)=0.6931

        S(play|Cool)=-0\cdot log(0)-1\cdot log(1)=0

        对应的条件熵

        S(play|Temperature)=-2/5\cdot 0-2/5\cdot 0.6931-1/5\cdot 0=0.2722

        对应另外的属性同理,也就是说,没当经过一个节点的分类之后,所选取的空间发生改变,对应的概率和熵值也会发生改变。

        3. 决策分类的结束条件

        当什么时候决策分类结束呢?当这个节点的所有结果都是一致的时候,结束决策分类(结果作为叶子节点),对应上式的信息增益为1(因为S(D)=1,S(D|P)=0)结束分类。

        最终构造完成的决策树

 三. 代码实现

        1. 主函数

        这一段代码主要是看第二段,传入所有数据构成一个节点tempID3,设置临界阈值为3(表示当数据个数<3之后结束判定分类);根据这个节点tempID3建树;输出建树的结果;检验测试(由训练数据作为测试数据,考察准确度)。

        这里最重要的是理解理解建树的递归思想(我们是根据这个tempID3节点建树,若条件满足则继续向下建树,不满足则退出);其次检验函数也需要用到递归思想,为方便理解我待会会在后面叙述。

    /**
     *************************
     * Test this class.
     *
     * @param args
     *            Not used now.
     *************************
     */
    public static void main(String[] args) {

        id3Test();
    }// Of main
    /**
     *************************
     * Test this class.
     *
     * @param args
     *            Not used now.
     *************************
     */
    public static void id3Test() {
        ID3 tempID3 = new ID3("D:/data/weather.arff");
        // ID3 tempID3 = new ID3("D:/data/mushroom.arff");
        ID3.smallBlockThreshold = 3;
        tempID3.buildTree();

        System.out.println("The tree is: \r\n" + tempID3);

        double tempAccuracy = tempID3.selfTest();
        System.out.println("The accuracy is: " + tempAccuracy);
    }// Of id3Test

        2. 两个构造函数

        第一个构造函数主要是在程序开始的时候传入数据,

        ①输入路径,根据路径找到数据data,再根据data创建instance类的dataset对象,创建失败则抛出异常。

        ②setClassIndex函数设置标签对应的是哪一个属性(4),numClasses记录标签种类的个数(以天气数据为例,只有去玩和不去玩两种,故numClasses=2)

        ③availableInstances是这个节点的所有数据,可以理解为要分类的数据索引;availableAttributes是除去标签的属性数组(availableAttributes数组的每一个空记录是第几个属性(也表示这个节点还可能对哪几个属性判定分类))

        ④初始化孩子为null(因为还没有判断);getMajorityClass根据此时的数据情况得到这个节点的标签(这一点其实没什么用,除了在叶子节点对标签的判断外,在非叶子节点判断无用,不过因为是同一个对象,可能也就判断了);pureJudge函数判定是否数据“纯”,即这个节点的数据的标签是否都是一类。

    /**
     ********************
     * The constructor.
     *
     * @param paraFilename
     *            The given file.
     ********************
     */
    public ID3(String paraFilename) {
        dataset = null;
        try {
            FileReader fileReader = new FileReader(paraFilename);
            dataset = new Instances(fileReader);
            fileReader.close();
        } catch (Exception ee) {
            System.out.println("Cannot read the file: " + paraFilename + "\r\n" + ee);
            System.exit(0);
        } // Of try

        dataset.setClassIndex(dataset.numAttributes() - 1);
        numClasses = dataset.classAttribute().numValues();

        availableInstances = new int[dataset.numInstances()];
        for (int i = 0; i < availableInstances.length; i++) {
            availableInstances[i] = i;
        } // Of for i
        availableAttributes = new int[dataset.numAttributes() - 1];
        for (int i = 0; i < availableAttributes.length; i++) {
            availableAttributes[i] = i;
        } // Of for i

        // Initialize.
        children = null;
        // Determine the label by simple voting.
        label = getMajorityClass(availableInstances);
        // Determine whether or not it is pure.
        pure = pureJudge(availableInstances);
    }// Of the first constructor

        第二个构造函数待会运行到的时候再说明。

        3. 打标签函数getMajorityClass和判定纯度函数pureJudge

        首先是getMajorityClass函数,这个时候只有一个节点可能理解比较简单,但是对于后面将孩子分类之后打标签就可能理解不了。其实本质上都是一样的,现在的所有数据都集中在如下的节点。

         我们根据这个节点的所有标签个数(去玩还是不去玩),谁多就打上谁的标签(对于这个节点,不去玩的个数少于去玩的个数,所以标签是不去玩)

        这个函数的作用就是我们学习得到的判定结果,待会需要和原本已知的数据作比较得到准确度。

    /**
     **********************************
     * Compute the majority class of the given block for voting.
     *
     * @param paraBlock
     *            The block.
     * @return The majority class.
     **********************************
     */
    public int getMajorityClass(int[] paraBlock) {
        int[] tempClassCounts = new int[dataset.numClasses()];
        for (int i = 0; i < paraBlock.length; i++) {
            tempClassCounts[(int) dataset.instance(paraBlock[i]).classValue()]++;
        } // Of for i

        int resultMajorityClass = -1;
        int tempMaxCount = -1;
        for (int i = 0; i < tempClassCounts.length; i++) {
            if (tempMaxCount < tempClassCounts[i]) {
                resultMajorityClass = i;
                tempMaxCount = tempClassCounts[i];
            } // Of if
        } // Of for i

        return resultMajorityClass;
    }// Of getMajorityClass

        接着是“纯度函数”pureJudge,还是根据这个节点的所有数据判断纯度,若所有的标签都一致,则输出为true;否则则输出false。这个函数的主要作用是判断是否结束某一个子树向下的延伸,即我还需不需要再加孩子扩充决策树。

    /**
     **********************************
     * Is the given block pure?
     *
     * @param paraBlock
     *            The block.
     * @return True if pure.
     **********************************
     */
    public boolean pureJudge(int[] paraBlock) {
        pure = true;

        for (int i = 1; i < paraBlock.length; i++) {
            if (dataset.instance(paraBlock[i]).classValue() != dataset.instance(paraBlock[0])
                    .classValue()) {
                pure = false;
                break;
            } // Of if
        } // Of for i

        return pure;
    }// Of pureJudge

        4. 核心代码建立决策树

        这部分代码是核心,主要理解三个点:①怎么样用递归建立决策树②怎么样对数据进行划分③怎么样对已使用和未使用的属性进行判别。

    /**
     **********************************
     * Build the tree recursively.
     **********************************
     */
    public void buildTree() {
        if (pureJudge(availableInstances)) {
            return;
        } // Of if
        if (availableInstances.length <= smallBlockThreshold) {
            return;
        } // Of if

        selectBestAttribute();
        int[][] tempSubBlocks = splitData(splitAttribute);
        children = new ID3[tempSubBlocks.length];

        // Construct the remaining attribute set.
        int[] tempRemainingAttributes = new int[availableAttributes.length - 1];
        for (int i = 0; i < availableAttributes.length; i++) {
            if (availableAttributes[i] < splitAttribute) {
                tempRemainingAttributes[i] = availableAttributes[i];
            } else if (availableAttributes[i] > splitAttribute) {
                tempRemainingAttributes[i - 1] = availableAttributes[i];
            } // Of if
        } // Of for i

        // Construct children.
        for (int i = 0; i < children.length; i++) {
            if ((tempSubBlocks[i] == null) || (tempSubBlocks[i].length == 0)) {
                children[i] = null;
                continue;
            } else {
                // System.out.println("Building children #" + i + " with
                // instances " + Arrays.toString(tempSubBlocks[i]));
                children[i] = new ID3(dataset, tempSubBlocks[i], tempRemainingAttributes);

                // Important code: do this recursively
                children[i].buildTree();
            } // Of if
        } // Of for i
    }// Of buildTree

        4.1 判定是否结束子树构造

        回到我们之前的过程,我们现在已经建立了一个节点——原数据集,现在对这个节点进行判断,若它已经是“纯”数据了(标签一致)直接结束;若它的数据个数≤smallBlockThreshold直接结束。显然两个条件都没有满足。

        4.2 根据信息增益原则寻找最优属性

        现在我们就需要对这部分数据进行分类selectBestAttribute函数的作用是选出最佳的分类属性:根据现在的属性个数(0,1,2,3)做循环,计算每一个的条件熵(上面的公式推导过,因为S(D)都是一致的,所有我们只需要计算条件熵)。到条件熵函数conditionalEntropy:传入一个属性paraAttribute,构造数组用于记录paraAttribute属性下的每个具体属性的个数,构造tempCountMatrix用于记录每个具体属性下标签的个数。

        接着开始计算条件熵,第一重循环对某一个属性的具体属性个数循环i,第二重循环对标签的个数循环j,现在的概率pi=tempCountMatrix[i][j]/ tempValueCounts[i],对于某一个具体属性的条件熵tempEntropy-=p1*logp1-p2*logp2(这里2是标签的个数),最后计算某一个属性的条件熵resultEntropy=resultEntropy+tempValueCounts[i](某个具体属性的所有个数)/数据总数tempNumInstances*tempEntropy某个属性的条件熵。

        最后记录下最小的条件熵,输出它的类别,即完成了selectBestAttribute()的作用。

    /**
     **********************************
     * Select the best attribute.
     *
     * @return The best attribute index.
     **********************************
     */
    public int selectBestAttribute() {
        splitAttribute = -1;
        double tempMinimalEntropy = 10000;
        double tempEntropy;
        for (int i = 0; i < availableAttributes.length; i++) {
            tempEntropy = conditionalEntropy(availableAttributes[i]);
            if (tempMinimalEntropy > tempEntropy) {
                tempMinimalEntropy = tempEntropy;
                splitAttribute = availableAttributes[i];
            } // Of if
        } // Of for i
        return splitAttribute;
    }// Of selectBestAttribute

    /**
     **********************************
     * Compute the conditional entropy of an attribute.
     *
     * @param paraAttribute
     *            The given attribute.
     *
     * @return The entropy.
     **********************************
     */
    public double conditionalEntropy(int paraAttribute) {
        // Step 1. Statistics.
        int tempNumClasses = dataset.numClasses();
        int tempNumValues = dataset.attribute(paraAttribute).numValues();
        int tempNumInstances = availableInstances.length;
        double[] tempValueCounts = new double[tempNumValues];
        double[][] tempCountMatrix = new double[tempNumValues][tempNumClasses];

        int tempClass, tempValue;
        for (int i = 0; i < tempNumInstances; i++) {
            tempClass = (int) dataset.instance(availableInstances[i]).classValue();
            tempValue = (int) dataset.instance(availableInstances[i]).value(paraAttribute);
            tempValueCounts[tempValue]++;
            tempCountMatrix[tempValue][tempClass]++;
        } // Of for i

        // Step 2.
        double resultEntropy = 0;
        double tempEntropy, tempFraction;
        for (int i = 0; i < tempNumValues; i++) {
            if (tempValueCounts[i] == 0) {
                continue;
            } // Of if
            tempEntropy = 0;
            for (int j = 0; j < tempNumClasses; j++) {
                tempFraction = tempCountMatrix[i][j] / tempValueCounts[i];
                if (tempFraction == 0) {
                    continue;
                } // Of if
                tempEntropy += -tempFraction * Math.log(tempFraction);
            } // Of for j
            resultEntropy += tempValueCounts[i] / tempNumInstances * tempEntropy;
        } // Of for i

        return resultEntropy;
    }// Of conditionalEntropy

        4.3 根据最优属性进行分类

        上面我们已经找出了最优的属性splitAttribute,splitData是将现在的节点数据按最优属性划分出来,返回根据最优属性splitAttribute构建的二维数组,行表示每一个具体的属性,列表示对应的数据索引。

    /**
     **********************************
     * Split the data according to the given attribute.
     *
     * @return The blocks.
     **********************************
     */
    public int[][] splitData(int paraAttribute) {
        int tempNumValues = dataset.attribute(paraAttribute).numValues();
        // System.out.println("Dataset " + dataset + "\r\n");
        // System.out.println("Attribute " + paraAttribute + " has " +
        // tempNumValues + " values.\r\n");
        int[][] resultBlocks = new int[tempNumValues][];
        int[] tempSizes = new int[tempNumValues];

        // First scan to count the size of each block.
        int tempValue;
        for (int i = 0; i < availableInstances.length; i++) {
            tempValue = (int) dataset.instance(availableInstances[i]).value(paraAttribute);
            tempSizes[tempValue]++;
        } // Of for i

        // Allocate space.
        for (int i = 0; i < tempNumValues; i++) {
            resultBlocks[i] = new int[tempSizes[i]];
        } // Of for i

        // Second scan to fill.
        Arrays.fill(tempSizes, 0);
        for (int i = 0; i < availableInstances.length; i++) {
            tempValue = (int) dataset.instance(availableInstances[i]).value(paraAttribute);
            // Copy data.
            resultBlocks[tempValue][tempSizes[tempValue]] = availableInstances[i];
            tempSizes[tempValue]++;
        } // Of for i

        return resultBlocks;
    }// Of splitData

        4.4 构造孩子节点和更新节点信息

        首先更新其余属性的值,由于我们已经选出了最优属性,现在需要将这个属性从之前的属性组availableAttributes剥离开来。接着构造孩子节点:由于splitData函数返回的是分好类的二维数组,我们根据这个二维数组构建孩子节点,每个孩子节点的数据是数组tempSubBlocks的一行。接着递归操作,建树。

        5. 输出函数

    /**
     *******************
     * Overrides the method claimed in Object.
     *
     * @return The tree structure.
     *******************
     */
    public String toString() {
        String resultString = "";
        String tempAttributeName = dataset.attribute(splitAttribute).name();
        if (children == null) {
            resultString += "class = " + label;
        } else {
            for (int i = 0; i < children.length; i++) {
                if (children[i] == null) {
                    resultString += tempAttributeName + " = "
                            + dataset.attribute(splitAttribute).value(i) + ":" + "class = " + label
                            + "\r\n";
                } else {
                    resultString += tempAttributeName + " = "
                            + dataset.attribute(splitAttribute).value(i) + ":" + children[i]
                            + "\r\n";
                } // Of if
            } // Of for i
        } // Of if

        return resultString;
    }// Of toString

        6. 准确性检验

        主要是理解test函数和classify函数

        对于test函数,做leave-out-leave测试,若检测classify(paraDataset.instance(i))每一个实例值和原数据不相对应的话,correct自加1。

        对于classify函数:若此时孩子节点为null(表示该节点为叶子节点)输出标签;若不然tempChild指向决策树的子节点,若tempChild为null(表示没有以这个属性分类的节点)输出标签。最后递归paraInstance。

    /**
     **********************************
     * Classify an instance.
     *
     * @param paraInstance
     *            The given instance.
     * @return The prediction.
     **********************************
     */
    public int classify(Instance paraInstance) {
        if (children == null) {
            return label;
        } // Of if

        ID3 tempChild = children[(int) paraInstance.value(splitAttribute)];
        if (tempChild == null) {
            return label;
        } // Of if

        return tempChild.classify(paraInstance);
    }// Of classify

    /**
     **********************************
     * Test on a testing set.
     *
     * @param paraDataset
     *            The given testing data.
     * @return The accuracy.
     **********************************
     */
    public double test(Instances paraDataset) {
        double tempCorrect = 0;
        for (int i = 0; i < paraDataset.numInstances(); i++) {
            if (classify(paraDataset.instance(i)) == (int) paraDataset.instance(i).classValue()) {
                tempCorrect++;
            } // Of i
        } // Of for i

        return tempCorrect / paraDataset.numInstances();
    }// Of test

    /**
     **********************************
     * Test on the training set.
     *
     * @return The accuracy.
     **********************************
     */
    public double selfTest() {
        return test(dataset);
    }// Of selfTest

四. 运行结果

        weather数据的运行结果:

        mushroom数据的运行结果:

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/721592.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Matlab把两个不同的x轴和y轴画在同一个图里

我们知道画两个y轴可以用yyaxis. 那么画两个x轴呢? 这时候可以用神奇的tiledlayout. % 创建两组数据 x1 0:0.1:40; y1 4.*cos(x1)./(x12); x2 1:0.2:20; y2 x2.^2./x2.^3;t tiledlayout(1,1); % 创建一个tiledlayout % 第一个坐标系 ax1 axes(t); % 创建坐标系, 指定t为…

Go语言MinGW的安装

Go语言MinGW的安装 相比在 Linux 平台上安装 GCC 编译环境&#xff0c;在 Windows 平台上安装 MinGW 是比较简单的&#xff0c;只需经历以下几个过 程。 1、MINGW32位安装 1、打开 [MinGW 官网] https://osdn.net/projects/mingw/&#xff0c;下载 MinGW 安装包。 点击即可…

个人对于SAR的粗浅理解

个人对于SAR的粗浅理解 有同学问我是做成像的&#xff0c;让我解释一下SAR成像&#xff0c;我思索了一下&#xff0c;决定这样简单回答&#xff1a; 首先SAR的全称为Synthetic Aperture Radar&#xff0c;即合成孔径雷达&#xff0c;本质还是一种Radar 合成孔径&#xff0c;其…

CSS 制作动态蚂蚁线

效果&#xff1a; 代码&#xff1a; <html> <head> <meta http-equiv"Content-Type" content"text/html; charsetUTF-8"> <style type"text/css"> .line{position:relative;margin-bottom: 5px;width: 1200px;height: …

【电商API接口系列】关键词搜索商品列表,品牌监控场景

API接口允许不同应用程序之间共享数据&#xff0c;在系统之间传输、读取和更新数据。例如&#xff0c;一个电商网站可以通过API接口获取支付系统的支付状态。API接口允许开发人员使用他人开发的功能来扩展自己的应用程序。通过调用第三方API接口&#xff0c;开发人员无需重新实…

我的创作纪念日兼GPT模型简单介绍

目录 一、引言 二、收获与开端 2.1 问题&#xff1a;在创作的过程中都有哪些收获&#xff1f; 2.2 模型开端 三、日常与深入 3.1 问题&#xff1a;当前创作和你的学习是什么样的关系&#xff1f; 3.2 模型深入介绍 3.2.1 无监督预训练 3.2.2 有监督下游任务精调 四、…

自动驾驶产业链躁动,四维图新能否做好新时代“Tier 1”?

自动驾驶行业的“劲风”又来了。 6月21日&#xff0c;工信部副部长辛国斌在国务院政策例行吹风会上表示&#xff0c;将启动智能网联汽车准入和上路通行试点&#xff0c;他强调&#xff0c;“这里面讲的是L3级&#xff0c;及更高级别的自动驾驶功能商业化应用”。此前工信部曾透…

C#实现低耦合读卡

我们经常要给用户实现读卡查询。有很多种读卡器&#xff0c;每个厂商的接口也不同。归纳为两类&#xff0c;一类是感应式读卡&#xff0c;卡片接触上去就读出数据。一种是触发式的&#xff0c;程序调用读卡方法&#xff0c;硬件再进入读卡轮询。对应触发式的只能加按钮触发了&a…

技术分享 | app自动化测试(Android)-- 特殊控件 Toast 识别

Toast 是 Android 系统中的一种消息框类型&#xff0c;它属于一种轻量级的消息提示&#xff0c;常常以小弹框的形式出现&#xff0c;一般出现 1 到 2 秒会自动消失&#xff0c;可以出现在屏幕上中下任意位置。它不同于 Dialog&#xff0c;它没有焦点。Toast 的设计思想是尽可能…

不要再纠结了!看完这篇,你就能秒懂webpack、vite、rollup三者间差异!

前端打包工具webpack、vite、rollup的区别及使用 webpack、vite、rollup的区别webpack、vite的区别为什么vite比webpack打包快&#xff1f;为什么有人会说vite慢&#xff1f;首屏问题懒加载问题解决方案注意点 webpackwebpack打包过程webpack打包原理第一次冷启动慢的原因热更新…

在yolov5源码中添加注意力机制

yolov5源码中添加注意力机制 1 项目环境配置1.1 yolov5 源码下载1.2 创建虚拟环境1.3 安装依赖 2 常用的注意力机制2.1 SE 注意力机制2.2 CBAM 注意力机制2.3 ECA 注意力机制2.4 CA 注意力机制 3 添加方式3.1 修改 common.py 文件3.2 修改 yolo.py 文件3.3 修改 yolov5s.yaml 文…

易查分如何导入数据?这个最关键的要点别忽略

我们在使用易查分制作查询系统时&#xff0c;偶尔会遇到Excel文件没有办法正常上传的情况。这个问题困扰着许多老师&#xff0c;他们不知道该如何解决。今天我想和大家讨论一下&#xff0c;易查分导入数据时最常出现错误的原因&#xff0c;其中这个要点最关键&#xff0c;但很多…

谷歌Bard入门指南

文章目录 谷歌Bard入门指南一、简介二、使用指南三、中文化3.1 中文提问3.2 中文回答 四、Hello Game五、亮点 谷歌Bard入门指南 一、简介 Bard 是一个大型语言模型&#xff0c;也称为对话式 AI 或聊天机器人&#xff0c;经过训练&#xff0c;内容丰富且全面。Bard 接受过大量…

Mysql——》哈希索引

推荐链接&#xff1a; 总结——》【Java】 总结——》【Mysql】 总结——》【Redis】 总结——》【Kafka】 总结——》【Spring】 总结——》【SpringBoot】 总结——》【MyBatis、MyBatis-Plus】 总结——》【Linux】 总结——》【MongoD…

接口测试-postman,JMeter与LoadRunner比较

目录 JMeter与LoadRunner比较 JMeter缺点 一.创建测试用例集、子集 二.创建测试用例 三.设置变量 四.添加响应处理 五.批量执行测试用例 总结&#xff1a; postman是一个谷歌出的轻量级的专门测试接口的小工具~&#xff08;PS&#xff1a;postman包括两种&#xff1a;C…

PostgreSQL如何根据执行计划进行性能调优?

EXPLAIN命令 PG中EXPLAIN命令语法格式如下&#xff1a; EXPLAIN [(option[,...])] statement EXPLAIN [ANALYZE] [VERBOSE] statement该命令的options如下&#xff1a; ANALYZE [boolean]VERBOSE [boolean]COSTS [boolean]BUFFERS [boolean]FORMAT {TEXT | XML | JSON | YAM…

【UnityDOTS 十】DynamicBufferComponent介绍

DynamicBufferComponent 前言 DynamicBufferComponent 作为一种特殊的组件存在&#xff0c;可以作为一种非托管内存下可动态调整带下的数组容器组件。 一、DynamicBufferComponent是什么&#xff1f; DynamicBufferComponent也是组件的一种。 需要关注的是内部指针&#xf…

spring使用01

① 导入 Spring 开发的基本包坐标 ② 编写 Dao 接口和实现类 ③ 创建 Spring 核心配置文件 ④ 在 Spring 配置文件中配置 UserDaoImpl ⑤ 使用 Spring 的 API 获得 Bean 实例 第一步&#xff1a;创建maven的web骨架 然后&#xff0c;导入 Spring 开发的基本包坐标 <depe…

工资10K,副业20K,这届程序员搞副业真野

最近刚完成了一个远程外包项目工作&#xff0c;钱刚到账&#xff0c;小金库又添了一笔&#xff1a; 从一开始的15K死工资&#xff0c;到现在的主业副业一共25K收入&#xff0c;最近的经济压力小了很多&#xff0c;终于也有闲钱和老婆去旅旅游&#xff0c;升级一下外设&#xff…

平板电脑的触控笔有必要买吗?平价电容笔排行榜

伴随着ipad的流行&#xff0c;部分学习党开始从传统的纸笔教学向无纸化教学转变。于是&#xff0c;原本属于苹果专利的电容笔&#xff0c;一下子就火了起来&#xff0c;不少人都对这个价格接近一千块钱的电容笔产生了浓厚的兴趣。我想&#xff0c;苹果电容笔特有的的“重力压感…