深度学习 · 手撕 DeepLearning4J ,用Java实现手写数字识别 (附UI效果展示)

引言

随着人工智能技术的不断发展,手写数字识别已经成为深度学习领域的一个经典案例。不管是老牌的机器学习模型还是现代的神经网络架构,手写数字识别总是大家学习和实战的起点之一。而对于我们日常使用的Java开发者来说,借助DeepLearning4J这个强大的Java深度学习框架,可以很方便地在Java项目中实现手写数字识别的功能。在这里插入图片描述

在本文中,我们会以一种轻松的方式,带你一步步实现一个完整的手写数字识别系统,使用Spring Boot作为后端框架,结合ThymeleafBootstrap来构建用户友好的界面。

为什么选择 DeepLearning4J

在很多人提到深度学习时,首先想到的可能是TensorFlowPyTorch。那么,为什么我们这次要使用DeepLearning4J呢?其实,DeepLearning4J最大的优势就在于它是专门为Java和JVM语言设计的深度学习框架。对于熟悉Java生态的开发者来说,DeepLearning4J让我们可以利用现有的Java工具链和库,轻松构建和部署深度学习模型。

除此之外,DeepLearning4J支持大规模的分布式训练,甚至可以与Hadoop和Spark进行集成,方便企业级应用开发。

项目概览

在本文中,我们将实现一个可以识别手写数字的Web应用。用户可以通过网页上传一张手写数字的图片,系统会自动识别并返回预测结果。

为了实现这个功能,我们会按照以下步骤进行:

  1. 使用DeepLearning4J训练一个简单的神经网络模型,专注于识别手写数字。
  2. 将训练好的模型集成到Spring Boot应用中。
  3. 构建一个Web页面,用户可以上传图片并查看识别结果。

项目结构

我们的项目结构将包括后端服务用于处理上传的图片和执行预测逻辑,以及前端页面用于用户上传图片和查看预测结果。

├── src/
│   ├── main/
│   │   ├── java/
│   │   │   ├── com/neo/
│   │   │   │   ├── controller/
│   │   │   │   │   └── OCRController.java
│   │   │   │   ├── service/
│   │   │   │   │   ├── ImageProcessingService.java
│   │   │   │   │   └── OCRPredictionService.java
│   │   │   │   └── model/
│   │   │   │       └── OCRModelService.java
│   │   ├── resources/
│   │   │   ├── static/
│   │   │   ├── templates/
│   │   │   │   └── upload.html
│   ├── pom.xml

模型训练

使用 DeepLearning4J 进行手写数字识别模型的训练

在这段代码中,我们使用DeepLearning4J框架训练了一个手写数字识别模型,基于经典的MNIST数据集。下面我们将逐步解释每一部分代码的功能和作用,以便更好地理解整个模型训练的过程。

package com.neo.service;import org.deeplearning4j.datasets.iterator.impl.MnistDataSetIterator;
import org.deeplearning4j.nn.conf.MultiLayerConfiguration;
import org.deeplearning4j.nn.conf.NeuralNetConfiguration;
import org.deeplearning4j.nn.conf.layers.DenseLayer;
import org.deeplearning4j.nn.conf.layers.OutputLayer;
import org.deeplearning4j.nn.multilayer.MultiLayerNetwork;
import org.deeplearning4j.optimize.listeners.ScoreIterationListener;
import org.deeplearning4j.util.ModelSerializer;
import org.nd4j.linalg.activations.Activation;
import org.nd4j.linalg.dataset.api.iterator.DataSetIterator;
import org.nd4j.linalg.learning.config.Nesterovs;
import org.nd4j.linalg.lossfunctions.LossFunctions;public class OCRModelService {public static void trainModel() throws Exception {int batchSize = 128;int rngSeed = 123;int numEpochs = 1;DataSetIterator mnistTrain = new MnistDataSetIterator(batchSize, true, rngSeed);MultiLayerConfiguration conf = new NeuralNetConfiguration.Builder().seed(rngSeed).updater(new Nesterovs(0.006, 0.9)).l2(1e-4).list().layer(0, new DenseLayer.Builder().nIn(28 * 28).nOut(1000).activation(Activation.RELU).build()).layer(1, new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD).activation(Activation.SOFTMAX).nIn(1000).nOut(10).build()).build();MultiLayerNetwork model = new MultiLayerNetwork(conf);model.init();model.setListeners(new ScoreIterationListener(100));model.fit(mnistTrain, numEpochs);// 保存模型ModelSerializer.writeModel(model, "ocr-model.zip", true);}public static void main(String[] args) {try {// 调用训练方法OCRModelService.trainModel();System.out.println("模型训练完成并保存为 ocr-model.zip");} catch (Exception e) {e.printStackTrace();}}
}

详细介绍模型训练

1. 数据集加载

int batchSize = 128;
int rngSeed = 123;
DataSetIterator mnistTrain = new MnistDataSetIterator(batchSize, true, rngSeed);
  • batchSize:每次训练使用的样本数量。在这里,我们设置为128,这意味着每次从数据集中取出128张图片用于模型训练。
  • rngSeed:随机数种子,保证每次运行时数据的随机性一致,方便调试和复现结果。
  • MnistDataSetIterator:这是DeepLearning4J提供的一个迭代器,用于加载MNIST数据集。它会自动将数据集分批加载到内存中,供模型训练使用。

2. 模型配置

MultiLayerConfiguration conf = new NeuralNetConfiguration.Builder().seed(rngSeed).updater(new Nesterovs(0.006, 0.9)).l2(1e-4).list().layer(0, new DenseLayer.Builder().nIn(28 * 28).nOut(1000).activation(Activation.RELU).build()).layer(1, new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD).activation(Activation.SOFTMAX).nIn(1000).nOut(10).build()).build();
  • NeuralNetConfiguration.Builder:这是一个用于配置神经网络的构建器。我们在这里设置了一些全局参数,如随机种子、优化器和正则化参数。
  • seed(rngSeed):设置随机种子,保证每次训练的结果一致。
  • updater(new Nesterovs(0.006, 0.9)):使用Nesterov加速梯度下降法,学习率为0.006,动量为0.9。
  • l2(1e-4):L2正则化,用于防止过拟合。
  • list():开始构建网络的层次结构。
  • DenseLayer.Builder():第一层是一个全连接层,输入为28x28个像素(MNIST图片的尺寸),输出为1000个神经元,使用ReLU激活函数。
  • OutputLayer.Builder():输出层是一个具有10个神经元的全连接层,每个神经元对应一个数字类别(0到9),使用Softmax激活函数来输出概率分布,损失函数选择负对数似然(Negative Log Likelihood)。

3. 模型初始化和训练

MultiLayerNetwork model = new MultiLayerNetwork(conf);
model.init();
model.setListeners(new ScoreIterationListener(100));
  • MultiLayerNetwork:使用配置构建一个多层神经网络模型。
  • model.init():初始化模型,准备进行训练。
  • model.setListeners(new ScoreIterationListener(100)):设置一个监听器,每训练100次迭代后输出一次损失函数的值,帮助我们跟踪模型的训练进度。

4. 训练模型

model.fit(mnistTrain, numEpochs);
  • fit方法接受数据集迭代器和训练轮数(numEpochs)作为参数,进行模型训练。在这段代码中,numEpochs设置为1,这意味着我们只训练一个完整的训练集。

5. 保存模型

ModelSerializer.writeModel(model, "ocr-model.zip", true);
  • ModelSerializer.writeModel:这是一个方便的方法,可以将训练好的模型保存到文件中。我们将模型保存为ocr-model.zip,以便后续加载和使用。

通过这段代码,我们实现了一个简单的深度学习模型,用于手写数字识别。我们详细讲解了每个部分的功能,包括数据加载、模型配置、训练和保存。

白话模型训练

如果说,上面的概念太难懂,那我们来把深度学习模型训练的过程用一个做菜的例子来解释,让它变得更加简单易懂。

假设我们要做一道菜——“手写数字识别”。在这个比喻里,我们的目标就是“做出一道完美的菜”,也就是训练出一个能够准确识别手写数字的模型。而我们做菜的过程就像是模型的训练过程。

1. 准备食材(加载数据集)

在做菜之前,首先要准备好食材。对于我们的“手写数字识别”任务,食材就是MNIST数据集,这个数据集包含了大量的手写数字图片,类似于我们准备的“原料”。

int batchSize = 128;  // 这里就像是一次做菜时需要的食材数量
DataSetIterator mnistTrain = new MnistDataSetIterator(batchSize, true, 123);  // 这是我们的食材(训练数据)
  • **食材准备好了吗?就是数据加载到内存中的过程。我们用MnistDataSetIterator**来把数据集分成一批一批(比如每次做菜时要准备128个食材),确保我们每次只用一定量的食材,避免浪费资源。

2. 挑选菜谱(设置模型配置)

接下来,就像做菜要挑选合适的菜谱一样,我们需要为模型挑选合适的结构和步骤。比如,你要做一道麻辣火锅,你不能随便照着糖醋排骨的菜谱做,它的“配料”和“步骤”都不一样。

MultiLayerConfiguration conf = new NeuralNetConfiguration.Builder().seed(123)  // 随机种子,就像菜谱的版本,确保每次做出来的菜口感一致.updater(new Nesterovs(0.006, 0.9))  // 就像选择了一个合适的调味料:Nesterovs优化器.l2(1e-4)  // 加一点调味料:L2正则化,防止做出来的菜太腻.list()  // 开始选择菜谱中的每个步骤.layer(0, new DenseLayer.Builder().nIn(28 * 28)  // 食材的数量,就是手写数字图片的大小.nOut(1000)  // 菜谱第一步:做成1000种口味的菜.activation(Activation.RELU)  // 用的调味料是ReLU.build()).layer(1, new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD).activation(Activation.SOFTMAX)  // 做的最后一道工序,用SOFTMAX调料,让它有个概率结果.nIn(1000).nOut(10)  // 最后把1000种口味缩减到10种:0到9的数字.build()).build();

在这里,我们通过**NeuralNetConfiguration.Builder**来搭建了一个神经网络模型,它就像是我们选择的菜谱。我们定义了神经网络的“结构”:

  • 第一层是一个DenseLayer,就是把28x28个像素的图片“切割成”1000个特征,类似于我们把食材切成小块。
  • 第二层是OutputLayer,最后输出10个数字的概率,类似于最后一道步骤:做成一道美味的菜,看看它的口味是哪个。

3. 开始做菜(训练模型)

当你选择好了菜谱,就要开始实际动手做菜了。我们通过训练模型,类似于在厨房里开始切菜、炒菜,经过一段时间的“烹饪”,最终做出一道合格的菜。

MultiLayerNetwork model = new MultiLayerNetwork(conf);  // 按照菜谱开始做菜
model.init();  // 开始做菜的过程
model.setListeners(new ScoreIterationListener(100));  // 观察一下菜的火候,每100次迭代检查一次
model.fit(mnistTrain, 1);  // 做菜:一次完整的“烹饪”,这里是1次迭代
  • 做菜的时间:这里就是模型的训练过程,**model.fit**方法就像是我们把食材按步骤处理、混合,最后做成一道菜。
  • 每100次迭代检查一次菜的火候:这个就像是我们在做菜时,不时检查一下味道(通过**ScoreIterationListener**监听器查看损失函数的变化)。如果菜做得不对,就赶紧调整火候和调料。

4. 做好的菜(保存模型)

一旦菜做得差不多了,我们就要把做好的菜保存下来,下一次可以重新享用,这就像是我们训练好的模型需要保存,以便以后使用。

ModelSerializer.writeModel(model, "ocr-model.zip", true);  // 把做好的菜保存起来,方便下次享用

通过**ModelSerializer.writeModel,我们把做好的模型保存为ocr-model.zip**,它就像是我们做好的菜装进了保鲜盒,可以随时取出来再次使用。

训练和保存模型

model.fit(mnistTrain, numEpochs);
ModelSerializer.writeModel(model, new File("ocr-model.zip"), true);

我们使用model.fit方法进行训练,训练完成后将模型保存到ocr-model.zip文件中。这样,我们的模型就可以在Spring Boot项目中使用了。

图像处理

在Web应用中,用户上传的图片可能是各种格式、尺寸和颜色的。我们需要对图片进行预处理,转换为模型能够接受的输入格式。

package com.neo.service;import org.nd4j.linalg.api.ndarray.INDArray;
import org.nd4j.linalg.dataset.DataSet;
import org.nd4j.linalg.dataset.api.preprocessor.NormalizerStandardize;
import org.nd4j.linalg.factory.Nd4j;
import org.springframework.stereotype.Service;import java.awt.image.BufferedImage;@Service
public class ImageProcessingService {public INDArray preprocessImage(BufferedImage image) {int width = 28;int height = 28;INDArray array = Nd4j.zeros(1, width * height);// 遍历图像,将每个像素值转换为灰度值for (int i = 0; i < width; i++) {for (int j = 0; j < height; j++) {int rgb = image.getRGB(i, j);int r = (rgb >> 16) & 0xFF;int g = (rgb >> 8) & 0xFF;int b = rgb & 0xFF;// 灰度转换公式double gray = (0.299 * r + 0.587 * g + 0.114 * b) / 255.0;array.putScalar(new int[]{0, j * width + i}, gray);}}// 使用随机数据模拟样本,计算标准化参数INDArray sampleData = Nd4j.rand(new int[]{100, width * height});DataSet sampleDataSet = new DataSet(sampleData, null);NormalizerStandardize normalizer = new NormalizerStandardize();normalizer.fit(sampleDataSet); // 计算标准化参数// 归一化图像数据DataSet dataSet = new DataSet(array, null);normalizer.transform(dataSet);return dataSet.getFeatures(); // 返回标准化后的特征}
}

这段代码的功能是处理图像,将图像转换为适合深度学习模型输入的格式,并对图像进行标准化处理。下面我们将详细解释每一部分的代码和背后的原理。

1. 创建空的INDArray

int width = 28;
int height = 28;
INDArray array = Nd4j.zeros(1, width * height);

这段代码的目的是为图像数据创建一个INDArray,也就是一个二维数组。在深度学习中,数据通常以数组的形式输入模型。

  • widthheight:这里设置了图像的宽度和高度为28x28像素。28x28是MNIST数据集中的图像尺寸(手写数字的大小),并且深度学习模型通常要求输入的数据大小一致。
  • INDArray:是由ND4J(一个用于处理多维数组的库)创建的数组,类似于numpy.ndarray。这里我们创建一个形状为1x784的INDArray,它代表了图像数据的平铺形式(28 * 28 = 784)。将图像的每个像素值存储在这个数组里。

2. 将图像转换为灰度图像

for (int i = 0; i < width; i++) {for (int j = 0; j < height; j++) {int rgb = image.getRGB(i, j);int r = (rgb >> 16) & 0xFF;int g = (rgb >> 8) & 0xFF;int b = rgb & 0xFF;// 灰度转换公式double gray = (0.299 * r + 0.587 * g + 0.114 * b) / 255.0;array.putScalar(new int[]{0, j * width + i}, gray);}
}

这部分代码的目的是将图像的每个像素值转换为灰度值,因为在处理手写数字识别任务时,颜色信息(如红、绿、蓝)对识别数字的帮助有限,使用灰度图像就足够了。

  • image.getRGB(i, j):获取图像上第(i, j)位置的RGB颜色值。
  • rgb >> 16 & 0xFFrgb >> 8 & 0xFFrgb & 0xFF:分别提取RGB颜色的红、绿、蓝分量。每个分量都由8位二进制组成,0xFF用于提取每个分量的低8位。
  • 灰度转换公式:(0.299 * r + 0.587 * g + 0.114 * b)是将RGB值转换为灰度值的标准公式。该公式根据人眼对不同颜色的敏感度对红、绿、蓝三个通道的权重进行了加权。
  • 例如,绿色分量在视觉上比红色和蓝色更重要,因此它的权重为0.587。
  • array.putScalar(new int[]{0, j * width + i}, gray):将计算出的灰度值存储到INDArray数组中。j * width + i计算出当前像素的在一维数组中的位置,0表示这是第一张图像(批量处理时可能有多张图像)。

3. 计算标准化参数(拟合标准化器)

INDArray sampleData = Nd4j.rand(new int[]{100, width * height});
DataSet sampleDataSet = new DataSet(sampleData, null);
NormalizerStandardize normalizer = new NormalizerStandardize();
normalizer.fit(sampleDataSet); // 计算标准化参数

这一部分的目的是通过一组随机数据来计算标准化参数。标准化是深度学习中常用的一种预处理方法,目的是让数据的分布更符合模型的要求,通常是将数据的均值调整为0,标准差调整为1。

  • Nd4j.rand(new int[]{100, width * height}):生成一个大小为100x784的随机数组。这些数据并不是真实的图像数据,只是用来模拟真实数据的统计特性。这里的随机数据相当于假设我们在训练时会遇到的输入数据。
  • new DataSet(sampleData, null):将随机生成的数据包装成一个DataSet对象。DataSet是一个包含特征和标签的容器。这里没有标签,只有特征数据。
  • NormalizerStandardize normalizer = new NormalizerStandardize():创建一个标准化器对象。NormalizerStandardize是ND4J提供的一个标准化工具,它会自动计算数据的均值和标准差,并应用标准化操作。
  • normalizer.fit(sampleDataSet):使用模拟数据(sampleDataSet)来计算标准化的参数(均值和标准差)。这个步骤会“拟合”标准化器,确保我们接下来的图像数据能够进行正确的标准化处理。

4. 归一化图像数据

DataSet dataSet = new DataSet(array, null);
normalizer.transform(dataSet);

现在我们已经有了一个训练好的标准化器,可以用它来对图像进行标准化处理。

  • new DataSet(array, null):将预处理后的图像数据(已经转换为灰度值的28x28像素数据)包装成一个DataSet对象,注意这里没有标签(null)。
  • normalizer.transform(dataSet):使用前面计算出的标准化参数,处理我们当前的图像数据。transform方法会根据均值和标准差,将图像数据进行标准化操作。

5. 返回处理后的数据

return dataSet.getFeatures(); // 返回标准化后的特征

这行代码返回处理后的图像数据,dataSet.getFeatures()获取了经过标准化处理的特征数据。这个特征就是接下来输入到深度学习模型中的数据。

6. 总结图像处理的整体过程

  1. 图像转灰度:从RGB图像中提取出每个像素的灰度值,标准化图像信息。
  2. 创建一个空的**INDArray**:存储图像的像素值,并为接下来的计算做准备。
  3. 标准化处理:为了确保数据更符合深度学习模型的要求,使用NormalizerStandardize来标准化图像数据。
  4. 返回标准化数据:将标准化后的图像数据返回,准备将其输入到训练好的深度学习模型进行预测。

图像处理的核心目的是将图像数据转换为适合深度学习模型处理的格式,同时通过标准化减少数据的偏差,确保模型能够更快收敛,并提高预测的准确性。

Spring Boot 控制器

接下来,我们需要一个控制器来处理用户上传的图片,并调用模型进行预测。

package com.neo.controller;import com.neo.service.ImageProcessingService;
import com.neo.service.OCRPredictionService;
import org.nd4j.linalg.api.ndarray.INDArray;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;@Controller
public class OCRController {@Autowiredprivate ImageProcessingService imageProcessingService;@Autowiredprivate OCRPredictionService ocrPredictionService;// 文件保存路径,您可以根据实际需求修改此路径private static final String UPLOAD_DIR = "src/main/resources/static/uploads/";// 显示上传页面@RequestMapping("/ocr")public String showUploadPage() {return "upload";  // 返回上传页面的视图}// 处理上传的图片并进行 OCR 预测@PostMapping("/ocr/predict")public String predict(@RequestParam("file") MultipartFile file, Model model) {try {String fileName = file.getOriginalFilename();// 创建保存文件的路径Path uploadPath = Paths.get(UPLOAD_DIR);if (!Files.exists(uploadPath)) {Files.createDirectories(uploadPath);  // 创建目录}// 保存文件到本地Path filePath = uploadPath.resolve(fileName);file.transferTo(filePath);// 读取保存的文件// 读取图片BufferedImage image = ImageIO.read(filePath.toFile());// 处理图片并预测INDArray processedImage = imageProcessingService.preprocessImage(image);// 使用模型进行预测int predictedDigit = ocrPredictionService.predict(processedImage);System.out.println("识别的数字是: " + predictedDigit);// 将图片和预测结果传递给前端model.addAttribute("imagePath", fileName);  // 只传递相对路径model.addAttribute("prediction", predictedDigit);return "upload";  // 返回上传页面并显示预测结果} catch (IOException e) {e.printStackTrace();model.addAttribute("error", "图片处理失败,请重新上传");return "upload";  // 如果出现错误,返回上传页面}}
}

构建前端界面

为了让用户能够方便地上传图片,我们需要一个友好的用户界面。使用ThymeleafBootstrap,我们可以快速构建一个简洁的上传页面。

HTML模板代码

通过这个简单的表单,用户可以上传图片,然后在页面上查看预测结果。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>OCR 图像上传和预测</title><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5"><h2 class="text-center">手写数字识别 OCR</h2><!-- 上传图片表单 --><form action="/ocr/predict" method="post" enctype="multipart/form-data" class="mt-4"><div class="mb-3"><label for="file" class="form-label">选择图片文件</label><input type="file" class="form-control" id="file" name="file" required></div><button type="submit" class="btn btn-primary">上传并预测</button></form><!-- 错误信息 --><div th:if="${error}" class="alert alert-danger mt-3" role="alert"><p th:text="${error}"></p></div><!-- 显示上传的图片 --><div th:if="${imagePath}"><h3 class="mt-4">上传的图片:</h3><img th:src="@{/uploads/{image}(image=${imagePath})}" alt="Uploaded Image" class="img-fluid"></div><div th:if="${prediction != null}"><h3 class="mt-4">预测结果:</h3><div class="alert alert-success" role="alert">识别结果:<strong th:text="${prediction}"></strong></div></div>
</div><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

效果演示

在这里插入图片描述

总结

我们完成了一个从模型训练到Web应用集成的完整流程,展示了如何使用Spring Boot和DeepLearning4J构建一个手写数字识别系统。这个项目不仅展示了深度学习在实际应用中的潜力,也展示了Java开发环境中集成深度学习技术的可能性。

通过这个示例,相信你对深度学习有了更加直观的理解,也希望你能够在此基础上,进一步探索深度学习和Java的结合应用。无论是扩展这个项目的功能,还是尝试不同的数据集和模型结构,都是很好的学习和实践方式。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux平台RTMP|RTSP播放器如何回调SEI数据?

我们在对接Linux平台RTMP|RTSP播放的时候&#xff0c;有遇到这样的技术需求&#xff0c;合作企业在做无人机视觉分析场景的时候&#xff0c;除了需要低延迟的拿到解码后的RGB|YUV数据&#xff0c;然后投递给他们自己的视觉算法处理模块外&#xff0c;还需要播放器支持SEI的回调…

vue2 - Day05 - VueX

Vuex 是 Vue.js 官方的状态管理库。它是一个让你能在应用中集中管理共享状态的工具。当应用的规模逐渐增大&#xff0c;组件之间的数据传递变得越来越复杂时&#xff0c;Vuex 就成为了救星&#xff0c;提供了一个集中式的存储来管理所有的组件状态&#xff0c;并且保证状态以一…

Linux系统之kill命令的基本使用

Linux系统之kill命令的基本使用 一、kill命令介绍1. kill命令简介2. kill命令的使用场景3. kill命令使用注意事项 二、kill命令的使用帮助1. 查看kill命令帮助信息2. kill命令帮助解释 三、kill常用的信号1. 列出所有的信号2.kill常用的信号 四、kill命令的基本使用1. 运行一个…

HTML之拜年/跨年APP(改进版)

目录&#xff1a; 一&#xff1a;目录 二&#xff1a;效果 三&#xff1a;页面分析/开发逻辑 1.页面详细分析&#xff1a; 2.开发逻辑&#xff1a; 四&#xff1a;完整代码&#xff08;不多废话&#xff09; index.html部分 app.json部分 二&#xff1a;效果 三&#xff1a;页面…

深入探索Python人脸识别技术:从原理到实践

一、引言在当今数字化时代,人脸识别技术已然成为了计算机视觉领域的璀璨明星,广泛且深入地融入到我们生活的各个角落。从门禁系统的安全守护,到金融支付的便捷认证,再到安防监控的敏锐洞察,它的身影无处不在,以其高效、精准的特性,极大地提升了我们生活的便利性与安全性…

JupyterLab 安装以及部分相关配置

安装 JupyterLab pip install jupyter启动 JupyterLab jupyter lab [--port <指定的端口号>] [--no-browser] # --port 指定端口 # --no-browser 启动时不打开浏览器安装中文 首先安装中文包 pip install jupyterlab-language-pack-zh-CN安装完成后重启 JupyterLab 选…

mac m1下载maven安装并配置环境变量

下载地址&#xff1a;Download Apache Maven – Maven 解压到一个没有中文和空格的文件夹 输入pwd查看安装路径 输入cd返回根目录再输入 code .zshrc 若显示 command not found: code你可以通过以下步骤来安装和配置 code 命令&#xff1a; 1. 确保你已经安装了 Visual Studio…

【环境搭建】Metersphere v2.x 容器部署教程踩坑总结

前言 Metersphere部署过程中遇到的问题有点多&#xff0c;原因是其容器的架构蛮复杂的&#xff0c;比较容易踩坑&#xff0c;所以记录一下。 介绍 MeterSphere 是开源持续测试平台&#xff0c;遵循 GPL v3 开源许可协议&#xff0c;涵盖测试管理、接口测试、UI 测试和性能测…

VSCode的配置与使用(C/C++)

从0开始教你在vscode调试一个C文件 一.首先是配置你的编译环境&#xff0c;添加到环境变量&#xff08;默认你是全新的电脑&#xff0c;没有安装vs2019之类的&#xff09; 原因&#xff1a;因为相比于vs2019&#xff0c;vscode只是个代码编辑器&#xff0c;相当于一个彩色的、…

QTableWidget的简单使用

1.最简单的表格示例&#xff1a; ui->tableWidget->setRowCount(2);// 设置行数ui->tableWidget->setColumnCount(3);// 设置列数&#xff0c;一定要放在设置行表头之前QStringList rowHeaderList;// 行表头rowHeaderList << QStringLiteral("姓名"…

七大排序算法

文章目录 排序的概念及引用1.插入排序2.希尔排序(缩小增量排序)3.选择排序4.堆排序5.冒泡排序6.快速排序7.归并排序8.代码排序部分的测试9.代码加效果大致测试时间&#xff08;仅供参考&#xff09; 排序的概念及引用 排序:将数据按照特定的规律排成递增或递减的操作 稳定性:…

前端常见标签

1. <!-- ! 快速生成标签 --> &#xff01;回车会立刻生成模板 2. <!-- CTRL / 生成注释--> 3. 文本标签 <!-- span 文本标签 --> 生成如下&#xff1a; 4. <!-- h1-h6标题标签 --> 大小依次递减 生成&…

PHP教育系统小程序

&#x1f310; 教育系统&#xff1a;全方位学习新体验&#xff0c;引领未来教育风尚 &#x1f680; 教育系统&#xff1a;创新平台&#xff0c;智慧启航 &#x1f4f1; 教育系统&#xff0c;一款深度融合科技与教育的创新平台&#xff0c;匠心独运地采用先进的ThinkPHP框架与U…

MySQL配置my.ini文件

my.ini文件中存储了数据库的文件地址&#xff0c;数据库数据存储地址以及登录密码等基础信息。在遇到忘记密码或者其他基础问题时&#xff0c;修改my.ini文件很方便。但是部分数据库版本默认不生成my.ini文件&#xff0c;需要自己进行配置。 1.停止数据库服务。在搜索框中输入…

【电视盒子】HI3798MV300刷机教程笔记/备份遥控码修复遥控器/ADB/线刷卡刷/电视盒子安装第三方应用软件

心血来潮&#xff0c;看到电视机顶盒满天飞的广告&#xff0c;想改造一下家里的电视盒子&#xff0c;学一下网上的人刷机&#xff0c;但是一切都不知道怎么开始&#xff0c;虽然折腾了一天&#xff0c;以失败告终&#xff0c;还是做点刷机笔记。 0.我的机器 年少不会甄别&…

Java中的构造器

Java中的构造器详解 1. 什么是构造器 构造器&#xff08;Constructor&#xff09; 是一种特殊的方法&#xff0c;用于在创建对象时初始化对象的状态。构造器的名字必须与类名相同&#xff0c;且没有返回类型&#xff0c;连 void 也不能使用。 2. 构造器的特点 名称与类名相同…

Zabbix监控山特UPS电源:实现高效监控与告警

背景 近期&#xff0c;随着机房迁移的进行&#xff0c;为了提升电力保障并确保设备的持续运行&#xff0c;我们在原有基础上新增了多台山特UPS电源作为备用电源。这些UPS电源的主要作用是在电力中断时为机房设备提供足够的电力支持&#xff0c;确保设备有充足的时间进行正常关…

计算机系统原理:一些断言

0 虚拟机和解释器 在Java中&#xff0c;JVM既充当了一个虚拟机的角色&#xff0c;也包含了用于执行字节码的解释器。同样地&#xff0c;Python的CPython实现也是先将源代码编译成字节码&#xff0c;然后由Python虚拟机执行。 1 从源代码中提取token的过程就是词法分析 词法分…

【正则表达式】从0开始学习正则表达式

正则表达式&#xff08;英语&#xff1a;Regular Expression&#xff0c;在代码中常简写为regex、regexp或RE&#xff09; 一、推荐学习网站 正则表达式 – 语法 | 菜鸟教程 正则表达式30分钟入门教程 | 菜鸟教程 编程胶囊-打造学习编程的最好系统 二、必知必记 2.1 元字符…

mysql之基本常用的语法

mysql之基本常用的语法 1.增加数据2.删除数据3.更新/修改数据4.查询数据 4.1.where子句4.2.order by4.3.limit与offset4.4.分组与having4.5.连接 5.创建表 1.增加数据 insert into 1.指定列插入 语法&#xff1a;insert into table_name(列名1,列名2,....,列名n) values (值…