【AICon】探索RAG 技术在实际应用中遇到的挑战及应对策略!AICon精华内容已上线73%>>> 了解详情
写点什么

基于 PaddlePaddle 的机器翻译教程 | 深度学习基础任务系列

  • 2019-05-29
  • 本文字数:15822 字

    阅读完需:约 52 分钟

基于PaddlePaddle的机器翻译教程 | 深度学习基础任务系列

机器翻译(machine translation, MT)是用计算机来实现不同语言之间翻译的技术。需要翻译的语言通常称为源语言(source language),翻译成的结果语言称为目标语言(target language)。机器翻译即实现从源语言到目标语言转换的过程,是自然语言处理的重要研究领域之一。


本文将带领大家了解经典的端到端神经网络机器翻译 Seq2Seq 模型,以及如何用 PaddlePaddle 来训练。如果您想要实践效果更佳的翻译模型,请参考 GitHhub 模型库中 Transformer 实现。


Seq2Seq 项目地址:


https://github.com/PaddlePaddle/book/blob/develop/08.machine_translation/README.cn.md


Transformer 项目地址:


https://github.com/PaddlePaddle/models/tree/develop/PaddleNLP/neural_machine_translation/transformer。

背景介绍

早期机器翻译系统多为基于规则的翻译系统,需要由语言学家编写两种语言之间的转换规则,再将这些规则录入计算机。该方法对语言学家的要求非常高,而且我们几乎无法总结一门语言会用到的所有规则,更何况两种甚至更多的语言。因此统计机器翻译(Statistical Machine Translation, SMT)技术应运而生。


在统计机器翻译技术中,转化规则是由机器自动从大规模的语料中学习得到的,而非我们人主动提供规则。因此,它克服了基于规则的翻译系统所面临的知识获取瓶颈的问题,但仍然存在许多挑战:1)人为设计许多特征(feature),但永远无法覆盖所有的语言现象;2)难以利用全局的特征;3)依赖于许多预处理环节,如词语对齐、分词或符号化(tokenization)、规则抽取、句法分析等,而每个环节的错误会逐步累积,对翻译的影响也越来越大。


近年来,深度学习技术的发展为解决上述挑战提供了新的思路。将深度学习应用于机器翻译任务的方法大致分为两类:1)仍以统计机器翻译系统为框架,只是利用神经网络来改进其中的关键模块,如语言模型、调序模型等(见图 1 的左半部分);2)不再以统计机器翻译系统为框架,而是直接用神经网络将源语言映射到目标语言,即端到端的神经网络机器翻译(End-to-End Neural Machine Translation, End-to-End NMT)(见图 1 的右半部分),简称为 NMT 模型。作为经典模型的实现,可以帮助大家更好的理解机器翻译。



图 1:基于神经网络的机器翻译系统

效果展示

以中英翻译(中文翻译到英文)的模型为例,当模型训练完毕时,如果输入如下已分词的中文句子:


这些 是 希望 的 曙光 和 解脱 的 迹象 .


如果设定显示翻译结果的条数为 3,生成的英语句子如下:


0 -5.36816 These are signs of hope and relief .1 -6.23177 These are the light of hope and relief .2 -7.7914 These are the light of hope and the relief of hope .


  • 左起第一列是生成句子的序号;左起第二列是该条句子的得分(从大到小),分值越高越好;左起第三列是生成的英语句子。

  • 另外有两个特殊标志:表示句子的结尾,表示未登录词(unknown word),即未在训练字典中出现的词。

模型概览

本节依次介绍双向循环神经网络(Bi-directional Recurrent Neural Network),NMT 模型中典型的编码器-解码器(Encoder-Decoder)框架以及柱搜索(beam search)算法。

双向循环神经网络

我们这里介绍 Bengio 团队在论文[2,4]中提出的另一种结构。该结构的目的是输入一个序列,得到其在每个时刻的特征表示,即输出的每个时刻都用定长向量表示到该时刻的上下文语义信息。


具体来说,该双向循环神经网络分别在时间维以顺序和逆序——即前向(forward)和后向(backward)——依次处理输入序列,并将每个时间步 RNN 的输出拼接成为最终的输出层。这样每个时间步的输出节点,都包含了输入序列中当前时刻完整的过去和未来的上下文信息。下图展示的是一个按时间步展开的双向循环神经网络。该网络包含一个前向和一个后向 RNN,其中有六个权重矩阵:输入到前向隐层和后向隐层的权重矩阵(W_1,W_3),隐层到隐层自己的权重矩阵(W_2,W_5),前向隐层和后向隐层到输出层的权重矩阵(W_4,W_6)。注意,该网络的前向隐层和后向隐层之间没有连接。



图 2:按时间步展开的双向循环神经网络

编码器-解码器框架

编码器-解码器(Encoder-Decoder)[2]框架用于解决由一个任意长度的源序列到另一个任意长度的目标序列的变换问题。即编码阶段将整个源序列编码成一个向量,解码阶段通过最大化预测序列概率,从中解码出整个目标序列。编码和解码的过程通常都使用 RNN 实现。



图 3:编码器-解码器框架

编码器

编码阶段分为三步:


  1. one-hot vector 表示:将源语言句子 x={x_1, x_2,…, x_t}的每个词 x_i 表示成一个列向量 W_iϵ〖{0,1}〗^(|v|),i=1,2,…,T。这个向量 W_i 的维度与词汇表大小|V| 相同,并且只有一个维度上有值 1(该位置对应该词在词汇表中的位置),其余全是 0。

  2. 映射到低维语义空间的词向量:one-hot vector 表示存在两个问题,1)生成的向量维度往往很大,容易造成维数灾难;2)难以刻画词与词之间的关系(如语义相似性,也就是无法很好地表达语义)。因此,需再 one-hot vector 映射到低维的语义空间,由一个固定维度的稠密向量(称为词向量)表示。记映射矩阵为 CϵR^(K*|V|),用 S_i= C_(W_i )表示第 i 个词的词向量,K 为向量维度。

  3. 用 RNN 编码源语言词序列:这一过程的计算公式为 h_i = ∅_θ (h_(i-1), S_i),其中 h_0 是一个全零的向量,∅_θ是一个非线性激活函数,最后得到的 h = {h_1,…, h_T}就是 RNN 依次读入源语言 T 个词的状态编码序列。整句话的向量表示可以采用 h 在最后一个时间步 T 的状态编码,或使用时间维上的池化(pooling)结果。


第 3 步也可以使用双向循环神经网络实现更复杂的句编码表示,具体可以用双向 GRU 实现。前向 GRU 按照词序列(x_1,…, x_T)的顺序依次编码源语言端词,并得到一系列隐层状态



类似的,后向 GRU 按照(x_T,…, x_1)的顺序依次编码源语言端词,得到



最后对于词 x_i,通过拼接两个 GRU 的结果得到它的隐层状态,即 h_i =




图 4:使用双向 GRU 的编码器

解码器

机器翻译任务的训练过程中,解码阶段的目标是最大化下一个正确的目标语言词的概率。思路是: 1. 每一个时刻,根据源语言句子的编码信息(又叫上下文向量,context vector)c、真实目标语言序列的第 i 个词 u_i 和 i 时刻 RNN 的隐层状态 z_i,计算出下一个隐层状态 z_(i+1)。计算公式如下:



其中∅_θ’是一个非线性激活函数;c 是源语言句子的上下文向量,在不使用注意力机制时,如果编码器的输出是源语言句子编码后的最后一个元素,则可以定义 c = h_t ;u_i 是目标语言序列的第 i 个单词,u_0 是目标语言序列的开始标记 < s > ,表示解码开始;z_i 是 i 时刻解码 RNN 的隐层状态,z_0 是一个全零的向量。


  1. 将 z_(i+1)通过 softmax 归一化,得到目标语言序列的第 i+1 个单词的概率分布 p_(i+1)。概率分布公式如下:



其中 w_(sZ_(i+1) )+b_z 是对每个可能的输出单词进行打分,再 softmax 归一化就可以得到第 i+1 个词的概率 p_(i+1)。


2. 根据 p_(i+1)和 u_(i+1)计算代价。


3. 重复步骤 1~23,直到目标语言序列中的所有词处理完毕。


机器翻译任务的生成过程,通俗来讲就是根据预先训练的模型来翻译源语言句子。生成过程中的解码阶段和上述训练过程的有所差异,具体介绍请见柱搜索算法。

柱搜索算法

柱搜索(beam search)是一种启发式图搜索算法,用于在图或树中搜索有限集合中的最优扩展节点,通常用在解空间非常大的系统(如机器翻译、语音识别)中,原因是内存无法装下图或树中所有展开的解。如在机器翻译任务中希望翻译“< s >你好”,就算目标语言字典中只有 3 个词(< s >,, hello),也可能生成无限句话(hello 循环出现的次数不定),为了找到其中较好的翻译结果,我们可采用柱搜索算法。


柱搜索算法使用广度优先策略建立搜索树,在树的每一层,按照启发代价(heuristic cost)(本教程中,为生成词的 log 概率之和)对节点进行排序,然后仅留下预先确定的个数(文献中通常称为 beam width、beam size、柱宽度等)的节点。只有这些节点会在下一层继续扩展,其他节点就被剪掉了,也就是说保留了质量较高的节点,剪枝了质量较差的节点。因此,搜索所占用的空间和时间大幅减少,但缺点是无法保证一定获得最优解。


使用柱搜索算法的解码阶段,目标是最大化生成序列的概率。思路是:


1. 每一个时刻,根据源语言句子的编码信息 cc、生成的第 ii 个目标语言序列单词 u_i 和 i 时刻 RNN 的隐层状态 z_i,计算出下一个隐层状态 z_(i+1)。


2. 将 z_(i+1)通过 softmax 归一化,得到目标语言序列的第 i+1 个单词的概率分布 p_(i+1)。


3. 根据 p_(i+1)采样出单词 u_(i+1)。


4. 重复步骤 1~3,直到获得句子结束标记或超过句子的最大生成长度为止。


注意:z_(i+1)和 p_(i+1)的计算公式同解码器中的一样。且由于生成时的每一步都是通过贪心法实现的,因此并不能保证得到全局最优解。

数据介绍

本教程使用 WMT-14 数据集中的 bitexts(after selection)作为训练集,dev+test data 作为测试集和生成集。

数据预处理

我们的预处理流程包括两步:


  • 将每个源语言到目标语言的平行语料库文件合并为一个文件:

  • 合并每个 XXX.src 和 XXX.trg 文件为 XXX。

  • XXX 中的第 i 行内容为 XXX.src 中的第 i 行和 XXX.trg 中的第 i 行连接,用’t’分隔。


创建训练数据的“源字典”和“目标字典”。每个字典都有 DICTSIZE 个单词,包括:语料中词频最高的(DICTSIZE - 3)个单词,和 3 个特殊符号< s >(序列的开始)、(序列的结束)和(未登录词)。

示例数据

因为完整的数据集数据量较大,为了验证训练流程,PaddlePaddle 接口 paddle.dataset.wmt14 中默认提供了一个经过预处理的较小规模的数据集。


该数据集有 193319 条训练数据,6003 条测试数据,词典长度为 30000。因为数据规模限制,使用该数据集训练出来的模型效果无法保证。

模型配置说明

下面我们开始根据输入数据的形式配置模型。首先引入所需的库函数以及定义全局变量。


from __future__ import print_functionimport paddleimport paddle.fluid as fluidimport paddle.fluid.layers as pdimport osimport systry:    from paddle.fluid.contrib.trainer import *    from paddle.fluid.contrib.inferencer import *except ImportError:    print(        "In the fluid 1.0, the trainer and inferencer are moving to paddle.fluid.contrib",        file=sys.stderr)    from paddle.fluid.trainer import *    from paddle.fluid.inferencer import *
dict_size = 30000 # 字典维度source_dict_dim = target_dict_dim = dict_size # 源/目标语言字典维度hidden_dim = 32 # 编码器中的隐层大小word_dim = 16 # 词向量维度batch_size = 2 # batch 中的样本数max_length = 8 # 生成句子的最大长度topk_size = 50beam_size = 2 # 柱宽度
is_sparse = Truedecoder_size = hidden_dim # 解码器中的隐层大小model_save_dir = "machine_translation.inference.model"
复制代码


然后如下实现编码器框架:


def encoder(is_sparse): # 定义源语言id序列的输入数据 src_word_id = pd.data(     name="src_word_id", shape=[1], dtype='int64', lod_level=1) # 将上述编码映射到低维语言空间的词向量 src_embedding = pd.embedding(     input=src_word_id,     size=[dict_size, word_dim],     dtype='float32',     is_sparse=is_sparse,     param_attr=fluid.ParamAttr(name='vemb')) # LSTM层:fc + dynamic_lstm fc1 = pd.fc(input=src_embedding, size=hidden_dim * 4, act='tanh') lstm_hidden0, lstm_0 = pd.dynamic_lstm(input=fc1, size=hidden_dim * 4) # 取源语言序列编码后的最后一个状态 encoder_out = pd.sequence_last_step(input=lstm_hidden0) return encoder_out
复制代码


再实现训练模式下的解码器:


def train_decoder(context):    # 定义目标语言id序列的输入数据,并映射到低维语言空间的词向量    trg_language_word = pd.data(        name="target_language_word", shape=[1], dtype='int64', lod_level=1)    trg_embedding = pd.embedding(        input=trg_language_word,        size=[dict_size, word_dim],        dtype='float32',        is_sparse=is_sparse,        param_attr=fluid.ParamAttr(name='vemb'))
rnn = pd.DynamicRNN() with rnn.block(): # 使用 DynamicRNN 定义每一步的计算 # 获取当前步目标语言输入的词向量 current_word = rnn.step_input(trg_embedding) # 获取隐层状态 pre_state = rnn.memory(init=context) # 解码器计算单元:单层前馈网络 current_state = pd.fc(input=[current_word, pre_state], size=decoder_size, act='tanh') # 计算归一化的单词预测概率 current_score = pd.fc(input=current_state, size=target_dict_dim, act='softmax') # 更新RNN的隐层状态 rnn.update_memory(pre_state, current_state) # 输出预测概率 rnn.output(current_score)
return rnn()
复制代码


实现推测模式下的解码器:


def decode(context):    init_state = context    # 定义解码过程循环计数变量    array_len = pd.fill_constant(shape=[1], dtype='int64', value=max_length)    counter = pd.zeros(shape=[1], dtype='int64', force_cpu=True)
# 定义 tensor array 用以保存各个时间步的内容,并写入初始id,score和state state_array = pd.create_array('float32') pd.array_write(init_state, array=state_array, i=counter)
ids_array = pd.create_array('int64') scores_array = pd.create_array('float32')
init_ids = pd.data(name="init_ids", shape=[1], dtype="int64", lod_level=2) init_scores = pd.data( name="init_scores", shape=[1], dtype="float32", lod_level=2)
pd.array_write(init_ids, array=ids_array, i=counter) pd.array_write(init_scores, array=scores_array, i=counter)
# 定义循环终止条件变量 cond = pd.less_than(x=counter, y=array_len) # 定义 while_op while_op = pd.While(cond=cond) with while_op.block(): # 定义每一步的计算 # 获取解码器在当前步的输入,包括上一步选择的id,对应的score和上一步的state pre_ids = pd.array_read(array=ids_array, i=counter) pre_state = pd.array_read(array=state_array, i=counter) pre_score = pd.array_read(array=scores_array, i=counter)
# 更新输入的state为上一步选择id对应的state pre_state_expanded = pd.sequence_expand(pre_state, pre_score) # 同训练模式下解码器中的计算逻辑,包括获取输入向量,解码器计算单元计算和 # 归一化单词预测概率的计算 pre_ids_emb = pd.embedding( input=pre_ids, size=[dict_size, word_dim], dtype='float32', is_sparse=is_sparse, param_attr=fluid.ParamAttr(name='vemb'))
current_state = pd.fc(input=[pre_state_expanded, pre_ids_emb], size=decoder_size, act='tanh') current_state_with_lod = pd.lod_reset(x=current_state, y=pre_score) current_score = pd.fc(input=current_state_with_lod, size=target_dict_dim, act='softmax') topk_scores, topk_indices = pd.topk(current_score, k=beam_size)
# 计算累计得分,进行beam search accu_scores = pd.elementwise_add( x=pd.log(topk_scores), y=pd.reshape(pre_score, shape=[-1]), axis=0) selected_ids, selected_scores = pd.beam_search( pre_ids, pre_score, topk_indices, accu_scores, beam_size, end_id=10, level=0) with pd.Switch() as switch: with switch.case(pd.is_empty(selected_ids)): pd.fill_constant( shape=[1], value=0, dtype='bool', force_cpu=True, out=cond) with switch.default(): pd.increment(x=counter, value=1, in_place=True)
pd.array_write(current_state, array=state_array, i=counter) pd.array_write(selected_ids, array=ids_array, i=counter) pd.array_write(selected_scores, array=scores_array, i=counter)
length_cond = pd.less_than(x=counter, y=array_len) finish_cond = pd.logical_not(pd.is_empty(x=selected_ids)) pd.logical_and(x=length_cond, y=finish_cond, out=cond)
translation_ids, translation_scores = pd.beam_search_decode( ids=ids_array, scores=scores_array, beam_size=beam_size, end_id=10)
return translation_ids, translation_scores
复制代码


进而,我们定义一个 train_program 来使用 inference_program 计算出的结果,在标记数据的帮助下来计算误差。我们还定义了一个 optimizer_func 来定义优化器。


def train_program():    context = encoder()    rnn_out = train_decoder(context)    label = pd.data(        name="target_language_next_word", shape=[1], dtype='int64', lod_level=1)    cost = pd.cross_entropy(input=rnn_out, label=label)    avg_cost = pd.mean(cost)    return avg_cost
def optimizer_func(): return fluid.optimizer.Adagrad( learning_rate=1e-4, regularization=fluid.regularizer.L2DecayRegularizer( regularization_coeff=0.1))
复制代码

训练模型

定义训练环境

定义您的训练环境,可以指定训练是发生在 CPU 还是 GPU 上。


if use_cuda and not fluid.core.is_compiled_with_cuda():  returnplace = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()
复制代码

定义数据提供器

下一步是为训练和测试定义数据提供器。提供器读入一个大小为 BATCH_SIZE 的数据。paddle.dataset.wmt.train 每次会在乱序化后提供一个大小为 BATCH_SIZE 的数据,乱序化的大小为缓存大小 buf_size。


train_reader = paddle.batch(        paddle.reader.shuffle(            paddle.dataset.wmt14.train(dict_size), buf_size=1000),        batch_size=batch_size)
复制代码

构造训练器(trainer)

训练器需要一个训练程序和一个训练优化函数。


trainer = Trainer(        train_func=train_program, place=place, optimizer_func=optimizer_func)
复制代码

提供数据

feed_order 用来定义每条产生的数据和 paddle.layer.data 之间的映射关系。比如,wmt14.train 产生的第一列的数据对应的是 src_word_id 这个特征。


feed_order = [        'src_word_id', 'target_language_word', 'target_language_next_word'    ]
复制代码

事件处理器

回调函数 event_handler 在一个之前定义好的事件发生后会被调用。例如,我们可以在每步训练结束后查看误差。


def event_handler(event):    if isinstance(event, EndStepEvent):        if event.step % 10 == 0:            print('pass_id=' + str(event.epoch) + ' batch=' + str(event.step))  if isinstance(event, EndEpochEvent):    trainer.save_params(model_save_dir)
复制代码

开始训练

最后,我们传入训练循环数(num_epoch)和一些别的参数,调用 trainer.train 来开始训练。


trainer = Trainer(        train_func=train_program, place=place, optimizer_func=optimizer_func)trainer.train(        reader=train_reader,        num_epochs=EPOCH_NUM,        event_handler=event_handler,        feed_order=feed_order)
复制代码

应用模型

定义解码部分

使用上面定义的 encoder 和 decoder 函数来推测翻译后的对应 id 和分数.


context = encoder()translation_ids, translation_scores = decode(context)
复制代码

定义数据

我们先初始化 id 和分数来生成 tensors 来作为输入数据。在这个预测例子中,我们用 wmt14.test 数据中的第一个记录来做推测,最后我们用"源字典"和"目标字典"来列印对应的句子结果。


init_ids_data = np.array([1 for _ in range(batch_size)], dtype='int64')init_scores_data = np.array(    [1. for _ in range(batch_size)], dtype='float32')init_ids_data = init_ids_data.reshape((batch_size, 1))init_scores_data = init_scores_data.reshape((batch_size, 1))init_lod = [1] * batch_sizeinit_lod = [init_lod, init_lod]
init_ids = fluid.create_lod_tensor(init_ids_data, init_lod, place)init_scores = fluid.create_lod_tensor(init_scores_data, init_lod, place)
test_data = paddle.batch( paddle.reader.shuffle( paddle.dataset.wmt14.test(dict_size), buf_size=1000), batch_size=batch_size)
feed_order = ['src_word_id']feed_list = [ framework.default_main_program().global_block().var(var_name) for var_name in feed_order]feeder = fluid.DataFeeder(feed_list, place)
src_dict, trg_dict = paddle.dataset.wmt14.get_dict(dict_size)
复制代码

测试

现在我们可以进行预测了。我们要在 feed_order 提供对应参数,放在 executor 上运行以取得 id 和分数结果


for data in test_data():    feed_data = map(lambda x: [x[0]], data)    feed_dict = feeder.feed(feed_data)    feed_dict['init_ids'] = init_ids    feed_dict['init_scores'] = init_scores
results = exe.run( framework.default_main_program(), feed=feed_dict, fetch_list=[translation_ids, translation_scores], return_numpy=False)
result_ids = np.array(results[0]) result_ids_lod = results[0].lod() result_scores = np.array(results[1])
print("Original sentence:") print(" ".join([src_dict[w] for w in feed_data[0][0][1:-1]])) print("Translated score and sentence:") for i in xrange(beam_size): start_pos = result_ids_lod[1][i] + 1 end_pos = result_ids_lod[1][i+1] print("%d\t%.4f\t%s\n" % (i+1, result_scores[end_pos-1], " ".join([trg_dict[w] for w in result_ids[start_pos:end_pos]])))
break
复制代码

总结

端到端的神经网络机器翻译是近几年兴起的一种全新的机器翻译方法。在本文中,我们介绍了 NMT 中典型的“编码器-解码器”框架。由于 NMT 是一个典型的 Seq2Seq(Sequence to Sequence,序列到序列)学习问题,因此,Seq2Seq 中的 query 改写(query rewriting)、摘要、单轮对话等问题都可以用本教程的模型来解决。

参考文献

1.Koehn P. Statistical machine translation[M]. Cambridge University Press, 2009.


2. Cho K, Van Merriënboer B, Gulcehre C, et al. Learning phrase representations using RNN encoder-decoder for statistical machine translation[C]//Proceedings of the 2014 Conference on Empirical Methods in Natural Language Processing (EMNLP), 2014: 1724-1734.


3. Chung J, Gulcehre C, Cho K H, et al. Empirical evaluation of gated recurrent neural networks on sequence modeling[J]. arXiv preprint arXiv:1412.3555, 2014.


4. Bahdanau D, Cho K, Bengio Y. Neural machine translation by jointly learning to align and translate[C]//Proceedings of ICLR 2015, 2015.


5. Papineni K, Roukos S, Ward T, et al. BLEU: a method for automatic evaluation of machine translation[C]//Proceedings of the 40th annual meeting on association for computational linguistics. Association for Computational Linguistics, 2002: 311-318.


公众号推荐:

2024 年 1 月,InfoQ 研究中心重磅发布《大语言模型综合能力测评报告 2024》,揭示了 10 个大模型在语义理解、文学创作、知识问答等领域的卓越表现。ChatGPT-4、文心一言等领先模型在编程、逻辑推理等方面展现出惊人的进步,预示着大模型将在 2024 年迎来更广泛的应用和创新。关注公众号「AI 前线」,回复「大模型报告」免费获取电子版研究报告。

AI 前线公众号
2019-05-29 15:326979

评论

发布
暂无评论
发现更多内容

leetcode 241. Different Ways to Add Parentheses 为运算表达式设计优先级(中等)

okokabcd

LeetCode 分治 数据结构与算法

SysOM 案例解析:消失的内存都去哪了 !| 龙蜥技术

OpenAnolis小助手

开源 案例 内存泄漏 龙蜥技术 allocpage

如何参与开源项目 - 细说 GitHub 上的 PR 全过程

玩转Devop和研发效能DevStream/DevLake

GitHub 开源 DevOps DevStream

Python源码扫描工具Bandit小试牛刀

阿呆

Python Bandit 源码扫描

重磅!海泰方圆工业强基智能网联汽车项目顺利通过验收

电子信息发烧客

作为Java程序员,阿里一面Synchronized连珠炮你是否能够顶住

了不起的程序猿

面试题 Java 开发 Java’

低代码平台中的数据连接方式(下)

Baidu AICLOUD

前端 低代码 爱速搭

2022年5月互联网医疗领域月度观察

易观分析

互联网医疗

Python性能分析利器pyinstrument讲解

曲鸟

Python 7月月更

作战图鉴:12大场景详述容器安全建设要求

青藤云安全

网络安全 解决方案 容器安全

【堡垒机】云堡垒机和普通堡垒机的区别是什么?

行云管家

云计算 数据安全 堡垒机 云堡垒机 IT安全

Linux透明大页机制在云上大规模集群实践介绍

百度Geek说

Linux 运维 linux 文件权限控制

2022PAGC 金帆奖 | 融云荣膺「年度杰出产品技术服务商」

融云 RongCloud

Spring Cloud源码分析之Eureka篇第四章:服务注册是如何发起的

程序员欣宸

Java spring Spring Cloud Eureka 7月月更

跬智 Kyligence 入选工信部“工业大数据分析与集成应用重点实验室”工作组成员单位

Kyligence

大数据 Kyligence 工业数据智能

谈谈 SAP iRPA Studio 创建的本地项目的云端部署问题

Jerry Wang

SAP 7月月更 iRPA 智能机器人 流程自动化

交付效率提升52倍,运营效率提升10倍,看《金融云原生技术实践案例汇编》(附下载)

York

云原生 金融科技 金融行业

云计算安全扩展要求关注的安全目标和实现方式区分原则有哪些?

行云管家

云计算 等保 等保2.0 云计算安全扩展

ORACLE进阶(六)ORACLE expdp/impdp详解

No Silver Bullet

oracle 7月月更 expdp impdp 数据泵

社会责任·价值共创,中关村网络安全与信息化产业联盟对话网信企业家海泰方圆董事长姜海舟先生

电子信息发烧客

Navigation — 这么好用的导航框架你确定不来看看?

编程的平行世界

android 架构 框架学习 android jetpack

银行需要搭建智能客服模块的中台能力,驱动全场景智能客服务升级

易观分析

人工智能

融云斩获 2022 中国信创数字化办公门户卓越产品奖!

融云 RongCloud

麒麟信安根植欧拉:共筑中国操作系统崛起之路

脑极体

spark调优(三):持久化减少二次查询

怀瑾握瑜的嘉与嘉

spark 7月月更

打造All-in-One应用开发平台,轻流树立无代码行业标杆

ToB行业头条

小程序目录结构

小恺

7月月更

什么是数据泄露

AIWeker

机器学习 Kaggle 数据泄露 7月月更

最佳实践 | 用腾讯云AI意愿核身为电话合规保驾护航

牵着蜗牛去散步

人工智能 腾讯云 腾讯 技术干货 电话合规

内部排序——插入排序

乔乔

7月月更

讲师征集令 | Apache SeaTunnel(Incubating) Meetup 分享嘉宾火热招募中!

Apache SeaTunnel

基于PaddlePaddle的机器翻译教程 | 深度学习基础任务系列_AI&大模型_PaddlePaddle技术团队_InfoQ精选文章