Kaggle竞赛——手写数字识别(Digit Recognizer)

news2024/9/21 0:51:01

目录

  • 1. 数据集介绍
  • 2. 数据分析
  • 3. 数据处理与封装
    • 3.1 数据集划分
    • 3.2 将数据转为tensor张量
    • 3.3 数据封装
  • 4. 模型训练
    • 4.1 定义功能函数
    • 4.1 resnet18模型
    • 4.3 CNN模型
    • 4.4 FCNN模型
  • 5. 结果分析
    • 5.1 混淆矩阵
    • 5.2 查看错误分类的样本
  • 6. 加载最佳模型
  • 7. 参考文献

本次手写数字识别使用了resnet18(比resnet50精度更好)、CNN和FCNN三种模型,精度上resnet18 > CNN > FCNN,最终提交到官网的测试集精度为0.983,排名为758(提交时间:2024年9月1日)。数据集、代码、python虚拟环境和训练后的最佳模型已打包上传到Gitee,[点击直达]。
在这里插入图片描述

1. 数据集介绍

竞赛使用的是 MNIST (Modified National Institute of Standards and Technology, 美国国家标准与技术研究院修改版) 手写图像数据集,其中训练集42000条,测试集28000条,每条数据有784 个像素点,即原始图像的像素为 28 * 28。训练集中的Label列表示手写数字的类别(共10个类别,0-10)。

2. 数据分析

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split

train = pd.read_csv("D:/Desktop/kaggle数据集/digit-recognizer/train.csv")
test  = pd.read_csv("D:/Desktop/kaggle数据集/digit-recognizer/test.csv")
train.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 42000 entries, 0 to 41999
Columns: 785 entries, label to pixel783
dtypes: int64(785)
memory usage: 251.5 MB
test.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 28000 entries, 0 to 27999
Columns: 784 entries, pixel0 to pixel783
dtypes: int64(784)
memory usage: 167.5 MB

查看空缺值

#--------------------------------------------------------------------------------------------------------------------------------#
# train_data.isnull(): 返回一个与 train_data 相同维度的布尔值数据框,其中 True 表示该位置存在缺失值,False 表示没有缺失值
# any(): 对每一列进行操作,如果某列中存在至少一个 True 那么这一列的结果就是 True;否则就是 False,结果是一个布尔类型的 Series
# describe(): 统计摘要,包括总列数、唯一值个数、最频繁出现的值(top)及其出现频率(freq)
#--------------------------------------------------------------------------------------------------------------------------------#
train.isnull().any().describe()
count       785
unique        1
top       False
freq        785
dtype: object

由结果可知,仅有一个唯一值False,且出现785次,故训练集中无缺失值。

查看类别统计

sns.countplot(x=train['label']);

在这里插入图片描述

3. 数据处理与封装

3.1 数据集划分

将训练集划分为训练集和验证集。

# 分割特征和标签
train_labels = train["label"]
train= train.drop(labels=["label"], axis=1)

# 划分训练集和验证集
X_train, X_val, y_train, y_val = train_test_split(train, train_labels, test_size = 0.2, random_state=41)
print("训练集大小:{},验证集大小:{}".format(len(X_train), len(X_val)))
训练集大小:33600,验证集大小:8400

3.2 将数据转为tensor张量

dataFrameSeries类型需要先转为numpy类型,

import torch
from torch.utils.data import DataLoader, TensorDataset

X_train_tensor = torch.tensor(X_train.values, dtype = torch.float32)
y_train_tensor = torch.tensor(y_train.values)

X_val_tensor   = torch.tensor(X_val.values, dtype = torch.float32)
y_val_tensor   = torch.tensor(y_val.values)

test_tensor = torch.tensor(test.values, dtype = torch.float32)

3.3 数据封装

使用TensorDataset创建创建包含数据特征和数据类别的tensor数据集,再用DataLoader划分封装数据集。封装数据集时,训练集中的shuffle参数设置为True(随机打乱数据),可以防止模型学习到数据的顺序,从而提高模型的泛化能力;验证集和测试集shuffle参数设置为False,能够保证测试集预测结果的一致性和可比性。

train_tensor = TensorDataset(X_train_tensor, y_train_tensor)
train_loader = DataLoader(train_tensor, batch_size=100, shuffle=True)

val_tensor   = TensorDataset(X_val_tensor, y_val_tensor)
val_loader   = DataLoader(val_tensor, batch_size=100, shuffle=False)

test_loader  = DataLoader(test_tensor, batch_size = 100, shuffle=False)

可视化训练集中的一张图像

plt.imshow(train.values[10].reshape(28,28), cmap='gray')
plt.axis("off")
plt.title(str(train_labels.values[10]));

在这里插入图片描述

4. 模型训练

4.1 定义功能函数

定义模型的训练和验证函数:

"""
模型训练函数
Params:
    epoch:        训练轮次
    model:        预定义模型
    dataloader:   批处理数据
    criterion:    损失函数(交叉熵)
    optimizer:    优化器
Returns
    running_loss/len(train_loader):本轮次(遍历一遍训练集)的平均损失
    sum_correct/train_num:本轮次(遍历一遍训练集)准确率
"""
def model_train(epoch, model, model_name, dataloader, criterion, optimizer):
#     print("-------------------------Training-------------------------")
    # 设置模型为训练模式
    model.train()
    running_loss = 0.0
    # 训练集大小
    train_num = len(X_train)
    #记录遍历一轮数据集后分类正确的样本数
    sum_correct = 0
    for step, data in enumerate(dataloader):
        images, labels = data
        if model_name == 'resnet18':
            #-------------------------------------------------------------------------------------------------#
            # ResNet18 期望输入的形状为 [batch_size, channels, height, width],其中 channels 为 3(RGB 图像)
            # expand(): 沿指定维度扩展张量(但不复制数据,只改变视图)
            #-------------------------------------------------------------------------------------------------#
            images = images.view(-1, 1, 28, 28).expand(-1, 3, -1, -1)
        if model_name == 'cnn':
            # 自定义CNN的输入维度为 1
            images = images.view(-1, 1, 28, 28)
            
        # 模型为FCNN时无需转换
        
        images = images.to(device)
        labels = labels.to(device)
        # 清除上一次迭代的梯度信息,防止梯度累积
        optimizer.zero_grad()

        #-------------------------------------------------------------------------------------------------#
        # outputs的尺寸[每次输入的样本数(batch_size), 类别数]
        # 表示的含义:对应样本被分为某一类别的概率
        #-------------------------------------------------------------------------------------------------#
        outputs = model(images)
        # 计算损失值
        loss = criterion(outputs, labels)
        #-------------------------------------------------------------------------------------------------#
        # 计算损失函数相对于模型参数的梯度,并将这些梯度存储在每个参数的 .grad 属性中。
        # 随后,优化器会使用这些梯度来更新模型参数,从而逐步最小化损失函数,实现模型的训练
        #-------------------------------------------------------------------------------------------------#
        loss.backward()
        
        # 使用优化器 optimizer 更新模型参数
        optimizer.step()
        running_loss += loss.item()
        
        #-------------------------------------------------------------------------------------------------#
        # torch.max()函数返回两个值:每行的最大值和最大值的索引
        # _:表示忽略了第一个返回值(每行的最大值)
        # 1:寻找每行的最大值和索引
        #-------------------------------------------------------------------------------------------------#
        _, predicted = torch.max(outputs, 1)
        
        #-------------------------------------------------------------------------------------------------#
        # sum(): 将布尔张量转换为整数张量并对其进行求和,得到正确预测的总数。
        # 布尔值 True 计算为 1,False 计算为 0。
        # item(): 将单元素张量转换为 Python 标量值,便于计算
        #-------------------------------------------------------------------------------------------------#
        correct = (predicted == labels).sum().item()
        sum_correct += correct
        train_acc = correct / len(labels)    
#         print("[Epoch {}, step: {}] Train Loss: {:.4f}, Train Acc: {:.2f}%".format(epoch + 1, step+1, loss, train_acc*100))
        
#     print("-------------------------Training-------------------------")
    return running_loss/len(train_loader), sum_correct/train_num


"""
模型评估函数
Params:
    epoch:        训练轮次
    model:        预定义模型
    dataloader:   批处理数据
    criterion:    损失函数(交叉熵)
Returns
    running_loss/len(train_loader):本轮次(遍历一遍验证集)的平均损失
    sum_correct/train_num:本轮次(遍历一遍验证集)准确率
"""
def model_validate(epoch, model, model_name, dataloader, criterion):
#     print("------------------------Validating------------------------")
    # 设置模型为测试模式
    model.eval()
    val_loss = 0.0
    # 训练集大小
    val_num = len(X_val)
    sum_correct  = 0
    # 禁止梯度反传
    with torch.no_grad():
        for step, data in enumerate(dataloader):
            images, labels = data
            if model_name == 'resnet18':
                #-------------------------------------------------------------------------------------------------#
                # ResNet18 期望输入的形状为 [batch_size, channels, height, width],其中 channels 为 3(RGB 图像)
                # expand(): 沿指定维度扩展张量(但不复制数据,只改变视图)
                #-------------------------------------------------------------------------------------------------#
                images = images.view(-1, 1, 28, 28).expand(-1, 3, -1, -1)
                
            if model_name == 'cnn':
                # 自定义CNN的输入维度为 1
                images = images.view(-1, 1, 28, 28)
                
            # 模型为FCNN时无需转换
            
            images = images.to(device)
            labels = labels.to(device)
            outputs = model(images)
            
            # 计算损失值
            loss = criterion(outputs, labels)
            val_loss += loss.item()
            
            #-------------------------------------------------------------------------------------------------#
            # torch.max()函数返回两个值:每行的最大值和最大值的索引
            # _:表示忽略了第一个返回值(每行的最大值)
            # 1:寻找每行的最大值和索引
            #-------------------------------------------------------------------------------------------------#
            _, predicted = torch.max(outputs, 1)
            correct = (predicted == labels).sum().item()
            sum_correct += correct
            total = len(labels)
            val_acc = correct / total
#             print("[Epoch {}, step: {}] Val Loss: {:.4f}, Val Acc: {:.2f}%".format(epoch + 1, step+1, loss, val_acc*100))
            
#     print("------------------------Validating------------------------")
    return val_loss/len(train_loader), sum_correct/val_num

定义模型训练与验证的综合函数:

import torch.nn as nn
import torch.optim as optim
"""
模型整体训练与验证函数
Params:
    model:        预定义模型
"""
def train_val(model, model_name):
    # 定义损失函数(交叉熵)和优化器
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)
    
    # 验证集上的最佳准确率和最佳轮次
    best_val_acc = 0.0
    best_epoch = 0  

    for epoch in range(10): 
        # 模型训练
        train_loss, train_acc = model_train(epoch, model, model_name, train_loader, criterion, optimizer)
        train_losses.append(train_loss)
        train_accuracies.append(train_acc)

        # 模型验证
        val_loss, val_acc = model_validate(epoch, model, model_name, val_loader, criterion)
        val_losses.append(val_loss)
        val_accuracies.append(val_acc)

        if val_acc > best_val_acc:
            best_val_acc = val_acc
            best_epoch = epoch + 1
            torch.save(model.state_dict(), model_name+'_best_model.pth')  

        print("[第{}轮训练完成,训练集中 Loss:{},Accuracy:{}]".format(epoch+1, train_loss, train_acc))

    print("训练完成!最佳训练轮次:{},该轮次验证集上的准确率:{}".format(best_epoch, best_val_acc))

定义损失值和准确率的可视化函数:


"""
可视化损失值和准确率
"""
def loss_acc_plot(train_losses, val_losses, train_accuracies, val_accuracies):
    plt.figure(figsize=(12, 4))

    plt.subplot(1, 2, 1)
    # 默认情况下,plt.plot 会将 train_losses 的索引作为 X 轴的值
    plt.plot(train_losses, label='Train Loss')
    plt.plot(val_losses, label='Validation Loss')
    plt.xlabel('Epoch')
    plt.ylabel('Loss')
    plt.legend()

    plt.subplot(1, 2, 2)
    plt.plot(train_accuracies, label='Train Accuracy')
    plt.plot(val_accuracies, label='Validation Accuracy')
    plt.xlabel('Epoch')
    plt.ylabel('Accuracy')
    plt.legend()

    plt.tight_layout()

4.1 resnet18模型

from torchvision import models
# 使用GPU训练模型(如果GPU可用的话)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 调用resnet18
resnet_model = models.resnet18()
resnet_model = resnet_model.to(device)

# 记录训练集和验证集的损失值和准确率
train_losses = []
val_losses = []
train_accuracies = []
val_accuracies = []

train_val(resnet_model, "resnet18")
[1轮训练完成,训练集中 Loss:0.48536758923104834,Accuracy:0.9060714285714285]
[2轮训练完成,训练集中 Loss:0.05270050720095502,Accuracy:0.985]
[3轮训练完成,训练集中 Loss:0.02555189496238849,Accuracy:0.9938392857142857]
[4轮训练完成,训练集中 Loss:0.015233770400560129,Accuracy:0.9965773809523809]
[5轮训练完成,训练集中 Loss:0.007979269749263213,Accuracy:0.9988690476190476]
[6轮训练完成,训练集中 Loss:0.005160370017706771,Accuracy:0.9996428571428572]
[7轮训练完成,训练集中 Loss:0.0035936778385803336,Accuracy:0.9998511904761904]
[8轮训练完成,训练集中 Loss:0.0028507261213235324,Accuracy:0.9999107142857143]
[9轮训练完成,训练集中 Loss:0.002293311161511589,Accuracy:0.9998809523809524]
[10轮训练完成,训练集中 Loss:0.0019566422187857653,Accuracy:0.9998511904761904]
训练完成!最佳训练轮次:6,该轮次验证集上的准确率:0.9858333333333333

可视化损失值和准确率:

loss_acc_plot(train_losses, val_losses, train_accuracies, val_accuracies)

在这里插入图片描述

4.3 CNN模型

定义CNN模型结构:

class CNNModel(nn.Module):
    def __init__(self):
        super(CNNModel, self).__init__()
        # 卷积层 1
        self.cnn1 = nn.Conv2d(in_channels=1, out_channels=16, kernel_size=5, stride=1, padding=0)
        self.relu1 = nn.ReLU()
        # 最大池化层 1
        self.maxpool1 = nn.MaxPool2d(kernel_size=2)
        # 卷积层 2
        self.cnn2 = nn.Conv2d(in_channels=16, out_channels=32, kernel_size=5, stride=1, padding=0)
        self.relu2 = nn.ReLU() 
        # 最大池化层 2
        self.maxpool2 = nn.MaxPool2d(kernel_size=2)
        # 全连接层
        self.fc1 = nn.Linear(32 * 4 * 4, 10) 
    
    def forward(self, x):
        # 卷积层 1
        out = self.cnn1(x)
        out = self.relu1(out)
        #最大池化层 1
        out = self.maxpool1(out)
        # 卷积层 2 
        out = self.cnn2(out)
        out = self.relu2(out)
        # 最大池化层 2 
        out = self.maxpool2(out)
        # flatten层
        out = out.view(out.size(0), -1)
        # 全连接层
        out = self.fc1(out)
        
        return out

训练模型:

cnn_model = CNNModel()
cnn_model = cnn_model.to(device)

# 记录训练集和验证集的损失值和准确率
train_losses = []
val_losses = []
train_accuracies = []
val_accuracies = []

train_val(cnn_model, "cnn")
[1轮训练完成,训练集中 Loss:1.004740373009727,Accuracy:0.8109226190476191]
[2轮训练完成,训练集中 Loss:0.17581947764293068,Accuracy:0.945625]
[3轮训练完成,训练集中 Loss:0.13953285299551985,Accuracy:0.9569345238095238]
[4轮训练完成,训练集中 Loss:0.12599757561526662,Accuracy:0.9607738095238095]
[5轮训练完成,训练集中 Loss:0.11612535938842311,Accuracy:0.9638095238095238]
[6轮训练完成,训练集中 Loss:0.10294126443720113,Accuracy:0.9671726190476191]
[7轮训练完成,训练集中 Loss:0.09651396153051228,Accuracy:0.9698214285714286]
[8轮训练完成,训练集中 Loss:0.09004475945229864,Accuracy:0.9717857142857143]
[9轮训练完成,训练集中 Loss:0.08583687311537298,Accuracy:0.9727083333333333]
[10轮训练完成,训练集中 Loss:0.08039018868779142,Accuracy:0.9748214285714286]
训练完成!最佳训练轮次:9,该轮次验证集上的准确率:0.9721428571428572

可视化损失值和准确率:

loss_acc_plot(train_losses, val_losses, train_accuracies, val_accuracies)

在这里插入图片描述

4.4 FCNN模型

定义FCNN模型结构:

class FCNNModel(nn.Module):  
    def __init__(self, input_dim, hidden_dim, output_dim):
        super(FCNNModel, self).__init__()
        # 784 --> 150
        self.fc1 = nn.Linear(input_dim, hidden_dim) 
        # 激活函数
        self.relu1 = nn.ReLU()
        
        # 150 --> 150
        self.fc2 = nn.Linear(hidden_dim, hidden_dim)
        # 激活函数
        self.tanh2 = nn.Tanh()
        
        # 150 --> 150
        self.fc3 = nn.Linear(hidden_dim, hidden_dim)
        # 激活函数
        self.elu3 = nn.ELU()
        
        # 150 --> 10
        self.fc4 = nn.Linear(hidden_dim, output_dim)  
    
    def forward(self, x):
        # 784 --> 150
        out = self.fc1(x)
        out = self.relu1(out)
        
        # 150 --> 150
        out = self.fc2(out)
        out = self.tanh2(out)
        
        # 150 --> 150
        out = self.fc3(out)
        out = self.elu3(out)
        
        # 150 --> 10
        out = self.fc4(out)
        
        return out

模型训练:

# 记录训练集和验证集的损失值和准确率
train_losses = []
val_losses = []
train_accuracies = []
val_accuracies = []

input_dim = 28*28
# 可微调
hidden_dim = 150 
output_dim = 10

fcnn_model = FCNNModel(input_dim, hidden_dim, output_dim)
fcnn_model = fcnn_model.to(device)
train_val(fcnn_model, "fcnn")
[1轮训练完成,训练集中 Loss:0.8977700323753414,Accuracy:0.7811904761904762]
[2轮训练完成,训练集中 Loss:0.3077347204089165,Accuracy:0.9172916666666666]
[3轮训练完成,训练集中 Loss:0.2244828560034789,Accuracy:0.9372619047619047]
[4轮训练完成,训练集中 Loss:0.18338089338725522,Accuracy:0.9476488095238095]
[5轮训练完成,训练集中 Loss:0.15651956990006424,Accuracy:0.9541071428571428]
[6轮训练完成,训练集中 Loss:0.1355396158483234,Accuracy:0.9603869047619048]
[7轮训练完成,训练集中 Loss:0.11753073033122789,Accuracy:0.965625]
[8轮训练完成,训练集中 Loss:0.10319345946135443,Accuracy:0.9705059523809524]
[9轮训练完成,训练集中 Loss:0.09024346410296857,Accuracy:0.974047619047619]
[10轮训练完成,训练集中 Loss:0.07875394061695606,Accuracy:0.9776785714285714]
训练完成!最佳训练轮次:10,该轮次验证集上的准确率:0.963452380952381

可视化损失值和准确率:

loss_acc_plot(train_losses, val_losses, train_accuracies, val_accuracies)

在这里插入图片描述

5. 结果分析

5.1 混淆矩阵

计算混淆矩阵,

from sklearn.metrics import confusion_matrix
cm = confusion_matrix(all_labels, all_predictions)
plt.figure(figsize=(5, 5))
sns.heatmap(cm, annot=True, fmt="d", cmap="Blues", cbar=False, 
            xticklabels=range(10), yticklabels=range(10))
plt.xlabel("Predicted Label")
plt.ylabel("True Label")
plt.title("Confusion Matrix");

在这里插入图片描述
横轴为预测类别,纵轴为实际类别。对标线上的值表示模型正确预测的样本数量,非对角线上的值表示模型错误预测的样本数量。对角线(1, 1)中的值900表示实际类别为1的样本中有900条被正确预测为1;(1, 4)中的值为1表示实际类别为1的样本中有1个样本被错误预测为4。

5.2 查看错误分类的样本

incorrect_images = []
incorrect_labels = []
predicted_labels = []
 
with torch.no_grad():
    for step, data in enumerate(val_loader):
        images, labels = data
        images = images.view(-1, 1, 28, 28)
        outputs = best_resnet_model(images.expand(-1, 3, -1, -1))
        _, predicted = torch.max(outputs, 1)
        for i in range(len(predicted)):
            if predicted[i] != labels[i]:
                incorrect_images.append(images[i])
                incorrect_labels.append(labels[i])
                predicted_labels.append(predicted[i])
 # 展示部分预测错误的样本
num_samples = 6
fig, axes = plt.subplots(nrows=2, ncols=num_samples // 2, figsize=(10, 6))
axes = axes.flatten()
 
for i in range(num_samples):
    ax = axes[i]
    img = incorrect_images[i].reshape(28,28)
    ax.imshow(img, cmap='gray')
    ax.set_title(f"True: {incorrect_labels[i]}, Pred: {predicted_labels[i]}")
    ax.axis('off')

在这里插入图片描述

6. 加载最佳模型

保存的最佳模型中,resnet18CNNFCNN在验证集中的准确率分别为98.58%97.21%96.35%,因此选择resnet18模型来预测测试集。

best_model = models.resnet18()
best_model.load_state_dict(torch.load("./resnet18_best_model.pth"))
predictions = []
with torch.no_grad():
    for data in test_loader:
        images = data.view(-1, 1, 28, 28).expand(-1, 3, -1, -1)
        outputs = best_model(images)
        _, predicted = torch.max(outputs, 1)
        predictions.extend(predicted.numpy())
# 保存预测结果
submission = pd.DataFrame({'ImageId': range(1, len(test) + 1), 'Label': predictions})
# submission.to_csv('/kaggle/working/submission.csv', index=False)
print('Submission file created!')

7. 参考文献

[1] kaggle:Digit Recognizer《手写数字识别》你的第一个图像识别竞赛项目
[2] Pytorch Tutorial for Deep Learning Lovers

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

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

相关文章

【书生2.5】XTuner 微调个人小助手认知

XTuner 微调个人小助手认知 【Intern Studio的gpu不足。本实验使用自有服务器】 1 环境安装 # 创建虚拟环境 conda create -n xtuner python3.10 -y# 激活虚拟环境&#xff08;注意&#xff1a;后续的所有操作都需要在这个虚拟环境中进行&#xff09; conda activate xtuner…

同样128个内核,AMD霄龙9755性能翻倍:Zen 5架构下的性能飞跃

近日&#xff0c;AMD在服务器处理器领域再次展示了其强大的技术实力&#xff0c;随着AMD EPYC“Turin”处理器发布日期的临近&#xff0c;其基准测试结果也开始浮出水面。硬件爱好者博主9550pro近期分享了AMD 128核EPYC 9755“Turin”处理器在7zip压缩/解压缩基准测试中的跑分数…

深圳MES系统在电子制造业中的应用体现

深圳是中国电子制造业的重要基地&#xff0c;许多电子制造企业在深圳地区都在应用MES系统来优化生产管理、提高生产效率和产品质量。深圳MES系统在电子制造业中的应用主要体现在以下几个方面&#xff1a; 生产计划管理&#xff1a;电子制造企业通常面临订单量大、产品种类多的情…

【知识图谱】4、LLM大模型结合neo4j图数据库实现AI问答的功能

昨天写了一篇文章&#xff0c;使用fastapi直接操作neo4j图数据库插入数据的例子&#xff0c; 本文实现LLM大模型结合neo4j图数据库实现AI问答功能。 废话不多说&#xff0c;先上代码 import gradio as gr from fastapi import FastAPI, HTTPException, Request from pydantic…

分享使用智狐联创AI助手生成的一个食品选择器网页

先看效果&#xff1a; 使用的是智狐超强模型&#xff0c;只有一个html网页&#xff0c;点击开始会有动画的选择动画。效果很不错&#xff0c;你可以更改成任意类似场景使用&#xff0c;如&#xff1a;抽奖等等。感兴趣的可以去搜索官网试试&#xff0c;也有免费模型。https://w…

【云故事探索】NO.8:揭秘餐饮行业龙头 SaaS 厂商神州商龙的全栈可观测实践

云布道师 天津市神州商龙科技股份有限公司成立于1998年&#xff0c;是一家专为餐饮行业提供数字化整体解决方案及咨询业务的高新技术企业。秉承着“产品是第一生产力”的发展理念&#xff0c;神州商龙凭借过硬的产品与服务质量&#xff0c;为呷哺呷哺、大董、新荣记、刘一手、巴…

需方软件供应链安全保障要求及开源场景对照自评表(下)

国标《信息安全技术 软件供应链安全要求》确立了软件供应链安全目标&#xff0c;规定了软件供应链安全风险管理要求和供需双方的组织管理和供应活动管理安全要求。 开源软件供应链作为软件供应链的一种特殊形式&#xff0c;该国标亦适用于指导开源软件供应链中的供需双方开展组…

完美解决LBP2900打印机安装驱动提示无法识别USB及连接错误等问题(附Win11全新安装支持及卸载方案)

目录 前言驱动获取方法简易全新安装方法安装完成后的验证方法常见驱动卸载方法 前言 LBP2900打印机虽然属于经典老旧款&#xff0c;但依旧好用不过时。老早之前也分享过心相关的解决方案&#xff0c;请戳&#xff1a;&#x1f449;旧版解决方案。但因年代久远还diss部分系统不通…

ssm“最多跑一次”微信小程序论文源码调试讲解

2系统相关技术 2.1 Java语言简介 Java是由SUN公司推出&#xff0c;该公司于2010年被oracle公司收购。Java本是印度尼西亚的一个叫做爪洼岛的英文名称&#xff0c;也因此得来java是一杯正冒着热气咖啡的标识。Java语言在移动互联网的大背景下具备了显著的优势和广阔的前景&…

HarmonyOS开发实战( Beta5版)应用TSJS高性能编程工具最佳实践

概述 本文参考业界标准&#xff0c;并结合应用TS&JS部分的性能优化实践经验&#xff0c;从应用编程指南、高性能编程实践、性能优化调试工具等维度&#xff0c;为应用开发者提供参考指导&#xff0c;助力开发者开发出高性能的应用。 本文主要提供TS&JS高性能编程实践…

什么是短视频矩阵?一个人能做好短视频矩阵营销吗?

很多人认为做短视频矩阵就是多账号、多发视频就可以了&#xff0c;但其实做短视频矩阵&#xff0c;并不仅仅是更多账号更多视频那么简单&#xff0c;它的核心在于搭建一个全方位的内容传播方式。这种方式包括三个方面&#xff1a;账号矩阵、平台矩阵和内容矩阵。 首先是账号矩阵…

TikTok运营:IP地址如何影响TikTok的内容运营?

TikTok作为外贸人宣传推广的重要平台&#xff0c;其运营成效与产品的实际转化率息息相关。然而&#xff0c;在TikTok的运营过程中&#xff0c;一个看似微不足道的元素—IP地址&#xff0c;却扮演着至关重要的角色。本文将深入探讨TikTok运营中IP地址的重要性&#xff0c;揭示其…

炫700头猪!所有长久的关系,都是讲条件的——早读(逆天打工人爬取热门微信文章解读)

A股呀A股你好狠呀 引言Python 代码第一篇 洞见 所有长久的关系&#xff0c;都是讲条件的第二篇 华为小黑子&#xff1f;结尾 &#xff08;这哥们是来炫他家的700头猪的吧&#xff0c;等我有钱了&#xff0c;包圆咯&#xff09; 引言 周末工作一天 休息一天 周六上班 那天晚上…

8Manage PM:掌握高效项目进度跟踪的关键策略

在负责管理众多项目的同时&#xff0c;管理人员最不希望遭遇因项目进度滞后而产生额外负担。 项目可能会因资源分配不当、范围蔓延以及其他意外中断等因素而遭受延误&#xff0c;有时这种延误甚至可能是长期的。 因此&#xff0c;掌握项目进度跟踪的方法相当重要。在整个项目…

Unity(2022.3.41LTS) - UI详细介绍-InputField(输入字段)

目录 零.简介 一、基本功能与用途 二、组件介绍 三、使用方法 四、优化和注意事项 零.简介 在 Unity 中&#xff0c;输入字段&#xff08;Input Field&#xff09;是一个非常实用的用户界面组件&#xff0c;以下是更详细的介绍&#xff0c;包括组件方面的深入分析&#x…

深入理解MySQL慢查询优化(2) -- SQL的执行流程

要优化一条SQL语句&#xff0c;要先理解SQL操作的执行流程 1. 不同SQL操作的执行流程 1.1 order by order by用于排序&#xff0c;如果用于排序的列上没有索引&#xff0c;就需要把整张表加载进内存进行排序&#xff0c;非常耗时。如果有索引&#xff0c;因为B树存储的数据本…

计算机毕业设计选题推荐-高校一卡通系统-Java/Python项目实战

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

史记——我与历史的缘妙

究天人之际&#xff0c;通古今之变&#xff0c;成一家之言。 注解&#xff1a;这句话出自司马迁《史记》之《报任安书》。意思是通过“史实”现象揭示本质,探究自然现象和人类社会之间的相依相对关系。通晓从古到今的社会的各种发展演变,进而寻找历代王朝兴衰成败之道理。通过…

【Unity编辑器扩展】SpriteAltas资源一键转换为TMP_SpriteAsset或Sprite图集

【Unity编辑器扩展】艺术字/自定义图片字体/TextMeshPro艺术字生成工具_unity 艺术字-CSDN博客 博文工具源码见GF_X自动化游戏开发框架&#xff1a;GitHub - sunsvip/GF_X: Unity GameFramework HybridCLR&#xff0c;Includes several automated editor extension tools, an …

vue 批量导出pdf 压缩包 zip

vue 批量导出pdf 压缩包 zip 使用插件 html2canvas jspdf jszip &#xff08;百度ai搜出来的是zip-js 这个没法安装&#xff09; file-saver 思路&#xff1a; 1.使用 html2canvasjspdf 将页面转图片转pdf&#xff08;这个怎么转的可以网上搜下很多&#xff09; 2.利用jszipfil…