LBERT论文详解

论文地址:https://arxiv.org/abs/2105.07148

代码地址:https://github.com/liuwei1206/LEBERT

模型创新

  1. LEBRT采用句子中的词语对(论文中称为Char-Word Pair)的特征作为输入
  2. 作者设计Lexicon adapter,在BERT的中间某一层注入词汇特征

  • 左图是在BERT之后的架构上面引入词汇信息

  • 右图是在BERT底层时引入词汇信息

Char-Word Pair

首先,对于给定的中文句子
s c = { c 1 , c 2 , . . . , c n } c i 代表句子中的第 i 个字符 s_c = \{c_1,c_2,...,c_n\}\quad c_i代表句子中的第i个字符 sc={c1,c2,...,cn}ci代表句子中的第i个字符
利用词典D匹配出句子中包含的潜在词汇(这里设定最多匹配出相关性最强的三个,不足三个则用PAD填充)。然后,每个字符和包含该字符的词汇组成词汇对,表示为
s c w = { ( c 1 , w s 1 ) , ( c 2 , w s 2 ) , . . . , ( c n , w s n ) } w s i 表示包含 c i 词汇组成的集合 s_{cw} = \{(c_1,ws_1),(c_2,ws_2),...,(c_n,ws_n)\}\quad ws_i表示包含c_i词汇组成的集合 scw={(c1,ws1),(c2,ws2),...,(cn,wsn)}wsi表示包含ci词汇组成的集合
此时就构成了Char-Words Pair Sequence

Lexicon adapter

将输入数据构建成Char-Words Pair Sequence后,句子中的每个位置包含了字符特征和词汇特征,为了把词汇特征注入到BERT中,作者设计了Lexicon adapter

Char-Words Pair Sequence中的第i个位置char-words pair表示为 ( h i c , x i w s ) (h_i^c,x_i^{ws}) (hic,xiws)

  • h i c h_i^c hic:第i个位置的字符特征,该特征是BERT的某个Transformer层的输出
  • x i w s = { x i 1 w , x i 2 w , . . . , x i m w } x_i^{ws} = \{x_{i1}^w,x_{i2}^w,...,x_{im}^w\} xiws={xi1w,xi2w,...,ximw} :第i个位置字符对应m个词汇的词向量

对词向量使用非线性变换,以至于和字符向量进行维度对齐
v i j w = W 2 ( t a n h ( W 1 x i j w + b 1 ) ) + b 2 j = 1 , . . . , m v_{ij}^w = W_2(tanh(W_1x_{ij}^w + b_1)) + b_2\quad\quad j=1,...,m vijw=W2(tanh(W1xijw+b1))+b2j=1,...,m

  • d c d_c dc:字符特征维度
  • d w d_w dw:词向量维度
  • W 1 ∈ R d c ∗ d w W_1\in R ^{d_c*d_w} W1Rdcdw
  • W 2 ∈ R d c ∗ d c W_2\in R ^{d_c*d_c} W2Rdcdc
  • v i j w ∈ R d c v_{ij}^w \in R^{d_c} vijwRdc

此时,对于Char-Words Pair Sequence中的第i个位置,进行维度变换后的词向量集合为
V i = ( v i 1 w , . . . , v i m w ) ∈ R m ∗ d c V_i = (v_{i1}^w,...,v_{im}^w)\in R^{m*d_c} Vi=(vi1w,...,vimw)Rmdc
此时,使用注意力机制对 V i V_i Vi中的m个词向量进行融合
a i = s o f t m a x ( h i c W a t t n V i T ) a_i = softmax(h_i^cW_{attn}V_i^T) ai=softmax(hicWattnViT)

  • h i c h_i^c hic:为query向量
  • V i V_i Vi:为value
  • a i a_i ai:使用双线性变换矩阵计算相似度得分得到

之后,再利用相似度得分对 V i V_i Vi进行加权求和得到融合后词特征:
z i w = ∑ j = 1 m a i j v i j w z_i^w = \sum_{j=1}^m a_{ij}v_{ij}^w ziw=j=1maijvijw
最后,再把字符特征和融合后的词特征相加得到:
h ^ i = h i c + z i w \hat h_i^ = h_i^c + z_i^w h^i=hic+ziw

Lexicon Enhanced BERT

对于给定的中文句子 s c = { c 1 , c 2 , . . . , c n } s_c = \{c_1,c_2,...,c_n\} sc={c1,c2,...,cn},将其构建成character-words pair sequence形式
s c w = { ( c 1 , w s 1 ) , ( c 2 , w s 2 ) , . . . , ( c n , w s n ) } s_{cw} = \{(c_1,ws_1),(c_2,ws_2),...,(c_n,ws_n)\} scw={(c1,ws1),(c2,ws2),...,(cn,wsn)}
{ c 1 , c 2 , . . . , c n } \{c_1,c_2,...,c_n\} {c1,c2,...,cn}输入到BERT的Input Embedder当中,得到输出 E = { e 1 , e 2 , . . . , e n } E = \{e_1,e_2,...,e_n\} E={e1,e2,...,en},之后将E($H^0 = E $)输入到BERT的Transformer encoder中,每个Transformer encoder表示为如下形式:
$$
G &= Layernormalization(H^{l-1 } + Multiheadattention(H^{l-1}))\

H &= Layernormalization(G + FFN(G))
$$
之后,通过Lexicon Adapter把词汇信息注入到第k层和第k+1层的Transformer层之间

第k层Transformer层的输出为 H k = { h 1 k , h 2 k , . . . , h n k } H^k = \{h_1^k,h_2^k,...,h_n^k\} Hk={h1k,h2k,...,hnk}。将其中的每一个Char-Words Pair( h i k , x i w s h_i^k,x_i^{ws} hik,xiws)利用Lexicon Adapter进行转化得到:
h ^ k = L A ( h i k , x i w s ) \hat h^k = LA(h_i^k,x_i^{ws}) h^k=LA(hik,xiws)

代码讲解

词向量处理

加载词向量(load_word_embedding)

input

  • word_embed_path:词向量的路径,这里选取腾讯的tencent-ailab-embedding-zh-d200-v0.2.0-s.txt
    • 词的个数为2000000,向量维度为200
  • max_scan_num:最多加载多少个词向量

output

  • word_embed_dict:每个词对应的词向量 200000 * 200
  • word_list:词集合,2000000
  • word_embed_dim:词的维度,200
    @classmethoddef build_trie_tree(cls, word_list, save_path):"""# todo 是否不将单字加入字典树中构建字典树:return:"""logger.info('building trie tree')trie_tree = Trie()for word in word_list:trie_tree.insert(word)write_pickle(trie_tree, save_path)return trie_treedef load_word_embedding(cls, word_embed_path, max_scan_num):"""todo 存在许多单字的,考虑是否去掉"""logger.info('loading word embedding from pretrain')word_embed_dict = dict()word_list = list()with open(word_embed_path, 'r', encoding='utf8') as f:for idx, line in tqdm(enumerate(f)):# 只扫描前max_scan_num个词向量if idx > max_scan_num:breakitems = line.strip().split()if idx == 0:assert len(items) == 2num_embed, word_embed_dim = itemsnum_embed, word_embed_dim = int(num_embed), int(word_embed_dim)else:assert len(items) == word_embed_dim + 1word = items[0]embedding = np.empty([1, word_embed_dim])embedding[:] = items[1:]word_embed_dict[word] = embeddingword_list.append(word)logger.info('word_embed_dim:{}'.format(word_embed_dim))logger.info('size of word_embed_dict:{}'.format(len(word_embed_dict)))logger.info('size of word_list:{}'.format(len(word_list)))return word_embed_dict, word_list, word_embed_dim
构建字典树(build_trie_tree)

input

  • word_list:word_list:词集合,2000000
  • save_path:字典树的保存路径(方便复用)

output

  • trie_tree:字典树
    @classmethoddef build_trie_tree(cls, word_list, save_path):"""# todo 是否不将单字加入字典树中"""logger.info('building trie tree')trie_tree = Trie()for word in word_list:trie_tree.insert(word)write_pickle(trie_tree, save_path)return trie_treedef write_pickle(x, path):with open(path, 'wb') as f:pickle.dump(x, f)
找到数据集中的所有单词(get_words_from_corpus)

input

  • files:训练、验证、测试的文件路径
  • save_file:文件保存路径
  • trie_tree:字典树

output

  • all_matched_words:找到了所有跟我们训练、验证、测试数据有关的所有词
    @classmethoddef get_words_from_corpus(cls, files, save_file, trie_tree):"""找出文件中所有匹配的单词"""logger.info('getting words from corpus')all_matched_words = set()for file in files:with open(file, 'r', encoding='utf8') as f:lines = f.readlines()for idx in trange(len(lines)):line = lines[idx].strip()data = json.loads(line)text = data['text']matched_words = cls.get_words_from_text(text, trie_tree)_ = [all_matched_words.add(word) for word in matched_words]all_matched_words = list(all_matched_words)all_matched_words = sorted(all_matched_words)write_lines(all_matched_words, save_file)return all_matched_words@classmethoddef get_words_from_text(cls, text, trie_tree):"""找出text中所有的单词"""length = len(text)matched_words_set = set()   # 存储匹配到的单词for idx in range(length):sub_text = text[idx:idx + trie_tree.max_depth]words = trie_tree.enumerateMatch(sub_text)_ = [matched_words_set.add(word) for word in words]matched_words_set = list(matched_words_set)matched_words_set = sorted(matched_words_set)return matched_words_setdef write_lines(lines, path, encoding='utf8'):with open(path, 'w', encoding=encoding) as f:for line in lines:f.writelines('{}\n'.format(line))
初始化模型的词向量(init_model_word_embedding)

input

  • corpus_words:所有跟我们训练、验证、测试数据有关的所有词
  • word_embed_dict:每个词对应的词向量 200000 * 200
  • save_embed_path:词向量的保存路径
  • save_word_vocab_path:词表的保存保存

output

  • model_word_embedding:模型的嵌入向量 --> 20857 * 200
  • word_vocab:模型的词表 --> 20857
  • embed_dim:嵌入维度 --> 200
    def init_model_word_embedding(self, corpus_words, word_embed_dict, save_embed_path, save_word_vocab_path):logger.info('initializing model word embedding')# 构建单词和id的映射word_vocab = Vocabulary(corpus_words, vocab_type='word')# embed_dim = len(word_embed_dict.items()[1].size)embed_dim = next(iter(word_embed_dict.values())).sizescale = np.sqrt(3.0 / embed_dim)model_word_embedding = np.empty([word_vocab.size, embed_dim])matched = 0not_matched = 0for idx, word in enumerate(word_vocab.idx2token):if word in word_embed_dict:model_word_embedding[idx, :] = word_embed_dict[word]matched += 1else:model_word_embedding[idx, :] = np.random.uniform(-scale, scale, [1, embed_dim])not_matched += 1logger.info('num of match:{}, num of not_match:{}'.format(matched, not_matched))write_pickle(model_word_embedding, save_embed_path)write_pickle(word_vocab, save_word_vocab_path)return model_word_embedding, word_vocab, embed_dim

数据加载格式

每个汉字对应的单词列表(get_char2words)

input:

  • text:文本

output:

  • char_index2words:文本中每个汉字所对应的词
    def get_char2words(self, text):"""获取每个汉字,对应的单词列表"""text_len = len(text)char_index2words = [[] for _ in range(text_len)]for idx in range(text_len):sub_sent = text[idx:idx + self.trie_tree.max_depth]  # speed using max depthwords = self.trie_tree.enumerateMatch(sub_sent)  # 找到以text[idx]开头的所有单词for word in words:start_pos = idxend_pos = idx + len(word)for i in range(start_pos, end_pos):char_index2words[i].append(word)return char_index2words
数据加载格式(collate)

output

  • 特征输入为:
'text': text, 
'input_ids': input_ids, 
'attention_mask': input_mask, 
'token_type_ids': token_type_ids,
'word_ids': word_ids, 
'word_mask': word_mask, 
'label_ids': label_ids
    def get_input_data(self, file):lines = load_lines(file)features = []cls_token_id = self.tokenizer.cls_token_idsep_token_id = self.tokenizer.sep_token_idpad_token_id = self.tokenizer.pad_token_ido_label_id = self.label_vocab.convert_token_to_id('O')pad_label_id = self.label_vocab.convert_token_to_id('[PAD]')for line in tqdm(lines):data = json.loads(line)text = data['text']labels = data['label']char_index2words = self.get_char2words(text)# 在开头与结尾分别添加[CLS]与[SEP]input_ids = [cls_token_id] + self.tokenizer.convert_tokens_to_ids(text) + [sep_token_id]label_ids = [o_label_id] + self.label_vocab.convert_tokens_to_ids(labels) + [o_label_id]word_ids_list = []word_pad_id = self.word_vocab.convert_token_to_id('[PAD]')for words in char_index2words:words = words[:self.max_word_num]word_ids = self.word_vocab.convert_tokens_to_ids(words)word_pad_num = self.max_word_num - len(words)word_ids = word_ids + [word_pad_id] * word_pad_numword_ids_list.append(word_ids)# 开头和结尾进行paddingword_ids_list = [[word_pad_id]*self.max_word_num] + word_ids_list + [[word_pad_id]*self.max_word_num]if len(input_ids) > self.max_seq_len:input_ids = input_ids[: self.max_seq_len]label_ids = label_ids[: self.max_seq_len]word_ids_list = word_ids_list[: self.max_seq_len]input_mask = [1] * len(input_ids)token_type_ids = [0] * len(input_ids)assert len(input_ids) == len(label_ids) == len(word_ids_list)# paddingpadding_length = self.max_seq_len - len(input_ids)input_ids += [pad_token_id] * padding_lengthinput_mask += [0] * padding_lengthtoken_type_ids += [0] * padding_lengthlabel_ids += [pad_label_id] * padding_lengthword_ids_list += [[word_pad_id]*self.max_word_num] * padding_lengthtext = ''.join(text)input_ids = torch.LongTensor(input_ids)label_ids = torch.LongTensor(label_ids)input_mask = torch.LongTensor(input_mask)token_type_ids = torch.LongTensor(token_type_ids)word_ids = torch.LongTensor(word_ids_list)word_mask = (word_ids != word_pad_id).long()feature = {'text': text, 'input_ids': input_ids, 'attention_mask': input_mask, 'token_type_ids': token_type_ids,'word_ids': word_ids, 'word_mask': word_mask, 'label_ids': label_ids}features.append(feature)return features

模型运行

模型初步加载
  • config.word_vocab_size:20857
  • config.word_embed_dim:200
  • LEBertModel:对其中的BertEncoder模块进行改造,后续会详细介绍
class LEBertSoftmaxForNer(BertPreTrainedModel):def __init__(self, config):super(LEBertSoftmaxForNer, self).__init__(config)self.word_embeddings = nn.Embedding(config.word_vocab_size, config.word_embed_dim)self.num_labels = config.num_labelsself.bert = LEBertModel(config)self.dropout = nn.Dropout(config.hidden_dropout_prob)self.classifier = nn.Linear(config.hidden_size, config.num_labels)self.loss_type = config.loss_typeself.init_weights()def forward(self, input_ids, attention_mask, token_type_ids, word_ids, word_mask, ignore_index, labels=None):word_embeddings = self.word_embeddings(word_ids)outputs = self.bert(input_ids=input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids,word_embeddings=word_embeddings, word_mask=word_mask)sequence_output = outputs[0]sequence_output = self.dropout(sequence_output)logits = self.classifier(sequence_output)outputs = (logits,) + outputs[2:]  # add hidden states and attention if they are hereif labels is not None:assert self.loss_type in ['lsr', 'focal', 'ce']if self.loss_type == 'lsr':loss_fct = LabelSmoothingCrossEntropy(ignore_index=ignore_index)elif self.loss_type == 'focal':loss_fct = FocalLoss(ignore_index=ignore_index)else:loss_fct = CrossEntropyLoss(ignore_index=ignore_index)# Only keep active parts of the lossif attention_mask is not None:active_loss = attention_mask.contiguous().view(-1) == 1active_logits = logits.contiguous().view(-1, self.num_labels)[active_loss]active_labels = labels.contiguous().view(-1)[active_loss]loss = loss_fct(active_logits, active_labels)else:loss = loss_fct(logits.view(-1, self.num_labels), labels.view(-1))outputs = (loss,) + outputsreturn outputs  # (loss), scores, (hidden_states), (attentions)

model.word_embeddings.weight.data.copy_(torch.from_numpy(processor.word_embedding))

  • 把词向量的word_embedding赋给LEBertSoftmaxForNer
简要概述
class LEBertModel(BertPreTrainedModel):def __init__(self, config):self.encoder = BertEncoder(config)class BertEncoder(nn.Module):def __init__(self, config):super().__init__()self.config = configself.layer = nn.ModuleList([BertLayer(config) for _ in range(config.num_hidden_layers)])self.word_embedding_adapter = WordEmbeddingAdapter(config)def forward():# 在第i层之后,进行融合if i == self.config.add_layer:hidden_states = self.word_embedding_adapter(hidden_states, word_embeddings, word_mask)
核心代码
class WordEmbeddingAdapter(nn.Module):def __init__(self, config):super(WordEmbeddingAdapter, self).__init__()self.dropout = nn.Dropout(config.hidden_dropout_prob)self.tanh = nn.Tanh()self.linear1 = nn.Linear(config.word_embed_dim, config.hidden_size)self.linear2 = nn.Linear(config.hidden_size, config.hidden_size)attn_W = torch.zeros(config.hidden_size, config.hidden_size)self.attn_W = nn.Parameter(attn_W)self.attn_W.data.normal_(mean=0.0, std=config.initializer_range)self.layer_norm = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps)def forward(self, layer_output, word_embeddings, word_mask):""":param layer_output:bert layer的输出,[b_size, len_input, d_model]:param word_embeddings:每个汉字对应的词向量集合,[b_size, len_input, num_word, d_word]:param word_mask:每个汉字对应的词向量集合的attention mask, [b_size, len_input, num_word]"""# transform# 将词向量,与字符向量进行维度对齐word_outputs = self.linear1(word_embeddings)word_outputs = self.tanh(word_outputs)word_outputs = self.linear2(word_outputs)word_outputs = self.dropout(word_outputs)   # word_outputs:[b_size, len_input, num_word, d_model]# 计算每个字符向量,与其对应的所有词向量的注意力权重,然后加权求和。采用双线性映射计算注意力权重# layer_output = layer_output.unsqueeze(2)    # layer_output:[b_size, len_input, 1, d_model]socres = torch.matmul(layer_output.unsqueeze(2), self.attn_W)  # [b_size, len_input, 1, d_model]socres = torch.matmul(socres, torch.transpose(word_outputs, 2, 3))  # [b_size, len_input, 1, num_word]socres = socres.squeeze(2)  # [b_size, len_input, num_word]socres.masked_fill_(word_mask, -1e9)  # 将pad的注意力设为很小的数socres = F.softmax(socres, dim=-1)  # [b_size, len_input, num_word]attn = socres.unsqueeze(-1)  # [b_size, len_input, num_word, 1]weighted_word_embedding = torch.sum(word_outputs * attn, dim=2)  # [N, L, D]   # 加权求和,得到每个汉字对应的词向量集合的表示layer_output = layer_output + weighted_word_embeddinglayer_output = self.dropout(layer_output)layer_output = self.layer_norm(layer_output)return layer_output

 # transform# 将词向量,与字符向量进行维度对齐word_outputs = self.linear1(word_embeddings)word_outputs = self.tanh(word_outputs)word_outputs = self.linear2(word_outputs)word_outputs = self.dropout(word_outputs)   # word_outputs:[b_size, len_input, num_word, d_model]

        # 计算每个字符向量,与其对应的所有词向量的注意力权重,然后加权求和。采用双线性映射计算注意力权重# layer_output = layer_output.unsqueeze(2)    # layer_output:[b_size, len_input, 1, d_model]socres = torch.matmul(layer_output.unsqueeze(2), self.attn_W)  # [b_size, len_input, 1, d_model]socres = torch.matmul(socres, torch.transpose(word_outputs, 2, 3))  # [b_size, len_input, 1, num_word]socres = socres.squeeze(2)  # [b_size, len_input, num_word]socres.masked_fill_(word_mask, -1e9)  # 将pad的注意力设为很小的数socres = F.softmax(socres, dim=-1)  # [b_size, len_input, num_word]attn = socres.unsqueeze(-1)  # [b_size, len_input, num_word, 1]

weighted_word_embedding = torch.sum(word_outputs * attn, dim=2) 

layer_output = layer_output + weighted_word_embedding

参照资料

论文解说:https://zhuanlan.zhihu.com/p/374720213

复现代码:https://github.com/yangjianxin1/LEBERT-NER-Chinese

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

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

相关文章

图形编辑器开发:是否要像 Figma 一样上 wasm

大家好,我是前端西瓜哥。 wasm 拿来做 Web 端的图形编辑器貌似是不错的选择。 因为图形处理会有相当多无法利用到 WebGL GPU 加速的 CPU 密集的计算。比如对一条复杂贝塞尔曲线进行三角化,对多个图形进行复杂图形的布尔运算。 图形编辑器性能天花板 F…

从娱乐产品到效率工具,ARknovv首款AR眼镜回归“AR本质”

如果说2022年是AR的元年,2023年则有望成为消费级AR眼镜的新拐点。 今年AR眼镜行业发展明显加快,且不断有大厂入局:今年2月小米发布无线AR眼镜探索版;3月荣耀也推出了一款全新的观影眼镜;而苹果在6月发布的MR头显Visio…

【计算机视觉中的 GAN 】 - 生成学习简介(1)

一、说明 在阅读本文之前,强烈建议先阅读预备知识,否则缺乏必要的推理基础。本文是相同理论GAN原理的具体化范例,阅读后有两个好处:1 巩固了已经建立的GAN基本概念 2 对具体应用的过程和套路进行常识学习,这种练习题一…

transformer理解

transformer的理解 Q、K、V的理解 核心是自注意力机制。即每个位置的结果为所有位置的加权平均和。为了得到每个位置的权重,需要Q*K得到。 整个多头的self-attention过程 单个encoder encoder-decoder encoder中的K和V会传到decoder中的encoder-decoder attention中。 …

IOS + Appium自动化教程

前言 项目闲置下来了,终于抽空有时间搞自动化了,看了下网上的教程基本通篇都是android自动化的介绍 ,ios自动化方面的内容网上简介的少之可怜。由于本人对ios自动化也是第一次做,甚至对苹果电脑的使用都不太熟悉,花了大…

Android kotlin系列讲解之最佳的UI体验 - Material Design 实战

目录 一、什么是Material Design二、Toolbar三、滑动菜单1、DrawerLayout2、NavigationView 四、悬浮按钮和可交互提示1、FloatingActionButton2、Snackbar3、CoordinatorLayout 五、卡片式布局1、MaterialCardView2、AppBarLayout 六、可折叠式标题栏1、CollapsingToolbarLayo…

linux驱动开发入门(学习记录)

2023.7.6及7.7 概述了解 一 1.驱动框架 2. 字符设备 块设备,存储相关 网络设备驱动 不一定属于某一种类型二 1.获取外设或传感器数据,控制外设,数据会提交给应用程序 2.编写一个驱动,及测试应用程序 app。驱动和应用完全分开 3.驱…

OpenCV:图像直方图计算

图像直方图为图像中像素强度的分布提供了有价值的见解。通过了解直方图,你可以获得有关图像对比度、亮度和整体色调分布的信息。这些知识对于图像增强、图像分割和特征提取等任务非常有用。 本文旨在为学习如何使用 OpenCV 执行图像直方图计算提供清晰且全面的指南。…

高清视频制作GIF怎么操作?一个工具在线完成视频转GIF

一段视频为了方便传输分享想要做成GIF动画的时候要怎么操作呢?很简单,只需要一款专业的GIF在线制作工具-GIF中文网,使用视频转GIF(https://www.gif.cn/)功能,上新MP4格式视频,能够快速制作1分钟…

Django学习笔记-视图(views)的使用

Django中可以使用views进行管理,类似于WPF的MVVM的ViewModel层,也相当于MVC架构的模Controller层。 一、基于函数的视图FBV(Function-Based View) 通过定义一个函数,包含HttpRequest对象作为参数,用来接受…

如何提高自己的软件测试水平之bug定位

同学们在面试投简历的时候会经常看到人家公司JD上写的要求之一,如下: 这句话大家不要以为随便写写的,在我工作的十几年过程中起码见过10个以上试用期没过的公司新人,公司在衡量一个测试工程师是否专业的标准之一就是:…

Linux下在终端输入密码隐藏方法

Linux系统中,如何将在终端输入密码时将密码隐藏? 最近做简单的登录界面时,不做任何操作的话,在终端输入密码的同时也会显示输入的密码是什么,这样对于隐蔽性和使用都有不好的体验。那么我就想到将密码用字符*隐藏起来…

freeswitch的mod_xml_curl模块

概述 freeswitch是一款简单好用的VOIP开源软交换平台。 随着fs服务的增多,每一台fs都需要在后台单独配置,耗时耗力,心力憔悴。 如果有一个集中管理配置的配置中心,统一管理所有fs的配置,并可以实现动态的修改配置就…

mybatis日志工厂

前言: 如果一个数据库操作,出现异常,我们需要排错,日志就是最好的助手 官方给我们提供了logImpl:指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 默认工厂: 在配置文件里添加&#xf…

深度剖析APP开发中的UI/UX设计

作为一个 UI/UX设计师,除了要关注 UI/UX设计之外,还要掌握移动开发知识,同时在日常工作中也需要对用户体验有一定的认知,在本次分享中,笔者就针对自己在工作中积累的一些经验来进行一个总结,希望能够帮助到…

如何连接远程服务器?快解析内内网穿透可以吗?

如今我们迎来了数字化转型的时代,众多企业来为了更好地推动业务的发展,常常需要在公司内部搭建一个远程服务器。然而,对于企业员工来说,在工作过程中经常需要与这个服务器进行互动,而服务器位于公司的局域网中&#xf…

简述IO(BIO NIO IO多路复用)

在unix网络变成中的五种IO模型: Blocking IO(阻塞IO) NoneBlocking IO (非阻塞IO) IO mulitplexing(IO多路复用) signal driven IO (信号驱动IO) asynchronous IO (异步IO) BIO BIO(Blocking IO)是一种阻塞IO模型,也是传统的IO操作模型之一…

RocketMQ概论

目录 前言: 1.概述 2.下载安装、集群搭建 3.消息模型 4.如何保证吞吐量 4.1.消息存储 4.1.1顺序读写 4.1.2.异步刷盘 4.1.3.零拷贝 4.2.网络传输 前言: RocketMQ的代码示例在安装目录下有全套详细demo,所以本文不侧重于讲API这种死…

数据结构:快速的Redis有哪些慢操作?

redis 为什么要这莫快?一个就是他是基于内存的,另外一个就是他是他的数据结构 说到这儿,你肯定会说:“这个我知道,不就是 String(字符串)、List(列表)、 Hash&#xff08…

1.Ansible

文章目录 Ansible概念作用特性总结 部署AnsibleAnsible模块commandshellcronusergroupcopyfilehostnamepingyumserice/systemdscriptmountarchiveunarchivereplacesetup inventory主机清单主机变量组变量组嵌套 Ansible 概念 Ansible是一个基于Python开发的配置管理和应用部署…