bert模型简介、transformers中bert模型源码阅读、分类任务实战和难点总结

bert模型简介、transformers中bert模型源码阅读、分类任务实战和难点总结:https://blog.csdn.net/HUSTHY/article/details/105882989

目录

一、bert模型简介

bert与训练的流程:

bert模型的输入

二、huggingface的bert源码浅析

bert提取文本词向量

BertModel代码阅读

BertEmbedding子模型

BertEncoder

BertAttention

BertIntermediate

BertOutput(config)

BertPooler()

三、Bert文本分类任务实战

四、Bert模型难点总结

写在最前面,这篇博客篇幅有点长,原因是贴的代码和图有点多,感兴趣的可以坚持读下去!


一、bert模型简介

        2018年bert模型被谷歌提出,它在NLP的11项任务中取得了state of the art 的结果。bert模型是由很多层transformer结构堆叠而成,这里简单看看一下transformer的结构,上一张经典的图片,如下:

可以看到transformer是由encoder和decoder模块构成,而bert模型则是利用了transformer的encoder模块。最轻量的bert买模型是由12层transformer,12头注意力,768维的hidden state,在论文中的结构简图如下:

这样的双向transformer的结构,在NLP的大部分任务中取得了很好的效果,具备较强的泛化能力。由于使用了海量的语料进行了训练,bert模型可以使用pretrain——fine-tune这种方式来进行各类NLP任务。

bert与训练的流程:

这个过程包括两个任务,一个是Masked Language Model(遮掩语言模型),另外一个是Next Sentence Prediction(下一句预测)。

Masked Language Model(遮掩语言模型)可以理解为是做完型填空,把语料中15%的词遮掩掉,来学习词和词之间的一些规律;

Next Sentence Prediction就是学习语料中上下文中2个句子之间的关系规律。

通过这2个阶段任务的学习,bert就会把文本的语法和语义信息学习到。bert模型中的self-attention机制可以使用文本其他的词来增强目标词的语义表示,这也是bert模型吊打其他模型的一个关键原因。

bert模型的输入

bert模型的输入可以是一个句子或者句子对,代码层面来说,就是输入了句子或者句子对对应的3个向量。它们分别是token embedding,segment embedding和position embedding,具体的含义:

token embedding:句子的词向量

segment embedding:是那个句子的0和1

position embedding:位置向量,指明每个字在句中的位置。

关于position embedding这里有两种求法,一种是有相应的三角函数公式得出的,这种是绝对向量;还有一种是学习得到的,这种是相对向量。具体形式如下:

二、huggingface的bert源码浅析

关于bert模型的使用,我主要是使用huggingface的transformer库来调用bert和使用——一般是直接用来bert来获取词向量。这里就bert的使用和huggingface中的源码进行一些解读。

bert提取文本词向量

首先看一段简单的代码,使用huggingface的transformers(其实就是实现的bert)来提取句——我爱武汉!我爱中国!——的向量。代码如下:bert提取文本词向量 首先看一段简单的代码,使用huggingface的transformers(其实就是实现的bert)来提取句——我爱武汉!我爱中国!——的向量。代码如下:

from transformers import BertModel,BertTokenizer,BertConfig
import torchconfig = BertConfig.from_pretrained('pretrain_model/chinese-bert-wwm')#第一步加载模型配置文件
bertmodel = BertModel.from_pretrained('pretrain_model/chinese-bert-wwm',config=config)#第二步初始化模型,并加载权重
# print('***************************bertmodel***************************')
tokenizer = BertTokenizer.from_pretrained('pretrain_model/chinese-bert-wwm')#第三步加载tokenizertext1 = '我爱武汉!我爱中国!'
tokeniz_text1 = tokenizer.tokenize(text1)
# print(tokeniz_text1)
# print('tokeniz_text1:',len(tokeniz_text1))
indexed_tokens_1 = tokenizer.convert_tokens_to_ids(tokeniz_text1)
print('len(indexed_tokens_1):',len(indexed_tokens_1))
print(indexed_tokens_1)input_ids_1 = indexed_tokens_1
# print(indexed_tokens_1)
# print('indexed_tokens_1:',len(indexed_tokens_1))
segments_ids_1 = [0]*len(input_ids_1)#其实这个输入可以不用的,因为是单句的原因
input_masks_1 = [1]*len(input_ids_1)#其实这个输入可以不用的,因为是单句的原因input_ids_1_tensor = torch.tensor([input_ids_1])
vector1,pooler1 = bertmodel(input_ids_1_tensor)#应该是输入3个向量的,但是单句情况下,它自会自己做判断,然后自动生成对应的segments_ids和input_masks向量
#这里的输出最后一层的last_hidden_state和最后一层首个token的hidden-statetext2 = '[CLS]我爱武汉!我爱中国![SEP]'
tokeniz_text2 = tokenizer.tokenize(text2)
indexed_tokens_2 = tokenizer.convert_tokens_to_ids(tokeniz_text2)input_ids_2 = indexed_tokens_2
segments_ids_2 = [0]*len(input_ids_2)#其实这个输入可以不用的,因为是单句的原因
input_masks_2 = [1]*len(input_ids_2)#其实这个输入可以不用的,因为是单句的原因input_ids_2_tensor = torch.tensor([input_ids_2])
vector2,pooler2 = bertmodel(input_ids_2_tensor)
print('pooler2:',pooler2)
print('vector2[:,0:1,:]:',vector2[:,0:1,:])text1_encode = tokenizer.encode(text1,add_special_tokens=True)
print('len(text1_encode):',len(text1_encode))
print('text1_encode:',text1_encode)
  • input_ids_2_tensor = torch.tensor([input_ids_2])
  • vector2,pooler2 = bertmodel(input_ids_2_tensor)
  • print('pooler2:',pooler2)
  • print('vector2[:,0:1,:]:',vector2[:,0:1,:])
  • text1_encode = tokenizer.encode(text1,add_special_tokens=True)
  • print('len(text1_encode):',len(text1_encode))
  • print('text1_encode:',text1_encode)
  • #这里的text1_encode和indexed_tokens_2是一模一样的,encode()函数会自动为文本添加特殊字符[UNK][CLS][SEP][MASK]等
  • 以上代码是基于pytorch来实现的,同时应用到了transoformers库!可以看到bert模型的使用非常简单!

    第一步,初始化bert模型和加载权重。这个步骤中,首先加载配置文件、然后加载bert模型和载入权重。

    第二步,对输入文本做词表映射,形成初始词向量。

    第三步,输入喂入bert模型中得到输入文本的结果向量。

    文中是bert模型的输入我这里只给出了一个那就是input_ids,另外的2个没有给出。这里的原因就是这里是单个句子,模型内部可以对另外2个输入做自动添加的处理——并不是没有,这点要注意到。

    这里有个疑问因为bert的输入文本得添加一个[cls]特殊字符,我认为最后的输出lsat_hidden_state中的lsat_hidden_state[:,0:1,:]应该和pooler结果是一样的,可是这里是不一样的,有点理解的偏差,不知道为什么。

    BertModel代码阅读

    通过上文中的代码,大致可以知道怎么调用一些API来创建bert模型和应用它。那么huggingface中是怎么实现BertModel的这个也是比较重要的,这里我们就好好阅读以下其中关于BertModel实现的代码。看一张transformers项目文件结构图:

    这么面封装了很多模型的构建,我们主要是阅读modeling_bert.py文件,它在里面详细的展示了如何构建一个Bert模型的:

    class BertModel(BertPreTrainedModel):"""......."""def __init__(self, config):super().__init__(config)self.config = configself.embeddings = BertEmbeddings(config)self.encoder = BertEncoder(config)self.pooler = BertPooler(config)self.init_weights()def get_input_embeddings(self):return self.embeddings.word_embeddingsdef set_input_embeddings(self, value):self.embeddings.word_embeddings = valuedef _prune_heads(self, heads_to_prune):""" Prunes heads of the model.heads_to_prune: dict of {layer_num: list of heads to prune in this layer}See base class PreTrainedModel"""for layer, heads in heads_to_prune.items():self.encoder.layer[layer].attention.prune_heads(heads)@add_start_docstrings_to_callable(BERT_INPUTS_DOCSTRING)def forward(self,input_ids=None,attention_mask=None,token_type_ids=None,position_ids=None,head_mask=None,inputs_embeds=None,encoder_hidden_states=None,encoder_attention_mask=None,):r"""......."""if input_ids is not None and inputs_embeds is not None:raise ValueError("You cannot specify both input_ids and inputs_embeds at the same time")elif input_ids is not None:input_shape = input_ids.size()elif inputs_embeds is not None:input_shape = inputs_embeds.size()[:-1]else:raise ValueError("You have to specify either input_ids or inputs_embeds")device = input_ids.device if input_ids is not None else inputs_embeds.deviceif attention_mask is None:attention_mask = torch.ones(input_shape, device=device)if token_type_ids is None:token_type_ids = torch.zeros(input_shape, dtype=torch.long, device=device)# We can provide a self-attention mask of dimensions [batch_size, from_seq_length, to_seq_length]# ourselves in which case we just need to make it broadcastable to all heads.extended_attention_mask: torch.Tensor = self.get_extended_attention_mask(attention_mask, input_shape, self.device)# If a 2D ou 3D attention mask is provided for the cross-attention# we need to make broadcastabe to [batch_size, num_heads, seq_length, seq_length]if self.config.is_decoder and encoder_hidden_states is not None:encoder_batch_size, encoder_sequence_length, _ = encoder_hidden_states.size()encoder_hidden_shape = (encoder_batch_size, encoder_sequence_length)if encoder_attention_mask is None:encoder_attention_mask = torch.ones(encoder_hidden_shape, device=device)encoder_extended_attention_mask = self.invert_attention_mask(encoder_attention_mask)else:encoder_extended_attention_mask = None# Prepare head mask if needed# 1.0 in head_mask indicate we keep the head# attention_probs has shape bsz x n_heads x N x N# input head_mask has shape [num_heads] or [num_hidden_layers x num_heads]# and head_mask is converted to shape [num_hidden_layers x batch x num_heads x seq_length x seq_length]head_mask = self.get_head_mask(head_mask, self.config.num_hidden_layers)embedding_output = self.embeddings(input_ids=input_ids, position_ids=position_ids, token_type_ids=token_type_ids, inputs_embeds=inputs_embeds)encoder_outputs = self.encoder(embedding_output,attention_mask=extended_attention_mask,head_mask=head_mask,encoder_hidden_states=encoder_hidden_states,encoder_attention_mask=encoder_extended_attention_mask,)sequence_output = encoder_outputs[0]pooled_output = self.pooler(sequence_output)outputs = (sequence_output, pooled_output,) + encoder_outputs[1:]  # add hidden_states and attentions if they are herereturn outputs  # sequence_output, pooled_output, (hidden_states), (attentions)
    

    以上就是BertModel的全部代码,可以看到在BertModel类中,首先__init__()函数中定义了模型的基本模块,然后在forward()函数里面使用这些结构模块具体实现了Bert的逻辑。

    def __init__(self, config):super().__init__(config)self.config = configself.embeddings = BertEmbeddings(config)self.encoder = BertEncoder(config)self.pooler = BertPooler(config)self.init_weights()
    

    init()函数中定义的模型模块主要是3个,分别是BertEmbedding、BertEncoder和BertPooler。然后在forward(),输入顺序的经过这3个模块的处理就得到了我们要的结果——对应文本的bert向量。

    下面来阅读forward():

    if input_ids is not None and inputs_embeds is not None:raise ValueError("You cannot specify both input_ids and inputs_embeds at the same time")
    elif input_ids is not None:input_shape = input_ids.size()
    elif inputs_embeds is not None:input_shape = inputs_embeds.size()[:-1]
    else:raise ValueError("You have to specify either input_ids or inputs_embeds")device = input_ids.device if input_ids is not None else inputs_embeds.deviceif attention_mask is None:attention_mask = torch.ones(input_shape, device=device)
    if token_type_ids is None:token_type_ids = torch.zeros(input_shape, dtype=torch.long, device=device)# We can provide a self-attention mask of dimensions [batch_size, from_seq_length, to_seq_length]
    # ourselves in which case we just need to make it broadcastable to all heads.
    if attention_mask.dim() == 3:extended_attention_mask = attention_mask[:, None, :, :]
    elif attention_mask.dim() == 2:# Provided a padding mask of dimensions [batch_size, seq_length]# - if the model is a decoder, apply a causal mask in addition to the padding mask# - if the model is an encoder, make the mask broadcastable to [batch_size, num_heads, seq_length, seq_length]if self.config.is_decoder:batch_size, seq_length = input_shapeseq_ids = torch.arange(seq_length, device=device)causal_mask = seq_ids[None, None, :].repeat(batch_size, seq_length, 1) <= seq_ids[None, :, None]causal_mask = causal_mask.to(attention_mask.dtype)  # causal and attention masks must have same type with pytorch version < 1.3extended_attention_mask = causal_mask[:, None, :, :] * attention_mask[:, None, None, :]else:extended_attention_mask = attention_mask[:, None, None, :]
    else:raise ValueError("Wrong shape for input_ids (shape {}) or attention_mask (shape {})".format(input_shape, attention_mask.shape))# Since attention_mask is 1.0 for positions we want to attend and 0.0 for
    # masked positions, this operation will create a tensor which is 0.0 for
    # positions we want to attend and -10000.0 for masked positions.
    # Since we are adding it to the raw scores before the softmax, this is
    # effectively the same as removing these entirely.
    extended_attention_mask = extended_attention_mask.to(dtype=next(self.parameters()).dtype)  # fp16 compatibility
    extended_attention_mask = (1.0 - extended_attention_mask) * -10000.0# If a 2D ou 3D attention mask is provided for the cross-attention
    # we need to make broadcastabe to [batch_size, num_heads, seq_length, seq_length]
    if self.config.is_decoder and encoder_hidden_states is not None:encoder_batch_size, encoder_sequence_length, _ = encoder_hidden_states.size()encoder_hidden_shape = (encoder_batch_size, encoder_sequence_length)if encoder_attention_mask is None:encoder_attention_mask = torch.ones(encoder_hidden_shape, device=device)if encoder_attention_mask.dim() == 3:encoder_extended_attention_mask = encoder_attention_mask[:, None, :, :]elif encoder_attention_mask.dim() == 2:encoder_extended_attention_mask = encoder_attention_mask[:, None, None, :]else:raise ValueError("Wrong shape for encoder_hidden_shape (shape {}) or encoder_attention_mask (shape {})".format(encoder_hidden_shape, encoder_attention_mask.shape))encoder_extended_attention_mask = encoder_extended_attention_mask.to(dtype=next(self.parameters()).dtype)  # fp16 compatibilityencoder_extended_attention_mask = (1.0 - encoder_extended_attention_mask) * -10000.0
    else:encoder_extended_attention_mask = None# Prepare head mask if needed
    # 1.0 in head_mask indicate we keep the head
    # attention_probs has shape bsz x n_heads x N x N
    # input head_mask has shape [num_heads] or [num_hidden_layers x num_heads]
    # and head_mask is converted to shape [num_hidden_layers x batch x num_heads x seq_length x seq_length]
    if head_mask is not None:if head_mask.dim() == 1:head_mask = head_mask.unsqueeze(0).unsqueeze(0).unsqueeze(-1).unsqueeze(-1)head_mask = head_mask.expand(self.config.num_hidden_layers, -1, -1, -1, -1)elif head_mask.dim() == 2:head_mask = (head_mask.unsqueeze(1).unsqueeze(-1).unsqueeze(-1))  # We can specify head_mask for each layerhead_mask = head_mask.to(dtype=next(self.parameters()).dtype)  # switch to fload if need + fp16 compatibility
    else:head_mask = [None] * self.config.num_hidden_layers
    

    以上是一些预处理的代码。判定input_ids的合法性,不能为空不能和inputs_embeds同时输入;接着就获取使用的设备是CPU还是GPU;判定attention_mask和token_type_ids的合法性,为None的话就新建一个;处理attention_mask得到encoder_extended_attention_mask,把它传播给所有的注意力头;最后就是判定是否启用decoder——bert模型是基于encoder的,我认为这里就不必要做这个判定,bert的encoder的结果只是传递给下一层encoder,并没有传递到decoder。

    下面具体看核心的部分。

    上面把输入做一些预处理后,使得输入都合法,然后就可以喂入模型的功能模块中。第一个就是

    embedding_output = self.embeddings(input_ids=input_ids, position_ids=position_ids, token_type_ids=token_type_ids, inputs_embeds=inputs_embeds)
    

    BertEmbedding子模型
    其中的self.embeddings()就是__inti__()的BertEmbeddings(config)模块,它可以看做是一个起embedding功能作用的子模型,具体代码:

    class BertEmbeddings(nn.Module):"""Construct the embeddings from word, position and token_type embeddings."""def __init__(self, config):super().__init__()self.word_embeddings = nn.Embedding(config.vocab_size, config.hidden_size, padding_idx=0)self.position_embeddings = nn.Embedding(config.max_position_embeddings, config.hidden_size)self.token_type_embeddings = nn.Embedding(config.type_vocab_size, config.hidden_size)# self.LayerNorm is not snake-cased to stick with TensorFlow model variable name and be able to load# any TensorFlow checkpoint fileself.LayerNorm = BertLayerNorm(config.hidden_size, eps=config.layer_norm_eps)self.dropout = nn.Dropout(config.hidden_dropout_prob)def forward(self, input_ids=None, token_type_ids=None, position_ids=None, inputs_embeds=None):if input_ids is not None:input_shape = input_ids.size()else:input_shape = inputs_embeds.size()[:-1]seq_length = input_shape[1]device = input_ids.device if input_ids is not None else inputs_embeds.deviceif position_ids is None:position_ids = torch.arange(seq_length, dtype=torch.long, device=device)position_ids = position_ids.unsqueeze(0).expand(input_shape)if token_type_ids is None:token_type_ids = torch.zeros(input_shape, dtype=torch.long, device=device)if inputs_embeds is None:inputs_embeds = self.word_embeddings(input_ids)position_embeddings = self.position_embeddings(position_ids)token_type_embeddings = self.token_type_embeddings(token_type_ids)embeddings = inputs_embeds + position_embeddings + token_type_embeddingsembeddings = self.LayerNorm(embeddings)embeddings = self.dropout(embeddings)return embeddings
    

    它的具体作用就是:首先把我们输入的input_ids、token_type_ids和position_ids——(这里输入的是对应元素在词典中的index集合)经过torch.nn.Embedding()在各自的词典中得到词嵌入。然后把这3个向量直接做加法运算,接着做层归一化以及dropout()操作。这里为何可以直接相加是可以做一个专门的问题来讨论的,这里的归一化的作用应该就是避免一些数值问题、梯度问题和模型收敛问题以及分布改变问题,dropout操作随机丢弃掉一部分特征,可以增加模型的泛化性能。

    BertEncoder
    经过上述的处理后,我们就得到了一个维度是[batch_size,sequence_length,hidden_states]的向量embeddings。然后再把这个embeddings输入到Encoder中,代码如下,参数都很清晰明确:

    encoder_outputs = self.encoder(embedding_output,attention_mask=extended_attention_mask,head_mask=head_mask,encoder_hidden_states=encoder_hidden_states,encoder_attention_mask=encoder_extended_attention_mask,)
    

    这里的self.encoder同样是__init__()中的BertEncoder(config)模型,全部代码如下:

    class BertEncoder(nn.Module):def __init__(self, config):super().__init__()self.output_attentions = config.output_attentionsself.output_hidden_states = config.output_hidden_statesself.layer = nn.ModuleList([BertLayer(config) for _ in range(config.num_hidden_layers)])def forward(self,hidden_states,attention_mask=None,head_mask=None,encoder_hidden_states=None,encoder_attention_mask=None,):all_hidden_states = ()all_attentions = ()for i, layer_module in enumerate(self.layer):if self.output_hidden_states:all_hidden_states = all_hidden_states + (hidden_states,)layer_outputs = layer_module(hidden_states, attention_mask, head_mask[i], encoder_hidden_states, encoder_attention_mask)hidden_states = layer_outputs[0]if self.output_attentions:all_attentions = all_attentions + (layer_outputs[1],)# Add last layerif self.output_hidden_states:all_hidden_states = all_hidden_states + (hidden_states,)outputs = (hidden_states,)if self.output_hidden_states:outputs = outputs + (all_hidden_states,)if self.output_attentions:outputs = outputs + (all_attentions,)return outputs 
    

    其中模型定义部分的核心代码如下:

    self.layer = nn.ModuleList([BertLayer(config) for _ in range(config.num_hidden_layers)])
    

    通过这句代码和config中的参数——“num_hidden_layers”: 12——可以得出BertEncoder使用12个(层)BertLayer组成的。对每一层的bertlayer在forward()中的for循环做如下操作:

    for i, layer_module in enumerate(self.layer):if self.output_hidden_states:all_hidden_states = all_hidden_states + (hidden_states,)layer_outputs = layer_module(hidden_states, attention_mask, head_mask[i], encoder_hidden_states, encoder_attention_mask)hidden_states = layer_outputs[0]if self.output_attentions:all_attentions = all_attentions + (layer_outputs[1],)
    

    更新hidden_states(也就是layer_outputs[0]),然后把更新后的hidden_states传入到下一层BertLayer中,同时把每一层的hidden_states和attentions(也就是layer_outputs[1])记录下来,然后作为一个整体输出。所有最后的输出里包含的有最后一层BertLayer的hidden_states和12层所有的hidden_states以及attentions。

    BertLayer具体又是什么样的呢?这里就需要看看具体的BertLayer的实现:

    class BertLayer(nn.Module):def __init__(self, config):super().__init__()self.attention = BertAttention(config)self.is_decoder = config.is_decoderif self.is_decoder:self.crossattention = BertAttention(config)self.intermediate = BertIntermediate(config)self.output = BertOutput(config)
    

    可以看到BertLayer是由BertAttention()、BertIntermediate()和BertOutput()构成。它的forward()是比较简单的,没有什么奇特的操作,都是顺序的把输入经过BertAttention()、BertIntermediate()和BertOutput()这些子模型。这里主要来看看这些子模型的实现:

    BertAttention
    这里它又嵌套了一层,由BertSelfAttention()和BertSelfOutput()子模型组成!

    这里马上就看到self-attention机制的实现了!感觉好激动!——Self-Attention则利用了Attention机制,计算每个单词与其他所有单词之间的关联(说实话理解的不是很透彻!)

    class BertSelfAttention(nn.Module):def __init__(self, config):super().__init__()if config.hidden_size % config.num_attention_heads != 0 and not hasattr(config, "embedding_size"):raise ValueError("The hidden size (%d) is not a multiple of the number of attention ""heads (%d)" % (config.hidden_size, config.num_attention_heads))self.output_attentions = config.output_attentionsself.num_attention_heads = config.num_attention_headsself.attention_head_size = int(config.hidden_size / config.num_attention_heads)self.all_head_size = self.num_attention_heads * self.attention_head_sizeself.query = nn.Linear(config.hidden_size, self.all_head_size)self.key = nn.Linear(config.hidden_size, self.all_head_size)self.value = nn.Linear(config.hidden_size, self.all_head_size)self.dropout = nn.Dropout(config.attention_probs_dropout_prob)def transpose_for_scores(self, x):new_x_shape = x.size()[:-1] + (self.num_attention_heads, self.attention_head_size)x = x.view(*new_x_shape)return x.permute(0, 2, 1, 3)def forward(self,hidden_states,attention_mask=None,head_mask=None,encoder_hidden_states=None,encoder_attention_mask=None,):mixed_query_layer = self.query(hidden_states)# If this is instantiated as a cross-attention module, the keys# and values come from an encoder; the attention mask needs to be# such that the encoder's padding tokens are not attended to.if encoder_hidden_states is not None:mixed_key_layer = self.key(encoder_hidden_states)mixed_value_layer = self.value(encoder_hidden_states)attention_mask = encoder_attention_maskelse:mixed_key_layer = self.key(hidden_states)mixed_value_layer = self.value(hidden_states)query_layer = self.transpose_for_scores(mixed_query_layer)key_layer = self.transpose_for_scores(mixed_key_layer)value_layer = self.transpose_for_scores(mixed_value_layer)# Take the dot product between "query" and "key" to get the raw attention scores.attention_scores = torch.matmul(query_layer, key_layer.transpose(-1, -2))attention_scores = attention_scores / math.sqrt(self.attention_head_size)if attention_mask is not None:# Apply the attention mask is (precomputed for all layers in BertModel forward() function)attention_scores = attention_scores + attention_mask# Normalize the attention scores to probabilities.attention_probs = nn.Softmax(dim=-1)(attention_scores)# This is actually dropping out entire tokens to attend to, which might# seem a bit unusual, but is taken from the original Transformer paper.attention_probs = self.dropout(attention_probs)# Mask heads if we want toif head_mask is not None:attention_probs = attention_probs * head_maskcontext_layer = torch.matmul(attention_probs, value_layer)context_layer = context_layer.permute(0, 2, 1, 3).contiguous()new_context_layer_shape = context_layer.size()[:-2] + (self.all_head_size,)context_layer = context_layer.view(*new_context_layer_shape)outputs = (context_layer, attention_probs) if self.output_attentions else (context_layer,)return outputs
    

    阅读代码之前先回顾一下,self-attention的公式是什么样的,公式编辑比较麻烦直接上2个图,都是来自Attention机制详解(二)——Self-Attention与Transformer文章中:

    首先定义Q、K、V

    然后应用到公式中:

    以上就是单个头的self-attention的公式,多头的话就可以计算多次,然后在合并起来。这里就可以应用到矩阵运算了,还要注意的点就是Q、K、V的学习参数都是共享的——(要去验证),代码对应的就是:

    1. self.query = nn.Linear(config.hidden_size, self.all_head_size)
    2. self.key = nn.Linear(config.hidden_size, self.all_head_size)
    3. self.value = nn.Linear(config.hidden_size, self.all_head_size)
    4. #注意这里的nn.Linear包含的学习参数一个是权重参数weights一个是偏置参数bias
    5. #而且这里的query、key以及value它们的参数不一样,也就是并不共享参数

    参数都包含在nn.Linear中了,这里的self.query对应的是12个头的self-attention机制对应的Q的学习参数模型,当然query、key以及value它们的参数不一样,也就是并不共享参数。

    那么在forward()中是如何实现的呢?

    1. mixed_query_layer = self.query(hidden_states)#计算Q
    2. if encoder_hidden_states is not None:
    3. mixed_key_layer = self.key(encoder_hidden_states)
    4. mixed_value_layer = self.value(encoder_hidden_states)
    5. attention_mask = encoder_attention_mask
    6. else:
    7. mixed_key_layer = self.key(hidden_states) #计算K
    8. mixed_value_layer = self.value(hidden_states)#计算V
    9. #做转置操作——这有点特殊:mixed_query_layer[batch_size,sequence_length,hidden_states]
    10. #query_layer的维度:[batch_size,num_attention_heads,sequence_length,attention_head_size]
    11. query_layer = self.transpose_for_scores(mixed_query_layer)
    12. key_layer = self.transpose_for_scores(mixed_key_layer)
    13. value_layer = self.transpose_for_scores(mixed_value_layer)
    14. #Q和K做点积
    15. attention_scores = torch.matmul(query_layer, key_layer.transpose(-1, -2))
    16. #Q和K做点积后然后除以根号下多头主力的尺寸
    17. attention_scores = attention_scores / math.sqrt(self.attention_head_size)
    18. if attention_mask is not None:
    19. # Apply the attention mask is (precomputed for all layers in BertModel forward() function)
    20. attention_scores = attention_scores + attention_mask
    21. # Normalize the attention scores to probabilities.
    22. #做softmax操作,归一化
    23. attention_probs = nn.Softmax(dim=-1)(attention_scores)
    24. # This is actually dropping out entire tokens to attend to, which might
    25. # seem a bit unusual, but is taken from the original Transformer paper.
    26. attention_probs = self.dropout(attention_probs)
    27. # Mask heads if we want to
    28. if head_mask is not None:
    29. attention_probs = attention_probs * head_mask
    30. #中间结果和V做点积,得到最终结果——注意力得分也就是公式中的Z
    31. context_layer = torch.matmul(attention_probs, value_layer)

    以上代码的中文注释就把计算过程分析清楚了,计算mixed_query_layer、mixed_key_layer和mixed_value_layer,然后做转置(说是维度变换更贴切一点);接着mixed_query_layer、mixed_key_layer做点积操作,然后除以注意力头的尺寸的开方,做softmax操作;最后和mixed_value_layer相乘,得到注意力得分————矩阵计算代码就很好的实现了self-attention。

    以上就是完成了self-attention,然后接下来就进入BertSelfOutput():

    1. class BertSelfOutput(nn.Module):
    2. def __init__(self, config):
    3. super().__init__()
    4. self.dense = nn.Linear(config.hidden_size, config.hidden_size)
    5. self.LayerNorm = BertLayerNorm(config.hidden_size, eps=config.layer_norm_eps)
    6. self.dropout = nn.Dropout(config.hidden_dropout_prob)
    7. def forward(self, hidden_states, input_tensor):
    8. hidden_states = self.dense(hidden_states)
    9. hidden_states = self.dropout(hidden_states)
    10. hidden_states = self.LayerNorm(hidden_states + input_tensor)
    11. return hidden_states

    以上BertSelfOutput()代码很简单,把self-attention输出的结果经过线性模型和dropout操作,最后做层归一化。到这里就跳出了BertAttention()模型,然后就进入中间层BertIntermediate()。

    BertIntermediate

    BertIntermediate()作为中间层代码很简单:

    1. class BertIntermediate(nn.Module):
    2. def __init__(self, config):
    3. super().__init__()
    4. self.dense = nn.Linear(config.hidden_size, config.intermediate_size)
    5. if isinstance(config.hidden_act, str):
    6. self.intermediate_act_fn = ACT2FN[config.hidden_act]
    7. else:
    8. self.intermediate_act_fn = config.hidden_act
    9. def forward(self, hidden_states):
    10. hidden_states = self.dense(hidden_states)
    11. hidden_states = self.intermediate_act_fn(hidden_states)
    12. return hidden_states

    经过一个全连接层,由于config.hidden_size<config.intermediate_size,这里的Linear把特征空间变大了,然后进过了gelu激活函数,增加了特征的非线性性。

    BertOutput(config)

    跳出BertIntermediate()作为中间层后,就进入了BertOutput(config)模型,这个是BertLayer()模型的最后一个子模型。

    1. class BertOutput(nn.Module):
    2. def __init__(self, config):
    3. super().__init__()
    4. self.dense = nn.Linear(config.intermediate_size, config.hidden_size)
    5. self.LayerNorm = BertLayerNorm(config.hidden_size, eps=config.layer_norm_eps)
    6. self.dropout = nn.Dropout(config.hidden_dropout_prob)
    7. def forward(self, hidden_states, input_tensor):
    8. hidden_states = self.dense(hidden_states)
    9. hidden_states = self.dropout(hidden_states)
    10. hidden_states = self.LayerNorm(hidden_states + input_tensor)
    11. return hidden_states

    经过线性模型和dropout操作,最后做层归一化,把特征空间又缩小回来了。最后输出一个hidden_states,这里就是一个BertLayer()的输出了。

    BertPooler()

    然后经历了12个BertLayer()的操作,一层一层的变换,最后得出的outputs进入BertPooler():

    1. sequence_output = encoder_outputs[0]
    2. pooled_output = self.pooler(sequence_output)

    pooler代码如下:

    1. class BertPooler(nn.Module):
    2. def __init__(self, config):
    3. super().__init__()
    4. self.dense = nn.Linear(config.hidden_size, config.hidden_size)
    5. self.activation = nn.Tanh()
    6. def forward(self, hidden_states):
    7. # We "pool" the model by simply taking the hidden state corresponding
    8. # to the first token.
    9. first_token_tensor = hidden_states[:, 0]
    10. pooled_output = self.dense(first_token_tensor)
    11. pooled_output = self.activation(pooled_output)
    12. return pooled_output
    13. #以上的pooler作用要具体的去调试hidden_states的shape。

    由代码可知这个pooler的功能就是把last_hidden_states的第二维的第一维也就是文本对应的第一个;。。。、。。

    以上差不多就是BertModel的具体实现,由于这个模型的代码嵌套调用过多,可能理解起来有一定的困惑,那么接下来就需要一个图片来可视化理解。上图:

    上图是huggingface中的BertModel的结构流程图(简图,有很多疏漏的地方勿怪!),bertModel的输入和基本的子模型以及数据的流向都显示出来了,对应着代码理解起来更加方便。黄色的图形就是torch中的基本函数模块(这里的Q、K和V不是),其他颜色的矩形就是模型,平行四边形就是数据。

    以上就是对BertModel实现代码的简单解析,里面涉及到很多的细节:不同模型模块的参数以及它们的维度信息,还有就是变量的维度变化,以及每个模型模块的具体作用和意义,没有去深究,读者有精力的话可以自己去深究。

    三、Bert文本分类任务实战

            这里我们要写一个使用transformers项目中的分类器来实现一个简单的文本分类任务,这里我们没有自己取重写Dataloader以及模型的训练,就是直接把transformers项目中的bert分类器拿过来进行fine-tune,工作量少,结果也比较好!当然也可以完全自己实现(前面也自己实现过一个基于bert的句子分类的任务——使用bert模型做句子分类,有兴趣的可以移步),后续有时间的话可以写一个各个模型文本分类任务的比较博客,更加熟练文本分类的一些代码coding和知识——增加熟练度,也可以给大家分享一下。

    来看本文的transformers项目中的bert分类器进行fine-tune作文本分类的任务,在这个项目里面已经把全部的代码写好了,我们只需要把我们的文本处理成项目能够识别和读取的形式。简单的分析一下,分类任务的代码:

    主要的分类任务的代码是在run_glue.py文件中,这里面定义了main函数,命令行参数接收器,模型的加载和调用,模型的训练以及验证,和数据读取以及处理的功能模块调用。

    我们看一下这里调用的分类模型,代码是这样的:

    1. model = AutoModelForSequenceClassification.from_pretrained(
    2. args.model_name_or_path,
    3. from_tf=bool(".ckpt" in args.model_name_or_path),
    4. config=config,
    5. cache_dir=args.cache_dir,
    6. )

    其实最终这里的AutoModelForSequenceClassification.from_pretrained()调用的是modeling_bert.py中的BertForSequenceClassification类,它就是具体的分类器实现:

    1. class BertForSequenceClassification(BertPreTrainedModel):
    2. def __init__(self, config):
    3. super().__init__(config)
    4. self.num_labels = config.num_labels
    5. self.bert = BertModel(config)
    6. self.dropout = nn.Dropout(config.hidden_dropout_prob)
    7. self.classifier = nn.Linear(config.hidden_size, self.config.num_labels)
    8. self.init_weights()
    9. def forward(
    10. self,
    11. input_ids=None,
    12. attention_mask=None,
    13. token_type_ids=None,
    14. position_ids=None,
    15. head_mask=None,
    16. inputs_embeds=None,
    17. labels=None,
    18. ):
    19. outputs = self.bert(
    20. input_ids,
    21. attention_mask=attention_mask,
    22. token_type_ids=token_type_ids,
    23. position_ids=position_ids,
    24. head_mask=head_mask,
    25. inputs_embeds=inputs_embeds,
    26. )
    27. pooled_output = outputs[1]
    28. pooled_output = self.dropout(pooled_output)
    29. logits = self.classifier(pooled_output)
    30. outputs = (logits,) + outputs[2:] # add hidden states and attention if they are here
    31. if labels is not None:
    32. if self.num_labels == 1:
    33. # We are doing regression
    34. loss_fct = MSELoss()
    35. loss = loss_fct(logits.view(-1), labels.view(-1))
    36. else:
    37. loss_fct = CrossEntropyLoss()
    38. loss = loss_fct(logits.view(-1, self.num_labels), labels.view(-1))
    39. outputs = (loss,) + outputs
    40. return outputs

    模型调用了BertModel,然后做使用nn.Linear(config.hidden_size, self.config.num_labels)做分类,loss函数是常用的交叉熵损失函数。以上就是分类器的一些简单的分析。 我们要做的工作就是仿照项目里的代码写一个任务处理器:

    项目目录结构:transformerer_local/data/glue.py,注意这里的transformerer_local原本应该是transformerer,我这里已经做了修改。在glue.py添加上我们的分类任务代码——添加一个读取文件中的文本然后,然后把每条数据序列化成Example,注意get_labels()函数,把自己的类别数目实现过来,代码如下:

    1. class MyownProcessor(DataProcessor):
    2. """Processor for the CoLA data set (GLUE version)."""
    3. def get_example_from_tensor_dict(self, tensor_dict):
    4. """See base class."""
    5. return InputExample(
    6. tensor_dict["idx"].numpy(),
    7. tensor_dict["sentence"].numpy().decode("utf-8"),
    8. None,
    9. str(tensor_dict["label"].numpy()),
    10. )
    11. def get_train_examples(self, data_dir):
    12. """See base class."""
    13. return self._create_examples(self._read_tsv(os.path.join(data_dir, "train.tsv")), "train")
    14. def get_dev_examples(self, data_dir):
    15. """See base class."""
    16. return self._create_examples(self._read_tsv(os.path.join(data_dir, "dev.tsv")), "dev")
    17. def get_predict_examples(self, data_dir):
    18. return self._create_examples(self._read_tsv(os.path.join(data_dir, "test.tsv")), "predict")
    19. def get_labels(self):
    20. """See base class."""
    21. return ["0", "1","2","3","4","5","6","7"]
    22. def _create_examples(self, lines, set_type):
    23. """Creates examples for the training and dev sets."""
    24. examples = []
    25. for (i, line) in enumerate(lines):
    26. guid = "%s-%s" % (set_type, i)
    27. if len(line)==2:
    28. text_a = line[0]
    29. label = line[1]
    30. examples.append(InputExample(guid=guid, text_a=text_a, text_b=None, label=label))
    31. else:
    32. print(line)
    33. return examples

    同时在验证的时候,对应评价指标函数,我们这里不是binary,计算f1_score的时候要采用其他的策略:

    transformerer_local/data/metrics/__init__.py,注意这里的transformerer_local原本应该是transformerer,添加内容:

    1. #添加多分类评价函数
    2. def acc_and_f1_multi(preds, labels):
    3. acc = simple_accuracy(preds, labels)
    4. f1 = f1_score(y_true=labels, y_pred=preds,average='micro')
    5. return {
    6. "acc": acc,
    7. "f1": f1,
    8. "acc_and_f1": (acc + f1) / 2,
    9. }
    10. def glue_compute_metrics(task_name, preds, labels):
    11. assert len(preds) == len(labels)
    12. if task_name == "cola":
    13. return {"mcc": matthews_corrcoef(labels, preds)}
    14. elif task_name == "sst-2":
    15. return {"acc": simple_accuracy(preds, labels)}
    16. elif task_name == "mrpc":
    17. return acc_and_f1(preds, labels)
    18. elif task_name == "sts-b":
    19. return pearson_and_spearman(preds, labels)
    20. elif task_name == "qqp":
    21. return acc_and_f1(preds, labels)
    22. elif task_name == "mnli":
    23. return {"acc": simple_accuracy(preds, labels)}
    24. elif task_name == "mnli-mm":
    25. return {"acc": simple_accuracy(preds, labels)}
    26. elif task_name == "qnli":
    27. return {"acc": simple_accuracy(preds, labels)}
    28. elif task_name == "rte":
    29. return {"acc": simple_accuracy(preds, labels)}
    30. elif task_name == "wnli":
    31. return {"acc": simple_accuracy(preds, labels)}
    32. elif task_name == "hans":
    33. return {"acc": simple_accuracy(preds, labels)}
    34. #添加我们的多分类任务调用函数
    35. elif task_name == "myown":
    36. return acc_and_f1_multi(preds, labels)
    37. else:
    38. raise KeyError(task_name)

    添加内容就在注释部分。

    OK,现在代码部分已经做好了,接下来就是数据部分了。直接上数据:

    数据截图部分就是上面这样的,把pat_summary和ipc_class属性提取出来,这里的数据质量比较好,然后只需要把超级长的文本去掉(长度大于510的):

    数据长度分布直方图,发现几乎全部都是小于510的长度,只有少部分比较长,只有128条,这里数据集总规模是24.8W条,可以把这少部分的直接去掉。然后把数据分割成训练集和测试集比例(8:2),保存为tsv格式。

    接下来就是直接进行训练了,编写如下命令行,在train_glue_classification.sh文件中:

    1. export TASK_NAME=myown
    2. python -W ignore ./examples/run_glue.py \
    3. --model_type bert \
    4. --model_name_or_path ./pretrain_model/Chinese-BERT-wwm/ \
    5. --task_name $TASK_NAME \
    6. __do_train \
    7. --do_eval \
    8. --data_dir ./data_set/patent/ \
    9. --max_seq_length 510 \
    10. --per_gpu_eval_batch_size=8 \
    11. --per_gpu_train_batch_size=8 \
    12. --per_gpu_predict_batch_size=48 \
    13. --learning_rate 2e-5 \
    14. --num_train_epochs 5.0 \
    15. --output_dir ./output/

    直接在终端上运行这个sh文件,bash train_glue_classification.sh。注意这里的训练显卡显存得11G以上,不然跑步起来,batch_size不能太大。训练过程中,一个epoch大概时间3.5小时,所以时间还要蛮久的。最后给出结果:

    可以看到acc=0.8508,一个8分类的任务准确率85%粗略一看还能接受。如果要详细的分析,可以把每一类的准确率和召回率给弄出来,或者分析一下ROC,对模型的性能做详细的分析,这里不做过多讨论。另外关于这个模型的优化,怎么提高准确率,也不做考虑。

     

    小结:以上就是直接使用transformers项目中的bert分类器拿过来进行fine-tune,做文本分类,其实代码都写好了,我们只需要简单的修改一下代码和配置,就能很快的训练好自己的分类器。

    四、Bert模型难点总结

    其实关于Bert模型还有很多细节可以去探究,这里推荐知乎上的一些文章:超细节的BERT/Transformer知识点。

    1、Bert模型怎么解决长文本问题?

    如果文本的长度不是特别长,511-600左右,可以直接把大于510的部分直接去掉,这是一种最粗暴的处理办法。

    如果文本内容很长,而且内容也比较重要,那么就不能够这么直接粗暴的处理了。主要思路是global norm + passage rank + sliding window——来自Amazon EMNLP的这篇文章:Multi-passage BERT。简单的说一下sliding window,滑窗法就是把文档分割成有部分重叠的短文本段落,然后把这些文本得出的向量拼接起来或者做mean pooling操作。具体的效果,要去做实验。

    2、Bert的输入向量Token Embedding、Segment Embedding、Position Embedding,它们都有自己的物理含义,为什么可以相加后输入到模型中取呢?

    这个问题在知乎上已经有人提问了,回答的大佬很多。我个人倾向接受这个解释:one hot向量concat后经过一个全连接等价于向量embedding后直接相加。

    Token Embedding、Segment Embedding、Position Embedding分别代表了文本的具体语义,段落含义和位置含义,现在要把这3个不同的向量一起放到模型中去训练,我认为concat的操作就能完整的保留文本的含义。[input_ids] 、[token_type_ids] 和[position_ids]这3个向量,concat以后形成一个[input_ids token_type_ids position_ids]新的向量,这样丢入模型中取训练就应该是我们初始要的结果。但是在丢入模型之前这个向量[input_ids token_type_ids position_ids]是需要经过Embedding的,而[input_ids] 、[token_type_ids] 和[position_ids]先经过Embedding然后相加和上面的效果是等价的。这样的好处是降低了参数的维度的同时达到了同样的效果,所以就采用了直接相加。

    3、BERT在第一句前会加一个[CLS]标志,为什么?作用是什么?

    最后一层的transformer的输出该位置的向量,由于本身并不具有任何意义,就能很公平的融合整个句子的含义,然后做下游任务的时候就很好了。

    其实在huggingface实现的bert代码中,作者认为这个向量并不是很好,要想做下有任务,还是得靠自己取把最后一层的hidden_states[B,S,D]去做一些操作,比如mean pool操作。我这里没有实验过,只是拿来使用,在使用bert模型做句子分类一文中使用了这样的思想。

    4、Bert模型的非线性来自什么地方?

    主要是来子前馈层的gelu激活函数和self-attention。

    5、Bert模型为何要使用多头注意力机制?

    谷歌bert作者在论文中提到的是模型有多头的话,就可以形成多个子空间,那么模型就可以去关注不同方面的信息。

    可以这样理解,多头attention机制确实有点类似多个卷积核的作用,可以捕捉到文本更多更丰富的信息。

    当然知乎有人专门研究这个问题,列举了头和头直接的异同关系,作了一个比较综合全面的回答,可以去阅读!为什么Transformer 需要进行 Multi-head Attention?

     

    写在最后:

    我个人理解的Bert模型就只有 这么多,其实Bert模型就是一个提取词向量的语言模型,由于提取的词向量能很好的包含文本的语义信息,它能够做很多任务并且取得不错的效果。NER、关系抽取、文本相似度计算、文本分类、阅读理解等等任务Bert都能做。

    这个博客个人算是花了一定的精力了的(五一到现在,差不多10天时间吧),作为这段时间以来学习NLP的一个总结还是很有收获感的。加油!继续努力!当然博客可能写的不是干货,也许还有错误的地方,作者水平有限,望大家提出改正!

     

    参考文章

    一文读懂bert模型

    超细节的BERT/Transformer知识点

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

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

相关文章

LeetCode 476. 数字的补数(移位 异或^)

1. 题目 给定一个正整数&#xff0c;输出它的补数。补数是对该数的二进制表示取反。 2. 解题 先求出该数的2进制有多少位然后分别每位与1进行异或操作 class Solution { public:int findComplement(int num) {int n 1, num_copy num;while(num_copy/2){n;num_copy / 2;}wh…

论文浅尝 - ICLR 2020 | 用于文本推理的神经模块网络

论文笔记整理&#xff1a;邓淑敏&#xff0c;浙江大学在读博士&#xff0c;研究方向为低资源条件下知识图谱自动化构建关键技术研究。论文链接&#xff1a;https://openreview.net/pdf?idSygWvAVFPr Demo链接: https://demo.allennlp.org/reading-comprehension 代码链接: htt…

Android官方开发文档Training系列课程中文版:通知用户之构建通知

原文地址&#xff1a;http://android.xsoftlab.net/training/notify-user/index.html 引言 通知用于在有事件发生时&#xff0c;将事情以更便捷的方式展示给用户。用户可以在他们方便的时候直接与通知交互。 Notifications design guide课程讲述了如何设计有效的通知以及何时…

前端安全系列(二):如何防止CSRF攻击?

背景 随着互联网的高速发展&#xff0c;信息安全问题已经成为企业最为关注的焦点之一&#xff0c;而前端又是引发企业安全问题的高危据点。在移动互联网时代&#xff0c;前端人员除了传统的 XSS、CSRF 等安全问题之外&#xff0c;又时常遭遇网络劫持、非法调用 Hybrid API 等新…

全栈深度学习第3期: 怎样科学管理实验数据?

一起追剧鸭简介Berkeley全栈深度学习追剧计划是由夕小瑶的卖萌屋发起的优质公开课打卡项目&#xff0c;通过微信群为同期追剧的小伙伴提供交流平台。关于该计划的详请见这里。1. Berkeley深度学习追剧群目前已有1000小伙伴加入&#xff0c;公众号后台回复口令 深度学习追剧 入群…

PDFMiner:python 读取 pdf 内容

PDF的格式不是规范的&#xff0c;很多情况下没有逻辑结构&#xff0c;不能自适应页面大小的调整。PDFMiner是通过尝试猜测PDF的布局来重建其结构&#xff0c;有时候效果并不理想。 import importlib import sys import timeimportlib.reload(sys) time1 time.time()import os…

论文浅尝 - ICLR2020 | 通过神经逻辑归纳学习有效地解释

论文笔记整理&#xff1a;朱渝珊&#xff0c;浙江大学直博生。研究方向&#xff1a;知识图谱&#xff0c;快速表示学习等。论文链接&#xff1a;https://arxiv.org/pdf/1910.02481.pdf本文是ICLR 2020的一篇关于知识图谱中关于复杂&#xff08;树状、组合&#xff09;规则可微学…

Android官方开发文档Training系列课程中文版:通知用户之创建不同导航方式的Activity

原文地址&#xff1a;http://android.xsoftlab.net/training/notify-user/navigation.html 设计通知时要考虑到用户所预想的导航体验。通常有以下两种情况&#xff1a; 常规的Activity(Regular activity) 这里所启动的Activity是作为应用程序的正常流程部分出现的。 指定的…

上海交大张拳石:神经网络的变量交互可解释性研究

文 | Qs.Zhang张拳石知乎可解释性研究一直有两副嘴脸&#xff0c;一副烈火烹油繁花似锦&#xff0c;一副如履薄冰零丁洋里叹零丁。在2018年我开始发知乎是为了“活着”——被刷榜为王的风气屡屡打击之后&#xff0c;一朝中稿&#xff0c;倒过一口气来&#xff0c;终于可以跟大家…

基于TensorFlow Serving的深度学习在线预估

一、前言 随着深度学习在图像、语言、广告点击率预估等各个领域不断发展&#xff0c;很多团队开始探索深度学习技术在业务层面的实践与应用。而在广告CTR预估方面&#xff0c;新模型也是层出不穷&#xff1a; Wide and Deep[^1]、DeepCross Network[^2]、DeepFM[^3]、xDeepFM[^…

python库Camelot从pdf抽取表格数据以及python库camelot安装及使用中的一些注意事项

一、python库camelot安装及使用中的一些注意事项 1&#xff09;camelot方法有两种解析模式&#xff1a;流解析&#xff08;stream&#xff09;、格子解析&#xff08;lattice&#xff09;&#xff0c;其中格子解析能够保留表格完整的样式&#xff0c;对于复杂表格来说要优于流…

LeetCode 561. 数组拆分 I

1. 题目 给定长度为 2n 的数组, 你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), …, (an, bn) &#xff0c;使得从1 到 n 的 min(ai, bi) 总和最大。 示例 1:输入: [1,4,3,2]输出: 4 解释: n 等于 2, 最大总和为 4 min(1, 2) min(3, 4).来源&#xff1a;力扣&…

Android官方开发文档Training系列课程中文版:通知用户之更新或移除通知

原文地址&#xff1a;http://android.xsoftlab.net/training/notify-user/managing.html#Removing 当需要在不同时段发布同一事件类型的通知时&#xff0c;应当避免创建新的通知。相反的&#xff0c;应当考虑更新原有的通知&#xff0c;比如更改通知的某些值或者添加一些信息给…

论文浅尝 - 计算机工程 | 知识图谱可视化查询技术综述

本文转载自公众号&#xff1a;计算机工程。知识图谱可视化查询技术综述王鑫, 傅强, 王林, 徐大为, 王昊奋知识图谱作为符号主义发展的产物&#xff0c;是人工智能技术和系统中的重要组成部分&#xff0c;其在百科知识、生物信息、社交网络以及网络安全等领域被广泛运用。知识图…

用VS Code直接浏览GitHub代码 | 12.1K星

文 | 金磊(发自凹非寺)源 | 量子位“看GitHub代码”这件事上&#xff0c;还在网页上点点点&#xff1f;用开发工具看代码&#xff0c;不香吗&#xff1f;于是&#xff0c;它来了&#xff0c;它来了——可以直接用VS Code方式打开GitHub代码的工具。而且在短短几天时间里&#x…

APPKIT打造稳定、灵活、高效的运营配置平台

一、背景 美团App、大众点评App都是重运营的应用。对于App里运营资源、基础配置&#xff0c;需要根据城市、版本、平台、渠道等不同的维度进行运营管理。如何在版本快速迭代过程中&#xff0c;保持运营资源能够被高效、稳定和灵活地配置&#xff0c;是我们团队面临的重大考验。…

python编程:tabula、pdfplumber、camelot进行表格数据识别

三大神器助力Python提取pdf文档信息&#xff1a;https://cloud.tencent.com/developer/article/1395339 python编程&#xff1a;tabula、pdfplumber、camelot进行表格数据识别&#xff1a; https://blog.csdn.net/mouday/article/details/85057226 Python&#xff1a;解析PDF…

Android官方开发文档Training系列课程中文版:通知用户之大视图通知

原文地址&#xff1a;http://android.xsoftlab.net/training/notify-user/expanded.html#big-view 通知在通知栏中以两种风格呈现&#xff1a;正常视图与大视图。只有在通知展开的时候才会展示大视图。这只有在通知处于通知栏顶部时或者用户点击了通知时才会出现。 大视图于A…

论文浅尝-WSDM | Stepwise Reasoning for Multi-Relation QA

论文笔记整理&#xff1a;谭亦鸣&#xff0c;东南大学博士。来源&#xff1a;WSDM ’20链接&#xff1a;https://dl.acm.org/doi/pdf/10.1145/3336191.33718121.介绍知识图谱问答旨在利用知识图谱的结构化信息回答以自然语言提出的问题。当面对多关系问题时&#xff0c;现有基于…

从零搭建基于知识图谱的问答系统(以医疗行业为例)

清华大学人工智能研究院院长张钹院士2020年发表署名文章&#xff0c;首次全面阐述第三代人工智能的理念&#xff0c;提出第三代人工智能的发展路径是融合第一代的知识驱动和第二代的数据驱动的人工智能。基于知识图谱的推理&#xff0c;恰恰体现了第三代人工智能的特点。知识图…