从零开始实现神经网络(三)_RNN循环神经网络

参考文章:rnn循环神经网络介绍

循环神经网络 (RNN) 是一种专门处理序列的神经网络。它们通常用于自然语言处理 (NLP) 任务,因为它们在处理文本方面很有效。在这篇文章中,我们将探讨什么是 RNN,了解它们是如何工作的,并在 Python 中从头开始(仅使用 numpy)构建一个真正的 RNN。

这篇文章假设你对神经网络有基本的了解

我的另一篇文章(从零开始实现神经网络(一)_NN神经网络)涵盖了你需要知道的一切,所以我建议先阅读它。

让我们开始吧!

一. 使用rnn的原因

普通神经网络(以及CNN)的一个问题是,它们只在预定大小的情况下工作:它们接受固定大小的输入并产生固定大小的输出。RNN 很有用,因为它们让我们可以将可变长度的序列作为输入和输出。以下是 RNN 的几个示例:

输入为红色,RNN 本身为绿色,输出为蓝色

这种处理序列的能力使得RNN非常有用。例如:

  • 机器翻译(例如谷歌翻译)是用“多对多”RNN完成的。原始文本序列被输入到 RNN 中,然后生成翻译后的文本作为输出。
  • 情感分析(例如,这是正面评论还是负面评论?)通常使用“多对一”RNN来完成。要分析的文本被输入到RNN中,然后生成单个输出分类(例如,这是一个正面评论)。

在本文的后面,我们将从头开始构建一个“多对一”的 RNN 来执行基本的情感分析。

二. 如何实现rnn

让我们考虑一个带有输入的“多对多”RNN ,输入是x0,x1,x2.......xn,想要产生输出y0​,y1​,y2......yn​.这些xi​和yi​是向量,可以具有任意维度。

RNN 通过迭代更新隐藏状态(h)来工作,这也是一个可以具有任意维度的向量。在任何给定步数(t)时

  1. 下一个隐藏状态 ( ht​ )使用先前的隐藏状态( ht-1)和下一个输入xt​进行计算。举例:如果要求h2的值,那么就是h1和x2的值进行计算得出。
  2. 下一个输出yt​是由ht计算得来​.

这就是 RNN 循环的原因它对每个步骤使用相同的权重。更具体地说,一个典型的 vanilla RNN 只使用 3 组权重来执行其计算:

  • W_{xh}​,权重用于所有x_{t} h_{t}之间的运算。
  • W_{hh},权重用于所有h_{t-1}​  h_{t}之间的运算。
  • W_{hy}​,权重用于所有h_{t}  y_{t}之间的运算。

我们还将对 RNN 使用两种偏差:

  • b_{h},在计算h_{t}时被加上。
  • b_{y}​,在计算y_{t}时被加上.

我们将权重表示为矩阵,将偏差表示为向量。这 3 个权重和 2 个偏差构成了整个 RNN!

以下是将所有内容组合在一起的方程式:

h_{t} = tanh(W_{xh}*x_{t} + W_{hh}*h_{t-1} + b_{h})

y_{t} = W_{hy}*h_{t} + b_{y}

不要略过这些方程式。停下来盯着它看一分钟。另外,请记住,权重是矩阵,其他变量是向量

使用矩阵乘法应用所有权重,并将偏差添加到所得产品中。然后我们使用 tanh 作为第一个方程的激活函数(但也可以使用其他激活,如 sigmoid)。

三. rnn要解决的问题

让我们亲自动手!我们将从头开始实现一个 RNN 来执行一个简单的情感分析任务:确定给定的文本字符串是正向情感的还是负向情感。

以下是我为这篇文章整理的小型数据集中的一些示例:

文本字符串正向?
i am good
i am bad
this is very good
this is not bad
i am bad not good
i am not at all happy
this was good earlier
i am not at all bad or sad right now

四. 使用方案

由于这是一个分类问题,我们将使用“多对一”RNN。这类似于我们之前讨论的“多对多”RNN,但它只使用最终的隐藏状态来产生一个输出y:

每个x_{i}将是一个向量,表示文本中的单词。输出 y 将是一个包含两个数字的向量,一个表示正数,另一个表示负数。我们将应用 Softmax 将这些值转换为概率,并最终在正/负之间做出决定。

让我们开始构建我们的 RNN!

五. 预处理

我前面提到的数据集由两个 Python 字典组成,True = 正,False = 负

#this file is data.pytrain_data = {'good': True,'bad': False,# ... more data
}test_data = {'this is happy': True,'i am good': True,# ... more data
}

我们必须进行一些预处理,以将数据转换为可用的格式。首先,我们将构建数据中存在的所有单词的词汇表

#this file is main.py
from data import train_data, test_data# Create the vocabulary.
vocab = list(set([w for text in train_data.keys() for w in text.split(' ')]))
vocab_size = len(vocab)
print('%d unique words found' % vocab_size) # 18 unique words found

vocab现在包含至少一个训练文本中出现的所有单词的列表。接下来,我们将分配一个整数索引来表示词汇中的每个单词。

#this file is main.py
# Assign indices to each word.
word_to_idx = { w: i for i, w in enumerate(vocab) }
idx_to_word = { i: w for i, w in enumerate(vocab) }
print(word_to_idx['good']) # 16 (this may change)
print(idx_to_word[0]) # sad (this may change)

现在,我们可以用其相应的整数索引来表示任何给定的单词!这是必要的,因为 RNN 无法理解单词——我们必须给它们数字。

最后,回想一下每个输入 x_{i} ​我们的RNN是一个向量。我们将使用 one-hot (独热)向量,它包含除了一个是 1 之外其他都为零的向量。每个 one-hot 向量中的“one”将位于单词的相应整数索引处。

由于我们的词汇表中有 18 个独特的单词,因此每个单词x_{i}​将是一个 18 维的独热向量。

#this file is main.pyimport numpy as npdef createInputs(text):'''Returns an array of one-hot vectors representing the wordsin the input text string.- text is a string- Each one-hot vector has shape (vocab_size, 1)'''inputs = []for w in text.split(' '):v = np.zeros((vocab_size, 1))v[word_to_idx[w]] = 1inputs.append(v)return inputs

我们稍后将使用向量输入来传递到我们的 RNN。createInputs()

六. 前向传播

是时候开始实现我们的 RNN 了!我们将首先初始化我们的 RNN 需要的 3 个权重和 2 个偏差:

#this file is rnn.pyimport numpy as np
from numpy.random import randnclass RNN:# A Vanilla Recurrent Neural Network.def __init__(self, input_size, output_size, hidden_size=64):# Weightsself.Whh = randn(hidden_size, hidden_size) / 1000self.Wxh = randn(hidden_size, input_size) / 1000self.Why = randn(output_size, hidden_size) / 1000# Biasesself.bh = np.zeros((hidden_size, 1))self.by = np.zeros((output_size, 1))

注意:我们将除以 1000 以减少权重的初始方差。这不是初始化权重的最佳方法,但它很简单,适用于这篇文章。

我们使用 np.random.randn() 从标准正态分布初始化我们的权重。

接下来,让我们实现 RNN 的前向传播。还记得我们之前看到的这两个方程式吗?

h_{t} = tanh(W_{xh}*x_{t} + W_{hh}*h_{t-1} + b_{h})

y_{t} = W_{hy}*h_{t} + b_{y}

以下是代码实现这个方程式:

#this file is rnn.py
class RNN:# ...def forward(self, inputs):'''Perform a forward pass of the RNN using the given inputs.Returns the final output and hidden state.- inputs is an array of one-hot vectors with shape (input_size, 1).'''h = np.zeros((self.Whh.shape[0], 1))# Perform each step of the RNNfor i, x in enumerate(inputs):h = np.tanh(self.Wxh @ x + self.Whh @ h + self.bh)# Compute the outputy = self.Why @ h + self.byreturn y, h

很简单,对吧?请注意,我们初始化了 到第一步的零向量,因为没有上一步 h 我们可以在当前节点上使用。

#this file is main.py
# ...def softmax(xs):# Applies the Softmax Function to the input array.return np.exp(xs) / sum(np.exp(xs))# Initialize our RNN!
rnn = RNN(vocab_size, 2)inputs = createInputs('i am very good')
out, h = rnn.forward(inputs)
probs = softmax(out)
print(probs) # [[0.50000095], [0.49999905]]

如果您需要复习 Softmax,请阅读我当前系列的cnn文章,里面有详细介绍。

我们的 RNN 有效,但还不是很有用。让我们改变一下......

七. 反向传播

为了训练我们的RNN,我们首先需要一个损失函数。我们将使用交叉熵损失,它通常与 Softmax 配对。以下是我们的计算方式:

L = -ln(p_{c})

pc是我们的 RNN 对正确类别(正或负)的预测概率。例如,如果我们的 RNN 预测一个正文本为 90%,则损失为:

L = -ln(0.90) = 0.105

想要更长的解释吗?阅读我的CNN中的交叉熵损失部分。

现在我们有一个损失,我们将使用梯度下降来训练我们的 RNN,以尽量减少损失。这意味着是时候推导出一些梯度了!

⚠️ 以下部分假定您具备多变量微积分的基本知识。如果你愿意,你可以跳过它,但我建议你略读一下,即使你不太了解。在得出结果时,我们将逐步编写代码,即使是表面的理解也会有所帮助。

7.1 定义

首先,定义一些变量:

  • 让 y 表示 RNN 的原始输出。
  • 让 p 表示最终概率:p=softmax(y)
  • 让 引用某个文本样本的真实标签,也称为“正确”类。
  • 让 是交叉熵损失:L = -ln(p_{c})
  • W_{xh}​,W_{hh},W_{hy}成为我们 RNN 中的 3 个权重矩阵。
  • b_{h}​和b_{y}成为我们 RNN 中的 2 个偏置向量。

7.2 设置

接下来,我们需要修改我们的正向传播,以缓存一些数据以在反向传播使用。与此同时,我们还将为反向传播搭建骨架。

rnn.py
class RNN:# ...def forward(self, inputs):'''Perform a forward pass of the RNN using the given inputs.Returns the final output and hidden state.- inputs is an array of one-hot vectors with shape (input_size, 1).'''h = np.zeros((self.Whh.shape[0], 1))self.last_inputs = inputsself.last_hs = { 0: h }# Perform each step of the RNNfor i, x in enumerate(inputs):h = np.tanh(self.Wxh @ x + self.Whh @ h + self.bh)self.last_hs[i + 1] = h# Compute the outputy = self.Why @ h + self.byreturn y, hdef backprop(self, d_y, learn_rate=2e-2):'''Perform a backward pass of the RNN.- d_y (dL/dy) has shape (output_size, 1).- learn_rate is a float.'''pass

是不是好奇我们为什么要做这个缓存?在前面cnn的章节中有相关解释

 

7.3 梯度(求偏导)

现在是数学时间!我们将从计算\frac{\partial L}{\partial y}开始.

我们知道:

p_{c} = softmax(y_{c})

L = -ln(p_{c} ) = -ln(softmax(y_{c}))

我将留下\frac{\partial L}{\partial y}​使用链式法则:

这里假设当前要计算的索引值的i,那么当i是正确的类c时,有:

\frac{\partial L}{\partial y_{i}}\left\{\begin{matrix} p_{i} & if (i\neq c) \\ p_{i}-1 & if(i=c) \end{matrix}\right.

例如,如果我们有p=[0. 2,0. 2,0.6],正确的类是c=0,那么我们会得到\frac{\partial L}{\partial y}=[−0.8,0.2,0.6]。这也很容易转化为代码:

# Loop over each training example
for x, y in train_data.items():inputs = createInputs(x)target = int(y)# Forwardout, _ = rnn.forward(inputs)probs = softmax(out)# Build dL/dyd_L_d_y = probsd_L_d_y[target] -= 1# Backwardrnn.backprop(d_L_d_y)

 好。接下来,让我们来看看偏导W_{HY}b_{y},仅用于将最终的隐藏状态转换为 RNN 的输出。我们有


h_{n}是最终的隐藏状态。因此

同样地

我们现在可以开始实现反向传播了!backprop()

class RNN:# ...def backprop(self, d_y, learn_rate=2e-2):'''Perform a backward pass of the RNN.- d_y (dL/dy) has shape (output_size, 1).- learn_rate is a float.'''n = len(self.last_inputs)# Calculate dL/dWhy and dL/dby.d_Why = d_y @ self.last_hs[n].T    d_by = d_y

 

提醒:我们之前创建过。self.last_hs   forward()

最后,我们需要偏导W_{hh}W_{xh}b_h,在 RNN 期间的每一步都会使用。我们有:

因为改变W_{xh}影响每一个 h_t,这些都会影响y,并最终影响L。为了充分计算W_{xh},我们需要通过所有时间步进反向传播,这称为时间反向传播 (BPTT):

W_{xh}用于所有xt​  ht​前向链接,因此我们必须向反传播回每个链接。

一旦我们到达给定的步骤t,我们需要计算\frac{\partial h_{t}}{\partial W_{xh}}:

h_t = tanh(W_{xh}*x_t +W_{hh}*h_{t-1}+b_h )

我们所知道的tanh求导:

\frac{dtanh(x)}{dx}=1-tanh^{2}(x)

我们像之前那样使用链式法则:

\frac{\partial h_t}{\partial W_{xh}} = (1-h^{2}_t)x_t

同理:

\frac{\partial h_t}{\partial W_{hh}} = (1-h^{2}_t)h_{t-1}

\frac{\partial h_t}{\partial b_{h}} = (1-h^{2}_t)

最后一步,我们需要\frac{\partial y}{\partial h_t},我们可以递归计算

我们将从最后一个隐藏状态开始实现 BPTT 并反向传播,因此我们已经有了\frac{\partial y}{\partial h_{T+1}}到我们要计算\frac{\partial y}{\partial h_t}的时候!最后一个隐藏状态是例外,h_n

\frac{\partial y}{\partial h_n} = W_{HY}

我们现在拥有最终实现 BPTT 并完成了所需的一切:backprop()

class RNN:# ...def backprop(self, d_y, learn_rate=2e-2):'''Perform a backward pass of the RNN.- d_y (dL/dy) has shape (output_size, 1).- learn_rate is a float.'''n = len(self.last_inputs)# Calculate dL/dWhy and dL/dby.d_Why = d_y @ self.last_hs[n].Td_by = d_y# Initialize dL/dWhh, dL/dWxh, and dL/dbh to zero.d_Whh = np.zeros(self.Whh.shape)d_Wxh = np.zeros(self.Wxh.shape)d_bh = np.zeros(self.bh.shape)# Calculate dL/dh for the last h.d_h = self.Why.T @ d_y# Backpropagate through time.for t in reversed(range(n)):# An intermediate value: dL/dh * (1 - h^2)temp = ((1 - self.last_hs[t + 1] ** 2) * d_h)# dL/db = dL/dh * (1 - h^2)d_bh += temp# dL/dWhh = dL/dh * (1 - h^2) * h_{t-1}d_Whh += temp @ self.last_hs[t].T# dL/dWxh = dL/dh * (1 - h^2) * xd_Wxh += temp @ self.last_inputs[t].T# Next dL/dh = dL/dh * (1 - h^2) * Whhd_h = self.Whh @ temp# Clip to prevent exploding gradients.for d in [d_Wxh, d_Whh, d_Why, d_bh, d_by]:np.clip(d, -1, 1, out=d)# Update weights and biases using gradient descent.self.Whh -= learn_rate * d_Whhself.Wxh -= learn_rate * d_Wxhself.Why -= learn_rate * d_Whyself.bh -= learn_rate * d_bhself.by -= learn_rate * d_by

 需要注意的几点:

  • 我们已经合并了\frac{\partial L}{\partial y} * \frac{\partial y}{\partial h}\frac{\partial L}{\partial h}
  • 我们会不断更新一个包含最新变量的变量d_h,\frac{\partial L}{\partial h_{t+1}}​,我们需要计算\frac{\partial L}{\partial h_t}​.
  • 完成 BPTT 后,我们 np.clip() 限制低于 -1 或高于 1 的梯度值。这有助于缓解梯度爆炸问题,即梯度由于具有大量相乘项而变得非常大。对于普通 RNN 来说,梯度的爆炸或消失是相当成问题的——更复杂的 RNN(如 LSTM)通常更有能力处理它们。
  • 计算完所有梯度后,我们使用梯度下降更新权重和偏差。

我们做到了!我们的 RNN 已经完成了。

八. 进行预测

终于到了我们期待的时刻——让我们测试一下我们的 RNN!

首先,我们将编写一个辅助函数来使用 RNN 处理数据:

import randomdef processData(data, backprop=True):'''Returns the RNN's loss and accuracy for the given data.- data is a dictionary mapping text to True or False.- backprop determines if the backward phase should be run.'''items = list(data.items())random.shuffle(items)loss = 0num_correct = 0for x, y in items:inputs = createInputs(x)target = int(y)# Forwardout, _ = rnn.forward(inputs)probs = softmax(out)# Calculate loss / accuracyloss -= np.log(probs[target])num_correct += int(np.argmax(probs) == target)if backprop:# Build dL/dyd_L_d_y = probsd_L_d_y[target] -= 1# Backwardrnn.backprop(d_L_d_y)return loss / len(data), num_correct / len(data)

 现在,我们可以编写训练循环:

# Training loop
for epoch in range(1000):train_loss, train_acc = processData(train_data)if epoch % 100 == 99:print('--- Epoch %d' % (epoch + 1))print('Train:\tLoss %.3f | Accuracy: %.3f' % (train_loss, train_acc))test_loss, test_acc = processData(test_data, backprop=False)print('Test:\tLoss %.3f | Accuracy: %.3f' % (test_loss, test_acc))

 运行应该输出如下内容:main.py

--- Epoch 100
Train:  Loss 0.688 | Accuracy: 0.517
Test:   Loss 0.700 | Accuracy: 0.500
--- Epoch 200
Train:  Loss 0.680 | Accuracy: 0.552
Test:   Loss 0.717 | Accuracy: 0.450
--- Epoch 300
Train:  Loss 0.593 | Accuracy: 0.655
Test:   Loss 0.657 | Accuracy: 0.650
--- Epoch 400
Train:  Loss 0.401 | Accuracy: 0.810
Test:   Loss 0.689 | Accuracy: 0.650
--- Epoch 500
Train:  Loss 0.312 | Accuracy: 0.862
Test:   Loss 0.693 | Accuracy: 0.550
--- Epoch 600
Train:  Loss 0.148 | Accuracy: 0.914
Test:   Loss 0.404 | Accuracy: 0.800
--- Epoch 700
Train:  Loss 0.008 | Accuracy: 1.000
Test:   Loss 0.016 | Accuracy: 1.000
--- Epoch 800
Train:  Loss 0.004 | Accuracy: 1.000
Test:   Loss 0.007 | Accuracy: 1.000
--- Epoch 900
Train:  Loss 0.002 | Accuracy: 1.000
Test:   Loss 0.004 | Accuracy: 1.000
--- Epoch 1000
Train:  Loss 0.002 | Accuracy: 1.000
Test:   Loss 0.003 | Accuracy: 1.000

 

九. 结束

就是这样!在这篇文章中,我们完成了循环神经网络的演练,包括它们是什么、它们是如何工作的、为什么它们有用、如何训练它们以及如何实现它们。

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

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

相关文章

图文教程:从0开始安装stable-diffusion

现在AI绘画还是挺火,Midjourney虽然不错,但是对于我来说还是挺贵的。今天我就来安一下开源的AI绘画stable-diffusion,它的缺点就是对电脑的要求比较高,尤其是显卡。 话不多说开搞。 访问sd的github,https://github.com/AUTOMATIC…

〖大前端 - 基础入门三大核心之JS篇(51)〗- 面向对象之认识上下文与上下文规则

说明:该文属于 大前端全栈架构白宝书专栏,目前阶段免费,如需要项目实战或者是体系化资源,文末名片加V!作者:哈哥撩编程,十余年工作经验, 从事过全栈研发、产品经理等工作,目前在公司…

qt-C++笔记之addAction和addMenu的区别以及QAction的使用场景

qt-C笔记之addAction和addMenu的区别以及QAction的使用场景 code review! 文章目录 qt-C笔记之addAction和addMenu的区别以及QAction的使用场景1.QMenu和QMenuBar的关系与区别2.addMenu和addAction的使用场景区别3.将QAction的信号连接到槽函数4.QAction的使用场景5.将例1修改…

基于单片机智能浇花控制系统设计

**单片机设计介绍,基于单片机智能浇花控制系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的智能浇花控制系统可以通过水泵、传感器和单片机等硬件组件实现自动浇水,减轻人工浇花的工作…

基于OpenCV+CNN+IOT+微信小程序智能果实采摘指导系统——深度学习算法应用(含python、JS工程源码)+数据集+模型(三)

目录 前言总体设计系统整体结构图系统流程图 运行环境Python环境TensorFlow 环境Jupyter Notebook环境Pycharm 环境微信开发者工具OneNET云平台 模块实现1. 数据预处理1)爬取功能2)下载功能 2. 创建模型并编译1)定义模型结构2)优化…

《PySpark大数据分析实战》-03.了解Hive

📋 博主简介 💖 作者简介:大家好,我是wux_labs。😜 热衷于各种主流技术,热爱数据科学、机器学习、云计算、人工智能。 通过了TiDB数据库专员(PCTA)、TiDB数据库专家(PCTP…

恢复Django 项目

随笔记录 目录 1. 重建Mysql DB 2. 启动Django 项目 2.1 确保你的系统上已安装pip工具。你可以使用以下命令来检查pip是否已安装 2.2 安装Packages 2.2.1 安装Django 2.2.2 安装pymysql 2.2.3 安装 kafka 2.2.4 安装 requests 2.2.5 安装simplepro 2.2.6 安装libjp…

持续集成交付CICD:GitLabCI上传Nexus制品

目录 一、实验 1.GitLabCI上传Nexus制品 2.优化GitLabCI(引用系统变量) 3.添加if条件判断项目类型 4.优化GitLabCI(模板类) 二、问题 1.GitLabCI获取jar文件失败 2. GitLabCI获取流水线项目命名空间失败 3.GItLab Packag…

.NET 8 编写 LiteDB vs SQLite 数据库 CRUD 接口性能测试(准备篇)

WebAppDbTest 项目准备 项目准备1、.net cli 创建项目2、nuget 包引用和项目结构2.1、项目添加相关 nuget 包2.2、WebAppDbTest 项目结构 3、项目代码说明3.1、CSharp/C# 类文件说明3.2、json 配置文件说明 4、项目运行预览 数据库 .db 文件准备1、创建 SQLite 数据库1.1、在 W…

Python---综合案例

一、系统需求分析 1、需求分析 使用面向对象编程思想完成学员管理系统的开发,具体如下: ① 系统要求:学员数据存储在文件中 ② 系统功能:添加学员、删除学员、修改学员信息、查询学员信息、显示所有学员信息、保存学员信息及退…

56.windows docker 安装ES、Go操作ES

文章目录 一、环境准备1、 docker安装ES2、启动容器3、图像化客户端工具ElasticSearch-Head 二、Go ES连接与认证1、 连接ES2、配置ES认证 三、索引的增删改查四、文档的增删改创建单个文档根据文档id删除文档批量删除文档批量创建文档 五、文档查询列表查询精确匹配模糊匹配嵌…

ubuntu18.04配置cuda+cudnn+tensorrt+anconda+pytorch-gpu+pycharm

一、显卡驱动安装 执行nvidia-smi查看安装情况 二、cuda安装 cuda官网下载cuda_11.6.2_510.47.03_linux.run,安装执行 sudo sh cuda_11.6.2_510.47.03_linux.run提升安装项,驱动不用安装,即第一项(Driver)&#xff…

如何使用Java在Excel中添加动态数组公式?

本文由葡萄城技术团队发布。转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。 前言 动态数组公式是 Excel 引入的一项重要功能,它将 Excel 分为两种风格:Excel 365 和传统 …

虹科技术 | IO-Link Wireless如何赋能工厂车间迈向无线自动化?

大规模定制、卓越运营和商业智能正在从根本上改变制造业,为了在竞争中立于不败之地,制造商需要更加灵活、通用、可扩展和具有成本效益的机器和生产线。随着制造商向工业 4.0 迈进,更好的适应性、更高的吞吐量和更短的停机时间是他们的共同要求…

打包CSS

接上一个打包HTML继续进行CSS的打包 1.在之前的文件夹里的src文件夹创建一个css文件 2.在浏览器打开webpack——>中文文档——>指南——>管理资源——>加载CSS 3.复制第一句代码到终端 4.复制下图代码到webpack.config.js脚本的plugins:[.....]内容下…

【docker】Hello World

搜索hello-world镜像 docker search hello-world拉去镜像 docker pull hello-world查看本地镜像 docker images 运行镜像 docker run hello-world查看所有的容器 docker ps -a查询start状态容器 docker ps 输出介绍 CONTAINER ID: 容器 ID。IMAGE: 使用的镜像。COMMAN…

分布式搜索引擎02

分布式搜索引擎02 在昨天的学习中,我们已经导入了大量数据到elasticsearch中,实现了elasticsearch的数据存储功能。但elasticsearch最擅长的还是搜索和数据分析。 所以今天,我们研究下elasticsearch的数据搜索功能。我们会分别使用DSL和Res…

ElasticSearch应用场景以及技术选型[ES系列] - 第496篇

历史文章(文章累计490) 《国内最全的Spring Boot系列之一》 《国内最全的Spring Boot系列之二》 《国内最全的Spring Boot系列之三》 《国内最全的Spring Boot系列之四》 《国内最全的Spring Boot系列之五》 《国内最全的Spring Boot系列之六》 M…

PDF控件Spire.PDF for .NET【转换】演示:将 PDF 转换为 Excel

PDF是一种通用的文件格式,但它很难编辑。如果您想修改和计算PDF数据,将PDF转换为Excel将是一个理想的解决方案。在本文中,您将了解如何使用Spire.PDF for .NET在 C# 和 VB.NET 中将 PDF 转换为 Excel。 Spire.Doc 是一款专门对 Word 文档进行…