【机器学习】李宏毅-食物图像分类器

news2024/12/27 11:32:14

李宏毅-食物图像分类器

在这里插入图片描述


1 实验目的

掌握使用Pytorch的使用方法:

  • Pytorch的安装以及环境搭建
  • Pytorch处理数据
  • Pytorch计算梯度以及搭建神经网络
  • Pytorch训练模型

并使用Pytorch来训练CNN模型,实作一个食物的图像分类器。


2 实验要求

  • 可以使用tensorflow或者pytorch库
  • 必须使用CNN实作model
  • 不能使用额外dataset
  • 禁止使用 pre-trained model(只能自己手写CNN)
  • 请不要上网寻找 label
  • 上传格式为 csv,第一行必须为 Id, Category,第二行开始为预测结果,
    每行分别为 id 以及预测的 Category,请以逗号分隔
  • 请说明你实现的CNN模型,其模型架构、训练参数量和准确率为何
  • 请实作与第一题接近的参数量,但CNN深度(CNN层数)减半的模型,并说明其模型架构、训练参数量和准确率为何
  • 请说明由 1 ~ 2 题的实验中你观察到了什么
  • 请尝试 data normalization 及 data augmentation,说明实作方法并且说明实行前后对准确率有什么样的影响

3 实验环境

3.1 硬件环境

  • 笔记本电脑
  • Intel Core i5
  • NVIDIA GeForce MX350

3.2 软件环境

  • windows10操作系统
  • Python 3.8 64-bit
  • Visual Studio Code
  • NVIDIA CUDA
  • Kaggle Accelerator GPU

4 实验数据

此次数据集为网络上搜集到的食物照片,共有11类:

Bread, Dairy product, Dessert, Egg, Fried food, Meat, Noodles/Pasta, Rice, Seafood, Soup, Vegetable/Fruit

Training set: 9866张

Validation set: 3430张

Testing set: 3347张

下载 zip 文件后解压缩会有三个文件夹,分别为training、validation 以及 testing
training 以及 validation 中的照片名称格式为 [类别]_[编号].jpg,例如 3_100.jpg 即为类别 3 的照片(编号不重要)


5 模型A:5Layers

5.1 读取数据集

首先定义函数readfile,输入参数path为文件夹路径名,布尔型参数label判断是否需要额外读取label。

readfile函数先通过os.listdir()返回文件夹包含的文件或文件夹的名字的列表,再利用OpenCV(cv2)读入图片,将每个图片的形状统一缩放为128*128,最后存放在numpy array中返回:

def readfile(path, label):
    image_dir = sorted(os.listdir(path))
    # 创建array类型的x为输入向量
    # 一共len(image_dir)个图片,每个图片像素大小128*128,分为3个channel
    # 所以向量大小为len(image_dir)*128*128*3
    # 其次规定了数据类型为uint8
    x = np.zeros((len(image_dir), 128, 128, 3), dtype=np.uint8)
    # 创建array类型的y为输出向量
    # 一共len(image_dir)个图片,每个图片各有1个label
    # 所以向量大小为len(image_dir)*1
    # 同样规定了数据类型为uint8
    y = np.zeros((len(image_dir)), dtype=np.uint8)
    # 枚举每个图片的路径
    for i, file in enumerate(image_dir):
        # 拼接图片路径并读取图片数据
        img = cv2.imread(os.path.join(path, file))
        # 将每个图片统一resize为128*128
        x[i, :, :] = cv2.resize(img,(128, 128))
        # 是否有label即输出,有则将其存入y向量
        if label:
          y[i] = int(file.split("_")[0])
    # 根据label是否为True返回值不同
    if label:
      return x, y
    else:
      return x

找到数据集的目录路径,利用刚刚定义的readfile函数读取数据集,并计算输出训练集和验证集的大小:

# 所有数据集的目录路径
workspace_dir = '../input/ml-lab/food11'
print("Reading data......")
# 用自定义的readfile函数读取训练集数据,label已知,所以参数为True
train_x, train_y = readfile(os.path.join(workspace_dir, "training"), True)
# 训练集大小
print("Size of training data = {}".format(len(train_x)))
# 用自定义的readfile函数读取验证集数据,label已知,所以参数为True
val_x, val_y = readfile(os.path.join(workspace_dir, "validation"), True)
# 验证集大小
print("Size of validation data = {}".format(len(val_x)))
# 用自定义的readfile函数读取测试集数据,label未知,所以参数为False
test_x = readfile(os.path.join(workspace_dir, "test"), False)
# 测试集大小
print("Size of Testing data = {}".format(len(test_x)))

5.2 数据预处理

定义训练集和验证集的数据处理对象transforms.Compose(),transforms.Compose()是一个图像预处理包,它可以包含其类内的一系列操作:

  • transforms.Resize()把给定的图片resize到given size
  • transforms.ToPILImage()将tensor转换为PIL图像
  • transforms.ToTensor()将PIL图像转换为tensor

这里将训练集和验证集的数据处理分开定义是为了便于之后模型的改进:

train_transform = transforms.Compose([
    # 转换为PIL图像
    transforms.ToPILImage(),
    # 转换为Tensor对象
    transforms.ToTensor(), 
])
test_transform = transforms.Compose([
    # 转换为PIL图像
    transforms.ToPILImage(),
    # 转换为Tensor对象
    transforms.ToTensor(), 
])

自定义一个类ImgDataset来继承Dataset,然后实现getitem()方法和len()方法,len()返回Dataset的大小,getitem()返回某个规定index图片处理后的结果:

class ImgDataset(Dataset):
    # 魔术构造函数初始化
    def __init__(self, x, y=None, transform=None):
        # 承接向量x和y
        self.x = x
        self.y = y
        # 将label的类型转为LongTensor
        if y is not None:
            self.y = torch.LongTensor(y)
        # 使用刚刚定义的transform
        self.transform = transform
    def __len__(self):
        return len(self.x)
    def __getitem__(self, index):
        X = self.x[index]
        if self.transform is not None:
            # 图片处理
            X = self.transform(X)
        if self.y is not None:
            # 获取label
            Y = self.y[index]
            return X, Y
        else:
            return X

定义数据处理的块大小,再对训练集和验证集实例化刚刚定义的Dataset类:

batch_size = 128
train_set = ImgDataset(train_x, train_y, train_transform)
val_set = ImgDataset(val_x, val_y, test_transform)

Dataloader的处理逻辑是先通过Dataset类里面的 getitem()函数获取单个的数据,然后组合成batch,再使用collate_fn所指定的函数对这个batch做一些操作,比如padding等。

另外Dataloader的布尔类型参数shuffle决定是否打乱数据,训练集需要,验证集不需要:

train_loader = DataLoader(train_set, batch_size=batch_size, shuffle=True)
val_loader = DataLoader(val_set, batch_size=batch_size, shuffle=False)

5.3 神经网络架构

5.3.1 CNN神经网络

在使用torch.nn.Sequential构建CNN神经网络过程中使用了pytorch库中nn模块的四个函数,分别为:

  • 卷积运算函数torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding)
  • 归一化处理函数torch.nn.BatchNorm2d(channels)可以使得在过激活函数的时候不会因为数据过大导致网络的不稳定
  • 激活函数torch.nn.ReLU()
  • 池化函数torch.nn.MaxPool2d(kernel_size, stride, padding)采用MaxPooling策略,可以降低模型的运算量

第一层:

卷积层3个channel,64个filter,每个filter大小3*3,stride=1,padding=1,输入3*128*128,输出64*128*128。

池化层使用Max Pooling方法,输入64*128*128,输出64*64*64。

nn.Conv2d(3, 64, 3, 1, 1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(2, 2, 0),

第二层:

卷积层64个channel,128个filter,每个filter大小3*3,stride=1,padding=1,输入64*64*64,输出128*64*64。

池化层使用Max Pooling方法,输128*64*64,输出128*32*32。

nn.Conv2d(64, 128, 3, 1, 1),
nn.BatchNorm2d(128),
nn.ReLU(),
nn.MaxPool2d(2, 2, 0),

第三层:

卷积层,128个channel,256个filter,每个filter大小3*3,stride=1,padding=1,输入128*32*32,输出256*32*32。

池化层使用Max Pooling方法,输入256*32*32,输出256*16*16。

nn.Conv2d(128, 256, 3, 1, 1),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.MaxPool2d(2, 2, 0),

第四层:

卷积层,256个channel,512个filter,每个filter大小3*3,stride=1,padding=1,输入256*16*16,输出512*16*16。

池化层使用Max Pooling方法,输入512*16*16,输出512*8*8。

nn.Conv2d(256, 512, 3, 1, 1),
nn.BatchNorm2d(512),
nn.ReLU(),
nn.MaxPool2d(2, 2, 0),

第五层:

卷积层,512个channel,512个filter,每个filter大小3*3,stride=1,padding=1,输入512*8*8,输出512*8*8。

池化层使用Max Pooling方法,输入512*8*8,输出512*4*4。

nn.Conv2d(512, 512, 3, 1, 1),
nn.BatchNorm2d(512),
nn.ReLU(),
nn.MaxPool2d(2, 2, 0),
5.3.2 全连接神经网络

在使用torch.nn.Sequential构建全连接神经网络过程中使用了pytorch库中nn模块的两个函数,分别为:

  • 全连接分类器torch.nn.Linear(in_features , out_features)
  • 激活函数torch.nn.ReLU()
self.fc = nn.Sequential(
	# 输入512*4*4,输出1024*1
	nn.Linear(512*4*4, 1024),
	nn.ReLU(),
	# 输入1024*1,输出512*1
	nn.Linear(1024, 512),
	nn.ReLU(),
	# 输入512*1,输出11*1,即11种食物种类
	nn.Linear(512, 11)
)
5.3.3 计算参数量
输入filter输出计算式参数量
CNN13*(128*128)64*(3*3)64*(128*128)64*(3*3*3)1728
CNN264*(64*64)128*(3*3)128*(64*64)128*(3*3*64)73728
CNN3128*(32*32)256*(3*3)256*(32*32)256*(3*3*128)294912
CNN4256*(16*16)512*(3*3)512*(16*16)512*(3*3*256)1179648
CNN5512*(8*8)512*(3*3)512*(8*8)512*(3*3*512)2359296
FC1512*4*41024(8192+1)*10248389632
FC21024512(1024+1)*512524800
FC351211(512+1)*115643

参数总量=1728+73728+294912+1179648+2359296+8389632+524800+5643=12829387

5.4 训练和验证

首先实例化刚刚定义的训练模型,并初始化一些参数,如GPU加速、损失函数、学习率、迭代次数等:

# GPU加速
model = Classifier().cuda()
# 由于是分类模型,损失函数使用交叉熵
loss = nn.CrossEntropyLoss()
# optimizer使用Adam优化器,学习率设为0.001
optimizer = torch.optim.Adam(model.parameters(), lr=0.001) 
# 迭代次数
num_epoch = 30

每次迭代的过程如下:

训练过程包括切换model为train,通过模型输出预测值,计算某个batch的损失函数和梯度,利用梯度更新参数值,最后汇总得到这一轮迭代的准确率和loss。

验证过程也类似,切换model为eval确保在验证的过程是使用的训练数据的参数,直接通过模型输出预测值与实际label比较计算得到准确率和loss即可。

for epoch in range(num_epoch):
    # 记录开始时间
    epoch_start_time = time.time()
    # 训练集上的准确率
    train_acc = 0.0
    # 训练集上的loss
    train_loss = 0.0
    # 验证集上的准确率
    val_acc = 0.0
    # 验证集上的loss
    val_loss = 0.0
    # 确保model是在train model,即训练
    model.train() 
    for i, data in enumerate(train_loader):
        # 梯度参数归零
        optimizer.zero_grad()
        # 呼叫forward函数,并得到预测值
        train_pred = model(data[0].cuda())
        # 计算这个batch的loss
        batch_loss = loss(train_pred, data[1].cuda())
        # 计算每个参数的梯度
        batch_loss.backward()
        # 更新参数值
        optimizer.step() 
        # 更新准确率,numpy.argmax(array, axis)返回一个numpy数组中最大值的索引值
        train_acc += np.sum(np.argmax(train_pred.cpu().data.numpy(), axis=1) == data[1].numpy())
        # 将这个batch的loss添加到总loss
        train_loss += batch_loss.item()
	# 切换model到eval model,即测试
    model.eval()
    with torch.no_grad():
        for i, data in enumerate(val_loader):
            # 呼叫forward函数,并得到预测值
            val_pred = model(data[0].cuda())
            # 计算这个batch的loss
            batch_loss = loss(val_pred, data[1].cuda())
            # 更新准确率,numpy.argmax(array, axis)返回一个numpy数组中最大值的索引值
            val_acc += np.sum(np.argmax(val_pred.cpu().data.numpy(), axis=1) == data[1].numpy())
            # 将这个batch的loss添加到总loss
            val_loss += batch_loss.item()
        #打印本次迭代的结果
        print('[%03d/%03d] %2.2f sec(s) Train Acc: %3.6f Loss: %3.6f | Val Acc: %3.6f loss: %3.6f' % (epoch + 1, num_epoch, time.time()-epoch_start_time, train_acc/train_set.__len__(), train_loss/train_set.__len__(), val_acc/val_set.__len__(), val_loss/val_set.__len__()))

5.5 扩大训练集

在5.4中我们已经通过训练找到了比较好的参数,并通过验证集大概得到了模型的准确率。

我们还想进一步训练模型,但是限于实验要求不能使用数据集之外的数据。

一个很好的策略就是扩大训练集,利用的就是5.4中的验证集,将训练集和验证集合并为新的更大的训练集,在这个新的训练集上再次训练,这样充分利用了实验给出的数据集。

下面的代码展示了如何将训练集和验证集合并为新的更大的训练集:

# 拼接训练集和验证集成为一个新的更大的训练集
train_val_x = np.concatenate((train_x, val_x), axis=0)
train_val_y = np.concatenate((train_y, val_y), axis=0)
# 对新的训练集实例化Dataset
train_val_set = ImgDataset(train_val_x, train_val_y, train_transform)
# 对新的训练集进行DataLoader,其中布尔类型参数shuffle决定是否打乱数据,训练集需要,所以为True
train_val_loader = DataLoader(train_val_set, batch_size=batch_size, shuffle=True)

训练过程同5.4中的训练过程,此时已经没有验证集,所以没有验证过程,代码几乎相同,限于篇幅这里就不赘述了。

5.6 预测结果

先载入测试集到Dataset中,切换模型的model为eval确保在预测的过程是使用的训练数据的参数,另外定义列表prediction用于存储预测结果:

# 对测试集实例化Dataset
test_set = ImgDataset(test_x, transform=test_transform)
# 对测试集进行DataLoader,其中布尔类型参数shuffle决定是否打乱数据,测试集不需要,所以为False
test_loader = DataLoader(test_set, batch_size=batch_size, shuffle=False)
# 切换model到eval model,即测试
model_best.eval()
# 存储预测值,以便于写入结果文件
prediction = []

预测过程类似验证过程,将输入给到训练好的模型,得到输出结果并添加到结果列表prediction:

with torch.no_grad():
    for i, data in enumerate(test_loader):
        # 呼叫forward函数,并得到预测值
        test_pred = model_best(data.cuda())
        # 得到该图片的预测结果,通过numpy.argmax(array, axis)返回一个numpy数组中最大值的索引值
        test_label = np.argmax(test_pred.cpu().data.numpy(), axis=1)
        #将结果加入预测结果的列表
        for y in test_label:
            prediction.append(y)

最终按照实验要求整理格式,并输出结果到csv文件

with open("predict_模型A_5Layers.csv", 'w') as f:
    f.write('Id,Category\n')
    for i, y in enumerate(prediction):
        f.write('{},{}\n'.format(i, y))

5.7 模型准确率

训练次数训练集准确率验证集准确率扩大训练集准确率
100.6471720.4725950.706453
200.8615450.5810500.942464
300.9798300.6387760.980445

6 模型B:3Layers

6.1 深度减半的CNN神经网络

为了达到与模型A相近的参数量,不可以单纯从5层CNN中随意删除2层来达到目的,需要适当调整filter的数量。

第一层:

卷积层3个channel,256个filter,每个filter大小3*3,stride=1,padding=1,输入3*128*128,输出256*128*128。

池化层使用Max Pooling方法,输入256*128*128,输出256*64*64。

nn.Conv2d(3, 256, 3, 1, 1),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.MaxPool2d(2, 2, 0),

第二层:

卷积层256个channel,512个filter,每个filter大小3*3,stride=1,padding=1,输入256*64*64,输出512*64*64。

池化层使用Max Pooling方法,输入512*64*64,输出512*16*16。

nn.Conv2d(256, 512, 3, 1, 1),
nn.BatchNorm2d(512),
nn.ReLU(),
nn.MaxPool2d(4, 4, 0),

第三层:

卷积层,512个channel,512个filter,每个filter大小3*3,stride=1,padding=1,输入512*16*16,输出512*16*16。

池化层使用Max Pooling方法,输入512*16*16,输出512*4*4。

nn.Conv2d(512, 512, 3, 1, 1),
nn.BatchNorm2d(512),
nn.ReLU(),
nn.MaxPool2d(4, 4, 0),

6.2 计算参数量

输入filter输出计算式参数量
CNN13*(128*128)256*(3*3)256*(128*128)256*(3*3*3)6912
CNN2256*(64*64)512*(3*3)512*(64*64)512*(3*3*256)1179648
CNN3512*(16*16)512*(3*3)512*(16*16)512*(3*3*512)2359296
FC1512*4*41024(8192+1)*10248389632
FC21024512(1024+1)*512524800
FC351211(512+1)*115643

参数总量=6912+1179648+2359296+8389632+524800+5643=12465931

可见参数总量和模型A的参数总量(12829387)相近,可以更客观地反映出CNN深度对于模型准确率的影响。

6.3 模型准确率

训练次数训练集准确率验证集准确率扩大训练集准确率
100.6972430.5151600.742329
200.8655990.5860060.922307
300.9771940.6344020.973827

7 模型C:5Layers+normalization

7.1 归一化过程

transforms.Normalize()用均值和标准差归一化张量图像,将每个元素分布到(-1,1),参照如下公式:
σ = 1   N ∑ i = 1 N ( x i − μ ) 2 z i = x i − μ σ \sigma=\sqrt{\frac{1}{\mathrm{~N}} \sum_{i=1}^{\mathrm{N}}\left(\mathrm{x}_{\mathrm{i}}-\mu\right)^{2}}\\ \mathrm{z}_{\mathrm{i}}=\frac{\mathrm{x}_{\mathrm{i}}-\mu}{\sigma} σ= N1i=1N(xiμ)2 zi=σxiμ
修改训练集和验证集的数据处理对象transforms.Compose():

train_transform = transforms.Compose([
    # 转换为PIL图像
    transforms.ToPILImage(),
    # 转换为Tensor对象
    transforms.ToTensor(), 
    # 用均值和标准差归一化张量图像
    transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))
])
test_transform = transforms.Compose([
    # 转换为PIL图像
    transforms.ToPILImage(),
    # 转换为Tensor对象
    transforms.ToTensor(), 
    # 用均值和标准差归一化张量图像
    transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))
])

7.2 模型准确率

训练次数训练集准确率验证集准确率扩大训练集准确率
100.6327790.4772590.727888
200.8404620.6256560.947879
300.9882420.6504370.980295

8 模型D:5Layers+augmentation

8.1 数据增强过程

数据增强包括翻转(flips)、移位(translations)、旋转(rotations)、缩放比例(Scale)、裁剪(Crop)、高斯噪声(Gaussian Noise)等微小的改变,以此训练出的模型适应性会更强。

在图像预处理包transforms.Compose()中可以找到相应的函数来完成这些操作:

  • transforms.RandomHorizontalFlip()以0.5的概率水平翻转给定的PIL图像
  • transforms.RandomVerticalFlip()以0.5的概率竖直翻转给定的PIL图像
  • transforms.RandomGrayscale()将图像以一定的概率转换为灰度图像
  • transforms.ColorJitter()随机改变图像的亮度对比度和饱和度
  • transforms.RandomResizedCrop()将PIL图像裁剪成任意大小和纵横比
  • transforms.RandomRotation()按照degree将图像随机旋转一定角度

由于测试集不需要数据增强,因此只需要修改训练集的数据处理对象transforms.Compose():

# 训练集相比测试集额外添加了数据增强(data augmentation)
train_transform = transforms.Compose([
    # 转换为PIL图像
    transforms.ToPILImage(),
    # 以0.5概率水平翻转随机PIL图像
    transforms.RandomHorizontalFlip(),
    # 按照degree将图像随机旋转一定角度
    transforms.RandomRotation(15), 
    # 转换为Tensor对象
    transforms.ToTensor(), 
])

8.2 模型准确率

训练次数训练集准确率验证集准确率扩大训练集准确率
100.5871680.5317780.660725
200.7572470.6145770.809792
300.8500910.6615160.912229

9 分析评估

9.1 CNN深度对模型准确率的影响

根据5.7和6.3中的数据,模型B无论是在训练集还是验证集上的准确率相比模型A均有轻微下降,且模型B在训练过程中的收敛速度稍快。

分析原因,模型A的参数总量为12829387,而模型B的参数总量为12465931,参数在数量上接近,后者相比前者略少,CNN层数也仅差2层,非线性的表达能力差别不是特别大。

在一定范围内,CNN的深度越深,参数量越多,模型的训练效果就会越好。

但是目前研究已经发现发现传统的CNN网络结构随着层数加深到一定程度之后,越深的CNN网络训练出的模型反而效果更差。

9.2 归一化对模型准确率的影响

根据5.7和7.2中的数据,经过data normalization之后,模型C无论是在训练集还是验证集上的准确率相比模型A均有提升,其中在验证集上的准确率提升了2个百分点,可见归一化操作对模型性能提升方面发挥了积极作用。

由于模型A和模型C的神经网络架构相同,均为5Layers,所以两个模型在训练过程中的收敛速度接近。

9.3 数据增强对模型准确率的影响

根据5.7和8.2中的数据,经过data augmentation之后,模型D虽然在训练集上的准确率相比模型A有所降低,但是在验证集上的准确率有了比模型C更大的提升,可以认为data augmentation增强了模型的适应性,提升了模型的性能。

由于模型A和模型D的神经网络架构相同,均为5Layers,所以两个模型在训练过程中的收敛速度接近。

9.4 进一步优化模型

如果想要进一步优化模型,还可以从以下几个并未在实验中探究的方面入手:

  • 尝试寻找filter的最佳kernel size
  • 尝试寻找最佳的learning rate
  • 尝试寻找最佳的batch size
  • 进一步完善模型的神经网络结构
  • 进一步对数据集data augmentation

10 References

[1] python.org

[2] kaggle.com

[3] baike.baidu.com

[4] www.csdn.net

[5] pytorch.org

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

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

相关文章

print()函数的使用

一、print()函数共三种类型的使用方法二、代码展示

Excel求解运输问题——以福斯特公司问题为例

目录 1.1 问题 福斯特问题例 1.2 数学模型 1.3 excel求解 第一步:建立一个工作表 第二步:求解器求解 1.1 问题 运输问题通常出现在计划货物配送机从供给地区到达需求地区之间的服务中,一般供给地区货物数量有限,需求地区货物…

时间序列分析原理

一、定义 时间序列,是指将某种现象某一个统计指标在不同时间上的各个数值,按时间先后顺序排列而形成的序列 生活中各领域各行业有很多时间序列的数据,销售额,顾客数,访问量,股价,油价&#xff…

k8s 部署canal admin及server单机服务

目录 1. 前言 2. 部署canal-admin 2.1 数据库初始化 2.2 canal-admin k8s yaml部署文件 3. 部署canal-server单机 1. 前言 canal官方文档:https://github.com/alibaba/canal/wiki 主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费。…

一文读懂深度学习中文本处理的4种方式

一、序言文本处理方式是深度学习领域中自然语言处理的基础,即把文本转变成计算机识别的语言过程。转变之后才能用算法做后续的文本分析和理解。所以有必要了解文本处理的几种方式,做到对不同的场景采用不同的处理方式。常见的文本处理方式有独热编码(one…

OSI参考模型与TCP/IP模型比较

TCP/IP模型 — TCP/IP协议簇 TCP/IP分为两种体系结构,一种是分为四层网络接口层、网络层、传输层和应用层;另外一种是分为五层物理层、数据链路层、网络层、传输层和应用层。这两种体系结构都对。 TCP/IP四层模型 — TCP/IP标准模型 TCP/IP五层模型 — T…

hive数据仓库搭建

一、虚拟机安装CentOS7并配置共享文件夹 二、CentOS 7 上hadoop伪分布式搭建全流程完整教程 三、本机使用python操作hdfs搭建及常见问题 四、mapreduce搭建 五、mapper-reducer编程搭建 六、hive数据仓库安装 hive数据仓库搭建一、hive数据仓库安装1.1下载hive安装包1.2修改配置…

2022年亚太杯APMCM数学建模大赛C题全球是否变暖求解全过程文档及程序

2022年亚太杯APMCM数学建模大赛 C题 全球是否变暖 为方便各位阅览及了解掌握亚太杯的写作技巧,这里非技术使用中文,公式部分由于翻译过程繁琐使用英文来撰写此文章. 原题再现: 加拿大的49.6C创造了地球北纬50以上地区的气温新纪录&#xf…

SpringBoot的字符画(banner.txt)

好多小伙伴最近问我如何在启动项目的时候,在控制台打印如下的图案logo 其实很简单,Springboot提供了很方便的操作,在resource目录先新建一个banner.txt 然后将我们想要的图案拼接即可,如下图: 配置完成后正常启动项目…

TorchServe 详解:5 步将模型部署到生产环境

内容导读 TorchServe 自 2020 年 4 月推出至今,经历了 2 年多的发展,变得愈发成熟和稳定,本文将对 TorchServe 进行全面介绍。 TorchServe 是 PyTorch 中将模型部署到生产环境的首选解决方案。它是一个性能良好且可扩展的工具,用 …

【Java】多线程详解

目录 一、线程简介 进程(Process )与 线程(Thread) 二、线程创建 1、线程Thread 1.1. 步骤 1.2 应用 1.3 案例:下载图片 2、实现Runnable接口 2.1 步骤 2.2 应用 3.小结 3. 实现Callable接口(了解…

利用Github账号实现(多个)个人网站

创建仓库 命名一定要是<username>.github.io这种形式username就是自己github账号的用户名。因为我已经有这个仓库&#xff0c;所以这里的报错可以忽略。 往仓库放入静态html文件 命名为index.html&#xff0c;随便放点内容。 在设置里面开启github page 这里可以选…

verilog学习笔记- 2)时序约束文件

目录 为什么要创建时序约束文件&#xff1f; 时序&#xff1a; 创建时序约束文件&#xff1a; 为什么要创建时序约束文件&#xff1f; 对于一些简单的工程时序的要求并不是特别的严格&#xff0c;添不添加时序约束文件影响不大。但是对于一些复杂的工程&#xff0c;涉及到高…

【SpringBoot应用篇】SpringBoot 启动扩展点/常用接口

【SpringBoot应用篇】SpringBoot 启动扩展点/常用接口SpringBoot常用的接口ApplicationContextInitializerBeanDefinitionRegistryPostProcessorBeanFactoryPostProcessorInstantiationAwareBeanPostProcessorSmartInstantiationAwareBeanPostProcessorBeanFactoryAwareApplica…

Dom 重点核心

关于dom操作&#xff0c;主要针对元素的操作。 主要有创建&#xff0c;增&#xff0c;删&#xff0c;改&#xff0c;查&#xff0c;属性操作&#xff0c;事件操作。 一、创建 1.document.write 2.innerHTML 3.createElement 二、增 1.appendChild&#xff08;在后面添加&am…

【iOS】块与大中枢派发

文章目录[TOC](文章目录)前言理解“块”这一概念块的基础知识块的内部结构全局块&#xff0c;栈块&#xff0c;堆块为常用的块类型创建typedef用handler块降低代码分散程度用块引用其所属对象时不要出现保留环多用派发系列&#xff0c;少用同步锁多用GCD&#xff0c;少用perfor…

Linux/ARM下QT MQTT库的编译安装

&#x1f482; 个人主页:风间琉璃&#x1f91f; 版权: 本文由【风间琉璃】原创、在CSDN首发、需要转载请联系博主&#x1f4ac; 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)和订阅专栏哦目录 一、 Linux 环境下编译安装 二、 ARM Linux 环境下安装 一、 Linux 环境下…

破解分布式光伏运维难题,光伏+屋面数字化监控融合是关键

2022年5月&#xff0c;在东南地区某城市一幢写字楼上&#xff0c;技术人员们正不辞辛苦爬上数十米高的墙面&#xff0c;对写字楼的屋面和墙面进行勘察。随后&#xff0c;他们准备赶在台风季来临之前完成该写字楼的建筑光伏一体化项目安装与部署。 这是森特士兴集团股份有限公司…

fixed:error:0308010C:digital envelope routines::unsupported

目录1.故障现场2. 问题分析3. 修复方案4. 参考文献1.故障现场 最近由于一些原因&#xff0c;从Mac OSX 迁移到 Windows 平台&#xff0c;在尝试运行基于vue-element-admin 项目时&#xff0c;发生了如下异常&#xff1a; error:0308010C:digital envelope routines::unsuppor…

MySQL的一些指令,函数以及关键字

这个里面我准备记录一些比较有意思的MySQL的指令和函数&#xff0c;当然使用函数的时候我们要注意&#xff0c;会不会因为函数导致不走索引&#xff0c;走全表扫描的情况。 因为对索引字段做函数操作&#xff0c;可能会破坏索引值的有序性&#xff0c;因此优化器就决定放弃走树…