位置: IT常识 - 正文

Bert+LSTM+CRF命名实体识别pytorch代码详解(lstm crf)

编辑:rootadmin
Bert+LSTM+CRF命名实体识别pytorch代码详解 Bert+LSTM+CRF命名实体识别

推荐整理分享Bert+LSTM+CRF命名实体识别pytorch代码详解(lstm crf),希望有所帮助,仅作参考,欢迎阅读内容。

文章相关热门搜索词:bert crf lstm,bert crf lstm,bi-lstm crf,bert crf lstm,bi-lstm crf,bert crf lstm,bert-crf,bert crf lstm,内容如对您有帮助,希望把文章链接给更多的朋友!

从0开始解析源代码。

理解原代码的逻辑,具体了解为什么使用预训练的bert,bert有什么作用,网络的搭建是怎么样的,训练过程是怎么训练的,输出是什么

调试运行源代码

NER目标

NER是named entity recognized的简写,对人名、地名、机构名、日期时间、专有名词等进行识别。

结果输出标注方法

采用细粒度标注,就是对于每一个词都给一个标签,其中连续的词可能是一个标签,与原始数据集的结构不同,需要对数据进行处理,转化成对应的细粒度标注形式。

数据集形式修改

形式:

{"text": "浙商银行企业信贷部叶老桂博士则从另一个角度对五道门槛进行了解读。叶老桂认为,对目前国内商业银行而言,","label": {"name": {"叶老桂": [[9, 11],[32, 34]]},"company": {"浙商银行": [[0, 3]]}}}

修改后数据集对应格式:

sentence: ['温', '格', '的', '球', '队', '终', '于', '又', '踢', '了', '一', '场', '经', '典', '的', '比', '赛', ',', '2', '比', '1', '战', '胜', '曼', '联', '之', '后', '枪', '手', '仍', '然', '留', '在', '了', '夺', '冠', '集', '团', '之', '内', ',']label: ['B-name', 'I-name', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'B-organization', 'I-organization', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O']数据预处理

对于一个句子不进行分词,原因是NER为序列标注任务,需要确定边界,分词后就可能产生错误的分词结果影响效果(B-x,I-x这种连续性,分词后会影响元意思表达)。

def preprocess(self, mode): """ params: words:将json文件每一行中的文本分离出来,存储为words列表 labels:标记文本对应的标签,存储为labels examples: words示例:['生', '生', '不', '息', 'C', 'S', 'O', 'L'] labels示例:['O', 'O', 'O', 'O', 'B-game', 'I-game', 'I-game', 'I-game'] """np.savez_compressed(output_dir, words=word_list, labels=label_list)

保存的文件也还是一句是一句的,所以后续处理中只有CLS,不需要终止符。

数据集分集与分batchdef dev_split(dataset_dir): """split dev set""" data = np.load(dataset_dir, allow_pickle=True)#加载npz文件 words = data["words"] labels = data["labels"] x_train, x_dev, y_train, y_dev = train_test_split(words, labels, test_size=config.dev_split_size, random_state=0) return x_train, x_dev, y_train, y_dev

调用train_test_split实现分train和dev的数据集。

将数据转化形式,用idx表示,构造NERDataset类表示使用数据集 def __init__(self, words, labels, config, word_pad_idx=0, label_pad_idx=-1): self.tokenizer = BertTokenizer.from_pretrained(config.bert_model, do_lower_case=True)#调用预训练模型 self.label2id = config.label2id#字典 self.id2label = {_id: _label for _label, _id in list(config.label2id.items())}##字典 self.dataset = self.preprocess(words, labels)#数据集预处理 self.word_pad_idx = word_pad_idx self.label_pad_idx = label_pad_idx self.device = config.device def preprocess(self, origin_sentences, origin_labels): """ Maps tokens and tags to their indices and stores them in the dict data. examples: word:['[CLS]', '浙', '商', '银', '行', '企', '业', '信', '贷', '部'] sentence:([101, 3851, 1555, 7213, 6121, 821, 689, 928, 6587, 6956], array([ 1, 2, 3, 4, 5, 6, 7, 8, 9])) label:[3, 13, 13, 13, 0, 0, 0, 0, 0] """ data = [] sentences = [] labels = [] # eg. i am cutting tokenize: cutting->[cut,'##ing']自动修改形式变成单数或者恢复原型 for line in origin_sentences: # replace each token by its index # we can not use encode_plus because our sentences are aligned to labels in list type words = [] word_lens = [] for token in line: words.append(self.tokenizer.tokenize(token)) word_lens.append(len(token))#如果含有英文会出现上面的情况,中文没有分词一般是1 #>> [1]*9 # 变成单个字的列表,开头加上[CLS] words = ['[CLS]'] + [item for token in words for item in token] token_start_idxs = 1 + np.cumsum([0] + word_lens[:-1])# np.array:[1,2,3] 自动广播机制 每个+1 a[1,2,3] a[:-1]->[1,2] 求出每个词在没加【cls】的句首字母idx # 这里计数tokens在words中的索引,第一个起始位置+1(加了cls)了,所以每一个+1 sentences.append((self.tokenizer.convert_tokens_to_ids(words), token_start_idxs)) #单词转化成idx,直接调用函数即可 for tag in origin_labels: label_id = [self.label2id.get(t) for t in tag] #单个句子的tag idx labels.append(label_id) for sentence, label in zip(sentences, labels): data.append((sentence, label))#句子编码、token在words中的位置、对应的label(一个token可能占用多个word(cutting->cut+ing) return data

preprocess处理token和word,记录每个token在word中的起始位置用于后续的对齐,对于每个单词进行tokennize(中文无变化,英文可能会有,但数据处理过程中将单词分成字母,所以无影响),然后在句首加上开始字符,因为生成第一个单词也需要概率因此句首不能省略,然后就是将字符转化成idx存储,tag也转化成idx;

类中的功能函数def __getitem__(self, idx):#class使用索引 """sample data to get batch""" word = self.dataset[idx][0] label = self.dataset[idx][1] return [word, label]def __len__(self):#class 使用长度 """get dataset size""" return len(self.dataset)

可以索引访问与访问长度。

encode_plus可以直接编码,但这里不能使用:align限制

因为单词要和标签对应,直接tokennize后编码,不能确定与标签的对应关系;

tokennize()

对于英文一个token通过tokennize会得到多个word:cutting->cut+##ing;

np.cumsum(a)累计计数[1,1,1]--->[1,2,3]模型架构

首先要明确,是继承bert基类,然后自定义forward函数就建好网络了,基本结构试:

class Module(nn.Module): def __init__(self): super(Module, self).__init__() # ...... def forward(self, x): # ...... return xdata = ..... #输入数据# 实例化一个对象module = Module()# 前向传播module(data) # 而不是使用下面的# module.forward(data) 关于forward的解释

nn.module中实现时就在call函数中定义了调用forward,然后传参就自动调用了。

定义__call__方法的类可以当作函数调用,具体参考Python的面向对象编程。也就是说,当把定义的网络模型model当作函数调用的时候就自动调用定义的网络模型的forward方法。nn.Module 的__call__方法部分源码如下所示:

def __call__(self, *input, **kwargs):result = self.forward(*input, **kwargs)BERT模式:选择对应,在代码的不同部分都有切换(model.eval();model.train())trainevalpredictnonezero()函数a = mat([[1,1,0],[1,1,0],[1,0,3]])print(a.nonzero())#>>(array([0, 0, 1, 1, 2, 2], dtype=int64), array([0, 1, 0, 1, 0, 2], dtype=int64))squeeze()函数介绍

去掉为1的维度,如[[0,1,2],[1,2,3]]dim(1,2,3)-->squeeze(1)--->[[0,1,2].[1,2,3]]

CRF层训练

训练目标:lstm输出分数+转移分数+前面序列的累计转移分数也就是 emission Score和transition Score(ref),函数使用,初始设置只需要标签数目,后续forward需要batch;如果想要知道结果需要使用decode函数

>>> import torch>>> from torchcrf import CRF>>> num_tags = 5 # number of tags is 5>>> model = CRF(num_tags)emissions = torch.randn(seq_length, batch_size, num_tags) #初始输入>>> model(emissions, tags, mask=mask)tensor(-10.8390, grad_fn=<SumBackward0>)#得到这个句子的概率#没有tag预测>>> model.decode(emissions)[[3, 1, 3], [0, 1, 0]]

引用这个图:

模型构造:

class BertNER(BertPreTrainedModel): def __init__(self, config): super(BertNER, self).__init__(config) self.num_labels = config.num_labels self.bert = BertModel(config)#第一层 self.dropout = nn.Dropout(config.hidden_dropout_prob)#非线性层 self.bilstm = nn.LSTM(#LSTM层 input_size=config.lstm_embedding_size, # 1024 hidden_size=config.hidden_size // 2, # 1024 因为是双向LSTM,隐藏层大小为原来的一半 batch_first=True, num_layers=2, dropout=config.lstm_dropout_prob, # 0.5 非线性 bidirectional=True ) self.classifier = nn.Linear(config.hidden_size, config.num_labels) #得到每个词对于所有tag的分数 self.crf = CRF(config.num_labels, batch_first=True)#CEF层 self.init_weights()#初始化权重,先全部随机初始化,然后调用bert的预训练模型中的权重覆盖

直接使用pytorch已经实现的函数,设置好bert层,后面通过droupout非线性层随机失活,然后使加上双向LSTM,注意双向的隐藏层是将两个方向的直接拼接,因此每个的长度设置为总的隐藏层输出长度的一半;然后接线性层,得到的是对于这些tag的每一个的分数,对于每一个位置,都给出是n钟tag的分数,这些分数作为crf层得到输入;然后进入crf层;

初始化权重:对于预训练模型,已经有的参数直接加载,没有的参数将随机初始化。

Bert+LSTM+CRF命名实体识别pytorch代码详解(lstm crf)

设置前向传播训练,:

def forward(self, input_data, token_type_ids=None, attention_mask=None, labels=None, position_ids=None, inputs_embeds=None, head_mask=None): input_ids, input_token_starts = input_data outputs = self.bert(input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids, position_ids=position_ids, head_mask=head_mask, inputs_embeds=inputs_embeds) sequence_output = outputs[0] # 去除[CLS]标签等位置,获得与label对齐的pre_label表示 origin_sequence_output = [layer[starts.nonzero().squeeze(1)] for layer, starts in zip(sequence_output, input_token_starts)] # 将sequence_output的pred_label维度padding到最大长度 padded_sequence_output = pad_sequence(origin_sequence_output, batch_first=True) # dropout pred_label的一部分feature padded_sequence_output = self.dropout(padded_sequence_output) lstm_output, _ = self.bilstm(padded_sequence_output) # 得到判别值 logits = self.classifier(lstm_output) outputs = (logits,) if labels is not None:#如果标签存在就计算loss,否则就是输出线性层对应的结果,这样便于通过后续crf的decode函数解码得到预测结果。 loss_mask = labels.gt(-1) loss = self.crf(logits, labels, loss_mask) * (-1) outputs = (loss,) + outputs # contain: (loss), scores return outputs

如果标签存在就计算loss,否则就是输出线性层对应的结果,这样便于通过后续crf的decode函数解码得到预测结果。在train.py/evaluate()里面用到了:

batch_output = model((batch_data, batch_token_starts), token_type_ids=None, attention_mask=batch_masks)[0] #没有标签只会得到线性层的输出 # (batch_size, max_len - padding_label_len) batch_output = model.crf.decode(batch_output, mask=label_masks)#得到预测的标签

各个层的作用为:

bert

提供词的嵌入表示,通过大规模训练,得到的结果泛化性更强,因此使用预训练模型,然参数有个比较好的初始化值。

lstm

从这里开始是正式的模型内容,这里是双向lstm,能够学习句子的上下文内容,从而给出每个字的标注。

crf

由于原始句法约束,lstm没有学习到原始的句法约束,因此使用条件随机场crf层来限制句法要求,从而加强结果。loss为发射分数和转移分数统一的分数,越小越好

验证

使用f1 score,兼顾了分类模型的精确率和召回率,最大为1,最小为0,越大越好。

模型训练

训练时采用patience_counter策略,如果连续patience_counter次f1值没有提升,而且已经达到了最小训练次数,训练停止,代码实现为:

def train(train_loader, dev_loader, model, optimizer, scheduler, model_dir): """train the model and test model performance""" # reload weights from restore_dir if specified if model_dir is not None and config.load_before: model = BertNER.from_pretrained(model_dir) model.to(config.device) logging.info("--------Load model from {}--------".format(model_dir)) best_val_f1 = 0.0#最小值 patience_counter = 0#超过这个次数 f1值连续没有提升而且已经过了最小训练次数就终止 # start training for epoch in range(1, config.epoch_num + 1): train_epoch(train_loader, model, optimizer, scheduler, epoch) val_metrics = evaluate(dev_loader, model, mode='dev')#验证 val_f1 = val_metrics['f1']#得到f1值 logging.info("Epoch: {}, dev loss: {}, f1 score: {}".format(epoch, val_metrics['loss'], val_f1)) improve_f1 = val_f1 - best_val_f1#控制精度连续提升 if improve_f1 > 1e-5: best_val_f1 = val_f1 model.save_pretrained(model_dir) logging.info("--------Save best model!--------") if improve_f1 < config.patience: patience_counter += 1 else: patience_counter = 0 else: patience_counter += 1 # Early stopping and logging best f1 if (patience_counter >= config.patience_num and epoch > config.min_epoch_num) or epoch == config.epoch_num: logging.info("Best val f1: {}".format(best_val_f1)) break logging.info("Training Finished!")参数更新,学习率衰减

采用学习率分离,adamW优化采纳数,动态调整学习率的策略。

设置控制系数不衰减的项,然后optimizer_grouped_parameters要将全部的参数都写进去,注意写法的不同:crf层的参数学习率更高,而且写法不同是直接的parameters,见下文写法:

if config.full_fine_tuning: # model.named_parameters(): [bert, bilstm, classifier, crf] # 模型是哪个层中的参数 bert_optimizer = list(model.bert.named_parameters()) lstm_optimizer = list(model.bilstm.named_parameters()) classifier_optimizer = list(model.classifier.named_parameters()) no_decay = ['bias', 'LayerNorm.bias', 'LayerNorm.weight'] #控制系数不衰减的项 optimizer_grouped_parameters = [#其他参数在优化过程权重衰减 {'params': [p for n, p in bert_optimizer if not any(nd in n for nd in no_decay)], #bert中衰减项参数 'weight_decay': config.weight_decay}, {'params': [p for n, p in bert_optimizer if any(nd in n for nd in no_decay)], 'weight_decay': 0.0},#不衰减的也要写出来 {'params': [p for n, p in lstm_optimizer if not any(nd in n for nd in no_decay)],#lstm层的系数 'lr': config.learning_rate * 5, 'weight_decay': config.weight_decay}, {'params': [p for n, p in lstm_optimizer if any(nd in n for nd in no_decay)], 'lr': config.learning_rate * 5, 'weight_decay': 0.0}, {'params': [p for n, p in classifier_optimizer if not any(nd in n for nd in no_decay)],#线性层参数 'lr': config.learning_rate * 5, 'weight_decay': config.weight_decay}, {'params': [p for n, p in classifier_optimizer if any(nd in n for nd in no_decay)], 'lr': config.learning_rate * 5, 'weight_decay': 0.0}, {'params': model.crf.parameters(), 'lr': config.learning_rate * 5}#crf层的参数学习率更高,而且写法不同是直接的parameters ] # only fine-tune the head classifier 如果不微调也就是bert层全部使用原本的权重,不会根据数据集微调 # 问题:预训练模型的参数只包含bert的?那么这里的lstm层为什么不训练;预训练模型,对照表,给定单词(有一个初始顺序)给出编码然后进入后续模型 else: param_optimizer = list(model.classifier.named_parameters()) optimizer_grouped_parameters = [{'params': [p for n, p in param_optimizer]}] optimizer = AdamW(optimizer_grouped_parameters, lr=config.learning_rate, correct_bias=False) train_steps_per_epoch = train_size // config.batch_size scheduler = get_cosine_schedule_with_warmup(optimizer, num_warmup_steps=(config.epoch_num // 10) * train_steps_per_epoch, num_training_steps=config.epoch_num * train_steps_per_epoch) # Train the model logging.info("--------Start Training!--------") train(train_loader, dev_loader, model, optimizer, scheduler, config.model_dir)

源代码这里不微调逻辑存有问题,原github已提交issue,暂时没有回应(没用到)

结果分析

f1score最终为0.79;

在书籍、公司、游戏、政府、人名上f1 score都大于0.8,效果较好;

原数据:

模型BiLSTM+CRFRoberta+SoftmaxRoberta+CRFRoberta+BiLSTM+CRFaddress47.3757.5064.1163.15book65.7175.3280.9481.45company71.0676.7180.1080.62game76.2882.9083.7485.57government71.2979.0283.1481.31movie67.5383.2383.1185.61name71.4988.1287.4488.22organization73.2974.3080.3280.53position72.3377.3978.9578.82scene51.1662.5671.3672.86overall67.4775.9079.3479.64

这里使用的是bert预训练模型,可以看到从预训练模型上说,和roberta在各个数据上稍微差一些,但最后的差值和原本实验结果相近。

实验test时的bad—case分析

枪手这里系统错判为组织;

教委错判为政府;

彩票监管部门认为是政府,实际是组织;

中材中心认为是公司,实际是组织;

枪手错判;

一些景点和地名分不清;

以及这种

可以看出由于有了条件随机场的限制,没有明显的B-peron后面跟I-name这种错误,出现的错误大都是内容上的,即使是人也不一定分清,可见这个模型的强大。

参考

是对文章里面不涉及的部分的进一步解析,适合小白开箱使用。

源码为:传送门

本文链接地址:https://www.jiuchutong.com/zhishi/298402.html 转载请保留说明!

上一篇:代码会说话——pyttsx3简介(用代码说话)

下一篇:ORB_SLAM2+kinect稠密建图实战项目总结

  • 搜狗输入法汪仔怎么关闭(搜狗输入法汪仔怎么开)

    搜狗输入法汪仔怎么关闭(搜狗输入法汪仔怎么开)

  • 魅族17有红外功能线的吗(魅族17p有没有红外)

    魅族17有红外功能线的吗(魅族17p有没有红外)

  • 美图秀秀网络连接不顺畅(美图秀秀为何显示网络连接不到)

    美图秀秀网络连接不顺畅(美图秀秀为何显示网络连接不到)

  • 腾讯课堂怎么关摄像头(腾讯课堂怎么关麦)

    腾讯课堂怎么关摄像头(腾讯课堂怎么关麦)

  • 网易云可以开启一起听歌吗(网易云可以开启麦克风吗)

    网易云可以开启一起听歌吗(网易云可以开启麦克风吗)

  • 为什么退款原因选不了(为什么退款原因默认不喜欢)

    为什么退款原因选不了(为什么退款原因默认不喜欢)

  • 44w快充有多快(45w快充有多快)

    44w快充有多快(45w快充有多快)

  • 华为手机如何清空通讯录(华为手机如何清除试卷答案)

    华为手机如何清空通讯录(华为手机如何清除试卷答案)

  • 拼多多修改主图影响权重吗(拼多多修改主图后多久能看到)

    拼多多修改主图影响权重吗(拼多多修改主图后多久能看到)

  • 天翼网关跟路由器区别(天翼网关跟路由器的ip地址一样)

    天翼网关跟路由器区别(天翼网关跟路由器的ip地址一样)

  • 淘宝同步物流给好友怎么取消(淘宝同步物流给好友能看到价格吗)

    淘宝同步物流给好友怎么取消(淘宝同步物流给好友能看到价格吗)

  • 苹果收不到验证码是怎么回事(苹果收不到验证码怎么办)

    苹果收不到验证码是怎么回事(苹果收不到验证码怎么办)

  • 台式电脑不用键盘怎么打字(台式电脑不用键鼠怎么电亮屏幕)

    台式电脑不用键盘怎么打字(台式电脑不用键鼠怎么电亮屏幕)

  • 投影仪是什么东西(投影仪叫什么术语)

    投影仪是什么东西(投影仪叫什么术语)

  • 苹果手机震动怎么调(苹果手机震动怎么不振了)

    苹果手机震动怎么调(苹果手机震动怎么不振了)

  • 美团订后砍五折怎么砍(美团订单5折退款)

    美团订后砍五折怎么砍(美团订单5折退款)

  • 剪映怎么剪掉多余的音乐(剪映怎么剪掉多余的音乐时间)

    剪映怎么剪掉多余的音乐(剪映怎么剪掉多余的音乐时间)

  • airpods2买无线还是有线(airpods2无线充电需要单独买无线充电器吗)

    airpods2买无线还是有线(airpods2无线充电需要单独买无线充电器吗)

  • 怎么发送实况照片(怎么发送实况照片给微信好友)

    怎么发送实况照片(怎么发送实况照片给微信好友)

  • qq音乐投屏怎么设置

    qq音乐投屏怎么设置

  • 快手怎么看别人的动态(快手怎么看别人的点赞)

    快手怎么看别人的动态(快手怎么看别人的点赞)

  • 魅族16s什么时候上市(魅族16s什么时候停产的)

    魅族16s什么时候上市(魅族16s什么时候停产的)

  • word文档怎么靠左顶格(word文档怎么靠上居中)

    word文档怎么靠左顶格(word文档怎么靠上居中)

  • 快手第一个作品怎么火(快手第一个作品两千播放量正常吗)

    快手第一个作品怎么火(快手第一个作品两千播放量正常吗)

  • 华为nova9值得买吗详情(华为nova9值得买吗知乎)

    华为nova9值得买吗详情(华为nova9值得买吗知乎)

  • smss.exe是什么进程?详解Windows会话管理器中的smss.exe(smss.exe是干嘛的)

    smss.exe是什么进程?详解Windows会话管理器中的smss.exe(smss.exe是干嘛的)

  • mysql外键约束有什么要求(mysql外键约束的基本语法结构)

    mysql外键约束有什么要求(mysql外键约束的基本语法结构)

  • 计入职工福利费的有哪些
  • 增值税的账务处理 月末怎么结转 案例
  • 期末存货采用成本与可变现净值孰低法
  • 不是小微企业的小规模纳税人的税收优惠
  • 当月收到的发票可以当月认证吗
  • 计算错误多缴税怎么处理
  • 代开运输发票是否预征企业所得税?
  • 接受税务稽查补缴所得税账务处理怎么做?
  • 房地产计税毛利率什么意思
  • 存货周转率 高
  • 商贸运费核算到几号结束
  • 无形资产后续支出计入什么科目
  • 医院其他收入包括哪些项目
  • 城建税如何做会计分录
  • 专票上没写开户行合规吗
  • 股权转让收益算利润吗
  • 建安类增值税专用发票什么时候改的
  • 团队建设费用怎么入账
  • 一般纳税人设备租赁税率
  • 以前年度少计收入 会计怎么处理
  • 资本化利息支出是什么意思
  • 金税盘服务费计入什么会计科目
  • 每月结转本年利润会计分录
  • 其他公司垫付货款合法吗
  • 公司租赁的办公室装修费用可以抵进项税吗
  • win10右键没有打印
  • 代扣代缴的城建税和教育费附加按委托方所在地的税率
  • 自用房地产转换为采用公允价值
  • 溢价购入债权投资是为啥
  • PHP:Memcached::getServerList()的用法_Memcached类
  • php system函数的用法
  • 主营业务收入含义
  • 计提坏账准备对所得税费用的影响
  • 项目完工叫什么
  • 再保险业务核算方法
  • 公司挂靠社保的人员需要申报个人所得税吗?
  • 尚硅谷docker笔记
  • 公司买的公文包会计怎么做
  • 怎么冲暂估入账
  • 第二季度所得税可以弥补以前年度亏损吗
  • html导航链接
  • 工程发票预缴是什么意思
  • js与或运算符
  • 几个项目可以合到一起招标吗
  • 建筑工程承包合同
  • 个人所得税租房专项扣除标准
  • 什么是全面一次性奖金
  • 失业保险金领取多少钱
  • 增值税发票联次及其作用
  • 计提坏账准备如何做会计分录
  • 土地增值税应纳税额
  • 纳税期限与申报期限的区别
  • 进口向海关缴纳消费税
  • 库存商品如何结转生产成本
  • 零申报是不是什么都不用填
  • 结汇是把外币换成人民币吗
  • 小规模纳税人劳务派遣差额征税税率
  • 实缴资本在公司能查到吗
  • 汽车4s店有哪些功能
  • 事业单位会计制度
  • 股权部分转让如何计算
  • unins000.exe - unins000是什么意思
  • Windows tips小技巧
  • 如何使用升级助力器
  • win7的收藏夹在哪里
  • win10怎么用ios上网
  • linux系统怎么关闭137端口
  • 模型图怎么画
  • 获取本机ip地址命令
  • excel表格布局
  • javascript基础编程
  • 创建自己的app外卖平台
  • Error: String types not allowed (at 'layout_gravity' with value 'bottom/center_horizontal').
  • shell按行读取文件存入数组
  • unity3d总结
  • 获取intent传递的数据
  • python语言中
  • js设置点击效果
  • 进项税和销项税怎么抵扣举例说明
  • 建筑一般纳税人开3%
  • 免责声明:网站部分图片文字素材来源于网络,如有侵权,请及时告知,我们会第一时间删除,谢谢! 邮箱:opceo@qq.com

    鄂ICP备2023003026号

    网站地图: 企业信息 工商信息 财税知识 网络常识 编程技术

    友情链接: 武汉网站建设