Kaggle系列之识别狗的品种类别(深度残差网络模型ResNet-34)

news/2024/4/27 10:52:08/文章来源:https://blog.csdn.net/weixin_41896770/article/details/129135643

我们来到这个比赛页面:https://www.kaggle.com/competitions/dog-breed-identification

这个数据集的目标是Determine the breed of a dog in an image(确定图像中狗的品种)

我们先下载数据集解压之后来看下(当然不手动解压,也可以使用),这里我放在一个DOG-BREED目录的dataset里面:

D:\DOG-BREED\dataset\train:10222张训练图
D:\DOG-BREED\dataset\test:10357张测试图
D:\DOG-BREED\dataset\labels.csv:10222行的id与breed,就是图片名称与对应的类别
D:\DOG-BREED\dataset\sample_submission.csv:提交的格式,121列,除了第一列的id,其余列是120个狗的品种分别对应的识别概率,10357行。
狗的品种之多属实超出我的想象:比如有㹴犬类、金毛猎犬、苏俄牧羊犬、马尔济斯犬、蓝色快狗、美洲赤狗等等

从下载的数据集来看是跟上一篇的Kaggle系列之CIFAR-10图像分类(残差网络模型ResNet-18)

的CIFAR-10数据集类似,区别就是图片尺寸更大了,尺寸也不是固定的大小。那对于熟悉了CIFAR-10分类方法来说,这节就显得比较容易了,大部分都差不多,对比上节来说有个新的知识点,就是图片大了,网络模型大了,我们使用预训练模型来获取特征,这个可以节省时间和训练期间存储参数的梯度的空间,对于配置低的伙伴们来说是最大的帮助了。

整理数据集

下载好之后,我们同样对数据集进行一些必要的整理,在原始训练数据集中切分出验证集,参数valid_ratio表示验证集中每类狗的样本数与原始训练集中数量最少一类的狗的样本数(66)之比。同一类狗的图像分别放到各自对应的目录中,便于后面的读取。

代码如下:

import collections
import d2lzh as d2l
import os
from mxnet import autograd,gluon,init,nd
from mxnet.gluon import data as gdata,loss as gloss,nn,model_zoo
import shutil
import time
import mathdef reorg_train_valid(data_dir, train_dir, input_dir, valid_ratio, idx_label):# 选择训练集最少一类的狗的样本数min_n_train_per_label = (collections.Counter(idx_label.values()).most_common()[:-2:-1][0][1])#验证集中每类狗的样本数n_valid_per_label = math.floor(min_n_train_per_label*valid_ratio)label_count = {}for train_file in os.listdir(os.path.join(data_dir, train_dir)):idx = train_file.split('.')[0]label = idx_label[idx]d2l.mkdir_if_not_exist([data_dir, input_dir, 'train_valid', label])src1 = os.path.join(data_dir, train_dir, train_file)dst1 = os.path.join(data_dir, input_dir, 'train_valid', label)shutil.copy(src1, dst1)if label not in label_count or label_count[label] < n_valid_per_label:d2l.mkdir_if_not_exist([data_dir, input_dir, 'valid', label])src2 = os.path.join(data_dir, train_dir, train_file)dst2 = os.path.join(data_dir, input_dir, 'valid', label)shutil.copy(src2, dst2)label_count[label] = label_count.get(label, 0)+1else:d2l.mkdir_if_not_exist([data_dir, input_dir, 'train', label])src3 = os.path.join(data_dir, train_dir, train_file)dst3 = os.path.join(data_dir, input_dir, 'train', label)shutil.copy(src3, dst3)def reorg_dog_data(data_dir, label_file, train_dir, test_dir, input_dir, valid_ratio):'''读取训练数据的标签,以及切分验证集并整理测试集'''with open(os.path.join(data_dir, label_file), 'r') as f:lines = f.readlines()[1:]tokens = [l.rstrip().split(',') for l in lines]idx_label = dict(((idx, label) for idx, label in tokens))reorg_train_valid(data_dir, train_dir, input_dir, valid_ratio, idx_label)# 整理测试集d2l.mkdir_if_not_exist([data_dir, input_dir, 'test', 'unknown'])for test_file in os.listdir(os.path.join(data_dir, test_dir)):src = os.path.join(data_dir, test_dir, test_file)dst = os.path.join(data_dir, input_dir, 'test', 'unknown')shutil.copy(src, dst)data_dir, label_file, train_dir, test_dir = 'dataset', 'labels.csv', 'train', 'test'
input_dir, batch_size, valid_ratio = 'train_valid_test', 128, 0.1
reorg_dog_data(data_dir, label_file, train_dir, test_dir, input_dir, valid_ratio)

执行之后,我们将会在dataset目录看到生成了一个train_valid_test目录,在其下面创建了四个目录:trainvalidtrain_validtest

然后在这些目录里面分别生成120个目录(文件夹名称就是类别),里面是对应品种的狗的图片,本人电脑的目录如下:

D:\DOG-BREED\dataset\train_valid_test\train\[affenpinscher]...\[74张]....jpg
D:\DOG-BREED\dataset\train_valid_test\valid\[affenpinscher]...\6张.jpg
D:\DOG-BREED\dataset\train_valid_test\train_valid\[affenpinscher]...\[74+6张]....jpg
D:\DOG-BREED\dataset\train_valid_test\test\unknown\10357张.jpg

collections.Counter

其中有一个collections.Counter方法,简单介绍下,也是官方源码:

c=collections.Counter('wwaabbcdeg')

得到的是一组字典,并分别统计它们各自的数量

Counter({'w': 2, 'a': 2, 'b': 2, 'c': 1, 'd': 1, 'e': 1, 'g': 1})

元组组成的列表形式

c.most_common()
#[('w', 2), ('a', 2), ('b', 2), ('c', 1), ('d', 1), ('e', 1), ('g', 1)]

所以在代码中选择最少一类的狗的样本数就是最后一个元组的第二列

c.most_common()[-2:-1][0][1]

查看元素

list(c.elements())
#['w', 'w', 'a', 'a', 'b', 'b', 'c', 'd', 'e', 'g']

排序且去重

sorted(c)
#['a', 'b', 'c', 'd', 'e', 'g', 'w']

统计数量

sum(c.values())#10

查看单个元素的统计数

c['w']#2

添加一个计数器

d=collections.Counter('wwwpla')
c.update(d)
#Counter({'w': 5, 'a': 3, 'b': 2, 'c': 1, 'd': 1, 'e': 1, 'g': 1, 'p': 1, 'l': 1})

基本用法就这些,更多的用法示例可以查看其定义。

图像增广

这次的数据集的图片比CIFAR10要大很多,所以在图片增广方面,尽量选取更多有用的图像增广操作。

transform_train = gdata.vision.transforms.Compose([# 随机裁剪0.08~1倍,高宽比3/4~4/3,然后缩放为高宽为224像素的图像gdata.vision.transforms.RandomResizedCrop(224, scale=(0.08, 1.0), ratio=(3.0/4.0, 4.0/3.0)),# 随机左右翻转gdata.vision.transforms.RandomFlipLeftRight(),# 随机变化亮度、对比度、饱和度gdata.vision.transforms.RandomColorJitter(brightness=0.4, contrast=0.4, saturation=0.4),# 随机加噪声gdata.vision.transforms.RandomLighting(0.1),gdata.vision.transforms.ToTensor(),gdata.vision.transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])#测试时,需要确定性的图像预处理操作
transform_test = gdata.vision.transforms.Compose([gdata.vision.transforms.Resize(256), gdata.vision.transforms.CenterCrop(224),gdata.vision.transforms.ToTensor(),gdata.vision.transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])

ToTensor()的用法

在图像增广的方法里面我们可以看到,在做图像增广操作后标准化之前,都需要做一次ToTensor(),这里有两个作用:

1、将形状从H*W*C转换成C*H*W
2、里面的值的范围[0, 255]转换成[0,1)

我们来看个示例,官方源码:

transformer = gdata.vision.transforms.ToTensor()
image = mx.nd.random.uniform(0, 255, (4, 2, 3)).astype(dtype=np.uint8)
'''
[[[144 100 236][213  18  86]][[ 22 165   5][ 93 212 244]][[198  35 221][221 249 120]][[203 204 117][132 199 173]]]
<NDArray 4x2x3 @cpu(0)>
'''
transformer(image)
'''
[[[0.5647059  0.8352941 ][0.08627451 0.3647059 ][0.7764706  0.8666667 ][0.79607844 0.5176471 ]][[0.39215687 0.07058824][0.64705884 0.83137256][0.13725491 0.9764706 ][0.8        0.78039217]][[0.9254902  0.3372549 ][0.01960784 0.95686275][0.8666667  0.47058824][0.45882353 0.6784314 ]]]
<NDArray 3x4x2 @cpu(0)>
'''

读取整理好的数据集

将数据集做了分类存放以及图像增广操作之后,接下来就是对数据集的读取了,同样是使用ImageFolderDataset实例来去读

train_ds = gdata.vision.ImageFolderDataset(os.path.join(data_dir, input_dir, 'train'), flag=1)
valid_ds = gdata.vision.ImageFolderDataset(os.path.join(data_dir, input_dir, 'valid'), flag=1)
train_valid_ds = gdata.vision.ImageFolderDataset(os.path.join(data_dir, input_dir, 'train_valid'), flag=1)
test_ds = gdata.vision.ImageFolderDataset(os.path.join(data_dir, input_dir, 'test'), flag=1)train_iter = gdata.DataLoader(train_ds.transform_first(transform_train), batch_size, shuffle=True, last_batch='keep')
valid_iter = gdata.DataLoader(valid_ds.transform_first(transform_test), batch_size, shuffle=True, last_batch='keep')
train_valid_iter = gdata.DataLoader(train_valid_ds.transform_first(transform_train), batch_size, shuffle=True, last_batch='keep')
test_iter = gdata.DataLoader(test_ds.transform_first(transform_test), batch_size, shuffle=False, last_batch='keep')

定义网络模型

数据集都做了预处理之后,我们就选择一个合适的网络模型,这里用ResNet-34模型,由于比赛数据集属于预训练数据集的子集(Gluon提供了很多的预训练模型),因此我们直接复用预训练模型在输出层的输入,即抽取的特征。然后自定义一个小型的输出网络即可,这样的好处就是不需要训练模型来抽取特征了,节省了训练时间,以及省去了存储参数梯度占用的空间。

def get_net(ctx):# 加载模型预训练的权重finetune_net = model_zoo.vision.resnet34_v2(pretrained=True)# 定义新的输出网络finetune_net.output_new = nn.HybridSequential(prefix='')finetune_net.output_new.add(nn.Dense(256, activation='relu'))# 狗的品种是120finetune_net.output_new.add(nn.Dense(120))# 初始化输入网络finetune_net.output_new.initialize(init.Xavier(), ctx=ctx)# 将模型参数分配到内存或显存上finetune_net.collect_params().reset_ctx(ctx)return finetune_netloss=gloss.SoftmaxCrossEntropyLoss()def evaluate_loss(data_iter, net, ctx):l_sum, n = 0.0, 0for X, y in data_iter:y = y.as_in_context(ctx)output_features = net.features(X.as_in_context(ctx))outputs = net.output_new(output_features)l_sum += loss(outputs, y).sum().asscalar()n += y.sizereturn l_sum/n

训练模型

当数据集和模型准备好了之后,就开始来训练模型,先定义一个训练函数train:

def train(net, train_iter, valid_iter, num_epochs, lr, wd, ctx, lr_period, lr_decay):trainer = gluon.Trainer(net.output_new.collect_params(), 'sgd', {'learning_rate': lr, 'momentum': 0.9, 'wd': wd})for epoch in range(num_epochs):train_l_sum, n, start = 0.0, 0, time.time()if epoch > 0 and epoch % lr_period == 0:trainer.set_learning_rate(trainer.learning_rate*lr_decay)for X, y in train_iter:y = y.as_in_context(ctx)output_features = net.features(X.as_in_context(ctx))with autograd.record():outputs = net.output_new(output_features)l = loss(outputs, y).sum()l.backward()trainer.step(batch_size)train_l_sum += l.asscalar()n += y.sizetime_s = "time %.2f sec" % (time.time()-start)if valid_iter is not None:valid_loss = evaluate_loss(valid_iter, net, ctx)epoch_s = ("epoch %d,train loss %f,valid loss %f," %(epoch+1, train_l_sum/n, valid_loss))else:epoch_s = ("epoch %d,train loss %f," % (epoch+1, train_l_sum/n))print(epoch_s+time_s+',lr '+str(trainer.learning_rate))ctx, num_epochs, lr, wd = d2l.try_gpu(), 1, 0.01, 1e-4
lr_period, lr_decay, net = 10, 0.1, get_net(ctx)
net.hybridize()
train(net, train_iter, valid_iter, num_epochs,lr, wd, ctx, lr_period, lr_decay)

批处理batch_size大了,很容易内存溢出:

mxnet.base.MXNetError: [12:31:44] c:\jenkins\workspace\mxnet-tag\mxnet\src\storage\./pooled_storage_manager.h:157: cudaMalloc failed: out of memory

所以需要结合自己的硬件配置来调整这个批大小,将128调小,我这里调成16先运行下是否正常

epoch 1,train loss 1.784260,valid loss 0.756940,time 104.48 sec,lr 0.01

然后我们使用全部训练数据集(含验证集)来重新训练模型,并对测试集分类,生成提交文件submission.csv,这里我将batch_size调成64,num_epochs设置成10,全部代码如下:

import collections
import d2lzh as d2l
import os
from mxnet import autograd,gluon,init,nd
from mxnet.gluon import data as gdata,loss as gloss,nn,model_zoo
import shutil
import time
import mathdef reorg_train_valid(data_dir, train_dir, input_dir, valid_ratio, idx_label):# 选择训练集最少一类的狗的样本数min_n_train_per_label = (collections.Counter(idx_label.values()).most_common()[:-2:-1][0][1])#验证集中每类狗的样本数n_valid_per_label = math.floor(min_n_train_per_label*valid_ratio)label_count = {}for train_file in os.listdir(os.path.join(data_dir, train_dir)):idx = train_file.split('.')[0]label = idx_label[idx]d2l.mkdir_if_not_exist([data_dir, input_dir, 'train_valid', label])src1 = os.path.join(data_dir, train_dir, train_file)dst1 = os.path.join(data_dir, input_dir, 'train_valid', label)shutil.copy(src1, dst1)if label not in label_count or label_count[label] < n_valid_per_label:d2l.mkdir_if_not_exist([data_dir, input_dir, 'valid', label])src2 = os.path.join(data_dir, train_dir, train_file)dst2 = os.path.join(data_dir, input_dir, 'valid', label)shutil.copy(src2, dst2)label_count[label] = label_count.get(label, 0)+1else:d2l.mkdir_if_not_exist([data_dir, input_dir, 'train', label])src3 = os.path.join(data_dir, train_dir, train_file)dst3 = os.path.join(data_dir, input_dir, 'train', label)shutil.copy(src3, dst3)def reorg_dog_data(data_dir, label_file, train_dir, test_dir, input_dir, valid_ratio):'''读取训练数据的标签,以及切分验证集并整理测试集'''with open(os.path.join(data_dir, label_file), 'r') as f:lines = f.readlines()[1:]tokens = [l.rstrip().split(',') for l in lines]idx_label = dict(((idx, label) for idx, label in tokens))reorg_train_valid(data_dir, train_dir, input_dir, valid_ratio, idx_label)# 整理测试集d2l.mkdir_if_not_exist([data_dir, input_dir, 'test', 'unknown'])for test_file in os.listdir(os.path.join(data_dir, test_dir)):src = os.path.join(data_dir, test_dir, test_file)dst = os.path.join(data_dir, input_dir, 'test', 'unknown')shutil.copy(src, dst)data_dir, label_file, train_dir, test_dir = 'dataset', 'labels.csv', 'train', 'test'
input_dir, batch_size, valid_ratio = 'train_valid_test', 64, 0.1
#reorg_dog_data(data_dir, label_file, train_dir, test_dir, input_dir, valid_ratio)# -------- 图像增广 ------------
transform_train = gdata.vision.transforms.Compose([# 随机裁剪0.08~1倍,高宽比3/4~4/3,然后缩放为高宽为224像素的图像gdata.vision.transforms.RandomResizedCrop(224, scale=(0.08, 1.0), ratio=(3.0/4.0, 4.0/3.0)),# 随机左右翻转gdata.vision.transforms.RandomFlipLeftRight(),# 随机变化亮度、对比度、饱和度gdata.vision.transforms.RandomColorJitter(brightness=0.4, contrast=0.4, saturation=0.4),# 随机加噪声gdata.vision.transforms.RandomLighting(0.1),gdata.vision.transforms.ToTensor(),gdata.vision.transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])#测试时,需要确定性的图像预处理操作
transform_test = gdata.vision.transforms.Compose([gdata.vision.transforms.Resize(256), gdata.vision.transforms.CenterCrop(224),gdata.vision.transforms.ToTensor(),gdata.vision.transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])# -----读取数据集-------
train_ds = gdata.vision.ImageFolderDataset(os.path.join(data_dir, input_dir, 'train'), flag=1)
valid_ds = gdata.vision.ImageFolderDataset(os.path.join(data_dir, input_dir, 'valid'), flag=1)
train_valid_ds = gdata.vision.ImageFolderDataset(os.path.join(data_dir, input_dir, 'train_valid'), flag=1)
test_ds = gdata.vision.ImageFolderDataset(os.path.join(data_dir, input_dir, 'test'), flag=1)train_iter = gdata.DataLoader(train_ds.transform_first(transform_train), batch_size, shuffle=True, last_batch='keep')
valid_iter = gdata.DataLoader(valid_ds.transform_first(transform_test), batch_size, shuffle=True, last_batch='keep')
train_valid_iter = gdata.DataLoader(train_valid_ds.transform_first(transform_train), batch_size, shuffle=True, last_batch='keep')
test_iter = gdata.DataLoader(test_ds.transform_first(transform_test), batch_size, shuffle=False, last_batch='keep')# ----定义模型-----
def get_net(ctx):# 加载模型预训练的权重finetune_net = model_zoo.vision.resnet34_v2(pretrained=True)# 定义新的输出网络finetune_net.output_new = nn.HybridSequential(prefix='')finetune_net.output_new.add(nn.Dense(256, activation='relu'))# 狗的品种是120finetune_net.output_new.add(nn.Dense(120))# 初始化输入网络finetune_net.output_new.initialize(init.Xavier(), ctx=ctx)# 将模型参数分配到内存或显存上finetune_net.collect_params().reset_ctx(ctx)return finetune_netloss=gloss.SoftmaxCrossEntropyLoss()def evaluate_loss(data_iter, net, ctx):l_sum, n = 0.0, 0for X, y in data_iter:y = y.as_in_context(ctx)output_features = net.features(X.as_in_context(ctx))outputs = net.output_new(output_features)l_sum += loss(outputs, y).sum().asscalar()n += y.sizereturn l_sum/ndef train(net, train_iter, valid_iter, num_epochs, lr, wd, ctx, lr_period, lr_decay):trainer = gluon.Trainer(net.output_new.collect_params(), 'sgd', {'learning_rate': lr, 'momentum': 0.9, 'wd': wd})for epoch in range(num_epochs):train_l_sum, n, start = 0.0, 0, time.time()if epoch > 0 and epoch % lr_period == 0:trainer.set_learning_rate(trainer.learning_rate*lr_decay)for X, y in train_iter:y = y.as_in_context(ctx)output_features = net.features(X.as_in_context(ctx))with autograd.record():outputs = net.output_new(output_features)l = loss(outputs, y).sum()l.backward()trainer.step(batch_size)train_l_sum += l.asscalar()n += y.sizetime_s = "time %.2f sec" % (time.time()-start)if valid_iter is not None:valid_loss = evaluate_loss(valid_iter, net, ctx)epoch_s = ("epoch %d,train loss %f,valid loss %f," %(epoch+1, train_l_sum/n, valid_loss))else:epoch_s = ("epoch %d,train loss %f," % (epoch+1, train_l_sum/n))print(epoch_s+time_s+',lr '+str(trainer.learning_rate))ctx, num_epochs, lr, wd = d2l.try_gpu(), 1, 0.01, 1e-4
lr_period, lr_decay, net = 10, 0.1, get_net(ctx)
net.hybridize()
#train(net, train_iter, valid_iter, num_epochs,lr, wd, ctx, lr_period, lr_decay)num_epochs=10
train(net, train_valid_iter, None, num_epochs,lr, wd, ctx, lr_period, lr_decay)
preds = []
for data, label in test_iter:output_features = net.features(data.as_in_context(ctx))output = nd.softmax(net.output_new(output_features))preds.extend(output.asnumpy())
ids = sorted(os.listdir(os.path.join(data_dir, input_dir, 'test/unknown')))
with open('submission.csv', 'w') as f:f.write('id,'+",".join(train_valid_ds.synsets)+'\n')for i, output in zip(ids, preds):f.write(i.split('.')[0]+','+','.join([str(num) for num in output])+'\n')'''
epoch 1,train loss 2.346387,time 105.90 sec,lr 0.01
epoch 2,train loss 1.054525,time 98.97 sec,lr 0.01
epoch 3,train loss 0.936225,time 100.72 sec,lr 0.01
epoch 4,train loss 0.882884,time 100.87 sec,lr 0.01
epoch 5,train loss 0.829612,time 98.21 sec,lr 0.01
epoch 6,train loss 0.820044,time 98.81 sec,lr 0.01
epoch 7,train loss 0.820517,time 101.82 sec,lr 0.01
epoch 8,train loss 0.789499,time 100.52 sec,lr 0.01
epoch 9,train loss 0.780991,time 98.92 sec,lr 0.01
epoch 10,train loss 0.742645,time 99.46 sec,lr 0.01
'''

将提交文件提交到Kaggle看下结果如何:

这个结果跟批处理大小和迭代次数有关,有兴趣的伙伴们可以调大点试试,当然还可以修改残差网络模型,加深层数看下效果会怎么样。

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

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

相关文章

记住这12个要点,你也能打造出让HR和技术主管前一亮的前端简历

第一篇章&#xff1a;吸引HR 如果你想在众多简历中脱颖而出&#xff0c;需要注意以下几点&#xff1a; 1、突出你的亮点&#xff1a; 给你的简历一个吸引人的文件命名和头部&#xff0c;突出你的关键技能和经验。 2、采用简洁的语言&#xff1a; 用简单易懂的语言来描述你的…

笔记本cpu温度多少正常?温度过高的4个常见原因

电脑CPU指的是中央处理器&#xff0c;它与电脑运行速度的快慢存在很大关系。如果电脑的处理器温度过高&#xff0c;就会影响我们电脑的运行速度&#xff0c;甚至出现蓝屏、卡顿的情况。 那么&#xff0c;对于电脑来说&#xff0c;笔记本cpu温度多少正常&#xff1f;有什么原因…

macOS Big Sur 11.7.4(20g1220)With OpenCore 0.8.9正式版 and winPE双引导分区原版镜像

原文来源于黑果魏叔官网&#xff0c;转载需注明出处。镜像特点完全由黑果魏叔官方制作&#xff0c;针对各种机型进行默认配置&#xff0c;让黑苹果安装不再困难。系统镜像设置为双引导分区&#xff0c;全面去除clover引导分区&#xff08;如有需要&#xff0c;可以自行直接替换…

KT1025A蓝牙音频芯片_立讯KC认证FCC测试现场整改记录

目录 一、问题说明简介 测试机构立讯反馈&#xff0c;客户寄的样板进行无线KC【韩国】测试不过&#xff0c;体现在如下两点 蓝牙部分接收杂散不过 蓝牙的发射功率偏低 2.1 单独只给蓝牙部分供电的测试图片--OK 2.2 单独给整板供电--但是使用电池供电 2.3 单独给整板供电-…

关于机器人坐标系变换的笔记

ROS TFros中&#xff0c;可以通过TF Tree来进行获取 机器人不同坐标系之间的转换关系&#xff0c;命令如下&#xff1a;rosrun tf tf_echo base_link head_link1意思为&#xff0c;从源坐标系base_link&#xff0c;到目标坐标系head_link1的变换关系&#xff0c;结果如下所示。…

Crafting interpreters 中文翻译,持续修正

本书在线地址 http://craftinginterpreters.com/ 感谢作者 作者用近 4 年的时间持续创作和改进本书&#xff0c;并把其 Web 版本公开在网上。这本纸质书于今年 7 月出版&#xff0c;立刻在 Hacker News 等网络媒介上引起关注和讨论。 书中作者首先定义了一个动态类型的语言 …

棋牌类游戏测试用例怎么写?我敢打赌你绝对不知道

目录 一&#xff0e;登陆 二&#xff0e;大厅 三&#xff0e;小游戏 四&#xff0e;银行功能 五&#xff0e;其他按钮 总结感谢每一个认真阅读我文章的人&#xff01;&#xff01;&#xff01; 重点&#xff1a;配套学习资料和视频教学 一&#xff0e;登陆 1&#xff0e…

使用拦截器实现登录状态检测(以及在注册拦截器类时要使用ioc中的拦截器类)

拦截器 preHandler(HttpServletRequest request, HttpServletResponse response, Object handler) 方法在请求处理之前被调用。该方法在 Interceptor 类中最先执行&#xff0c;用来进行一些前置初始化操作或是对当前请求做预处理&#xff0c;也可以进行一些判断来决定请求是否…

【MyBatis】源码学习 04 - 从 MapperMethod 简单分析一条 SQL 的映射操作流程

文章目录前言参考目录学习笔记1、测试代码说明2、binding 包的主要功能3、获取 Mapper 接口实例过程4、SQL 语句执行流程4.1、方法调用器4.2、MapperMethod 绑定方法4.2.1、SqlCommand4.2.2、MethodSignature4.3、MapperMethod#execute前言 本文内容对应的是书本第 13 章的内容…

循环、函数、对象——js基础练习

目录 一、循环练习 1.1 取款机案例 1.2 九九乘法表 1.3 根据数据生成柱形图 1.4 冒泡排序 1.6综合大练习 二、函数 2.1 转换时间案例 三、对象 1. 遍历数组对象 2. 猜数字游戏 3. 生成随机颜色 4. 学成在线页面渲染案例 一、循环练习 1.1 取款机案例 // 准备一个…

电商项目之Feign与Dubbo技术选型

文章目录1 问题背景2 前言3 思路4 Feign与Dubbo的区别5 总结6 真实案例1 问题背景 电商项目&#xff0c;B端以consul作为注册中心。重构了一个营销服务&#xff0c;以Nacos作为注册中心。B端需要调用营销服务。关于远程调用框架&#xff0c;营销服务用了Dubbo&#xff0c;而B端…

黑马程序员-Linux网络编程-01

目录 课程链接 协议 分层模型 网络传输数据封装流程 以太网帧和ARP请求 IP协议 TCP协议 BS与CS模型对比 套接字 网络字节序 IP地址转换函数 sockaddr地址结构 socket模型创建流程 socket()和bind() listen()和accept()​ 课程链接 03-协议_哔哩哔哩_bilibili 协…

java并发笔记

文章目录HashMapput方法resize方法ConcurrentHashMapput方法initTable方法sizectl代表什么&#xff1a;扩容计数器ConcurrentHashMap的读操作会阻塞嘛AQS唤醒线程时&#xff0c;AQS为什么从后往前遍历&#xff1f;AQS为什么要有一个虚拟的head节点AQS为什么用双向链表&#xff…

万字C语言学习笔记,带你学C带你飞(四)

文章目录单链表typedef1、基础typedef2、进阶typedef共用体枚举类型1、声明枚举类型2、定义枚举变量位域位操作文件的写入与写出C语言学习笔记&#xff0c;记录所学&#xff0c;便于复习。 由于篇幅过大&#xff0c;考虑到观感&#xff0c;准备分多篇记录。学习视频链接&#x…

Vue3.x使用Echarts绘制世界地图并进行定点

Vue3.x使用Echarts绘制世界地图并进行定点 一、需求 绘制世界地图并根据返回经纬度数据进行定点将定点数据展示在世界地图内 二、解决 绘制世界地图&#xff0c;利用Echarts图表组件时间&#xff0c;需要世界地图Geojson数据的可以在资源中下载世界地图Geojson数据-Javascr…

2022FALL嵌入式大纲

Jamslade 部分内容有遗漏&#xff0c;可结合 超文本 2022FALL《嵌入式系统原理》期末复习笔记 一起观看 文章目录嵌入式系统片上系统实时系统硬实时系统软实时系统伪指令DMA传输波特率单/半双/全双工通信&#xff1b;对齐/非对齐访问地址译码代码临界区RISCBIOSUARTSPII2CWDTRO…

2.5|shell简介|Linux支持的网络协议|Linux的网络服务

shell简介shell是一种具备特殊功能的程序&#xff0c;它是介于使用者和Unix/Linux操作系统内核间的一个接口。操作计算机需要通过命令&#xff08;command&#xff09;或是程序&#xff08;program&#xff09;&#xff1b;程序需要编译器&#xff08;compiler&#xff09;将程…

东南大学研究生英语18-19秋试卷解析

写在前面 作者&#xff1a;夏日 博客地址&#xff1a;https://blog.csdn.net/zss192 本文为东南大学研究生英语上学期18-19年期末试卷解析&#xff0c;答案来源于 ChatGPT International Conference 单选题 1.A presenter is supposed to do the following in an introdu…

【数据结构趣味多】八大排序

目录 1.直接插入排序 基本思想 代码实现&#xff1a; 直接插入排序的特性总结&#xff1a; 2.希尔排序 基本思想 代码实现 &#xff08;递归实现&#xff09; 希尔排序的特性总结 3.直接选择排序 基本思想 代码实现&#xff1a; 直接选择排序的特性总结 4.堆排序 …

Springboot 全局异常处理类

全局异常处理 在开发过程中&#xff0c;不管是Dao、Servie、Controller,层都有可能发生异常&#xff0c;对于异常处理&#xff0c;通常是try&#xff0d;catch或者直接throw&#xff0c;这会让try&#xff0d;catch的代码在代码中任意出现&#xff0c;系统的代码耦合度高&…