【Pytorch with fastai】第 11 章 :使用 fastai 的中级 API 进行数据处理

news2024/11/17 21:19:51

🔎大家好,我是Sonhhxg_柒,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎

📝个人主页-Sonhhxg_柒的博客_CSDN博客 📃

🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝​

📣系列专栏 - 机器学习【ML】 自然语言处理【NLP】  深度学习【DL】

 🖍foreword

✔说明⇢本人讲解主要包括Python、机器学习(ML)、深度学习(DL)、自然语言处理(NLP)等内容。

如果你对这个系列感兴趣的话,可以关注订阅哟👋

文章目录

深入了解 fastai 的分层 API

转换

编写自己的转换

管道

TfmdLists 和数据集:转换后的集合

TfmdLists

数据集

应用中级数据 API:SiamesePair

结论


我们已经看到了对一系列的内容Tokenizer和做了什么Numericalize 文本,以及它们如何在数据块 API 中使用,它直接使用TextBlock. 但是,如果我们只想应用这些转换中的一个怎么办,要么是为了查看中间结果,要么是因为我们已经对文本进行了标记化?更一般地说,当数据块 API 不够灵活以适应我们的特定用例时,我们该怎么办?为此,我们需要使用 fastai 的中级 API来处理数据。数据块 API 建立在该层之上,因此它允许您执行数据块 API 所做的一切,甚至更多。

深入了解 fastai 的分层 API

fastai 库建立在分层 API之上。在最顶层是允许我们在五行中训练模型的应用程序代码,正如我们在第 1 章 中看到的那样。DataLoaders例如,在为文本分类器创建的情况 下,我们使用了这一行:

from fastai.text.all import *

dls = TextDataLoaders.from_folder(untar_data(URLs.IMDB), valid='test')

 工厂方法TextDataLoaders.from_folder的时候很方便 您的数据的排列方式与 IMDb 数据集的排列方式完全相同,但在实践中,情况通常并非如此。数据块 API 提供了更大的灵活性。正如我们在前一章中看到的,我们可以通过以下方式获得相同的结果:

path = untar_data(URLs.IMDB)
dls = DataBlock(
    blocks=(TextBlock.from_folder(path),CategoryBlock),
    get_y = parent_label,
    get_items=partial(get_text_files, folders=['train', 'test']),
    splitter=GrandparentSplitter(valid_name='test')
).dataloaders(path)

但它有时不够灵活。例如,出于调试目的,我们可能只需要应用此数据块附带的部分转换。或者我们可能想为 DataLoadersfastai 不直接支持的应用程序创建一个。在本节中,我们将深入研究 fastai 内部用于实现数据块 API 的部分。了解这些将使您能够利用此中间层 API 的强大功能和灵活性。

中级API

中级 API 不仅包含用于创建DataLoaders. 它还具有回调系统,它允许我们以我们喜欢的任何方式自定义训练循环,以及通用优化器。两者都将在第 16 章中介绍。

转换

我们在前一章学习标记化和数值化时, 我们从抓取一堆文本开始:

files = get_text_files(path, folders = ['train', 'test'])
txts = L(o.open().read() for o in files[:2000])

然后我们展示了如何用Tokenizer

tok = Tokenizer.from_folder(path)
tok.setup(txts)
toks = txts.map(tok)
toks[0]
(#374) ['xxbos','xxmaj','well',',','"','cube','"','(','1997',')'...]

以及如何数值化,包括为我们的语料库自动创建词汇:

num = Numericalize()
num.setup(toks)
nums = toks.map(num)
nums[0][:10]
tensor([   2,    8,   76,   10,   23, 3112,   23,   34, 3113,   33])

这些类也有一个decode方法。例如, Numericalize.decode给我们返回字符串标记:

nums_dec = num.decode(nums[0][:10]); nums_dec
(#10) ['xxbos','xxmaj','well',',','"','cube','"','(','1997',')']

Tokenizer.decode将其变回单个字符串(但是,它可能与原始字符串不完全相同;这取决于分词器是否可逆,而在我们撰写本书时,默认词分词器不是可逆的):

tok.decode(nums_dec)
'xxbos xxmaj well , "cube" ( 1997 )'

decode由 fastaishow_batch和 show_results以及其他一些推理方法使用,将预测和小批量转换为人类可理解的表示。

对于前面的每个示例toknum在前面的示例中,我们创建了一个名为setup 方法的对象(如果需要,它会训练分词器tok并为 创建词汇表num),将其应用于我们的原始文本(通过将对象作为函数调用),最后将结果解码回可理解的表示形式。大多数数据预处理任务都需要这些步骤,所以fastai提供了一个类来封装它们。这是 Transform班级。Tokenize和都是s NumericalizeTransform

一般来说,aTransform是一个行为类似于函数的对象,它有一个可选的setup方法来初始化一个内部状态(就像里面的词汇num)和一个可选的decode方法来反转函数(这种反转可能并不完美,正如我们在tok).

一个很好的例子decode是在Normalize我们的变换中在第 7 章 中看到:为了能够绘制图像,它的decode方法取消了归一化(即,它乘以标准偏差并加回平均值)。另一方面,数据增强转换没有decode方法,因为我们想显示对图像的影响以确保数据增强按我们想要的方式工作。

s 的一个特殊行为Transform是它们总是应用于元组。通常,我们的数据总是一个元组(input,target) (有时有多个输入或多个目标)。当对像这样的项目应用转换时Resize,我们不想调整整个元组的大小;相反,我们希望分别调整输入(如果适用)和目标(如果适用)的大小。对于进行数据增强的批处理变换也是如此:当输入是图像并且目标是分割掩码时,需要将变换(以相同的方式)应用于输入和目标。

如果我们将一个文本元组传递给tok

tok((txts[0], txts[1]))
((#374) ['xxbos','xxmaj','well',',','"','cube','"','(','1997',')'...],
 (#207)
 > ['xxbos','xxmaj','conrad','xxmaj','hall','went','out','with','a','bang'...])

编写自己的转换

如果您想编写一个自定义转换以应用于您的数据,最简单的方法是编写一个函数。正如你在这个例子中看到的,一个 Transform如果提供了类型,将仅应用于匹配类型(否则,它将始终被应用)。在以下代码:int中,函数签名中的 表示f仅应用于ints. 这就是tfm(2.0)returns的原因2.0,但 在这里tfm(2)返回3

def f(x:int): return x+1
tfm = Transform(f)
tfm(2),tfm(2.0)
(3, 2.0)

在这里,f被转换为一个Transform没有setup和没有decode 方法。

Python 有一种特殊的语法,用于将函数(如f)传递给另一个函数(或行为类似于函数的东西, 在 Python 中称为可调用对象),称为装饰器。装饰器的使用方法是在可调用函数前面加上一个可调用@项并将其放在函数定义之前(有很多关于 Python 装饰器的在线教程,如果这对您来说是一个新概念,请看一看)。以下代码与前面的代码相同:

@Transform
def f(x:int): return x+1
f(2),f(2.0)
(3, 2.0)

如果您需要setupdecode,则需要子类 Transform化以实现 中的实际编码行为encodes,然后(可选)实现中的设置行为setups和 中的解码行为decodes

class NormalizeMean(Transform):
    def setups(self, items): self.mean = sum(items)/len(items)
    def encodes(self, x): return x-self.mean
    def decodes(self, x): return x+self.mean

这里,NormalizeMean会在setup过程中初始化某个状态(所有元素通过的均值);那么变换就是减去那个均值。出于解码目的,我们通过添加均值来实现该转换的逆向。这是一个实际的例子NormalizeMean :

tfm = NormalizeMean()
tfm.setup([1,2,3,4,5])
start = 2
y = tfm(start)
z = tfm.decode(y)
tfm.mean,y,z
(3.0, -1.0, 2.0)

请注意,对于这些方法中的每一个,调用的方法和实现的方法是不同的:

ClassTo call To implement

nn.Module(PyTorch)

()i.e., call as function

forward

Transform

()

encodes

Transform

decode()

decodes

Transform

setup()

setups

因此,例如,您永远不会setups直接调用,而是调用setup. 原因是setup在调用setups你之前和之后做了一些工作。要了解有关 Transforms 的更多信息以及如何使用它们根据输入类型实现不同的行为,请务必查看 fastai 文档中的教程。

管道

为了将多个转换组合在一起,fastai 提供了这个Pipeline类。我们Pipeline通过将 s 的列表传递给 a 来 定义 a Transform;然后它将在其中组合转换。当你Pipeline在一个对象上调用 a 时,它会自动调用里面的转换,顺序是:

tfms = Pipeline([tok, num])
t = tfms(txts[0]); t[:20]
tensor([   2,    8,   76,   10,   23, 3112,   23,   34, 3113,   33,   10,    8,
 > 4477,   22,   88,   32,   10,   27,   42,   14])

你可以调用decode你的编码结果,取回你可以显示和分析的东西:

tfms.decode(t)[:100]
'xxbos xxmaj well , " cube " ( 1997 ) , xxmaj vincenzo \'s first movie , was one
 > of the most interesti'

唯一与其中工作方式不同的部分 Transform是设置。要在某些数据上正确设置 a Pipelineof Transform,您需要使用 a TfmdLists

TfmdLists 和数据集:转换后的集合

您的数据通常是一组原始项目(如文件名或 DataFrame 中的行),您要对其应用一系列转换。 我们刚刚看到连续的转换由 Pipelinefastai 表示。将它与您的原始项目分组的类Pipeline称为TfmdLists.

TfmdLists

这是我们在上一节中看到的进行转换的简短方法:

tls = TfmdLists(files, [Tokenizer.from_folder(path), Numericalize])

在初始化时,TfmdLists将自动按顺序调用setup each 的方法,Transform按顺序为每个提供的不是原始项目,而是由所有先前Transforms 转换的项目。我们可以Pipeline通过索引到任何原始元素来获得我们的结果TfmdLists

t = tls[0]; t[:20]
tensor([    2,     8,    91,    11,    22,  5793,    22,    37,  4910,    34,
 > 11,     8, 13042,    23,   107,    30,    11,    25,    44,    14])

并且TfmdLists知道如何解码以用于展示目的:

tls.decode(t)[:100]
'xxbos xxmaj well , " cube " ( 1997 ) , xxmaj vincenzo \'s first movie , was one
 > of the most interesti'

其实它还有一个show方法:

tls.show(t)
xxbos xxmaj well , " cube " ( 1997 ) , xxmaj vincenzo 's first movie , was one
 > of the most interesting and tricky ideas that xxmaj i 've ever seen when
 > talking about movies . xxmaj they had just one scenery , a bunch of actors
 > and a plot . xxmaj so , what made it so special were all the effective
 > direction , great dialogs and a bizarre condition that characters had to deal
 > like rats in a labyrinth . xxmaj his second movie , " cypher " ( 2002 ) , was
 > all about its story , but it was n't so good as " cube " but here are the
 > characters being tested like rats again .

 " nothing " is something very interesting and gets xxmaj vincenzo coming back
 > to his ' cube days ' , locking the characters once again in a very different
 > space with no time once more playing with the characters like playing with
 > rats in an experience room . xxmaj but instead of a thriller sci - fi ( even
 > some of the promotional teasers and trailers erroneous seemed like that ) , "
 > nothing " is a loose and light comedy that for sure can be called a modern
 > satire about our society and also about the intolerant world we 're living .
 > xxmaj once again xxmaj xxunk amaze us with a great idea into a so small kind
 > of thing . 2 actors and a blinding white scenario , that 's all you got most
 > part of time and you do n't need more than that . xxmaj while " cube " is a
 > claustrophobic experience and " cypher " confusing , " nothing " is
 > completely the opposite but at the same time also desperate .

 xxmaj this movie proves once again that a smart idea means much more than just
 > a millionaire budget . xxmaj of course that the movie fails sometimes , but
 > its prime idea means a lot and offsets any flaws . xxmaj there 's nothing
 > more to be said about this movie because everything is a brilliant surprise
 > and a totally different experience that i had in movies since " cube " .

TfmdLists以“s”命名,因为它可以处理带有splits参数的训练集和验证集。您只需要传递训练集中元素的索引和验证集中元素的索引:

cut = int(len(files)*0.8)
splits = [list(range(cut)), list(range(cut,len(files)))]
tls = TfmdLists(files, [Tokenizer.from_folder(path), Numericalize],
                splits=splits)

然后您可以通过trainvalid属性访问它们:

tls.valid[0][:20]
tensor([    2,     8,    20,    30,    87,   510,  1570,    12,   408,   379,
 > 4196,    10,     8,    20,    30,    16,    13, 12216,   202,   509])

如果您手动编写了一次Transform执行所有预处理的程序,将原始项目转换为包含输入和目标的元组,TfmdLists那么 你需要的课程。您可以使用 方法将其直接转换为DataLoaders对象dataloaders。这就是我们将在本章后面的连体示例中做的事情。

不过,一般来说,您将有两个(或更多)并行的转换管道:一个用于将原始项目处理为输入,另一个用于将原始项目处理为目标。例如,在这里,我们定义的管道仅将原始文本处理为输入。如果我们要做文本分类,我们还必须将标签处理成 目标。

为此,我们需要做两件事。首先,我们从父文件夹中获取标签名称。parent_label为此,有一个函数:

lbls = files.map(parent_label)
lbls
(#50000) ['pos','pos','pos','pos','pos','pos','pos','pos','pos','pos'...]

然后我们需要一个Transform在设置过程中获取独特项目并用它们构建词汇表的工具,然后在调用时将字符串标签转换为整数。fastai 为我们提供了这个;它被称为Categorize

cat = Categorize()
cat.setup(lbls)
cat.vocab, cat(lbls[0])
((#2) ['neg','pos'], TensorCategory(1))

要在我们的文件列表中自动完成整个设置,我们可以TfmdLists像以前一样创建一个:

tls_y = TfmdLists(files, [parent_label, Categorize()])
tls_y[0]
TensorCategory(1)

但是我们最终会得到两个单独的对象作为输入和目标,这不是我们想要的。这是Datasets救援的地方。

数据集

Datasets将对同一个原始对象并行应用两个(或更多)管道,并用结果构建一个元组。就像TfmdLists,它会 自动为我们进行设置,当我们索引到 a 时Datasets,它会返回一个包含每个管道结果的元组:

x_tfms = [Tokenizer.from_folder(path), Numericalize]
y_tfms = [parent_label, Categorize()]
dsets = Datasets(files, [x_tfms, y_tfms])
x,y = dsets[0]
x[:20],y

像 a 一样TfmdLists,我们可以传递splits给 aDatasets以在训练集和验证集之间拆分我们的数据:

x_tfms = [Tokenizer.from_folder(path), Numericalize]
y_tfms = [parent_label, Categorize()]
dsets = Datasets(files, [x_tfms, y_tfms], splits=splits)
x,y = dsets.valid[0]
x[:20],y
(tensor([    2,     8,    20,    30,    87,   510,  1570,    12,   408,   379,
 > 4196,    10,     8,    20,    30,    16,    13, 12216,   202,   509]),
 TensorCategory(0))

它还可以解码任何处理过的元组或直接显示它:

t = dsets.valid[0]
dsets.decode(t)
('xxbos xxmaj this movie had horrible lighting and terrible camera movements .
 > xxmaj this movie is a jumpy horror flick with no meaning at all . xxmaj the
 > slashes are totally fake looking . xxmaj it looks like some 17 year - old
 > idiot wrote this movie and a 10 year old kid shot it . xxmaj with the worst
 > acting you can ever find . xxmaj people are tired of knives . xxmaj at least
 > move on to guns or fire . xxmaj it has almost exact lines from " when a xxmaj
 > stranger xxmaj calls " . xxmaj with gruesome killings , only crazy people
 > would enjoy this movie . xxmaj it is obvious the writer does n\'t have kids
 > or even care for them . i mean at show some mercy . xxmaj just to sum it up ,
 > this movie is a " b " movie and it sucked . xxmaj just for your own sake , do
 > n\'t even think about wasting your time watching this crappy movie .',
 'neg')

最后一步是将我们的Datasets对象转换为 a DataLoaders,这可以通过dataloaders方法来完成。这里我们需要传递一个特殊的参数来处理填充问题(正如我们在前一章中看到的)。这需要在我们批处理元素之前发生,所以我们将它传递给before_batch

dls = dsets.dataloaders(bs=64, before_batch=pad_input)

dataloaders直接调用DataLoader我们的每个子集 Datasets。fastai DataLoader扩展了同名的 PyTorch 类,并负责将我们数据集中的项目整理成批次。它有很多定制点,但您应该了解的最重要的点如下:

after_item

在数据集中抓取每个项目后应用于每个项目。这相当于item_tfmsDataBlock.

before_batch

在整理之前应用于项目列表。这是将物品填充到相同尺寸的理想位置。

after_batch

施工后应用于整个批次。这相当于batch_tfmsDataBlock.

作为结论,这里是为文本 分类准备数据所需的完整代码:

tfms = [[Tokenizer.from_folder(path), Numericalize], [parent_label, Categorize]]
files = get_text_files(path, folders = ['train', 'test'])
splits = GrandparentSplitter(valid_name='test')(files)
dsets = Datasets(files, tfms, splits=splits)
dls = dsets.dataloaders(dl_type=SortedDL, before_batch=pad_input)

与之前代码的两个不同之处在于使用 GrandparentSplitter来拆分我们的训练和验证数据,以及 dl_type参数。这是告诉 dataloaders使用SortedDL 类DataLoader,而不是通常的类。SortedDL通过将大致相同长度的样本放入批次中来构建批次。

这与我们之前的完全相同DataBlock

path = untar_data(URLs.IMDB)
dls = DataBlock(
    blocks=(TextBlock.from_folder(path),CategoryBlock),
    get_y = parent_label,
    get_items=partial(get_text_files, folders=['train', 'test']),
    splitter=GrandparentSplitter(valid_name='test')
).dataloaders(path)

但现在您知道如何自定义它的每一部分了!

现在让我们在计算机视觉示例上练习我们刚刚学到的使用这个中级 API 进行数据预处理的知识。

应用中级数据 API:SiamesePair

Siamese 模型拍摄两张图像,必须确定它们是否属于 同一个班级。对于此示例,我们将再次使用 Pet 数据集并为模型准备数据,该模型必须预测两张宠物图像是否属于同一品种。我们将在这里解释如何为这样的模型准备数据,然后我们将在 第 15 章中训练该模型。

首先,让我们获取数据集中的图像:

from fastai.vision.all import *
path = untar_data(URLs.PETS)
files = get_image_files(path/"images")

如果我们根本不关心显示我们的对象,我们可以直接创建一个转换来完全预处理该文件列表。但是,我们想要查看这些图像,因此我们需要创建一个自定义类型。当您在 a或 对象上调用该show方法时,它将解码项目,直到它到达包含方法的类型并使用它来显示对象。该 方法传递了一个,它可以是图像的轴或文本的 DataFrame 行。TfmdListsDatasetsshowshowctxmatplotlib

在这里,我们创建了一个SiameseImage子类对象,fastuple旨在包含三样东西:两张图像和一个布尔值,表示True图像是否属于同一品种。我们还实现了特殊的show方法,这样它将两个图像连接起来,中间有一条黑线。不要太担心if测试中的部分(这是为了显示SiameseImage 图像何时是 Python 图像,而不是张量);重要的部分在最后三行:

class SiameseImage(fastuple):
    def show(self, ctx=None, **kwargs):
        img1,img2,same_breed = self
        if not isinstance(img1, Tensor):
            if img2.size != img1.size: img2 = img2.resize(img1.size)
            t1,t2 = tensor(img1),tensor(img2)
            t1,t2 = t1.permute(2,0,1),t2.permute(2,0,1)
        else: t1,t2 = img1,img2
        line = t1.new_zeros(t1.shape[0], t1.shape[1], 10)
        return show_image(torch.cat([t1,line,t2], dim=2),
                          title=same_breed, ctx=ctx)

让我们先创建一个SiameseImage并检查我们的 show方法是否有效:

img = PILImage.create(files[0])
s = SiameseImage(img, img, True)
s.show();

我们还可以尝试使用不属于同一类的第二张图片:

img1 = PILImage.create(files[1])
s1 = SiameseImage(img, img1, False)
s1.show();

我们之前看到的转换的重要之处在于它们在元组或其子类上进行分派。这正是我们选择fastuple在这个实例中进行子类化的原因——这样,我们可以将任何适用于图像的转换应用于我们的SiameseImage,并且它将应用于元组中的每个图像:

s2 = Resize(224)(s1)
s2.show();

这里Resize变换应用于两个图像中的每一个,但不是布尔标志。即使我们有自定义类型,我们也可以从库中的所有数据增强转换中受益。

我们现在准备好构建Transform我们将用来为 Siamese 模型准备好数据的 。首先,我们需要一个函数来确定我们所有图像的类别:

def label_func(fname):
    return re.match(r'^(.*)_\d+.jpg$', fname.name).groups()[0]

对于每张图像,我们的变换将以 0.5 的概率从同一类中绘制图像并返回 SiameseImage带有真实标签的图像,或者从另一个类中绘制图像并返回SiameseImage带有错误标签的图像。这都是在私有_draw函数中完成的。训练集和验证集之间有一个区别,这就是为什么需要用分割来初始化转换:在训练集上,我们会在每次读取图像时随机选择,而在验证集上,我们会这个随机选择在初始化时一劳永逸。这样,我们在训练过程中获得了更多不同的样本,但始终使用相同的验证集:

class SiameseTransform(Transform):
    def __init__(self, files, label_func, splits):
        self.labels = files.map(label_func).unique()
        self.lbl2files = {l: L(f for f in files if label_func(f) == l)
                          for l in self.labels}
        self.label_func = label_func
        self.valid = {f: self._draw(f) for f in files[splits[1]]}

    def encodes(self, f):
        f2,t = self.valid.get(f, self._draw(f))
        img1,img2 = PILImage.create(f),PILImage.create(f2)
        return SiameseImage(img1, img2, t)

    def _draw(self, f):
        same = random.random() < 0.5
        cls = self.label_func(f)
        if not same:
            cls = random.choice(L(l for l in self.labels if l != cls))
        return random.choice(self.lbl2files[cls]),same

然后我们可以创建我们的主要转换:

splits = RandomSplitter()(files)
tfm = SiameseTransform(files, label_func, splits)
tfm(files[0]).show();

在用于数据收集的中级 API 中,我们有两个对象可以帮助我们对一组项目应用转换:TfmdLists和 Datasets。如果您还记得我们刚刚看到的内容,一个应用一个 Pipeline变换,另一个并行应用几个Pipeline变换,以构建元组。在这里,我们的主要转换已经构建了元组,因此我们使用TfmdLists

tls = TfmdLists(files, tfm, splits=splits)
show_at(tls.valid, 0);

我们终于可以DataLoaders通过调用该 dataloaders方法来获取我们的数据了。这里要注意的一件事是此方法不采用item_tfmsbatch_tfms喜欢DataBlock. fastaiDataLoader有几个以事件命名的钩子;在这里,我们在项目被抓取后应用到它们上的东西被称为 after_item,我们在批次构建后应用到它上的东西被称为after_batch

dls = tls.dataloaders(after_item=[Resize(224), ToTensor],
    after_batch=[IntToFloatTensor, Normalize.from_stats(*imagenet_stats)])

请注意,我们需要传递比平时更多的转换——这是因为数据块 API 通常会自动添加它们:

  • ToTensor是将图像转换为张量的那个(同样,它应用于元组的每个部分)。

  • IntToFloatTensor将包含从 0 到 255 的整数的图像张量转换为浮点数张量,并除以 255 使值介于 0 和 1 之间。

我们现在可以使用它来训练模型DataLoaders。它需要比 提供的通常模型更多的定制,因为它必须拍摄两张图像而不是一张,但我们将在第 15 章cnn_learner中看到如何创建这样的模型并对其进行训练。

结论

fastai 提供了一个分层的 API。当数据处于常用设置之一时,只需一行代码即可获取数据,这使初学者可以轻松地专注于模型的训练,而无需花费太多时间来组装数据。然后,高级数据块 API 允许您混合和匹配构建块,从而为您提供更大的灵活性。在它之下,中级 API 为您提供了更大的灵活性,可以在您的项目上应用转换。在您的实际问题中,这可能是您需要使用的,我们希望它能使数据处理步骤尽可能简单。

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

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

相关文章

第2关:子节点创建、列出、删除

子节点创建、列出 首先&#xff0c;需要启动服务器&#xff0c;并使用zkCli.sh连接服务器&#xff0c;进入客户端命令行界面&#xff08;如第一关所述&#xff09;。 创建子节点类似于创建新的节点&#xff0c;子节点也具有四种类型的节点。唯一的区别是&#xff1a;子节点的…

redis学习4-list

基本的数据类型&#xff0c;列表,redis命令是不区分大小写的 在redis中&#xff0c;我们可以把list玩成&#xff0c;线&#xff0c;队列&#xff0c;阻塞队列&#xff01; 所有的list命令都是用l开头的 [rootcentos7964 bin]# redis-cli -p 6379 127.0.0.1:6379> LPUSH li…

Oracle Primavera Unifier计划管理器(Planning Manager)

目录 一、前言 二、介绍 一、前言 在计划管理器中&#xff0c;Oracle Primavera Unifier 用户可以计划新项目/外壳和提案&#xff0c;并为已在 Unifier 中运行的项目/外壳创建预测。他们不能像在 Unifier 中管理真实项目/外壳那样管理计划的项目/外壳;但是&#xff0c;他们可…

Observer

一些比较方便的 DOM 监测的 API。 一个 Observer 实例具备的实例方法&#xff1a; observe。向监听的目标集合添加一个元素。unobserve。停止对一个元素的观察。disconnect。终止对所有目标元素的观察。… 一、IntersectionObserver 提供了一种异步检测目标元素与祖先元素或…

图解LeetCode——792. 匹配子序列的单词数(难度:中等)

一、题目 给定字符串 s 和字符串数组 words, 返回 words[i] 中是s的子序列的单词个数 。 字符串的 子序列 是从原始字符串中生成的新字符串&#xff0c;可以从中删去一些字符(可以是none)&#xff0c;而不改变其余字符的相对顺序。 例如&#xff0c; “ace” 是 “abcde” 的…

css3对页面打印设置的一些特殊属性,如@page,target-counter等

公司内部应业务需求&#xff0c;需要将html生成pdf并能打印&#xff0c;前后台都各有方式&#xff0c;这里综合比较选择用java去生成&#xff0c;避免了前端生成带来的诸多问题&#xff0c;后台用的框架是 iTextPdf 但是在做的同时发现用iText实现的pdf生成和公司的业务需要生成…

Spring学习第6篇: 基于注解使用IOC

大家家好&#xff0c;我是一名网络怪咖&#xff0c;北漂五年。相信大家和我一样&#xff0c;都有一个大厂梦&#xff0c;作为一名资深Java选手&#xff0c;深知Spring重要性&#xff0c;现在普遍都使用SpringBoot来开发&#xff0c;面试的时候SpringBoot原理也是经常会问到&…

【数据链路层】封装成帧和透明传输和差错控制

注&#xff1a;最后有面试挑战&#xff0c;看看自己掌握了吗 文章目录前言链路层功能功能封装成帧和透明传输组帧的四种方法透明传输差错控制检错编码差错链路层的差错控制检错编码纠错编码链路层代码实现&#x1f343;博主昵称&#xff1a;一拳必胜客 &#x1f338;博主寄语&a…

27.gateway的限流实战(springcloud)

1 什么是限流 通俗的说&#xff0c;限流就是限制一段时间内&#xff0c;用户访问资源的次数&#xff0c;减轻服务器压力&#xff0c;限流大致分为两种&#xff1a; 1. IP 限流&#xff08;5s 内同一个 ip 访问超过 3 次&#xff0c;则限制不让访问&#xff0c;过一段时间才可继…

E-Prime心理学实验设计软件丨产品简介

拖放设计 通过将对象拖放到时间轴上来构建文本、图像、声音和视频的实验。利用我们的实验库中的免费模板和预建实验。 计时精度 E-Prime 3.0 将计时精度报告到毫秒精度级别。请务必使用我们的测试工具来确认您的计算机硬件能够进行关键计时。将Chronos添加到您的研究设置中&a…

Kubernetes 系统化学习之 资源清单篇(三)

Kubernetes 是一个可移植的、可扩展的开源平台&#xff0c;用于管理容器化的工作负载和服务&#xff0c;可促进声明式配置和自动化。Kubernetes 拥有一个庞大且快速增长的生态系统。 根据不同的级别&#xff0c;可以将 Kubernetes 中的资源进行多种分类。以下列举的内容都是 K…

轻松学习jQuery控制DOM

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;前端开发者…

ESP8266--Arduino开发(驱动WS2812B)

文章目录一、WS2812彩灯介绍二、安装Adafruit_NeoPixel驱动库三、Adafruit_NeoPixel库常用接口四、使用示例五、网页端控制WS2812B灯带实例一、WS2812彩灯介绍 WS2812是一个集控制电路与发光电路于一体的智能外控LED光源&#xff0c;外型与5050LED灯珠相同&#xff0c;每个灯珠…

Linux修改默认登录端口22

目录 一、编辑sshd配置 二、重启sshd 三、防火墙开放端口 四、重启防火墙 五、测试连接 六、防火墙关闭22端口 前言&#xff1a;ssh登录的默认端口是22&#xff0c;如果不修改默认端口的话&#xff0c;会不安全&#xff0c;默认端口会遭到攻击&#xff0c;为了安全要修…

JavaEE之HTTP协议 Ⅰ

文章目录前言一、协议格式总结二、认识URL三、认识"方法"(method)1.GETGET请求的特点2.POSTPOST 请求的特点总结前言 网络技术中,最核心的概念,就是"协议",HTTP就是应用层典型的协议 应用层,很多时候需要程序员自定义应用层协议,也有一些现成的协议,供我们…

代码随想录算法训练营第57天 | 647. 回文子串 516.最长回文子序列 dp总结

代码随想录系列文章目录 动态规划篇 —— 区间dp 文章目录代码随想录系列文章目录动态规划篇 —— 区间dp647. 回文子串516.最长回文子序列代码随想录中动态规划总结647. 回文子串 题目链接 回文子串还是很难的我觉得&#xff0c;所以应该多做几遍 这道题的dp数组代表就不是问…

Linux之用户操作【用户的增删改查你要的都有】【详细】

目录用户相关介绍添加用户useradd [选项] 用户名passwd 用户名细节说明删除用户userdel 用户名userdel -r 用户名查询用户id 用户名切换用户su 用户名默认输入su切换到管理员目录用户组groupadd 组名userdel 组名补充&#xff1a; useradd -g 用户组 用户名补充&#xff1a;use…

BigLEN(rat)脑内最丰富的多肽之一、LENSSPQAPARRLLPP

BigLEN(rat) TFA 是脑内最丰富的多肽之一&#xff0c;是有效的 GPR171 激动剂&#xff0c;其EC50 值为1.6 nM。 BigLEN(rat) TFA, one of the most abundant peptides in brain, is a potent GPR171 agonist, with an EC50 of 1.6 nM[1][2].编号: 200557 中文名称: BigLEN(rat)…

详解MySQL事务日志——undo log

前言 众所周知&#xff0c;事务的一大特点是原子性&#xff0c;即同一事务的SQL要同时成功或者失败。那大家有没有想过在MySQL的innoDB存储引擎中是如何保证这样的原子性操作的&#xff1f;实际上它是利用事务执行过程中生成的日志undo log来实现的&#xff0c;那么undo log究…

加速推进企业信息化建设,SRM采购系统赋能建筑工程产业生态链实现数字化转型

建筑工程行业是拉动国民经济发展的重要支柱产业之一。近年来建筑业占国民生产总值的20&#xff05;左右&#xff0c;对国民经济影响很大。随着我国建筑业企业生产和经营规模的不断扩大&#xff0c;建筑业总产值持续增长&#xff0c;传统的管理手段早已无法实现企业的精细化管理…