PaDiM 无监督异常检测和定位-论文和源码阅读

news2024/11/26 15:41:42

目录

1. 论文

1.1 检测效果

1.2 框架 

1.2.1 特征提取embedding extraction

1.2.2 正样本学习Learning of the normality

1.2.3 计算异常图 inference: computation of the anomaly map

2. 源码

2.1 dataset

2.2 model

2.3 提取特征


1. 论文

https://arxiv.org/abs/2011.08785

思路:数据特征的分布被假定为一个多元高斯分布,异常值通常在多元高斯分布中表现为远离数据集的中心(均值向量)的数据点。协方差矩阵可以描述各个特征之间的相关性和离散程度。通过计算数据点相对于协方差矩阵的马氏距离,可以识别潜在的异常点。 

1.1 检测效果

 

SPADE精度不错,但是推理会非常慢,因为算法是基于KNN,测试的时候要访问整个训练正常数据集的embedding vectors。

1.2 框架 

步骤如下: 

1.2.1 特征提取embedding extraction

将与此图像块相对应的三个激活图区域特征concat在一起,组成一个特征向量(这样向量就包含了多尺度信息)。N张图,就有N个特征向量。组成X_ij特征空间,该过程不需要训练,直接用预训练权重比如resnet18、resnet50;

(1) infer

layer1: batch infer -> (b,256,56,56) ->cat->(img_num,256,56,56)

layer2: batch infer -> (b,512,28,28) ->cat->(img_num,512,28,28)

layer3: batch infer -> (b,1024,14,14) ->cat->(img_num,1024,14,14)

全部cat在一起, 得到embedding_vectors (img_num,1792,56,56).

(2) 随机选择d个特征

embedding_vectors有56*56个特征块,每个块有1792长度的特征,随机选d(550)个特征。

embedding_vectors.shape -> (img_num,550,56,56).

1.2.2 正样本学习Learning of the normality

为了求整个正样本数据分布情况,作者假设数据是符合多元高斯分布,然后求取此分布的参数: 均值mean和协方差covariance。

(1)均值:特征均值是某一特征(或变量)在数据集中的平均值。它是所有样本中某一特征的数值之和除以样本数量。特征均值用来表示数据在这一特征上的中心趋势。

B, C, H, W = embedding_vectors.size()
embedding_vectors = embedding_vectors.view(B, C, H * W)  # (img_num,550,56,56) -> (img_num,550,3136). 3136块
mean = torch.mean(embedding_vectors, dim=0).numpy()  # (550,3136). 整个数据集的均值

(2)协方差:协方差反映了多个变量之间的相关性以及它们与各自均值的偏离程度,如果一个变量的值在均值附近波动较大(方差大),那么它对协方差的贡献也会较大。

(3)协方差矩阵:有p个特征,协方差矩阵是一个p x p的矩阵,其中每个元素(i, j)表示特征i和特征j之间的协方差。协方差矩阵通常用Σ来表示。 协方差矩阵的对角线上的元素是各个特征的方差(方差是协方差自己和自己的情况),而非对角线上的元素是不同特征之间的协方差。协方差计算公式如下。

x_{ij}^k是第k个图像的图像块ij的特征向量, u_{ij}是整个正样本数据集的图像块ij位置上的特征均值。\epsilon I是正常项。每个块都有自己的协方差矩阵。

1.2.3 计算异常图 inference: computation of the anomaly map

基于前面求得的正常样本均值mean和协方差矩阵covariance参数,求图像块特征的马氏距离,此距离作为每个块的异常分数。

其中x_{ij}是测试图像块的patch embedding. μ和∑是前面正常样本求得的均值和协方差。T是转置,-1是求逆。

2. 源码

https://github.com/xiahaifeng1995/PaDiM-Anomaly-Detection-Localization-master

2.1 dataset

没有数据增强,只有个中心裁剪,因为作者提出的方法没有训练过程,直接使用预训练模型,提取激活层的特征向量。

import os
# import tarfile
from PIL import Image
from tqdm import tqdm
# import urllib.request

import torch
from torch.utils.data import Dataset
from torchvision import transforms as T


# URL = 'ftp://guest:GU.205dldo@ftp.softronics.ch/mvtec_anomaly_detection/mvtec_anomaly_detection.tar.xz'
CLASS_NAMES = ['bottle', 'cable', 'capsule', 'carpet', 'grid',
               'hazelnut', 'leather', 'metal_nut', 'pill', 'screw',
               'tile', 'toothbrush', 'transistor', 'wood', 'zipper']

# CLASS_NAMES = ['len_base']


class MVTecDataset(Dataset):
    def __init__(self, dataset_path='D:/dataset/mvtec_anomaly_detection', class_name='bottle', is_train=True,
                 resize=256, cropsize=224):
        assert class_name in CLASS_NAMES, 'class_name: {}, should be in {}'.format(class_name, CLASS_NAMES)
        self.dataset_path = dataset_path
        self.class_name = class_name  # 'bottle', 'cable' or 'capsule' et al.
        self.is_train = is_train  # bool.
        self.resize = resize  # 256
        self.cropsize = cropsize  # 224  CenterCrop

        # load dataset: x:整个数据集的图片路径,对应的label(0/1),对应的缺陷mask图像路径(good类别使用None)。
        self.x, self.y, self.mask = self.load_dataset_folder() 

        # set transforms
        self.transform_x = T.Compose([T.Resize(resize, Image.ANTIALIAS),
                                      T.CenterCrop(cropsize),
                                      T.ToTensor(),
                                      T.Normalize(mean=[0.485, 0.456, 0.406],
                                                  std=[0.229, 0.224, 0.225])])
        self.transform_mask = T.Compose([T.Resize(resize, Image.NEAREST),  # 注意mask
                                         T.CenterCrop(cropsize),
                                         T.ToTensor()])

    def __getitem__(self, idx):
        x, y, mask = self.x[idx], self.y[idx], self.mask[idx]  # 获取路径

        x = Image.open(x).convert('RGB')
        x = self.transform_x(x)

        if y == 0:  # good图像的mask路径是None,所以这里生成全0缺陷mask
            mask = torch.zeros([1, self.cropsize, self.cropsize]) 
        else:
            mask = Image.open(mask)
            mask = self.transform_mask(mask)

        return x, y, mask

    def __len__(self):
        return len(self.x)

    def load_dataset_folder(self):
        phase = 'train' if self.is_train else 'test'
        x, y, mask = [], [], []  # x:整个数据集的图片路径,对应的label(0/1),对应的缺陷mask图像路径(good类别使用None)。

        img_dir = os.path.join(self.dataset_path, self.class_name, phase)
        gt_dir = os.path.join(self.dataset_path, self.class_name, 'ground_truth')

        img_types = sorted(os.listdir(img_dir))  # train文件夹下只有good文件夹
        for img_type in img_types:  # 遍历不同的文件夹,比如train目录下只有good文件夹。test目录下有各种缺陷文件夹。

            # load images
            img_type_dir = os.path.join(img_dir, img_type)
            if not os.path.isdir(img_type_dir):
                continue
            img_fpath_list = sorted([os.path.join(img_type_dir, f)
                                     for f in os.listdir(img_type_dir)
                                     if f.endswith('.png')])
            x.extend(img_fpath_list)

            # load gt labels。二分类问题:good or not. 
            if img_type == 'good':
                y.extend([0] * len(img_fpath_list))  # 如果是good类别,则当前所有图像label都0
                mask.extend([None] * len(img_fpath_list))  # good类别没有缺陷mask
            else:
                y.extend([1] * len(img_fpath_list))  # 不是good,则当前所有图像label都是1
                gt_type_dir = os.path.join(gt_dir, img_type)
                img_fname_list = [os.path.splitext(os.path.basename(f))[0] for f in img_fpath_list]
                gt_fpath_list = [os.path.join(gt_type_dir, img_fname + '_mask.png')
                                 for img_fname in img_fname_list]
                mask.extend(gt_fpath_list)  # 并保存对应的mask图像路径

        assert len(x) == len(y), 'number of x and y should be same'

        return list(x), list(y), list(mask)

2.2 model

from torchvision.models import wide_resnet50_2, resnet18

def main():

    args = parse_args()

    # 1, load model
    if args.arch == 'resnet18':
        model = resnet18(pretrained=True, progress=True)
        t_d = 448
        d = 100
    elif args.arch == 'wide_resnet50_2':
        model = wide_resnet50_2(pretrained=True, progress=True)
        t_d = 1792  #
        d = 550
    model.to(device)
    model.eval()

    # 这一行将Python内置的random模块的随机种子设置为1024。设置种子可以确保在相同种子的情况下,使用该模块进行的任何随机操作都会生成相同的随机数序列。通常用于实现可重现性。
    random.seed(1024)
    # 这一行将PyTorch的随机数生成器的种子设置为1024。这用于确保在PyTorch内进行的任何随机操作(例如神经网络权重的初始化)在相同种子下生成相同的结果。
    torch.manual_seed(1024)
    if use_cuda:
        # 这一行设置PyTorch的CUDA(GPU)随机数生成器的种子。这将确保在GPU上进行的随机操作在使用种子1024时也能生成可重现的结果。
        torch.cuda.manual_seed_all(1024)
    idx = torch.tensor(sample(range(0, t_d), d))  # 在范围[0,1792]范围内随机生成550个整数。

    # set model's intermediate outputs。 只使用中间3个layer输出,第四个layer输出没有使用
    outputs = []

    # 提取layer1,layer2,layer3层输出结果。
    def hook(module, input, output):
        outputs.append(output)
    model.layer1[-1].register_forward_hook(hook)  # 每次前向都会调用一次hook,然后output会保持到outputs中。
    model.layer2[-1].register_forward_hook(hook)
    model.layer3[-1].register_forward_hook(hook)

2.3 提取特征

没有训练,则不需要定义loss函数、optimizer等等。直接dataset取数据,infer模型推理,获取中间的三个layer输出结果,然后提取特征向量的均值和协方差。

def main():

    # 创建特征pickle文件保存路径,其实就是存放[mean, cov],每个数据集都有一个pickle文件。
    os.makedirs(os.path.join(args.save_path, 'temp_%s' % args.arch), exist_ok=True)
    fig, ax = plt.subplots(1, 2, figsize=(20, 10))  # 一行两列fig
    fig_img_rocauc = ax[0]  # 第0列fig
    fig_pixel_rocauc = ax[1]  # 第1列fig

    total_roc_auc = []  # test auc list. 不同class的auc
    total_pixel_roc_auc = []  #

    for class_name in mvtec.CLASS_NAMES:  # 遍历不同的数据集
        # 2, dataset
        train_dataset = mvtec.MVTecDataset(args.data_path, class_name=class_name, is_train=True)
        train_dataloader = DataLoader(train_dataset, batch_size=32, pin_memory=True)
        test_dataset = mvtec.MVTecDataset(args.data_path, class_name=class_name, is_train=False)
        test_dataloader = DataLoader(test_dataset, batch_size=32, pin_memory=True)
        # layer1:所有数据集layer1输出向量,每个向量的shape是[b,256,56,56]
        # layer2: 所有数据集layer2输出向量,每个向量的shape是[b,512,28,28]
        # layer3: 所有数据集layer3输出向量,每个向量的shape是[b,1024,14,14]
        train_outputs = OrderedDict([('layer1', []), ('layer2', []), ('layer3', [])])
        test_outputs = OrderedDict([('layer1', []), ('layer2', []), ('layer3', [])])
        # 3, extract train set features (pickle文件保存)
        train_feature_filepath = os.path.join(args.save_path, 'temp_%s' % args.arch, 'train_%s.pkl' % class_name)
        if not os.path.exists(train_feature_filepath):  # 不存在此数据集的特征pickle文件,则进行特征提取
            for (x, _, _) in tqdm(train_dataloader, '| feature extraction | train | %s |' % class_name):
                # model prediction
                with torch.no_grad():  # 没有训练,x(b,32,224,224) to cuda,然后直接 batch infer
                    _ = model(x.to(device))  # 没有使用返回结果,因为前面定义了hook保存了中间结果到outputs
                # get intermediate layer outputs
                for k, v in zip(train_outputs.keys(), outputs):  # 保存3个cuda tensor到字典中
                    train_outputs[k].append(v.cpu().detach())
                # initialize hook outputs
                outputs = []  # 置空,迭代赋值
            for k, v in train_outputs.items():
                train_outputs[k] = torch.cat(v, 0)  # list of tensor(b,256,56,56). -> (img_num,256,56,56)

            # Embedding concat
            embedding_vectors = train_outputs['layer1']
            for layer_name in ['layer2', 'layer3']:
                embedding_vectors = embedding_concat(embedding_vectors, train_outputs[layer_name])

            # randomly select d dimension
            embedding_vectors = torch.index_select(embedding_vectors, 1, idx)  # 在范围[0,1792]范围内随机生成550个整数。

            # calculate multivariate Gaussian distribution
            B, C, H, W = embedding_vectors.size()
            embedding_vectors = embedding_vectors.view(B, C, H * W)  # (img_num,550,56,56) -> (img_num,550,3136). 3136块
            mean = torch.mean(embedding_vectors, dim=0).numpy()  # (550,3136). 整个数据集的均值
            cov = torch.zeros(C, C, H * W).numpy()  # (550,550,3136)协方差矩阵
            I = np.identity(n=C)  # (550,550).单位矩阵
            for i in range(H * W):  # 依次求每个块的协方差矩阵(550,550)。每个块都有自己的协方差矩阵。
                # cov[:, :, i] = LedoitWolf().fit(embedding_vectors[:, :, i].numpy()).covariance_
                # np.cov: covariance matrix
                # rowvar=False: each column represents a variable(特征、变量), while the rows contain observations(观察,样本).
                cov[:, :, i] = np.cov(embedding_vectors[:, :, i].numpy(), rowvar=False) + 0.01 * I
            # save learned distribution
            train_outputs = [mean, cov]  # list of numpy. mean.shape=(550,3136). cov.shape=(550,550,3136)
            with open(train_feature_filepath, 'wb') as f:
                pickle.dump(train_outputs, f)
        else:  # 已存在,则直接载入。
            print('load train set feature from: %s' % train_feature_filepath)
            with open(train_feature_filepath, 'rb') as f:
                train_outputs = pickle.load(f)

test

        gt_list = []
        gt_mask_list = []
        test_imgs = []

        # 4, extract test set features
        for (x, y, mask) in tqdm(test_dataloader, '| feature extraction | test | %s |' % class_name):
            test_imgs.extend(x.cpu().detach().numpy())
            gt_list.extend(y.cpu().detach().numpy())
            gt_mask_list.extend(mask.cpu().detach().numpy())
            # model prediction
            with torch.no_grad():
                _ = model(x.to(device))  # x to cuda. x.shape=(32,3,224,224)
            # get intermediate layer outputs
            for k, v in zip(test_outputs.keys(), outputs):
                test_outputs[k].append(v.cpu().detach())
            # initialize hook outputs
            outputs = []
        for k, v in test_outputs.items():
            test_outputs[k] = torch.cat(v, 0)
        
        # Embedding concat. 将不同层的输出cat在一起
        # layer1(img_num,256,56,56),layer2(img_num,512,28,28),layer3(img_num,1024,14,14)
        embedding_vectors = test_outputs['layer1']
        for layer_name in ['layer2', 'layer3']:
            embedding_vectors = embedding_concat(embedding_vectors, test_outputs[layer_name])

        # randomly select d dimension. embedding_vectors.shape=torch.Size([img_num, 1792, 56, 56])
        # 注意idx,要和前面train一样。
        embedding_vectors = torch.index_select(embedding_vectors, 1, idx)  # (img_num,1792,56,56)->(img_num,550,56,56)
        
        # 5. calculate distance matrix
        B, C, H, W = embedding_vectors.size()
        embedding_vectors = embedding_vectors.view(B, C, H * W).numpy()  # (img_num,550,56,56)->(img_num,550,56*56)
        dist_list = []
        for i in range(H * W):  # 遍历每个patch
            mean = train_outputs[0][:, i]  # train_outputs=[mean(550,3136), cov(550,550,3136)]. 第i块均值
            conv_inv = np.linalg.inv(train_outputs[1][:, :, i])  # 第i块协方差矩阵的逆矩阵。(550,550)

            # embedding_vectors.shape(img_num,550,3136). sample.shape(550,3136)
            # 特征块 sample[:, i].shape(550). 均值mean.shape(550). 协方差矩阵的逆矩阵conv_inv(550,550)
            # dist. list of dis. len=(img_num). 每张图像的每个patch有一个距离值. scipy.spatial.distance
            dist = [mahalanobis(sample[:, i], mean, conv_inv) for sample in embedding_vectors]  # 遍历测试图像,马氏距离
            dist_list.append(dist)  # {0: list(img_num), 1: list(img_num), patch_idx: list(img_num)}

        dist_list = np.array(dist_list).transpose(1, 0).reshape(B, H, W)  # (patch_num,image_num)->(83,3136)->(83,56,56)

        # 6. upsample distance map.
        dist_list = torch.tensor(dist_list)
        score_map = F.interpolate(dist_list.unsqueeze(1), size=x.size(2), mode='bilinear',
                                  align_corners=False).squeeze().numpy()  # (83,56,56)->(83,224,224)
        
        # 7. apply gaussian smoothing on the score map. 将patch score平滑成image score.
        for i in range(score_map.shape[0]):  # each test image. scipy.ndimage
            score_map[i] = gaussian_filter(score_map[i], sigma=4)  # (224,224)
        
        # Normalization. val->(0,1)
        max_score = score_map.max()
        min_score = score_map.min()
        scores = (score_map - min_score) / (max_score - min_score)  # (img_num,224,224)
        
        # 8. calculate image-level ROC AUC score. 图像级别的异常分数
        img_scores = scores.reshape(scores.shape[0], -1).max(axis=1)  # (img_num,224,224) -> (img_num,224*224) ->(img_num)
        gt_list = np.asarray(gt_list)  # (img_num,)
        fpr, tpr, _ = roc_curve(gt_list, img_scores)  # sklearn.metrics. shape (img_num)
        img_roc_auc = roc_auc_score(gt_list, img_scores)  # auc.
        total_roc_auc.append(img_roc_auc)  # auc list
        print('image ROCAUC: %.3f' % (img_roc_auc))  # 当前数据集类别(bottle)的auc
        fig_img_rocauc.plot(fpr, tpr, label='%s img_ROCAUC: %.3f' % (class_name, img_roc_auc))
        
        # get optimal threshold:# 使用最大F值对应的那个阈值(同时考虑召回率和准确率)
        gt_mask = np.asarray(gt_mask_list)  # list of tensor. -> (img_num,1,224,224). scores.shape(img_num,224,224)
        # thresholds不同的阈值,返回不同的准确率和召回率
        precision, recall, thresholds = precision_recall_curve(gt_mask.flatten(), scores.flatten())  # sklearn.metrics
        a = 2 * precision * recall  # 不同的阈值下,求F值
        b = precision + recall  # a/b
        f1 = np.divide(a, b, out=np.zeros_like(a), where=b != 0)  # 防止除0操作
        threshold = thresholds[np.argmax(f1)]  # 使用最大F值对应的那个阈值。

        # calculate per-pixel level ROCAUC. (img_num,1,224,224). scores.shape(img_num,224,224)
        fpr, tpr, _ = roc_curve(gt_mask.flatten(), scores.flatten())
        per_pixel_rocauc = roc_auc_score(gt_mask.flatten(), scores.flatten())
        total_pixel_roc_auc.append(per_pixel_rocauc)
        print('pixel ROCAUC: %.3f' % (per_pixel_rocauc))

        fig_pixel_rocauc.plot(fpr, tpr, label='%s ROCAUC: %.3f' % (class_name, per_pixel_rocauc))
        save_dir = args.save_path + '/' + f'pictures_{args.arch}'
        os.makedirs(save_dir, exist_ok=True)
        # 原图list (3,224,224).
        plot_fig(test_imgs, scores, gt_mask_list, threshold, save_dir, class_name)

    print('Average ROCAUC: %.3f' % np.mean(total_roc_auc))
    fig_img_rocauc.title.set_text('Average image ROCAUC: %.3f' % np.mean(total_roc_auc))
    fig_img_rocauc.legend(loc="lower right")

    print('Average pixel ROCUAC: %.3f' % np.mean(total_pixel_roc_auc))
    fig_pixel_rocauc.title.set_text('Average pixel ROCAUC: %.3f' % np.mean(total_pixel_roc_auc))
    fig_pixel_rocauc.legend(loc="lower right")

    fig.tight_layout()
    fig.savefig(os.path.join(args.save_path, 'roc_curve.png'), dpi=100)

参考:GitHub - xiahaifeng1995/PaDiM-Anomaly-Detection-Localization-master: This is an unofficial implementation of the paper “PaDiM: a Patch Distribution Modeling Framework for Anomaly Detection and Localization”.This is an unofficial implementation of the paper “PaDiM: a Patch Distribution Modeling Framework for Anomaly Detection and Localization”. - GitHub - xiahaifeng1995/PaDiM-Anomaly-Detection-Localization-master: This is an unofficial implementation of the paper “PaDiM: a Patch Distribution Modeling Framework for Anomaly Detection and Localization”.icon-default.png?t=N7T8https://github.com/xiahaifeng1995/PaDiM-Anomaly-Detection-Localization-master

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

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

相关文章

Redis 6.0 新功能

1-支持 ACL 1.1-ACL 简介 官网:https://redis.io/topics/acl Redis ACL 是访问控制列表(Access Control List)的缩写,该功能允许根据可以执行的命令和可以访问的键来限制某些连接。 Redis 5 版本之前,Redis 安全规则只有密码控制&#xf…

Python元编程详细教程

嗨喽~大家好呀,这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 简单定义“元编程是一种编写计算机程序的技术,这些程序可以将自己看做数据, 因此你可以在运行时对它进行内审、生成和/或修改”&#xff…

SpringCloud篇---第三篇

系列文章目录 文章目录 系列文章目录一、负载平衡的意义什么?二、什么是Hystrix?它如何实现容错?三、什么是Hystrix断路器?我们需要它吗?一、负载平衡的意义什么? 在计算中,负载平衡可以改善跨计算机,计算机集群,网络链接,中央处理单元或磁盘驱动器等多 种计算资源的…

超高真空变温台的真空压力和气氛精密控制解决方案

摘要:针对目前国内外显微镜探针冷热台普遍缺乏真空压力和气氛环境精密控制装置这一问题,本文提出了解决方案。解决方案采用了电动针阀快速调节进气和排气流量的动态平衡法实现0.1~1000Torr范围的真空压力精密控制,采用了气体质量流量计实现多…

配置OpenCV

Open CV中包含很多图像处理的算法,因此学会正确使用Open CV也是人脸识别研究的一项重要工作。在 VS2017中应用Open CV,需要进行手动配置,下面给出在VS2017中配置Open CV的详细步骤。 1.下载并安装OpenCV3.4.1与VS2017的软件。 2.配置Open CV环…

纠结蓝桥杯参加嵌入式还是单片机组?

纠结蓝桥杯参加嵌入式还是单片机组? 单片机包含于嵌入式,嵌入式不只是单片机。. 你只有浅浅的的单片机基础,只能报单片机了。最近很多小伙伴找我,说想要一些单片机资料,然后我根据自己从业十年经验,熬夜搞了几个通宵…

Selenium安装WebDriver Chrome驱动(含 116/117/118/119/120/)

1、确认浏览器的版本 在浏览器的地址栏,输入chrome://version/,回车后即可查看到对应版本 2、找到对应的chromedriver版本 2.1 114及之前的版本可以通过点击下载chromedriver,根据版本号(只看大版本)下载对应文件 2.2 116版本…

1. eulerAngles函数

对线性代数库Eigen3中eulerAngles函数的理解_qingtian11112的博客-CSDN博客作用: 将旋转矩阵转换为欧拉角 Vector3f ea mat.eulerAngles(2, 0, 2); // 等价于 mat AngleAxisf(ea[0], Vector3f::UnitZ())* AngleAxisf(ea[1], Vector3f::UnitX())* AngleAxisf(ea[…

《Pytorch新手入门》第二节-动手搭建神经网络

《Pytorch新手入门》第二节-动手搭建神经网络 一、神经网络介绍二、使用torch.nn搭建神经网络2.1 定义网络2.2 torch.autograd.Variable2.3 损失函数与反向传播2.4 优化器torch.optim 三、实战-实现图像分类(CIFAR-10数据集)3.1 CIFAR-10数据集加载与预处理3.2 定义网络结构3.3…

99/年服务器测评,续费也99一年,真香,值得入手

每个帐号都有开通名额,开通99元一年,配置2核2G 40G 3M(不限流量),续费也是99一年 开通之后就可以直接续费,免除后顾之忧(一年一年的去续费,直接选择5年价格不太对) 地址:https://mur…

成为java高手的八个条件

成为java高手的八个条件 1、扎实的基础 数据结构、离散数学、编译原理,这些是所有计算机科学的基础,如果不掌握它们,很难写出高水平的程序。程序人人都会写,但当你发现写到一定程度很难再提高的时候,就应该想想是不…

投资者如何保障个人利益?行业律师与欧科云链专家给出建议

香港作为全球加速拥抱Web3变革的引领之地,规定自今年6月起在香港经营虚拟资产服务业务需申领牌照。蜂拥而至的Web3创业公司,伺机而动的加密货币交易所,以及跃跃欲试的行业从业者,都让这座金融之都热闹非凡。但近期伴随JPEX诈骗案等…

HarmonyOS UI 开发

引言 HarmonyOS 提供了强大的 UI 开发工具和组件,使开发者能够创建吸引人的用户界面。本章将详细介绍在 HarmonyOS 中应用 JS、CSS、HTML,HarmonyOS 的 UI 组件以及如何自定义 UI 组件。 目录 JS、CSS、HTML 在 HarmonyOS 中的应用HarmonyOS 的 UI 组…

使用 Sealos 将 ChatGLM3 接入 FastGPT,打造完全私有化 AI 客服

FastGPT 是一款专为客服问答场景而定制的开箱即用的 AI 知识库问答系统。该系统具备可视化工作流功能,允许用户灵活地设计复杂的问答流程,几乎能满足各种客服需求。 在国内市场环境下,离线部署对于企业客户尤为重要。由于数据安全和隐私保护…

springboot--多环境配置快速切换开发、测试、生产环境

多环境配置快速切换开发、测试、生产环境 前言1、使用1.1指定环境Profile({"dev","test"})Spring Profiles 提供一个隔离配置的方式,使其仅在特定环境生效 任何Component,Configuration或ConfigurationProperties 可以使用Profile标记&#xff…

11.2树的高度,表达式树,非递归遍历,层序遍历,奇偶树

课上 前序,根左右 中序,左根右 若前序中序相同,则树都没有左节点 求树的高度 表达式树 中缀表达式树 主要考虑括号问题 这个就是考虑递归底层,要结束时的情形;以及根节点的情形; 由于表达式树是满树&…

数据库实验:SQL的数据定义与单表查询

目录 实验目的实验内容实验要求实验过程实验步骤实例代码结果示意 数据库的实验,对关系型数据库MySQL进行一些实际的操作 实验目的 (1) 掌握DBMS的数据定义功能 (2) 掌握SQL语言的数据定义语句 (3) 掌握RDBMS的数据单表查询功能 (4) 掌握SQL语言的数据单表查询语句…

UN38.3认证和MSDS有什么区别? 锂电池UN38.3检测标准要求

通常很多人做电池检测的时候,将UN38.3和MSDS混淆或者认为是同一个认证,这对办理检测有非常大的影响。MSDS是化学安全技术说明书,是一份对锂电池成分以及应急处理的文件,UN38.3是对锂电池的一份安全检测。因为锂电池要空运&#xf…

使用 javascript 在 n*m 网格中演示 BFS 广度优先搜索算法求最短路径

完整代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><style type"text/css">#box1 table{border: 0px;border-collapse: collapse;cursor: poi…