当前位置: 首页 > news >正文

RNN - 语言模型

语言模型

  • 给定文本序列 x 1 , … , x T x_1, \ldots, x_T x1,,xT,语言模型的目标是估计联合概率 p ( x 1 , … , x T ) p(x_1, \ldots, x_T) p(x1,,xT)
  • 它的应用包括
    • 做预训练模型(eg BERT,GPT-3)
    • 生成本文,给定前面几个词,不断的使用 x t ∼ p ( x t ∣ x 1 , … , x t − 1 ) x_t \sim p(x_t|x_1, \ldots, x_{t-1}) xtp(xtx1,,xt1) 来生成后续文本
    • 判断多个序列中哪个更常见,e.g. “to recognize speech” vs “to wreck a nice beach”

使用计数来建模

  • 假设序列长度为2,我们预测
    p ( x , x ′ ) = p ( x ) p ( x ′ ∣ x ) = n ( x ) n n ( x , x ′ ) n ( x ) p(x, x') = p(x)p(x'|x) = \frac{n(x)}{n} \frac{n(x, x')}{n(x)} p(x,x)=p(x)p(xx)=nn(x)n(x)n(x,x)
    • 这里 n n n 是总词数, n ( x ) , n ( x , x ′ ) n(x), n(x, x') n(x),n(x,x) 是单个单词和连续单词对的出现次数
  • 很容易拓展到长为3的情况
    p ( x , x ′ , x ′ ′ ) = p ( x ) p ( x ′ ∣ x ) p ( x ′ ′ ∣ x , x ′ ) = n ( x ) n n ( x , x ′ ) n ( x ) n ( x , x ′ , x ′ ′ ) n ( x , x ′ ) p(x, x', x'') = p(x)p(x'|x)p(x''|x, x') = \frac{n(x)}{n} \frac{n(x, x')}{n(x)} \frac{n(x, x', x'')}{n(x, x')} p(x,x,x′′)=p(x)p(xx)p(x′′x,x)=nn(x)n(x)n(x,x)n(x,x)n(x,x,x′′)

N 元语法

  • 当序列很长时,因为文本量不够大,很可能 n ( x 1 , … , x T ) ≤ 1 n(x_1, \ldots, x_T) \leq 1 n(x1,,xT)1
  • 使用马尔科夫假设可以缓解这个问题
    • 一元语法:(这里 τ \tau τ = 0 ,也就是不用管前面的数据,数据间相互独立)
      p ( x 1 , x 2 , x 3 , x 4 ) = p ( x 1 ) p ( x 2 ∣ x 1 ) p ( x 3 ∣ x 2 ) p ( x 4 ∣ x 3 ) = n ( x 1 ) n n ( x 2 ) n n ( x 3 ) n n ( x 4 ) n p(x_1, x_2, x_3, x_4) = p(x_1)p(x_2|x_1)p(x_3|x_2)p(x_4|x_3) = \frac{n(x_1)}{n} \frac{n(x_2)}{n} \frac{n(x_3)}{n} \frac{n(x_4)}{n} p(x1,x2,x3,x4)=p(x1)p(x2x1)p(x3x2)p(x4x3)=nn(x1)nn(x2)nn(x3)nn(x4)
    • 二元语法:
      p ( x 1 , x 2 , x 3 , x 4 ) = n ( x 1 ) n n ( x 1 , x 2 ) n ( x 1 ) n ( x 2 , x 3 ) n ( x 2 ) n ( x 3 , x 4 ) n ( x 3 ) p(x_1, x_2, x_3, x_4) = \frac{n(x_1)}{n} \frac{n(x_1, x_2)}{n(x_1)} \frac{n(x_2, x_3)}{n(x_2)} \frac{n(x_3, x_4)}{n(x_3)} p(x1,x2,x3,x4)=nn(x1)n(x1)n(x1,x2)n(x2)n(x2,x3)n(x3)n(x3,x4)
    • 三元语法:(每个词和前面两个词是有关系的)
      p ( x 1 , x 2 , x 3 , x 4 ) = p ( x 1 ) p ( x 2 ∣ x 1 ) p ( x 3 ∣ x 1 , x 2 ) p ( x 4 ∣ x 1 , x 2 , x 3 ) p(x_1, x_2, x_3, x_4) = p(x_1)p(x_2|x_1)p(x_3|x_1,x_2)p(x_4|x_1,x_2,x_3) p(x1,x2,x3,x4)=p(x1)p(x2x1)p(x3x1,x2)p(x4x1,x2,x3)

总结

  • 语言模型估计文本序列的联合概率
  • 使用统计方法时常采用 n n n 元语法

代码实现

语言模型和数据集

在 RNN - 文本预处理 中,了解了如何将文本数据映射为词元,以及将这些词元可以视为一系列离散的观测,例如单词或字符。假设长度为 T T T的文本序列中的词元依次为 x 1 , x 2 , … , x T x_1, x_2, \ldots, x_T x1,x2,,xT。于是, x t x_t xt 1 ≤ t ≤ T 1 \leq t \leq T 1tT)可以被认为是文本序列在时间步 t t t处的观测或标签。在给定这样的文本序列时,语言模型(language model)的目标是估计序列的联合概率
P ( x 1 , x 2 , … , x T ) . P(x_1, x_2, \ldots, x_T). P(x1,x2,,xT).例如,只需要一次抽取一个词元 x t ∼ P ( x t ∣ x t − 1 , … , x 1 ) x_t \sim P(x_t \mid x_{t-1}, \ldots, x_1) xtP(xtxt1,,x1),一个理想的语言模型就能够基于模型本身生成自然文本。与猴子使用打字机完全不同的是,从这样的模型中提取的文本都将作为自然语言(例如,英语文本)来传递。只需要基于前面的对话片断中的文本,就足以生成一个有意义的对话。显然,我们离设计出这样的系统还很遥远,因为它需要“理解”文本,而不仅仅是生成语法合理的内容。

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

学习语言模型

显而易见,我们面对的问题是如何对一个文档,甚至是一个词元序列进行建模。假设在单词级别对文本数据进行词元化,可以依靠在 RNN - 序列模型 中对序列模型的分析。
首先从基本概率规则开始:
P ( x 1 , x 2 , … , x T ) = ∏ t = 1 T P ( x t ∣ x 1 , … , x t − 1 ) . P(x_1, x_2, \ldots, x_T) = \prod_{t=1}^T P(x_t \mid x_1, \ldots, x_{t-1}). P(x1,x2,,xT)=t=1TP(xtx1,,xt1).例如,包含了四个单词的一个文本序列的概率是:
P ( deep , learning , is , fun ) = P ( deep ) P ( learning ∣ deep ) P ( is ∣ deep , learning ) P ( fun ∣ deep , learning , is ) . P(\text{deep}, \text{learning}, \text{is}, \text{fun}) = P(\text{deep}) P(\text{learning} \mid \text{deep}) P(\text{is} \mid \text{deep}, \text{learning}) P(\text{fun} \mid \text{deep}, \text{learning}, \text{is}). P(deep,learning,is,fun)=P(deep)P(learningdeep)P(isdeep,learning)P(fundeep,learning,is).为了训练语言模型,我们需要计算单词的概率,以及给定前面几个单词后出现某个单词的条件概率。这些概率本质上就是语言模型的参数。

这里,我们假设训练数据集是一个大型的文本语料库。比如,维基百科的所有条目、古登堡计划,或者所有发布在网络上的文本。训练数据集中词的概率可以根据给定词的相对词频来计算。例如,可以将估计值 P ^ ( deep ) \hat{P}(\text{deep}) P^(deep)计算为任何以单词“deep”开头的句子的概率。一种(稍稍不太精确的)方法是统计单词“deep”在数据集中的出现次数,然后将其除以整个语料库中的单词总数。这种方法效果不错,特别是对于频繁出现的单词。接下来,我们可以尝试估计
P ^ ( learning ∣ deep ) = n ( deep, learning ) n ( deep ) , \hat{P}(\text{learning} \mid \text{deep}) = \frac{n(\text{deep, learning})}{n(\text{deep})}, P^(learningdeep)=n(deep)n(deep, learning),其中 n ( x ) n(x) n(x) n ( x , x ′ ) n(x, x') n(x,x)分别是单个单词和连续单词对的出现次数。

不幸的是,由于连续单词对“deep learning”的出现频率要低得多,所以估计这类单词正确的概率要困难得多。特别是对于一些不常见的单词组合,要想找到足够的出现次数来获得准确的估计可能都不容易。而对于三个或者更多的单词组合,情况会变得更糟。许多合理的三个单词组合可能是存在的,但是在数据集中却找不到。除非我们提供某种解决方案,来将这些单词组合指定为非零计数,否则将无法在语言模型中使用它们。如果数据集很小,或者单词非常罕见,那么这类单词出现一次的机会可能都找不到。

一种常见的策略是执行某种形式的拉普拉斯平滑(Laplace smoothing),具体方法是在所有计数中添加一个小常量。用 n n n表示训练集中的单词总数,用 m m m表示唯一单词的数量。此解决方案有助于处理单元素问题,例如通过:
P ^ ( x ) = n ( x ) + ϵ 1 / m n + ϵ 1 , P ^ ( x ′ ∣ x ) = n ( x , x ′ ) + ϵ 2 P ^ ( x ′ ) n ( x ) + ϵ 2 , P ^ ( x ′ ′ ∣ x , x ′ ) = n ( x , x ′ , x ′ ′ ) + ϵ 3 P ^ ( x ′ ′ ) n ( x , x ′ ) + ϵ 3 . \begin{aligned} \hat{P}(x) & = \frac{n(x) + \epsilon_1/m}{n + \epsilon_1}, \\ \hat{P}(x' \mid x) & = \frac{n(x, x') + \epsilon_2 \hat{P}(x')}{n(x) + \epsilon_2}, \\ \hat{P}(x'' \mid x,x') & = \frac{n(x, x',x'') + \epsilon_3 \hat{P}(x'')}{n(x, x') + \epsilon_3}. \end{aligned} P^(x)P^(xx)P^(x′′x,x)=n+ϵ1n(x)+ϵ1/m,=n(x)+ϵ2n(x,x)+ϵ2P^(x),=n(x,x)+ϵ3n(x,x,x′′)+ϵ3P^(x′′).其中, ϵ 1 , ϵ 2 \epsilon_1,\epsilon_2 ϵ1,ϵ2 ϵ 3 \epsilon_3 ϵ3是超参数。以 ϵ 1 \epsilon_1 ϵ1为例:当 ϵ 1 = 0 \epsilon_1 = 0 ϵ1=0时,不应用平滑;当 ϵ 1 \epsilon_1 ϵ1接近正无穷大时, P ^ ( x ) \hat{P}(x) P^(x)接近均匀概率分布 1 / m 1/m 1/m。上面的公式是 :cite:Wood.Gasthaus.Archambeau.ea.2011的一个相当原始的变形。

然而,这样的模型很容易变得无效,原因如下:首先,我们需要存储所有的计数;其次,这完全忽略了单词的意思。例如,“猫”(cat)和“猫科动物”(feline)可能出现在相关的上下文中,
但是想根据上下文调整这类模型其实是相当困难的。最后,长单词序列大部分是没出现过的,
因此一个模型如果只是简单地统计先前“看到”的单词序列频率,那么模型面对这种问题肯定是表现不佳的。

马尔可夫模型与 n n n元语法

在讨论包含深度学习的解决方案之前,需要了解更多的概念和术语。在 RNN - 序列模型 中对马尔可夫模型的讨论,并且将其应用于语言建模。如果 P ( x t + 1 ∣ x t , … , x 1 ) = P ( x t + 1 ∣ x t ) P(x_{t+1} \mid x_t, \ldots, x_1) = P(x_{t+1} \mid x_t) P(xt+1xt,,x1)=P(xt+1xt),则序列上的分布满足一阶马尔可夫性质。阶数越高,对应的依赖关系就越长。这种性质推导出了许多可以应用于序列建模的近似公式:
P ( x 1 , x 2 , x 3 , x 4 ) = P ( x 1 ) P ( x 2 ) P ( x 3 ) P ( x 4 ) , P ( x 1 , x 2 , x 3 , x 4 ) = P ( x 1 ) P ( x 2 ∣ x 1 ) P ( x 3 ∣ x 2 ) P ( x 4 ∣ x 3 ) , P ( x 1 , x 2 , x 3 , x 4 ) = P ( x 1 ) P ( x 2 ∣ x 1 ) P ( x 3 ∣ x 1 , x 2 ) P ( x 4 ∣ x 2 , x 3 ) . \begin{aligned} P(x_1, x_2, x_3, x_4) &= P(x_1) P(x_2) P(x_3) P(x_4),\\ P(x_1, x_2, x_3, x_4) &= P(x_1) P(x_2 \mid x_1) P(x_3 \mid x_2) P(x_4 \mid x_3),\\ P(x_1, x_2, x_3, x_4) &= P(x_1) P(x_2 \mid x_1) P(x_3 \mid x_1, x_2) P(x_4 \mid x_2, x_3). \end{aligned} P(x1,x2,x3,x4)P(x1,x2,x3,x4)P(x1,x2,x3,x4)=P(x1)P(x2)P(x3)P(x4),=P(x1)P(x2x1)P(x3x2)P(x4x3),=P(x1)P(x2x1)P(x3x1,x2)P(x4x2,x3).通常,涉及一个、两个和三个变量的概率公式分别被称为一元语法(unigram)、二元语法(bigram)和三元语法(trigram)模型。下面,我们将学习如何去设计更好的模型。

自然语言统计

我们看看在真实数据上如果进行自然语言统计。根据 RNN - 文本预处理 中介绍的时光机器数据集构建词表,并打印前 10 10 10个最常用的(频率最高的)单词。

import random
import torch
from d2l import torch as d2ltokens = d2l.tokenize(d2l.read_time_machine())
# 因为每个文本行不一定是一个句子或一个段落,因此我们把所有文本行拼接到一起
corpus = [token for line in tokens for token in line]
vocab = d2l.Vocab(corpus)
vocab.token_freqs[:10]

在这里插入图片描述
正如我们所看到的,(最流行的词)看起来很无聊,这些词通常(被称为停用词)(stop words),因此可以被过滤掉。尽管如此,它们本身仍然是有意义的,会在模型中使用它们。此外,还有个明显的问题是词频衰减的速度相当地快。例如,最常用单词的词频对比,第 10 10 10个还不到第 1 1 1个的 1 / 5 1/5 1/5。为了更好地理解,我们可以[画出的词频图]:
在这里插入图片描述
通过此图我们可以发现:词频以一种明确的方式迅速衰减。将前几个单词作为例外消除后,剩余的所有单词大致遵循双对数坐标图上的一条直线。这意味着单词的频率满足齐普夫定律(Zipf’s law),即第 i i i 个最常用单词的频率 n i n_i ni 为:
n i ∝ 1 i α , n_i \propto \frac{1}{i^\alpha}, niiα1,等价于
log ⁡ n i = − α log ⁡ i + c , \log n_i = -\alpha \log i + c, logni=αlogi+c,其中 α \alpha α是刻画分布的指数, c c c是常数。这告诉我们想要通过计数统计和平滑来建模单词是不可行的,因为这样建模的结果会大大高估尾部单词的频率,也就是所谓的不常用单词。那么[其他的词元组合,比如二元语法、三元语法等等,又会如何呢?]我们来看看二元语法的频率是否与一元语法的频率表现出相同的行为方式。

bigram_tokens = [pair for pair in zip(corpus[:-1], corpus[1:])]
bigram_vocab = d2l.Vocab(bigram_tokens)
bigram_vocab.token_freqs[:10]

在这里插入图片描述
在这里插入图片描述
最后,[直观地对比三种模型中的词元频率]:一元语法、二元语法和三元语法。
在这里插入图片描述
这张图非常令人振奋!原因有很多:

  1. 除了一元语法词,单词序列似乎也遵循齐普夫定律,尽管指数的大小受序列长度的影响;
  2. 词表中 n n n元组的数量并没有那么大,这说明语言中存在相当多的结构,这些结构给了我们应用模型的希望;
  3. 很多 n n n元组很少出现,这使得拉普拉斯平滑非常不适合语言建模。作为代替,我们将使用基于深度学习的模型。

读取长序列数据

由于序列数据本质上是连续的,因此我们在处理数据时需要解决这个问题。在 RNN - 序列模型 中我们以一种相当特别的方式做到了这一点:当序列变得太长而不能被模型一次性全部处理时,我们可能希望拆分这样的序列方便模型读取。

在介绍该模型之前,我们看一下总体策略。假设我们将使用神经网络来训练语言模型,模型中的网络一次处理具有预定义长度(例如 n n n个时间步)的一个小批量序列。现在的问题是如何[随机生成一个小批量数据的特征和标签以供读取。]

首先,由于文本序列可以是任意长的,例如整本《时光机器》(The Time Machine),于是任意长的序列可以被我们划分为具有相同时间步数的子序列。当训练我们的神经网络时,这样的小批量子序列将被输入到模型中。假设网络一次只处理具有 n n n个时间步的子序列。 下图画出了从原始文本序列获得子序列的所有不同的方式,其中 n = 5 n=5 n=5,并且每个时间步的词元对应于一个字符。请注意,因为我们可以选择任意偏移量来指示初始位置,所以我们有相当大的自由度。
在这里插入图片描述
因此,我们应该从上图中选择哪一个呢?事实上,他们都一样的好。然而,如果我们只选择一个偏移量,那么用于训练网络的、所有可能的子序列的覆盖范围将是有限的。因此,我们可以从随机偏移量开始划分序列,以同时获得覆盖性(coverage)和随机性(randomness)。
下面,我们将描述如何实现随机采样(random sampling)和顺序分区(sequential partitioning)策略。

随机采样

(在随机采样中,每个样本都是在原始的长序列上任意捕获的子序列。)在迭代过程中,来自两个相邻的、随机的、小批量中的子序列不一定在原始序列上相邻。对于语言建模,目标是基于到目前为止我们看到的词元来预测下一个词元,因此标签是移位了一个词元的原始序列。

下面的代码每次可以从数据中随机生成一个小批量。在这里,参数batch_size指定了每个小批量中子序列样本的数目,参数num_steps是每个子序列中预定义的时间步数。

def seq_data_iter_random(corpus, batch_size, num_steps):  # @save"""使用随机抽样生成一个小批量子序列"""# 从随机偏移量开始对序列进行分区,随机范围包括num_steps-1corpus = corpus[random.randint(0, num_steps - 1):]  # 把前面的随机丢一点# 减去1,是因为我们需要考虑标签,看可以生成多少个子序列出来num_subseqs = (len(corpus) - 1) // num_steps# 长度为num_steps的子序列的起始索引,每个子序列开始的下标initial_indices = list(range(0, num_subseqs * num_steps, num_steps))# 在随机抽样的迭代过程中,# 来自两个相邻的、随机的、小批量中的子序列不一定在原始序列上相邻random.shuffle(initial_indices)def data(pos):# 返回从pos位置开始的长度为num_steps的序列return corpus[pos: pos + num_steps]num_batches = num_subseqs // batch_sizefor i in range(0, batch_size * num_batches, batch_size):# 在这里,initial_indices包含子序列的随机起始索引initial_indices_per_batch = initial_indices[i: i + batch_size]X = [data(j) for j in initial_indices_per_batch]Y = [data(j + 1) for j in initial_indices_per_batch]yield torch.tensor(X), torch.tensor(Y)

下面我们[生成一个从 0 0 0 34 34 34的序列]。假设批量大小为 2 2 2,时间步数为 5 5 5,这意味着可以生成 ⌊ ( 35 − 1 ) / 5 ⌋ = 6 \lfloor (35 - 1) / 5 \rfloor= 6 ⌊(351)/5=6个“特征-标签”子序列对。如果设置小批量大小为 2 2 2,我们只能得到 3 3 3个小批量。
在这里插入图片描述

顺序分区

在迭代过程中,除了对原始序列可以随机抽样外,我们还可以[保证两个相邻的小批量中的子序列在原始序列上也是相邻的]。这种策略在基于小批量的迭代过程中保留了拆分的子序列的顺序,因此称为顺序分区。

def seq_data_iter_sequential(corpus, batch_size, num_steps):  #@save"""使用顺序分区生成一个小批量子序列"""# 从随机偏移量开始划分序列offset = random.randint(0, num_steps)num_tokens = ((len(corpus) - offset - 1) // batch_size) * batch_sizeXs = torch.tensor(corpus[offset: offset + num_tokens])Ys = torch.tensor(corpus[offset + 1: offset + 1 + num_tokens])Xs, Ys = Xs.reshape(batch_size, -1), Ys.reshape(batch_size, -1)num_batches = Xs.shape[1] // num_stepsfor i in range(0, num_steps * num_batches, num_steps):X = Xs[:, i: i + num_steps]Y = Ys[:, i: i + num_steps]yield X, Y

基于相同的设置,通过顺序分区[读取每个小批量的子序列的特征X和标签Y]。通过将它们打印出来可以发现:迭代期间来自两个相邻的小批量中的子序列在原始序列中确实是相邻的。
在这里插入图片描述
现在,我们[将上面的两个采样函数包装到一个类中],以便稍后可以将其用作数据迭代器。

class SeqDataLoader:  #@save"""加载序列数据的迭代器"""def __init__(self, batch_size, num_steps, use_random_iter, max_tokens):if use_random_iter:self.data_iter_fn = d2l.seq_data_iter_randomelse:self.data_iter_fn = d2l.seq_data_iter_sequentialself.corpus, self.vocab = d2l.load_corpus_time_machine(max_tokens)self.batch_size, self.num_steps = batch_size, num_stepsdef __iter__(self):return self.data_iter_fn(self.corpus, self.batch_size, self.num_steps)

[最后,我们定义了一个函数load_data_time_machine,它同时返回数据迭代器和词表],因此可以与其他带有load_data前缀的函数类似地使用。

def load_data_time_machine(batch_size, num_steps,  #@saveuse_random_iter=False, max_tokens=10000):"""返回时光机器数据集的迭代器和词表"""data_iter = SeqDataLoader(batch_size, num_steps, use_random_iter, max_tokens)return data_iter, data_iter.vocab

小结

  • 语言模型是自然语言处理的关键。
  • n n n元语法通过截断相关性,为处理长序列提供了一种实用的模型。
  • 长序列存在一个问题:它们很少出现或者从不出现。
  • 齐普夫定律支配着单词的分布,这个分布不仅适用于一元语法,还适用于其他 n n n元语法。
  • 通过拉普拉斯平滑法可以有效地处理结构丰富而频率不足的低频词词组。
  • 读取长序列的主要方式是随机采样和顺序分区。在迭代过程中,后者可以保证来自两个相邻的小批量中的子序列在原始序列上也是相邻的。

QA 思考

Q1:在文本预处理中,所构建的词汇表把文本映射成数字,文本数据量越大,映射的数字也就越大,这些数字还需要做预处理吗?例如归一化处理等。是否对模型有影响?
A1:数字在此处主要是起到一个 id 的作用。这个数字只是index,真正的东西是embedding。

Q2:连续单词对是指有固定先后顺序的还是任何顺序都可行呢?
A2:是有一个先后顺序的,顺序不一样值就完全不一样。

Q3:o(T),T 指什么
A3:T 是 num_of_steps 也就是每次需要看一个多长的序列

后记

整理了一下代码:
text_preprocessing 在 RNN - 文本预处理 后记中
sequence 在 RNN - 序列模型 后记中

import random
import matplotlib.pyplot as plt
import torch# 从 text_preprocessing 模块导入所需的函数和类
from text_preprocessing import load_corpus_time_machine
from text_preprocessing import tokenize, read_time_machine, Vocab
# 从 sequence 模块导入 plot 函数
from sequence import plot# 打印前十个最常用的单词
# 对时间机器文本进行分词
tokens = tokenize(read_time_machine())
# 将二维的分词结果展平为一维列表
corpus = [token for line in tokens for token in line]
# 创建词表对象
vocab = Vocab(corpus)
# 打印词表中出现频率最高的前 10 个词元及其频率
print(vocab.token_freqs[:10])# 获取词元频率列表
freqs = [freq for token, freq in vocab.token_freqs]
# 绘制词元频率图,使用对数坐标轴
plot(freqs, xlabel='token: x', ylabel='frequency: n(x)',xscale='log', yscale='log')
# 显示图形
plt.show()# 二元语法的频率
# 生成二元语法词元列表
bigram_tokens = [pair for pair in zip(corpus[:-1], corpus[1:])]
# 创建二元语法词表对象
bigram_vocab = Vocab(bigram_tokens)
# 打印二元语法词表中出现频率最高的前 10 个词元及其频率
print(bigram_vocab.token_freqs[:10])# 三元语法的频率
# 生成三元语法词元列表
trigram_tokens = [triple for triple in zip(corpus[:-2], corpus[1:-1], corpus[2:]
)]
# 创建三元语法词表对象
trigram_vocab = Vocab(trigram_tokens)
# 打印三元语法词表中出现频率最高的前 10 个词元及其频率
print(trigram_vocab.token_freqs[:10])# 可视化展示
# 获取二元语法词元频率列表
bigram_freqs = [freq for token, freq in bigram_vocab.token_freqs]
# 获取三元语法词元频率列表
trigram_freqs = [freq for token, freq in trigram_vocab.token_freqs]
# 绘制一元、二元和三元语法词元频率图,使用对数坐标轴
plot([freqs, bigram_freqs, trigram_freqs], xlabel='token: x',ylabel='frequency: n(x)', xscale='log', yscale='log',legend=['unigram', 'bigram', 'trigram'])
# 显示图形
plt.show()def seq_data_iter_random(corpus, batch_size, num_steps):"""使用随机抽样生成一个小批量子序列。例子:假设 corpus = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],batch_size = 2,num_steps = 3首先随机选择一个起始位置,假设从位置 1 开始,即从 1 开始可以得到子序列:[1, 2, 3], [4, 5, 6], [7, 8, 9]然后打乱这些子序列的顺序,随机选取 batch_size 个子序列作为一个批次例如选取 [4, 5, 6] 和 [1, 2, 3] 作为一个批次该批次的 X 为 [[4, 5, 6], [1, 2, 3]],Y 为 [[5, 6, 7], [2, 3, 4]]:param corpus: 语料库,一个一维的词元列表:param batch_size: 每个小批量中的样本数量:param num_steps: 每个子序列的长度:return: 生成器,每次生成一个小批量的特征 X 和标签 Y"""# 随机选择一个起始位置corpus = corpus[random.randint(0, num_steps - 1):]# 计算可生成的子序列数量num_subseqs = (len(corpus) - 1) // num_steps# 生成每个子序列的起始索引initial_indices = list(range(0, num_subseqs * num_steps, num_steps))# 打乱起始索引的顺序random.shuffle(initial_indices)def data(pos):"""获取从位置 pos 开始的长度为 num_steps 的子序列:param pos: 起始位置:return: 子序列"""return corpus[pos:pos + num_steps]# 计算可生成的批次数量num_batches = num_subseqs // batch_size# 遍历每个批次for i in range(0, batch_size * num_batches, batch_size):# 获取当前批次的起始索引列表initial_indices_per_batch = initial_indices[i:i + batch_size]# 生成当前批次的特征 XX = [data(j) for j in initial_indices_per_batch]# 生成当前批次的标签 YY = [data(j + 1) for j in initial_indices_per_batch]# 生成当前批次的特征 X 和标签 Yyield torch.tensor(X), torch.tensor(Y)# 生成一个从 0 到 34 的序列
my_seq = list(range(35))
# 使用随机抽样生成小批量子序列,并打印每个批次的特征 X 和标签 Y
for X, Y in seq_data_iter_random(my_seq, batch_size=2, num_steps=5):print('X: ', X, '\nY:', Y)def seq_data_iter_sequential(corpus, batch_size, num_steps):"""使用顺序分区生成一个小批量子序列。例子:假设 corpus = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],batch_size = 2,num_steps = 3首先随机选择一个偏移量,假设偏移量为 1,即从 1 开始然后将语料库按 batch_size 进行分区,得到:[[1, 2, 3, 4], [5, 6, 7, 8]]再将每个分区按 num_steps 进行划分,得到:第一个分区:[[1, 2, 3], [4]]第二个分区:[[5, 6, 7], [8]]选取每个分区的第一个子序列作为一个批次,即 [[1, 2, 3], [5, 6, 7]]该批次的 X 为 [[1, 2, 3], [5, 6, 7]],Y 为 [[2, 3, 4], [6, 7, 8]]:param corpus: 语料库,一个一维的词元列表:param batch_size: 每个小批量中的样本数量:param num_steps: 每个子序列的长度:return: 生成器,每次生成一个小批量的特征 X 和标签 Y"""# 随机选择一个偏移量offset = random.randint(0, num_steps)# 计算可用于生成子序列的词元数量num_tokens = ((len(corpus) - offset - 1) // batch_size) * batch_size# 截取语料库,得到特征 X 的数据Xs = torch.tensor(corpus[offset:offset + num_tokens])# 截取语料库,得到标签 Y 的数据Ys = torch.tensor(corpus[offset + 1:offset + num_tokens + 1])# 调整 Xs 和 Ys 的形状,使其适合按批次划分Xs, Ys = Xs.reshape(batch_size, -1), Ys.reshape(batch_size, -1)# 计算可生成的批次数量num_batches = Xs.shape[1] // num_steps# 遍历每个批次for i in range(0, num_steps * num_batches, num_steps):# 选取当前批次的特征 XX = Xs[:, i:i + num_steps]# 选取当前批次的标签 YY = Ys[:, i:i + num_steps]# 生成当前批次的特征 X 和标签 Yyield X, Y# 基于相同的设置,读取每个小批量的子序列的特征 X 和标签 Y
for X, Y in seq_data_iter_sequential(my_seq, batch_size=2, num_steps=5):print('X: ', X, '\nY:', Y)class SeqDataLoader:"""序列数据加载器类,用于生成小批量的序列数据。可以选择使用随机抽样或顺序分区的方式生成数据。"""def __init__(self, batch_size, num_steps, use_random_iter, max_tokens):"""初始化序列数据加载器。:param batch_size: 每个小批量中的样本数量:param num_steps: 每个子序列的长度:param use_random_iter: 是否使用随机抽样的方式生成数据:param max_tokens: 语料库中使用的最大词元数量"""if use_random_iter:# 如果使用随机抽样,选择 seq_data_iter_random 函数self.data_iter_fn = seq_data_iter_randomelse:# 如果使用顺序分区,选择 seq_data_iter_sequential 函数self.data_iter_fn = seq_data_iter_sequential# 加载时间机器语料库和词表self.corpus, self.vocab = load_corpus_time_machine(max_tokens)# 保存批次大小和子序列长度self.batch_size, self.num_steps = batch_size, num_stepsdef __iter__(self):"""实现迭代器方法,返回一个生成器,用于生成小批量的序列数据。:return: 生成器,每次生成一个小批量的特征 X 和标签 Y"""return self.data_iter_fn(self.corpus, self.batch_size, self.num_steps)def load_data_time_machine(batch_size, num_steps,use_random_iter=False, max_token=10000):"""加载时间机器数据集,返回数据迭代器和词表。:param batch_size: 每个小批量中的样本数量:param num_steps: 每个子序列的长度:param use_random_iter: 是否使用随机抽样的方式生成数据:param max_token: 语料库中使用的最大词元数量:return: 数据迭代器和词表"""data_iter = SeqDataLoader(batch_size=batch_size, num_steps=num_steps,use_random_iter=use_random_iter, max_tokens=max_token)return data_iter, data_iter.vocab

相关文章:

RNN - 语言模型

语言模型 给定文本序列 x 1 , … , x T x_1, \ldots, x_T x1​,…,xT​,语言模型的目标是估计联合概率 p ( x 1 , … , x T ) p(x_1, \ldots, x_T) p(x1​,…,xT​)它的应用包括 做预训练模型(eg BERT,GPT-3)生成本文&#xff…...

前端面试题---GET跟POST的区别(Ajax)

GET 和 POST 是两种 HTTP 请求方式,它们在传输数据的方式和所需空间上有一些重要区别: ✅ 一句话概括: GET 数据放在 URL 中,受限较多;POST 数据放在请求体中,空间更大更安全。 📦 1. 所需空间…...

【MCP】第一篇:MCP协议深度解析——大模型时代的“神经连接层“架构揭秘

【MCP】第一篇:MCP协议深度解析——大模型时代的"神经连接层"架构揭秘 一、什么是MCP?二、为什么需要MCP?三、MCP的架构四、MCP与AI交互的原理4.1 ReAct(Reasoning Acting)模式4.2 Function Calling 模式 五…...

新生宿舍管理系统

收藏关注不迷路!! 🌟文末获取源码数据库🌟 感兴趣的可以先收藏起来,还有大家在毕设选题(免费咨询指导选题),项目以及论文编写等相关问题都可以给我留言咨询,希望帮助更多…...

@Autowird 注解与存在多个相同类型对象的解方案

现有一个 Student 类,里面有两个属性,分别为 name 和 id;有一个 StuService 类,里面有两个方法,返回值均为类型为 Student 的对象;还有一个 StuController 类,里面有一个 Student 类型的属性&am…...

MQTT客户端核心架构解析:clients.h源码深度解读

MQTT客户端核心架构解析:clients.h源码深度解读 一、头文件概览与设计哲学 clients.h作为MQTT客户端核心数据结构定义文件,体现了以下设计原则: 分层架构:网络层/协议层/业务层解耦状态管理:通过状态机实现复杂协议…...

音视频学习 - ffmpeg 编译与调试

编译 环境 macOS Ventrua 13.4 ffmpeg 7.7.1 Visual Studio Code Version: 1.99.0 (Universal) 操作 FFmpeg 下载源码 $ cd ffmpeg-x.y.z $ ./configure nasm/yasm not found or too old. Use --disable-x86asm for a crippled build.If you think configure made a mistake…...

解读《人工智能指数报告 2025》:洞察 AI 发展新态势

美国斯坦福大学 “以人为本人工智能研究院”(HAI)近日发布的第八版《人工智能指数报告》(AI Index Report 2025)备受全球瞩目。自 2017 年首次发布以来,该报告一直为政策制定者、研究人员、企业高管和公众提供准确、严…...

【嵌入式系统设计师(软考中级)】第一章:计算机系统基础知识(中)

文章目录 3 算术运算和逻辑运算3.1 二进制数运算方法3.2 逻辑代数的基本运算与逻辑表达式化简 4. 计算机组成及工作原理4.1 CPU的组成与工作原理4.1.1 运算器(数据加工中心)4.1.2 控制器(指令指挥中心)4.1.3 计算机指令4.1.4 寻址…...

实时数据处理的革命:Apache Flink 在大数据流处理中的应用

实时数据处理的革命:Apache Flink 在大数据流处理中的应用 在大数据时代,数据的价值不仅仅体现在存储和分析,更重要的是实时处理。传统的批处理模式往往无法满足现代业务对数据的实时性需求,而流式计算技术的兴起,让数据处理从“静态分析”变成了“动态决策”。其中,Apa…...

HttpSessionListener 的用法笔记250417

HttpSessionListener 的用法笔记250417 以下是关于 HttpSessionListener 的用法详解,涵盖核心方法、实现步骤、典型应用场景及注意事项,帮助您全面掌握会话(Session)生命周期的监听与管理: 1. 核心功能 HttpSessionLi…...

基于html实现的课题随机点名

这是一个用于随机点名系统的HTML网页,具有中国古典风格的设计。 下面我将从多个方面详细介绍这个文件: 1. 文件基本信息 文件名:name.html 文件类型:HTML5文档 语言:简体中文(zh-CN) 编码:UTF-8 标题&…...

【KWDB 创作者计划】深度实操体验 KWDB 2.2.0:从安装到实战的全流程解析以及实操体验

一、引言 KWDB 是一款高性能的分布式数据库,支持事务、强一致性和水平扩展。本文将详细介绍如何通过 Docker 快速部署 KWDB 2.2.0,并基于实际操作演示数据库的核心功能,涵盖环境准备、容器运行、数据操作及集群部署等关键环节。 二、Docker…...

ASP.NET Core中SqlSugar基本使用

创建数据模型 public class News{[SugarColumn(IsIdentity true, IsPrimaryKey true)]public int Id { get; set; }//nvarchar带中文比较好[SugarColumn(ColumnDataType "nvarchar(30)")]public string Title { get; set; }[SugarColumn(ColumnDataType "te…...

【软考-系统架构设计师】设计模式三大类型解析

设计模式三大类型深度解析 一、创建型模式(Creational Patterns) 核心目标:解耦对象的创建与使用过程,提供灵活的对象生成机制,降低系统对具体类的依赖。 适用场景:需要动态创建对象、隐藏对象创建细节或…...

正则表达式在爬虫中的应用:匹配 HTML 和 JSON 的技巧

在爬虫开发中,正则表达式是一种强大的工具,可以帮助我们从复杂的文本中提取所需信息。无论是处理 HTML 页面还是 JSON 数据,正则表达式都能发挥重要作用。本文将深入探讨正则表达式在爬虫中的应用,包括如何匹配 HTML 和 JSON 数据…...

LaTeX文章写法

文章目录 模板1、无序列表格式2、对齐2.1、section对齐 模板 文章模板 %\documentclass[a4paper,12pt]{article} % 选择 A4 纸张和 12pt 字体大小 \documentclass[12pt,a4paper]{ctexart}% 加载必要的宏包 \usepackage{fontspec} % 支持字体设置 \usepackage{xeCJK} …...

电力变压器油的<油质气象色谱>指标分析

目录 1.变压器油质化验指标分析 2.变压器油质化验原理及流程 变压器油质气象色谱(气相色谱,Gas Chromatography, GC)检测是一种通过分离和定量分析油中溶解气体成分的技术,用于诊断变压器内部故障。其核心原理基于不同气体在流动…...

赋能能源 | 智慧数据,构建更高效智能的储能管理系统

行业背景 随着新能源产业的快速发展,大规模储能系统在电力调峰、调频及可再生能源消纳等领域的重要性日益凸显。 储能电站作为核心基础设施,其能量管理系统(EMS)需要处理海量实时数据,包括电池状态、功率变化、环境监…...

AWS中国区服务部署与ICP备案全流程指南:从0到1实现合规上线

导语: 在中国大陆地区使用AWS服务,不仅需要了解AWS的基本操作,还需要熟悉中国特有的法规要求。本文将为您提供一个全面的指南,涵盖AWS中国区账号创建、服务部署、ICP备案申请,以及合规运营的全过程。无论您是AWS新手还是经验丰富的开发者,这篇文章都将为您在AWS中国区的journey…...

android系统使用FFmpeng集成OpenSL音频录制和播放

目录 一、背景 二、方案 三、代码实现 3.1 初始化OpenSL 3.2 设置播放回掉 3.3 使用FFmpeg计算出转换后的样本数目 一、背景 FFmpeg不能够操作Android的硬件设备,所以要在Android系统上面播放音频的话需要另找办法 二、方案 Android 环境下音频播放通常有两…...

顺序表和链表,时间和空间复杂度--数据结构初阶(1)(C/C++)

文章目录 前言时间复杂度和空间复杂度理论部分习题部分 顺序表和链表理论部分作业部分 前言 这期的话会给大家讲解复杂度,顺序表和链表的一些知识和习题部分(重点是习题部分,因为这几个理念都比较简单) 时间复杂度和空间复杂度 理论部分 时间复杂度和…...

【C++面向对象】封装(下):探索C++运算符重载设计精髓

🔥个人主页 🔥 😈所属专栏😈 每文一诗 💪🏼 年年岁岁花相似,岁岁年年人不同 —— 唐/刘希夷《代悲白头翁》 译文:年年岁岁繁花依旧,岁岁年年看花之人却不相同 目录 C运…...

分布式存储数据恢复—hbase和hive数据库文件被删除如何恢复数据?

分布式存储数据恢复环境: 16台某品牌R730xd服务器节点,每台服务器节点上有数台虚拟机。 虚拟机上部署Hbase和Hive数据库。 分布式存储故障: 数据库底层文件被误删除,数据库不能使用。要求恢复hbase和hive数据库。 北亚企安数据恢复…...

【并行分布计算】Hadoop伪分布搭建

Hadoop伪分布搭建 1. 修改core-site.xml fs.defaultFS设置的是HDFS的地址,设置运行在本地的9000端口上 hadoop.tmp.dir设置的是临时目录,如果没有设置的话默认在/tmp/hadoop-${user.name}中,系统重启后会导致数据丢失,因此修改这…...

Redis面试——常用命令

一、String (1)设置值相关命令 1.1.1 SET 功能:设置一个键值对,如果键已存在则覆盖旧值语法: SET key value [EX seconds] [PX milliseconds] [NX|XX]EX seconds:设置键的过期时间为 seconds 秒 PX milli…...

告别定时任务!用Dagster监听器实现秒级数据响应自动化

在数据管道开发中,我们经常面临需要根据外部事件触发计算任务的场景。传统基于时间的调度方式存在资源浪费和时效性不足的问题。本文将通过Dagster的**传感器(Sensor)**功能,演示如何构建事件驱动的数据处理流程。 场景模拟&…...

测试用例的生命周期:从诞生到退役的全过程管理

测试用例不是一成不变的标本 在敏捷开发时代,测试用例就像有机生命体一样会经历完整的生命周期。据Microsoft Research调查,良好管理的测试用例可使缺陷发现率提升40%,而缺乏管理的用例库在6个月后失效比例高达65%,本文将深入解析…...

【并行分布计算】Hadoop单机分布搭建

Hadoop单机分布搭建 环境:VMware Workstation虚拟机centos7镜像MobaXterm远程连接工具 为了使机器都处于同一个局域网中,先要修改机器的ip地址分配方式为固定ip,并为其固定分配一个ip地址。 [rootlocalhost ~]# vi /etc/sysconfig/network-…...

Android studio前沿开发--利用socket服务器连接AI实现前后端交互(全站首发思路)

我们在前几期学习了利用socket进行前后端的交互,但那只是基础性知识,这次,通过参考讯飞星火的java参考文档,再结合之前所学的socket服务,成功实现了通过后端将AI的调用实现在了自己的APP中。 本次的学习内容 1.真机的…...

Redis的下载安装和使用(超详细)

目录 一、所需的安装包资源小编放下述网盘了,提取码:wshf 二、双击打开文件redis.desktop.manager.exe 三、点击next后,再点击i agree 四、点击箭头指向,选择安装路径,然后点击Install进行安装 五、安装完后依次点…...

手机状态:UML 状态图(State Diagram)的解析与绘画

目录 一、UML 状态图(State Diagram)是什么 二、题目原型 三、手机状态图的解析 状态转换的触发条件 四、状态图的构建与解读 图的解读 五、状态图的实际应用 六、总结与展望 一、UML 状态图(State Diagram)是什么 UML …...

MyBatisPlus-QueryWrapper的exists方法拼接SQL中的EXISTS子句

在 MyBatis-Plus 中,QueryWrapper 的 exists 方法用于拼接 SQL 中的 EXISTS 子句,通常用于构 建子查询条件。以下是具体用法和示例: ​​1. 基本语法​​ // 判断是否存在符合条件的记录 queryWrapper.exists(String existsSql); queryWrapper.notExists(String existsSq…...

HarmonyOS-ArkUI: 自定义组件冻结功能@ComonentV2 freezeWhenInactive属性

引 @ComponentV2 装饰器是可以接收参数的,叫freezeWhenInactive, 顾名思义,就是当组件变成Inactive的时候,冻结。其默认值是false。所以如果您没有传参数时,默认不冻结。 冻结到底是一种什么状态呢?说简单点就是状态变量不响应更新。@Monitor修饰的那些状态变量更新检测…...

【问题】一招解决vscode输出和终端不一致的困扰

背景(闲话Trae) Trae是挺好,用了几天,发现它时不时检查文件,一检测就转悠半天,为此我把当前环境清空,就留一个正在调的程序,结果还照样检测,虽然没影响什么,…...

【CODESYS学习笔记001】MODBUS-TCP 与 标准TCP通信的优缺点对比

1. MODBUS-TCP 优点: 1. 标准化协议 - 基于工业标准(RFC标准),兼容性强,几乎所有PLC和工业设备都支持。 - 固定功能码(如0x03读寄存器、0x10写寄存器),开发简单。 2. 数据格…...

⭐ Unity 使用Odin Inspector增强编辑器的功能:UIManager脚本实例

先看一下测试效果: 在Unity开发中,Odin Inspector已经成为了一个非常受欢迎的工具,它通过增强编辑器的功能,使得开发者在工作中更加高效,尤其是在处理复杂数据和自定义编辑器方面。今天,我们将通过一个简…...

Linux网络协议栈深度解析:从数据封装到子网划分的底层架构

知识点5 1、封装和解封装的流程 封装数据报文:发送数据 解封装数据报文:接收报文 以后我们的网络编程过程中,只需要告知IP与端口号,链路层的MAC地址 有协议栈帮我们提供。 2、链路层报文格式(mac报文) …...

Java与MySQL数据库连接的JDBC驱动配置教程

系列文章目录 Java JDBC编程 文章目录 系列文章目录前言一、JDBC简介:二、mysql-connector-java驱动详解: 驱动版本特性介绍: 三、JDBC驱动安装与配置: 1.IDE项目设置:2.命令行安装:3.使用Maven或Gradle :…...

光伏产品研发项目如何降本增效?8Manage 项目管理软件在复合材料制造的应用

在复合材料制造领域,特别是光伏PECVD石墨舟和燃料电池石墨双极板等高精尖产品的研发过程中,高效的项目管理直接决定了产品开发周期、质量和市场竞争力。然而,许多企业在项目立项、进度跟踪、资源分配和质量控制等环节面临挑战。 针对这些痛点…...

矫平机:工业制造中的“板材整形师“

在机械制造车间此起彼伏的轰鸣声中,一卷卷冷轧钢卷正经历着神奇的蜕变。经过开卷、矫平、剪切等工序,原本蜷曲的金属板材变得平整如镜,这些改变都源于生产线上一个关键设备——矫平机。这台被称作"板材整形师"的精密机械&#xff0…...

数据江湖:Node.js 与 SQLite3 的轻量之道

前言 在这个“万码奔腾”的时代,想在江湖中闯出一片天地,不光要有剑(JavaScript),还得有招式(数据库)!本篇秘籍便是教你如何用 Node.js + SQLite3 打造一座小而美的“数据藏经阁”。初学者可轻松上手,高手可在细节中悟出更深的“数据库心法”。 简介 SQLite 在前端…...

4.15BUUCTF Ez_bypass,HardSQL,AreUSerialz,BabyUpload,CheckIn

[MRCTF2020]Ez_bypass 打开环境,看源码 include flag.php; $flagMRCTF{xxxxxxxxxxxxxxxxxxxxxxxxx}; if(isset($_GET[gg])&&isset($_GET[id])) {$id$_GET[id];$gg$_GET[gg];if (md5($id) md5($gg) && $id ! $gg) {echo You got the first step;i…...

【HarmonyOS NEXT+AI】问答 03:找不到 DevEco Studio Cangjie Plugin 下载链接?

【HarmonyOS NEXTAI】问答 03:找不到 DevEco Studio Cangjie Plugin 下载链接? 在 "HarmonyOS NEXTAI 大模型打造智能助手 APP (仓颉版)" 课程里面,有学员提到了这样一个问题:我在华为开发者社区官网找不到 DevEco Stu…...

使用 reverse-sourcemap 工具反编译 Vue 项目

要使用 reverse-sourcemap 工具反编译 Vue 项目,可以按照以下步骤操作: 步骤一:安装 reverse-sourcemap 首先,需要全局安装 reverse-sourcemap 工具。在命令行中执行以下命令: npm install --global reverse-sourcem…...

通信安全员历年考试重难点有哪些?

通信安全员考试的重难点紧密围绕行业特性和法规更新展开,需结合最新政策与实践案例综合掌握。以下是基于历年考试趋势及 2025 年新规的深度解析: 一、核心法规与标准体系(占比 30%-40%) 1. 安全生产法与行业规定 《安全生产法》…...

C++(OpenCV)实现MATLAB的edge(I, “sobel“)边缘检测

文章目录 方案分析具体代码实现关键步骤说明注意事项 为了实现类似于MATLAB的edge(I, "sobel")函数的C代码,我们需要复现其完整的边缘检测流程,包括梯度计算、非极大值抑制和阈值处理。以下是具体的方案及代码实现: 方案分析 图像…...

uniapp通过uni.addInterceptor实现路由拦截

注:此拦截不能首次拦截路由跳转的方法(switchTab, navigateTo, reLaunch, redirectTo),拦截request请求api可以 1. app.vue 代码 import { onLaunch} from dcloudio/uni-appimport permission from ./utils/permissiononLaunch(…...

vue2.x Echart label根据数据长度选择不同的间隔显示

折线图需要在各个点上方展示数据,但是数据数字的位数可能达到5~8位,需要根据密度进行间隔展示。例如,如果数据长度小于7,则每一项都展示,如果在7~10之间,2位展示一项,如果大于10,那么…...

Wifi密码查看软件V1.0

⭐本软件用于查看电脑连接过所有WiFi密码,不具备破解功能。 可在忘记WiFi密码或他人输入密码自己不知道的情况下使用。 ⭐⭐为便于快速分享,加入双击【密码】列可将WIFI密码复制在粘贴板。 ⭐⭐⭐双击【名称】列可生成用于手机连接的二维码进行显示&…...