【多任务案例:猫狗脸部定位与分类】

news2024/11/25 18:50:16

【猫狗脸部定位与识别】

  • 1 引言
  • 2 损失函数
  • 3 The Oxford-IIIT Pet Dataset数据集
  • 4 数据预处理
  • 4 创建模型输入
  • 5 自定义数据集加载方式
  • 6 显示一批次数据
  • 7 创建定位模型
  • 8 模型训练
  • 9 绘制损失曲线
  • 10 模型保存与预测

1 引言

猫狗脸部定位与识别分为定位和识别,即定位猫狗脸部位置,识别脸部是狗还是猫。
在这里插入图片描述
针对既要预测类别还要定位目标位置的问题,首先使用卷积模型提取图片特征,然后分别连接2个输出,一个做回归输出位置(xim,ymin,xmax,ymax);另一个做分类,输出两个类别概率(0,1)。

2 损失函数

回归问题使用L2损失–均方误差(MSE_loss),分类问题使用交叉熵损失(CrossEntropyLoss),将两者相加即为总损失。

3 The Oxford-IIIT Pet Dataset数据集

数据来源:https://www.robots.ox.ac.uk/~vgg/data/pets/
在这里插入图片描述
包含两类(猫和狗)共37种宠物,每种宠物约有200张图。
dataset文件结构如下:
±–dataset
| ±–annotations
| | ±–trimaps
| | —xmls
| —images

images包含所有猫狗图片,annotation包含标签数据和trimaps(三元图[0,1,2])标签图,xmls包含脸部坐标位置和种类。

4 数据预处理

(1)导入基本库

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils import data

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

import torchvision
from torchvision import transforms
import os

from lxml import etree         # etree网页解析模块                   # 安装 lxml : conda install lxml
from matplotlib.patches import Rectangle    # Rectangle画矩形
import glob

from PIL import Image

(2)读取一张图片

BATCH_SIZE = 4
pil_img = Image.open(r'dataset/images/Abyssinian_1.jpg')
np_img = np.array(pil_img)
print(np_img.shape)
plt.imshow(np_img)
plt.show()

在这里插入图片描述
(3) 打开一个xml文件

xml = open(r'dataset/annotations/xmls/Abyssinian_1.xml').read()
sel = etree.HTML(xml)
width = sel.xpath('//size/width/text()')[0]

height = sel.xpath('//size/height/text()')[0]
xmin = sel.xpath('//bndbox/xmin/text()')[0]
ymin = sel.xpath('//bndbox/ymin/text()')[0]
xmax = sel.xpath('//bndbox/xmax/text()')[0]
ymax = sel.xpath('//bndbox/ymax/text()')[0]

width = int(width)
height = int(height)
xmin = int(xmin)
ymin = int(ymin)
xmax = int(xmax)
ymax = int(ymax)

plt.imshow(np_img)
rect = Rectangle((xmin, ymin), (xmax-xmin), (ymax-ymin), fill=False, color='red')
ax = plt.gca()
ax.axes.add_patch(rect)
plt.show()

在这里插入图片描述
(4)当图片的尺寸发生变化时,脸部的定位坐标要相对原来的宽高按比例缩放(xmin=xmin* new_ width/old_width)

img = pil_img.resize((224, 224))

xmin = xmin*224/width
ymin = ymin*224/height
xmax = xmax*224/width
ymax = ymax*224/height

plt.imshow(img)
rect = Rectangle((xmin, ymin), (xmax-xmin), (ymax-ymin), fill=False, color='red')
ax = plt.gca()
ax.axes.add_patch(rect)
plt.show()

在这里插入图片描述

4 创建模型输入

xml和images数量不一致,并不是所有图片都具有标签,所以需要逐一找出具有位置信息的图片并保存地址

images = glob.glob('dataset/images/*.jpg')
print(images[:5])
print(len(images))	

xmls = glob.glob('dataset/annotations/xmls/*.xml')
print(len(xmls)) # xml和images数量不一致,并不是所有图片都具有标签,所以需要逐一找出具有位置信息的图片并保存地址
print(xmls[:5]) 

xmls_names = [x.split('/')[-1].split('.xml')[0] for x in xmls]
print(xmls_names[:3])
print(len(xmls_names))

# 遍历所有具有定位坐标的图片,并保存图片路径
imgs = [img for img in images if 
        img.split('/')[-1].split('.jpg')[0] in xmls_names]

print(len(imgs))
print(imgs[:5])

# 重新定义尺寸为224,并将定位和类别保存到labels中
scal = 224
name_to_id = {'cat':0, 'dog':1}
id_to_name = {0:'cat', 1:'dog'}
def to_labels(path):
    xml = open(r'{}'.format(path)).read()
    sel = etree.HTML(xml)
    name = sel.xpath('//object/name/text()')[0]
    width = int(sel.xpath('//size/width/text()')[0])
    height = int(sel.xpath('//size/height/text()')[0])
    xmin = int(sel.xpath('//bndbox/xmin/text()')[0])
    ymin = int(sel.xpath('//bndbox/ymin/text()')[0])
    xmax = int(sel.xpath('//bndbox/xmax/text()')[0])
    ymax = int(sel.xpath('//bndbox/ymax/text()')[0])
    return (xmin/width, ymin/height, xmax/width, ymax/height, name_to_id.get(name))
labels = [to_labels(path) for path in xmls]

np.random.seed(2022)
index = np.random.permutation(len(imgs))

# 划分训练集和测试集
images = np.array(imgs)[index]
print(images[0])
labels = np.array(labels, np.float32)[index]
print(labels[0])

sep = int(len(imgs)*0.8)
train_images = images[ :sep]
train_labels = labels[ :sep]
test_images = images[sep: ]
test_labels = labels[sep: ]

输出如下:

['dataset/images/german_shorthaired_102.jpg',
 'dataset/images/havanese_150.jpg',
 'dataset/images/great_pyrenees_143.jpg',
 'dataset/images/Bombay_41.jpg',
 'dataset/images/newfoundland_2.jpg']
7390
3686

['dataset/annotations/xmls/american_bulldog_178.xml',
 'dataset/annotations/xmls/scottish_terrier_114.xml',
 'dataset/annotations/xmls/american_pit_bull_terrier_179.xml',
 'dataset/annotations/xmls/Birman_171.xml',
 'dataset/annotations/xmls/staffordshire_bull_terrier_107.xml']

['american_bulldog_178',
 'scottish_terrier_114',
 'american_pit_bull_terrier_179']

3686

3686

['dataset/images/german_shorthaired_102.jpg',
 'dataset/images/havanese_150.jpg',
 'dataset/images/great_pyrenees_143.jpg',
 'dataset/images/samoyed_137.jpg',
 'dataset/images/newfoundland_189.jpg']

['dataset/annotations/xmls/american_bulldog_178.xml',
 'dataset/annotations/xmls/scottish_terrier_114.xml',
 'dataset/annotations/xmls/american_pit_bull_terrier_179.xml',
 'dataset/annotations/xmls/Birman_171.xml',
 'dataset/annotations/xmls/staffordshire_bull_terrier_107.xml']

dataset/images/pug_184.jpg
[0.19117647 0.21       0.8        0.624      1.        ]

5 自定义数据集加载方式

transform = transforms.Compose([
                    transforms.Resize((224, 224)),
                    transforms.ToTensor(),
])

class Oxford_dataset(data.Dataset):
    def __init__(self, img_paths, labels, transform):
        self.imgs = img_paths
        self.labels = labels
        self.transforms = transform
        
    def __getitem__(self, index):
        img = self.imgs[index]
        label = self.labels[index]
        pil_img = Image.open(img) 
        pil_img = pil_img.convert("RGB")
        pil_img = transform(pil_img)
        return pil_img, label[:4],label[4] # 图片像素(3, 224, 224),定位4个值,分类1个值
    
    def __len__(self):
        return len(self.imgs)

train_dataset = Oxford_dataset(train_images, train_labels, transform)
test_dataset = Oxford_dataset(test_images, test_labels, transform)
train_dl = data.DataLoader(train_dataset,batch_size=BATCH_SIZE,shuffle=True)
test_dl = data.DataLoader(test_dataset,batch_size=BATCH_SIZE)

6 显示一批次数据

(imgs_batch, labels1_batch,labels2_batch) = next(iter(train_dl))
print(imgs_batch.shape, labels1_batch.shape,labels2_batch.shape)

plt.figure(figsize=(12, 8))
for i, (img, label_1,label_2) in enumerate(zip(imgs_batch[:6], labels1_batch[:6],labels2_batch[:6])):
    img = img.permute(1,2,0).numpy() #+ 1)/2
    plt.subplot(2, 3, i+1)
    plt.imshow(img)
    plt.title(id_to_name.get(label_2.item()))
    xmin, ymin, xmax, ymax = tuple(label_1.numpy()*224)
    rect = Rectangle((xmin, ymin), (xmax-xmin), (ymax-ymin), fill=False, color='red')
    ax = plt.gca()
    ax.axes.add_patch(rect)
plt.savefig('pics/example.jpg', dpi=400)

输出如下:

(torch.Size([4, 3, 224, 224]), torch.Size([4, 4]), torch.Size([4]))
在这里插入代码片

在这里插入图片描述

7 创建定位模型

借用renet50网络模型的卷积部分,而分类部分自定义如下:

resnet = torchvision.models.resnet50(pretrained=True)
#print(resnet)
in_f = resnet.fc.in_features
print(in_f)
print(list(resnet.children()))  # 以生成器形式返回模型所包含的所有层

输出如下:

2048
[Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False), BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True), ReLU(inplace=True), MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False), Sequential(
  (0): Bottleneck(
    (conv1): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
    (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
    (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
    (bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (relu): ReLU(inplace=True)
    (downsample): Sequential(
      (0): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
      (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (1): Bottleneck(
    (conv1): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
    (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
    (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
    (bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (relu): ReLU(inplace=True)
  )
  (2): Bottleneck(
...
    (bn3): BatchNorm2d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (relu): ReLU(inplace=True)
  )
), AdaptiveAvgPool2d(output_size=(1, 1)), Linear(in_features=2048, out_features=1000, bias=True)]

自定义分类和定位模型如下:

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv_base = nn.Sequential(*list(resnet.children())[:-1])   # 以生成器方式返回模型所包含的所有层
        self.fc1 = nn.Linear(in_f, 4)   # 位置坐标
        self.fc2 = nn.Linear(in_f, 2)   # 两分类概率

    def forward(self, x):
        x = self.conv_base(x)
        x = x.view(x.size(0), -1)
        x1 = self.fc1(x)
        x2 = self.fc2(x)
        return x1,x2

8 模型训练

model = Net()

device = "cuda" if torch.cuda.is_available() else "cpu"
print("Using {} device".format(device))

model = model.to(device)
loss_mse = nn.MSELoss()
loss_crossentropy = nn.CrossEntropyLoss()

from torch.optim import lr_scheduler
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
exp_lr_scheduler = lr_scheduler.StepLR(optimizer, step_size=7, gamma=0.5, verbose = True)

def train(dataloader, model, loss_mse,loss_crossentropy, optimizer):  
    num_batches = len(dataloader)
    train_loss = 0
    model.train()
    for X, y1,y2 in dataloader:
        X, y1, y2 = X.to(device), y1.to(device), y2.to(device)
        # Compute prediction error
        y1_pred, y2_pred = model(X)
        loss = loss_mse(y1_pred, y1) + loss_crossentropy(y2_pred,y2.long())

        # Backpropagation
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        
        with torch.no_grad():
            train_loss += loss.item()
    train_loss /= num_batches
    return train_loss

def test(dataloader, model,loss_mse,loss_crossentropy):   
    num_batches = len(dataloader)
    model.eval()
    test_loss = 0
    with torch.no_grad():
        for X, y1, y2 in dataloader:
            X, y1, y2 = X.to(device), y1.to(device), y2.to(device)
            # Compute prediction error
            y1_pred, y2_pred = model(X)
            loss = loss_mse(y1_pred, y1) + loss_crossentropy(y2_pred,y2.long())
            test_loss += loss.item()
    test_loss /= num_batches
    return test_loss

def fit(epochs, train_dl, test_dl, model, loss_mse,loss_crossentropy, optimizer): 
    train_loss = []
    test_loss = []

    for epoch in range(epochs):
        epoch_loss = train(train_dl, model, loss_mse,loss_crossentropy, optimizer)    #
        epoch_test_loss = test(test_dl, model, loss_mse,loss_crossentropy) #
        train_loss.append(epoch_loss)
        test_loss.append(epoch_test_loss)
        exp_lr_scheduler.step()
        
        template = ("epoch:{:2d}/{:2d}, train_loss: {:.5f}, test_loss: {:.5f}")
        print(template.format(epoch+1,epochs, epoch_loss, epoch_test_loss))
    print("Done!")
    
    return train_loss, test_loss

epochs = 50

train_loss, test_loss = fit(epochs, train_dl, test_dl, model, loss_mse,loss_crossentropy, optimizer)  #

输出如下:

Using cuda device

Adjusting learning rate of group 0 to 1.0000e-04.
epoch: 1/50, train_loss: 0.68770, test_loss: 0.69263
Adjusting learning rate of group 0 to 1.0000e-04.
epoch: 2/50, train_loss: 0.64950, test_loss: 0.69668
Adjusting learning rate of group 0 to 1.0000e-04.
epoch: 3/50, train_loss: 0.63532, test_loss: 0.71381
Adjusting learning rate of group 0 to 1.0000e-04.
epoch: 4/50, train_loss: 0.61014, test_loss: 0.74332
Adjusting learning rate of group 0 to 1.0000e-04.
epoch: 5/50, train_loss: 0.57072, test_loss: 0.76198
Adjusting learning rate of group 0 to 1.0000e-04.
epoch: 6/50, train_loss: 0.45499, test_loss: 0.93127
Adjusting learning rate of group 0 to 5.0000e-05.
epoch: 7/50, train_loss: 0.31113, test_loss: 0.96860
Adjusting learning rate of group 0 to 5.0000e-05.
epoch: 8/50, train_loss: 0.14169, test_loss: 1.35223
Adjusting learning rate of group 0 to 5.0000e-05.
epoch: 9/50, train_loss: 0.08092, test_loss: 1.50338
Adjusting learning rate of group 0 to 5.0000e-05.
epoch:10/50, train_loss: 0.06381, test_loss: 1.49817
Adjusting learning rate of group 0 to 5.0000e-05.
epoch:11/50, train_loss: 0.05252, test_loss: 1.49126
Adjusting learning rate of group 0 to 5.0000e-05.
epoch:12/50, train_loss: 0.04227, test_loss: 1.45301
Adjusting learning rate of group 0 to 5.0000e-05.
...
epoch:49/50, train_loss: 0.00632, test_loss: 2.19361
Adjusting learning rate of group 0 to 7.8125e-07.
epoch:50/50, train_loss: 0.00594, test_loss: 2.16411
Done!

9 绘制损失曲线

结果较差,需要优化网络模型,但思路不变。

plt.figure()
plt.plot(range(1, len(train_loss)+1), train_loss, 'r', label='Training loss')
plt.plot(range(1, len(train_loss)+1), test_loss, 'b', label='Validation loss')
plt.title('Training and Validation Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss Value')
plt.legend()
plt.show()

在这里插入图片描述

10 模型保存与预测

PATH = 'model_path/location_model.pth'
torch.save(model.state_dict(), PATH)
model = Net()
model.load_state_dict(torch.load(PATH))
model = model.cuda()        #.cpu() 模型使用GPU或CPU加载

plt.figure(figsize=(8, 8))
imgs, _,_ = next(iter(test_dl))
imgs =imgs.to(device)
out1,out2 = model(imgs)
for i in range(4):
    plt.subplot(2, 2, i+1)
    
    plt.imshow(imgs[i].permute(1,2,0).detach().cpu())
    plt.title(id_to_name.get(torch.argmax(out2[i],0).item()))
    xmin, ymin, xmax, ymax = tuple(out1[i].detach().cpu().numpy()*224)
    rect = Rectangle((xmin, ymin), (xmax-xmin), (ymax-ymin), fill=False, color='red')
    ax = plt.gca()
    ax.axes.add_patch(rect)
plt.savefig('pics/predict.jpg',dpi =400)

在这里插入图片描述

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

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

相关文章

批量导入、筛选与删除,文件改名从未如此简单!

您是否曾经为了给文件夹中的文件批量改名而感到烦恼?现在,有了我们的文件批量改名工具,这些问题都将成为过去! 第一步,我们打开需要改名文件夹,可以看到里面文件名很多,很乱。 第二步进入文件批…

Acwing 844. 走迷宫

Acwing 844. 走迷宫 知识点题目描述思路讲解代码展示 知识点 BFS 题目描述 思路讲解 宽搜可以搜到最短路径&#xff1a; 代码展示 #include <cstring> #include <iostream> #include <algorithm> #include <queue>using namespace std;typedef pa…

传输层协议—UDP协议

传输层协议—UDP协议 文章目录 传输层协议—UDP协议传输层再谈端口号端口号范围划分pidofnetstat UDP协议端格式UDP报文UDP特点UDP缓冲区基于UDP的应用层协议 传输层 在学习HTTP/HTTPS等应用层协议时&#xff0c;为了方便理解&#xff0c;可以简单认为HTTP将请求和响应直接发送…

基于Java的图书个性化推荐系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

基于Java的校园失物招领平台设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

E. Mishap in Club

题目&#xff1a; 样例1&#xff1a; 输入 --输出 1 样例2&#xff1a; 输入 --- 输出 3 思路&#xff1a; 数学贪心模拟思路&#xff0c;由于不知道在俱乐部的人数和在外面的人数&#xff0c;又要尽可能少的人数&#xff0c;那么定义两个变量&#xff0c;一个是里面的人数 i…

Python中使用IDLE调试程序

在IDLE中&#xff0c;使用菜单栏中的“Debug”对IDLE打开的python程序进行调试。 1 打开调试开关 选择IDLE菜单栏的“Debug->Debugger”&#xff0c;如图1①所示&#xff1b;此时在IDLE中会显示“[DEBUG ON]”&#xff0c;即“调试模式已打开”&#xff0c;如图1②所示&am…

JMeter学习第三天+

性能测试前言 老师开局一句话&#xff1a;性能测试和你会不会JMeter一点关系没有…… 作者坚持技多不压身的原则&#xff0c;还是多学一点JMeter吧&#xff0c;看老师到底要怎么讲下去&#xff0c;什么并发量、吞吐量啥的…… 性能测试的核心思想&#xff1a;在于创造大量并发去…

16,8和4位浮点数是如何工作的

50年前Kernighan、Ritchie和他们的C语言书的第一版开始&#xff0c;人们就知道单精度“float”类型有32位大小&#xff0c;双精度类型有64位大小。还有一种具有扩展精度的80位“长双精度”类型&#xff0c;这些类型几乎涵盖了浮点数据处理的所有需求。但是在最近几年&#xff0…

Javase ------> 泛型

Jdk自从5.0后引入泛型之后一直没有删除,而且在我们的集合框架中进场能使用的到,今天我们就详细介绍一下泛型的一些特性和使用须知,希望能对你的编程学习带来一些帮助. 1.什么是泛型 泛型&#xff0c;即“参数化类型”。一提到参数&#xff0c;最熟悉的就是定义方法时有形参&…

c# 中的类

反射 Activator.CreateInstance class Program {static void Main(string[] args){//反射Type t typeof(Student);object o Activator.CreateInstance(t, 1, "FJ");Student stu o as Student;Console.WriteLine(stu.Name);//动态编程dynamic stu2 Activator.Cre…

【算法】二分相关题目

文章目录 二分相关二分查找在排序数组中查找元素的第一个和最后一个位置寻找峰值x 的平方根0~n-1中缺失的数字## 搜索插入位置 二分相关 二分查找 https://leetcode.cn/problems/binary-search/ 在一个有序数组当中&#xff0c;查找值为target的元素&#xff0c;返回下标 cla…

SSM - Springboot - MyBatis-Plus 全栈体系(十七)

第三章 MyBatis 五、MyBatis 高级扩展 1. mapper 批量映射优化 1.1 需求 Mapper 配置文件很多时&#xff0c;在全局配置文件中一个一个注册太麻烦&#xff0c;希望有一个办法能够一劳永逸。 1.2 配置方式 Mybatis 允许在指定 Mapper 映射文件时&#xff0c;只指定其所在的…

2023年中国家用智能门锁市场发展概况分析:家用智能门锁线上市场销量290.4万套[图]

智能门锁是指区别于传统机械锁的基础上改进的&#xff0c;在用户安全性、识别、管理性方面更加智能化简便化的锁具。智能门锁是门禁系统中锁门的执行部件。智能门锁区别于传统机械锁, 是具有安全性, 便利性, 先进技术的复合型锁具。 智能门锁级别分类 资料来源&#xff1a;共研…

MARS: An Instance-aware, Modular and Realistic Simulator for Autonomous Driving

● MARS: An Instance-aware, Modular and Realistic Simulator for Autonomous Driving&#xff08;基于神经辐射场的自动驾驶仿真器&#xff09; ● https://github.com/OPEN-AIR-SUN/mars ● https://arxiv.org/pdf/2307.15058.pdf ● https://mp.weixin.qq.com/s/6Ion_DZGJ…

flink处理函数--副输出功能

背景 在flink中&#xff0c;如果你想要访问记录的处理时间或者事件时间&#xff0c;注册定时器&#xff0c;或者是将记录输出到多个输出流中&#xff0c;你都需要处理函数的帮助&#xff0c;本文就来通过一个例子来讲解下副输出 副输出 本文还是基于streaming-with-flink这本…

解决SpringBoot Configuration Annotation Processor not configured

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 问题描述 在使用ConfigurationProperties注解和EnableConfigurationProperties注解时&#xff0c;IDEA报错&#xff1a;SpringBoot Configuration Annotation Processor no…

【chainlit】使用chainlit部署chatgpt

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…

微服务架构改造案例

最后一个部分&#xff0c;结合我们自己的财务共享平台项目进行了微服务架构改造案例分析。 对于改造前的应用&#xff0c;实际上存在四个方面的问题。 其一是关于高可用性方面的&#xff0c;即传统的单体应用我们在进行数据库水平扩展的时候已经很难扩展&#xff0c;已经出现…