MXNet的机器翻译实践《编码器-解码器(seq2seq)和注意力机制》

news2025/7/9 12:02:08

机器翻译就是将一种语言翻译成另外一种语言,输入和输出的长度都是不定长的,所以这里会主要介绍两种应用,编码器-解码器以及注意力机制。

编码器是用来分析输入序列,解码器用来生成输出序列。其中在训练时,我们会使用一些特殊符号来表示,<bos>表示序列开始(beginning of sequence),<eos>表示序列的终止(end of sequence),<unk>表示未知符,以及<pad>用于补充句子长度的填充符号。

编码器的作用是将一个不定长的输入序列变换成一个定长的背景变量c,并在该背景变量中编码输入序列信息。编码器可以使用循环神经网络,对于循环神经网络的描述可以查阅前期的两篇文章:

循环神经网络(RNN)之门控循环单元(GRU)

循环神经网络(RNN)之长短期记忆(LSTM)

解码器输出的条件概率是基于之前的输出序列和背景变量c,即

根据最大似然估计,我们可以最大化输出序列基于输入序列的条件概率:

这里的的输入序列的编码就是背景变量c,所以可以简化成:

然后我们就可以得到该输出序列的损失:

在模型训练中,所有输出序列损失的均值通常作为需要最小化的损失函数。在训练中我们也可以将标签序列(训练集的真实输出序列)在上一个时间步的标签作为解码器在当前时间步的输入,这叫做强制教学(teacher forcing)。

整理数据集

这里的数据集我们使用一个很小的法语-英语的句子对,名称是fr-en-small.txt的一个文本文档,里面是20条法语与英语的句子对,法语和英语之间使用制表符来隔开。内容如下(显示好像没有隔开了,这个自己使用Tab键隔开):

elle est vieille .she is old .
elle est tranquille .she is quiet .
elle a tort .she is wrong .
elle est canadienne .she is canadian .
elle est japonaise .she is japanese .
ils sont russes .they are russian .
ils se disputent .they are arguing .
ils regardent .they are watching .
ils sont acteurs .they are actors .
elles sont crevees .they are exhausted .
il est mon genre !he is my type !
il a des ennuis .he is in trouble .
c est mon frere .he is my brother .
c est mon oncle .he is my uncle .
il a environ mon age .he is about my age .
elles sont toutes deux bonnes .they are both good .
elle est bonne nageuse .she is a good swimmer .
c est une personne adorable .he is a lovable person .
il fait du velo .he is riding a bicycle .
ils sont de grands amis .they are great friends .

然后我们对这个数据集进行一些必要的整理,为上述的法语词和英语词分别创建词典。法语词的索引和英语词的索引相互独立。

import collections
import io
import math
from mxnet import autograd,gluon,init,nd
from mxnet.contrib import text
from mxnet.gluon import data as gdata,loss as gloss,nn,rnn

PAD,BOS,EOS='<pad>','<bos>','<eos>'

def process_one_seq(seq_tokens,all_tokens,all_seqs,max_seq_len):
    '''将一个序列中的所有词记录在all_tokens中'''
    all_tokens.extend(seq_tokens)
    #序列后面添加PAD直到序列长度变为max_seq_len
    seq_tokens+=[EOS]+[PAD]*(max_seq_len - len(seq_tokens)-1)
    all_seqs.append(seq_tokens)

def build_data(all_tokens,all_seqs):
    '''将上面的词构造词典,并将所有序列中的词变换为词索引后构造NDArray实例'''
    vocab=text.vocab.Vocabulary(collections.Counter(all_tokens),reserved_tokens=[PAD,BOS,EOS])
    indices=[vocab.to_indices(seq) for seq in all_seqs]
    return vocab,nd.array(indices)


def read_data(max_seq_len):
    in_tokens,out_tokens,in_seqs,out_seqs=[],[],[],[]
    with io.open('fr-en-small.txt') as f:
        lines=f.readlines()
    for line in lines:
        in_seq,out_seq=line.rstrip().split('\t')
        in_seq_tokens,out_seq_tokens=in_seq.split(' '),out_seq.split(' ')
        if max(len(in_seq_tokens),len(out_seq_tokens)) > max_seq_len-1:
            continue
        process_one_seq(in_seq_tokens,in_tokens,in_seqs,max_seq_len)
        process_one_seq(out_seq_tokens,out_tokens,out_seqs,max_seq_len)
    in_vocab,in_data=build_data(in_tokens,in_seqs)
    out_vocab,out_data=build_data(out_tokens,out_seqs)
    return in_vocab,out_vocab,gdata.ArrayDataset(in_data,out_data)
    
max_seq_len=7
in_vocab,out_vocab,dataset=read_data(max_seq_len)
print(in_vocab.token_to_idx)
'''
{'<unk>': 0, '<pad>': 1, '<bos>': 2, '<eos>': 3, '.': 4, 'est': 5, 'elle': 6, 'ils': 7, 'sont': 8, 'il': 9, 'mon': 10, 'a': 11, 'c': 12, 'elles': 13, '!': 14, 'acteurs': 15, 'adorable': 16, 'age': 17, 'amis': 18, 'bonne': 19, 'bonnes': 20, 'canadienne': 21, 'crevees': 22, 'de': 23, 'des': 24, 'deux': 25, 'disputent': 26, 'du': 27, 'ennuis': 28, 'environ': 29, 'fait': 30, 'frere': 31, 'genre': 32, 'grands': 33, 'japonaise': 34, 'nageuse': 35, 'oncle': 36, 'personne': 37, 'regardent': 38, 'russes': 39, 'se': 40, 'tort': 41, 'toutes': 42, 'tranquille': 43, 'une': 44, 'velo': 45, 'vieille': 46}
'''

print(out_vocab.idx_to_token)
'''
{'<unk>': 0, '<pad>': 1, '<bos>': 2, '<eos>': 3, '.': 4, 'is': 5, 'are': 6, 'he': 7, 'they': 8, 'she': 9, 'my': 10, 'a': 11, 'good': 12, '!': 13, 'about': 14, 'actors': 15, 'age': 16, 'arguing': 17, 'bicycle': 18, 'both': 19, 'brother': 20, 'canadian': 21, 'exhausted': 22, 'friends': 23, 'great': 24, 'in': 25, 'japanese': 26, 'lovable': 27, 'old': 28, 'person': 29, 'quiet': 30, 'riding': 31, 'russian': 32, 'swimmer': 33, 'trouble': 34, 'type': 35, 'uncle': 36, 'watching': 37, 'wrong': 38}
'''
print(out_vocab.unknown_token)#<unk>
print(out_vocab.reserved_tokens)#['<pad>', '<bos>', '<eos>']
print(out_vocab.token_to_idx['arguing'],out_vocab.idx_to_token[17])#17 arguing

print(dataset[0])
'''
(
[ 6.  5. 46.  4.  3.  1.  1.]
<NDArray 7 @cpu(0)>,
[ 9.  5. 28.  4.  3.  1.  1.]
<NDArray 7 @cpu(0)>)
'''

读取的数据集,我们查看了输入和输出的词典的一些属性,熟悉构建的词典里面的词与索引,也打印第一个样本看下,内容是法语和英语的词索引序列,长度为7,数据集整理好之后,我们接下来就是使用含有注意力机制的编码器-解码器来简单的做个机器翻译。

这里简短介绍下注意力机制,我们回头选一个法语-英语句子对示例来说明下,比如法语:“ils regardent .they are watching .”法语ils regardent .翻译成英语they are watching .我们发现其实英语they are只需关注法语中的ils,watching关注regardent,.直接映射即可。这个例子表明解码器在每一时间步对输入序列中不同时间步的表征或编码信息分配不同的注意力一样,这种机制就叫做注意力机制。

那么这里可以了解到,注意力机制通过对编码器所有时间步的隐藏状态做加权平均来得到背景变量c,解码器在每一时间步调整这些权重,即注意力权重,从而能够在不同时间步分别关注输入序列中的不同部分并编码进相应时间步的背景变量。本质上来讲就是,注意力机制能够为表征中较有价值的部分分配较多的计算资源。除了在自然语言处理NLP中应用,还广泛使用到图像分类,自动图像描述和语音识别等等。目前很火爆的ChatGPT是基于Transformer,而这个变换器模型的设计就是依靠注意力机制来编码输入序列并解码出输出序列的。

编码器

我们通过对输入语言的词索引做词嵌入得到特征(或叫词的表征),然后输入到一个多层门控循环单元中(GRU),在前面文章有介绍过,Gluonrnn.GRU实例在前向计算后也会返回输出和最终时间步的多层隐藏状态。其中的输出指的是最后一层的隐藏层在各个时间步的隐藏状态,并不涉及输出层的计算,注意力机制将这些输出作为键项和值项。

class Encoder(nn.Block):
    def __init__(self, vocab_size,embed_size,num_hiddens,num_layers,drop_prob=0,**kwargs):
        super(Encoder,self).__init__(**kwargs)
        #词嵌入
        self.embedding=nn.Embedding(vocab_size,embed_size)
        #对输入序列应用多层门控循环单元(GRU) RNN
        self.rnn=rnn.GRU(num_hiddens,num_layers,dropout=drop_prob)

    def forward(self, inputs,state):
        #输入的形状是(批量大小,时间步数),所以需要交换为(时间步数,批量大小)
        embedding=self.embedding(inputs).swapaxes(0,1)
        return self.rnn(embedding,state)
    
    def begin_state(self,*args,**kwargs):
        return self.rnn.begin_state(*args,**kwargs)

来测试下,使用一个批量大小为4,时间步数为7的小批量输入。其中门控循环单元的隐藏单元个数为16,隐藏层数设置为2。

编码器对该输入执行前向计算后返回的输出形状为(时间步数,批量大小,隐藏单元个数)

门控循环单元的多层隐藏状态的形状为(隐藏层个数,批量大小,隐藏单元个数),这里对门控循环单元来说state列表只含一个元素,即隐藏状态;如果使用长短期记忆,state列表还包含一个叫记忆细胞的元素。

encoder=Encoder(vocab_size=10,embed_size=8,num_hiddens=16,num_layers=2)
encoder.initialize()
output,state=encoder(nd.zeros((4,7)),encoder.begin_state(batch_size=4))
print(output.shape)#(7, 4, 16)  (时间步数,批量大小,隐藏单元个数)
#state是列表类型
print(state[0].shape)#(2, 4, 16) (隐藏层个数,批量大小,隐藏单元个数)

注意力机制

注意力机制的输入包括查询项、键项和值项。设编码器和解码器的隐藏单元个数相同,这里的查询项为解码器在上一时间步的隐藏状态,形状为(批量大小,隐藏单元个数);键项和值项均为编码器在所有时间步的隐藏状态,形状为(时间步数,批量大小,隐藏单元个数)

注意力机制返回当前时间步的背景变量,形状为(批量大小,隐藏单元个数)

在此之前先看下Dense里面的flatten参数为TrueFalse的区别

Dense实例会将除了第一维之外的维度都看做是仿射变换的特征维,将输入转成二维矩阵(样本维个数,特征维个数)

dense1=nn.Dense(2,flatten=True)
dense1.initialize()
print(dense1(nd.zeros((3,5,7))).shape)#(3,2)

如果我们希望全连接层只对最后一维做仿射变换,其他维的形状保持不变的话,只需将flatten项设置为False即可。

dense2=nn.Dense(2,flatten=False)
dense2.initialize()
print(dense2(nd.zeros((3,5,7))).shape)#(3,5,2)

接下来上注意力模型代码:

def attention_model(attention_size):
    model=nn.Sequential()
    model.add(nn.Dense(attention_size,activation='tanh',use_bias=False,flatten=False),
              nn.Dense(1,use_bias=False,flatten=False))
    return model

def attention_forward(model,enc_states,dec_state):
    #解码器隐藏状态广播到跟编码器隐藏状态形状相同后进行连结
    dec_states=nd.broadcast_axis(dec_state.expand_dims(0),axis=0,size=enc_states.shape[0])
    enc_and_dec_states=nd.concat(enc_states,dec_states,dim=2)
    e=model(enc_and_dec_states)#形状为(时间步数,批量大小,1)
    alpha=nd.softmax(e,axis=0)#在时间步维度做softmax运算
    return (alpha * enc_states).sum(axis=0)#返回背景变量

做个测试,编码器的批量大小为4,时间步为10,编码器和解码器的隐藏单元个数都为8。注意力机制返回一个小批量的背景向量,每个背景向量的长度等于编码器的隐藏单元个数,因此输出形状为(4,8)

seq_len,batch_size,num_hiddens=10,4,8
model=attention_model(10)
model.initialize()
enc_states=nd.zeros((seq_len,batch_size,num_hiddens))
dec_state=nd.zeros((batch_size,num_hiddens))
print(attention_forward(model,enc_states,dec_state).shape)#(4, 8)

其中attention_forward前向计算函数中的指定维度的广播broadcast_axis方法,附加示例说明下:

x = nd.array([[[1],[2]]])#(1, 2, 1)
#第三个维度进行广播,大小为3
print(nd.broadcast_axis(x,axis=2, size=3))
'''
[[[1. 1. 1.]
  [2. 2. 2.]]]
<NDArray 1x2x3 @cpu(0)>
'''

#将第一个维度和第三个维度进行广播,大小分别为2和3
print(nd.broadcast_axis(x, axis=(0,2), size=(2,3)))
'''
[[[1. 1. 1.]
  [2. 2. 2.]]

 [[1. 1. 1.]
  [2. 2. 2.]]]
<NDArray 2x2x3 @cpu(0)>
'''

含注意力机制的解码器

编码器搞定之后,来看下解码器,在解码器的前向计算中,我们先通过上面的注意力机制计算得到当前时间步的背景向量。由于解码器的输入来自输出语言的词索引,我们将输入通过词嵌入层可以得到表征,然后和背景向量在特征维连结。

我们将连结后的结果与上一时间步的隐藏状态通过门控循环单元计算出当前时间步的输出和隐藏状态。最后,我们将输出通过全连接层变换为有关各个输出词的预测,形状为(批量大小,输出词典大小)

class Decoder(nn.Block):
    def __init__(self,vocab_size,embed_size,num_hiddens,num_layers,attention_size,drop_prob=0,**kwargs):
        super(Decoder,self).__init__(**kwargs)
        self.embedding=nn.Embedding(vocab_size,embed_size)
        self.attention=attention_model(attention_size)
        self.rnn=rnn.GRU(num_hiddens,num_layers,dropout=drop_prob)
        self.out=nn.Dense(vocab_size,flatten=False)

    def forward(self,cur_input,state,enc_states):
        #使用注意力机制计算背景向量
        #将编码器在最终时间步的隐藏状态作为解码器的初始隐藏状态
        c=attention_forward(self.attention,enc_states,state[0][-1])
        #将嵌入的输入和背景向量在特征维进行连结
        input_add_c=nd.concat(self.embedding(cur_input),c,dim=1)
        #为连结后的变量增加时间步维,时间步个数为1
        output,state=self.rnn(input_add_c.expand_dims(0),state)
        #移除时间步维,输出形状为(批量大小,输出词典大小)
        output=self.out(output).squeeze(axis=0)
        return output,state
    
    def begin_state(self,enc_state):
        #将编码器最终时间步的隐藏状态作为解码器的初始隐藏状态
        return enc_state

训练模型

计算损失函数,并训练模型:

def batch_loss(encoder,decoder,X,Y,loss):
    batch_size=X.shape[0]
    enc_state=encoder.begin_state(batch_size=batch_size)
    enc_outputs,enc_state=encoder(X,enc_state)
    dec_state=decoder.begin_state(enc_state)#初始化解码器的隐藏状态
    dec_input=nd.array([out_vocab.token_to_idx[BOS]] * batch_size)#解码器的最初时间步输入为<bos>
    mask,num_not_pad_tokens=nd.ones(shape=(batch_size,)),0#使用mask掩码变量来忽略掉标签为填充项的损失
    l=nd.array([0])
    for y in Y.T:
        dec_ouput,dec_state=decoder(dec_input,dec_state,enc_outputs)
        l=l+(mask*loss(dec_ouput,y)).sum()
        dec_input=y#使用强制教学
        num_not_pad_tokens+=mask.sum().asscalar()
        mask=mask*(y!=out_vocab.token_to_idx[EOS])
    return l/num_not_pad_tokens

#同时迭代编码器和解码器的模型参数
def train(encoder,decoder,dataset,lr,batch_size,num_epochs):
    encoder.initialize(init.Xavier(),force_reinit=True)
    decoder.initialize(init.Xavier(),force_reinit=True)
    enc_trainer=gluon.Trainer(encoder.collect_params(),'adam',{'learning_rate':lr})
    dec_trainer=gluon.Trainer(decoder.collect_params(),'adam',{'learning_rate':lr})
    loss=gloss.SoftmaxCrossEntropyLoss()
    data_iter=gdata.DataLoader(dataset,batch_size,shuffle=True)
    for epoch in range(num_epochs):
        l_sum=0.0
        for X,Y in data_iter:
            with autograd.record():
                l=batch_loss(encoder,decoder,X,Y,loss)
            l.backward()
            enc_trainer.step(1)
            dec_trainer.step(1)
            l_sum+=l.asscalar()
        if (epoch+1) % 10 == 0:
            print("epoch %d,loss %.4f" % (epoch+1,l_sum/len(data_iter)))

embed_size,num_hiddens,num_layers=64,64,2
attention_size,drop_prob,lr,batch_size,num_epochs=10,0.5,0.01,2,50
encoder=Encoder(len(in_vocab),embed_size,num_hiddens,num_layers,drop_prob)
decoder=Decoder(len(out_vocab),embed_size,num_hiddens,num_layers,attention_size,drop_prob)
train(encoder,decoder,dataset,lr,batch_size,num_epochs)

'''
epoch 10,loss 0.5872
epoch 20,loss 0.2703
epoch 30,loss 0.1843
epoch 40,loss 0.0730
epoch 50,loss 0.0354
'''

机器翻译

损失函数写好了之后,我们试着来看下翻译的效果如何?

def translate(encoder,decoder,input_seq,max_seq_len):
    in_tokens=input_seq.split(' ')
    in_tokens+=[EOS]+[PAD]*(max_seq_len-len(in_tokens)-1)
    enc_input=nd.array([in_vocab.to_indices(in_tokens)])
    enc_state=encoder.begin_state(batch_size=1)
    enc_output,enc_state=encoder(enc_input,enc_state)
    dec_input=nd.array([out_vocab.token_to_idx[BOS]])
    dec_state=decoder.begin_state(enc_state)
    output_tokens=[]
    for _ in range(max_seq_len):
        dec_output,dec_state=decoder(dec_input,dec_state,enc_output)
        pred=dec_output.argmax(axis=1)
        pred_token=out_vocab.idx_to_token[int(pred.asscalar())]
        if pred_token==EOS:
            break
        else:
            output_tokens.append(pred_token)
            dec_input=pred
    return output_tokens

print(translate(encoder,decoder,'ils regardent .',max_seq_len))#['they', 'are', 'watching', '.']
print(translate(encoder,decoder,'c est une personne adorable .',max_seq_len))#['he', 'is', 'a', 'lovable', 'person', '.']

OK,翻译的效果还是很好,完美呈现。

评价翻译结果

当然上面的翻译是在训练数据集里面的,如果不在训练集里面的话,泛化能力如何呢?比如:print(translate(encoder,decoder,'ils sont canadiens .',max_seq_len))#['they', 'are', 'russian', '.']这个翻译出来的结果就错误了,正确翻译结果应该是They are Canadian.

所以我们最好是有个评估函数去评价它,一般使用BLEU(Bilingual Evaluation Understudy),直接上代码:

def bleu(pred_tokens,label_tokens,k):
    #预测的词与真实标签词的评估
    len_pred,len_label=len(pred_tokens),len(label_tokens)
    score=math.exp(min(0,1-len_label/len_pred))
    for n in range(1,k+1):
        num_matches,label_subs=0,collections.defaultdict(int)
        for i in range(len_label-n+1):
            label_subs[''.join(label_tokens[i:i+n])] += 1
        for i in range(len_pred-n+1):
            if label_subs[''.join(pred_tokens[i:i+n])]>0:
                num_matches+=1
                label_subs[''.join(pred_tokens[i:i+n])]-=1
        p=num_matches/(len_pred-n+1)
        score *= math.pow(p,math.pow(0.5,n))
    return score

def score(input_seq,label_seq,k):
    pred_tokens=translate(encoder,decoder,input_seq,max_seq_len)
    label_tokens=label_seq.split(' ')
    print('BLEU %.3f,翻译结果:%s' % (bleu(pred_tokens,label_tokens,k),' '.join(pred_tokens)))

score('ils regardent .','they are watching .',k=2)#BLEU 1.000,翻译结果:they are watching .
score('ils sont canadiens .','they are canadian .',k=2)#BLEU 0.658,翻译结果:they are russian .

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

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

相关文章

centos7安装教程

1.点击文件–新建虚拟机 2.根据图片一直下一步或者做一些改动 3. 点击自定义硬件&#xff0c;点击浏览选中下载好的ISO文件 4.配置完成后启动虚拟机 5.选择语言&#xff0c;中英文都可&#xff0c;按需求选择 6.进行设置目标位置&#xff0c;配置分区 7.选择网络和主机名 8.配置…

.net6 web api使用EF Core,根据model类自动生成表

1.安装EF Core和mysql数据库的nuget包 Microsoft.EntityFrameworkCore Pomelo.EntityFrameworkCore.MySql 2.创建models文件夹&#xff0c;在文件夹下创建实体类 public class Users{public int Id { get; set; }[Column(TypeName "varchar(200)"), Required]publ…

Streaming systems 第三章中文

Chapter 3. Watermarks GIthub链接&#xff0c;欢迎志同道合的小伙伴一起翻译 到目前为止&#xff0c;我们一直从pipeline设计者或数据科学家的角度来研究流处理。第二章介绍了水印&#xff0c;对事件时间处理中发生的位置和处理时间中结果何时输出的基本问题做了一部分回答。…

制造企业数字化工厂建设步骤的建议

随着工业4.0、中国制造2025的深度推进&#xff0c;越来越多的制造企业开始迈入智能制造的领域&#xff0c;那数字工厂要从何入手呢&#xff1f; 数字工厂规划的核心&#xff0c;也正是信息域和物理域这两个维度&#xff0c;那就从这两个维度来进行分析&#xff0c;看如何进行数…

MySQL架构篇

一、进阶学习环境说明 1.1 MySQL服务器环境 Linux虚拟机&#xff1a;CentOS 7 MySQL&#xff1a;MySQL5.7.30 在Linux服务器中安装MySQL&#xff1a; ps.如果有自己的云服务器&#xff0c;可忽略前两步&#xff0c;直接进行第三步 1.2 服务器日志文件说明 MySQL是通过文件系统对…

论文投稿指南——中文核心期刊推荐(经济管理)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…

什么蓝牙耳机好用戴着舒适?佩戴舒适音质好的无线蓝牙耳机

戴耳机时间长会导致耳朵痛&#xff0c;时间长了对我们的听力还有影响。一款佩戴舒适的耳机更贴合耳朵的设计&#xff0c;能够带来更加优质的聆听体验&#xff0c;下面甄选出了几款佩戴比较舒适的耳机推荐&#xff0c;音质还非常出众。 一、南卡小音舱蓝牙耳机 单耳重量&#x…

Protobuf 使用和原理

文章目录1. protobuf 简介1.1. 发展背景1.2. 优缺点1.2.1. 优点1.2.2. 缺点2. 使用2.1. 消息类型2.1.1. 字段限制2.1.2. 数据类型2.1.3. 分配字段编号2.1.4. 保留字段2.1.5. 默认字段规则2.1.6. 枚举2.2. Protobuf 工作流程2.2.1 编译proto文件2.3. 使用建议3. 原理3.1. 编码格…

抓包工具fiddler详细使用教程

各位做测试的同学想必对抓包工具fiddler并不陌生&#xff0c;但是很多同学可能没有总结过它的用法&#xff0c;下面我总结了fiddler一些常用的用法。 Web端抓包配置 打开Fiddler&#xff0c;Tools -> Fiddler Options -> HTTPS 配置完后记得要重启Fiddler 选中Decrpt …

第2章 线程安全与共享资源竞争

第2章 线程安全与共享资源竞争 2.1 synchronized同步介绍 synchronized要解决的是共享资源冲突的问题。当共享资源被任务使用时&#xff0c;要对资源提前加锁。所有任务都采用抢占模式&#xff0c;即某个任务会抢先对共享资源加上第一把锁。如果这是一个排他锁&#xff0c;…

汇编指令学习(LOOP)

一、xor异或操作&#xff0c;相同为0&#xff0c;不同为1xor eax,eaxeax异或eax&#xff0c;相同为0&#xff0c;并把结果存放到eax&#xff0c;简单说该语句就是想eax寄存器清零。二、ECX&#xff0c;计数器mov ecx,0x3将ecx寄存器设置为3三、DEC减一操作dec ecxecx寄存器的值…

扬帆优配|昔日白马股濒临退市,却6天5涨停!ST股突然集体爆发

尽管再度重申“公司股票将被停止上市”&#xff0c;但3月8日早间&#xff0c;*ST辅仁股价仍是在开盘后快速封住涨停板。这已是该公司近6个买卖日来&#xff0c;第5次呈现涨停。 无独有偶&#xff0c;8日早间ST东瀛也在此前多次涨停后&#xff0c;再度呈现近4%的涨幅。而就在7日…

图像的读取与保存

图像是由一个个像素点组成&#xff0c;像素点就是颜色点&#xff0c;而颜色最简单的方式就是用RGB或RGBA表示图像保存图像将像素信息按照 一定格式&#xff0c;一定顺序&#xff08;即编码&#xff09; 存在硬盘上的 二进制文件 中保存图像需要以下必要信息&#xff1a;1. 文件…

ChatGPT的N种用法(持续更新中。。。)

目录前言一、语法更正二、文本翻译三、语言转换3-1、Python-->JAVA四、代码解释-1五、代码解释-2六、修复代码错误六、作为百科全书七、信息提取七、好友聊天八、创意生成器8-1、VR和密室结合8-2、再结合AR九、采访问题9-1、采访问题清单9-2、采访问题清单并给出相应答案十、…

优思学院|六西格玛管理的核心理念是什么?

六西格玛管理是一种基于数据分析的质量管理方法&#xff0c;旨在通过降低过程的变异性来达到质量稳定和优化的目的。该方法以希腊字母“σ”为名&#xff0c;代表标准差&#xff0c;是衡量过程变异性的重要指标。 六西格玛管理的核心理念是“以客户为中心、以数据为基础、追求…

【JAVA程序设计】【C00114】基于SSM+微信小程序的食堂订餐点餐管理系统——有文档

基于微信小程序的食堂订餐点餐管理系统项目简介项目获取开发环境项目技术运行截图项目简介 基于ssm框架管理前端使用vue&#xff0c;用户使用微信小程序的食堂订餐点餐小程序共分为三个角色&#xff1a;系统管理员、商家、用户 管理员角色包含以下功能&#xff1a; 首页展示、…

提升数字品牌的5个技巧

“品牌”或“品牌推广”的概念通常用于营销。因为建立您的企业品牌对于产品来说极其重要&#xff0c;品牌代表了您与客户互动的身份和声音。今天&#xff0c;让我们来看看在数字领域提升品牌的一些有用的技巧。如何在数字领域提升您的品牌&#xff1f;在了解这些技巧之前&#…

Android特别的数据结构(二)ArrayMap源码解析

1. 数据结构 public final class ArrayMap<K,V> implements Map<K,V> 由两个数组组成&#xff0c;一个int[] mHashes用来存放Key的hash值&#xff0c;一个Object[] mArrays用来连续存放成对的Key和ValuemHashes数组按非严格升序排列初始默认容量为0减容&#xff…

Hbase 映射到Hive

目录 一、环境配置修改 关闭掉hbase&#xff0c;zookeeper和hive服务 进入hive312/conf 修改hive-site.xml配置&#xff0c; 在代码最后添加配置 将hbase235的jar包全部拷贝到hive312的lib目录&#xff0c;并且所有的是否覆盖信息全部输入n&#xff0c;不覆盖 查看hive312下…

详解抓包原理以及抓包工具whistle的用法

什么是抓包? 分析网络问题业务分析分析网络信息流通量网络大数据金融风险控制探测企图入侵网络的攻击探测由内部和外部的用户滥用网络资源探测网络入侵后的影响监测链接互联网宽频流量监测网络使用流量(包括内部用户&#xff0c;外部用户和系统)监测互联网和用户电脑的安全状…