动手学深度学习——6.循环神经网络

1.序列模型

处理序列数据需要统计工具和新的深度神经网络架构。 为了简单起见,我们以 图8.1.1所示的股票价格(富时100指数)为例。

../_images/ftse100.png

图8.1.1 近30年的富时100指数

其中,用𝑥𝑡表示价格,即在时间步(time step) 𝑡∈𝑍+时,观察到的价格𝑥𝑡。 请注意,𝑡对于本文中的序列通常是离散的,并在整数或其子集上变化。 假设一个交易员想在𝑡日的股市中表现良好,于是通过以下途径预测𝑥𝑡:

为了实现这个预测,交易员可以使用回归模型, 例如在 3.3节中训练的模型。 仅有一个主要问题:输入数据的数量, 输入𝑥𝑡−1,…,𝑥1本身因𝑡而异。 也就是说,输入数据的数量这个数字将会随着我们遇到的数据量的增加而增加, 因此需要一个近似方法来使这个计算变得容易处理。 本章后面的大部分内容将围绕着如何有效估计 𝑃(𝑥𝑡∣𝑥𝑡−1,…,𝑥1)展开。 简单地说,它归结为以下两种策略。

第一种策略,假设在现实情况下相当长的序列 𝑥𝑡−1,…,𝑥1可能是不必要的, 因此我们只需要满足某个长度为𝜏的时间跨度, 即使用观测序列𝑥𝑡−1,…,𝑥𝑡−𝜏。 当下获得的最直接的好处就是参数的数量总是不变的, 至少在𝑡>𝜏时如此,这就使我们能够训练一个上面提及的深度网络。 这种模型被称为自回归模型(autoregressive models), 因为它们是对自己执行回归。

第二种策略,如 图8.1.2所示, 是保留一些对过去观测的总结ℎ𝑡, 并且同时更新预测𝑥^𝑡和总结ℎ𝑡。 这就产生了基于𝑥^𝑡=𝑃(𝑥𝑡∣ℎ𝑡)估计𝑥𝑡, 以及公式ℎ𝑡=𝑔(ℎ𝑡−1,𝑥𝑡−1)更新的模型。 由于ℎ𝑡从未被观测到,这类模型也被称为 隐变量自回归模型(latent autoregressive models)。

小结

  • 内插法(在现有观测值之间进行估计)和外推法(对超出已知观测范围进行预测,换句话说,我们必须使用我们自己的预测(而不是原始数据)来进行多步预测)在实践的难度上差别很大。因此,对于所拥有的序列数据,在训练时始终要尊重其时间顺序,即最好不要基于未来的数据进行训练。

  • 序列模型的估计需要专门的统计工具,两种较流行的选择是自回归模型和隐变量自回归模型。

  • 对于时间是向前推进的因果模型,正向估计通常比反向估计更容易。

  • 对于直到时间步𝑡的观测序列,其在时间步𝑡+𝑘的预测输出是“𝑘步预测”。随着我们对预测时间𝑘值的增加,会造成误差的快速累积和预测质量的极速下降。

2.文本预处理

对于序列数据处理问题,我们在 8.1节中 评估了所需的统计工具和预测时面临的挑战。 这样的数据存在许多种形式,文本是最常见例子之一。 例如,一篇文章可以被简单地看作一串单词序列,甚至是一串字符序列。 本节中,我们将解析文本的常见预处理步骤。 这些步骤通常包括:

  1. 将文本作为字符串加载到内存中。

  2. 将字符串拆分为词元(如单词和字符)。

  3. 建立一个词表,将拆分的词元映射到数字索引。

  4. 将文本转换为数字索引序列,方便模型操作。

3.语言模型

我们了解了如何将文本数据映射为词元, 以及将这些词元可以视为一系列离散的观测,例如单词或字符。 假设长度为𝑇的文本序列中的词元依次为𝑥1,𝑥2,…,𝑥𝑇。 于是,𝑥𝑡(1≤𝑡≤𝑇) 可以被认为是文本序列在时间步𝑡处的观测或标签。 在给定这样的文本序列时,语言模型(language model)的目标是估计序列的联合概率

例如,只需要一次抽取一个词元𝑥𝑡∼𝑃(𝑥𝑡∣𝑥𝑡−1,…,𝑥1), 一个理想的语言模型就能够基于模型本身生成自然文本。 与猴子使用打字机完全不同的是,从这样的模型中提取的文本 都将作为自然语言(例如,英语文本)来传递。 只需要基于前面的对话片断中的文本, 就足以生成一个有意义的对话。 显然,我们离设计出这样的系统还很遥远, 因为它需要“理解”文本,而不仅仅是生成语法合理的内容。

尽管如此,语言模型依然是非常有用的。 例如,短语“to recognize speech”和“to wreck a nice beach”读音上听起来非常相似。 这种相似性会导致语音识别中的歧义,但是这很容易通过语言模型来解决, 因为第二句的语义很奇怪。 同样,在文档摘要生成算法中, “狗咬人”比“人咬狗”出现的频率要高得多, 或者“我想吃奶奶”是一个相当匪夷所思的语句, 而“我想吃,奶奶”则要正常得多。

4.循环神经网络

假设我们在时间步𝑡有小批量输入𝑋𝑡∈𝑅𝑛×𝑑。 换言之,对于𝑛个序列样本的小批量, 𝑋𝑡的每一行对应于来自该序列的时间步𝑡处的一个样本。 接下来,用𝐻𝑡∈𝑅𝑛×ℎ 表示时间步𝑡的隐藏变量。 与多层感知机不同的是, 我们在这里保存了前一个时间步的隐藏变量𝐻𝑡−1, 并引入了一个新的权重参数𝑊ℎℎ∈𝑅ℎ×ℎ, 来描述如何在当前时间步中使用前一个时间步的隐藏变量。 具体地说,当前时间步隐藏变量由当前时间步的输入 与前一个时间步的隐藏变量一起计算得出:

与隐藏层相比, 循环神经网络多添加了一项 𝐻𝑡−1𝑊ℎℎ, 从而实例化了 。 从相邻时间步的隐藏变量𝐻𝑡和 𝐻𝑡−1之间的关系可知, 这些变量捕获并保留了序列直到其当前时间步的历史信息, 就如当前时间步下神经网络的状态或记忆, 因此这样的隐藏变量被称为隐状态(hidden state)。 由于在当前时间步中, 隐状态使用的定义与前一个时间步中使用的定义相同, 因此 (8.4.5)的计算是循环的(recurrent)。 于是基于循环计算的隐状态神经网络被命名为 循环神经网络(recurrent neural network)。 在循环神经网络中执行 (8.4.5)计算的层 称为循环层(recurrent layer)。

有许多不同的方法可以构建循环神经网络, 由 (8.4.5)定义的隐状态的循环神经网络是非常常见的一种。 对于时间步𝑡,输出层的输出类似于多层感知机中的计算:

循环神经网络的参数包括隐藏层的权重 𝑊𝑥ℎ∈𝑅𝑑×ℎ,𝑊ℎℎ∈𝑅ℎ×ℎ和偏置𝑏ℎ∈𝑅1×ℎ, 以及输出层的权重𝑊ℎ𝑞∈𝑅ℎ×𝑞 和偏置𝑏𝑞∈𝑅1×𝑞。 值得一提的是,即使在不同的时间步,循环神经网络也总是使用这些模型参数。 因此,循环神经网络的参数开销不会随着时间步的增加而增加。

上图展示了循环神经网络在三个相邻时间步的计算逻辑。 在任意时间步𝑡,隐状态的计算可以被视为:

  1. 拼接当前时间步𝑡的输入𝑋𝑡和前一时间步𝑡−1的隐状态𝐻𝑡−1;

  2. 将拼接的结果送入带有激活函数𝜙的全连接层。 全连接层的输出是当前时间步𝑡的隐状态𝐻𝑡。

在本例中,模型参数是𝑊𝑥ℎ和𝑊ℎℎ的拼接, 以及𝑏ℎ的偏置,所有这些参数都来自 (8.4.5)。 当前时间步𝑡的隐状态𝐻𝑡 将参与计算下一时间步𝑡+1的隐状态𝐻𝑡+1。 而且𝐻𝑡还将送入全连接输出层, 用于计算当前时间步𝑡的输出𝑂𝑡。

我们刚才提到,隐状态中 𝑋𝑡𝑊𝑥ℎ+𝐻𝑡−1𝑊ℎℎ的计算, 相当于𝑋𝑡和𝐻𝑡−1的拼接 与𝑊𝑥ℎ和𝑊ℎℎ的拼接的矩阵乘法。 虽然这个性质可以通过数学证明, 但在下面我们使用一个简单的代码来说明一下。 首先,我们定义矩阵XW_xhHW_hh, 它们的形状分别为,(3,1)、,(1,4)、,(3,4)和,(4,4)。 分别将X乘以W_xh,将H乘以W_hh, 然后将这两个乘法相加,我们得到一个形状为,(3,4)的矩阵。

举例

回想一下 8.3节中的语言模型, 我们的目标是根据过去的和当前的词元预测下一个词元, 因此我们将原始序列移位一个词元作为标签。 Bengio等人首先提出使用神经网络进行语言建模 (Bengio et al., 2003)。 接下来,我们看一下如何使用循环神经网络来构建语言模型。 设小批量大小为1,批量中的文本序列为“machine”。 为了简化后续部分的训练,我们考虑使用 字符级语言模型(character-level language model), 将文本词元化为字符而不是单词。 图8.4.2演示了 如何通过基于字符级语言建模的循环神经网络, 使用当前的和先前的字符预测下一个字符。

在训练过程中,我们对每个时间步的输出层的输出进行softmax操作, 然后利用交叉熵损失计算模型输出和标签之间的误差。 由于隐藏层中隐状态的循环计算, 图8.4.2中的第3个时间步的输出𝑂3 由文本序列“m”“a”和“c”确定。 由于训练数据中这个文本序列的下一个字符是“h”, 因此第3个时间步的损失将取决于下一个字符的概率分布, 而下一个字符是基于特征序列“m”“a”“c”和这个时间步的标签“h”生成的。

5.评价指标

最后,让我们讨论如何度量语言模型的质量, 这将在后续部分中用于评估基于循环神经网络的模型。 一个好的语言模型能够用高度准确的词元来预测我们接下来会看到什么。 

我们可以通过计算序列的似然概率来度量模型的质量。我们在引入softmax回归 ( 3.4.7节)时定义了熵、惊异和交叉熵, 并在信息论的在线附录 中讨论了更多的信息论知识。 如果想要压缩文本,我们可以根据当前词元集预测的下一个词元。 一个更好的语言模型应该能让我们更准确地预测下一个词元。 因此,它应该允许我们在压缩序列时花费更少的比特。 所以我们可以通过一个序列中所有的𝑛个词元的交叉熵损失的平均值来衡量

其中𝑃由语言模型给出, 𝑥𝑡是在时间步𝑡从该序列中观察到的实际词元。 这使得不同长度的文档的性能具有了可比性。 由于历史原因,自然语言处理的科学家更喜欢使用一个叫做困惑度(perplexity)的量。

困惑度的最好的理解是“下一个词元的实际选择数的调和平均数”。 

6.实现循环神经网络

回想一下,在train_iter中,每个词元都表示为一个数字索引, 将这些索引直接输入神经网络可能会使学习变得困难。 我们通常将每个词元表示为更具表现力的特征向量。 最简单的表示称为独热编码(one-hot encoding), 它在 3.4.1节中介绍过。

简言之,将每个索引映射为相互不同的单位向量: 假设词表中不同词元的数目为𝑁(即len(vocab)), 词元索引的范围为0到𝑁−1。 如果词元的索引是整数𝑖, 那么我们将创建一个长度为𝑁的全0向量, 并将第𝑖处的元素设置为1。 此向量是原始词元的一个独热向量。

我们每次采样的小批量数据形状是二维张量: (批量大小,时间步数)。 one_hot函数将这样一个小批量数据转换成三维张量, 张量的最后一个维度等于词表大小(len(vocab))。 我们经常转换输入的维度,以便获得形状为 (时间步数,批量大小,词表大小)的输出。 这将使我们能够更方便地通过最外层的维度, 一步一步地更新小批量数据的隐状态。

为了定义循环神经网络模型, 我们首先需要一个init_rnn_state函数在初始化时返回隐状态。 这个函数的返回是一个张量,张量全用0填充, 形状为(批量大小,隐藏单元数)。 在后面的章节中我们将会遇到隐状态包含多个变量的情况, 而使用元组可以更容易地处理些。

def rnn(inputs, state, params):# inputs的形状:(时间步数量,批量大小,词表大小)W_xh, W_hh, b_h, W_hq, b_q = paramsH, = stateoutputs = []# X的形状:(批量大小,词表大小)for X in inputs:H = torch.tanh(torch.mm(X, W_xh) + torch.mm(H, W_hh) + b_h)Y = torch.mm(H, W_hq) + b_qoutputs.append(Y)return torch.cat(outputs, dim=0), (H,)

定义了所有需要的函数之后,接下来我们创建一个类来包装这些函数, 并存储从零开始实现的循环神经网络模型的参数。

class RNNModelScratch: #@save"""从零开始实现的循环神经网络模型"""def __init__(self, vocab_size, num_hiddens, device,get_params, init_state, forward_fn):self.vocab_size, self.num_hiddens = vocab_size, num_hiddensself.params = get_params(vocab_size, num_hiddens, device)self.init_state, self.forward_fn = init_state, forward_fndef __call__(self, X, state):X = F.one_hot(X.T, self.vocab_size).type(torch.float32)return self.forward_fn(X, state, self.params)def begin_state(self, batch_size, device):return self.init_state(batch_size, self.num_hiddens, device)

让我们检查输出是否具有正确的形状。 例如,隐状态的维数是否保持不变。

num_hiddens = 512
net = RNNModelScratch(len(vocab), num_hiddens, d2l.try_gpu(), get_params,init_rnn_state, rnn)
state = net.begin_state(X.shape[0], d2l.try_gpu())
Y, new_state = net(X.to(d2l.try_gpu()), state)
Y.shape, len(new_state), new_state[0].shape
(torch.Size([10, 28]), 1, torch.Size([2, 512]))

我们可以看到输出形状是(时间步数×批量大小,词表大小), 而隐状态形状保持不变,即(批量大小,隐藏单元数)。

7.训练模型

在训练模型之前,让我们定义一个函数在一个迭代周期内训练模型。 它与我们训练 3.6节模型的方式有三个不同之处。

  1. 序列数据的不同采样方法(随机采样和顺序分区)将导致隐状态初始化的差异。

  2. 我们在更新模型参数之前裁剪梯度。 这样的操作的目的是,即使训练过程中某个点上发生了梯度爆炸,也能保证模型不会发散。

  3. 我们用困惑度来评价模型。如 8.4.4节所述, 这样的度量确保了不同长度的序列具有可比性。

具体来说,当使用顺序分区时, 我们只在每个迭代周期的开始位置初始化隐状态。 由于下一个小批量数据中的第𝑖个子序列样本 与当前第𝑖个子序列样本相邻, 因此当前小批量数据最后一个样本的隐状态, 将用于初始化下一个小批量数据第一个样本的隐状态。 这样,存储在隐状态中的序列的历史信息 可以在一个迭代周期内流经相邻的子序列。 然而,在任何一点隐状态的计算, 都依赖于同一迭代周期中前面所有的小批量数据, 这使得梯度计算变得复杂。 为了降低计算量,在处理任何一个小批量数据之前, 我们先分离梯度,使得隐状态的梯度计算总是限制在一个小批量数据的时间步内。

当使用随机抽样时,因为每个样本都是在一个随机位置抽样的, 因此需要为每个迭代周期重新初始化隐状态。 与 3.6节中的 train_epoch_ch3函数相同, updater是更新模型参数的常用函数。 它既可以是从头开始实现的d2l.sgd函数, 也可以是深度学习框架中内置的优化函数。

#@save
def train_epoch_ch8(net, train_iter, loss, updater, device, use_random_iter):"""训练网络一个迭代周期(定义见第8章)"""state, timer = None, d2l.Timer()metric = d2l.Accumulator(2)  # 训练损失之和,词元数量for X, Y in train_iter:if state is None or use_random_iter:# 在第一次迭代或使用随机抽样时初始化statestate = net.begin_state(batch_size=X.shape[0], device=device)else:if isinstance(net, nn.Module) and not isinstance(state, tuple):# state对于nn.GRU是个张量state.detach_()else:# state对于nn.LSTM或对于我们从零开始实现的模型是个张量for s in state:s.detach_()y = Y.T.reshape(-1)X, y = X.to(device), y.to(device)y_hat, state = net(X, state)l = loss(y_hat, y.long()).mean()if isinstance(updater, torch.optim.Optimizer):updater.zero_grad()l.backward()grad_clipping(net, 1)updater.step()else:l.backward()grad_clipping(net, 1)# 因为已经调用了mean函数updater(batch_size=1)metric.add(l * y.numel(), y.numel())return math.exp(metric[0] / metric[1]), metric[1] / timer.stop()

循环神经网络模型的训练函数既支持从零开始实现, 也可以使用高级API来实现。

#@save
def train_ch8(net, train_iter, vocab, lr, num_epochs, device,use_random_iter=False):"""训练模型(定义见第8章)"""loss = nn.CrossEntropyLoss()animator = d2l.Animator(xlabel='epoch', ylabel='perplexity',legend=['train'], xlim=[10, num_epochs])# 初始化if isinstance(net, nn.Module):updater = torch.optim.SGD(net.parameters(), lr)else:updater = lambda batch_size: d2l.sgd(net.params, lr, batch_size)predict = lambda prefix: predict_ch8(prefix, 50, net, vocab, device)# 训练和预测for epoch in range(num_epochs):ppl, speed = train_epoch_ch8(net, train_iter, loss, updater, device, use_random_iter)if (epoch + 1) % 10 == 0:print(predict('time traveller'))animator.add(epoch + 1, [ppl])print(f'困惑度 {ppl:.1f}, {speed:.1f} 词元/秒 {str(device)}')print(predict('time traveller'))print(predict('traveller'))

现在,我们训练循环神经网络模型。 因为我们在数据集中只使用了10000个词元, 所以模型需要更多的迭代周期来更好地收敛。

num_epochs, lr = 500, 1
train_ch8(net, train_iter, vocab, lr, num_epochs, d2l.try_gpu())
困惑度 1.0, 67212.6 词元/秒 cuda:0
time traveller for so it will be convenient to speak of himwas e
travelleryou can show black is white by argument said filby

小结

  • 我们可以训练一个基于循环神经网络的字符级语言模型,根据用户提供的文本的前缀生成后续文本。

  • 一个简单的循环神经网络语言模型包括输入编码、循环神经网络模型和输出生成。

  • 循环神经网络模型在训练以前需要初始化状态,不过随机抽样和顺序划分使用初始化方法不同。

  • 当使用顺序划分时,我们需要分离梯度以减少计算量。

  • 在进行任何预测之前,模型通过预热期进行自我更新(例如,获得比初始值更好的隐状态)。

  • 梯度裁剪可以防止梯度爆炸,但不能应对梯度消失。

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

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

相关文章

LIS检验信息软件源码,适合二级医院的应用

LIS系统主要面向医院检验科,包含检验医生日常处理、报告处理、质量控制、条码管理、仪器双工通讯、无人值守等诸多功能模块,能与HIS系统、体检系统和电子病历信息系统实现无缝连接,已成功应用于多家各种规模的医院,满足客户各方面…

Git之repo sync -c与repo sync -dc用法区别(四十八)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒…

InternLM学习笔记

入门岛 1. Linux基础知识 2. Python 基础知识 from collections import Countertext """ Got this panda plush toy for my daughters birthday, who loves it and takes it everywhere. Its soft and super cute, and its face has a friendly look. Its a …

论文阅读【检测】:Facebook ECCV2020 | DETR

文章目录 论文地址AbstractMotivation模型框架详细结构小结 论文地址 DETR Abstract 提出了一种将目标检测视为直接集预测问题的新方法。简化了检测pipeline,有效地消除了许多手工设计的组件的需求,例如非最大抑制过程或锚生成,这些组件明…

设计模式|观察者模式

观察者模式是一种行为设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象发生变化时,它的所有观察者都会收到通知并更新。观察者模式常用于实现事件处理系统、发布-订阅模式等。在项目中&#xff0c…

磁盘管理与磁盘卷--红帽Linux操作系统<>

分区的两种格式 1、MBR分区 MBR(Master Boot Record,主引导记录)是传统的分区机制,使用BI0S引导PC设备,寻址空间只有32bit长。 分区空间最大支持2.2TB 支持的分区数量:4个主分区或者3个主分区1个扩展分区 为什么MBR最多只能有4个主分区?…

云服务部署项目(Spring + Vue)

云计算:腾讯云 操作系统:Ubuntu 22.04.4 LTS 项目:若依前后端分离项目(SpringBoot Vue) 首先要安装基本的一些依赖环境,大家可以看一下我往期的文章: Ubuntu在线JDK Ubuntu在线安装Nginx Ubunt…

文件解析的终极工具:Apache Tika

文件解析的终极工具:Apache Tika Apache Tika 简介 Apache Tika 是一个开源的、跨平台的库,用于检测、提取和解析各种类型文件的元数据。 它支持多种文件格式,包括文档、图片、音频和视频。 Tika是一个底层库,经常用于搜索引擎…

Android 列表或网格形式展示大量数据:RecyclerView

目录 RecyclerView是什么如何使用RecyclerView 涉及到的类LayoutManager为Item设置不同的布局样式制作拖动的RecyclerView 一、RecyclerView是什么 RecyclerView是Android支持库中的一个控件,用于在列表或网格形式展示大量数据。它是ListView的升级版&#xff0c…

《梦醒蝶飞:释放Excel函数与公式的力量》18.1 图表类型与设计

第18章:创建图表和数据可视化 18.1 图表类型与设计 Excel提供了多种图表类型,帮助用户以直观的方式展示数据。选择合适的图表类型和设计可以显著提高数据的可读性和理解度。以下将介绍常见的图表类型及其应用,并通过具体案例进行说明。 18.…

如何利用Jenkins自动化管理、部署数百个应用

目录 1. Jenkins 安装与部署步骤 1.1 系统要求 1.2 安装步骤 1.2.1 Windows 系统 1.2.2 CentOS 系统 1.3 初次配置 2. Gradle 详细配置方式 2.1 安装 Gradle 2.1.1 Windows 系统 2.1.2 CentOS 系统 2.2 配置 Jenkins 中的 Gradle 3. JDK 详细配置方式 3.1 安装 JD…

Java:防止输入输出超时

一、防止输入超时 当我们直接使用Scanner进行输入操作的时候,每次读取输入的数据都会进行一次硬盘的IO操作,这个操作是很慢的,如果要读取的数据过多,那么我们在刷题网站上就很有可能因为多次的数据读取操作产生超时!那…

渠道查问卷调查个人怎么做?

大家好,我是橙河老师,今天讲一讲渠道查问卷调查个人怎么做? 对海外问卷项目有过一些了解的人呢,都应该知道一些渠道查的优势,首先是省去了注册账号、养号一系列的繁琐操作,那通过测题、做题,然…

vscode调试nextjs前端后端程序、nextjs api接口

最近有一个项目使用了nextjs框架,并且使用nextjs同时实现了前后端,由于之前前后端都是分离的,前端的调试可以通过在代码种添加debugger或者直接在浏览器中打断点实现,现在想调试后端接口,前面的方式就不适用了。故研究…

CMA软件实验室评审如何做好人员技术能力的评价?

人员作为实验室的一个重要质量因素,其技术能力和素质水平体现了实验室水平的高低。人员能力是随着时间动态变化的,有效地评价实验室人员的技术能力,是保证实验室活动的必要条件。CMA软件实验室评审也要求实验室要注意对人员能力的监督&#x…

基于web的物流配送管理系统/基于客户时间窗变化的物流配送管理系统/快递配送管理系统

摘 要 随着信息技术和网络技术的飞速发展,人类已进入全新信息化时代,传统管理技术已无法高效,便捷地管理信息。为了迎合时代需求,优化管理效率,各种各样的管理系统应运而生,各行各业相继进入信息管理时代&a…

性能测试工具、负载测试工具、缺陷跟踪工具推荐

负载测试工具 - 有助于对站点或应用程序进行性能/负载测试 1)WebLOAD WebLOAD 是一款出色的测试工具,提供了许多强大的脚本功能,有助于测试复杂场景。该工具支持从 Selenium 到移动端、从企业应用到网络协议的数百种技术。使用这款工具可以…

JAVA.包、final、权限

包 final 权限 代码块 1.构造代码块 创建这个本类的对象的时候会先指向构造代码块再执行构造方法 作用:把构造方法重复的部分抽取出来 2.静态代码块 static 随着类的加载而加载,只执行一次。 作用:数据初始化,比如在学生管…

CSS学习笔记[Web开发]

CSS学习 本文为学习笔记,参考菜鸟和w3c 文章目录 CSS 简介CSS 插入外部 CSS内部 CSS行内 CSS多个样式表层叠顺序 CSS 语法例子解释 CSS 选择器CSS 元素选择器CSS id 选择器实例CSS 类选择器实例CSS 通用选择器实例CSS 分组选择器CSS 后代选择器CSS 子元素选择器CSS …

OnlyOffice社区版部署及前端嵌入使用实现office的docx、xlsx等在线协同编辑预览

一、OnlyOffice介绍 ONLYOFFICE 是一款功能丰富的在线办公软件。它由 Ascensio System SIA 公司开发,有社区版、企业版和开发版等版本。本教程介绍开源社区版的安装使用,实现查看、编辑并协作处理文档、工作表、幻灯片,多人实时协同编辑&…