任务:使用CNN把食物图片分为11类(不能使用预训练的模型)。此任务很耗时,一次训练至少1h,所以要利用好Kaggle notebook中Save Version功能,并行训练节省时间。

基准

  1. Simple : 0.50099
  2. Medium : 0.73207 Training Augmentation + Train Longer
  3. Strong : 0.81872 Training Augmentation + Model Design + Train Looonger (+ Cross Validation + Ensemble)
  4. Boss : 0.88446 Training Augmentation + Model Design +Test Time Augmentation + Train Looonger (+ Cross Validation + Ensemble)

结果

在这里插入图片描述

优化思路

  1. Residual Connection(残差连接)
  2. 用checkpoint保存模型后继续训练
  3. 使用已存在的模型:Visit torchvision.models for a list of model structures, or go to timm for the latest model structures
  4. data augmentation (数据增强): Modify the image data so non-identical inputs are given to the model each epoch, to prevent overfitting of the model; Visit torchvision.transforms for a list of choices and their corresponding effect. Diversity is encouraged! Usually, stacking multiple transformations leads to better results.
  5. 使用mixup实现数据增强,不过需要改写torch.utils.Dataset, getitem(),写自己的CrossEntropyLoss支持多个标签
  6. Test Time Augmentation
  7. Cross Validation: uses different portions of the data to validate and train a model on different iterations
  8. 融合现有的训练和测试数据,增加训练数据比例(Currently, train : validation ~ 3 : 1)
  9. Ensemble:Average of logits or probability或Voting
  10. spatial transformer layer
  11. Image Normalization与batch Normalization

原代码中的错误及其他改进

  1. 如果直接运行sample code,不能正确显示训练进度条报错如下。这个错误是由于 Jupyter widgets 的版本不兼容导致的。查了多种解决方式都无效。最后将原代码中from tqdm.auto import tqdm改为from tqdm import tqdm后解决了,tqdm.auto本来是用来自动识别当前运行环境,但在这里反而出错,只能说此功能还不够完善。

Failed to load model class ‘HBoxModel’ from module ‘@jupyter-widgets/controls’ Error: Module @jupyter-widgets/controls, version ^1.5.0 is not registered, however, 2.0.0 is at …

  1. 加入以下代码,显示训练时间、最佳准确率。
#在训练开始前加入
import time
total_start = time.time()#在训练完成后加入
total_time = time.time() - total_start
print(f'\nTotal time: {int(total_time // 60)} min {int(total_time % 60)} sec',end=', ')
print(f'best accuracy: {best_acc:.5f}')

实验过程

  1. 直接运行sample code,epoch = 24,耗时55min,效果不错,best acc = 0.69254,Kaggle private score = 0.70066,逼近了Medium水平。
  2. data augmentation,修改test_tfm,增加中间三行代码,Total time: 53 min 51 sec, best accuracy: 0.47737
test_tfm = transforms.Compose([transforms.Resize((128, 128)),transforms.RandomResizedCrop(128), # 随机裁剪区域,缩放到128*128(增加位置鲁棒性)transforms.RandomHorizontalFlip(), # 水平翻转,默认50%概率transforms.ColorJitter(brightness=0.2, contrast=0.2), # 随机调整亮度和对比度transforms.ToTensor(),
])
  1. test_tfm加入Image Standarlization,Total time: 54 min 58 sec, best accuracy: 0.44407
test_tfm = transforms.Compose([transforms.Resize((128, 128)),transforms.RandomResizedCrop(128), # 随机裁剪区域,缩放到128*128(增加位置鲁棒性)transforms.RandomHorizontalFlip(), # 水平翻转,默认50%概率transforms.ColorJitter(brightness=0.2, contrast=0.2), # 随机调整亮度和对比度transforms.ToTensor(),transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]) # 图像标准化
]) 
  1. 观察后发现了前两次实验效果变差的原因:应该调整train_tfm而非test_tfm!重新进行上述实验。将test_tfm改为原样,Total time: 56 min 11 sec, best accuracy: 0.70634,private score:0.70333。奇怪的是比起改动前几乎没有提升效果,此外发现训练时accuracy达到0.81210,存在Overfitting。
# 原代码如下:
train_tfm = transforms.Compose([# Resize the image into a fixed shape (height = width = 128)transforms.Resize((128, 128)),# You may add some transforms here.# 增强数据transforms.RandomChoice(transforms=[ # 随机选择以下两种操作的一种# Apply TrivialAugmentWide data augmentation methodtransforms.TrivialAugmentWide(), # 在训练过程中自动为每张图像选择一种随机增强操作# Return original imagetransforms.Lambda(lambda x: x),],p=[0.95, 0.1]), # 每个转换操作被选择的概率分布# ToTensor() should be the last one of the transforms.transforms.ToTensor(),
])# 修改后:
train_tfm = transforms.Compose([# Resize the image into a fixed shape (height = width = 128)transforms.Resize((128, 128)),# You may add some transforms here.# 增强数据transforms.RandomResizedCrop(size=128, scale=(0.8, 1.0)), # 随机裁剪区域,缩放到128*128(增加位置鲁棒性)transforms.RandomRotation(degrees=30),                # 随机旋转±30度transforms.RandomHorizontalFlip(),                    # 水平翻转,默认50%概率transforms.ColorJitter(brightness=0.2, contrast=0.2), # 随机调整亮度和对比度# ToTensor() should be the last one of the transforms.transforms.ToTensor(),
])
  1. train_tfm最后一步加入Image Standarlization。Total time: 58 min 18 sec, best accuracy: 0.34147,准确率暴跌。发现训练时acc = 0.81429,非常高,说明测试时出了问题
train_tfm = transforms.Compose([# Resize the image into a fixed shape (height = width = 128)transforms.Resize((128, 128)),# You may add some transforms here.# 增强数据transforms.RandomResizedCrop(size=128, scale=(0.8, 1.0)), # 随机裁剪区域,缩放到128*128(增加位置鲁棒性)transforms.RandomRotation(degrees=30),                # 随机旋转±30度transforms.RandomHorizontalFlip(),                    # 水平翻转,默认50%概率transforms.ColorJitter(brightness=0.2, contrast=0.2), # 随机调整亮度和对比度# ToTensor() should be the last one of the transforms.transforms.ToTensor(),transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]) # 图像标准化
])
  1. 找到了上次实验问题:测试时缺少与训练时一致的标准化操作,这种现象被称为 “预处理不一致偏差”,原因在于训练和测试数据分布不一致。修改train_tfm,加上相同的标准化处理,重新训练。Total time: 57 min 52 sec, best accuracy: 0.72379,private:0.73866,达到了Medium。
test_tfm = transforms.Compose([transforms.Resize((128, 128)),transforms.ToTensor(),transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]) # 测试时图像必须也标准化!
])
  1. 把epoch数量从24改为250,patience改为20(即连续20次无进步,退出训练)。训练34个epoch后,best acc已经达到0.72761,最终训练了102个epoch,Total time: 248 min 38 sec, best accuracy: 0.74015,private:0.74466。相比而言,多训练了5h,只提升了百分之一左右,效率很低。
  2. 残差连接。重构神经网络结构,换为残差连接(实现如代码所示),按照主流设计最后只用一层全连接神经网络(原代码用了三层),epoch设为150。24个epoch后acc = 0.66807。最终训练了63个epoch,Total time: 164 min 2 sec, best accuracy: 0.70428,private:0.71200
#残差链接:主流设计:两个卷积层 + 一次残差相加
class ResidualBlock(nn.Module):def __init__(self,in_channel,out_channel,stride=1):super().__init__()self.relu = nn.ReLU()  # 定义实例。nn.ReLU(x)是错的!!nn.ReLU是类self.conv1 = nn.Conv2d(in_channel,out_channel,kernel_size=3,stride=stride,padding=1) #第一个卷积层,可能特征图长宽会改变self.bn1 = nn.BatchNorm2d(out_channel)self.conv2 = nn.Conv2d(out_channel,out_channel,kernel_size=3,stride=1,padding=1)  #第二个卷积层self.bn2 = nn.BatchNorm2d(out_channel)self.shortcut = nn.Sequential() #residual connect, 维数相同则与输入相同if stride != 1 or in_channel != out_channel:self.shortcut = nn.Sequential(nn.Conv2d(in_channel,out_channel,kernel_size=1,stride=stride),nn.BatchNorm2d(out_channel))def forward(self, x):residual = self.shortcut(x)x = self.relu(self.bn1(self.conv1(x))) #F.rulu 和nn.ReLU不一样!!x = self.bn2(self.conv2(x))x += residualreturn self.relu(x)#新的神经网络架构# torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding)# torch.nn.MaxPool2d(kernel_size, stride, padding)# input 維度 [3, 128, 128]self.cnn = nn.Sequential(nn.Conv2d(3, 64, 3, 1, 1),  # [64, 128, 128]nn.BatchNorm2d(64),nn.ReLU(),nn.MaxPool2d(2, 2, 0),      # [64, 64, 64]#第一层池化,减少参数量,后面的残差连接无需池化    ResidualBlock(64,128,2), #处理后: [128, 32, 32],分别表示channel, sizeResidualBlock(128,256,2), #[256, 16, 16]ResidualBlock(256,512,2), #[512, 8, 8]ResidualBlock(512,1024,2),  #[1024, 4, 4])self.fc = nn.Linear(1024*4*4, 11) # 11个类别。后面不能加ReLu!# 原代码最后两层都是全连接网络
self.fc = nn.Sequential(nn.Linear(512*4*4, 1024),nn.ReLU(),nn.Linear(1024, 512),nn.ReLU(),nn.Linear(512, 11)
)
  1. 全局平均池化:在self.cnn的最后加上两行代码,把4×4的特征图取平均压缩为1×1,再降维。24个epoch后acc 0.71644,最终训练了118个epoch,Total time: 286 min 29 sec, best accuracy: 0.76066,private:0.77533。有大幅度提升。
self.cnn = nn.Sequential(nn.Conv2d(3, 64, 3, 1, 1),  # [64, 128, 128]nn.BatchNorm2d(64),nn.ReLU(),nn.MaxPool2d(2, 2, 0),      # [64, 64, 64]#第一层池化,减少参数量,后面的残差连接无需池化    ResidualBlock(64,128,2), #处理后: [128, 32, 32],分别表示channel, sizeResidualBlock(128,256,2), #[256, 16, 16]ResidualBlock(256,512,2), #[512, 8, 8]ResidualBlock(512,1024,2),  #[1024, 4, 4]nn.AdaptiveAvgPool2d((1, 1)), # [1024, 1, 1] → 全局平均池化nn.Flatten()                # [1024]
)
  1. 增加层数,epoch = 24,Total time: 64 min 55 sec, best accuracy: 0.71367,private:0.70133。相同epoch时,比起调整前反而有细微退步。
self.cnn = nn.Sequential(nn.Conv2d(3, 64, 3, 1, 1),  # [64, 128, 128]nn.BatchNorm2d(64),nn.ReLU(),nn.MaxPool2d(2, 2, 0),      # [64, 64, 64]#第一层池化,减少参数量,后面的残差连接无需池化    ResidualBlock(64,128,2), #处理后: [128, 32, 32],分别表示channel, sizeResidualBlock(128,128,1), #[128, 32, 32]ResidualBlock(128,256,2), #[256, 16, 16]ResidualBlock(256,256,1), #[256, 16, 16]ResidualBlock(256,512,2), #[512, 8, 8]ResidualBlock(512,512,1), #[512, 8, 8]ResidualBlock(512,1024,2),  #[1024, 4, 4]nn.AdaptiveAvgPool2d((1, 1)), # [1024, 1, 1] → 全局平均池化nn.Flatten()                # [1024]
)
  1. 增加dropout:self.cnn最后加入 nn.Dropout(0.3)。Total time: 74 min 47 sec, best accuracy: 0.71584,private:0.70866提升不大
  2. 把训练文件和测试文件合并、打乱,重新划分,比例从3:1改为4:1。把神经网络结构改回第八次的结构,epoch = 24。Total time: 60 min 36 sec, best accuracy: 0.72798,进步明显
path = "/kaggle/input/ml2023spring-hw3/train"  
all_files = ([os.path.join(path,x) for x in os.listdir(path) if x.endswith(".jpg")])  
path = "/kaggle/input/ml2023spring-hw3/valid"  
all_files += ([os.path.join(path,x) for x in os.listdir(path) if x.endswith(".jpg")])  
# shuffle and re-divide  
random.shuffle(all_files) #random seed已经固定,可以复现  
split_idx = int(len(all_files) * 0.8)  
train_files = all_files[:split_idx]  
valid_files = all_files[split_idx:]  # Construct train and valid datasets.  
# The argument "loader" tells how torchvision reads the data.  
train_set = FoodDataset("/kaggle/input/ml2023spring-hw3/train", tfm=train_tfm, files = train_files)  
train_loader = DataLoader(train_set, batch_size=batch_size, shuffle=True, num_workers=0, pin_memory=True)  
valid_set = FoodDataset("/kaggle/input/ml2023spring-hw3/valid", tfm=test_tfm, files = valid_files)  
valid_loader = DataLoader(valid_set, batch_size=batch_size, shuffle=True, num_workers=0, pin_memory=True)
  1. Test Time Augmentation。修改部分较多,具体见代码。从这个实验开始,改为colab平台上运行(因为恰好有colab pro+账号,GPU算力更高,选择L4 GPU),但在kaggle上运行也完全没问题。Total time: 51 min 7 sec, best accuracy: 0.73161,private:0.72266
#1.需要修改test_tfm
test_tfm = transforms.Lambda(lambda x: x)  # Return original image#2. designed for test time augmentation  
tta_tfm = transforms.Compose([  transforms.Resize((128, 128)),  transforms.RandomHorizontalFlip(p=0.5),  # 水平翻转  transforms.ColorJitter(brightness=0.2, contrast=0.2),  # 随机调整亮度和对比度  transforms.RandomRotation(30),  # 随机旋转  transforms.ToTensor(),  transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]),  # 测试时图像必须也作相同标准化处理!  
])#3. 利用继承的思想实现test time argumentation
class TTADataset(FoodDataset):  def __init__(self, *args, TTA_tfm=tta_tfm, n_aug=5, **kwargs):  """  n_aug 增强次数(不含原图),default = 5  """        super().__init__(*args, **kwargs)  self.n_aug = n_aug  self.tfm = TTA_tfm  self.ori_tfm = transforms.Compose([  # 处理原图像  transforms.Resize((128, 128)),  transforms.ToTensor(),  transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])  # 测试时图像必须也标准化!  ])  def __getitem__(self, index):  # 获取原始图像(此时还不是tensor)  image, label = super().__getitem__(index)  # 调用父类方法  tta_images = [self.ori_tfm(image)]  for _ in range(self.n_aug):  tta_images.append(self.tfm(image))  # return tta_images, label  # 返回list时,会出问题!!DataLoader 自动把每一位 TTA 样本合并成 batch 了!  tta_images = torch.stack(tta_images)  #转化为tensor [n_aug+1, 3, 128, 128]  return tta_images, label#4. 重写valid_set   
valid_set = TTADataset("/kaggle/input/ml2023spring-hw3/valid", TTA_tfm=tta_tfm, n_aug=5, tfm=test_tfm, files=valid_files)  
valid_loader = DataLoader(valid_set, batch_size=batch_size, shuffle=False, num_workers=0, pin_memory=True)#5. 修改训练阶段validation逻辑
for batch in tqdm(valid_loader):  # A batch consists of image data and corresponding labels.  imgs, labels = batch  with torch.no_grad():  if imgs.ndim == 5:  # [B, N, C, H, W], 启用了TTAB, N, C, H, W = imgs.shape  tta_images = imgs.view(B * N, C, H, W).to(device) # 首先改变形状,计算出全部结果  outputs = model(tta_images)  # shape: [B * N, num_classes]  outputs = outputs.view(B, N, -1)  #恢复原来的形状 shape: [B, N, num_classes]  # 加权平均预测  weights = torch.tensor([0.5] + [(0.5 / (N - 1))] * (N - 1), device=device)  # [N],权重,原图0.5,其余被平分(默认是5张)  weighted_preds = outputs * weights.view(1, -1, 1)  #广播乘法[B, N, C]               logits = weighted_preds.sum(dim=1)  #加权融合 shape: [B, num_classes]         else:  logits = model(imgs.to(device))#6. 改写测试集  
test_set = TTADataset("/kaggle/input/ml2023spring-hw3/test", TTA_tfm=tta_tfm, n_aug=5, tfm=test_tfm)  
test_loader = DataLoader(test_set, batch_size=batch_size, shuffle=False, num_workers=0, pin_memory=True)#7. 修改test逻辑
#改测试逻辑  
with torch.no_grad():  for batch in tqdm(test_loader):  imgs, _ = batch  # imgs可能是普通tensor或TTA列表  if imgs.ndim == 5:  # TTA模式  B, N, C, H, W = imgs.shape  tta_images = imgs.view(B * N, C, H, W).to(device) # 首先改变形状,计算出全部结果  outputs = model(tta_images)  # shape: [B * N, num_classes]  outputs = outputs.view(B, N, -1)  #恢复原来的形状 shape: [B, N, num_classes]  # 加权平均预测  weights = torch.tensor([0.5] + [(0.5 / (N - 1))] * (N - 1), device=device)  # [N],权重,原图0.5,其余被平分(默认是5张)  weighted_preds = outputs * weights.view(1, -1, 1)  #广播乘法 broadcasting: [B, N, C]            test_pred = weighted_preds.sum(dim=1)  #加权融合 shape: [B, num_classes]        else:  # 普通模式  test_pred = model_best(imgs.to(device))  # 获取预测标签  test_label = np.argmax(test_pred.cpu().numpy(), axis=1)  prediction.extend(test_label.tolist())
  1. 查阅资料后,发现上次实验有问题:TTA一般只用于test阶段,而在validation阶段不应该使用,必须保证 validation 的结果能真实反映模型对“原始数据”的泛化能力,只有在完全相同的条件下(都不用TTA) 比较模型,才能公平地判断出哪个模型的本体更强。 把validation阶段重新修改回原来的逻辑,即不执行上次实验的第5、6,并如下修改了代码。把epoch数量从24改为500,patience改为49,继续使用colab上L4 GPU(A100 GPU、L4 GPU在此任务中表现相近,但A100计算单元消耗速度快得多)。
    最终训练了412个epoch,Total time: 577 min 56 sec, best accuracy: 0.80946,0.78866。此次出现了Overfitting,train accuracy达到了99%;此外,patience设置过大,30要更为合理。
#TTADataset中初始化函数添加了mode = "no_tta",self.mode = mode  
#修改__getitem__逻辑,使其能返回未经tta增强的image
class TTADataset(FoodDataset):  def __init__(self, *args, TTA_tfm=tta_tfm, n_aug=5, mode = "no_tta", **kwargs):  """  n_aug 增强次数(不含原图),default = 5  mode 模式,启用tta或者不启用  """        super().__init__(*args, **kwargs)  self.n_aug = n_aug  self.tfm = TTA_tfm  self.mode = mode  self.ori_tfm = transforms.Compose([  # 处理原图像  transforms.Resize((128, 128)),  transforms.ToTensor(),  transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])  # 测试时图像必须也标准化!  ])  def __getitem__(self, index):  image, label = super().__getitem__(index)  # 调用父类方法  if self.mode != "no_tta":  # 此时为原始图像  tta_images = [self.ori_tfm(image)]  for _ in range(self.n_aug):  tta_images.append(self.tfm(image))  # return tta_images, label  # 返回list时,会出问题!!DataLoader 自动把每一位 TTA 样本合并成 batch 了!  tta_images = torch.stack(tta_images)  #转化为tensor [n_aug+1, 3, 128, 128]  return tta_images, label  image = self.ori_tfm(image)  return image, label#改validation、test,如果在kaggle上运行,要改路径
valid_set = TTADataset("/content/valid", tfm=test_tfm, files = valid_files)
test_set = TTADataset("/content/test", TTA_tfm=tta_tfm, n_aug=5, tfm=test_tfm,mode = "tta")

易错

  1. nn.ReLu不能直接调用!PyTorch 中 nn.ReLU 是一个类,不是函数,所以不能像普通函数那样直接 nn.ReLU(x) 使用。
#方法1 实例化
import torch
import torch.nn as nnx = torch.tensor([-1.0, 0.0, 2.0])
relu = nn.ReLU()   # 实例化
y = relu(x)        # 调用实例
print(y)           # tensor([0., 0., 2.])#方法2 F.relu(函数,直接可用)
import torch
import torch.nn.functional as Fx = torch.tensor([-1.0, 0.0, 2.0])
y = F.relu(x)  # 直接调用函数
print(y)       # tensor([0., 0., 2.])
  1. 实现的Dataset继承类中__getitem__返回的image(或其他东西)类型要是tensor,绝对不能是list,否则训练时DataLoader合并batch时会出错

相关技术

图像归一化(Normalization)与标准化(Standardlization)

  1. 区别。归一化:将数据按比例缩放至特定范围(如[0,1]或[-1,1]);标准化:将数据转换为均值为0、标准差为1的分布
  2. Pytorch中实现。归一化:transforms.ToTensor() # 将[0,255]线性映射到[0,1](Min-Max Normalization),标准化:transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])注意,PyTorch的Normalize()实际实现的是标准化
  3. 作用。归一化:统一特征尺度,加速梯度收敛等;标准化:提升模型泛化性,增强鲁棒性等。
  4. 易错。如果训练时数据进行了归一化与标准化,那么测试时数据也必须进行相同的归一化与标准化

残差连接

主流设计:两个卷积层 + 一次残差相加
核心思想:
不是直接让网络学习一个期望的映射 H(x),而是让网络学习残差映射 F(x)=H(x)−x
公式上,假设输入是 x,输出是 y:
y=F(x)+x

  • F(x):经过卷积层、BN、激活函数后的输出(残差)
  • x:直接加到输出上的原输入(shortcut/skip connection)

为什么有效?

  1. 减轻梯度消失:梯度可以直接沿着跳跃连接 x 反向传播,不经过很多非线性层。
  2. 学习残差更容易:通常残差 F(x) 比原映射 H(x)更接近零,网络只需学习微调,而不是整个复杂映射。
  3. 便于堆叠更多层:ResNet 可以轻松构建 50、101、甚至上百层的 CNN。

全局平均池化(Global Average Pooling, GAP)

这是一个非常简洁但极其强大的概念,在现代卷积神经网络(尤其是GoogLeNet、ResNet等)中扮演着关键角色。它将 每个通道的空间特征图 压缩成一个数值,即对每个通道做 空间维度的平均值

  • 输入:H × W × C 的特征图(高度 × 宽度 × 通道数)
  • 输出:1 × 1 × C 或直接 C 的向量
  • 常用于 网络末端替代全连接层
  • 巨大优势
    1. 极大减少参数量,防止过拟合:GAP本身零参数。它直接输出一个长度为 C(通道数)的向量,可以连接到最终的分类层(一个普通的全连接层,输入为C,输出为类别数)。参数量从亿级骤降到 C * num_classes,例如 512 * 1000 = 51.2万,减少了几个数量级。
    2. 增强模型鲁棒性:每个通道的特征图可以看作是对某个特定类别特征的“热度图”。GAP对空间信息进行整合,使得网络对输入物体的空间平移更加鲁棒。
    3. 原生支持任何输入尺寸:因为GAP不管输入特征图的高和宽是多少,它都直接取平均。这使得网络可以接受非固定尺寸的输入,而传统的FC层要求固定的输入维度。

Test Time Augmentation

在模型 推理阶段(测试阶段),对输入数据做多种数据增强(augmentation),然后将模型的多次预测结果进行融合(如平均或投票),以提高模型的鲁棒性和准确率。

  • 目的:缓解模型对输入分布的敏感性,让预测更稳健。
  • 区别于训练阶段数据增强
    • 训练时增强是为了增加数据量、防止过拟合
    • 测试时增强是为了提升预测稳定性和精度

总结

在hw3中,通过实现残差连接、TTA等技术,重新划分数据集并应用GAP,深化了对CNN的代码理解与实践能力,收获良多。不足之处在于没有尝试足够多网络架构,此外data argumentation做的也不够(做好了应该能较大幅度的提升准确率);如果使用ensemble技术,应该能较大地提高准确率。

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Element整体操作样式

Element: 给表格整体设置斑马纹 在main.js中 ElementUI.Table.props.stripe {type: Boolean,default: true }在element-ui.scss中 // // 为所有 el-table 设置默认斑马纹 // .el-table { // &.el-table--enable-row-hover .el-table__body tr:hover > td { // ba…

谷歌官宣组建“网络攻击部门”,美国网络安全战略转向“以攻代防”

谷歌宣布将组建网络攻击部门8月27日,谷歌宣布将组建网络攻击部门(disruption unit)。谷歌威胁情报集团副总裁Sandra Joyce在本周二的网络安全政策会议上表示,谷歌正在寻找“合法且符合道德规范的干扰方案”,通过情报主…

Rust Tokio异步任务实战教程(高级功能)

1. 强大的异步 I/O 多路复用Tokio 的核心竞争力之一是对操作系统原生异步 I/O 机制的封装(如 Linux 的 epoll、Windows 的 IOCP、macOS 的 kqueue),这是异步非阻塞的底层基石。作用:允许单线程同时监听成百上千个 I/O 事件&#x…

8.1【Q】VMware相关

在图四中,Interface Layer是用来干什么的?IOBus是什么我正在使用VMware虚拟机,但是没有网络(宿主机有网),我该如何配置网络?网络连接模式​​:​​NAT模式​​(推荐&…

从卡顿到丝滑:大型前端项目 CSS 优化全攻略

摘要 页面样式变重是大前端项目常见的后遗症:CSS 体积越来越大、首屏卡、切页抖、首包飙。核心问题其实就三件事:把首屏必须的样式尽快给到浏览器、把非首屏的样式晚点再说、把多余的样式坚决清理掉。本文用可运行的 Demo 和工程化流程,带你…

CSS基础学习第二天

1.emmet语法1)快速生成HTML结构语法---标签名tab键即可生成标签---标签*数量即可生成多个标签---如果有父子级关系的标签,用>,比如ul>litab键---如果有兄弟级的标签,用tab键---如果生成带有类名或者id名字的,直接…

【自记】 Python 中函数参数前加 *(单星号)的解包可迭代对象写法说明

在 Python 中,函数参数前加 *(单星号)是一种解包可迭代对象的写法,用于将可迭代对象(如元组、列表等)中的元素逐个传递给函数的参数。具体说明当有一个可迭代对象(比如元组 temp (1, 2, 3)&…

C语言————深入理解指针1(通俗易懂)

C语言越学到后面,越会感到恐慌,听到指针、结构体等等这些,想必很多人不自觉的就会感觉很难,就想打退堂鼓了。哈哈哈哈,被小博猜到了吧!!悄悄告诉你们,小博刚开始学习的时候也是。但是…

香港电讯为知名投资公司搭建高效、安全IT管理服务体系

客户背景 客户为一家世界知名的能源投资公司在中国设立的子公司,在中国拥有涵盖煤炭开采、火力发电、新能源以及能源贸易等贯穿整个能源供应链的业务体系,投资共计2个煤矿、4个电厂,以及7个光伏电站。 客户需求 客户希望通过位于北京的总部…

紧急安全通告:多款 OpenSSH 与 glibc 高危漏洞曝光,CVE-2023-38408 等须立即修复

概述:OpenSSH(OpenBSD Secure Shell)是加拿大OpenBSD计划组的一套用于安全访问远程计算机的连接工具。该工具是SSH协议的开源实现,支持对所有的传输进行加密,可有效阻止窃听、连接劫持以及其他网络级的攻击。 OpenSSH …

随时随地开发:通过 FRP 搭建从 Ubuntu 到 Windows 的远程 Android 调试环境

你是否曾梦想过这样的工作流:在咖啡馆里,你只带着一台轻薄的 Surface Pro,而代码的编译、运行和调试,全部交由家里那台性能强劲的 Ubuntu 台式机来完成?更酷的是,你甚至想将手机直接插在 Surface 上,让远端的 Ubuntu 无缝识别并进行开发。 今天,我们就将这个梦想变为现…

异步编程与面向对象知识总结

文章目录原型链关键字总结原型对象:prototype对象原型:__ proto__面向对象编程封装抽象多态总结异步编程基础循环宏任务嵌套微任务原型链关键字总结 原型对象:prototype 函数的属性,指向一个对象,这个对象是通过该函数作为构造函数创建的所有实例的原型 修改原型会…

Spring Boot + KingbaseES 连接池实战

文章目录一、前言二、什么是数据库连接池?三、SpringBoot KingbaseES 环境准备3.1 加依赖(pom.xml)3.2 基础连接信息(application.yml)四、四类主流连接池实战4.1 DBCP(迁移型 / 传统项目友好)…

矩阵待办ios app Tech Support

Getting Support: mail: 863299715qq.com

React中优雅管理CSS变量的最佳实践

在现代前端开发中,CSS变量(也称为CSS自定义属性)已成为管理样式系统的重要工具。它们提供了强大的动态样式能力,但在JavaScript中高效地访问和使用这些变量却存在一些挑战。本文将介绍一个优化的解决方案,帮助你在Reac…

智能制造——解读装备制造业智能工厂解决方案【附全文阅读】

适应人群为装备制造企业(如汽车、航空航天、能源装备等)中高层管理者、生产运营负责人、IT 部门(智能制造 / 工业互联网团队)、安全管理专员及园区数字化建设决策者。主要内容围绕装备制造业智能工厂解决方案展开,核心包括建设背景(解决生产安全管理缺失、工序手工记录无…

macos调用chrome后台下载wasm-binaries.tar.xz

实现脚本: down_wasm.sh DOWNLOAD_DIR="$HOME/Downloads" TARGET_FILE="wasm-binaries.tar.xz" TAG="32b8ae819674cb42b8ac2191afeb9571e33ad5e2" TARGET_DIR="$HOME/Desktop/sh/emsdk_setup/emsdk_deps"echo "下载路径: $DOW…

【Proteus仿真】按键控制系列仿真——LED灯表示按键状态/按键控制LED灯/4*4矩阵键盘控制LED

目录 1案例视频效果展示 1.1例子1:LED灯表示按键状态(两种方式) 1.2例子2:按键控制两排LED小灯闪烁移位 1.3例子3:按键控制LED灯逐个点亮/分组点亮/全部熄灭 1.4例子4:4*4矩阵按键实现带状LED灯控制 2例子1:LED灯…

829作业

用fgets&#xff0c;fputswanc代码#include<myhead.h> int main(int argc, const char *argv[]) {FILE *fp1 NULL;FILE *fp2 NULL;if (argc ! 3){printf("输入不合法:./a.out lydf.txt l.txt\n");return -1;}if ((fp1fopen(argv[1],"w"))NULL){pri…

CRMEB小程序订阅消息配置完整教程(PHP版)附常见错误解决

登录小程序后台 1.进入微信公众平台、小程序后台&#xff1a;功能->订阅消息。&#xff08;如未开通&#xff0c;点击申请即可开通&#xff09; 选择服务类目 2.选择服务类目&#xff1a;生活服务/百货/超市/便利店 同步小程序订阅消息 3.商城后台设置->消息管理 点击…