写在前面

RNN的神奇用的人都说话,不过在使用RNN的时候,还是需要知道一些技巧,有利于我们实现更符合预期的模型,本文将会介绍以下三种技巧。

  • 循环dropout(recurrent dropout)。这是一种特殊的内置方法,在循环层中使用dropout 来降低过拟合。
  • 堆叠循环层(stacking recurrent layers)。这会提高网络的表示能力(代价是更高的计算负 荷)。
  • 双向循环层(bidirectional recurrent layer)。将相同的信息以不同的方式呈现给循环网络, 可以提高精度并缓解遗忘问题。

温度预测问题

我们将使用一个天气时间序列数据集,它由德国耶拿的马克思 • 普朗克生物地球化学研究所的气 象站记录。在这个数据集中,每 10 分钟记录 14 个不同的量(比如气温、气压、湿度、风向等),其中包含多年的记录。原始数据可追溯到2003 年,但本例仅使用2009—2016 年的数据。这个数据 集非常适合用来学习处理数值型时间序列。我们将会用这个数据集来构建模型,输入最近的一 些数据(几天的数据点),可以预测 24 小时之后的气温。下载并解压数据,如下所示。

cd ~/Downloads
mkdir jena_climate
cd jena_climate
wget https://s3.amazonaws.com/keras-datasets/jena_climate_2009_2016.csv.zip  unzip jena_climate_2009_2016.csv.zip

来观察一下数据。

import osdata_dir = 'sample_data'
fname = os.path.join(data_dir, 'jena_climate_2009_2016.csv')f = open(fname)
data = f.read()
f.close()lines = data.split('\n')
header = lines[0].split(',')
lines = lines[1:]print(header)
print(len(lines))

从输出可以看出,共有420 551 行数据(每行是一个时间步,记录了一个日期和14 个与天 气有关的值),还输出了下列表头。

['"Date Time"', '"p (mbar)"', '"T (degC)"', '"Tpot (K)"', '"Tdew (degC)"', '"rh (%)"', '"VPmax (mbar)"', '"VPact (mbar)"', '"VPdef (mbar)"', '"sh (g/kg)"', '"H2OC (mmol/mol)"', '"rho (g/m**3)"', '"wv (m/s)"', '"max. wv (m/s)"', '"wd (deg)"']
420551

接下来,将 420 551 行数据转换成一个 Numpy 数组。

float_data = np.zeros((len(lines), len(header) - 1))
for i, line in enumerate(lines):values = [float(x) for x in line.split(',')[1:]]float_data[i, :] = values

比如,温度随时间的变化。在这张图中,你可以清楚地看 到温度每年的周期性变化。

temp = float_data[:, 1]
plt.plot(range(len(temp)), temp)
plt.show()

准备数据

这个问题的确切表述如下:一个时间步是10 分钟,每 steps 个时间步采样一次数据,给 定过去 lookback 个时间步之内的数据,能否预测 delay 个时间步之后的温度?用到的参数值 如下。

  • lookback = 720:给定过去 5 天内的观测数据。
  • steps = 6:观测数据的采样频率是每小时一个数据点。
  • delay = 144:目标是未来 24 小时之后的数据。 开始之前,你需要完成以下两件事。
    • 将数据预处理为神经网络可以处理的格式。这很简单。数据已经是数值型的,所以不需 要做向量化。但数据中的每个时间序列位于不同的范围(比如温度通道位于-20 到 +30 之间,但气压大约在1000 毫巴上下)。你需要对每个时间序列分别做标准化,让它们在 相似的范围内都取较小的值。
    • 编写一个 Python 生成器,以当前的浮点数数组作为输入,并从最近的数据中生成数据批 量,同时生成未来的目标温度。因为数据集中的样本是高度冗余的(对于第 N 个样本和 第N+1 个样本,大部分时间步都是相同的),所以显式地保存每个样本是一种浪费。相反, 我们将使用原始数据即时生成样本。

预处理数据的方法是,将每个时间序列减去其平均值,然后除以其标准差。我们将使用前 200 000 个时间步作为训练数据,所以只对这部分数据计算平均值和标准差。

mean = float_data[:200000].mean(axis=0)
float_data -= mean
std = float_data{:200000}.std(axis=0)
float_data /= std

给出了将要用到的生成器。它生成了一个元组 (samples, targets),其 中 samples 是输入数据的一个批量,targets 是对应的目标温度数组。生成器的参数如下。

  • data:浮点数数据组成的原始数组,并将其标准化。
  • lookback:输入数据应该包括过去多少个时间步。
  • delay:目标应该在未来多少个时间步之后。
  • min_index 和 max_index:data 数组中的索引,用于界定需要抽取哪些时间步。这有 助于保存一部分数据用于验证、另一部分用于测试。
  • shuffle:是打乱样本,还是按顺序抽取样本。
  • batch_size:每个批量的样本数。
  • step:数据采样的周期(单位:时间步)。我们将其设为 6,为的是每小时抽取一个数据点。
def generator(data, lookback, delay, min_index, max_index,shuffle=False, batch_size=128, step=6):if max_index is None:max_index = len(data) - delay -1i = min_index + lookbackwhile 1:if shuffle:rows = np.random.randint(min_index + lookback, max_index, size=batch_size)else:if i + batch_size >= max_index:i = min_index + lookbackrows = np.arange(i, min(i + batch_size, max_index))i += len(rows)samples = np.zeros((len(rows),lookback // step,data.shape[-1]))targets = np.zeros((len(rows),))for j, row in enumerate(rows):indices = range(rows[j] - lookback, rows[j], step)samples[j] = data[indices]targets[j] = data[rows[j] + delay][1]yield samples, targets

下面,我们使用这个抽象的 generator 函数来实例化三个生成器:一个用于训练,一个用于验证,还有一个用于测试。每个生成器分别读取原始数据的不同时间段:训练生成器读取前 200 000 个时间步,验证生成器读取随后的 100 000 个时间步,测试生成器读取剩下的时间步。

lookback = 1440
step = 6
delay = 144
batch_size = 128train_gen = generator(float_data,                       lookback=lookback,                     delay=delay,                       min_index=0,                       max_index=200000,                       shuffle=True,                       step=step,batch_size=batch_size)
val_gen = generator(float_data,                     lookback=lookback,                     delay=delay,                     min_index=200001,                     max_index=300000,                     step=step,batch_size=batch_size) test_gen = generator(float_data,                      lookback=lookback,                      delay=delay,                      min_index=300001,                      max_index=None,                      step=step,                      batch_size=batch_size) val_steps = (300000 - 200001 - lookback)  //batch_size  test_steps = (len(float_data) - 300001 - lookback)  //batch_size

一种基于常识的、非机器学习的基准方法

开始使用黑盒深度学习模型解决温度预测问题之前,我们先尝试一种基于常识的简单方法。 它可以作为合理性检查,还可以建立一个基准,更高级的机器学习模型需要打败这个基准才能 表现出其有效性。面对一个尚没有已知解决方案的新问题时,这种基于常识的基准方法很有用。 一个经典的例子就是不平衡的分类任务,其中某些类别比其他类别更常见。如果数据集中包含 90% 的类别A 实例和10% 的类别B 实例,那么分类任务的一种基于常识的方法就是对新样本 始终预测类别“A”。这种分类器的总体精度为90%,因此任何基于学习的方法在精度高于90% 时才能证明其有效性。有时候,这样基本的基准方法可能很难打败。

本例中,我们可以放心地假设,温度时间序列是连续的(明天的温度很可能接近今天的温 度),并且具有每天的周期性变化。因此,一种基于常识的方法就是始终预测24 小时后的温度 等于现在的温度。我们使用平均绝对误差(MAE)指标来评估这种方法。

np.mean(np.abs(preds - targets))

下面是评估的循环代码。

def evaluate_naive_method():batch_maes = []for step in range(val_steps):samples, targets = next(val_gen)preds = samples[:, -1, 1]mae = np.mean(np.abs(preds - targets))batch_maes.append(mae)print(np.mean(batch_maes))
evaluate_naive_method()

得到的MAE 为 0.29。因为温度数据被标准化成均值为0、标准差为1,所以无法直接对这 个值进行解释。它转化成温度的平均绝对误差为 0.29×temperature_std 摄氏度,即 2.57℃。

celsius_mae = 0.29 * std[1]

这个平均绝对误差还是相当大的。接下来的任务是利用深度学习知识来改进结果。

一种基本的机器学习方法

在尝试机器学习方法之前,建立一个基于常识的基准方法是很有用的;同样,在开始研究 复杂且计算代价很高的模型(比如RNN)之前,尝试使用简单且计算代价低的机器学习模型也 是很有用的,比如小型的密集连接网络。这可以保证进一步增加问题的复杂度是合理的,并且 会带来真正的好处。 下面代码中给出了一个密集连接模型,首先将数据展平,然后通过两个 Dense 层并运行。 注意,最后一个 Dense 层没有使用激活函数,这对于回归问题是很常见的。我们使用MAE 作 为损失。评估数据和评估指标都与常识方法完全相同,所以可以直接比较两种方法的结果。

from keras.models import Sequential  from keras import layers from keras.optimizers import RMSprop model = Sequential()
model.add(layers.Flatten(input_shape=(lookback // step, float_data.shape[-1])))
model.add(layers.Dense(32, activation='relu'))
model.add(layers.Dense(1))
model.compile(optimizer=RMSprop(), loss='mae')  history =
model.fit_generator(train_gen,                               steps_per_epoch=500,                                epochs=20,                                validation_data=val_gen,                                validation_steps=val_steps)

我们来显示验证和训练的损失曲线

import matplotlib.pyplot as plt loss = history.history['loss']
val_loss = history.history['val_loss'] epochs = range(1, len(loss) + 1) plt.figure() plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend() plt.show()


部分验证损失接近不包含学习的基准方法,但这个结果并不可靠。这也展示了首先建立这 个基准方法的优点,事实证明,超越这个基准并不容易。我们的常识中包含了大量有价值的信息, 而机器学习模型并不知道这些信息。 你可能会问,如果从数据到目标之间存在一个简单且表现良好的模型(即基于常识的基准 方法),那为什么我们训练的模型没有找到这个模型并进一步改进呢?原因在于,这个简单的解决方案并不是训练过程所要寻找的目标。我们在模型空间(即假设空间)中搜索解决方案,这 个模型空间是具有我们所定义的架构的所有两层网络组成的空间。这些网络已经相当复杂了。 如果你在一个复杂模型的空间中寻找解决方案,那么可能无法学到简单且性能良好的基准方法, 虽然技术上来说它属于假设空间的一部分。通常来说,这对机器学习是一个非常重要的限制: 如果学习算法没有被硬编码要求去寻找特定类型的简单模型,那么有时候参数学习是无法找到 简单问题的简单解决方案的。

第一个循环网络基准

第一个全连接方法的效果并不好,但这并不意味着机器学习不适用于这个问题。前一个方 法首先将时间序列展平,这从输入数据中删除了时间的概念。我们来看一下数据本来的样子: 它是一个序列,其中因果关系和顺序都很重要。我们将尝试一种循环序列处理模型,它应该特 别适合这种序列数据,因为它利用了数据点的时间顺序,这与第一个方法不同。 我们将使用GRU 层 ,而不是 LSTM 层。门控循环单元(GRU,gated recurrent unit)层的工作原理与LSTM 相同。但它做了一些简化,因此运行的计算代价更低(虽然表示能力可能不如 LSTM)。机器学习中到处可以见到这种计算代价与 表示能力之间的折中。

from tensorflow.keras.models import Sequential
from tensorflow.keras import layers
from tensorflow.keras.optimizers import RMSpropmodel = Sequential()
model.add(layers.GRU(32, input_shape=(None, float_data.shape[-1])))
model.add(layers.Dense(1))model.compile(optimizer=RMSprop(), loss='mae')
history = model.fit_generator(train_gen,steps_per_epoch=500,epochs=20,validation_data=val_gen,validation_steps=val_steps)

显示了模型结果。效果好多了!远优于基于常识的基准方法。这证明了机器学习的 价值,也证明了循环网络与序列展平的密集网络相比在这种任务上的优势。

新的验证MAE 约为0.265(在开始显著过拟合之前),反标准化转换成温度的平均绝对误 差为 2.35℃。与最初的误差 2.57℃相比,这个结果确实有所提高,但可能仍有改进的空间。

使用循环 dropout 来降低过拟合

从训练和验证曲线中可以明显看出,模型出现过拟合:几轮过后,训练损失和验证损失就 开始显著偏离。我们已经学过降低过拟合的一种经典技术——dropout,即将某一层的输入单 元随机设为0,其目的是打破该层训练数据中的偶然相关性。但在循环网络中如何正确地使用 dropout,这并不是一个简单的问题。

人们早就知道,在循环层前面应用 dropout,这种正则化会 妨碍学习过程,而不是有所帮助。2015 年,在关于贝叶斯深度学习的博士论文中,Yarin Gal 确 定了在循环网络中使用 dropout 的正确方法:对每个时间步应该使用相同的 dropout 掩码(dropout mask,相同模式的舍弃单元),而不是让dropout 掩码随着时间步的增加而随机变化。此外,为 了对 GRU、LSTM 等循环层得到的表示做正则化,应该将不随时间变化的dropout 掩码应用于层 的内部循环激活(叫作循环 dropout 掩码)。对每个时间步使用相同的 dropout 掩码,可以让网络 沿着时间正确地传播其学习误差,而随时间随机变化的dropout 掩码则会破坏这个误差信号,并 且不利于学习过程。 Yarin Gal 使用Keras 开展这项研究,并帮助将这种机制直接内置到Keras 循环层中。Keras 的每个循环层都有两个与dropout 相关的参数:一个是 dropout,它是一个浮点数,指定该层 输入单元的dropout 比率;另一个是 recurrent_dropout,指定循环单元的dropout 比率。我 们向 GRU 层中添加dropout 和循环dropout,看一下这么做对过拟合的影响。因为使用dropout 正则化的网络总是需要更长的时间才能完全收敛,所以网络训练轮次增加为原来的 2 倍。

from tensorflow.keras.models import Sequential
from tensorflow.keras import layers
from tensorflow.keras.optimizers import RMSpropmodel = Sequential()
model.add(layers.GRU(32, dropout=0.2, recurrent_dropout=0.2,input_shape=(None, float_data.shape[-1])))
model.add(layers.Dense(1))model.compile(optimizer=RMSprop(), loss='mae')
history = model.fit_generator(train_gen,steps_per_epoch=500,epochs=40,validation_data=val_gen,validation_steps=val_steps)

循环层堆叠

模型不再过拟合,但似乎遇到了性能瓶颈,所以我们应该考虑增加网络容量。回想一下机 器学习的通用工作流程:增加网络容量通常是一个好主意,直到过拟合变成主要的障碍(假设 你已经采取基本步骤来降低过拟合,比如使用dropout)。只要过拟合不是太严重,那么很可能 是容量不足的问题。 增加网络容量的通常做法是增加每层单元数或增加层数。循环层堆叠(recurrent layer stacking)是构建更加强大的循环网络的经典方法,例如,目前谷歌翻译算法就是7 个大型LSTM 层的堆叠——这个架构很大。 在 Keras 中逐个堆叠循环层,所有中间层都应该返回完整的输出序列(一个3D 张量),而 不是只返回最后一个时间步的输出。这可以通过指定 return_sequences=True 来实现。

from tensorflow.keras.models import Sequential
from tensorflow.keras import layers
from tensorflow.keras.optimizers import RMSpropmodel = Sequential()
model.add(layers.GRU(32, dropout=0.2, recurrent_dropout=0.5,return_sequences=True,input_shape=(None, float_data.shape[-1])))
model.add(layers.GRU(64, activation='relu',                      dropout=0.1,                      recurrent_dropout=0.5))
model.add(layers.Dense(1))model.compile(optimizer=RMSprop(), loss='mae')
history = model.fit_generator(train_gen,steps_per_epoch=500,epochs=40,validation_data=val_gen,validation_steps=val_steps)

可以看到,添加一层的确对结果有所改进,但并不显著。我们可以得 出两个结论。

  • 因为过拟合仍然不是很严重,所以可以放心地增大每层的大小,以进一步改进验证损失。 但这么做的计算成本很高。
  • 添加一层后模型并没有显著改进,所以你可能发现,提高网络能力的回报在逐渐减小。

使用双向 RNN

这里介绍的一种方法叫作双向RNN(bidirectional RNN)。双向RNN 是一种常见的 RNN 变体,它在某些任务上的性能比普通RNN 更好。它常用于自然语言处理,可谓深度学习 对自然语言处理的瑞士军刀。 RNN 特别依赖于顺序或时间,RNN 按顺序处理输入序列的时间步,而打乱时间步或反转 时间步会完全改变RNN 从序列中提取的表示。正是由于这个原因,如果顺序对问题很重要(比 如温度预测问题) ,RNN 的表现会很好。双向RNN 利用了RNN 的顺序敏感性:它包含两个普 通 RNN,比如你已经学过的 GRU 层和 LSTM 层,每个RN 分别沿一个方向对输入序列进行处理 (时间正序和时间逆序),然后将它们的表示合并在一起。通过沿这两个方向处理序列,双向 RNN 能够捕捉到可能被单向 RNN 忽略的模式。 值得注意的是,本节的RNN 层都是按时间正序处理序列(更早的时间步在前),这可能是 一个随意的决定。至少,至今我们还没有尝试质疑这个决定。如果RNN 按时间逆序处理输入序 列(更晚的时间步在前),能否表现得足够好呢?我们在实践中尝试一下这种方法,看一下会发 生什么。你只需要编写一个数据生成器的变体,将输入序列沿着时间维度反转(即将最后一行 代码替换为 yield samples[:, ::-1, :], targets)。

逆序 GRU 的效果甚至比基于常识的基准方法还要差很多,这说明在本例中,按时间正序处 理对成功解决问题很重要。这非常合理:GRU 层通常更善于记住最近的数据,而不是久远的数据, 与更早的数据点相比,更靠后的天气数据点对问题自然具有更高的预测能力(这也是基于常识 的基准方法非常强大的原因)。因此,按时间正序的模型必然会优于时间逆序的模型。重要的是, 对许多其他问题(包括自然语言)而言,情况并不是这样:直觉上来看,一个单词对理解句子 的重要性通常并不取决于它在句子中的位置。

from keras.datasets import imdb
from keras.preprocessing import sequence
from keras import layers
from keras.models import Sequential max_features = 10000   maxlen = 500   (x_train, y_train), (x_test, y_test) = imdb.load_data(     num_words=max_features)   x_train = [x[::-1] for x in x_train]
x_test = [x[::-1] for x in x_test] x_train = sequence.pad_sequences(x_train, maxlen=maxlen)
x_test = sequence.pad_sequences(x_test, maxlen=maxlen) model = Sequential()
model.add(layers.Embedding(max_features, 128))
model.add(layers.LSTM(32))
model.add(layers.Dense(1, activation='sigmoid')) model.compile(optimizer='rmsprop',               loss='binary_crossentropy',               metrics=['acc']) history = model.fit(x_train, y_train,                     epochs=10,                     batch_size=128,                     validation_split=0.2)

模型性能与正序 LSTM 几乎相同。值得注意的是,在这样一个文本数据集上,逆序处理的 效果与正序处理一样好,这证实了一个假设:虽然单词顺序对理解语言很重要,但使用哪种顺 序并不重要。重要的是,在逆序序列上训练的 RNN 学到的表示不同于在原始序列上学到的表示, 正如在现实世界中,如果时间倒流(你的人生是第一天死去、最后一天出生),那么你的心智模 型也会完全不同。在机器学习中,如果一种数据表示不同但有用,那么总是值得加以利用,这 种表示与其他表示的差异越大越好,它们提供了查看数据的全新角度,抓住了数据中被其他方 法忽略的内容,因此可以提高模型在某个任务上的性能。这是集成(ensembling)方法背后的直 觉,。 双向RNN 正是利用这个想法来提高正序RNN 的性能。它从两个方向查看数据, 从而得到更加丰富的表示,并捕捉到仅使用正序 RNN 时可能忽略的一些模式。

在 Keras 中将一个双向RNN 实例化,我们需要使用 Bidirectional 层,它的第一个参数 是一个循环层实例。Bidirectional 对这个循环层创建了第二个单独实例,然后使用一个实例 按正序处理输入序列,另一个实例按逆序处理输入序列。我们在IMDB 情感分析任务上来试一 下这种方法。

model = Sequential()
model.add(layers.Embedding(max_features, 32))
model.add(layers.Bidirectional(layers.LSTM(32)))
model.add(layers.Dense(1, activation='sigmoid')) model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
history = model.fit(x_train, y_train,                     epochs=10,                      batch_size=128,                      validation_split=0.2)

这个模型的表现比上一节的普通 LSTM 略好,验证精度超过89%。这个模型似乎也很快就 开始过拟合,这并不令人惊讶,因为双向层的参数个数是正序 LSTM 的 2 倍。添加一些正则化, 双向方法在这个任务上可能会有很好的表现。 接下来,我们尝试将相同的方法应用于温度预测任务。

from keras.models import Sequential
from keras import layers
from keras.optimizers import RMSprop model = Sequential()
model.add(layers.Bidirectional(     layers.GRU(32), input_shape=(None, float_data.shape[-1])))
model.add(layers.Dense(1)) model.compile(optimizer=RMSprop(), loss='mae')
history = model.fit_generator(train_gen,                               steps_per_epoch=500,                               epochs=40,                               validation_data=val_gen,                               validation_steps=val_steps)

这个模型的表现与普通 GRU 层差不多一样好。其原因很容易理解:所有的预测能力肯定都 来自于正序的那一半网络,因为我们已经知道,逆序的那一半在这个任务上的表现非常糟糕(本例同样是因为,最近的数据比久远的数据更加重要)。

更多尝试

为了提高温度预测问题的性能,你还可以尝试下面这些方法。

  • 在堆叠循环层中调节每层的单元个数。当前取值在很大程度上是任意选择的,因此可能 不是最优的。
  • 调节 RMSprop 优化器的学习率。
  • 尝试使用 LSTM 层代替 GRU 层。
  • 在循环层上面尝试使用更大的密集连接回归器,即更大的 Dense 层或 Dense 层的堆叠。
  • 不要忘记最后在测试集上运行性能最佳的模型(即验证MAE 最小的模型)。否则,你开 发的网络架构将会对验证集过拟合。

正如前面所说,深度学习是一门艺术而不是科学。我们可以提供指导,对于给定问题哪些 方法可能有用、哪些方法可能没用,但归根结底,每个问题都是独一无二的,你必须根据经验 对不同的策略进行评估。目前没有任何理论能够提前准确地告诉你,应该怎么做才能最优地解 决问题。你必须不断迭代。

RNN的一些高级用法-以温度预测问题为例相关推荐

  1. 《python深度学习》学习笔记与代码实现(第六章,6.3 循环神经网络的高级用法)

    6.3循环神经网络的高级用法 在这一节中,我们将回顾三种先进技术来提高递归神经网络的性能和泛化能力.在本节结束时,您将了解关于使用Keras的递归网络所知道的大部分内容.我们将展示一个天气预报问题的所 ...

  2. Python深度学习之循环神经网络的高级用法

    Deep Learning with Python 这篇文章是我学习<Deep Learning with Python>(第二版,François Chollet 著) 时写的系列笔记之 ...

  3. 《Python 深度学习》6.3 循环神经网络的高级用法 (代码)

    # 6.3 循环神经网络的高级用法 本节将介绍提高循环神经网络的性能和泛化能力的三种高级技巧.学完本节,你将会掌握 用 Keras 实现循环网络的大部分内容.我们将在温度预测问题中介绍这三个概念.在这 ...

  4. python深度学习--jena温度预测

    import numpy as np import pandas as pd import matplotlib.pyplot as plt import pylab from pandas impo ...

  5. python基础和第三方库 笔记(python基础完结包括高级用法,第三方库持续更新中...)

    python基础 注:本笔记面向有一定基础的人 本笔记是本人快速复习python过程中记录的,不适合零基础的人学习python的主工具,可以作为辅工具,本笔记记录了入门阶段常用操作,如有错误的地方,希 ...

  6. Excel表格中替换的高级用法

    Excel表格中替换的高级用法 目录 Excel表格中替换的高级用法 1.例如:将F列中合格替换为不合格 2. 在[开始]选项卡中,找到[替换]点击,在查找内容中输入[合格]替换为中输入[不合格],勾 ...

  7. Excel小技巧!格式刷的5个高级用法,你不一定知道哦

    Excel小技巧!格式刷的5个高级用法,你不一定知道哦 目录 Excel小技巧!格式刷的5个高级用法,你不一定知道哦 1.使用格式刷,填充单元格颜色 2.使用格式刷,复制列宽或行宽. 3.使用格式刷, ...

  8. vim的高级用法配置以及在系统中如何获取帮助

    vim的高级用法配置以及在系统中如何获取帮助 1 vim的三种模式 1.1 使用方法 1.2 vim模式 2 vim工作的基本配置 2.1 临时设定(set设定) 2.2 永久设定方式 3 搜索 4 ...

  9. Cacti Weathermap 高级用法 (二)

    成都长宽Weathermap实际运用的效果图示例: 这是一个CNC出口的质量监控图. 途中cnc节点(红色)是一个展示图例,TEL节点根据存活状态显示为绿色 TEL节点上面P:47.2ms 是一个,这 ...

最新文章

  1. React Native 0.59.0 发布,使用 React 编写原生应用
  2. python快速编程入门课后简答题答案-Python编程:从入门到实践(课后习题8)
  3. python excelwriter保存路径_python管理文件神器 os.walk
  4. linux中 tar 报参数列表过长,四种解决”Argument list too long”参数列表过长的办法...
  5. java 中的 io 系统总结
  6. Seurat | 单细胞分析工具
  7. 如何实现共享软件网络授权认证,包括注册新用户、登录、修改密码等操作
  8. ionic4 返回键退出app
  9. php select where,PHP where语句
  10. servlet 返回可访问文件_Windows 10 可直接访问 Linux 文件
  11. SpringCloud+Seata+nacos案例(包含源码 Seata及nacos安装教程)
  12. redis 介绍与安装
  13. 不加群提取群成员_QQ群排名优化技术教程
  14. 扩展kalman滤波matlab程序,扩展卡尔曼滤波算法的matlab程序
  15. 高颜值游戏专属蓝牙耳机推荐,2020五款商城高人气蓝牙耳机
  16. SQL语法创建及管理数据表结构
  17. ResponseBodyAdvice的使用
  18. android 发送短信sms
  19. sql文件导入mysql数据库出错_如何解决navicat导入sql文件出错的问题
  20. ThreadLocal如何保证获取到想要线程变量

热门文章

  1. GAMES101笔记_Lec10~12_几何 Geometry
  2. The Cluster ID xxx doesn't match stored clusterId Some(xxx) in meta.properties. The broker is trying
  3. uncertain temporal knowledge graph论文解读
  4. ChatGPT原理解读
  5. 微信红包封面开放平台使用指南
  6. 必读!!只需10分钟,NAS变身赚钱神器!
  7. 理解NS2中的OTcl/tclCL
  8. Web课程设计——“念念手账”网页APP制作
  9. VUE前后分离调起微信支付
  10. 一文读懂华为的组织绩效和个人绩效管理