动手学深度学习55 循环神经网络 RNN 的实现

动手学深度学习55 循环神经网络 RNN 的实现

  • 从零开始实现
  • 简洁实现
  • QA

课件:https://zh-v2.d2l.ai/chapter_recurrent-neural-networks/rnn-scratch.html

从零开始实现

%matplotlib inline
import math
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l
import collections
import re
import randomd2l.DATA_HUB['time_machine'] = (d2l.DATA_URL + 'timemachine.txt', '090b5e7e70c295757f55df93cb0a180b9691891a')def read_time_machine():"""将时间机器数据集加载到文本行的列表中"""with open(d2l.download('time_machine'), 'r') as f:lines = f.readlines()# 替换掉所有符号 转为小写return [re.sub('[^A-Za-z]+', ' ', line).strip().lower() for line in lines]lines = read_time_machine()
print(f'# 文本总行数: {len(lines)}')
print(lines[0])
print(lines[10])def tokenize(lines, token='word'):"""将文本行拆分为单词或字符词元"""if token == 'word':  # 一个单词return [line.split() for line in lines]elif token == 'char':  # 一个字符  字母return [list(line) for line in lines]else:print('错误:未知词元类型:' + token)tokens = tokenize(lines)
for i in range(11):print(tokens[i])class Vocab:"""文本词表"""def __init__(self, tokens=None, min_freq=0, reserved_tokens=None):if tokens is None:tokens = []if reserved_tokens is None:reserved_tokens = []# 按出现频率排序counter = count_corpus(tokens)self._token_freqs = sorted(counter.items(), key=lambda x: x[1], reverse=True)# 未知词元的索引为0self.idx_to_token = ['<unk>'] + reserved_tokensself.token_to_idx = {token: idx for idx, token in enumerate(self.idx_to_token)}for token, freq in self._token_freqs:if freq < min_freq:breakif token not in self.token_to_idx:self.idx_to_token.append(token)self.token_to_idx[token] = len(self.idx_to_token) - 1def __len__(self):return len(self.idx_to_token)def __getitem__(self, tokens):if not isinstance(tokens, (list, tuple)):return self.token_to_idx.get(tokens, self.unk)return [self.__getitem__(token) for token in tokens]def to_tokens(self, indices):if not isinstance(indices, (list, tuple)):return self.idx_to_token[indices]return [self.idx_to_token[index] for index in indices]@propertydef unk(self):  # 未知词元的索引为0return 0@propertydef token_freqs(self):return self._token_freqsdef count_corpus(tokens):"""统计词元的频率"""# 这里的tokens是1D列表或2D列表if len(tokens) == 0 or isinstance(tokens[0], list):# 将词元列表展平成一个列表tokens = [token for line in tokens for token in line]return collections.Counter(tokens)vocab = Vocab(tokens)
print(list(vocab.token_to_idx.items())[:10])for i in [0, 10]:print('文本:', tokens[i])print('索引:', vocab[tokens[i]])def load_corpus_time_machine(max_tokens=-1):"""返回时光机器数据集的词元索引列表和词表"""lines = read_time_machine()tokens = tokenize(lines, 'char') # 按字符-字母分vocab = Vocab(tokens)# 因为时光机器数据集中的每个文本行不一定是一个句子或一个段落,# 所以将所有文本行展平到一个列表中corpus = [vocab[token] for line in tokens for token in line]if max_tokens > 0:corpus = corpus[:max_tokens]return corpus, vocabdef seq_data_iter_random(corpus, batch_size, num_steps):"""使用随机抽样生成一个小批量子序列"""# 从随机偏移量开始对序列进行分区,随机范围包括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)my_seq = list(range(35))
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):"""使用顺序分区生成一个小批量子序列"""# 从随机偏移量开始划分序列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, Yfor 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):if use_random_iter:self.data_iter_fn = seq_data_iter_randomelse:self.data_iter_fn = seq_data_iter_sequentialself.corpus, self.vocab = 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)def load_data_time_machine(batch_size, num_steps, use_random_iter=False, max_tokens=10000):"""返回时光机器数据集的迭代器和词表"""data_iter = SeqDataLoader(batch_size, num_steps, use_random_iter, max_tokens)return data_iter, data_iter.vocab# num_steps 每一次取多长的序列 时间T
batch_size, num_steps = 32, 35
# vocab 把整数index转成对应的词
train_iter, vocab = load_data_time_machine(batch_size, num_steps)
# 独热编码 [0, 2]表示下标-类别 给一个下标,返回一个向量表示
F.one_hot(torch.tensor([0, 2]), len(vocab))# 小批量数据形状是二维张量: (批量大小,时间步数)--三维
X = torch.arange(10).reshape((2, 5))
# 转置好处:把时间提前,每次访问x_t都是一个连续的序列
F.one_hot(X.T, 28).shape# 初始化循环神经网络的模型参数 关键函数
def get_params(vocab_size, num_hiddens, device):# 多分类,类别个数字典所有字,可以是任意一个字num_inputs = num_outputs = vocab_sizedef normal(shape):# 最简单初始化 均值为0 方差为1 *0.01 把方差变成0.01return torch.randn(size=shape, device=device) * 0.01# 隐藏层参数# 输入x映射到隐藏层的大小W_xh = normal((num_inputs, num_hiddens))# 上一个时刻隐藏层变量映射到下一个时刻隐藏层变量W_hh = normal((num_hiddens, num_hiddens))# 隐藏元的b 偏移b_h = torch.zeros(num_hiddens, device=device)# 输出层参数# 隐藏变量到输出的映射W_hq = normal((num_hiddens, num_outputs))# 输出层的b 偏移b_q = torch.zeros(num_outputs, device=device)# 附加梯度params = [W_xh, W_hh, b_h, W_hq, b_q]for param in params:# 参数列表需要计算梯度param.requires_grad_(True)return params
# 在初始化时返回隐藏状态 
def init_rnn_state(batch_size, num_hiddens, device):# 写成tuple LSTM有两个值,为了统一化。return (torch.zeros((batch_size, num_hiddens), device=device), )# 给定一个小批量 计算所有时间步【x0-xt】 得到输出?
# 定义了如何在一个时间步内计算隐藏状态和输出
def rnn(inputs, state, params):# inputs的形状:(时间步数量,批量大小,词表大小)# state 初始隐藏状态W_xh, W_hh, b_h, W_hq, b_q = paramsH, = stateoutputs = []# inputs 3Dtensor 时间的步数,批量大小,词表大小# X的形状:(批量大小,词表大小)for X in inputs:# H 前一个时间的隐藏状态 先更新隐藏状态H = torch.tanh(torch.mm(X, W_xh) + torch.mm(H, W_hh) + b_h)# 当前时刻的预测 预测下一个时刻的字符是谁Y = torch.mm(H, W_hq) + b_q outputs.append(Y)# n个矩阵按列拼起来,列数不变,行数是批量大小*时间步数return torch.cat(outputs, dim=0), (H,)# 包装rnn函数
class RNNModelScratch:"""从零开始实现的循环神经网络模型"""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_fn# 重写 __call__ 或者 写forward函数都行def __call__(self, X, state):# x load的数据集 (批量大小,时间步数) onehot 整型变成浮点型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)
print(Y.shape, len(new_state), new_state[0].shape)# 预测
def predict_ch8(prefix, num_preds, net, vocab, device):  """prefix: 给定句子的开头num_preds: 预测多少词在prefix后面生成新字符"""state = net.begin_state(batch_size=1, device=device)outputs = [vocab[prefix[0]]]get_input = lambda: torch.tensor([outputs[-1]], device=device).reshape((1, 1))for y in prefix[1:]:  # 预热期_, state = net(get_input(), state)outputs.append(vocab[y])for _ in range(num_preds):  # 预测num_preds步y, state = net(get_input(), state)# 多分类 拿出最大概率的索引outputs.append(int(y.argmax(dim=1).reshape(1)))# token转成字符串拼接输出return ''.join([vocab.idx_to_token[i] for i in outputs])predict_ch8('time traveller ', 10, net, vocab, d2l.try_gpu())# 辅助函数 rnn 梯度乘法太多 容易梯度爆炸
def grad_clipping(net, theta):"""裁剪梯度"""# 拿出所有参与训练层的参数if isinstance(net, nn.Module):params = [p for p in net.parameters() if p.requires_grad]else:params = net.params# 把所有层的梯度拉成一个长向量,计算L2norm = torch.sqrt(sum(torch.sum((p.grad ** 2)) for p in params))# 梯度太大做映射投影 梯度小不做处理if norm > theta:for param in params:param.grad[:] *= theta / norm# 训练一个epoch
def train_epoch_ch8(net, train_iter, loss, updater, device, use_random_iter):"""训练网络一个迭代周期(定义见第8章)use_random_iter 随机iterate 下一个batch的样本和上一个batch的样本没有关系  不随机的话 两个batch的样本是相邻的"""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是个张量# detach_()不改变state的值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())# crossentropy 交叉熵 --> 困惑度 做个指数return math.exp(metric[0] / metric[1]), metric[1] / timer.stop()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'))num_epochs, lr = 500, 1
# train_ch8(net, train_iter, vocab, lr, num_epochs, d2l.try_gpu())train_ch8(net, train_iter, vocab, lr, num_epochs, d2l.try_gpu(), use_random_iter=True)

在这里插入图片描述
困惑度 1.1, 91434.3 词元/秒 cuda:0
time travelleryou can show black is white by argument said filby
travelleryou can show black is white by argument said filby
在这里插入图片描述
使用随机iter
在这里插入图片描述

简洁实现

import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l# 加载数据
batch_size, num_steps = 32, 35
train_iter, vocab = load_data_time_machine(batch_size, num_steps)num_hiddens = 256
rnn_layer = nn.RNN(len(vocab), num_hiddens)# 初始化隐藏状态
state = torch.zeros((1, batch_size, num_hiddens))
print(state.shape)# 初始化X Y还是3D
X = torch.rand(size=(num_steps, batch_size, len(vocab)))
Y, state_new = rnn_layer(X, state)
print(Y.shape, state_new.shape)class RNNModel(nn.Module):"""循环神经网络模型"""def __init__(self, rnn_layer, vocab_size, **kwargs):super(RNNModel, self).__init__(**kwargs)self.rnn = rnn_layerself.vocab_size = vocab_sizeself.num_hiddens = self.rnn.hidden_size# 如果RNN是双向的(之后将介绍),num_directions应该是2,否则应该是1if not self.rnn.bidirectional:self.num_directions = 1# 构造自己的输出层self.linear = nn.Linear(self.num_hiddens, self.vocab_size)else:self.num_directions = 2self.linear = nn.Linear(self.num_hiddens * 2, self.vocab_size)def forward(self, inputs, state):X = F.one_hot(inputs.T.long(), self.vocab_size)X = X.to(torch.float32)# Y 中间隐藏层的Y 时间步数,batch大小,隐藏层大小Y, state = self.rnn(X, state)# 全连接层首先将Y的形状改为(时间步数*批量大小,隐藏单元数)# 它的输出形状是(时间步数*批量大小,词表大小)。output = self.linear(Y.reshape((-1, Y.shape[-1])))return output, statedef begin_state(self, device, batch_size=1):if not isinstance(self.rnn, nn.LSTM):# nn.GRU以张量作为隐状态return  torch.zeros((self.num_directions * self.rnn.num_layers, batch_size, self.num_hiddens),device=device)else:# nn.LSTM以元组作为隐状态return (torch.zeros((self.num_directions * self.rnn.num_layers,batch_size, self.num_hiddens), device=device),torch.zeros((self.num_directions * self.rnn.num_layers, batch_size, self.num_hiddens), device=device))# 初始化
device = d2l.try_gpu()
net = RNNModel(rnn_layer, vocab_size=len(vocab))
net = net.to(device)
predict_ch8('time traveller', 10, net, vocab, device)num_epochs, lr = 500, 1
train_ch8(net, train_iter, vocab, lr, num_epochs, device)# 框架实现 把多个小矩阵乘法换成大矩阵乘法

在这里插入图片描述

QA

1 num_steps 输入到小批量里面面样本句子的长度。
2 torchserve 开始使用java编写的, triton框架。
3 转置是为了怎么取数据方便。
4 dim=0 在0维度堆积 一列多行的结果
5 normal 初始化权重
6 为什么是批量大小乘以时间长度? 每个批量每个样本要做t次分类【任意时间点都要做一次分类】,所以要乘。
在这里插入图片描述
7 不会预测特别长的东西。append没有模型内存爆炸的概率高。
8 H每个时间点都会更新一次。?
9 2批量大小
10 是
11 batch_size 和 time的区别? time是样本句子的长度,和数据相关; batch_sizi 批量大小。

在这里插入图片描述
12 prefix–label。预测的时候不要更新模型
13 依赖长度–T
14 单个时间点是一个矩阵–站点数。rnn做气象预测。
15 num_step 是计算多少次 不是隐藏层数

在这里插入图片描述
16 每一个帧用cnn抽特征–向量,不需要one-hot. rnn不能处理特别长的序列
17 梯度计算。不做detach会记住前面计算的东西,误差反传会多算东西。只关注当前计算的东西。
18 rnn不能做超长序列【超过100就难用了】。一般随机取。
19 随机取对模型好,不容易overfitting ; 第二rnn做不了超长序列
20 可以做。不去掉标点等。
在这里插入图片描述
21 字符预测起来比较简单,输出是28voab,分类数少。
22 定义了两个函数 样本batch随机取和batch相连。
23 h-长为256的向量,很长很难计算。变长类似很长的mlp,容易过拟合。
25 工具成熟 发展没有特别快
26 根据频率采样。把高频词概率压一压–低采样。
27 端侧–主要车上,未来家里所有深度学习都跑在车上–家庭超级计算机。
28 车内–人互动、娱乐–未来; 车外-自动驾驶

在这里插入图片描述
在这里插入图片描述

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

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

相关文章

数据结构篇

7.查找 查找效率 顺序查找&#xff0c;折半查找和分块查找 折半查找 分块查找 二插排序树 删除操作 二叉排序树前驱&#xff1a;从该节点向左&#xff0c;一路找到他的最右节点&#xff1b; 二叉排序树后继&#xff1a;从该节点向右&#xff0c;一路找到他的最左节点&#x…

1. Vue3入门

文章目录 使用create-vue创建项目关键文件<script setup>语法糖组合式API - reactive和ref函数组合式API - computed组合式API - watch组合式API - 生命周期函数组合式API - 父子通信组合式API - 模版引用组合式API - provide和inject综合案例 使用create-vue创建项目 n…

DataX 本地调试配置

简要说明 根据自己的开发需求&#xff0c;完成了reader、writer、transformer开发后&#xff0c;在ide内通过Engine入口&#xff0c;调试自己的插件和job的json。 前置条件 已在系统安装了datax&#xff0c;本例子是在windows环境下&#xff0c;安装包地址https://github.co…

<数据集>玉米地杂草识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;9900张 标注数量(xml文件个数)&#xff1a;9900 标注数量(txt文件个数)&#xff1a;9900 标注类别数&#xff1a;2 标注类别名称&#xff1a;[Maize, Weed] 序号类别名称图片数框数1Maize8439125142Weed959231048…

【Linux网络】应用层协议:HTTP 与 HTTPS

本篇博客整理了 TCP/IP 分层模型中应用层的 HTTP 协议和 HTTPS协议&#xff0c;旨在让读者更加深入理解网络协议栈的设计和网络编程。 目录 一、协议是什么 1&#xff09;结构化数据的传输 2&#xff09;序列化和反序列化 补&#xff09;网络版计算器 .1- 协议定制 .2- …

CSS(三)——CSS 背景

CSS 背景 CSS 背景属性用于定义HTML元素的背景。 CSS 背景属性 Property描述background简写属性&#xff0c;作用是将背景属性设置在一个声明中。background-attachment背景图像是否固定或者随着页面的其余部分滚动。background-color设置元素的背景颜色。background-image把…

数据结构系列-插入排序和希尔排序

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 排序的概念 常见的排序算法&#xff1a; 插入排序 直接插入排序是一种简单的插入排序法&#xff0c;其基本思想是&#xff1a; 把待排序的记录按其关键码值的大小逐个插入到…

API 技术开发分享:连接电商平台数据获取的桥梁

在当今数字化的时代&#xff0c;API&#xff08;Application Programming Interface&#xff0c;应用程序编程接口&#xff09;技术成为了实现不同系统之间通信和数据交换的关键。它就像是一座无形的桥梁&#xff0c;使得各种应用能够相互协作&#xff0c;共享资源&#xff0c;…

react.16+

1、函数式组件 在vite脚手架中执行&#xff1a; app.jsx: import { useState } from react import reactLogo from ./assets/react.svg import viteLogo from /vite.svg import ./App.cssfunction App() {console.log(this)return <h2>我是函数式组件</h2> }exp…

请你谈谈:vue的渲染机制(render)- 1 原理讲解

Vue 是如何将一份模板转换为真实的 DOM 节点的&#xff0c;又是如何高效地更新这些节点的呢&#xff1f;我们接下来就将尝试通过深入研究 Vue 的内部渲染机制来解释这些问题。 1 虚拟 DOM <template><div id"app">this is son component</div> &…

《javaEE篇》--阻塞队列详解

阻塞队列 阻塞队列概述 阻塞队列也是一种队列&#xff0c;和普通队列一样遵循先进先出的原则&#xff0c;但是阻塞队列相较于普通队列多了两项功能阻塞添加和阻塞移除&#xff0c;使得阻塞队列成为一种线程安全的数据结构 阻塞添加&#xff1a;当队列满的时候继续入队就会阻…

UE4 UnrealPak加密功能(配置AES encrypt key)

本文的重点在于如何使用UnrealPak的加密功能&#xff0c;以及相关的UE4源代码学习。本文参考了&#xff1a;https://www.cnblogs.com/shiroe/p/14803859.html 。 设置密钥 在编辑、项目设置中找到下面栏目&#xff0c;并点击“生成新的加密密钥”&#xff0c;就可以为Unreal P…

unity2D游戏开发10生命条脚本

HitPoints 在ScriptableObjects文件夹中创建新的脚本,叫HitPoint using System.Collections; using System.Collections.Generic; using UnityEngine;//创建条目,方便轻松创建HitPoints的实例 [CreateAssetMenu(menuName ="HitPoints")] public class HitPoints :…

锅总介绍CNCF主要目标、全景图及发展历史

一、CNCF简介 云原生计算基金会&#xff08;Cloud Native Computing Foundation&#xff0c;简称 CNCF&#xff09;是一个成立于 2015 年的非营利性组织&#xff0c;隶属于 Linux 基金会。CNCF 的主要目标是通过开源软件推动云原生计算技术的发展和普及&#xff0c;帮助企业更…

四、使用renren-generator生成基本代码

1、打开generator.properties配置文件&#xff0c;修改配置 主要修改包名、模块名、前缀信息 2、修改application.yml配置文件中的数据库信息 3、启动项目 直接访问代码生成器 http://localhost/#generator选择表&#xff0c;点击生成代码即可

怎么使用github上传XXX内所有文件

要将 目录中的所有文件上传到 GitHub&#xff0c;你可以按照以下步骤进行&#xff1a; 创建一个新的 GitHub 仓库 登录到你的 GitHub 账户。 点击右上角的加号&#xff08;&#xff09;&#xff0c;选择 “New repository”。 输入仓库名称&#xff08;例如&#xff1a;202407…

滑动窗口练习6-找到字符串中所有字母异位词

题目链接&#xff1a;**. - 力扣&#xff08;LeetCode&#xff09;** 题目描述&#xff1a; 给定两个字符串 s 和 p&#xff0c;找到 s 中所有 p 的 异位词 的子串&#xff0c;返回这些子串的起始索引。不考虑答案输出的顺序。 异位词 指由相同字母重排列形成的字符串&#…

《程序猿入职必会(6) · 返回结果统一封装》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

Profinet从站转TCP/IP协议转化网关(功能与配置)

如何将Profinet和TCP/IP网络连接通讯起来呢?近来几天有几个朋友问到这个问题&#xff0c;那么作者在这里统一说明一下。其实有一个不错的设备产品可以很轻易地解决这个问题&#xff0c;名为JM-DNT-PN。接下来作者就从该设备的功能及配置详细说明一下。 一&#xff0c;设备主要…

el-table合计行更新问题

说明&#xff1a;在使用el-table自带的底部合计功能时&#xff0c;初始界面不会显示合计内容 解决方案&#xff1a;使用 doLayout()方法 updated() {this.$nextTick(() > {this.$refs[inventorySumTable].doLayout();});},完整代码&#xff1a; // show-summary&#xff1a…