基于weka手工实现支持向量机smo算法

news2024/11/26 20:49:30

关于svm机器学习模型,我主要学习的是周志华老师的西瓜书(《机器学习》);

但是西瓜书中对于参数优化(即:Sequential Minimal Optimization,smo算法)部分讲解的十分简略,看起来不太好懂。因此这一部分参考的是John C. Platt 1998年发表的论文:Sequential Minimal Optimization: A Fast Algorithm for Training Support Vector Machines

值得注意的是,S.S. Keerthi在2001年又发表了一篇名为Imrovements to Platt’s SMO Algorithm for SVM Classifier Design的文章,在这篇文章中,它改进了原版本smo的收敛条件,并融入了许多缓存机制,好处是求解速度更快了,但理解起来较为晦涩。

因为smo数学原理较强,处于学习考虑,我这里的实现参考的的是John C. Platt 1998年发表的论文。

一、支持向量机(SVM)模型

支持向量机就是想找到一个间隔最大的超平面,将正负两种样本分割开来,进而实现分类的一个模型。

在这里插入图片描述

支持向量机寻找到的超平面可以用如下公式来表示:

w T x + b = 0 w^Tx+b=0 wTx+b=0

如果输入x,结果大于0,就为正例;
如果输入x,结果小于0,就为负例,进而实现分类任务。

根据周志华西瓜书(《机器学习》p121-123)中的公式推导,我们要想寻找到参数的最优解,最终的优化目标如下:

min ⁡ w , b 1 2 ∣ ∣ w ∣ ∣ 2 s . t . y i ( w T x i + b ) ≥ 1 , i = 1 , 2 , . . . , m \min_{w,b}\frac{1}{2}||w||^2\\ s.t. \quad y_i(w^Tx_i+b) \ge 1,\quad i=1,2,...,m w,bmin21∣∣w2s.t.yi(wTxi+b)1,i=1,2,...,m

上面这个最优化目标对应的 w w w b b b 就是我们最终想要的结果。1

二、序列最小优化算法(SMO)

而优化上述模型所采用的优化算法一种就是二次规划,采用线程的优化包进行求解,但是当样本量非常大的情况下,约束目标的数量也会非常大,会出现维度爆炸的问题,而相比之下,SMO算法就可以很好地解决这个问题。

在学习SMO算法的时候,我首先阅读的是西瓜书上的相关内容,但是十分晦涩,读完后一头雾水。

然后我又找同学借了本李航老师的《统计机器学习》进行阅读,读了几遍之后感觉虽然有了一个大体的思路,但是具体如何编码实现呢?比如如何判定一个样例是否满足KKT条件?还是不太会。

直到最后,被逼无奈之下去看了John C. Platt 1998年发表的原版论文(Sequential Minimal Optimization: A Fast Algorithm for Training Support Vector Machines),看完后真的有种柳暗花明又一村的感觉,感觉比上述两位老师写的教材还要好懂一些,所以十分建议阅读。最重要的是,在文章的末尾,John C. Platt前辈还提供了一段C语言的伪代码,对照着伪代码以及文章中的公式,再回过头来写svm的模型就很容易了。2

三、基于weka实现的SMO

因为svm需要经过一个sigmoid函数类似的指数类型的变换以及核函数的处理(高斯核、拉普拉斯核都会涉及到指数),因此,其值大小是非常重要的。如果svm输出的值为几百或者几千,那么经过指数后,直接就会变为无穷大inf或者Nan。

我在编码的完成后,自己写的svm的性能总是不好,找了很久的原因,最终定位在了这个bug上。

package weka.classifiers.myf;

import weka.classifiers.Classifier;
import weka.core.Instance;
import weka.core.Instances;

import weka.filters.Filter;
import weka.filters.unsupervised.attribute.NominalToBinary;
import weka.filters.unsupervised.attribute.Standardize;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

enum KernelType {
    KERNEL_LINEAR, KERNEL_POLYNOMIAL, KERNEL_RBF, KERNEL_SIGMOID
}

/**
 * @author YFMan
 * @Description 自定义的 SMO 分类器
 * @Date 2023/6/12 15:45
 */
public class mySMO extends Classifier {

    // 二元支持向量机
    public static class BinarySMO implements Serializable {

        // alpha
        protected double[] m_alpha;

        // bias
        protected double m_b;

        // 训练集
        protected Instances m_train;

        // 权重向量
        protected double[] m_weights;

        // 训练数据的类别标签
        protected double[] m_class;

        // 支持向量集合 {i: 0 < m_alpha[i] < C}
        protected Set<Integer> m_supportVectors;

        // 惩罚因子C,超参数
        protected double m_C = 10.0;

        // 容忍参数
        protected double m_toleranceParameter = 1.0e-3;

        // 四舍五入的容忍参数
        protected double m_epsilon = 1.0e-12;

        // 最大迭代次数
        protected int m_maxIterations = 100000;

        // 当前已经执行的迭代次数
        protected int m_numIterations = 0;


        // 定义核函数枚举类型
        protected KernelType m_kernelType = KernelType.KERNEL_LINEAR;

        // 定义多项式核函数的参数
        protected double m_exponent = 2.0;

        // 定义 高斯核 和 拉普拉斯 核函数的参数
        protected double m_gamma = 1.0;

        // 定义 SIGMOID 核函数的参数 beta
        protected double m_sigmoidBeta = 1.0;


        // 定义 sigmoid 核函数的参数 theta
        protected double m_sigmoidTheta = -1.0;

        // 程序精度误差
        protected double m_Del = 1000 * Double.MIN_VALUE;

        /*
         * @Author YFMan
         * @Description // 构建分类器
         * @Date 2023/6/12 22:03
         * @Param [instances 训练数据集, cl1 正类, cl2 负类]
         * @return void
         **/
        protected void buildClassifier(Instances instances, int cl1, int cl2) throws Exception {
            // 初始化 alpha
            m_alpha = new double[instances.numInstances()];

            // 初始化 bias
            m_b = 0;

            // 初始化训练集
            m_train = instances;

            // 初始化权重向量
            m_weights = new double[instances.numAttributes() - 1];

            // 初始化支持向量集合
            m_supportVectors = new HashSet<Integer>();

            // 初始化 m_class
            m_class = new double[instances.numInstances()];

            // 将标签转换为 -1 和 1
            for (int i = 0; i < m_class.length; i++) {
                // 如果实例的类别标签为负类,则将其转换为 -1
                if (instances.instance(i).classValue() == cl1) {
                    m_class[i] = -1;
                } else {
                    m_class[i] = 1;
                }
            }

            int numChanged = 0;         // 记录改变的拉格朗日乘子的个数
            boolean examineAll = true;  // 是否检查所有的实例

            while ((numChanged > 0 || examineAll) && (m_numIterations < m_maxIterations)) {
                numChanged = 0;
                if (examineAll) {
                    // loop over all training examples
                    for (int i = 0; i < m_train.numInstances(); i++) {
                        numChanged += examineExample(i);
                    }
                } else {
                    // loop over examples where alpha is not 0 & not C
                    for (int i = 0; i < m_train.numInstances(); i++) {
                        if ((m_alpha[i] != 0) && (m_alpha[i] != m_C)) {
                            numChanged += examineExample(i);
                        }
                    }
                }

                if (examineAll) {
                    examineAll = false;
                } else if (numChanged == 0) {
                    examineAll = true;
                }

                m_numIterations++;
            }
        }

        /*
         * @Author YFMan
         * @Description // 计算 SVM 的输出
         * @Date 2023/6/14 19:26
         * @Param [index, inst]
         * @return double
         **/
        public double SVMOutput(Instance instance) throws Exception {
            double result = 0;

            if (m_kernelType == KernelType.KERNEL_LINEAR) {
                for (int i = 0; i < m_weights.length; i++) {
                    result += m_weights[i] * instance.value(i);
                }
            } else {
                // 非线性核函数 计算 SVM 的输出
                for (int i = 0; i < m_train.numInstances(); i++) {
                    // 只有支持向量的拉格朗日乘子才会大于 0 且两个向量不重合
                    if (m_alpha[i] > 0) {
                        result += m_alpha[i] * m_class[i] * kernelFunction(m_train.instance(i), instance);
                    }
                }
            }

            result -= m_b;
            return result;
        }

        /*
         * @Author YFMan
         * @Description // 根据 i2 选择第二个变量,并且更新拉格朗日乘子
         * @Date 2023/6/14 19:58
         * @Param [i2]
         * @return int
         **/
        protected int examineExample(int i2) throws Exception {
            double y2 = m_class[i2];
            double alph2 = m_alpha[i2];
            double E2 = SVMOutput(m_train.instance(i2)) - y2;
            double r2 = E2 * y2;

            if (r2 < -m_toleranceParameter && alph2 < m_C || r2 > m_toleranceParameter && alph2 > 0) {
                // 第一种情况:违反KKT条件
                // 选择第二个变量
                if (m_supportVectors.size() > 1) {
                    // 选择第二个变量
                    int i1 = -1;
                    double max = 0;
                    for (Integer index : m_supportVectors) {
                        double E1 = SVMOutput(m_train.instance(index)) - m_class[index];
                        double temp = Math.abs(E1 - E2);
                        if (temp > max) {
                            max = temp;
                            i1 = index;
                        }
                    }
                    // 如果找到了第二个变量
                    if (i1 >= 0) {
                        if (takeStep(i1, i2) == 1) {
                            return 1;
                        }
                    }
                }
                // 第二种情况:没有选择第二个变量
                for (int index : m_supportVectors) {
                    if (takeStep(index, i2) == 1) {
                        return 1;
                    }
                }
                // 第三种情况:没有选择支持向量
                for (int index = 0; index < m_train.numInstances(); index++) {
                    if (takeStep(index, i2) == 1) {
                        return 1;
                    }
                }
            }
            return 0;
        }

        /*
         * @Author YFMan
         * @Description // 根据 i1 和 i2 更新拉格朗日乘子
         * @Date 2023/6/14 19:59
         * @Param [i1, i2]
         * @return int
         **/
        protected int takeStep(int i1, int i2) throws Exception {
            if (i1 == i2) {
                return 0;
            }

            double alph1 = m_alpha[i1];
            double alph2 = m_alpha[i2];
            double y1 = m_class[i1];
            double y2 = m_class[i2];
            double E1 = SVMOutput(m_train.instance(i1)) - y1;
            double E2 = SVMOutput(m_train.instance(i2)) - y2;
            double s = y1 * y2;

            double L = 0;
            double H = 0;
            if (y1 != y2) {
                L = Math.max(0, alph2 - alph1);
                H = Math.min(m_C, m_C + alph2 - alph1);
            } else {
                L = Math.max(0, alph2 + alph1 - m_C);
                H = Math.min(m_C, alph2 + alph1);
            }

            if (L == H) {
                return 0;
            }

            double k11 = kernelFunction(m_train.instance(i1), m_train.instance(i1));
            double k12 = kernelFunction(m_train.instance(i1), m_train.instance(i2));
            double k22 = kernelFunction(m_train.instance(i2), m_train.instance(i2));
            double eta = k11 + k22 - 2 * k12;

            double a1 = 0;
            double a2 = 0;

            if (eta > 0) {
                a2 = alph2 + y2 * (E1 - E2) / eta;
                if (a2 < L) {
                    a2 = L;
                } else if (a2 > H) {
                    a2 = H;
                }
            } else {
                double f1 = y1 * (E1 + m_b) - alph1 * k11 - s * alph2 * k12;
                double f2 = y2 * (E2 + m_b) - s * alph1 * k12 - alph2 * k22;
                double L1 = alph1 + s * (alph2 - L);
                double H1 = alph1 + s * (alph2 - H);

                // objective function at a2=L
                double Lobj = L1 * f1 + L * f2 + 0.5 * L1 * L1 * k11 + 0.5 * L * L * k22 + s * L * L1 * k12;
                // objective function at a2=H
                double Hobj = H1 * f1 + H * f2 + 0.5 * H1 * H1 * k11 + 0.5 * H * H * k22 + s * H * H1 * k12;

                if (Lobj > Hobj + m_epsilon) {
                    a2 = L;
                } else if (Lobj < Hobj - m_epsilon) {
                    a2 = H;
                } else {
                    a2 = alph2;
                }
            }

            if (Math.abs(a2 - alph2) < m_epsilon * (a2 + alph2 + m_epsilon)) {
                return 0;
            }

            if (a2 > m_C - m_Del * m_C) // m_Del = 1000 *
                // Double.MIN_VALUE,在精度误差上做了一点处理
                a2 = m_C;
            else if (a2 <= m_Del * m_C)
                a2 = 0;

            a1 = alph1 + s * (alph2 - a2);

            // Update threshold to reflect change in Lagrange multipliers
            double b1 = E1 + y1 * (a1 - alph1) * k11 + y2 * (a2 - alph2) * k12 + m_b;
            double b2 = E2 + y1 * (a1 - alph1) * k12 + y2 * (a2 - alph2) * k22 + m_b;

            if ((0 < a1 && a1 < m_C) && (0 < a2 && a2 < m_C)) {
                m_b = (b1 + b2) / 2;
            } else if (0 < a1 && a1 < m_C) {
                m_b = b1;
            } else if (0 < a2 && a2 < m_C) {
                m_b = b2;
            }

            // Update weight vector to reflect change in a1 & a2, if linear SVM
            if (m_kernelType == KernelType.KERNEL_LINEAR) {
                int column = 0;
                for (int i = 0; i < m_train.numAttributes(); i++) {
                    if (i != m_train.classIndex()) {
                        m_weights[column] += y1 * (a1 - alph1) * m_train.instance(i1).value(i) + y2 * (a2 - alph2) * m_train.instance(i2).value(i);
                        column++;
                    }
                }
            }

            m_alpha[i1] = a1;
            m_alpha[i2] = a2;

            return 1;
        }

        /*
         * @Author YFMan
         * @Description // 核函数
         * @Date 2023/6/14 19:29
         * @Param [i1, i2]
         * @return double
         **/
        protected double kernelFunction(Instance instance1, Instance instance2) throws Exception {
            switch (m_kernelType) {
                case KERNEL_LINEAR:
                    return linearKernel(instance1, instance2);
                case KERNEL_POLYNOMIAL:
                    return polynomialKernel(instance1, instance2);
                case KERNEL_RBF:
                    return rbfKernel(instance1, instance2);
                case KERNEL_SIGMOID:
                    return sigmoidKernel(instance1, instance2);
                default:
                    throw new Exception("Invalid kernel type.");
            }
        }

        /*
         * @Author YFMan
         * @Description // 线性核函数
         * @Date 2023/6/14 20:33
         * @Param [instance1, instance2]
         * @return double
         **/
        protected double linearKernel(Instance instance1, Instance instance2) {
            double result = 0;
            for (int i = 0; i < m_train.numAttributes() - 1; i++) {
                result += instance1.value(i) * instance2.value(i);
            }
            return result;
        }

        protected double polynomialKernel(Instance instance1, Instance instance2) {
            double result = 0;
            for (int i = 0; i < m_train.numAttributes() - 1; i++) {
                result += instance1.value(i) * instance2.value(i);
            }
            return Math.pow(result + m_gamma, m_exponent);
        }

        /*
         * @Author YFMan
         * @Description // 高斯核函数
         * @Date 2023/6/15 10:46
         * @Param [instance1, instance2]
         * @return double
         **/
        protected double rbfKernel(Instance instance1, Instance instance2) {
            double result = 0;
            for (int i = 0; i < m_train.numAttributes() - 1; i++) {
                result += Math.pow(instance1.value(i) - instance2.value(i), 2);
            }
            return Math.exp(-result / (2 * m_gamma * m_gamma));
        }

        /*
         * @Author YFMan
         * @Description // sigmoid 核函数
         * @Date 2023/6/15 10:47
         * @Param [instance1, instance2]
         * @return double
         **/
        protected double sigmoidKernel(Instance instance1, Instance instance2) {
            double result = 0;
            for (int i = 0; i < m_train.numAttributes() - 1; i++) {
                result += instance1.value(i) * instance2.value(i);
            }
            return Math.tanh(m_sigmoidBeta * result + m_sigmoidTheta);
        }

    }

    // 归一化数据的过滤器
    public static final int FILTER_NORMALIZE = 0;

    // 标准化数据的过滤器
    public static final int FILTER_STANDARDIZE = 1;

    // 不使用过滤器
    public static final int FILTER_NONE = 2;

    // 二元分类器
    protected BinarySMO m_classifier = null;

    // 是否使用过滤器
    protected int m_filterType = FILTER_NORMALIZE;

    // 用于标准化/归一化数据的过滤器
    protected Filter m_Filter = null;

    // 用于标准化数据的过滤器
    protected Filter m_StandardizeFilter = null;

    // 用于二值化数据的过滤器
    protected Filter m_NominalToBinary = null;

    /*
     * @Author YFMan
     * @Description // 构建分类器
     * @Date 2023/6/14 20:29
     * @Param [insts]
     * @return void
     **/
    public void buildClassifier(Instances insts) throws Exception {
        // 标准化数据
        m_StandardizeFilter = new Standardize();
        m_StandardizeFilter.setInputFormat(insts);
        insts = Filter.useFilter(insts, m_StandardizeFilter);

        // 二值化数据
        m_NominalToBinary = new NominalToBinary();
        m_NominalToBinary.setInputFormat(insts);
        insts = Filter.useFilter(insts, m_NominalToBinary);



        m_classifier = new BinarySMO();

        m_classifier.buildClassifier(insts, 0, 1);
    }

    /*
     * @Author YFMan
     * @Description // 分类实例
     * @Date 2023/6/14 20:43
     * @Param [inst]
     * @return double[]
     **/
    public double[] distributionForInstance(Instance inst) throws Exception {
        // 过滤实例
        m_StandardizeFilter.input(inst);
        inst = m_StandardizeFilter.output();

        m_NominalToBinary.input(inst);
        inst = m_NominalToBinary.output();

        double[] result = new double[2];

        double output = m_classifier.SVMOutput(inst);
        result[1] = 1.0 / (1.0 + Math.exp(-output));
        result[0] = 1.0 - result[1];

        return result;
    }

    /*
     * @Author YFMan
     * @Description // 主函数
     * @Date 2023/6/14 20:42
     * @Param [argv]
     * @return void
     **/
    public static void main(String[] argv) {
        runClassifier(new mySMO(), argv);
    }
}

四、感悟

支持向量机的优化部分smo数学原理很强,论文中的推导非常清晰,因此文中并没有对其过多解读,因为我解读的再细致,以我对smo的理解,也不可能有原作者好。

同时,虽然自己能将smo侥幸实现,但只能说按照文中公式及伪代码来理解一二,并不敢说对其理解有多深刻。直到现在,也依然有很多不明白的点。

对于计算机科学这门应用学科而言,数学永远是天花板,也许我们能侥幸的把它用起来,但如果真正的想要有所建树和理论创新,可能还要回归到数学吧。


  1. 《机器学习》周志华 ↩︎

  2. Platt J. Sequential minimal optimization: A fast algorithm for training support vector machines[J]. 1998. ↩︎

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

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

相关文章

Linux运维面试题(三)之shell编程类

Linux运维面试题&#xff08;三&#xff09;之shell编程类 文本截取有一个b.txt文本&#xff0c;要求将所有域名截取出来&#xff0c;并统计重复域名出现的次数统计当前服务器正在连接的IP地址&#xff0c;并按连接次数排序&#xff08;cut不能以空格做分隔符&#xff09; 随机…

Vis相关的期刊会议

中国计算机学会推荐国际学术会议和期刊目录 文档&#xff0c; 下载 link&#xff1a;CCF推荐国际学术刊物目录-中国计算机学会 一.可视化方向文章 1.IEEE VIS&#xff0c;是由 IEEE Visualization and Graphics Technical Committee(VGTC) 主办的数据可视化领域的顶级会议&a…

VMware桥接模式无法识别英特尔AX200无线网卡解决办法

1.先到英特尔网站下载最新驱动&#xff0c;更新网卡驱动适用于 Intel 无线网络卡的 Windows 10 和 Windows 11* Wi-Fi 驱动程序 2.到控制面板查看无线网卡属性是否有下图组件 没有的话&#xff0c;依次操作 安装---服务---添加---从磁盘安装----浏览--进入VMware安装目录&…

Linux系统的目录结构

目录 一、Linux系统使用注意 1、Linux严格区分大小写 2、Linux文件"扩展名" 3、Linux中所有内容以文件形式保存 4、Linux中存储设备都必须在挂载之后才能使用 二、目录结构 1、Linux分区与Windows分区 2、Linux系统文件架构 3、Linux系统的文件目录用途 一、…

Docker【安装与基本使用】

【1】Docker的安装 注意&#xff1a;如果之前安装过docker其他版本&#xff0c;请删除干净。 docker-01 10.0.0.51 2G docker-02 10.0.0.52 2G docker-01 [rootdocker-01 ~]# cp -rp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime cp: overwrite ‘/etc/localtime’? …

(中等)LeetCode146 LRU 缓存 Java

本题有两种实现操作&#xff0c;需要使用到一个哈希表和一个双向链表。在Java语言中&#xff0c;有一种结合了哈希表和双向链表的数据结构&#xff0c;LinkedHashMap import java.util.LinkedHashMap; import java.util.Map;class LRUCache extends LinkedHashMap<Integer,…

网络运维能转型到系统运维吗?

很多网工处于刚起步的初级阶段&#xff0c;各大公司有此专职&#xff0c;但重视或重要程度不高&#xff0c;可替代性强&#xff1b;小公司更多是由其它岗位来兼顾做这一块工作&#xff0c;没有专职&#xff0c;也不可能做得深入。 现在开始学习入门会有一些困难&#xff0c;不…

【深度学习】日常笔记13

前向传播&#xff08;forward propagation或forward pass&#xff09;指的是&#xff1a;按顺序&#xff08;从输⼊层到输出层&#xff09;计算和存储神经⽹络中每层的结果。 绘制计算图有助于我们可视化计算中操作符和变量的依赖关系。下图是与上述简单⽹络相对应的计算图&…

【C语言】深入了解分支和循环语句

&#x1f341; 博客主页:江池俊的博客 &#x1f341;收录专栏&#xff1a;C语言——探索高效编程的基石 &#x1f341; 如果觉得博主的文章还不错的话&#xff0c;请点赞&#x1f44d;收藏&#x1f31f; 三连支持一下博主&#x1f49e; &#x1f4ab;“每一天都是一个全新的机会…

【优选算法题练习】day7

文章目录 一、35. 搜索插入位置1.题目简介2.解题思路3.代码4.运行结果 二、69. x 的平方根1.题目简介2.解题思路3.代码4.运行结果 三、852. 山脉数组的峰顶索引1.题目简介2.解题思路3.代码4.运行结果 总结 一、35. 搜索插入位置 1.题目简介 35. 搜索插入位置 给定一个排序数组…

浅谈自动化测试工具 Appium

目录 前言&#xff1a; 一、简单介绍 &#xff08;一&#xff09;测试对象 &#xff08;二&#xff09;支持平台及语言 &#xff08;三&#xff09;工作原理 &#xff08;四&#xff09;安装工具 二、环境搭建 &#xff08;一&#xff09;安装 Android SDK &#xff0…

08 - 线性表的类型定义 - 循环双向链表

双向链表可以分为普通双向链表(前面我们已经学习过了)与循环双向链表。 循环双向链表的定义 循环双向链表就是在双向链表的基础之上,头结点的Prev指向尾结点,尾结点的Next指针则指向头结点,从而形成闭环结构。 内存中的结构 双向链表与循环双向链表的区别 双向链表 …

Vue项目中你是如何解决跨域的呢?

一、跨域是什么 跨域本质是浏览器基于同源策略的一种安全手段 同源策略&#xff08;Sameoriginpolicy&#xff09;&#xff0c;是一种约定&#xff0c;它是浏览器最核心也最基本的安全功能 所谓同源&#xff08;即指在同一个域&#xff09;具有以下三个相同点 协议相同&…

微信小程序开发入门指南

微信小程序商城是一种方便快捷的电商平台&#xff0c;让商家能够在微信平台上展示和销售自己的商品。本文将详细介绍如何使用乔拓云平台创建自己的小程序商城。即使是对编程没有任何基础的小白用户&#xff0c;也可以轻松创建自己的微信小程序商城。 第一步&#xff0c;登录乔拓…

上门服务小程序|上门家政小程序开发

随着现代生活节奏的加快和人们对便利性的追求&#xff0c;上门家政服务逐渐成为了许多家庭的首选。然而&#xff0c;传统的家政服务存在着信息不透明、服务质量不稳定等问题&#xff0c;给用户带来了困扰。为了解决这些问题&#xff0c;上门家政小程序应运而生。上门家政小程序…

拓数派荣获 “2023 年杭州市企业高新技术研究开发中心” 认定

近日&#xff0c;拓数派上榜由杭州市科学技术局公布的 “2023 年杭州市企业高新技术研究开发中心名单”&#xff0c;通过专业的技术研发、成果转化和高新技术产业化成绩&#xff0c;获得 “杭州市拓数派数据计算企业高新技术研究开发中心” 认定。 图为&#xff1a;“杭州市拓数…

spring boot 多模块项目非启动模块的bean无法注入(问题记录)

之前有说我搭了一个多模块项目&#xff0c;往微服务升级&#xff0c;注入的依赖在zuodou-bean模块中&#xff0c;入jwt拦截&#xff0c; Knife4j ,分页插件等等&#xff0c;但是启动类在system中&#xff0c;看网上说在启动类上加SpringBootApplication注解默认扫描范围为自己…

【HTML】:超文本标记语言的基础入门元素

目录 1️⃣前言2️⃣概述&#x1f331;什么是HTML&#xff1f;&#x1f331;初步认识HTML 3️⃣了解概念✨基本结构✔️元素&#x1f50b; 标签&#x1f4a1;属性 4️⃣基本内容学习标签特殊字符属性图像标签的属性超链接标签的属性 5️⃣锚点链接6️⃣表格表格标签表格标签的属…

(ceph)资源池poll管理

资源池 Pool 管理 前面的文章中我们已经完成了 Ceph 集群的部署&#xff08;ceph部署: 传送门&#xff09;&#xff0c;但是我们如何向 Ceph 中存储数据呢&#xff1f;首先我们需要在 Ceph 中定义一个 Pool 资源池。Pool 是 Ceph 中存储 Object 对象抽象概念。我们可以将其理解…

oracle 如何连同空表一起导出成dmp的方法

1、oracle导出dmp文件的时候&#xff0c;经常会出现一些空表&#xff0c;没有一并被导出的情况。 执行sql select alter table ||table_name|| allocate extent; from user_tables where num_rows0 or num_rows is null; 新建一个sql窗口&#xff0c;把查询结果的sql&#…