文章目录

  • 摘要
  • apex
  • DP和DDP
    • Parameter Server架构(PS模式)
    • ring-all-reduce模式
    • DDP的基本用法 (代码编写流程)
  • Mixup
  • 项目结构
  • 计算mean和std
  • 生成数据集
  • 训练
    • 导入项目使用的库
    • 设置全局参数
    • 设置distributed
    • 图像预处理与增强
    • 读取数据
    • 设置模型
    • 定义训练和验证函数
  • 测试

摘要

本例提取了植物幼苗数据集中的部分数据做数据集,数据集共有12种类别,模型使用最经典的resnet50,演示如何实现混合精度训练以及如何使用DDP的方式实现多卡并行训练。

通过本文你和学到:

1、如何使用混合精度训练?

2、如何制作ImageNet数据集?

3、如何使用DDP方式的进行多卡训练?

4、如何使用Mixup数据增强。

5、如何进行多卡BN同步?

6、如何使用余弦退火调整学习率?

7、如何使用classification_report实现对模型的评价。

8、预测的两种写法。

apex

使用apex实现混合精度训练,具体安装方法见:

https://blog.csdn.net/hhhhhhhhhhwwwwwwwwww/article/details/120839608

DP和DDP

pytorch中的有两种分布式训练方式,一种是常用的DataParallel(DP),另外一种是DistributedDataParallel(DDP),两者都可以用来实现数据并行方式的分布式训练,DP采用的是PS模式,DDP采用的是ring-all-reduce模式,两种分布式训练模式主要区别如下:

1、DP是单进程多线程的实现方式,DDP是采用多进程的方式。

2、DP只能在单机上使用,DDP单机和多机都可以使用。

3、DDP相比于DP训练速度要快。

Parameter Server架构(PS模式)

Parameter Server架构(PS模式)由server节点和worker节点组成。

server节点的主要功能是初始化和保存模型参数、接受worker节点计算出的局部梯度、汇总计算全局梯度,并更新模型参数(DP)。

worker节点的主要功能是各自保存部分训练数据,初始化模型,从server节点拉取最新的模型参数(pull),再读取参数,根据训练数据计算局部梯度,上传给server节点(push)。

详细的计算过程如下:

PS模式下的DP,会造成负载不均衡,因为充当server的GPU需要一定的显存用来保存worker节点计算出的局部梯度;另外server还需要将更新后的模型参数broadcast到每个worker,server的带宽就成了server与worker之间的通信瓶颈,server与worker之间的通信成本会随着worker数目的增加而线性增加。

ring-all-reduce模式

ring-all-reduce模式没有server节点,worker与worker之间的通信构成一个环。

ring-all-reduce模式下,所有worker只和自己相邻的两个worker进行通信,该工作模式分为两个工作阶段:

  1. Scatter Reduce:在这个 Scatter Reduce阶段,GPU 会逐步交换彼此的梯度并融合,最后每个 GPU 都会包含完整融合梯度的一部分

  2. Allgather:GPU 会逐步交换彼此不完整的融合梯度,最后所有 GPU 都会得到完整的融合梯度

计算过程如下:

DDP的基本用法 (代码编写流程)

  • 使用 torch.distributed.init_process_group 初始化进程组
  • 使用 torch.nn.parallel.DistributedDataParallel 创建 分布式模型
  • 使用 torch.utils.data.distributed.DistributedSampler 创建 DataLoader
  • 调整其他必要的地方(tensor放到指定device上,S/L checkpoint,指标计算等)
  • 使用 torch.distributed.launch / torch.multiprocessing 或 slurm 开始训练

Mixup

为了提高成绩我在代码中加入Mixup这种增强方式。使用到了timm,安装命令:

pip install timm

导入包:from timm.data.mixup import Mixup,

定义Mixup,和SoftTargetCrossEntropy

  mixup_fn = Mixup(mixup_alpha=0.8, cutmix_alpha=1.0, cutmix_minmax=None,prob=0.1, switch_prob=0.5, mode='batch',label_smoothing=0.1, num_classes=12)criterion_train = SoftTargetCrossEntropy()

项目结构

resnet_demo
├─data
│  ├─Black-grass
│  ├─Charlock
│  ├─Cleavers
│  ├─Common Chickweed
│  ├─Common wheat
│  ├─Fat Hen
│  ├─Loose Silky-bent
│  ├─Maize
│  ├─Scentless Mayweed
│  ├─Shepherds Purse
│  ├─Small-flowered Cranesbill
│  └─Sugar beet
├─mean_std.py
├─makedata.py
├─train.py
├─test1.py
└─test.py

mean_std.py:计算mean和std的值。

makedata.py:生成数据集。

计算mean和std

为了使模型更加快速的收敛,我们需要计算出mean和std的值,新建mean_std.py,插入代码:

from torchvision.datasets import ImageFolder
import torch
from torchvision import transformsdef get_mean_and_std(train_data):train_loader = torch.utils.data.DataLoader(train_data, batch_size=1, shuffle=False, num_workers=0,pin_memory=True)mean = torch.zeros(3)std = torch.zeros(3)for X, _ in train_loader:for d in range(3):mean[d] += X[:, d, :, :].mean()std[d] += X[:, d, :, :].std()mean.div_(len(train_data))std.div_(len(train_data))return list(mean.numpy()), list(std.numpy())if __name__ == '__main__':train_dataset = ImageFolder(root=r'data1', transform=transforms.ToTensor())print(get_mean_and_std(train_dataset))

数据集结构:

运行结果:

([0.3281186, 0.28937867, 0.20702125], [0.09407319, 0.09732835, 0.106712654])

把这个结果记录下来,后面要用!

生成数据集

我们整理还的图像分类的数据集结构是这样的

data
├─Black-grass
├─Charlock
├─Cleavers
├─Common Chickweed
├─Common wheat
├─Fat Hen
├─Loose Silky-bent
├─Maize
├─Scentless Mayweed
├─Shepherds Purse
├─Small-flowered Cranesbill
└─Sugar beet

pytorch和keras默认加载方式是ImageNet数据集格式,格式是

├─data
│  ├─val
│  │   ├─Black-grass
│  │   ├─Charlock
│  │   ├─Cleavers
│  │   ├─Common Chickweed
│  │   ├─Common wheat
│  │   ├─Fat Hen
│  │   ├─Loose Silky-bent
│  │   ├─Maize
│  │   ├─Scentless Mayweed
│  │   ├─Shepherds Purse
│  │   ├─Small-flowered Cranesbill
│  │   └─Sugar beet
│  └─train
│      ├─Black-grass
│      ├─Charlock
│      ├─Cleavers
│      ├─Common Chickweed
│      ├─Common wheat
│      ├─Fat Hen
│      ├─Loose Silky-bent
│      ├─Maize
│      ├─Scentless Mayweed
│      ├─Shepherds Purse
│      ├─Small-flowered Cranesbill
│      └─Sugar beet

新增格式转化脚本makedata.py,插入代码:

import glob
import os
import shutilimage_list=glob.glob('data1/*/*.png')
print(image_list)
file_dir='data'
if os.path.exists(file_dir):print('true')#os.rmdir(file_dir)shutil.rmtree(file_dir)#删除再建立os.makedirs(file_dir)
else:os.makedirs(file_dir)from sklearn.model_selection import train_test_splittrainval_files, val_files = train_test_split(image_list, test_size=0.3, random_state=42)train_dir='train'
val_dir='val'
train_root=os.path.join(file_dir,train_dir)
val_root=os.path.join(file_dir,val_dir)
for file in trainval_files:file_class=file.replace("\\","/").split('/')[-2]file_name=file.replace("\\","/").split('/')[-1]file_class=os.path.join(train_root,file_class)if not os.path.isdir(file_class):os.makedirs(file_class)shutil.copy(file, file_class + '/' + file_name)for file in val_files:file_class=file.replace("\\","/").split('/')[-2]file_name=file.replace("\\","/").split('/')[-1]file_class=os.path.join(val_root,file_class)if not os.path.isdir(file_class):os.makedirs(file_class)shutil.copy(file, file_class + '/' + file_name)

训练

完成上面的步骤后,就开始train脚本的编写,新建train.py.

导入项目使用的库

import torch
import torch.nn as nn
import torch.nn.parallel
import torch.optim as optim
import torch.utils.data
import torch.utils.data.distributed
import torchvision.datasets as datasets
import torchvision.transforms as transforms
from sklearn.metrics import classification_report
from timm.data.mixup import Mixup
from timm.loss import SoftTargetCrossEntropy
from torchvision.models.resnet import resnet50
from apex import amp
import torch.distributed as dist
from torch.nn.parallel import DistributedDataParallel as DDP
import os
from apex.parallel import convert_syncbn_model

设置全局参数

设置学习率、BatchSize、epoch等参数。

# 设置全局参数
model_lr = 1e-4
BATCH_SIZE = 256
EPOCHS = 1000
use_amp=False #是否使用混合精度
classes=12
CLIP_GRAD=5.0
is_distributed=True

model_lr:学习率,根据实际情况做调整。

BATCH_SIZE:batchsize,根据显卡的大小设置。

EPOCHS:epoch的个数,一般300够用。

use_amp:是否使用混合精度。

classes:类别个数。

CLIP_GRAD:梯度的最大范数,在梯度裁剪里设置。

rank:默认是0。

is_distributed:是否是分布式?

设置distributed

使用nccl的方式初始化初始化进程组。

# 0. set up distributed device
rank = int(os.environ["RANK"])
local_rank = int(os.environ["LOCAL_RANK"])
torch.cuda.set_device(rank % torch.cuda.device_count())
dist.init_process_group(backend="nccl")
device = torch.device("cuda", local_rank)
print(f"[init] == local rank: {local_rank}, global rank: {rank} ==")

进程组的参数介绍:

​ GROUP:进程组,大部分情况下DDP的各个进程是在同一个进程组下。

​ WORLD_SIZE:总的进程数量 (原则上一个process占用一个GPU是较优的)。

​ RANK:当前进程的序号,用于进程间通讯,rank = 0 的主机为 master 节点。

​ LOCAL_RANK:当前进程对应的GPU号。

图像预处理与增强

数据处理比较简单,加入了Cutout、做了Resize和归一化,定义Mixup函数。

# 数据预处理7
transform = transforms.Compose([transforms.Resize((224, 224)),transforms.ToTensor(),transforms.Normalize(mean=[0.51819474, 0.5250407, 0.4945761], std=[0.24228974, 0.24347611, 0.2530049])])
transform_test = transforms.Compose([transforms.Resize((224, 224)),transforms.ToTensor(),transforms.Normalize(mean=[0.51819474, 0.5250407, 0.4945761], std=[0.24228974, 0.24347611, 0.2530049])
])
mixup_fn = Mixup(mixup_alpha=0.8, cutmix_alpha=1.0, cutmix_minmax=None,prob=0.1, switch_prob=0.5, mode='batch',label_smoothing=0.1, classes=12)

读取数据

使用pytorch默认读取数据的方式,然后将dataset_train.class_to_idx打印出来,预测的时候要用到。

# 读取数据
train_dataset = datasets.ImageFolder('data/train', transform=transform)
train_sampler = torch.utils.data.distributed.DistributedSampler(train_dataset,shuffle=True)
val_dataset = datasets.ImageFolder("data/val", transform=transform_test)
print(train_dataset.class_to_idx)
# 导入数据
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=BATCH_SIZE, sampler=train_sampler)
test_loader = torch.utils.data.DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False)

设置模型

  • 设置loss函数,train的loss为:SoftTargetCrossEntropy,val的loss:nn.CrossEntropyLoss()。
  • 设置模型为resnet50,预训练设置为true,num_classes设置为12。
  • 使用convert_sync_batchnorm函数实现多卡之间的BN同步。
  • 创建DDP方式的多卡训练。
  • 优化器设置为adam。
  • 学习率调整策略选择为余弦退火。
  • 如果使用混合精度,则将amp初始化为“O1”。
# 实例化模型并且移动到GPU
criterion_train = SoftTargetCrossEntropy()
criterion_val = torch.nn.CrossEntropyLoss()
model_ft = resnet50(pretrained=True)
num_ftrs = model_ft.fc.in_features
model_ft.fc = nn.Linear(num_ftrs, classes)
#model_ft=convert_syncbn_model(model_ft)#使用apex同步BN
model_ft=torch.nn.SyncBatchNorm.convert_sync_batchnorm(model_ft)
model_ft.to(device)
print(model_ft)# 选择简单暴力的Adam优化器,学习率调低
optimizer = optim.Adam(model_ft.parameters(), lr=model_lr)
cosine_schedule = optim.lr_scheduler.CosineAnnealingLR(optimizer=optimizer, T_max=20, eta_min=1e-6)
if use_amp:model_ft, optimizer = amp.initialize(model_ft, optimizer, opt_level="O1") # 这里是“欧一”,不是“零一”
# DistributedDataParallel
model_ft = DDP(model_ft, device_ids=[local_rank], output_device=local_rank)

定义训练和验证函数

定义训练函数和验证函数。在训练函数中:

首先 ,调用set_epoch,每个 epoch 开始时调用 set_epoch() 方法,然后再创建 DataLoader 迭代器。

然后,初始化loss,开始遍历train_loader。

判断batch中有没有奇数的情况,如果有则减去一因为MixUp的loss需要偶数才行。

将数据放入转为cuda,计算mixup。

将data输入model得到输出结果,然后计算loss。

判断是否使用混合精度,如果使用则调用scaled_loss.backward()没有则调用loss.backward()。

clip_grad_norm_()执行梯度裁剪,防止梯度爆炸。

在一个epoch完成后,使用classification_report计算详细的得分情况。

# 定义训练过程
def train(model, device, train_loader, optimizer, epoch):if rank == 0:print("            =======  Training  ======= \n")train_sampler.set_epoch(epoch)model.train()sum_loss = 0total_num = len(train_loader.dataset)print(total_num, len(train_loader))for batch_idx, (data, target) in enumerate(train_loader):if len(data)%2!=0:print(len(data))data=data[0:len(data)-1]target=target[0:len(target)-1]print(len(data))data, target = data.to(device, non_blocking=True), target.to(device, non_blocking=True)samples, targets = mixup_fn(data, target)output = model(samples)loss = criterion_train(output, targets)optimizer.zero_grad()if use_amp:with amp.scale_loss(loss, optimizer) as scaled_loss:scaled_loss.backward()grad_norm = torch.nn.utils.clip_grad_norm_(amp.master_params(optimizer), CLIP_GRAD)else:loss.backward()grad_norm = torch.nn.utils.clip_grad_norm_(model.parameters(),CLIP_GRAD)#torch.nn.utils.clipgrad_norm() 的使用应该在loss.backward() 之后,optimizer.step()之前.#注意这个方法只在训练的时候使用,在测试的时候验证和测试的时候不用。optimizer.step()lr = optimizer.state_dict()['param_groups'][0]['lr']print_loss = loss.data.item()sum_loss += print_lossif (batch_idx + 1) % 10 == 0:print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}\tLR:{:.9f}'.format(epoch, (batch_idx + 1) * len(data), len(train_loader.dataset),100. * (batch_idx + 1) / len(train_loader), loss.item(), lr))ave_loss = sum_loss / len(train_loader)print('epoch:{},loss:{}'.format(epoch, ave_loss))
ACC = 0
# 验证过程
def val(model, device, test_loader):global ACCmodel.eval()test_loss = 0correct = 0total_num = len(test_loader.dataset)print(total_num, len(test_loader))val_list = []pred_list = []with torch.no_grad():for data, target in test_loader:for t in target:val_list.append(t.data.item())data, target = data.to(device), target.to(device)output = model(data)loss = criterion_val(output, target)_, pred = torch.max(output.data, 1)for p in pred:pred_list.append(p.data.item())correct += torch.sum(pred == target)print_loss = loss.data.item()test_loss += print_losscorrect = correct.data.item()acc = correct / total_numavgloss = test_loss / len(test_loader)print('\nVal set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(avgloss, correct, len(test_loader.dataset), 100 * acc))if acc > ACC:if isinstance(model, torch.nn.parallel.DistributedDataParallel):torch.save(model.module, 'model_' + str(epoch) + '_' + str(round(acc, 3)) + '.pth')else:torch.save(model, 'model_' + str(epoch) + '_' + str(round(acc, 3)) + '.pth')ACC = accreturn val_list, pred_list# 训练
is_set_lr = False
for epoch in range(1, EPOCHS + 1):train(model_ft, device, train_loader, optimizer, epoch)if epoch < 600:cosine_schedule.step()else:if is_set_lr:continuefor param_group in optimizer.param_groups:param_group["lr"] = 1e-6is_set_lr = Trueval_list, pred_list = val(model_ft, device, test_loader)print(classification_report(val_list, pred_list, target_names=train_dataset.class_to_idx))

然后在命令中执行命令:

python -m torch.distributed.launch --nproc_per_node=2 --nnodes=1 --node_rank=0 --master_addr=localhost --master_port=22222 train.py

nproc_per_node:表示每天机器上的显卡个数。

nnodes:节点个数。

node_rank:节点的rank。

master_addr:主节点的IP地址。

master_port:主节点的端口号。

更多的设置方式如下:

例1: 1 node, 4 GPUs per node (4GPUs)
>>> python -m torch.distributed.launch \--nproc_per_node=4 \--nnodes=1 \--node_rank=0 \--master_addr=localhost \--master_port=22222 \mnmc_ddp_launch.py[init] == local rank: 3, global rank: 3 ==
[init] == local rank: 1, global rank: 1 ==
[init] == local rank: 0, global rank: 0 ==
[init] == local rank: 2, global rank: 2 ==例2: 1 node, 2tasks, 4 GPUs per task (8GPUs)
>>> CUDA_VISIBLE_DEVICES=0,1,2,3 python -m torch.distributed.launch \--nproc_per_node=4 \--nnodes=2 \--node_rank=0 \--master_addr="10.198.189.10" \--master_port=22222 \mnmc_ddp_launch.py>>> CUDA_VISIBLE_DEVICES=4,5,6,7 python -m torch.distributed.launch \--nproc_per_node=4 \--nnodes=2 \--node_rank=1 \--master_addr="10.198.189.10" \--master_port=22222 \mnmc_ddp_launch.py例3: 2 node, 8 GPUs per node (16GPUs)
>>> python -m torch.distributed.launch \--nproc_per_node=8 \--nnodes=2 \--node_rank=0 \--master_addr="10.198.189.10" \--master_port=22222 \mnmc_ddp_launch.py>>> python -m torch.distributed.launch \--nproc_per_node=8 \--nnodes=2 \--node_rank=1 \--master_addr="10.198.189.10" \--master_port=22222 \mnmc_ddp_launch.py

注意:分布式训练只能在命令行中启动。

运行结果:

测试

我介绍两种常用的测试方式,第一种是通用的,通过自己手动加载数据集然后做预测,具体操作如下:

测试集存放的目录如下图:

第一步 定义类别,这个类别的顺序和训练时的类别顺序对应,一定不要改变顺序!!!!

第二步 定义transforms,transforms和验证集的transforms一样即可,别做数据增强。

第三步 加载model,并将模型放在DEVICE里,

第四步 读取图片并预测图片的类别,在这里注意,读取图片用PIL库的Image。不要用cv2,transforms不支持。

import torch.utils.data.distributed
import torchvision.transforms as transforms
from PIL import Image
from torch.autograd import Variable
import os
classes = ('Black-grass', 'Charlock', 'Cleavers', 'Common Chickweed','Common wheat','Fat Hen', 'Loose Silky-bent','Maize','Scentless Mayweed','Shepherds Purse','Small-flowered Cranesbill','Sugar beet')
transform_test = transforms.Compose([transforms.Resize((224, 224)),transforms.ToTensor(),transforms.Normalize(mean=[0.51819474, 0.5250407, 0.4945761], std=[0.24228974, 0.24347611, 0.2530049])
])DEVICE = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model = torch.load("model_2_0.418.pth",map_location='cuda:0')
model.eval()
model.to(DEVICE)path='data/test/'
testList=os.listdir(path)
for file in testList:img=Image.open(path+file)img=transform_test(img)img.unsqueeze_(0)img = Variable(img).to(DEVICE)out=model(img)# Predict_, pred = torch.max(out.data, 1)print('Image Name:{},predict:{}'.format(file,classes[pred.data.item()]))

运行结果:

第二种 使用自定义的Dataset读取图片

import torch.utils.data.distributed
import torchvision.transforms as transforms
from PIL import Image
import osclasses = ('Black-grass', 'Charlock', 'Cleavers', 'Common Chickweed','Common wheat','Fat Hen', 'Loose Silky-bent','Maize','Scentless Mayweed','Shepherds Purse','Small-flowered Cranesbill','Sugar beet')
transform_test = transforms.Compose([transforms.Resize((224, 224)),transforms.ToTensor(),transforms.Normalize(mean=[0.51819474, 0.5250407, 0.4945761], std=[0.24228974, 0.24347611, 0.2530049])
])DEVICE = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model = torch.load("model.pth")
model.eval()
model.to(DEVICE)dataset_test =SeedlingData('data/test/', transform_test,test=True)
print(len(dataset_test))
# 对应文件夹的labelfor index in range(len(dataset_test)):item = dataset_test[index]img, label = itemimg.unsqueeze_(0)data = Variable(img).to(DEVICE)output = model(data)_, pred = torch.max(output.data, 1)print('Image Name:{},predict:{}'.format(dataset_test.imgs[index], classes[pred.data.item()]))index += 1

运行结果:


完整的代码:
https://download.csdn.net/download/hhhhhhhhhhwwwwwwwwww/85143391

ResNet实战:单机多卡DDP方式、混合精度训练相关推荐

  1. torch dataloader 数据并行_PyTorch Parallel Training(单机多卡并行、混合精度、同步BN训练指南文档)

    0 写在前面 这篇文章是我做实验室组会汇报的时候顺带整理的文档,在1-3部分参考了很多知乎文章,感谢这些大佬们的工作,所以先贴出Reference,本篇文章结合了这些内容,加上了我的一些理解,不足之处 ...

  2. 实战 PK!RTX2080Ti 对比 GTX1080Ti 的 CIFAR100 混合精度训练

    雷锋网 AI 科技评论按:本文作者 Sanyam Bhutani 是一名机器学习和计算机视觉领域的自由职业者兼 Fast.ai 研究员.在文章中,他将 2080Ti 与 1080Ti 就训练时长进行了 ...

  3. 混合精度训练、分布式训练等训练加速方法

    以Pytorch为例 混合精度训练 Pytorch自动混合精度(AMP)训练 Pytorch自动混合精度(AMP)介绍与使用 1. 理论基础 pytorch从1.6版本开始,已经内置了torch.cu ...

  4. pytorch apex 混合精度训练和horovod分布式训练

    转载请注明出处: https://mp.csdn.net/postedit/103600124 如果你基于pytorch训练模型,然后,你想加快训练速度,增大batch_size,或者,你有一台配置多 ...

  5. 浅谈深度学习混合精度训练

    ↑ 点击蓝字 关注视学算法 作者丨Dreaming.O@知乎 来源丨https://zhuanlan.zhihu.com/p/103685761 编辑丨极市平台 本文主要记录下在学习和实际试用混合精度 ...

  6. 混合精度训练-Pytorch

    目录 1.需求解读 2.F16和FP32的区别与联系 3.F16优点简介 4.F16缺点简介 5.混合精度训练代码实战 5.1 代码实现 5.2 代码解析 6.F16训练效果展示 7.个人总结 参考资 ...

  7. 模型训练慢和显存不够怎么办?GPU加速混合精度训练

    目录 混合精度训练 理论原理 三大深度学习框架的打开方式 Pytorch Tensorflow PaddlePaddle 混合精度训练 一切还要从2018年ICLR的一篇论文说起... <MIX ...

  8. 混合精度训练amp,torch.cuda.amp.autocast():

    1 需要什么GPU: 在上面讲述了为什么利用混合精度加速,需要拥有 TensorCore 的GPU 0x02. 基础理论: 在日常中深度学习的系统,一般使用的是单精度 float(Single-Pre ...

  9. 深入理解混合精度训练:从 Tensor Core 到 CUDA 编程

    背景 近年来,自动混合精度(Auto Mixed-Precision,AMP)技术在各大深度学习训练框架中作为一种使用简单.代价低廉.效果显著的训练加速手段,被越来越广泛地应用到算法研究中. 然而大部 ...

最新文章

  1. 人是被经验塑造的动物,一家公司也是
  2. APT 信息收集——shodan.io ,fofa.so、 MX 及 邮件。mx记录查询。censys.io查询子域名。...
  3. A-Webkit第五章:添加成绩
  4. 华为畅享max有没有人脸识别_谁说千元机就要将就?华为畅享Z全面测评:5G、屏幕、拍照无短板...
  5. python中json模块读写数据
  6. HTML5 标签、事件句柄属性以及浏览器兼容情况速查手册
  7. 贵阳龙里计算机培训,贵州省龙里中等职业学校机械加工技术专业
  8. TensorFlow安装中遇到的问题
  9. MySQL5.6复制技术(2)-主从部署以及半同步配置详细过程
  10. JavaScript事件串连执行多个处理过程的方法
  11. 实用的才是最好的,教你如何以MATLAB的方式实现高等应用数学问题(一)
  12. 第五章 PCB 设计规则设置及 PCB 绘制
  13. FPGA高斯滤波实现并Modelsim仿真,与MATLAB高斯滤波进行对比
  14. IOS版添加phonegap--美洽客服插件教程
  15. 用python画猫咪怎么画-python画猫
  16. 计算机内存不足图片,电脑无法显示图片说内存不足
  17. 一个屌丝程序猿的人生(七十六)
  18. MSCOMM32控件注册的两种办法
  19. 世界上首先实现存储的电子数字计算机,世界上首先实现存储程序的电子数字计算机是ENIAC。...
  20. 读OpenSceneGraph快速入门指导(Paul Martz著王锐钱学雷译)有感

热门文章

  1. 陆平老师论文Closed-Loop Endoatmospheric Ascent Guidance读后总结
  2. 监听器:统计在线人数
  3. Winsock编程接口实验:实现ipconfig
  4. 什么是BST?什么是哈希表?一文带你了解并实现查找的基础知识
  5. win7 引导 ubuntu
  6. 《化工流体力学》课程笔记(四)
  7. OC加强(三)之protocol(协议)/代理
  8. 商务办公用什么邮箱,注册163.net邮箱怎么样
  9. 计算机基础操作(计算机硬件知识)
  10. WPS计算机一级考试知识点,计算机一级考试WPS练习题及答案