VGG分类实战:猫狗分类

关于数据集

数据集选择的是Kaggle上的Cat and Dog,猫狗图片数量上达到了上万张。你可以通过这里进入Kaggle下载数据集Cat and Dog | Kaggle。

在我的Github仓库当中也放了猫狗图片各666张。

VGG网络

VGG的主要特点是使用了一系列具有相同尺寸 3x3 大小的卷积核进行多次卷积操作。这种结构的一个优势是可以堆叠更多的卷积层,使得网络能够学习到更复杂的特征。

详情请看此篇VGG16模型详解_夏天是冰红茶的博客-CSDN博客。

今天让我们来探究一下在2014年的ImageNet图像分类竞赛中取得显著成绩的VGG模型效果如何。

# net.py

import torch
import torchvision
import torch.nn as nn
import torchsummary

from torch.hub import load_state_dict_from_url
# model = torchvision.models.vgg16()

model_urls = {
    "vgg16": "https://download.pytorch.org/models/vgg16-397923af.pth",
    "vgg19": "https://download.pytorch.org/models/vgg19-dcbb9e9d.pth"
}
cfgs = {
    "vgg16": [64, 64, "M", 128, 128, "M", 256, 256, 256, "M", 512, 512, 512, "M", 512, 512, 512, "M"],
    "vgg19": [64, 64, "M", 128, 128, "M", 256, 256, 256, 256, "M", 512, 512, 512, 512, "M", 512, 512, 512, 512, "M"],
}

class VGG(nn.Module):
    def __init__(self, features, num_classes = 1000, init_weights= True, dropout = 0.5):
        super(VGG,self).__init__()
        self.features = features
        self.avgpool = nn.AdaptiveAvgPool2d((7, 7))
        self.classifier = nn.Sequential(
            nn.Linear(512 * 7 * 7, 4096),
            nn.ReLU(True),
            nn.Dropout(p=dropout),
            nn.Linear(4096, 4096),
            nn.ReLU(True),
            nn.Dropout(p=dropout),
            nn.Linear(4096, num_classes),
        )
        if init_weights:
            for m in self.modules():
                if isinstance(m, nn.Conv2d):
                    nn.init.kaiming_normal_(m.weight, mode="fan_out", nonlinearity="relu")
                    if m.bias is not None:
                        nn.init.constant_(m.bias, 0)
                elif isinstance(m, nn.BatchNorm2d):
                    nn.init.constant_(m.weight, 1)
                    nn.init.constant_(m.bias, 0)
                elif isinstance(m, nn.Linear):
                    nn.init.normal_(m.weight, 0, 0.01)
                    nn.init.constant_(m.bias, 0)

    def forward(self, x):
        x = self.features(x)
        x = self.avgpool(x)
        x = torch.flatten(x, 1)
        x = self.classifier(x)
        return x


def make_layers_with_vgg(cfg, batch_norm = False):
    layers = []
    in_channels = 3
    for v in cfg:
        if v == "M":
            layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
        else:
            conv2d = nn.Conv2d(in_channels, v, kernel_size=(3,3), padding=1)
            if batch_norm:
                layers += [conv2d, nn.BatchNorm2d(v), nn.ReLU(inplace=True)]
            else:
                layers += [conv2d, nn.ReLU(inplace=True)]
            in_channels = v
    return nn.Sequential(*layers)

def vgg(mode='vgg16',pretrained=False, progress=True, num_classes=2):
    model = VGG(make_layers_with_vgg(cfgs[mode]))
    if pretrained:
        state_dict = load_state_dict_from_url(model_urls[mode], model_dir='./model', progress=progress)#预训练模型地址
        model.load_state_dict(state_dict)
    if num_classes != 1000:
        model.classifier = nn.Sequential(
            nn.Linear(512 * 7 * 7, 4096),
            nn.ReLU(True),
            nn.Dropout(p=0.5),
            nn.Linear(4096, 4096),
            nn.ReLU(True),
            nn.Dropout(p=0.5),
            nn.Linear(4096, num_classes),
        )
    return model


if __name__=='__main__':
    in_data = torch.ones(2, 3, 224, 224)
    net = vgg(mode='vgg16', pretrained=False, progress=True, num_classes=2)  # 使用默认的 VGG-16 架构
    # net = vgg(mode='vgg19', pretrained=False, progress=True, num_classes=2)  # 使用 VGG-19 架构
    out = net(in_data)
    print(out)
    torchsummary.summary(net, input_size=(3, 224, 224))

与前面纯手打的VGG16网络不同,这里还添加了VGG19网络结构以及预训练权重。

import torchvision

model = torchvision.models.vgg16()

你可以通过这里来查看VGG16的模型结构与预训练权重的url,上面也是从pytorch实现的网络中更改过的,所有你也可以去pytorch的官网查找。

创建分类数据列表

将指定路径中的图像文件的类别和类型信息写入到名为 class_data.txt 的文件中,以便后续用于分类任务或其他需要这些信息的应用。

清华源安装

pip install pyzjr==1.1.1 --user -i https://pypi.tuna.tsinghua.edu.cn/simple

猫狗分类任务的数据列表的脚本

# annotation_txt.py

import os
import pyzjr as pz

classes = ['cat', 'dog']
path = 'train'

if __name__ == '__main__':
    with open('class_data.txt', 'w') as txt_file:  # 打开文件,注意使用 'w' 模式
        file_list = [os.path.join(path, i) for i in os.listdir(path)]
        for data_path in file_list:
            types_name, _ = pz.getPhotopath(data_path, True)
            cls_id = classes.index(os.path.basename(data_path))
            for type_name in types_name:
                line = f"{str(cls_id)};{str(type_name)}"
                txt_file.write(line + '\n')  # 追加写入数据

txt文件大致内容如下:

0;D:/deeplearning/VGGnet/train/cat/cat000.jpg

0;D:/deeplearning/VGGnet/train/cat/cat001.jpg

0;D:/deeplearning/VGGnet/train/cat/cat002.jpg

......

1;D:/deeplearning/VGGnet/train/dog/dog198.jpg

1;D:/deeplearning/VGGnet/train/dog/dog199.jpg

1;D:/deeplearning/VGGnet/train/dog/dog200.jpg

由于我本人的笔记本类型不是很好,所以就仅仅各自取了200张进行一个测试。

文件批量重命名(可选)

才下载的数据,它是这样的:

import pyzjr as pz
import os
import shutil
# 原始图片所在路径、保存指定图片路径
image_folder_path = r"D:\pythonprojects\deeplabv3_pytorch\img"
save_image_folder_path = pz.CreateFolder(r"D:\pythonprojects\deeplabv3_pytorch\imgs")

newbasename = 'Crack'

if __name__=="__main__":
    imglist,allist=pz.getPhotopath(image_folder_path,debug=False)
    print(imglist)
    for i,file in enumerate(imglist):
        print(i,file)
        properties = pz.ImageAttribute(file)
        name, ext = os.path.splitext(properties['name'])
        # -----------------------------------------------
        # 格式可以在这里修改   i:03d ——> 001
        # 扩展名也可以自己定义,默认采用原本的ext(.png,.jpg这种)
        #
        newname = f"{newbasename}{i:03d}{ext}"
        #
        # -----------------------------------------------
        new_path = os.path.join(save_image_folder_path, newname)
        shutil.copy(file, new_path)
    print("文件批量重命名和保存完成")

只需要修改newbasename以及具体的格式即可,而扩展名我是默认使用的原本的ext,但要记住的是,修改扩展名时候要把“ . ”加上。

你也可以调用pyzjr.RenameFile进行批量化的重命名。

数据预处理与损失历史记录

这两个功能均在dataoperation.py文件当中,为深度学习模型的训练提供了一些辅助功能。可以在深度学习模型的训练过程中使用,以便更好地监控训练的进展和效果。

# dataoperation.py

import cv2
import numpy as np
import torch.utils.data as data
import matplotlib
import torch
matplotlib.use('Agg')
from matplotlib import pyplot as plt
import scipy.signal
from PIL import Image
from torch.utils.tensorboard import SummaryWriter
import os

def preprocess_input(x):
    x/=127.5
    x-=1.
    return x
def cvtColor(image):
    if len(np.shape(image))==3 and np.shape(image)[-2]==3:
        return image
    else:
        image=image.convert('RGB')
        return image


class DataGenerator(data.Dataset):
    def __init__(self,annotation_lines,inpt_shape,random=True):
        self.annotation_lines=annotation_lines
        self.input_shape=inpt_shape
        self.random=random

    def __len__(self):
        return len(self.annotation_lines)
    def __getitem__(self, index):
        annotation_path=self.annotation_lines[index].split(';')[1].split()[0]
        image=Image.open(annotation_path)
        image=self.get_random_data(image,self.input_shape,random=self.random)
        image=np.transpose(preprocess_input(np.array(image).astype(np.float32)),[2,0,1])
        y=int(self.annotation_lines[index].split(';')[0])
        return image,y
    def rand(self,a=0.,b=1.):
        return np.random.rand()*(b-a)+a

    def get_random_data(self,image,inpt_shape,jitter=.3,hue=.1,sat=1.5,val=1.5,random=True):

        image=cvtColor(image)
        iw,ih=image.size
        h,w=inpt_shape
        if not random:
            scale=min(w/iw,h/ih)
            nw=int(iw*scale)
            nh=int(ih*scale)
            dx=(w-nw)//2
            dy=(h-nh)//2

            image=image.resize((nw,nh),Image.BICUBIC)
            new_image=Image.new('RGB',(w,h),(128,128,128))

            new_image.paste(image,(dx,dy))
            image_data=np.array(new_image,np.float32)
            return image_data
        new_ar=w/h*self.rand(1-jitter,1+jitter)/self.rand(1-jitter,1+jitter)
        scale=self.rand(.75,1.25)
        if new_ar<1:
            nh=int(scale*h)
            nw=int(nh*new_ar)
        else:
            nw=int(scale*w)
            nh=int(nw/new_ar)
        image=image.resize((nw,nh),Image.BICUBIC)
        dx=int(self.rand(0,w-nw))
        dy=int(self.rand(0,h-nh))
        new_image=Image.new('RGB',(w,h),(128,128,128))
        new_image.paste(image,(dx,dy))
        image=new_image

        flip=self.rand()<.5
        if flip: image=image.transpose(Image.FLIP_LEFT_RIGHT)
        rotate=self.rand()<.5
        if rotate:
            angle=np.random.randint(-15,15)
            a,b=w/2,h/2
            M=cv2.getRotationMatrix2D((a,b),angle,1)
            image=cv2.warpAffine(np.array(image),M,(w,h),borderValue=[128,128,128])

        hue=self.rand(-hue,hue)
        sat=self.rand(1,sat) if self.rand()<.5 else 1/self.rand(1,sat)
        val=self.rand(1,val) if self.rand()<.5 else 1/self.rand(1,val)
        x=cv2.cvtColor(np.array(image,np.float32)/255,cv2.COLOR_RGB2HSV)#颜色空间转换
        x[..., 1] *= sat
        x[..., 2] *= val
        x[x[:, :, 0] > 360, 0] = 360
        x[:, :, 1:][x[:, :, 1:] > 1] = 1
        x[x < 0] = 0
        image_data=cv2.cvtColor(x,cv2.COLOR_HSV2RGB)*255
        return image_data


class LossHistory():
    def __init__(self, log_dir, model, input_shape):
        self.log_dir = log_dir
        self.losses = []
        self.val_loss = []

        os.makedirs(self.log_dir,True)
        self.writer = SummaryWriter(self.log_dir)
        try:
            dummy_input = torch.randn(2, 3, input_shape[0], input_shape[1])
            self.writer.add_graph(model, dummy_input)
        except:
            pass

    def append_loss(self, epoch, loss, val_loss):
        if not os.path.exists(self.log_dir):
            os.makedirs(self.log_dir)

        self.losses.append(loss)
        self.val_loss.append(val_loss)

        with open(os.path.join(self.log_dir, "epoch_loss.txt"), 'a') as f:
            f.write(str(loss))
            f.write("\n")
        with open(os.path.join(self.log_dir, "epoch_val_loss.txt"), 'a') as f:
            f.write(str(val_loss))
            f.write("\n")

        self.writer.add_scalar('loss', loss, epoch)
        self.writer.add_scalar('val_loss', val_loss, epoch)
        self.loss_plot()

    def loss_plot(self):
        iters = range(len(self.losses))

        plt.figure()
        # plt.plot(iters, self.losses, 'red', linewidth=2, label='train loss')
        # plt.plot(iters, self.val_loss, 'coral', linewidth=2, label='val loss')
        plt.plot(iters, [loss.item() for loss in self.losses], 'red', linewidth=2, label='train loss')
        plt.plot(iters, [loss.item() for loss in self.val_loss], 'coral', linewidth=2, label='val loss')
        try:
            if len(self.losses) < 25:
                num = 5
            else:
                num = 15

            plt.plot(iters, scipy.signal.savgol_filter(self.losses, num, 3), 'green', linestyle='--', linewidth=2,
                     label='smooth train loss')
            plt.plot(iters, scipy.signal.savgol_filter(self.val_loss, num, 3), '#8B4513', linestyle='--', linewidth=2,
                     label='smooth val loss')
        except:
            pass

        plt.grid(True)
        plt.xlabel('Epoch')
        plt.ylabel('Loss')
        plt.legend(loc="upper right")

        plt.savefig(os.path.join(self.log_dir, "epoch_loss.png"))

        plt.cla()
        plt.close("all")

训练主文件

import torch.nn as nn
from net import vgg
from torch.utils.data import DataLoader
from tqdm import tqdm
import datetime
from dataoperation import *

if __name__=="__main__":
    #---------------------------------#
    # Cuda       是否使用Cuda
    #            没有GPU可以设置成False
    #---------------------------------#
    Cuda = False
    # ---------------------------------#
    # 'vgg16' and  'vgg19'
    # ---------------------------------#
    Net = 'vgg16'
    # ---------------------------------#
    # 先运行annotation_txt脚本
    # ---------------------------------#
    annotation_path='class_data.txt'
    # ---------------------------------#
    # 输入图片尺寸
    # ---------------------------------#
    input_shape = [224, 224]
    # ---------------------------------#
    #  分类个数,比如这里只要猫和狗两类
    # ---------------------------------#
    num_classes = 2
    # -------------------------------------------------------#
    #   lr         模型的最大学习率
    #              当使用Adam优化器时建议设置  lr=5e-4
    #              当使用SGD优化器时建议设置   lr=7e-3
    # -------------------------------------------------------#
    lr = 0.0001
    # ---------------------------------#
    # 优化器选择 SGD 与 Adam
    # ---------------------------------#
    optimizer_type = "Adam"
    # ---------------------------------#
    # 验证集所占百分比
    # ---------------------------------#
    percentage = 0.2
    # ---------------------------------#
    # 训练轮次
    # ---------------------------------#
    epochs = 80
    # ---------------------------------#
    #   save_period 多少个epoch保存一次权值
    # ---------------------------------#
    save_period = 1
    # ------------------------------------------------------------------#
    #   save_dir        权值与日志文件保存的文件夹
    # ------------------------------------------------------------------#
    save_dir = 'log'

    if not os.path.exists(save_dir):
        os.makedirs(save_dir)
    time_str = datetime.datetime.strftime(datetime.datetime.now(), '%Y_%m_%d_%H_%M_%S')
    log_dir = os.path.join(save_dir, "loss_" + str(time_str))

    loss_history = LossHistory(log_dir=log_dir, model=Net, input_shape=input_shape)

    with open(annotation_path,'r') as f:
        lines=f.readlines()
    np.random.seed(10101)
    np.random.shuffle(lines)
    np.random.seed(None)

    num_val=int(len(lines) * percentage)
    num_train=len(lines) - num_val


    train_data=DataGenerator(lines[:num_train],input_shape,True)
    val_data=DataGenerator(lines[num_train:],input_shape,False)
    val_len=len(val_data)
    print(val_len)

    gen_train=DataLoader(train_data,batch_size=4)
    gen_test=DataLoader(val_data,batch_size=4)

    device=torch.device('cuda'if torch.cuda.is_available() and Cuda else "cpu")
    net=vgg(mode=Net, pretrained=True, progress=True, num_classes=num_classes)
    net.to(device)
    if optimizer_type == 'Adam':
        optim = torch.optim.Adam(net.parameters(), lr=lr)
    elif optimizer_type == 'SGD':
        optim = torch.optim.SGD(net.parameters(), lr=lr, momentum=0.9)
    else:
        raise ValueError("Unsupported optimizer type: {}".format(optimizer_type))

    sculer=torch.optim.lr_scheduler.StepLR(optim,step_size=1)


    for epoch in range(epochs):
        total_train=0
        for data in tqdm(gen_train, desc=f"Epoch{epoch + 1}/Train"):
            img,label=data
            with torch.no_grad():
                img =img.to(device)
                label=label.to(device)
            optim.zero_grad()
            output=net(img)
            train_loss=nn.CrossEntropyLoss()(output,label).to(device)
            train_loss.backward()
            optim.step()
            total_train+=train_loss

        sculer.step()
        total_test=0
        total_accuracy=0

        for data in tqdm(gen_test, desc=f"Epoch{epoch + 1}/Test"):
            img,label =data
            with torch.no_grad():
                img=img.to(device)
                label=label.to(device)
                optim.zero_grad()
                out=net(img)
                test_loss=nn.CrossEntropyLoss()(out,label).to(device)
                total_test+=test_loss
                accuracy=((out.argmax(1)==label).sum()).clone().detach().cpu().numpy()
                total_accuracy += accuracy
        print("训练集上的损失:{}".format(total_train))
        print("测试集上的损失:{}".format(total_test))
        print("测试集上的精度:{:.1%}".format(total_accuracy/val_len))
        loss_history.append_loss(epoch + 1, total_train, total_test)
        if (epoch+1) % save_period == 0:
            modepath = os.path.join(log_dir,"DogandCat{}.pth".format(epoch+1))
            torch.save(net.state_dict(),modepath)
        print("模型已保存")

设置相关参数:

  • Cuda: 是否使用GPU加速,默认为False
  • Net: 选择要使用的VGG网络版本,可以是 'vgg16''vgg19'
  • annotation_path: 数据集的注释文件路径,这是一个包含图像路径和标签的文本文件。
  • input_shape: 输入图像的尺寸。
  • num_classes: 分类的类别数量。
  • lr: 学习率。
  • optimizer_type: 选择优化器,可以是 'Adam''SGD'
  • percentage: 验证集所占百分比。
  • epochs: 训练轮次。
  • save_period: 多少个epoch保存一次模型权重。
  • save_dir: 模型权重和日志文件保存的目录。

接下来是进行数据准备将数据随机打乱并划分为训练集和验证集,创建训练集和验证集的数据生成器,然后实例化VGG模型,并根据选择的网络版本加载预训练权重,根据选择的优化器类型创建优化器,并设置学习率调度器,最后,每个epoch中计算训练集和验证集上的损失和精度,并记录到损失历史记录器中。

由于比较的费时间,这里我仅仅就进行了猫狗图片各自200张进行训练,主要是看看VGG的一个分类效果,所以就尽可能的快点。

模型预测

# predict.py

from torchvision import transforms
from PIL import Image
import matplotlib.pyplot as plt
import torch
import torch.nn.functional as F
from VGGnet.net import vgg

if __name__=="__main__":
    # ---------------------------------#
    # Cuda       是否使用Cuda
    #            没有GPU可以设置成False
    # ---------------------------------#
    Cuda = False
    # ---------------------------------#
    # 分类类型
    # ---------------------------------#
    num_classes = ['cat', 'dog']
    # ---------------------------------#
    # 'vgg16' and  'vgg19'
    # ---------------------------------#
    Netmode = 'vgg16'
    # ------------------------------------------------------------------------------#
    # detection_mode用于指定测试的模式:
    #
    # 'predict'           表示单张图片预测
    # 'dir_predict'       表示遍历文件夹进行检测并保存。默认遍历img文件夹,保存img_out文件夹
    # ------------------------------------------------------------------------------#
    detection_mode = "dir_predict"
    # -------------------------------------------------------#
    #   model_path指向log文件夹下的权值文件
    #   训练好后log文件夹下存在多个权值文件,选择验证集损失较低的即可。
    # -------------------------------------------------------#
    model_path = r"log\loss_2023_08_16_13_52_51\DogandCat30.pth"
    #-------------------------------------------------------------------------#
    #   dir_origin_path     指定了用于检测的图片的文件夹路径
    #   dir_save_path       指定了检测完图片的保存路径
    #
    #   dir_origin_path和dir_save_path仅在 detection_mode='dir_predict'时有效
    #-------------------------------------------------------------------------#
    dir_origin_path = "img/"
    dir_save_path   = "img_out/"

    device = torch.device("cuda" if torch.cuda.is_available() and Cuda else "cpu")

    model = vgg(mode=Netmode,num_classes=len(num_classes))
    model.load_state_dict(torch.load(model_path, map_location=device))
    model.to(device)
    model.eval()

    transform = transforms.Compose([
        transforms.Resize((224, 224)),
        transforms.ToTensor(),
        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
    ])


    def predict_single_image(image_path):
        image = Image.open(image_path)
        image = transform(image).unsqueeze(0).to(device)

        with torch.no_grad():
            model.eval()
            output = model(image)
            probabilities = F.softmax(output, dim=1)
            predicted_class = torch.argmax(probabilities).item()

        predicted_label = num_classes[predicted_class]
        predicted_prob = probabilities[0][predicted_class].item()
        print("Output tensor:", output)
        print("Probabilities tensor:", probabilities)
        print(f"Predicted class: {predicted_label}, Probability: {predicted_prob:.2f}")
        plt.imshow(Image.open(image_path))
        plt.title(f"Predicted class: {predicted_label}, Probability: {predicted_prob:.2f}")
        plt.axis('off')
        plt.show()


    def predict_images_in_directory(origin_path, save_path):
        import os
        os.makedirs(save_path, exist_ok=True)

        image_files = [f for f in os.listdir(origin_path) if f.lower().endswith(('.jpg', '.jpeg', '.png', '.gif'))]

        for image_file in image_files:
            image_path = os.path.join(origin_path, image_file)
            result_image_path = os.path.join(save_path, image_file)

            image = Image.open(image_path)
            image = transform(image).unsqueeze(0).to(device)

            with torch.no_grad():
                model.eval()
                output = model(image)
                probabilities = F.softmax(output, dim=1)
                predicted_class = torch.argmax(probabilities).item()

            predicted_label = num_classes[predicted_class]
            predicted_prob = probabilities[0][predicted_class].item()

            print("Predicted class:", predicted_label)
            print("Predicted probability:", predicted_prob)

            plt.imshow(Image.open(image_path))
            plt.title(f"Predicted class: {predicted_label}, Probability: {predicted_prob:.2f}")
            plt.axis('off')
            plt.savefig(result_image_path)
            # plt.show()

        print("Prediction and saving complete.")

    if detection_mode == "predict":
        while True:
            image_path = input('Input image filename (or "exit" to quit): ')
            if image_path.lower() == "exit":
                break
            predict_single_image(image_path)
    elif detection_mode == "dir_predict":
        predict_images_in_directory(dir_origin_path, dir_save_path)
    else:
        raise ValueError("Invalid detection_mode")

单张检测模式

 文件夹检测模式

资源链接

Auorui/VGG16-CatandDog: Explore the effectiveness of the VGG model, which achieved significant results in the ImageNet image classification competition in 2014, and use VGG for cat and dog classification (github.com)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据结构之动态内存管理机制

目录 数据结构之动态内存管理机制 占用块和空闲块 系统的内存管理 可利用空间表 分配存储空间的方式 空间分配与回收过程产生的问题 边界标识法管理动态内存 分配算法 回收算法 伙伴系统管理动态内存 可利用空间表中结点构成 分配算法 回收算法 总结 无用单元收…

PyTorch翻译官网教程-LANGUAGE MODELING WITH NN.TRANSFORMER AND TORCHTEXT

官网链接 Language Modeling with nn.Transformer and torchtext — PyTorch Tutorials 2.0.1cu117 documentation 使用 NN.TRANSFORMER 和 TORCHTEXT进行语言建模 这是一个关于训练模型使用nn.Transformer来预测序列中的下一个单词的教程。 PyTorch 1.2版本包含了一个基于论…

判断推理

六哥爱学习呀 产品经理 不是说我努力学习我就一定可以通过考试&#xff0c;所以是推不出&#xff0c;类似数学中充分必要性 8 回复 发布于 2019-08-07 16:28 官方解析&#xff1a; 当丙的范围足够大时&#xff0c;可能与甲相交或完全包含甲&#xff0c;在此情况下&#xff0c;有…

Python方式实现射后不管导弹的简易制导系统

1 问题 对QN-506上的S570智能反坦克制导导弹的射后不管产生了浓厚的兴趣&#xff0c;想用Python简易还原一下。 2 方法 之前查阅资料时了解到使用pygame库制作的贪吃蛇&#xff0c;是否有一种方法能让“贪吃蛇”一直跟着鼠标走呢&#xff1f;鼠标模拟行进中的坦克&#xff0c;“…

Linux 常见问题解决思路

Linux 常见问题解决思路 CPU 高系统平均负载高&#xff08;load average&#xff09; CPU 高 1&#xff0c;步骤&#xff1a;查找进程-》查找线程-》分析threadDump日志-》找出问题代码 a、查看 cpu 高的 java 进程 topb、生成进程下所有线程的栈日志 jstack 1721 > 1712.…

设计模式之单例设计模式

单例设计模式 2.1 孤独的太阳盘古开天&#xff0c;造日月星辰。2.2 饿汉造日2.3 懒汉的队伍2.4 大道至简 读《秒懂设计模式总结》 单例模式(Singleton)是一种非常简单且容易理解的设计模式。顾名思义&#xff0c;单例即单一的实例&#xff0c;确切地讲就是指在某个系统中只存在…

centos 7.9 部署django项目

1、部署框架 主要组件&#xff1a;nginx、uwsgi、django项目 访问页面流程&#xff1a;nginx---》uwsgi---》django---》uwsgi---》nginx 2、部署过程 操作系统&#xff1a;centos 7.9 配置信息&#xff1a;4核4G 50G 内网 eip &#xff1a;10.241.103.216 部署过程&…

pycharm调整最大堆发挥最大

python程序运行时&#xff0c;怎么提高效率&#xff0c;设置pycharm最大堆过程如下&#xff1b; 一、进入设置pycharm最大堆&#xff1b; 二、进入设置pycharm最大堆&#xff1b; 如果8g设置为6g左右&#xff0c;占75%左右最佳

数学建模之“TOPSIS数学模型”原理和代码详解

一、简介 TOPSIS&#xff08;Technique for Order Preference by Similarity to Ideal Solution&#xff09;是一种多准则决策分析方法&#xff0c;用于解决多个候选方案之间的排序和选择问题。它基于一种数学模型&#xff0c;通过比较每个候选方案与理想解和负理想解之间的相…

小程序CSS button按钮自定义高度之后不居中

问题&#xff1a; 按钮设置高度后不居中 <view><button class"btn1" size"">Save</button> </view> page {font-size: 30rpx; }.btn1 {margin-top: 100rpx;height: 190rpx;background: linear-gradient(90deg, #FF8A06, #FF571…

c语言每日一练(8)

前言&#xff1a;每日一练系列&#xff0c;每一期都包含5道选择题&#xff0c;2道编程题&#xff0c;博主会尽可能详细地进行讲解&#xff0c;令初学者也能听的清晰。每日一练系列会持续更新&#xff0c;暑假时三天之内必有一更&#xff0c;到了开学之后&#xff0c;将看学业情…

微信小程序:模板使用

目录 模板的优点&#xff1a; 一、静态模板创建 二、静态模板使用 1.*.wxml引入模板 2.模板使用 3.*.wxss引入模板的样式 三、动态模板创建 四、动态模板使用 1.*.wxml引入模板 2.模板使用 3.*.js定义动态数据 五、结果展示 总结 模板的优点&#xff1a; 有利于保持网…

NVIDIA Omniverse与GPT-4结合生成3D内容

全球各行业对 3D 世界和虚拟环境的需求呈指数级增长。3D 工作流程是工业数字化的核心&#xff0c;开发实时模拟来测试和验证自动驾驶车辆和机器人&#xff0c;操作数字孪生来优化工业制造&#xff0c;并为科学发现铺平新的道路。 如今&#xff0c;3D 设计和世界构建仍然是高度…

HDFS原理剖析

一、概述 HDFS是Hadoop的分布式文件系统&#xff08;Hadoop Distributed File System&#xff09;&#xff0c;实现大规模数据可靠的分布式读写。HDFS针对的使用场景是数据读写具有“一次写&#xff0c;多次读”的特征&#xff0c;而数据“写”操作是顺序写&#xff0c;也就是…

免费开源服务器资源监控系统grafana+prometheus+node_exporter

有项目做测试的时候需要查询服务器资源利用情况&#xff0c;自己又没写相应的模块&#xff0c;此时就需要一套好用的资源监控系统&#xff0c;&#xff0c;咨询了运维人员给推荐了一套&#xff0c;装完后真的很好用。 就是grafanaprometheusnode_exporter&#xff08;linux&am…

DAY3,ARM(LED点灯实验)

1.汇编实现开发板三盏灯点亮熄灭&#xff1b; .text .global _start _start: /**********LED123点灯**************/RCC_INIT:1使能PE10 PF10 PE8RCC..寄存器,E[4]1 F[5]1 0x50000a28ldr r0,0x50000a28ldr r1,[r0]orr r1,r1,#(0x3 << 4)str r1,[r0]LED1_INET:2初始化LED…

Vue3实现图片懒加载及自定义懒加载指令

Vue3实现图片懒加载及自定义懒加载指令 前言1.使用vue3-lazyload插件2.自定义v-lazy懒加载指令2.1 使用VueUse2.2 使用IntersectionObserver 前言 图片懒加载是一种常见性能优化的方式&#xff0c;它只去加载可视区域图片&#xff0c;而不是在网页加载完毕后就立即加载所有图片…

安防监控视频云存储平台EasyNVR通道频繁离线的原因排查与解决

安防视频监控汇聚EasyNVR视频集中存储平台&#xff0c;是基于RTSP/Onvif协议的安防视频平台&#xff0c;可支持将接入的视频流进行全平台、全终端分发&#xff0c;分发的视频流包括RTSP、RTMP、HTTP-FLV、WS-FLV、HLS、WebRTC等格式。为了满足用户的集成与二次开发需求&#xf…

H3C交换机如何配置本地端口镜像并在PC上使用Wireshake抓包

环境: H3C S6520-26Q-SI version 7.1.070, Release 6326 Win 10 专业版 Wireshake Version 4.0.3 问题描述: H3C交换机如何配置本地端口镜像并在PC上使用Wireshake抓包 解决方案: 配置交换机本地端口镜像 1.进入系统视图,并创建本地镜像组1 <H3C>system-vie…

酷开系统 | 酷开科技大数据,更好的与目标消费人群建立联系

众所周知&#xff0c;OTT的一大优势在于强曝光&#xff0c;能够给消费者带来强烈的视觉冲击&#xff0c;强化品牌认知。但是&#xff0c;要想达到提升品牌认知&#xff0c;首先要保证OTT的流量规模&#xff0c;实现对目标人群的有效覆盖。得年轻消费者得“天下”&#xff0c;年…