最近受邀参与了亚马逊云科技【云上探索实验室】活动,体验了一下Amazon SageMaker平台,训练了一个人脸的自编码器。对比本地的训练时间,速度提升比较明显。本文将围绕Amazon SageMaker和自编码器进行。
自编码器是一个非常简单的网络,早在上世纪90年代就提出了自编码器的概念。当时使用受限的玻尔兹曼机分层训练,在硬件强大的今天可以实现端到端的训练。自编码器有许多变种,比如变分自编码器、去噪自编码器、正则自编码器等。由于自编码器采用的是自监督学习,因此使用自编码器可以使用较低成本得到较好的效果。
今天我们将训练一个自编码器。自编码器训练完成后可以进行各种有趣的实验,像编辑一个人的表情、让人脸A渐渐变成人脸B、让一个人从小慢慢变老、生成人脸等。
在本文我们会做一部分实验,实现人脸渐变和生成人脸的操作。
Amazon SageMaker是一个完全托管的机器学习服务平台,包含了机器学习的各个流程,从标注到部署。开发人员可以快速构建模型并训练,还可以部署到托管环境。Amazon SageMaker提供了Jupyter笔记本,而且可以执行各种流行框架,不止是MXNet,还可以使用PyTorch、TensorFlow等主流框架。
在数据标注时,Ground Truth可以用来团队标注,在标注一定数据后,Ground Truth可以自动标注,当对标注不确定时才会让人工进行标注。
Amazon SageMaker提供了数据存储,模型部署等服务,完成这些操作都可以一键式完成。
同时Amazon SageMaker提供了许多高阶API,可以使用Amazon Auto Pilot自动训练模型与调优模型。同时还可以对模型的情况进行监控,以便更好地改善模型。
在开始使用Amazon SageMaker完成机器学习任务前,先熟悉一下机器学习的流程。机器学习的流程分为一下几个步骤:
上述步骤整体是一条直线,但是经常会回到前面几个步骤重新往下执行。如下图所示:
1-3为数据处理部分,主要是数据从无到有、从杂乱到规范,在此步骤中,我们会用到诸如爬虫、正则、归一化、标准化等技术。处理后的数据通常表示为一个张量(多维数组),不同类型的数据的形状有所不同。图像数据通常为n_sample × channel × height × width
,各维度含义分别为样本数、通道数、高、宽,有时候通道数会放在最后一个维度。表格数据被处理为n_sample × n_feature
,分别为样本数、特征数。文本数据则是n_sample × sequence_len × 1
,分别为样本数和文本长度。还有时间序列、视频、立体图片等数据都会处理成固定形状的张量。
数据处理成张量后还可以做一些特征工程,比如特征选择、标准化、归一化等。这一步操作有利于模型训练。
4-5为训练评估部分,在处理好数据后就可以开始训练模型了。训练模型需要我们确定几样东西,分别是模型结构(选取上面算法)、对应的超参数、优化器等。在此过程通常会训练多个模型,选取一个最优模型作为最终模型。
为了选择较优的超参数,会使用到网格搜索交叉验证。在sklearn模块中,提供了对应的实现。SageMaker中也有类似更强的功能,后面有具体体验。
当选取一个最优模型后,既可以将模型部署上线,模型的部署可以作为API、Web、小程序、APP等。在部署上线后,模型可能会出现各种问题,也会慢慢落后,因此还需要对模型进行监控维护。在上线后,可以继续收集一些用户授权的数据,然后把这些数据重复之前的步骤,对模型进行迭代优化。
SageMaker中机器学习流程于上面一致,下面我们来实际看看各个步骤如何操作。
Amazon SageMaker中可以创建Jupyter Notebook,创建的Notebook可以执行pip、wget等指令。我们可以使用以往使用的所有方式处理数据,也可以使用SageMaker自带的SDK。在SageMaker中有一个sklearn子模块,可以用来处理数据。
SKLearnProcessor可以执行sklearn脚本来处理数据,首先需要创建SKLearnProcessor对象,然后调用run方法对数据进行处理,示例代码如下:
from sagemaker import get_execution_role
from sagemaker.sklearn.processing import SKLearnProcessor
from sagemaker.processing import ProcessingInput, ProcessingOutput
role = get_execution_role()
sklearn_processor = SKLearnProcessor(
framework_version="0.20.0", role=role, instance_type="ml.t3.medium", instance_count=1
)
sklearn_processor.run(
code="preprocessing.py",
inputs=[
ProcessingInput(source="s3://your-bucket/path/to/your/data", destination="/opt/ml/processing/input"),
],
outputs=[
ProcessingOutput(output_name="train_data", source="/opt/ml/processing/train"),
ProcessingOutput(output_name="test_data", source="/opt/ml/processing/test"),
],
arguments=["--train-test-split-ratio", "0.2"],
)
preprocessing_job_description = sklearn_processor.jobs[-1].describe()
在创建调用run方法时,需要先创建好用于处理数据的preprocessing.py
文件,对应code参数。preprocessing.py
命令行参数由arguments给出。
preprocessing.py
可以用sklearn来做具体处理。处理完成后,处理结果等信息保存在preprocessing_job_description中,可以通过preprocessing_job_description[‘Outputs’]拿到处理结果。
模型的训练与处理数据一样,需要准备好对应的训练脚本train.py
。处理好数据并准备好train.py
脚本后,使用下面的代码就可以进行训练了,这里的实例类型要和数据处理的实例类型一致。
from sagemaker.sklearn.estimator import SKLearn
sklearn = SKLearn(
entry_point="train.py", framework_version="0.20.0", instance_type="ml.t3.medium", role=role
)
sklearn.fit({
"train": preprocessed_training_data})
training_job_description = sklearn.jobs[-1].describe()
model_data_s3_uri = "{}{}/{}".format(
training_job_description["OutputDataConfig"]["S3OutputPath"],
training_job_description["TrainingJobName"],
"output/model.tar.gz",
)
评估模型的代码也是一样的风格:
sklearn_processor.run(
code="evaluation.py",
inputs=[
ProcessingInput(source=model_data_s3_uri, destination="/opt/ml/processing/model"),
ProcessingInput(source=preprocessed_test_data, destination="/opt/ml/processing/test"),
],
outputs=[ProcessingOutput(output_name="evaluation", source="/opt/ml/processing/evaluation")],
)
evaluation_job_description = sklearn_processor.jobs[-1].describe()
构建和训练模型后,您可以将模型部署至终端节点,以中获取预测推理结果。部署使用下面代码即可完成:
predictor = estimator.deploy(initial_instance_count=1, instance_type="ml.m4.xlarge")
可以根据任务要求选择实例类型。
下面使用SageMaker完成自编码相关的实验。
自编码器是一个非常简单网络,通常由编码器和解码器两个部分组成。编码器解码器的结构可以用全连接,也可以用卷积,或者其它一些网络。在早期自编码器的编码器解码器需要分开训练,而现在通常是端到端的训练。
编码器部分会将输入逐步降维,最后得到一个固定长度的向量,这个向量可以作为输入数据的编码。解码器部分接收编码器的输出,结果解码器会得到一个形状与编码器输入一样的数据。自编码器训练的目的就是输出与数据尽可能接近。
整体上看自编码器使用的是监督学习方法,但是目标值和特征值是一样的,像这种标签由数据自身给出的学习方法又被称为自监督学习。
自编码器结构简单,但是有一些非常好的性质。比如训练简单,不需要人工标记数据等。使用自编码器可以对数据进行降维,创建新数据等。假设对人脸图像数据进行编码,编码器会得到1024维的向量。那么编码器得到的这个向量每一个维度可能代表着一种特征,比如第n个维度可能代表表情、第k个维度可能代表性别等。如果能知道这些信息,就可以做一些有趣的事情。
这里使用Amazon SageMaker的笔记本实例进行实验,创建笔记本实例,创建时使用默认选项即可。在创建时需要记住使用的实例类型,后续训练需要对应正确的类型。
创建好环境后,可以在笔记本中运行下面代码获取当前角色以及S3桶:
import sagemaker
import os
sess = sagemaker.Session()
role = sagemaker.get_execution_role()
bucket = sagemaker_session.default_bucket()
下面就可以开始数据的准备了。
这里使用PyTorch完成数据的处理和训练,下面是需要用到的一些模块:
from torch import nn
from torch import optim
from torch.utils.data import Dataset, DataLoader
from torchvision import datasets
from torchvision import transforms
本实验使用的是CelebA Dataset,可以在http://mmlab.ie.cuhk.edu.hk/projects/CelebA.html下载。数据集里面包含了10万修正好的人脸图片。将图片下载到电脑上后,再把数据上传到笔记本中。为了方便加载,创建一个Dataset类,完成数据的加载:
class FaceDataset(Dataset):
def __init__(self, data_dir="./datasets/img_align_celeba", image_size=64):
self.image_size = image_size
self.trans = transforms.Compose([
transforms.Resize(image_size),
transforms.CenterCrop(image_size),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
self.image_paths = []
if os.path.isdir(data_dir):
for root, dirs, files in os.walk(data_dir):
for file in files:
self.image_paths.append(os.path.join(root, file))
elif os.path.isfile(data_dir):
with open(data_dir, encoding='utf-8') as f:
self.image_paths = f.read().strip().split()
def __getitem__(self, item):
return self.trans(Image.open(self.image_paths[item]))
def __len__(self):
return len(self.image_paths)
下面创建DataLoader用于加载数据:
dataset = FaceDataset(data_dir="./datasets/img_align_celeba")
dataloader = DataLoader(dataset, 64)
另外,可以通过下面代码将数据上传至S3桶:
inputs = sagemaker_session.upload_data(path="datasets/img_align_celeba", bucket=bucket, key_prefix='sagemaker/img_align_celeba')
数据准备好后,需要编写模型以及训练脚本。
本文以人脸数据训练一个自编码器,而后用这个自编码器做一些其它事情,首先训练一个自编码器。网络结构由卷积和转置卷积组成,代码如下:
class FaceAutoEncoder(nn.Module):
def __init__(self, encoded_dim=1024):
super(FaceAutoEncoder, self).__init__()
# [b, 3, 64, 64] --> [b, 1024, 1, 1]
self.encoder = nn.Sequential(
nn.Conv2d(3, 64, 4, 2, 1, bias=False),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(64, 64 * 2, 4, 2, 1, bias=False),
nn.BatchNorm2d(64 * 2),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(64 * 2, 64 * 4, 4, 2, 1, bias=False),
nn.BatchNorm2d(64 * 4),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(64 * 4, 64 * 8, 4, 2, 1, bias=False),
nn.BatchNorm2d(64 * 8),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(64 * 8, encoded_dim, 4, 1, 0, bias=False),
nn.LeakyReLU(0.2, inplace=True)
)
# [b, 1024, 1, 1] - > [b, 3, 64, 64]
self.decoder = nn.Sequential(
nn.ConvTranspose2d(encoded_dim, 64 * 8, 4, 1, 0, bias=False),
nn.BatchNorm2d(64 * 8),
nn.ReLU(True),
nn.ConvTranspose2d(64 * 8, 64 * 4, 4, 2, 1, bias=False),
nn.BatchNorm2d(64 * 4),
nn.ReLU(True),
nn.ConvTranspose2d(64 * 4, 64 * 2, 4, 2, 1, bias=False),
nn.BatchNorm2d(64 * 2),
nn.ReLU(True),
nn.ConvTranspose2d(64 * 2, 64, 4, 2, 1, bias=False),
nn.BatchNorm2d(64),
nn.ReLU(True),
nn.ConvTranspose2d(64, 3, 4, 2, 1, bias=True),
nn.Tanh()
)
def forward(self, x):
x = self.encoder(x)
x = self.decoder(x)
return x
编码器部分将64×64的图片转换成1024维的向量,而解码器则是利用转置卷积将1024维的向量转换成64×64的图像。现在我们的目标是模型输入与数据越接近越好,所有可以用均方误差作为损失函数,同时输入和目标为同一批数据。下面编写一个训练脚本autoencoder.py
,代码如下:
def train(args):
device = "cuda" if torch.cuda.is_available() else "cpu"
dataset = FaceDataset()
dataloader = DataLoader(dataset, 64)
model = FaceAutoEncoder().to(device)
optimizer = optim.SGD(model.parameters(), lr=args.lr, momentum=0.9)
for epoch in range(1, args.epochs + 1):
model.train()
for batch_idx, data in enumerate(dataloader, 1):
data = data.to(device)
optimizer.zero_grad()
output = model(data)
loss = F.mse_loss(output, data)
loss.backward()
optimizer.step()
save_model(model, args.model_dir)
def model_fn(model_dir):
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = torch.nn.DataParallel(AutoEncoder())
with open(os.path.join(model_dir, "model.pth"), "rb") as f:
model.load_state_dict(torch.load(f))
return model.to(device)
def save_model(model, model_dir):
path = os.path.join(model_dir, "model.pth")
torch.save(model.decoder.cpu().state_dict(), path)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"--epochs",
type=int,
default=10,
metavar="N",
help="number of epochs to train (default: 10)",
)
parser.add_argument(
"--lr", type=float, default=0.01, metavar="LR", help="learning rate (default: 0.01)"
)
train(parser.parse_args())
我们可以使用argparse设置一些参数。脚本准备好后,可以开始进行训练,因为使用的是PyTorch,所以要改用sagemaker.pytorch中的PyTorch来进行训练,代码如下:
from sagemaker.pytorch import PyTorch
estimator = PyTorch(
entry_point="autoencoder.py",
role=role,
py_version="py38",
framework_version="1.11.0",
instance_count=1,
instance_type="ml.c5.2xlarge",
hyperparameters={
"epochs": 4},
)
estimator.fit({
"training": inputs})
其中entry_point就是前面的训练脚本。然后等待模型训练,训练完成后可以得到模型文件model.pth,这里只包含decoder部分。下面是输出的结果:
训练完成后使用下面一句就可以对模型进行部署:
predictor = estimator.deploy(initial_instance_count=1, instance_type="ml.m4.xlarge")
不过自编码器本身只是还原原有数据而已,要生成人脸需要使用Decoder部分,进行推理。
在前面提到自编码器的一个优点就是可以把对输入的操作改为对编码的操作,现在我们训练了一个人脸的自编码。假设人脸A被编码成z1,人脸B被编码成z2,现在想让人脸由A到B渐变。现在可以把这个问题转换为向量z1和z2之间的渐变,向量的渐变可以直接使用插值算法,我们在两个向量见插入n个向量,再把这些向量输入解码器,得到的人脸图像就是介于A和B之间的人脸。现在人脸渐变就变为了插值。
首先实现插值算法,插值的实现很简单,具体代码如下:
def interpolate(x1, x2, num):
result = torch.zeros((num, *x1.shape))
step = (x2 - x1) / (num - 1)
for i in range(num):
result[i] = x1 + step * i
return result
上面函数输入两个长度一样的向量,输出num个向量。这num个向量将作为Decoder的输入。接下来使用Decoder部分进行推理:
# 加载数据集
dataloader = DataLoader(
FaceDataset(data_dir='/home/zack/Files/datasets/img_align_celeba', image_size=64),
batch_size=2
)
model = FaceAutoEncoder()
model.load_state_dict(torch.load('../outputs/face_auto_encoder.pth'))
model.eval()
with torch.no_grad():
for idx, data in enumerate(dataloader):
# 对人脸编码
encoded1 = model.encoder(data[0].reshape((1, 3, 64, 64)))
encoded2 = model.encoder(data[1].reshape((1, 3, 64, 64)))
# 对人脸编码进行插值
encoded = interpolate(encoded1[0], encoded2[0], 64)
# 解码成人脸
outputs = model.decoder(encoded).reshape((64, 3, 64, 64))
outputs = make_grid(outputs, normalize=True)
plt.imshow(outputs.numpy().transpose((1, 2, 0)))
plt.show()
下面是实现的效果:
可以看到人脸转变的很自然。现在可以将这一部分部署,部署代码如下:
from sagemaker.pytorch import PyTorchModel
pytorch = PyTorchModel(
model_data=model_data,
role=role,
entry_point="inference.py",
source_dir="code",
framework_version="1.3.1",
py_version="py3",
sagemaker_session=sagemaker_session,
)
predictor = pytorch.deploy(
initial_instance_count=1,
instance_type='ml.m5.large',
endpoint_name=endpoint_name,
wait=True,
)
其中inference.py是推理脚本,具体代码如下:
def predict_fn(input_data, model):
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
input_data = input_data.to(device)
with torch.no_grad():
inputs = interpolate(input_data[:, 0], input_data[:, 1])
return model.decoder.forward(inputs)
def model_fn(model_dir="model.pth"):
loaded_model = torch.jit.load(model_dir, map_location=torch.device("cpu"))
loaded_model = loaded_model.eval()
return loaded_model
然后使用predictor.predict
传入2 × 3 × 64 × 64的张量就可以完成模型的推理,返回插值后的人脸图像。
自编码器除了实现人脸渐变外,还可以用来生成人脸,关键点依然在编码部分。
自编码器生成人脸的原理比较简单,在训练自编码器时,把人脸编码成一个长度为1024维的向量。现在我们假设人脸服从高斯分布,如果能求出均值和方差,就可以知道这个高斯分布的具体样子。在知道高斯分布的具体表达式后,就可以对从中采样人脸向量,把这个向量交给decoder就可以生成人脸。
均值和方差可以用统计的方式获取,具体代码如下,把结果保存为一个npz文件:
mean = np.zeros((zdim,), dtype=np.float32)
cov = np.zeros((zdim, zdim), dtype=np.float32)
device = "cuda" if torch.cuda.is_available() else "cpu"
model.eval()
model = model.to(device)
with torch.no_grad():
for idx, data in enumerate(dataloader):
try:
data = data.to(device)
encoded = model.encoder(data).view(128, -1)
mean += encoded.mean(axis=0).cpu().numpy()
cov += np.cov(encoded.cpu().numpy().T)
if idx % 50 == 0:
print(f"\ridx: {
idx}/{
len(dataloader)}", end="")
except Exception as e:
pass
mean /= (idx + 1)
cov /= (idx + 1)
np.savez('face_distribution.npz', mean, cov)
生成人脸的操作就是从前面的高斯分布中进行采样,然后把采样的向量交给decoder进行编码即可。具体代码:
# 加载人脸分布
distribution = np.load('face_distribution.npz')
mean = distribution['arr_0']
cov = distribution['arr_1']
# 生成编码向量
batch_size = 64
z = np.random.multivariate_normal(
mean,
cov,
batch_size
).astype(np.float32)
# 解码
with torch.no_grad():
encoded = torch.from_numpy(z).view(batch_size, 1024, 1, 1)
outputs = model.decoder(encoded)
outputs = torch.clamp(outputs, 0, 255)
grid = make_grid(outputs).numpy().transpose((1, 2, 0))
plt.imshow(grid)
plt.show()
下面是生成的一些人脸,五官部分可以看的很清楚,但是背景部分有一些模糊。
现在前面Amazon SageMaker部署的代码可以复用,只需要修改推理的代码即可。把inference.py中predict_fn函数修改成下面的样子:
def predict_fn(input_data, model):
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
z = torch.randn(1, 1024, 1, 1).to(device)
with torch.no_grad():
return model.decoder.forward(z)
在前面,我们使用Amazon SageMaker,围绕自编码器实现了人脸渐变和人脸生成的实验。自编码器并非主流的生成模型,对于图像生成,现在更流行Stable Diffusion模型,相比之下Stable Diffusion的能力更强,生成的图像更逼真。Amazon官方给出了Stable Diffusion相关实验:https://catalog.us-east-1.prod.workshops.aws/workshops/3b86fa89-da3a-4e5f-8e77-b45fb11adf4a/zh-CN。我们可以使用Amazon SageMaker直接运行,详细的实践可以参考实验手册,下面给出跑出来的一些示例图片。
比如文本输入:拿着长剑的猫,可以得到下面的图片
或者:梵高星空,可以得到下面图片
每次生成的图片是不一样的,大家可以多尝试一些有趣的东西。
自编码器的结构非常简单,训练起来相比GAN等要更容易,但是功能特别强大。在前面的实验中,我们都是把对结果的调整转为对编码的处理。在自编码器中有一个非常理想的设想,就是每个维度控制一个特征,但是实际上没有这么简单。自编码器还有一些其它变形,比如变分自编码器,ALAE等,可以实现更加强大的功能,甚至可以媲美StyleGAN,生成逼真的网络。
除了直接使用自编码器,在其它网络中也可以插入与自编码器类似的结构,比如Stable Diffusion中就有Unet网络,也是一种编码解码结构。
用于机器学习的平台有很多,Amazon SageMaker是比较全面,且面向应用的一个。在Amazon SageMaker中包括了机器学习的各个流程,在以往Python中的开发习惯可以在Amazon SageMaker中完全适用,另外Amazon SageMaker提供了更高阶的API,可以让用户更专注于算法的研究。
Amazon SageMaker支持Sklearn、PyTorch、TensorFlow、Hugging Face等,对这些主流模块和框架都有相应的封装。另外Amazon SageMaker提供了非常便捷的部署方式。
为了方便训练模型,Amazon SageMaker中提供了Amazon AutoPilot可以自动对各种模型以及各组超参数进行搜索,训练最优模型。
关于云上探索实验室的活动可以参考https://dev.amazoncloud.cn/experience?trk=cndc-detail&sc_medium=corecontent&sc_campaign=product&sc_channel=csdn,里面有许多有趣的实验案例。通过云上探索实验室,开发者可以学习实践云上技术,同时将自己的技术心得分享给其他开发者小伙伴。一同创造分享,互助启发,玩转云上技术。云上探索实验室不仅是体验的空间,更是分享的平台”
文章浏览阅读15次。空化气泡的大小和相应的空化能量可以通过调整完全标度的振幅水平来操纵和数字控制。通过强调超声技术中的更高通量处理和防止样品污染,Epigentek EpiSonic超声仪可以轻松集成到现有的实验室工作流程中,并且特别适合与表观遗传学和下一代应用的兼容性。Epigentek的EpiSonic已成为一种有效的剪切设备,用于在染色质免疫沉淀技术中制备染色质样品,以及用于下一代测序平台的DNA文库制备。该装置的经济性及其多重样品的能力使其成为每个实验室拥有的经济高效的工具,而不仅仅是核心设施。
文章浏览阅读4.2k次,点赞3次,收藏14次。目录点击这里查看所有博文 本系列博客,理论上适用于合宙的Air202、Air268、Air720x、Air720S以及最近发布的Air720U(我还没拿到样机,应该也能支持)。 先不管支不支持,如果你用的是合宙的模块,那都不妨一试,也许会有意外收获。 我使用的是Air720SL模块,如果在其他模块上不能用,那就是底层core固件暂时还没有支持,这里的代码是没有问题的。例程仅供参考!..._合宙获取天气
文章浏览阅读7.7k次,点赞2次,收藏41次。1 关于meshMesh的意思是网状物,以前读书的时候,在自动化领域有传感器自组网,zigbee、蓝牙等无线方式实现各个网络节点消息通信,通过各种算法,保证整个网络中所有节点信息能经过多跳最终传递到目的地,用于数据采集。十多年过去了,在无线路由器领域又把这个mesh概念翻炒了一下,各大品牌都推出了mesh路由器,大多数是3个为一组,实现在面积较大的住宅里,增强wifi覆盖范围,智能在多热点之间切换,提升上网体验。因为节点基本上在3个以内,所以mesh的算法不必太复杂,组网形式比较简单。各厂家都自定义了组_802.11s
文章浏览阅读5.2k次,点赞8次,收藏21次。线程的几种状态_线程状态
文章浏览阅读4.2w次,点赞124次,收藏688次。stack翻译为栈,是STL中实现的一个后进先出的容器。要使用 stack,应先添加头文件include<stack>,并在头文件下面加上“ using namespacestd;"1. stack的定义其定义的写法和其他STL容器相同, typename可以任意基本数据类型或容器:stack<typename> name;2. stack容器内元素的访问..._stack函数用法
文章浏览阅读71次。<li> <a href = "“#”>-</a></li><li>子节点:文本节点(回车),元素节点,文本节点。不同节点树: 节点(各种类型节点)childNodes:返回子节点的所有子节点的集合,包含任何类型、元素节点(元素类型节点):child。node.getAttribute(at...
文章浏览阅读3.4k次。//config的设置是全局的layui.config({ base: '/res/js/' //假设这是你存放拓展模块的根目录}).extend({ //设定模块别名 mymod: 'mymod' //如果 mymod.js 是在根目录,也可以不用设定别名 ,mod1: 'admin/mod1' //相对于上述 base 目录的子目录}); //你也可以忽略 base 设定的根目录,直接在 extend 指定路径(主要:该功能为 layui 2.2.0 新增)layui.exten_layui extend
文章浏览阅读3.2k次,点赞6次,收藏13次。分层思想分层思想分层思想-1分层思想-2分层思想-2OSI七层参考模型物理层和数据链路层物理层数据链路层网络层传输层会话层表示层应用层OSI七层模型的分层结构TCP/IP协议族的组成数据封装过程数据解封装过程PDU设备与层的对应关系各层通信分层思想分层思想-1在现实生活种,我们在喝牛奶时,未必了解他的生产过程,我们所接触的或许只是从超时购买牛奶。分层思想-2平时我们在网络时也未必知道数据的传输过程我们的所考虑的就是可以传就可以,不用管他时怎么传输的分层思想-2将复杂的流程分解为几个功能_5g分层结构
文章浏览阅读191次。在激光雕刻中,单向扫描(Unidirectional Scanning)是一种雕刻技术,其中激光头只在一个方向上移动,而不是来回移动。这种移动方式主要应用于通过激光逐行扫描图像表面的过程。具体而言,单向扫描的过程通常包括以下步骤:横向移动(X轴): 激光头沿X轴方向移动到图像的一侧。纵向移动(Y轴): 激光头沿Y轴方向开始逐行移动,刻蚀图像表面。这一过程是单向的,即在每一行上激光头只在一个方向上移动。返回横向移动: 一旦一行完成,激光头返回到图像的一侧,准备进行下一行的刻蚀。
文章浏览阅读577次。强连通:在有向图G中,如果两个点u和v是互相可达的,即从u出发可以到达v,从v出发也可以到达u,则成u和v是强连通的。强连通分量:如果一个有向图G不是强连通图,那么可以把它分成躲个子图,其中每个子图的内部是强连通的,而且这些子图已经扩展到最大,不能与子图外的任一点强连通,成这样的一个“极大连通”子图是G的一个强连通分量(SCC)。强连通分量的一些性质:(1)一个点必须有出度和入度,才会与其他点强连通。(2)把一个SCC从图中挖掉,不影响其他点的强连通性。_强连通分量
文章浏览阅读3.9k次,点赞5次,收藏18次。在做web开发,要给用户提供一个页面,页面包括静态页面+数据,两者结合起来就是完整的可视化的页面,django的模板系统支持这种功能,首先需要写一个静态页面,然后通过python的模板语法将数据渲染上去。1.创建一个templates目录2.配置。_django templates
文章浏览阅读1.7k次。Ubuntu等Linux系统显卡性能测试软件 Unigine 3DUbuntu Intel显卡驱动安装,请参考:ATI和NVIDIA显卡请在软件和更新中的附加驱动中安装。 这里推荐: 运行后,F9就可评分,已测试显卡有K2000 2GB 900+分,GT330m 1GB 340+ 分,GT620 1GB 340+ 分,四代i5核显340+ 分,还有写博客的小盒子100+ 分。relaybot@re...