基于ResNet50的手写符号识别系统
基于ResNet50的手写符号识别系统
项目概述
本项目实现了两个手写符号识别模型:
- ABCD字母识别模型:用于识别手写的A、B、C、D四个字母
- ✓×符号识别模型:用于识别手写的对勾(✓)和叉号(×)
两个模型均基于ResNet50预训练模型,采用迁移学习方式进行微调训练。项目包含完整的数据预处理、模型训练与评估、模型部署和预测的全流程实现。
数据集预处理
ABCD字母数据集
import tensorflow as tf
import os
import numpy as np
from sklearn.model_selection import train_test_split
import random# 数据路径定义
root_dir = "C:/Users/asus/Desktop/judge" # 你存放数据集的根目录
output_root = "balanced_emnist_abcd"
train_root = os.path.join(output_root, "train")
test_root = os.path.join(output_root, "test")# 创建输出目录
os.makedirs(train_root, exist_ok=True)
os.makedirs(test_root, exist_ok=True)# 筛选目标字符和标签
target_chars = ['A', 'B', 'C', 'D'] # 你要处理的字符类别
target_labels = [10, 11, 12, 13] # 与这些字符对应的标签(根据实际数据集标签设置)# 目标图像尺寸(统一为 64x64)
target_image_size = (64, 64)# 仅进行颜色反转的函数
def invert_image(image):"""反转图像颜色:黑色变白色,白色变黑色"""image = 255 - image # 图像反转return image# 随机水平和纵向拉伸(不超过原始图片的7%)
def random_scale(image):"""对图像进行随机拉伸,纵向或横向拉伸,最大拉伸 7%"""scale = random.uniform(0.93, 1.07) # 拉伸比例范围在 93% 到 107% 之间image = tf.image.resize(image, [int(target_image_size[0] * scale), int(target_image_size[1] * scale)])image = tf.image.resize(image, target_image_size) # 拉伸后再调整为目标尺寸return imagedef preprocess_image(img_path, label, invert=False, scale=False):"""图像预处理,包括颜色反转、尺寸调整、拉伸"""img = tf.io.read_file(img_path) # 读取图像文件img = tf.image.decode_png(img, channels=3) # 解码为 RGB 图像img = tf.image.resize(img, target_image_size) # 调整为目标尺寸if invert:img = invert_image(img) # 仅进行颜色反转if scale:img = random_scale(img) # 对图像进行随机拉伸img = tf.cast(img, tf.float32) / 255.0 # 归一化处理# 返回处理后的图像和标签return img, label, img_path# 保存图像函数
def save_image(image, label, img_path, target_folder, suffix=""):"""保存图像到指定路径,支持给文件名加后缀"""img_path_str = img_path.numpy().decode('utf-8') # 将 Tensor 转换为字符串img_name = os.path.basename(img_path_str) # 直接使用字符串的文件名img_name = img_name.replace('.png', f'_{suffix}.png') # 为增强图像加后缀output_img_path = os.path.join(target_folder, str(label), img_name)os.makedirs(os.path.dirname(output_img_path), exist_ok=True)tf.keras.preprocessing.image.save_img(output_img_path, image) # 保存为 PNG 格式# 遍历每个子集并处理图像
for target_char, target_label in zip(target_chars, target_labels):print(f"\nProcessing {target_char} images...")target_dir = os.path.join(root_dir, target_char) # 子文件夹路径image_paths = [os.path.join(target_dir, fname) for fname in os.listdir(target_dir) if fname.endswith('.png')]# 创建数据对列表image_label_pairs = [(img_path, target_label) for img_path in image_paths]# 将数据划分为训练集和测试集(80% 训练集,20% 测试集)train_pairs, test_pairs = train_test_split(image_label_pairs, test_size=0.2, random_state=42)# 创建训练集和测试集 TensorFlow 数据集train_dataset = tf.data.Dataset.from_generator(lambda: train_pairs,output_signature=(tf.TensorSpec(shape=(), dtype=tf.string), tf.TensorSpec(shape=(), dtype=tf.int64)))test_dataset = tf.data.Dataset.from_generator(lambda: test_pairs,output_signature=(tf.TensorSpec(shape=(), dtype=tf.string), tf.TensorSpec(shape=(), dtype=tf.int64)))# 预处理train_dataset = train_dataset.map(lambda img_path, label: preprocess_image(img_path, label, invert=True, scale=True))test_dataset = test_dataset.map(lambda img_path, label: preprocess_image(img_path, label, invert=True))# 生成增强数据,应用多种不同的增强方式,每种图像进行多次增强,并保存for image, label, img_path in train_dataset:for i in range(5): # 对每个图像进行 5 次增强# 对所有图像应用不同的增强(包括反转、拉伸等)processed_image, processed_label, processed_img_path = preprocess_image(img_path, label, invert=True, scale=True)save_image(processed_image, processed_label, processed_img_path, train_root, suffix=f"enhanced_{i}")# 遍历测试集并保存图像for image, label, img_path in test_dataset:save_image(image, label, img_path, test_root)print(f"Images for {target_char} saved successfully.")
预处理步骤:
- 图像尺寸统一调整为64×64
- 颜色反转处理,使得黑底白字变为白底黑字
- 随机拉伸增强,最大不超过原始图片的7%
- 将处理后的数据分为训练集(80%)和测试集(20%)
- 对训练数据进行数据增强,每个原始图像生成5个增强版本
- 对增强数据应用标准化处理
数据增强技术:
- 随机拉伸
- 颜色反转
- 尺寸调整
✓×符号数据集
import tensorflow as tf
import os
import numpy as np
from sklearn.model_selection import train_test_split
import random# 数据路径定义
root_dir = "D:/Grad/judge" # 你存放数据集的根目录
output_root = "balanced_emnist_duicuo"
train_root = os.path.join(output_root, "train")
test_root = os.path.join(output_root, "test")# 创建输出目录
os.makedirs(train_root, exist_ok=True)
os.makedirs(test_root, exist_ok=True)# 目标符号和标签
target_chars = ['dui', 'cuo'] # 符号✓和×对应的文件夹
target_labels = [0, 1] # 对应标签(0: 代表✓, 1: 代表×)# 目标图像尺寸(统一为 64x64)
target_image_size = (64, 64)# 随机水平和纵向拉伸(不超过原始图片的7%)
def random_scale(image):"""对图像进行随机拉伸,纵向或横向拉伸,最大拉伸 7%"""scale = random.uniform(0.93, 1.07) # 拉伸比例范围在 93% 到 107% 之间image = tf.image.resize(image, [int(target_image_size[0] * scale), int(target_image_size[1] * scale)])image = tf.image.resize(image, target_image_size) # 拉伸后再调整为目标尺寸return imagedef rotate_left_90(image):"""将图像整体向左旋转90度"""image = tf.image.rot90(image, k=3) # 旋转90度,k=3表示旋转90度return image# 随机裁剪(Random Crop)
def random_crop(image):"""对图像进行随机裁剪,裁剪比例范围为90%-100%"""crop_size = random.uniform(0.9, 1.0)cropped_image = tf.image.resize_with_crop_or_pad(image, target_height=int(target_image_size[0] * crop_size),target_width=int(target_image_size[1] * crop_size))return cropped_image# 放大符号
def zoom_symbol(image):"""对图像进行符号放大,模拟放大手写符号"""scale_factor = random.uniform(1.2, 1.5) # 随机选择放大倍数height, width, _ = image.shapenew_height = int(height * scale_factor)new_width = int(width * scale_factor)image = tf.image.resize(image, (new_height, new_width)) # 放大图像image = tf.image.resize_with_crop_or_pad(image, target_height=target_image_size[0],target_width=target_image_size[1]) # 缩小到目标大小return image# 图像颜色调整
def random_color_jitter(image):"""对图像进行颜色抖动,调整亮度、对比度、饱和度"""image = tf.image.random_brightness(image, max_delta=0.2) # 随机亮度调整image = tf.image.random_contrast(image, lower=0.7, upper=1.3) # 随机对比度调整image = tf.image.random_saturation(image, lower=0.7, upper=1.3) # 随机饱和度调整return image# 添加高斯模糊去噪
def random_gaussian_blur(image):"""对图像应用高斯模糊去噪"""image = tf.image.random_contrast(image, lower=0.8, upper=1.2) # 对比度微调return imagedef preprocess_image(img_path, label, scale=False, rotate=False, jitter=True, blur=False, zoom=False):"""图像预处理,包括尺寸调整、拉伸和旋转等"""img = tf.io.read_file(img_path) # 读取图像文件img = tf.image.decode_png(img, channels=3) # 解码为 RGB 图像img = tf.image.resize(img, target_image_size) # 调整为目标尺寸# 应用增强if scale:img = random_scale(img) # 对图像进行随机拉伸if rotate:print(f"Rotating image {img_path}...") # 打印调试信息img = rotate_left_90(img) # 对图像进行旋转if jitter:img = random_color_jitter(img) # 随机颜色调整if blur:img = random_gaussian_blur(img) # 高斯模糊去噪if zoom:img = zoom_symbol(img) # 放大符号区域img = tf.cast(img, tf.float32) / 255.0 # 归一化处理return img, label, img_path# 保存图像函数
def save_image(image, label, img_path, target_folder, suffix=""):"""保存图像到指定路径,支持给文件名加后缀"""img_path_str = img_path.numpy().decode('utf-8') # 将 Tensor 转换为字符串img_name = os.path.basename(img_path_str) # 直接使用字符串的文件名img_name = img_name.replace('.png', f'_{suffix}.png') # 为增强图像加后缀output_img_path = os.path.join(target_folder, str(label), img_name)os.makedirs(os.path.dirname(output_img_path), exist_ok=True)tf.keras.preprocessing.image.save_img(output_img_path, image) # 保存为 PNG 格式# 处理每个符号的数据
for target_char, target_label in zip(target_chars, target_labels):print(f"\nProcessing {target_char} images...")target_dir = os.path.join(root_dir, target_char) # 子文件夹路径image_paths = [os.path.join(target_dir, fname) for fname in os.listdir(target_dir) if fname.endswith('.png')]# 创建数据对列表image_label_pairs = [(img_path, target_label) for img_path in image_paths]# 将数据划分为训练集和测试集(80% 训练集,20% 测试集)train_pairs, test_pairs = train_test_split(image_label_pairs, test_size=0.2, random_state=42)# 创建训练集和测试集 TensorFlow 数据集train_dataset = tf.data.Dataset.from_generator(lambda: train_pairs,output_signature=(tf.TensorSpec(shape=(), dtype=tf.string), tf.TensorSpec(shape=(), dtype=tf.int64)))test_dataset = tf.data.Dataset.from_generator(lambda: test_pairs,output_signature=(tf.TensorSpec(shape=(), dtype=tf.string), tf.TensorSpec(shape=(), dtype=tf.int64)))# 预处理if target_char == 'dui': # 如果是"✓"符号,禁用旋转train_dataset = train_dataset.map(lambda img_path, label: preprocess_image(img_path, label, scale=True, rotate=False, jitter=True, blur=True,zoom=True))test_dataset = test_dataset.map(lambda img_path, label: preprocess_image(img_path, label, rotate=True)) # 添加旋转else: # 对"×"符号应用所有增强操作train_dataset = train_dataset.map(lambda img_path, label: preprocess_image(img_path, label, scale=True, rotate=True, jitter=True, blur=True,zoom=True))test_dataset = test_dataset.map(lambda img_path, label: preprocess_image(img_path, label, rotate=True))# 生成增强数据,应用多种不同的增强方式,每种图像进行多次增强,并保存for image, label, img_path in train_dataset:for i in range(4): # 对每个图像进行 4 次增强# 对所有图像应用不同的增强(包括拉伸、旋转、翻转、颜色调整等)processed_image, processed_label, processed_img_path = preprocess_image(img_path, label, scale=True,rotate=True, jitter=True, blur=True,zoom=True)save_image(processed_image, processed_label, processed_img_path, train_root, suffix=f"enhanced_{i}")# 遍历测试集并保存图像for image, label, img_path in test_dataset:processed_image, processed_label, processed_img_path = preprocess_image(img_path, label, rotate=True)save_image(processed_image, processed_label, processed_img_path, test_root)print(f"Images for {target_char} saved successfully.")
预处理步骤:
- 图像尺寸统一调整为64×64
- 按符号类型应用不同的增强策略:
- 对勾(✓)符号:颜色调整、缩放、模糊处理、放大
- 叉号(×)符号:额外应用了旋转增强
- 将处理后的数据分为训练集(80%)和测试集(20%)
- 对训练数据进行更丰富的数据增强,每个原始图像生成4个增强版本
数据增强技术:
- 随机水平拉伸
- 旋转处理
- 随机裁剪
- 符号放大
- 颜色调整
- 高斯模糊去噪
模型架构与训练
ABCD字母识别模型
模型架构:
import torch
import torch.nn as nn
from torch.optim import lr_scheduler
from torch.utils.data import DataLoader
from torchvision import models, transforms
import time
import os
from torchvision.datasets import ImageFolder
from PIL import Image
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.metrics import precision_score, recall_score, f1_score, confusion_matrix, roc_curve, auc
from sklearn.preprocessing import label_binarize
import numpy as np# 配置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei'] # 设置字体为 SimHei,支持中文
plt.rcParams['axes.unicode_minus'] = False # 解决负号问题# 配置设备
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')# 超参数
batch_size = 64
lr = 0.0005 # 初始学习率
total_epoch = 50 # 训练轮次
momentum = 0.9
patience = 10 # 早停的耐心次数# 数据路径
train_dir = "balanced_emnist_abcd/train"
test_dir = "balanced_emnist_abcd/test"# 数据增强和预处理
train_transform = transforms.Compose([transforms.Resize((64, 64)), # 统一尺寸transforms.ToTensor(), # 转为张量transforms.Normalize((0.5,), (0.5,)) # 归一化
])test_transform = transforms.Compose([transforms.Resize((64, 64)),transforms.ToTensor(),transforms.Normalize((0.5,), (0.5,)) # 归一化
])# 加载训练集和测试集
train_dataset = ImageFolder(root=train_dir, transform=train_transform)
test_dataset = ImageFolder(root=test_dir, transform=test_transform)# 创建数据加载器
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=4, pin_memory=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False, num_workers=4, pin_memory=True)# 加载预训练的 ResNet-50 模型
model = models.resnet50(weights=models.ResNet50_Weights.DEFAULT)
model.fc = nn.Sequential(nn.Linear(model.fc.in_features, 512),nn.ReLU(),nn.Dropout(0.3),nn.BatchNorm1d(512),nn.Linear(512, 4) # 输出 4 类(A, B, C, D)
)# 将模型移动到设备
model = model.to(device)# 损失函数与优化器
criterion = nn.CrossEntropyLoss() # 使用交叉熵损失
optimizer = torch.optim.Adam(model.parameters(), lr=lr)# 学习率调度器
scheduler = lr_scheduler.ReduceLROnPlateau(optimizer, 'min', patience=5, factor=0.5)# 早停机制变量
best_accuracy = 0.0
epochs_without_improvement = 0# 保存模型
checkpoint_dir = './checkpoints'
if not os.path.exists(checkpoint_dir):os.makedirs(checkpoint_dir)# 保存最佳模型
def save_model(epoch, model, accuracy, best_accuracy):if accuracy > best_accuracy:best_accuracy = accuracytorch.save(model.state_dict(), os.path.join(checkpoint_dir, f'best_model_epoch_{epoch + 1}.pth'))return best_accuracy# 训练函数
def train(epoch):model.train()running_loss = 0.0correct_train = 0total_train = 0for i, (images, labels) in enumerate(train_loader):images, labels = images.to(device), labels.to(device)# 前向传播outputs = model(images)# 计算损失loss = criterion(outputs, labels)optimizer.zero_grad()loss.backward() # 反向传播optimizer.step() # 更新参数running_loss += loss.item()# 计算训练准确率_, predicted = torch.max(outputs.data, 1)total_train += labels.size(0)correct_train += (predicted == labels).sum().item()if (i + 1) % 10 == 0:print(f"Epoch [{epoch + 1}/{total_epoch}], Step [{i + 1}/{len(train_loader)}], Loss: {loss.item():.4f}")train_accuracy = 100 * correct_train / total_trainprint(f"训练准确率 (Epoch {epoch + 1}): {train_accuracy:.2f}%")print(f"Epoch [{epoch + 1}], 平均损失: {running_loss / len(train_loader):.4f}")return running_loss / len(train_loader), train_accuracy # 返回损失和准确率# 测试函数
def test(epoch):global best_accuracy, epochs_without_improvementmodel.eval()correct, total = 0, 0test_loss = 0.0all_labels = []all_preds = []all_probs = []with torch.no_grad():for images, labels in test_loader:images, labels = images.to(device), labels.to(device)outputs = model(images)_, predicted = torch.max(outputs.data, 1)# 计算测试损失loss = criterion(outputs, labels)test_loss += loss.item()total += labels.size(0)correct += (predicted == labels).sum().item()# 收集所有标签和预测值,用于计算更多指标all_labels.extend(labels.cpu().numpy())all_preds.extend(predicted.cpu().numpy())all_probs.extend(torch.softmax(outputs, dim=1).cpu().numpy())accuracy = 100 * correct / totalprint(f"测试准确率 (Epoch {epoch + 1}): {accuracy:.2f}%")# 计算精确率、召回率和F1分数precision = precision_score(all_labels, all_preds, average='weighted')recall = recall_score(all_labels, all_preds, average='weighted')f1 = f1_score(all_labels, all_preds, average='weighted')print(f"精确率 (Precision): {precision:.4f}")print(f"召回率 (Recall): {recall:.4f}")print(f"F1分数 (F1 Score): {f1:.4f}")# 绘制混淆矩阵cm = confusion_matrix(all_labels, all_preds)plt.figure(figsize=(6, 5))sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=['A', 'B', 'C', 'D'],yticklabels=['A', 'B', 'C', 'D'])plt.xlabel('预测标签')plt.ylabel('真实标签')plt.title('混淆矩阵')plt.show()# 计算多类 AUC-ROC# 将标签转换为二进制形式all_labels_bin = label_binarize(all_labels, classes=[0, 1, 2, 3]) # 类别A=0, B=1, C=2, D=3fpr = {}tpr = {}roc_auc = {}# 为每个类别计算 AUC-ROC 曲线for i in range(4): # 有4个类别fpr[i], tpr[i], _ = roc_curve(all_labels_bin[:, i], [prob[i] for prob in all_probs])roc_auc[i] = auc(fpr[i], tpr[i])# 绘制每个类别的 ROC 曲线plt.figure(figsize=(6, 5))for i in range(4):plt.plot(fpr[i], tpr[i], lw=2, label='类别 %d (AUC = %0.2f)' % (i, roc_auc[i]))plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')plt.xlim([0.0, 1.0])plt.ylim([0.0, 1.05])plt.xlabel('假阳性率')plt.ylabel('真正率')plt.title('多类接收者操作特征 (ROC) 曲线')plt.legend(loc="lower right")plt.show()# 早停逻辑:如果准确率没有提升,增加`epochs_without_improvement`best_accuracy = save_model(epoch, model, accuracy, best_accuracy)if accuracy > best_accuracy:epochs_without_improvement = 0else:epochs_without_improvement += 1return test_loss / len(test_loader), accuracy # 返回测试损失和准确率# 训练和测试
def main():start_time = time.time()# 用于保存损失曲线的列表train_losses = []test_losses = []for epoch in range(total_epoch):train_loss, train_accuracy = train(epoch)test_loss, accuracy = test(epoch)# 保存损失train_losses.append(train_loss)test_losses.append(test_loss)# 通过 ReduceLROnPlateau 更新学习率scheduler.step(accuracy) # 使用 ReduceLROnPlateau 更新学习率# 早停判断:如果连续 `patience` 轮没有提升,停止训练if epochs_without_improvement >= patience:print("由于没有提升,提前停止训练。")breakend_time = time.time()print(f"训练完成,总耗时 {(end_time - start_time):.2f} 秒。")# 绘制损失曲线plt.figure(figsize=(10, 5))plt.plot(range(1, len(train_losses) + 1), train_losses, label="训练损失")plt.plot(range(1, len(test_losses) + 1), test_losses, label="测试损失")plt.xlabel('轮次 (Epoch)')plt.ylabel('损失 (Loss)')plt.title('训练与测试损失曲线')plt.legend()plt.show()if __name__ == "__main__":main()
训练参数:
- 批次大小:64
- 初始学习率:0.0005
- 优化器:Adam
- 损失函数:交叉熵损失
- 早停策略:10轮准确率无提升则停止
- 学习率调度:ReduceLROnPlateau
评估指标:
- 准确率(Accuracy)
- 精确率(Precision)
- 召回率(Recall)
- F1分数
- 混淆矩阵
- 多类AUC-ROC曲线
✓×符号识别模型
模型架构:
import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim import lr_scheduler
from torch.utils.data import DataLoader
from torchvision import transforms, models
from torchvision.datasets import ImageFolder
import os
import time
import matplotlib.pyplot as plt
from PIL import Image
from sklearn.metrics import precision_score, recall_score, f1_score, confusion_matrix, roc_curve, auc
import seaborn as sns
import numpy as np# 配置中文字体
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei'] # 设置字体为 Microsoft YaHei,支持中文
plt.rcParams['axes.unicode_minus'] = False # 解决负号问题# 配置设备
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')# 超参数
batch_size = 32
lr = 0.0005 # 初始学习率
total_epoch = 50 # 训练轮次
momentum = 0.9
patience = 10 # 早停的耐心次数
weight_decay = 0.0001 # L2正则化,防止过拟合
dropout_rate = 0.5 # Dropout值(防止过拟合)# 数据路径
train_dir = "balanced_emnist_duicuo/train" # 二分类数据路径
test_dir = "balanced_emnist_duicuo/test" # 二分类测试数据路径# 数据增强和预处理
train_transform = transforms.Compose([transforms.Resize((64, 64)), # 统一尺寸transforms.RandomHorizontalFlip(), # 随机水平翻转transforms.RandomRotation(20), # 随机旋转transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.2), # 随机颜色抖动transforms.RandomAffine(20, scale=(0.8, 1.2)), # 随机仿射变换(拉伸)transforms.ToTensor(), # 转为张量transforms.Normalize((0.5,), (0.5,)) # 归一化
])test_transform = transforms.Compose([transforms.Resize((64, 64)),transforms.ToTensor(),transforms.Normalize((0.5,), (0.5,)) # 归一化
])# 加载训练集和测试集
train_dataset = ImageFolder(root=train_dir, transform=train_transform)
test_dataset = ImageFolder(root=test_dir, transform=test_transform)# 创建数据加载器
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=4, pin_memory=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False, num_workers=4, pin_memory=True)# 使用预训练模型ResNet50
model = models.resnet50(pretrained=True) # 加载预训练的ResNet50
model.fc = nn.Sequential(nn.Linear(model.fc.in_features, 512),nn.ReLU(),nn.Dropout(dropout_rate),nn.Linear(512, 2) # 修改最后一层以适应二分类问题
)# 如果使用GPU
model = model.to(device)# 损失函数与优化器
criterion = nn.CrossEntropyLoss() # 使用交叉熵损失
optimizer = optim.Adam(model.parameters(), lr=lr, weight_decay=weight_decay) # L2正则化# 学习率调度器
scheduler = lr_scheduler.ReduceLROnPlateau(optimizer, 'min', patience=5, factor=0.5)# 早停机制变量
best_accuracy = 0.0
epochs_without_improvement = 0# 保存模型路径
checkpoint_dir = './checkpoints_2class' # 二分类模型保存路径
if not os.path.exists(checkpoint_dir):os.makedirs(checkpoint_dir)# 保存最佳模型
def save_model(epoch, model, accuracy, best_accuracy):if accuracy > best_accuracy:best_accuracy = accuracytorch.save(model.state_dict(), os.path.join(checkpoint_dir, f'best_model_epoch_{epoch + 1}.pth'))return best_accuracy# 训练函数
def train(epoch):model.train()running_loss = 0.0correct_train = 0total_train = 0for i, (images, labels) in enumerate(train_loader):images, labels = images.to(device), labels.to(device)# 前向传播outputs = model(images)# 计算损失loss = criterion(outputs, labels)optimizer.zero_grad()loss.backward() # 反向传播optimizer.step() # 更新参数running_loss += loss.item()# 计算训练准确率_, predicted = torch.max(outputs.data, 1)total_train += labels.size(0)correct_train += (predicted == labels).sum().item()if (i + 1) % 10 == 0:print(f"Epoch [{epoch + 1}/{total_epoch}], Step [{i + 1}/{len(train_loader)}], Loss: {loss.item():.4f}")train_accuracy = 100 * correct_train / total_trainprint(f"训练准确率 (Epoch {epoch + 1}): {train_accuracy:.2f}%")print(f"Epoch [{epoch + 1}], 平均损失: {running_loss / len(train_loader):.4f}")return running_loss / len(train_loader), train_accuracy # 返回损失和准确率# 测试函数
def test(epoch):global best_accuracy, epochs_without_improvementmodel.eval()correct, total = 0, 0test_loss = 0.0all_labels = []all_preds = []all_probs = []with torch.no_grad():for images, labels in test_loader:images, labels = images.to(device), labels.to(device)outputs = model(images)_, predicted = torch.max(outputs.data, 1)# 计算测试损失loss = criterion(outputs, labels)test_loss += loss.item()total += labels.size(0)correct += (predicted == labels).sum().item()# 收集所有标签和预测值,用于计算更多指标all_labels.extend(labels.cpu().numpy())all_preds.extend(predicted.cpu().numpy())all_probs.extend(torch.softmax(outputs, dim=1).cpu().numpy())accuracy = 100 * correct / totalprint(f"测试准确率 (Epoch {epoch + 1}): {accuracy:.2f}%")# 计算精确率、召回率和F1分数precision = precision_score(all_labels, all_preds, average='weighted')recall = recall_score(all_labels, all_preds, average='weighted')f1 = f1_score(all_labels, all_preds, average='weighted')print(f"精确率 (Precision): {precision:.4f}")print(f"召回率 (Recall): {recall:.4f}")print(f"F1分数 (F1 Score): {f1:.4f}")# 绘制混淆矩阵cm = confusion_matrix(all_labels, all_preds)plt.figure(figsize=(6, 5))sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=['✓', '×'], yticklabels=['✓', '×'])plt.xlabel('预测标签')plt.ylabel('真实标签')plt.title('混淆矩阵')plt.show()# 计算AUC-ROCfpr, tpr, _ = roc_curve(all_labels, [prob[1] for prob in all_probs]) # 以类别1的概率作为正类roc_auc = auc(fpr, tpr)# 绘制AUC-ROC曲线plt.figure(figsize=(6, 5))plt.plot(fpr, tpr, color='darkorange', lw=2, label='ROC曲线 (AUC = %0.2f)' % roc_auc)plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')plt.xlim([0.0, 1.0])plt.ylim([0.0, 1.05])plt.xlabel('假阳性率')plt.ylabel('真正率')plt.title('接收者操作特征 (ROC)')plt.legend(loc="lower right")plt.show()# 早停逻辑:如果准确率没有提升,增加epochs_without_improvementbest_accuracy = save_model(epoch, model, accuracy, best_accuracy)if accuracy > best_accuracy:epochs_without_improvement = 0else:epochs_without_improvement += 1return test_loss / len(test_loader), accuracy # 返回测试损失和准确率# 训练和测试
def main():start_time = time.time()# 用于保存损失和准确率曲线的列表train_losses = []test_losses = []train_accuracies = []test_accuracies = []for epoch in range(total_epoch):train_loss, train_accuracy = train(epoch)test_loss, accuracy = test(epoch)# 保存损失和准确率train_losses.append(train_loss)test_losses.append(test_loss)train_accuracies.append(train_accuracy)test_accuracies.append(accuracy)# 通过 ReduceLROnPlateau 更新学习率scheduler.step(accuracy) # 使用 ReduceLROnPlateau 更新学习率# 早停判断:如果连续 patience 轮没有提升,停止训练if epochs_without_improvement >= patience:print("由于没有提升,提前停止训练。")breakend_time = time.time()print(f"训练完成,总耗时 {(end_time - start_time):.2f} 秒。")# 绘制损失曲线plt.figure(figsize=(10, 5))plt.plot(range(1, len(train_losses) + 1), train_losses, label="训练损失")plt.plot(range(1, len(test_losses) + 1), test_losses, label="测试损失")plt.xlabel('轮次 (Epoch)')plt.ylabel('损失 (Loss)')plt.title('训练与测试损失曲线')plt.legend()plt.show()# 绘制准确率曲线plt.figure(figsize=(10, 5))plt.plot(range(1, len(train_accuracies) + 1), train_accuracies, label="训练准确率")plt.plot(range(1, len(test_accuracies) + 1), test_accuracies, label="测试准确率")plt.xlabel('轮次 (Epoch)')plt.ylabel('准确率 (Accuracy)')plt.title('训练与测试准确率曲线')plt.legend()plt.show()if __name__ == "__main__":main()
训练参数:
- 批次大小:32
- 初始学习率:0.0005
- 优化器:Adam(带L2正则化)
- 正则化参数:0.0001
- 损失函数:交叉熵损失
- Dropout率:0.5
- 早停策略:10轮准确率无提升则停止
- 学习率调度:ReduceLROnPlateau
评估指标:
- 准确率(Accuracy)
- 精确率(Precision)
- 召回率(Recall)
- F1分数
- 混淆矩阵
- 二分类AUC-ROC曲线
模型预测与应用
ABCD字母预测(test2.py)
预处理与预测流程:
- 加载训练好的模型
- 图像预处理:
- 顺时针旋转90度
- 调整为64×64大小
- 转换为RGB格式
- 标准化处理
- 模型推理获取预测类别
使用示例:
import torch
from torchvision import transforms, models
from PIL import Image
import os# 配置设备
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')# 加载训练好的模型
model = models.resnet50(weights=models.ResNet50_Weights.DEFAULT) # 使用预训练的ResNet50
model.fc = torch.nn.Sequential(torch.nn.Linear(model.fc.in_features, 512),torch.nn.ReLU(),torch.nn.Dropout(0.3),torch.nn.BatchNorm1d(512),torch.nn.Linear(512, 4) # 输出4类(A, B, C, D)
)# 加载最好的模型
checkpoint_path = './checkpoints/best_model_epoch_2.pth' # 请根据实际路径修改
model.load_state_dict(torch.load(checkpoint_path))
model = model.to(device)
model.eval() # 设置为评估模式# 图片转换方式
transform = transforms.Compose([transforms.Lambda(lambda x: x.rotate(-90, expand=True)), # 顺时针旋转90度transforms.Resize((64, 64)),transforms.Lambda(lambda x: x.convert("RGB")), # 强制转换为RGB格式transforms.ToTensor(),transforms.Normalize((0.5,), (0.5,))
])# 测试图片所在目录
image_dir = r"./exam_13" # 请根据实际路径修改
image_files = os.listdir(image_dir)[:20] # 获取目录下的前20张图片
# 处理并保存第一张旋转后的图片
if image_files:first_image_path = os.path.join(image_dir, image_files[0])original_image = Image.open(first_image_path)# 旋转并保存到根目录rotated_image = original_image.rotate(-90, expand=True) # 也修改这里为顺时针旋转90度save_path = os.path.join(os.getcwd(), "rotated_sample.jpg")rotated_image.save(save_path)print(f"已将旋转后的第一张图片保存到: {save_path}")
# 类别标签映射
class_labels = {0: 'A', 1: 'B', 2: 'C', 3: 'D'}# 遍历每张图片进行预测
for image_name in image_files:image_path = os.path.join(image_dir, image_name)# 加载图片并转换为RGB格式(解决RGBA问题)image = Image.open(image_path).convert("RGB") # 强制转换为RGB格式image = transform(image).unsqueeze(0).to(device) # 预处理并增加batch维度# 进行推理with torch.no_grad():outputs = model(image)_, predicted = torch.max(outputs.data, 1) # 获取预测标签# 打印结果predicted_label = predicted.item()print(f"图片 {image_name} 的预测结果是:{class_labels[predicted_label]}")
✓×符号预测
预处理与预测流程:
- 加载训练好的二分类模型
- 图像预处理:
- 调整为64×64大小
- 标准化处理
- 模型推理获取预测类别
- 将数字标签(0,1)映射为符号(✓,×)
使用示例:
from ye import model, device
import torch
from torchvision import models, transforms
from PIL import Imagedef predict_image(image_path):# 重新加载模型model.load_state_dict(torch.load('./checkpoints_2class/best_model_epoch_7.pth')) # 使用最新训练的模型model.eval()# 图像预处理transform = transforms.Compose([transforms.Resize((64, 64)), # 统一尺寸transforms.ToTensor(), # 转为张量transforms.Normalize((0.5,), (0.5,)) # 归一化])img = Image.open(image_path).convert('RGB')img = transform(img).unsqueeze(0).to(device) # 增加batch维度# 预测with torch.no_grad():outputs = model(img)_, predicted = torch.max(outputs, 1)# 显示预测结果class_names = ['A', 'B', 'C', 'D']predicted_class = class_names[predicted.item()]print(f"Predicted Class: {predicted_class}")
if __name__ == "__main__":predict_image("./exam_13/cell_021.png")
模型性能与评估
两个模型均采用了以下方法进行性能评估:
- 准确率/损失曲线:展示训练过程中模型性能的变化趋势
- 混淆矩阵:直观显示分类结果,识别模型的强项和弱项
- ROC曲线和AUC指标:评估模型的判别能力
- 精确率、召回率和F1分数:全面评估模型性能
技术难点与解决方案
-
数据预处理挑战:
- 手写符号多样性大,样式变化多
- 解决方案:应用多种数据增强技术,提高模型泛化能力
-
图像质量问题:
- 手写图像可能存在模糊、RGBA格式等问题
- 解决方案:统一转换为RGB格式,应用适当的预处理步骤
-
旋转角度校正:
- 手写图像方向可能不一致
- 解决方案:根据需要添加旋转预处理,确保图像方向一致
-
类别不平衡:
- 不同字母或符号的样本数量可能不均衡
- 解决方案:数据增强时对少数类别进行更多增强
未来改进方向
- 增加更多字母和符号类别,扩展识别范围
- 尝试其他深度学习模型架构,如EfficientNet或Vision Transformer
- 添加检测模块,实现自动定位和识别
- 开发移动端应用,实现实时手写识别
- 优化模型大小,提高推理速度
总结
本项目成功实现了两个手写符号识别模型,分别针对ABCD字母和✓×符号。通过迁移学习和精心设计的数据预处理流程,模型展现出良好的识别性能。此外,项目提供了完整的训练、评估和预测代码,可以作为其他手写符号识别任务的参考和基础。
相关文章:
基于ResNet50的手写符号识别系统
基于ResNet50的手写符号识别系统 项目概述 本项目实现了两个手写符号识别模型: ABCD字母识别模型:用于识别手写的A、B、C、D四个字母✓符号识别模型:用于识别手写的对勾(✓)和叉号() 两个模型均基于ResNet50预训练模型,采用迁…...
SpringBoot教学管理平台源码设计开发
概述 基于SpringBoot框架开发的教学管理平台完整项目,帮助开发者快速搭建在线教育平台。该系统包含学生端、教师端和管理后台,实现了课程管理、随堂测试、作业提交等核心功能,是学习SpringBoot开发的优质案例。 主要内容 1. 系统架…...
C++负载均衡远程调用学习之集成测试与自动启动脚本
目录 01 Lars-LbAgentV0.7-route_lb获取路由全部主机信息 02 Lars-LbAgentV0.7-API模块注册功能实现和测试工 03 Lars-LbAgentV0.7-项目构建工具 04 Lars-LbAgentV0.7-启动工具脚本实现 05 Lars-有关fd泄露的调试办法 06 Lars-qps性能测试 07 git企业开发基本流程 01 Lar…...
双ISP(双互联网服务提供商)
目录 核心作用 适用场景 实现方式 优缺点 假设一家外贸公司 双ISP(双互联网服务提供商) 是指用户同时接入两个不同的网络服务提供商(Internet Service Provider),通过冗余设计或负载均衡技术,提升网络…...
网工实验——静态路由与BFD联动
网络拓扑图 实验目的: PC与Server通信的时候主要走上面,当主用电路失效的时候走下面 设备: 一台PC主机 一台Server服务器 两台Router路由器 一台S3700交换机 配置 1.配置PC和Server的IP地址 PC Server 2.配置路由器 R3配置对应接口…...
谷歌在即将举行的I/O大会之前,意外泄露了其全新设计语言“Material 3 Expressive”的细节
每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…...
【网络原理】IP协议
目录 编辑 一. IP协议的作用 二. IP协议报头 三. 拆包和组包 四. 地址管理 1)动态分配IP地址 2)NAT机制(网络地址映射) 3) IPV6 五. IP转换 1)客户端到服务器 2)服务器到客户端 六…...
【Python 文件I/O】
Python 的文件 I/O 操作是数据处理的基础技能,涉及文件的读写、路径管理、异常处理等核心功能。以下是文件 I/O 的核心知识点: 一、基础文件操作 1. 打开文件 # 通用模式:r(读)/w(写)/a(追加) b(二进制)/t(文本,默认) f open(…...
torchrun单机多卡运行
torchrun命令使用 torchrun示例 export CUDA_VISIBLE_DEVICES0,1,2 nohup torchrun \--nproc_per_node3 \--nnodes1 \--node_rank0 \--master_addr"127.0.0.1" \--master_port1225 \/data/train.py \--batch_size 32 \--size 320运行如上sh命令发现,即 …...
数据管理平台是什么?企业应如何做好数据化管理?
目录 一、数据管理是什么? 二、数据管理平台有哪些作用? 1. 数据采集与整合 2. 数据清洗与预处理 3. 数据分析与挖掘 4. 数据共享与协作 三、企业应如何做好数据化管理? 1. 建立数据管理战略 2. 完善数据管理制度 3. 培养数据管理人…...
GuassDB如何创建兼容MySQL语法的数据库
GaussDB简介 GaussDB是由华为推出的一款全面支持OLTP和OLAP的分布式关系型数据库管理系统。它采用了分布式架构和高可靠性设计,可以满足大规模数据存储和处理的需求。GaussDB具有高性能、高可靠性和可扩展性等特点,适用于各种复杂的业务场景,…...
Qt国际化实战--精通Qt Linguist工具链
概述 在全球化的今天,软件产品需要支持多种语言和地区,以满足来自世界各地用户的需求。Qt框架提供了一套完整的工具集来帮助开发者实现应用程序的国际化(i18n)和本地化(l10n),其中最核心的就是Qt Linguist工具链 关于国际化与本地化 国际化(i18n): 指的是设计和开发…...
C++内联函数
总结:内联函数是把函数变成一个代码块直接塞入源程序中,省去了一些参数传递和栈操作(省时间),但是同时又增加了代码的大小(因为原本的函数只有会使用指针指向该函数,但是内联函数是直接塞入&…...
26届秋招收割offer指南
26届暑期实习已经陆续启动,这也意味着对于26届的同学们来说,“找工作”已经提上了日程。为了帮助大家更好地准备暑期实习和秋招,本期主要从时间线、学习路线、核心知识点及投递几方面给大家介绍,希望能为大家提供一些实用的建议和…...
Python之内省与反射应用
Python之内省与反射应用 Python作为一门动态语言,具备了强大的内省(Introspection)与反射(Reflection)机制。这两个概念在运行时查看对象的属性、类型、方法等信息,甚至可以动态调用方法或修改对象的属性。…...
第三章:langchain加载word文档构建RAG检索教程(基于FAISS库为例)
文章目录 前言一、载入文档(word)1、文档载入代码2、文档载入数据解读(Docx2txtLoader方法)输入数据输出文本内容 3、Docx2txtLoader底层代码文档读取解读Docx2txtLoader底层源码示例文档读取输出结果 二、文本分割1、文本分割代码…...
球速最快的是哪种球类运动·棒球1号位
在体育运动中,球速最快的项目与棒球结合来看,可以分两个角度解读: 一、球速最快的运动项目 羽毛球以426公里/小时(吉尼斯纪录)的杀球速度位列榜首,远超棒球投球速度。其极速源于: 羽毛球拍甜区…...
TVM中Python如何和C++联调?
1. 编译 Debug 版本 # 在项目根目录下创建构建目录(若尚未创建) mkdir -p build && cd build# 配置 Debug 构建 cmake -DCMAKE_BUILD_TYPEDebug ..# 编译(根据 CPU 核心数调整 -j 参数) make -j$(nproc)2. 获取 Python 进…...
从零实现基于Transformer的英译汉任务
1. model.py(用的是上一篇文章的代码:从0搭建Transformer-CSDN博客) import torch import torch.nn as nn import mathclass PositionalEncoding(nn.Module):def __init__ (self, d_model, dropout, max_len5000):super(PositionalEncoding,…...
在 PyTorch 中借助 GloVe 词嵌入完成情感分析
一. Glove 词嵌入原理 GloVe是一种学习词嵌入的方法,它希望拟合给定上下文单词i时单词j出现的次数。使用的误差函数为: 其中N是词汇表大小,是线性层参数, 是词嵌入。f(x)是权重项,用于平衡不同频率的单词对误差的影响…...
大数据应用开发和项目实战-电商双11美妆数据分析
数据初步了解 (head出现,意味着只出现前5行,如果只出现后面几行就是tail) info shape describe 数据清洗 重复值处理 这个重复值是否去掉要看实际情况,比如说:昨天卖了5瓶七喜,今天卖了5瓶七…...
web服务
一、nginx的安装与启用 nginx的安装 开源版本的Nginx官网:http://nginx.org Nginx在安装的过程中可以选择源码安装也可以选择使用软件包安装 源码安装下载相应的源码压缩包解压后编译完成安装 软件安装包可以使用rpm或者apt命令进行安装,也可以使用dnf…...
在Spring Boot 中如何配置MongoDB的副本集 (Replica Set) 或分片集群 (Sharded Cluster)?
在 Spring Boot 中配置 MongoDB 副本集 (Replica Set) 或分片集群 (Sharded Cluster) 非常相似,主要区别在于连接字符串 (URI) 中提供的主机列表和一些特定选项。 最常的方式是使用 spring.data.mongodb.uri 属性配置连接字符串。 1. 连接到 MongoDB 副本集 (Repl…...
Oracle中游标和集合的定义查询及取值
在 Oracle 存储过程中,使用游标处理自定义数据行类型时,可以通过 定义记录类型(RECORD) 和 游标(CURSOR) 结合实现。 1. 定义自定义记录类型 使用 TYPE … IS RECORD 定义自定义行数据类型: DE…...
Python企业级MySQL数据库开发实战指南
简介 Python与MySQL的完美结合是现代Web应用和数据分析系统的基石,能够创建高效稳定的企业级数据库解决方案。本文将从零开始,全面介绍如何使用Python连接MySQL数据库,设计健壮的表结构,实现CRUD操作,并掌握连接池管理、事务处理、批量操作和防止SQL注入等企业级开发核心…...
【LLM】Open WebUI 使用指南:详细图文教程
Open WebUI 是一个开源的、可扩展且用户友好的自托管 AI 平台,专为生成式人工智能模型交互而设计。 Open WebUI 旨在为用户提供一个简单易用、功能强大且高度定制化的界面,使其能够轻松与各种 AI 模型(如文本生成、图像生成、语音识别等)进行交互。 一、安装与初始化配置 扩…...
前端封装框架依赖管理全攻略:构建轻量可维护的私有框架
前端封装框架依赖管理全攻略:构建轻量可维护的私有框架 前言 在自研前端框架的开发中,依赖管理是决定框架可用性的关键因素。不合理的依赖设计会导致: 项目体积膨胀:重复依赖使最终打包体积增加30%版本地狱:不同项目…...
Listremove数据时报错:Caused by: java.lang.UnsupportedOperationException
看了二哥的foreach陷阱后,自己也遇见了需要循环删除元素的情况,立马想到了当时自己阴差阳错的避开所有坑的解决方式:先倒序遍历,再删除。之前好使,但是这次不好使了,报错Caused by: java.lang.UnsupportedO…...
互联网大厂Java求职面试:云原生与AI融合下的系统设计挑战-1
互联网大厂Java求职面试:云原生与AI融合下的系统设计挑战-1 在当今云计算和人工智能迅猛发展的背景下,互联网大厂对Java工程师的要求已从传统的单体架构和业务逻辑处理,转向了更复杂的云原生架构设计、AI模型集成以及高并发系统的性能优化能…...
并发设计模式实战系列(16):屏障(Barrier)
🌟 大家好,我是摘星! 🌟 今天为大家带来的是并发设计模式实战系列,第十六章屏障(Barrier),废话不多说直接开始~ 目录 一、核心原理深度拆解 1. 屏障的同步机制 2. 关键参数 二…...
pywinauto通过图片定位怎么更加精准的识别图片?
pywinauto通过图片定位怎么更加精准的识别图片? 可以使用置信度的配置,添加了对比图片相似程度达到多少就可以认为是合适的定位图片 import time from time import sleep from pywinauto.application import Application from pywinauto.keyboard impo…...
Spring Cloud Stream集成RocketMQ(kafka/rabbitMQ通用)
什么是Spring Cloud Stream Spring Cloud Stream 是 Spring 生态系统中的一个框架,用于简化构建消息驱动微服务的开发和集成。它通过抽象化的方式将消息中间件(如 RabbitMQ、Kafka、RocketMQ 等)的复杂通信逻辑封装成简单的编程模型…...
基于docker使用showdoc搭建API开发文档服务器
以下是基于 Docker 快速搭建 ShowDoc API 文档服务器的完整指南,包含优化配置和常见问题解决方案: 1. 快速部署方案 # 创建数据目录(确保权限) mkdir -p /showdoc_data/html && chmod 777 -R /showdoc_data# 一键启动容器…...
Vision-Language Models (VLMs) 视觉语言模型的技术背景、应用场景和商业前景(Grok3 DeepSearch模式回答)
prompt: 你是一位文笔精湛、十分专业的技术博客作者,你将从技术背景、应用场景和商业前景等多个维度去向读者介绍Vision-Language Models 关键要点 研究表明,视觉语言模型(VLMs)是多模态AI系统,能同时处理视觉和文本数…...
OpenAI大变革!继续与微软等,以非营利模式冲击AGI
今天凌晨2点,OpenAI宣布,将继续由非营利组织控制;现有的营利性实体将转变为一家公共利益公司;非营利组织将控制该公共利益公司,并成为其重要的持股方。 这也就是说OpenAI曾在去年提到的由非营利性转变成营利性公司&am…...
Ubuntu打开中文文本乱码
文章目录 中文乱码问题修复乱码系统字符编码修改文本编码修改vim乱码 utf-8编码原理特点应用场景与其他编码的转换 iso-8859-1基本信息字符涵盖应用场景与其他编码的关系 ubuntu打开文本出现乱码,可能是编码没设置对。 中文乱码问题 使用vim打开文本,或…...
车载通信网络安全:挑战与解决方案
1. 简介 当今时代见证了车载汽车技术的巨大发展,因为现代智能汽车可以被视为具有出色外部基础设施连接能力的信息物理系统 [ 1 ]。车载技术支持的现代智能汽车不应被视为类似于机械系统,而是由数百万行复杂代码组成的集成架构,可为车内乘客提…...
【Linux系统】读写锁
读者写者问题 重点 读者写者问题是并发编程中的经典问题,主要研究多个进程或线程对共享数据进行读和写操作时如何实现同步和互斥,以保证数据的一致性和操作的正确性 。 问题核心要点 同步与互斥:需要确保多个读者可以同时读共享数据&#…...
springBoot中自定义一个validation注解,实现指定枚举值校验
缘由 在后台写接口的时候,经常会出现dto某个属性是映射到一个枚举的情况。有时候还会出现只能映射到枚举类中部分枚举值的情况。以前都是在service里面自行判断,很多地方代码冗余,所以就想着弄一个自定义的validation注解来实现。 例如下面某…...
【Python】--装饰器
装饰器(Decorator)本质上是一个返回函数的函数 主要作用是:在不修改原函数代码的前提下,给函数增加额外的功能 比如:增加业务,日志记录、权限验证、执行时间统计、缓存等场景 my_decorator def func():pas…...
排序算法——堆排序
一、介绍 「堆排序heapsort」是一种基于堆数据结构实现的高效排序算法。我们可以利用已经学过的“建堆操作”和“元素出堆操作”实现堆排序。 1. 输入数组并建立小顶堆,此时最小元素位于堆顶。 2. 不断执行出堆操作,依次记录出堆元素,即可得…...
Day111 | 灵神 | 二叉树 | 验证二叉搜索树
Day111 | 灵神 | 二叉树 | 验证二叉搜索树 98.验证二叉搜索树 98. 验证二叉搜索树 - 力扣(LeetCode) 方法一:前序遍历 递归函数传入合法的左右边界,只有当前结点是合法的边界,才是二叉搜索树,否则就返回…...
软考-软件设计师中级备考 13、刷题 数据结构
倒计时17天时间不多了,数据库、UML、等知识点有基础直接略过,法律全靠考前的一两天刷题,英语直接放弃。 一、数据结构:链表、栈、队列、数组、哈希表、树、图 1、关于链表操作,说法正确的是: A)新增一个头…...
【5G通信】天线调整
在天线工程中,机械下倾角、电子下倾角和数字下倾角是调整天线波束指向的不同技术手段,其核心区别在于实现方式和灵活性: 1. 机械下倾角(Mechanical Downtilt) 定义:通过物理调整天线的安装角度,…...
Kafka的Log Compaction原理是什么?
Kafka的Log Compaction(日志压缩)是一种独特的数据保留策略,其核心原理是保留每个key的最新有效记录。以下是关键原理分点说明: 1. 键值保留机制 通过扫描所有消息的key,仅保留每个key对应的最新value值。例如&#…...
嵌入式面试八股文(十四)·内存管理机制、优先级继承机制以及优先级翻转
目录 1. 内存管理算法(五种内存管理机制) 1.1 heap_1.c 1.2 heap_2.c 1.3 heap_3.c 1.4 heap_4.c 1.5 heap_5.c 1.6 总结 2. STM32通知寄存器有哪些? 2.1 核心寄存器组(Cortex-M) 2.2 特殊功能寄存…...
深度剖析:可视化如何重塑驾驶舱信息交互模式
为什么你开车时总觉得“信息太多却抓不住重点”? 今天的汽车早已不是单纯的交通工具,而是一个高度集成的信息终端。从导航、油耗、胎压到自动驾驶提示,各种数据不断涌进驾驶舱。 但问题也随之而来: 关键信息被淹没在一堆图标里…...
app根据蓝牙名字不同,匹配不同的产品型号,显示对应的UI界面
在开发一个 App 时,如果希望根据蓝牙设备名称(Bluetooth Name)的不同,自动匹配不同的产品型号,并显示对应的 UI 界面,可以按照以下思路来实现: ✅ 功能目标 扫描并连接蓝牙设备;获取…...
数据结构 --- 栈
1.栈的初始化 2.入栈 3.出栈 4.取出栈顶元素 5.获取栈中有效元素个数 6.栈的销毁 栈:⼀种特殊的线性表,其只允许在固定的⼀端进⾏插⼊和删除元素操作。进⾏数据插⼊和删除操作 的⼀端称为栈顶,另⼀端称为栈底。栈中的数据元素遵守后进先…...
37-算法打卡-栈与队列-滑动窗口最大值-leetcode(239)-第三十七天
1 题目地址 239. 滑动窗口最大值 - 力扣(LeetCode)239. 滑动窗口最大值 - 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回 滑…...