NLP入门——RNN、LSTM模型的搭建、训练与预测

news2024/11/27 11:18:02

在卷积语言模型建模时,我们选取上下文长度ctx_len进行训练,预测时选取句子的最后ctx_len个分词做预测,这样句子的前0~seql-1-ctx_len个词对于预测没有任何帮助,这对于语言处理来说显然是不利的。
在词袋语言模型建模时,我们舍弃ctx_len的概念,利用前缀和,用前seql-1个词作为训练数据,后seql-1个词作为标签训练。最终用这句话所有的分词求前缀和做预测,这样句子中所有的分词都参与了预测,是更准确的。
但是,词袋语言模型没有考虑到分词的顺序带来的影响,例如:0 1 2 -> 3,将0 1 2装入词袋、张量求前缀和的结果和 0 2 1、 1 0 2、 1 2 0等等没有区别,没有办法区分分词的顺序。

在此基础上,如果考虑分词间的前后次序对预测的影响,有:
h ( x t ) = C e l l ( h ( x ( t − 1 ) + x t ) h(x_t) = Cell(h(x(t-1) + x_t) h(xt)=Cell(h(x(t1)+xt)
这样类似于二叉树结构顺序输入的模型,输入的不同次序带来的结果也不同。
在这里插入图片描述
如上图所示的二叉树结构,输入X_0 、X_1和X_2次序的不同,对于每层的影响也不同。这样就有效的保留了原句中分词间次序对于预测的影响。

RNN模型的搭建

#RNNLM.py
#encoding: utf-8
import torch
from torch import nn

class RNNCell(nn.Module):
    def __init__(self, isize, hsize, osize, dropout, **kwargs):
    
        super(RNNCell, self,).__init__()   ##调用父类的初始化函数
        self.net = nn.Sequential(nn.Linear(isize + osize, hsize),
        nn.ReLU(inplace=True), #设置relu激活函数,inplace=True在原始张量上进行
        nn.Dropout(p=dropout, inplace=False),#设置丢弃率防止过拟合,同时创建一个新的张量
        nn.Linear(hsize, osize, bias=False))       
    
    # x_t:(bsize, isize)
    def forward(self, x_t, h_p): # x_t为当前词向量,h_p是h_x(t-1)即上一步的结果
        
        return self.net(torch.cat([h_p, x_t], dim=-1))
    
    
    
class RNNLayer(nn.Module):
    def __init__(self, isize, hsize, dropout,norm_residual=True, **kwargs):
        super(RNNLayer, self,).__init__()   ##调用父类的初始化函数
        self.init_hx = nn.Parameter(torch.zeros(1, isize)) #h_0即最初初始化状态
        #初始化时第一维加1维,便于后续的拼接
        self.cell = RNNCell(isize, hsize, isize, dropout)
        self.drop = nn.Dropout(p=dropout, inplace=True)
        self.normer = nn.LayerNorm(isize) #做归一化
        self.norm_residual = norm_residual #设置变量存储做判断
        
    # input: (bsize, seql-1, isize)
    def forward(self, input, hx=None):#hx是None说明在训练,hx不是None是Tensor说明在解码
                                      #解码时候就用前一步的hx隐状态
        
        _ = self.normer(input) #稳定之后的结果 
        bsize = input.size(0)   #存一下bsize
        _hx = hx if isinstance(hx, torch.Tensor) else self.init_hx.expand(bsize, -1)
        #如果hx已经是一个张量不是None说明已开始计算,否则用初始化的值
        #expand函数将init_hx:(1,isize) -> (bsize, isize),设置参数为-1表示大小保留原状
        rs = []
        for xu in input.unbind(1): #将input在第一维展开 xu:(bsize, isize)
            _hx = self.cell(xu, _hx) #xu为新分词,hx为前一步隐状态
            rs.append(_hx)
            
        rs = (_ if self.norm_residual else input) + self.drop(torch.stack(rs, dim=1))
        #将rs在指定维度拼起来:rs:(bsize, seq-1, isize)
        #如果参数初始化做的好,就用LayerNorm后的值,否则用原始值
        if hx is None:
            return rs
        else:
            return (rs, _hx)   

class NNLM(nn.Module):
    
    def __init__(self, vcb_size, isize, hsize, dropout,
    nlayer, bindemb=True, **kwargs): #有多少个词,就分多少类,类别数为vcb_size
        super(NNLM, self).__init__()
        self.emb = nn.Embedding(vcb_size, isize,
        padding_idx=0)                #<pad>的索引为0
        #self.comp = nn.Linear(ctx_len * isize, isize, bias=False) #将4个词的向量降维为isize
        self.drop = nn.Dropout(p=dropout, inplace=True) #embedding后dropout
        self.nets = nn.Sequential(*[RNNLayer(isize, hsize, dropout)
        for _ in range(nlayer)])
        self.classifier = nn.Linear(isize, vcb_size)
        if bindemb:
            self.classifier.weight = self.emb.weight#将emb的权重赋给分类器
        self.normer = nn.LayerNorm(isize)
        self.out_normer = nn.LayerNorm(isize)
                
    # input: (bsize, seql-1) 句数,句长-1 由于最后一个词是预测不作为输入
    def forward(self, input):
        
        out = self.emb(input)
        # out: (bsize, seql-1, isize)   
        out = self.drop(out) 
        out = self.normer(out) #使用归一化,使模长均匀
        out = self.out_normer(self.nets(out))
        return self.classifier(out) #分类产生参数

RNN模型的训练

#RNNtrain.py
#encoding: utf-8

import torch
from torch import nn
from RNNLM import NNLM #导入模型
from h5py import File as h5File #读训练数据
from math import sqrt
from random import shuffle #使输入数据乱序,使模型更均衡
from lrsch import SqrtDecayLR
from tqdm import tqdm

train_data = "train.h5"#之前已经张量转文本的h5文件
isize = 64              
hsize = isize * 2       #设置初始参数
dropout = 0.3           #设置丢弃率
nlayer = 4              #设置层数
gpu_id = -1             #设置是否使用gpu
lr = 1e-3               #设置初始学习率
max_run = 8           #设置训练轮数

nreport = 5000          #每训练5000个batch打印一次
tokens_optm = 25000     #设置更新参数的词数阈值

def init_model_parameters(modin): #初始化模型参数
    with torch.no_grad():         #不是训练不用求导
        for para in modin.parameters():
            if para.dim() > 1:          #若维度大于1,说明是权重参数
                _ = 1.0 / sqrt(para.size(-1))
                para.uniform_(-_,_)     #均匀分布初始化
        for _m in modin.modules():      #遍历所有小模型
            if isinstance(_m, nn.Linear):#如果小模型是linear类型
                if _m.bias is not None: #初始化bias
                    _m.bias.zero_()
                elif isinstance(_m, nn.LayerNorm):#初始化LayerNorm参数
                    _m.weight.fill_(1.0)
                    _m.bias.zero_()
                elif isinstance(_m, nn.Embedding): #如果是嵌入层则进入初始化权重矩阵,将<pad>的向量初始化为零向量
                    if _m.padding_idx >= 0:
                        _m.weight[_m.padding_idx].zero_()
    return modin

def train(train_data, tl, model, lossf, optm, cuda_device,
 nreport=nreport, tokens_optm=tokens_optm):#nreport为每训练一部分词打一次epoch
    
    model.train() #设置模型在训练的模式
    src_grp = train_data["src"] #从输入数据中取出句子
    _l = 0.0  #_l用来存当前loss
    _t = 0    #_t用来存句子数
    _lb = 0.0
    _tb = 0
    _tom = 0
    
    for _i, _id in tqdm(enumerate(tl, 1)):
        seq_batch = torch.from_numpy(src_grp[_id][()])
        #seq_batch:[bsize, seql]
        _seqlen = seq_batch.size(-1)  #取出每个batch的句长

        if cuda_device is not None:
            seq_batch = seq_batch.to(cuda_device, non_blocking=True)
             #将数据放在同一gpu上
        seq_batch = seq_batch.long()   #数据转换为long类型
        seq_i = seq_batch.narrow(1, 0, _seqlen - 1) #训练数据读取前seql-1的数据
        #seq_i:[bsize, seql-1]
        seq_o = seq_batch.narrow(1, 1, _seqlen - 1) #预测数据读取后seql-1的数据做标签
        #seq_o:[bsize, seql-1]
        
        
        out = model(seq_i)          #获得模型结果
        #out: {bsize, seql-1, vcb_size} vcb_size即预测类别数
        
        loss = lossf(out.view(-1, out.size(-1)), seq_o.contiguous().view(-1)) 
        #转换out维度为[bsize*(seql-1),vcb_size],seq_o:[bsize*(seql-1)]
        _lossv = loss.item()
        _l += _lossv        #整个训练集的loss
        _lb += _lossv       #每个batch的loss
        _n = seq_o.ne(0).int().sum().item() #seq_o中不是<pad>的位置的数量
        _t += _n            #整个训练集的分词数
        _tb += _n           #每个batch的分词数
        _tom += _n
        loss.backward()                 #反向传播求导
        if _tom > tokens_optm:          #当词数大于时更新参数
            optm.step()                     #参数的更新
            optm.zero_grad(set_to_none=True)#参数更新后清空梯度
            _tom = 0
        if _i % nreport == 0:   #每训练5000个batch打印一次
            print("Average loss over %d tokens: %.2f"%(_tb, _lb/_tb))
            _lb = 0.0
            _tb = 0
            save_model(model, "checkpoint.rnn.pt") #暂存检查点模型
            
        
    return _l / _t #返回总的loss


def save_model(modin, fname): #保存模型所有内容 权重、偏移、优化
    
    torch.save({name: para.cpu() for name, para in
    model.named_parameters()}, fname)

t_data = h5File(train_data, "r")#以读的方式打开训练数据

vcb_size = t_data["nword"][()].tolist()[0] #将返回的numpy的ndarray转为list
#在我们的h5文件中存储了nword: 总词数

model = NNLM(vcb_size, isize, hsize, dropout, nlayer)
model = init_model_parameters(model) #在cpu上初始化模型
lossf = nn.CrossEntropyLoss(reduction='sum', ignore_index=0,
label_smoothing=0.1)
#设置ignore_index=0,即忽略<pad>的影响

if (gpu_id >= 0) and torch.cuda.is_available(): #如果使用gpu且设备支持cuda
    cuda_device = torch.device("cuda", gpu_id)  #配置gpu
    torch.set_default_device(cuda_device)
else:
    cuda_device = None

if cuda_device is not None:                     #如果要用gpu
    model.to(cuda_device)                       #将模型和损失函数放在gpu上
    lossf.to(cuda_device)

optm = torch.optim.Adam(model.parameters(), lr=lr, 
betas=(0.9, 0.98), eps=1e-08)
#使用model.parameters()返回模型所有参数,
lrm = SqrtDecayLR(optm, lr) #将优化器和初始学习率传入

tl = [str(_) for _ in range(t_data["ndata"][()].item())] #获得字符串构成的训练数据的list

#save_model(model, "eva.rnn.pt")
for i in range(1, max_run + 1):
    shuffle(tl)         #使数据乱序
    _tloss = train(t_data, tl, model, lossf, optm,
    cuda_device)  #获取每轮训练的损失
    print("Epoch %d: train loss %.2f"%(i, _tloss)) #打印日志
    save_model(model, "eva.rnn.pt")
    lrm.step() #每轮训练后更新学习率
    
t_data.close()

在命令行输入:

:~/nlp/lm$ python RNNtrain.py 

RNN模型的解码与预测

模型的解码

#RNNLM.py
#encoding: utf-8
 # input: (bsize, seql)
    def decode(self, input, maxlen=50): #设置最多生成50个分词
        
        rs = [input]
        bsize =input.size(0)
     
        hxd = {} #存每一层产生的hx
        out = self.normer(self.drop(self.emb(input)))
        # out:(bsize, seql, isize)
        for i, layer in enumerate(self.nets):
            out, hxd[i] = layer(out, hx=hxd.get(i, "init")) #手动遍历每一层
            #得到了每层的结果rs以及隐状态hxd[i]
        # out:(bsize, seql, isize)
        out = self.classifier(self.out_normer(out.narrow(
        1, out.size(1) - 1, 1))).argmax(-1)
        # narrow:(bsize, 1, isize) -> (bsize, 1, vcb_size) 
        # -> (bsize, 1)
        rs.append(out)
        done_trans = out.squeeze(1).eq(2)   #记录是否完成生成
        # done_trans:(bsize)
        
        if not done_trans.all().item():
            for i in range(maxlen - 1): #已经生成一个词并添加到rs中了
                
                out = self.normer(self.drop(self.emb(out)))
                # out:(bsize, 1, isize)
                for _, layer in enumerate(self.nets): #将前一层结果给下一层做输入
                    out, hxd[_] = layer(out, hx=hxd[_])
                    #将之前的隐状态输入生成下一层的隐状态
                
                out = self.classifier(self.out_normer(out)).argmax(-1) #最后的输出预测下一个分词
                # out:(bsize, 1, vcb_size) -> (bsize, 1)
                rs.append(out) #将新预测分词添加到结果

                done_trans |= out.squeeze(1).eq(2) 
                if done_trans.all().item(): #当全都为True,说明此batch中所有句子预测都为<eos>,即解码完成
                    break
        
        return torch.cat(rs, dim=1)

模型的预测

预测脚本与前面的词袋模型脚本基本一样,我们在命令行中输入:

:~/nlp/lm$ python RNNpredict.py test.h5 zh.vcb pred.txt checkpoint.rnn.pt 
:~/nlp/lm$ less pred.txt 

在这里插入图片描述
我们可以看到经过若干个epoch训练出的rnn模型对于文本的预测续写。

LSTM模型的搭建

在这里插入图片描述

但是,由于上图这样的结构,h_3是由h_2和X_2得出,而h_2是由h_1和X_1得出。那么对于h_3来说,X_2的影响就要比X_1大很多。于是在进行预测时,句末的分词对预测的影响就要大于句子中间或者开头的分词。
而在实际句子中,

回顾 安理会 主席 以 安理会 名义 在 1994 年 4 月 7 日 发表 的 声明 ( S / PRST / 1994 / 16 ) 和 在 1994 年 4 月 30 日 发表 的 声明 ( S / PRST / 1994 / 21 ) ,

上句中“声明”的预测很依赖于动词“发表”,这符合我们的模型的偏好即句末的分词影响更大。但是像本句句尾的“1994”是该年发表声明的时间,显然前面的“1994”对它影响更大,而不是句末的分词。
这就不符合我们模型的偏好,因此我们需要重新设计cell,使得模型的偏好更均衡,而不是句末的分词影响最大。

sigmoid激活函数

S i g m o i d Sigmoid Sigmoid 函数的公式如下所示,其可以将神经网络的输出映射到(0,1)之中,公式如下所示。
s i g m o i d ( x ) = 1 1 + e − x sigmoid(x)=\frac{1}{1+e^{-x}} sigmoid(x)=1+ex1
函数图像如下:
在这里插入图片描述
如图上图所示,当输入的值趋于正无穷或负无穷时,梯度会趋近零,神经网络学习不到特征,从而导致深度神经网络无法进行训练。

>>> import torch
>>> a=torch.randn(4,5)
>>> a.sigmoid()
tensor([[0.4421, 0.6359, 0.5781, 0.4000, 0.4741],
        [0.4456, 0.4710, 0.0643, 0.3690, 0.4005],
        [0.4287, 0.7616, 0.6746, 0.1963, 0.6259],
        [0.4268, 0.8297, 0.4883, 0.4696, 0.6682]])
>>> b=a.exp()
>>> b/(1+b)
tensor([[0.4421, 0.6359, 0.5781, 0.4000, 0.4741],
        [0.4456, 0.4710, 0.0643, 0.3690, 0.4005],
        [0.4287, 0.7616, 0.6746, 0.1963, 0.6259],
        [0.4268, 0.8297, 0.4883, 0.4696, 0.6682]])

我们利用sigmoid函数来使得学习的权重进行变化,通过0或1的变化有助于更新或忘记信息。
我们通过门来控制变化,要么是1则记住,要么是0则忘掉。因记忆能力有限,记住重要的,忘记无关紧要的。
LSTM的基本结构如下:

h t , c t = L S T M C e l l ( x t , h t − 1 , c t − 1 ) h_t, c_t = LSTMCell(x_t, h_{t-1}, c_{t-1}) htct=LSTMCell(xt,ht1,ct1)
h = x t ∣ h t − 1 _h = x_t | h_{t-1} h=xtht1
h i d d e n = a c t ( L i n e a r ( h ) ) hidden=act(Linear(_h)) hidden=act(Linear(h))
i g a t e = s i g m o i d ( L i n e a r ( h ) ) igate=sigmoid(Linear(_h)) igate=sigmoid(Linear(h))
o g a t e = s i g m o i d ( L i n e a r ( h ) ) ogate=sigmoid(Linear(_h)) ogate=sigmoid(Linear(h))
f g a t e = s i g m o i d ( L i n e a r ( h ) ) fgate=sigmoid(Linear(_h)) fgate=sigmoid(Linear(h))
c t = c t − 1 ∗ f g a t e + h i d d e n ∗ i g a t e c_t=c_{t-1}*fgate+hidden*igate ct=ct1fgate+hiddenigate
h t = c t ∗ o g a t e h_t=c_t*ogate ht=ctogate

#LSTMLM.py
#encoding: utf-8
import torch
from torch import nn

class LSTMCell(nn.Module):
    def __init__(self, isize, osize, dropout, **kwargs):
    
        super(LSTMCell, self,).__init__()   
        self.net = nn.Linear(isize + osize, 4 * osize) #4*osize后分块,分别得到hidden、igate、ogate和fgate
        self.drop = nn.Dropout(p=dropout, inplace=False)
        
    def forward(self, x_t, h_p):
        
        hx, cx = h_p
        out = self.net(torch.cat([hx, x_t], dim=-1))#将h_x和x_t向量拼接
    
        # out:(bsize, 4, osize)
        out = out.view(out.size(0), 4, -1)
        hidden = out.select(1, 0).relu() #取out第1维的第0个张量过relu激活函数
        igate, fgate, ogate = out.narrow(1, 1, 3).sigmoid().unbind(1) 
        #取out第1维的1,2,3个张量过sigmoid激活函数再按照第一维拆分
        #i/f/o gate:(bsize, 1, osize) hidden(bsize, 1, osize)
        cx = (cx * fgate).addcmul(hidden, igate) #cx*fgate+hidden*igate
        hx = cx * ogate
        
        return hx, cx #hx是本层输出,cx是向后传的cell
        
class LSTMLayer(nn.Module):
    def __init__(self, isize, dropout,norm_residual=True, **kwargs):
        super(LSTMLayer, self,).__init__()   
        self.init_hx = nn.Parameter(torch.zeros(1, isize))
        self.init_cx = nn.Parameter(torch.zeros(1, isize))
        self.cell = LSTMCell(isize, isize, dropout)
        self.drop = nn.Dropout(p=dropout, inplace=True)
        self.normer = nn.LayerNorm(isize) #做归一化
        self.norm_residual = norm_residual #设置变量存储做判断
        
    # input: (bsize, seql-1, isize)
    def forward(self, input, hx=None): #hx若不为None则为(hx, cx)
        
        _ = self.normer(input) #稳定之后的结果 
        bsize = input.size(0)
        _hx = hx if isinstance(hx, (tuple, list)) else (self.
        init_hx.expand(bsize, -1), self.init_cx.expand(bsize, -1))
        rs = []
        for xu in input.unbind(1):
            _hx = self.cell(xu, _hx)
            rs.append(_hx[0]) #只添加_hx中的hx
        rs = (_ if self.norm_residual else input) + self.drop(torch.stack(rs, dim=1))
        
        if hx is None:
            return rs
        else:
            return (rs, _hx)
        #如果参数初始化做的好,就用LayerNorm后的值,否则用原始值

class NNLM(nn.Module):
    
    def __init__(self, vcb_size, isize, hsize, dropout,
    nlayer, bindemb=True, **kwargs): #有多少个词,就分多少类,类别数为vcb_size
        super(NNLM, self).__init__()
        self.emb = nn.Embedding(vcb_size, isize,
        padding_idx=0)                #<pad>的索引为0
        #self.comp = nn.Linear(ctx_len * isize, isize, bias=False) #将4个词的向量降维为isize
        self.drop = nn.Dropout(p=dropout, inplace=True) #embedding后dropout
        self.nets = nn.Sequential(*[LSTMLayer(isize, dropout)
        for _ in range(nlayer)])
        self.classifier = nn.Linear(isize, vcb_size)
        if bindemb:
            self.classifier.weight = self.emb.weight#将emb的权重赋给分类器
        self.normer = nn.LayerNorm(isize)
        self.out_normer = nn.LayerNorm(isize)
        
        
    # input: (bsize, seql-1) 句数,句长-1 由于最后一个词是预测不作为输入
    def forward(self, input):
        
        out = self.emb(input)
        # out: (bsize, seql-1, isize)   
        out = self.drop(out) 
        out = self.normer(out) #使用归一化,使模长均匀
        out = self.out_normer(self.nets(out))
        return self.classifier(out) #分类产生参数
    
    # input: (bsize, seql)
    def decode(self, input, maxlen=50): #设置最多生成50个分词
        
        rs = [input]
        bsize =input.size(0)
     
        hxd = {}
        out = self.normer(self.drop(self.emb(input)))
        # out:(bsize, seql, isize)
        for i, layer in enumerate(self.nets):
            out, hxd[i] = layer(out, hx=hxd.get(i, "init"))
        out = self.classifier(self.out_normer(out.narrow(
        1, out.size(1) - 1, 1))).argmax(-1)
        # narrow:(bsize, 1, isize) -> (bsize, 1, vcb_size) 
        # -> (bsize, 1)
        rs.append(out)
        done_trans = out.squeeze(1).eq(2)   #记录是否完成生成
        # done_trans:(bsize)
        
        if not done_trans.all().item():
            for i in range(maxlen - 1):
                
                out = self.normer(self.drop(self.emb(out)))
                # out:(bsize, 1, isize)
                for _, layer in enumerate(self.nets):
                    out, hxd[_] = layer(out, hx=hxd[_])
                out = self.classifier(self.out_normer(out)).argmax(-1)
                # out:(bsize, 1, vcb_size) -> (bsize, 1)
                rs.append(out)

                done_trans |= out.squeeze(1).eq(2) 
                if done_trans.all().item(): #当全都为True,说明此batch中所有句子预测都为<eos>,即解码完成
                    break
        
        return torch.cat(rs, dim=1)

LSTM模型的预测

我们可以看出,LSTM的解码与RNN一样,只是在cell的设计上不同,训练脚本与预测脚本都是相同的,我们在命令行输入:

:~/nlp/lm$ python LSTMpredict.py test.h5 zh.vcb pred.txt checkpoint.lstm.pt 
:~/nlp/lm$ less pred.txt 

在这里插入图片描述

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

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

相关文章

Milvus 核心设计(5)--- scalar indexwork mechanism

目录 背景 Scalar index 简介 属性过滤 扫描数据段 相似性搜索 返回结果 举例说明 1. 属性过滤 2. 扫描数据段 3. 相似性搜索 实际应用中的考虑 Scalar Index 方式 Auto indexing Inverted indexing 背景 继续Milvus的很细设计&#xff0c;前面主要阐述了Milvu…

【排序算法】1.冒泡排序-C语言实现

冒泡排序&#xff08;Bubble Sort&#xff09;是最简单和最通用的排序方法&#xff0c;其基本思想是&#xff1a;在待排序的一组数中&#xff0c;将相邻的两个数进行比较&#xff0c;若前面的数比后面的数大就交换两数&#xff0c;否则不交换&#xff1b;如此下去&#xff0c;直…

C++ 入门基础:开启编程之旅

文章目录 引言一、C的第⼀个程序二、命名空间1、namespace2、namespace的定义 三、C输入 与 输出四、缺省参数五、函数重载六、引用1、引用的概念和定义2、引用的特性3、指针和引用的关系七、inline八、nullptr 引言 C 是一种高效、灵活且功能强大的编程语言&#xff0c;广泛应…

【java】力扣 合并两个有序数组

文章目录 题目链接题目描述代码第一种第二种 题目链接 88.合并两个有序数组 题目描述 代码 第一种 public void merge(int[] nums1, int m, int[] nums2, int n) {for(int i 0;i<n;i){nums1[mi] nums2[i];}Arrays.sort(nums1);}第二种 public void merge(int[] nums1,…

【数据结构】二叉树全攻略,从实现到应用详解

​ &#x1f48e;所属专栏&#xff1a;数据结构与算法学习 &#x1f48e; 欢迎大家互三&#xff1a;2的n次方_ ​ &#x1f341;1. 树形结构的介绍 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做…

JVM垃圾回收-----垃圾分类

一、垃圾分类定义 垃圾分类是JVM垃圾分类中的第一步&#xff0c;这一步将堆中的对象分为存活对象和垃圾对象两类。 在垃圾分类阶段&#xff0c;JVM会从一组根对象开始&#xff0c;通过对象之间的引用关系&#xff0c;遍历所有的对象&#xff0c;并将所有存活的对象进行标记。…

QT使用QPainter绘制多边形维度图

多边形统计维度图是一种用于展示多个维度的数据的图表。它通过将各个维度表示为图表中的多边形的边&#xff0c;根据数据的大小和比例来确定各个维度的长度。 一、简述 本示例实现六边形战力统计维度图&#xff0c;一种将六个维度的战力统计以六边形图形展示的方法。六个维度是…

leetcode-383.赎金信

题源 383.赎金信 题目描述 给你两个字符串&#xff1a;ransomNote 和 magazine &#xff0c;判断 ransomNote 能不能由 magazine 里面的字符构成。如果可以&#xff0c;返回 true &#xff1b;否则返回 false 。magazine 中的每个字符只能在 ransomNote 中使用一次。示例 1&…

MySQL(3)表的操作

目录 1. 表的操作; 2. 数据类型; 1. 表的操作: 1.1 创建表: 语法: create table 表名( 属性 类型 [comment ], 属性 类型 [comment ], 属性 类型 ) character set 字符集 collate 校验集 engine 存储引擎; 前面博客提到: MyISAM和InoDB这两个比较重要. 1.2 查看表…

Spring与设计模式实战之策略模式

Spring与设计模式实战之策略模式 引言 在现代软件开发中&#xff0c;设计模式是解决常见设计问题的有效工具。它们提供了经过验证的解决方案&#xff0c;帮助开发人员构建灵活、可扩展和可维护的系统。本文将探讨策略模式在Spring框架中的应用&#xff0c;并通过实际例子展示…

three.js领衔,10大基于webGL的JavaScript库。

Three.js的赫赫威名补多少&#xff0c;不了解的自行搜索或者翻看大宇之前的文章&#xff0c;除了three.js外&#xff0c;我想实现web3D效果还有其他库吗&#xff1f;答案是有的&#xff0c;而且还不少。 除了 Three.js&#xff0c;还有一些基于 WebGL 的库和框架&#xff0c;它…

动态环境下的激光slam论文列表

文章目录 Scan Context: Egocentric Spatial Descriptor for Place Recognition within 3D Point Cloud Map&#xff08;2018&#xff09;LIO-CSI: LiDAR inertial odometry with loop closure combined with semantic information&#xff08;2021&#xff09;Semantic Lidar-…

防火墙--双机热备

目录 双击热备作用 防火墙和路由器备份不同之处 如何连线 双机 热备 冷备 VRRP VGMP&#xff08;华为私有协议&#xff09; 场景解释 VGMP作用过程 主备的形成场景 接口故障的切换场景 整机故障 原主设备故障恢复的场景 如果没有开启抢占 如果开启了抢占 负载分…

网络原理(上)

前言&#x1f440;~ 上一章我们介绍了网络的一些基础知识&#xff0c;今天来讲解一下网络原理相关的知识点&#xff0c;分三篇进行阐述内容有点多​​​​​​​ 再谈协议分层 应用层 传输层&#xff08;重点&#xff09; UDP协议 TCP协议 TCP如何完成可靠传输&#xff…

在 PostgreSQL 里如何处理数据的归档和清理过程中的数据完整性验证?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01;&#x1f4da;领书&#xff1a;PostgreSQL 入门到精通.pdf 文章目录 在 PostgreSQL 里如何处理数据的归档和清理过程中的数据完整性验证 在 PostgreSQL 里如何处理数据的归…

3D数字孪生项目运行卡顿,来看看它要求的电脑配置。

有些小伙伴和我说&#xff0c;数字孪生项目运行卡顿&#xff0c;不知道啥原因&#xff0c;根源还是这类项目是浏览器渲染&#xff0c;对电脑配置要求很高。 运行3D数字孪生项目需要一台性能强大的电脑&#xff0c; 以下是一个推荐的配置清单&#xff1a; 1. 处理器&#xff1…

css实现每个小盒子占32%,超出就换行

代码 <div class"visitors"><visitor class"item" v-for"(user,index) in userArr" :key"user.id" :user"user" :index"index"></visitor></div><style lang"scss" scoped&…

Porfinet转DeviceNet主总线协议转换网关

产品功能 1. 远创智控YC-DNTM-PN型网关是Porfinet从转Devicenet主工业级Porfinet网关。‌这种网关设备允许将Porfinet网络中的设备连接到Devicenet网络中&#xff0c;‌从而实现不同工业通信协议之间的互操作性。‌这些网关设备通常具有两个以太网接口&#xff0c;‌分别用于连…

shell脚本-linux如何在脚本中远程到一台linux机器并执行命令

需求&#xff1a;我们需要从11.0.1.17远程到11.0.1.16上执行命令 实现&#xff1a; 1.让11.0.1.17 可以免密登录到11.0.1.16 [rootlocalhost ~]# ssh-keygen Generating public/private rsa key pair. Enter file in which to save the key (/root/.ssh/id_rsa): Created d…

Ubuntu 22.04.4 LTS (linux) 安装iftop 监控网卡流量 软件

1 安装iftop sudo apt update sudo apt-get install iftop 2 监控网卡 sudo iftop -i eth0 -n -p 界面最上面&#xff0c;显示的是类似刻度尺的刻度范围&#xff0c;显示流量图形的长条作标尺用的。 中间的< >这两个左右箭头&#xff0c;表示的是流量的进出方向.TX&…