NLP-信息抽取-NER-2015-BiLSTM+CRF(一):命名实体识别【预测每个词的标签】【评价指标:精确率=识别出正确的实体数/识别出的实体数、召回率=识别出正确的实体数/样本真实实体数】

本文主要是介绍NLP-信息抽取-NER-2015-BiLSTM+CRF(一):命名实体识别【预测每个词的标签】【评价指标:精确率=识别出正确的实体数/识别出的实体数、召回率=识别出正确的实体数/样本真实实体数】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、命名实体识别介绍

命名实体识别(Named Entity Recognition,NER)就是从一段自然语言文本中找出相关实体,并标注出其位置以及类型。是信息提取, 问答系统, 句法分析, 机器翻译等应用领域的重要基础工具, 在自然语言处理技术走向实用化的过程中占有重要地位. 包含行业, 领域专有名词, 如人名, 地名, 公司名, 机构名, 日期, 时间, 疾病名, 症状名, 手术名称, 软件名称等。具体可参看如下示例图:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第三方NER工具包无法识别专业领域的NE,需根据已有专业名词数据集来训练自用NER模型

1、命名实体识别的作用

  • 识别专有名词, 为文本结构化提供支持.
  • 主体识别, 辅助句法分析.
  • 实体关系抽取, 有利于知识推理.

2、命名实体识别常用方法:基于规则(正则表达式)、基于模型(BiLSTM+CRF)

基于规则: 针对有特殊上下文的实体, 或实体本身有很多特征的文本, 使用规则的方法简单且有效. 比如抽取文本中物品价格, 如果文本中所有商品价格都是“数字+元”的形式, 则可以通过正则表达式”\d*.?\d+元”进行抽取. 但如果待抽取文本中价格的表达方式多种多样, 例如“一千八百万”, “伍佰贰拾圆”, “2000万元”, 遇到这些情况就要修改规则来满足所有可能的情况. 随着语料数量的增加, 面对的情况也越来越复杂, 规则之间也可能发生冲突, 整个系统也可能变得不可维护. 因此基于规则的方式比较适合半结构化或比较规范的文本中的进行抽取任务, 结合业务需求能够达到一定的效果.

  • 优点: 简单, 快速.
  • 缺点: 适用性差, 维护成本高后期甚至不能维护.

基于模型: 从模型的角度来看, 命名实体识别问题实际上是序列标注问题。序列标注问题指的是模型的输入是一个序列, 包括文字, 时间等, 输出也是一个序列. 针对输入序列的每一个单元, 输出一个特定的标签. 以中文分词任务进行举例, 例如输入序列是一串文字: “我是中国人”, 输出序列是一串标签: “OOBII”, 其中BIO"组成了一种中文分词最基础的标签体系: B表示这个字是词的开始, I表示词的中间到结尾, O表示其他类型词(也可以用更多的字母来表示标签体系,“BIO”是最基础的一种标签体系). 因此我们可以根据输出序列"OOBII"进行解码, 得到分词结果"我\是\中国人”.

  • 序列标注问题涵盖了自然语言处理中的很多任务, 包括语音识别, 中文分词, 机器翻译, 命名实体识别等, 而常见的序列标注模型包括HMM, CRF, RNN, LSTM, GRU等模型.
  • 其中在命名实体识别技术上, 目前主流的命名实体识别技术是通过 “BiLSTM+CRF” 模型进行序列标注, 也是项目中要用到的模型.

3、医学文本特征

在这里插入图片描述

  • 简短精炼
  • 形容词相对较少
  • 泛化性相对较小
  • 医学名词错字率比较高
  • 同义词、简称比较多

4、常见问题

4.1 介绍下命名实体识别任务,如何标注?

命名实体识别是为了找出文本中具有特定意义的实体字符串边界,并归类到预定义类别。包括两个部分,实体边界划分与实体类别预测。通常有3种标注体系:IO、BIO、BIOES。大部分情况下,标签体系越复杂准确度也越高,但相应的训练时间也会增加。因此需要根据实际情况选择合适的标签体系,一般采用BIO模式。

4.2 命名实体识别的评价标准?

命名实体识别输入为文本序列,输出为类别序列。可看做基于token标签的多分类问题或者考虑实体边界与实体类型结合的基于实体的多分类问题。基于token标签的多分类不能真实反应识别的效果。一般基于实体进行多分类的评价,通过precision、recall、f1进行衡量。

4.3 介绍下HMM,如何应用到NER任务中?

HMM即隐马尔可夫模型,是一种统计模型,用来描述一个含有隐含未知参数的马尔可夫过程。

包含两个假设:齐次马尔科夫假设与观测独立假设。如果将NER数据的实体标签看做一个不可观测的隐状态, 而HMM模型描述的就是由这些隐状态序列(实体标记)生成可观测状态(可读文本)的过程。通过HMM的学习问题,采用极大似然估计对标注数据进行学习,估计模型的参数。预测时已知模型参数和观测序列,采用维特比解码求最有可能对应的状态序列。

4.4 介绍下CRF,如何应用到NER任务中?

这里说的CRF指的是用于序列标注问题的线性链条件随机场,是由输入序列来预测输出序列的判别式模型。CRF是条件概率分布模型P(Y|X),表示的是给定一组输入随机变量X的条件下另一组输出随机变量Y的马尔可夫随机场。CRF是序列标注问题的对数线性模型,通过定义状态特征与转移特征的特征函数,通过标注数据学习特征函数对应的权重。预测时采用维特比解码求最有可能对应的状态序列。

4.5 HMM与CRF的区别与联系?

CRF更加强大- CRF可以为任何HMM能够建模的事物建模,甚至更多。CRF可以定义更加广泛的特征集。 而HMM在本质上必然是局部的,而CRF就可以使用更加全局的特征。CRF可以有任意权重值,HMM的概率值必须满足特定的约束。

二、BiLSTM介绍

所谓的BiLSTM,就是(Bidirectional LSTM)双向LSTM. 单向的LSTM模型只能捕捉到从前向后传递的信息, 而双向的网络可以同时捕捉正向信息和反向信息, 使得对文本信息的利用更全面, 效果也更好.

在BiLSTM网络最终的输出层后面增加了一个线性层, 用来将BiLSTM产生的隐藏层输出结果投射到具有某种表达标签特征意义的区间, 具体如下图所示:

在这里插入图片描述
BiLSTM作用:记忆上下文。

如下图所示,如果用前馈神经网络(FNN)替代LSTM,则只能识别出槽位,不能识别出该槽位是目的地还是出发地。

在这里插入图片描述

BiLSTM模型实现:

  • 第一步: 实现类的初始化和网络结构的搭建.
  • 第二步: 实现文本向量化的函数.
  • 第三步: 实现网络的前向计算.

1、第一步: 实现类的初始化和网络结构的搭建

本段代码构建类BiLSTM, 完成初始化和网络结构的搭建。

总共3层:

  • 词嵌入层,
  • 双向LSTM层,
  • 全连接线性层
# 本段代码构建类BiLSTM, 完成初始化和网络结构的搭建
# 总共3层: 词嵌入层, 双向LSTM层, 全连接线性层
import torch
import torch.nn as nnclass BiLSTM(nn.Module):"""description: BiLSTM 模型定义"""def __init__(self, vocab_size, tag_to_id, input_feature_size, hidden_size,batch_size, sentence_length, num_layers=1, batch_first=True):"""description: 模型初始化:param vocab_size:          所有句子包含字符大小:param tag_to_id:           标签与 id 对照:param input_feature_size:  字嵌入维度( 即LSTM输入层维度 input_size ):param hidden_size:         隐藏层向量维度:param batch_size:          批训练大小:param sentence_length      句子长度:param num_layers:          堆叠 LSTM 层数:param batch_first:         是否将batch_size放置到矩阵的第一维度"""# 类继承初始化函数super(BiLSTM, self).__init__()# 设置标签与id对照self.tag_to_id = tag_to_id# 设置标签大小, 对应BiLSTM最终输出分数矩阵宽度self.tag_size = len(tag_to_id)# 设定LSTM输入特征大小, 对应词嵌入的维度大小self.embedding_size = input_feature_size# 设置隐藏层维度, 若为双向时想要得到同样大小的向量, 需要除以2self.hidden_size = hidden_size // 2# 设置批次大小, 对应每个批次的样本条数, 可以理解为输入张量的第一个维度self.batch_size = batch_size# 设定句子长度self.sentence_length = sentence_length# 设定是否将batch_size放置到矩阵的第一维度, 取值True, 或Falseself.batch_first = batch_first# 设置网络的LSTM层数self.num_layers = num_layers# 构建词嵌入层: 字向量, 维度为总单词数量与词嵌入维度# 参数: 总体字库的单词数量, 每个字被嵌入的维度self.embedding = nn.Embedding(vocab_size, self.embedding_size)# 构建双向LSTM层: BiLSTM (参数: input_size      字向量维度(即输入层大小),#                               hidden_size     隐藏层维度,#                               num_layers      层数,#                               bidirectional   是否为双向,#                               batch_first     是否批次大小在第一位)self.bilstm = nn.LSTM(input_size=input_feature_size,hidden_size=self.hidden_size,num_layers=num_layers,bidirectional=True,batch_first=batch_first)# 构建全连接线性层: 将BiLSTM的输出层进行线性变换self.linear = nn.Linear(hidden_size, self.tag_size)

代码实现位置: /data/doctor_offline/ner_model/bilstm.py

输入参数:

# 参数1:码表与id对照
char_to_id = {"双": 0, "肺": 1, "见": 2, "多": 3, "发": 4, "斑": 5, "片": 6,"状": 7, "稍": 8, "高": 9, "密": 10, "度": 11, "影": 12, "。": 13}# 参数2:标签码表对照
tag_to_id = {"O": 0, "B-dis": 1, "I-dis": 2, "B-sym": 3, "I-sym": 4}# 参数3:字向量维度
EMBEDDING_DIM = 200# 参数4:隐层维度
HIDDEN_DIM = 100# 参数5:批次大小
BATCH_SIZE = 8# 参数6:句子长度
SENTENCE_LENGTH = 20# 参数7:堆叠 LSTM 层数
NUM_LAYERS = 1

调用:

# 初始化模型
model = BiLSTM(vocab_size=len(char_to_id),tag_to_id=tag_to_id,input_feature_size=EMBEDDING_DIM,hidden_size=HIDDEN_DIM,batch_size=BATCH_SIZE,sentence_length=SENTENCE_LENGTH,num_layers=NUM_LAYERS)
print(model)

输出效果:

BiLSTM((embedding): Embedding(14, 200)(bilstm): LSTM(200, 50, batch_first=True, bidirectional=True)(linear): Linear(in_features=100, out_features=5, bias=True)
)

2、第二步:实现文本向量化的函数(将中文文本中的每个字映射为序列化的序号)

将句子中的每一个字符映射到码表中,比如:
char_to_id = {“双”: 0, “肺”: 1, “见”: 2, “多”: 3, “发”: 4, “斑”: 5, “片”: 6, “状”: 7, “稍”: 8, “高”: 9, “密”: 10, “度”: 11, “影”: 12, “。”: 13…}

# 本函数实现将中文文本映射为数字化的张量
def sentence_map(sentence_list, char_to_id, max_length):"""description: 将句子中的每一个字符映射到码表中:param sentence: 待映射句子, 类型为字符串或列表:param char_to_id: 码表, 类型为字典, 格式为{"字1": 1, "字2": 2}:return: 每一个字对应的编码, 类型为tensor"""# 字符串按照逆序进行排序, 不是必须操作sentence_list.sort(key=lambda c:len(c), reverse=True)# 定义句子映射列表sentence_map_list = []for sentence in sentence_list:# 生成句子中每个字对应的 id 列表sentence_id_list = [char_to_id[c] for c in sentence]# 计算所要填充 0 的长度padding_list = [0] * (max_length-len(sentence))# 组合sentence_id_list.extend(padding_list)# 将填充后的列表加入句子映射总表中sentence_map_list.append(sentence_id_list)# 返回句子映射集合, 转为标量return torch.tensor(sentence_map_list, dtype=torch.long)

代码实现位置: /data/doctor_offline/ner_model/bilstm.py

输入参数:

# 参数1:句子集合
sentence_list = ["确诊弥漫大b细胞淋巴瘤1年","反复咳嗽、咳痰40年,再发伴气促5天。","生长发育迟缓9年。","右侧小细胞肺癌第三次化疗入院","反复气促、心悸10年,加重伴胸痛3天。","反复胸闷、心悸、气促2多月,加重3天","咳嗽、胸闷1月余, 加重1周","右上肢无力3年, 加重伴肌肉萎缩半年"]# 参数2:码表与id对照
char_to_id = {"<PAD>":0}	# 初始化的码表# 参数3:句子长度
SENTENCE_LENGTH = 20

调用:

if __name__ == '__main__':for sentence in sentence_list:# 获取句子中的每一个字for _char in sentence:# 判断是否在码表 id 对照字典中存在if _char not in char_to_id:# 加入字符id对照字典char_to_id[_char] = len(char_to_id)# 将句子转为 id 并用 tensor 包装sentences_sequence = sentence_map(sentence_list, char_to_id, SENTENCE_LENGTH)print("sentences_sequence:\n", sentences_sequence)

输出效果:

sentences_sequence:
tensor([[14, 15, 16, 17, 18, 16, 19, 20, 21, 13, 22, 23, 24, 25, 26, 27, 28, 29, 30,  0],[14, 15, 26, 27, 18, 49, 50, 12, 21, 13, 22, 51, 52, 25, 53, 54, 55, 29, 30,  0],[14, 15, 53, 56, 18, 49, 50, 18, 26, 27, 57, 58, 59, 22, 51, 52, 55, 29, 0,  0],[37, 63, 64, 65, 66, 55, 13, 22, 61, 51, 52, 25, 67, 68, 69, 70, 71, 13, 0,  0],[37, 38, 39,  7,  8, 40, 41, 42, 43, 44, 45, 46, 47, 48,  0,  0,  0,  0, 0,  0],[16, 17, 18, 53, 56, 12, 59, 60, 22, 61, 51, 52, 12, 62,  0,  0,  0,  0, 0,  0],[ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13,  0,  0,  0,  0,  0, 0,  0],[31, 32, 24, 33, 34, 35, 36, 13, 30,  0,  0,  0,  0,  0,  0,  0,  0,  0, 0,  0]])

3、第三步: 实现网络的前向计算

将句子利用BiLSTM进行特征计算,分别经过Embedding->BiLSTM->Linear,获得发射矩阵(emission scores)。

BiLSTM层的输出维度是tag_size, 也就是每个单词 w i w_i wi 映射到 各个tag的发射概率值。

# 本函数实现类BiLSTM中的前向计算函数forward()
def forward(self, sentences_sequence):"""description: 将句子利用BiLSTM进行特征计算,分别经过Embedding->BiLSTM->Linear,获得发射矩阵(emission scores):param sentences_sequence: 句子序列对应的编码,若设定 batch_first 为 True,则批量输入的 sequence 的 shape 为(batch_size, sequence_length):return:    返回当前句子特征,转化为 tag_size 的维度的特征"""# 初始化隐藏状态值h0 = torch.randn(self.num_layers * 2, self.batch_size, self.hidden_size)# 初始化单元状态值c0 = torch.randn(self.num_layers * 2, self.batch_size, self.hidden_size)# 生成字向量, shape 为(batch, sequence_length, input_feature_size)# 注:embedding cuda 优化仅支持 SGD 、 SparseAdaminput_features = self.embedding(sentences_sequence)# 将字向量与初始值(隐藏状态 h0 , 单元状态 c0 )传入 LSTM 结构中# 输出包含如下内容:# 1, 计算的输出特征,shape 为(batch, sentence_length, hidden_size)#    顺序为设定 batch_first 为 True 情况, 若未设定则 batch 在第二位# 2, 最后得到的隐藏状态 hn , shape 为(num_layers * num_directions, batch, hidden_size)# 3, 最后得到的单元状态 cn , shape 为(num_layers * num_directions, batch, hidden_size)output, (hn, cn) = self.bilstm(input_features, (h0, c0))# 将输出特征进行线性变换,转为 shape 为 (batch, sequence_length, tag_size) 大小的特征sequence_features = self.linear(output)# 输出线性变换为 tag 映射长度的特征return sequence_features

代码实现位置: /data/doctor_offline/ner_model/bilstm.py

输入参数:

# 参数1:标签码表对照
tag_to_id = {"O": 0, "B-dis": 1, "I-dis": 2, "B-sym": 3, "I-sym": 4}# 参数2:字向量维度
EMBEDDING_DIM = 200# 参数3:隐层维度
HIDDEN_DIM = 100# 参数4:批次大小
BATCH_SIZE = 8# 参数5:句子长度
SENTENCE_LENGTH = 20# 参数6:堆叠 LSTM 层数
NUM_LAYERS = 1char_to_id = {"<PAD>":0}
SENTENCE_LENGTH = 20

调用:

if __name__ == '__main__':for sentence in sentence_list:for _char in sentence:if _char not in char_to_id:char_to_id[_char] = len(char_to_id)sentence_sequence = sentence_map(sentence_list, char_to_id, SENTENCE_LENGTH)model = BiLSTM(vocab_size=len(char_to_id), tag_to_id=tag_to_id, input_feature_size=EMBEDDING_DIM, \hidden_size=HIDDEN_DIM, batch_size=BATCH_SIZE, sentence_length=SENTENCE_LENGTH, num_layers=NUM_LAYERS)sentence_features = model(sentence_sequence)print("sequence_features:\n", sentence_features)

输出效果:

sequence_features:
tensor([[[ 4.0880e-02, -5.8926e-02, -9.3971e-02,  8.4794e-03, -2.9872e-01],[ 2.9434e-02, -2.5901e-01, -2.0811e-01,  1.3794e-02, -1.8743e-01],[-2.7899e-02, -3.4636e-01,  1.3382e-02,  2.2684e-02, -1.2067e-01],[-1.9069e-01, -2.6668e-01, -5.7182e-02,  2.1566e-01,  1.1443e-01],...[-1.6844e-01, -4.0699e-02,  2.6328e-02,  1.3513e-01, -2.4445e-01],[-7.3070e-02,  1.2032e-01,  2.2346e-01,  1.8993e-01,  8.3171e-02],[-1.6808e-01,  2.1454e-02,  3.2424e-01,  8.0905e-03, -1.5961e-01],[-1.9504e-01, -4.9296e-02,  1.7219e-01,  8.9345e-02, -1.4214e-01]],...[[-3.4836e-03,  2.6217e-01,  1.9355e-01,  1.8084e-01, -1.6086e-01],[-9.1231e-02, -8.4838e-04,  1.0575e-01,  2.2864e-01,  1.6104e-02],[-8.7726e-02, -7.6956e-02, -7.0301e-02,  1.7199e-01, -6.5375e-02],[-5.9306e-02, -5.4701e-02, -9.3267e-02,  3.2478e-01, -4.0474e-02],[-1.1326e-01,  4.8365e-02, -1.7994e-01,  8.1722e-02,  1.8604e-01],...[-5.8271e-02, -6.5781e-02,  9.9232e-02,  4.8524e-02, -8.2799e-02],[-6.8400e-02, -9.1515e-02,  1.1352e-01,  1.0674e-02, -8.2739e-02],[-9.1461e-02, -1.2304e-01,  1.2540e-01, -4.2065e-02, -8.3091e-02],[-1.5834e-01, -8.7316e-02,  7.0567e-02, -8.8845e-02, -7.0867e-02]],[[-1.4069e-01,  4.9171e-02,  1.4314e-01, -1.5284e-02, -1.4395e-01],[ 6.5296e-02,  9.3255e-03, -2.8411e-02,  1.5143e-01,  7.8252e-02],[ 4.1765e-03, -1.4635e-01, -4.9798e-02,  2.7597e-01, -1.0256e-01],...[-3.9810e-02, -7.6746e-03,  1.2418e-01,  4.9897e-02, -8.4538e-02],[-3.4474e-02, -1.0586e-02,  1.3861e-01,  4.0395e-02, -8.3676e-02],[-3.4092e-02, -2.3208e-02,  1.6097e-01,  2.3498e-02, -8.3332e-02],[-4.6900e-02, -5.0335e-02,  1.8982e-01,  3.6287e-03, -7.8078e-02],[-6.4105e-02, -4.2628e-02,  1.8999e-01, -2.9888e-02, -1.1875e-01]]],grad_fn=<AddBackward0>)

输出结果说明: 该输出结果为输入批次中句子的特征, 利用线性变换分别对应到每个汉字在每个tag上的得分. 例如上述标量第一个值:[ 4.0880e-02, -5.8926e-02, -9.3971e-02, 8.4794e-03, -2.9872e-01]表示的意思为第一个句子第一个字分别被标记为[“O”, “B-dis”, “I-dis”, “B-sym”, “I-sym”]的分数, 由此可以判断, 在这个例子中, 第一个字被标注为"O"的分数最高.

4、BILSTM模型完整代码

BiLSTM层的输出维度是tag_size, 也就是每个单词 w i w_i wi 映射到 各个tag的发射概率值

# 本段代码构建类BiLSTM, 完成初始化和网络结构的搭建
# 总共3层: 词嵌入层, 双向LSTM层, 全连接线性层
import torch
import torch.nn as nn# BiLSTM 模型定义
class BiLSTM(nn.Module):def __init__(self, vocab_size, tag_to_id, input_feature_size, hidden_size, batch_size, sentence_length, num_layers=1, batch_first=True):"""description: 模型初始化:param vocab_size:          所有句子包含字符大小【词汇表总数量】:param tag_to_id:           标签与 id 对照【 {"O": 0, "B-dis": 1, "I-dis": 2, "B-sym": 3, "I-sym": 4}】:param input_feature_size:  字嵌入维度( 即LSTM输入层维度 input_size ):param hidden_size:         隐藏层向量维度:param batch_size:          批训练大小:param sentence_length      句子长度:param num_layers:          堆叠 LSTM 层数:param batch_first:         是否将batch_size放置到矩阵的第一维度"""super(BiLSTM, self).__init__()  # 类继承初始化函数self.tag_to_id = tag_to_id  # 设置标签与id对照self.tag_size = len(tag_to_id)  # 设置标签大小, 对应BiLSTM最终输出分数矩阵宽度self.embedding_size = input_feature_size    # 设定LSTM输入特征大小, 对应词嵌入的维度大小self.hidden_size = hidden_size // 2 # 设置隐藏层维度, 若为双向时想要得到同样大小的向量, 需要除以2self.batch_size = batch_size    # 设置批次大小, 对应每个批次的样本条数, 可以理解为输入张量的第一个维度self.sentence_length = sentence_length  # 设定句子长度self.batch_first = batch_first  # 设定是否将batch_size放置到矩阵的第一维度, 取值True, 或Falseself.num_layers = num_layers    # 设置网络的LSTM层数self.embedding = nn.Embedding(vocab_size, self.embedding_size)  # 构建词嵌入层; vocab_size: 词汇表总单词数量; embedding_size: 每个字的词嵌入维度# 构建BiLSTM层【input_size:词向量维度(即输入层大小); hidden_size: 隐藏层维度; num_layers: 层数; bidirectional: 是否为双向; batch_first: 是否批次大小在第一位)self.bilstm = nn.LSTM(input_size=input_feature_size, hidden_size=self.hidden_size, num_layers=num_layers, bidirectional=True, batch_first=batch_first)  # 此处的hidden_size:【self.hidden_size = hidden_size // 2】self.linear = nn.Linear(hidden_size, self.tag_size) # 构建全连接线性层: 将BiLSTM的输出层进行线性变换【最终维度是tag的类型数量】 # 此处的hidden_size就是传入的参数hidden_size# 本函数实现类BiLSTM中的前向计算函数forward()【将句子利用BiLSTM进行特征计算,分别经过Embedding->BiLSTM->Linear,获得发射矩阵(emission scores),返回当前句子特征,转化为 tag_size 的维度的特征】def forward(self, sentences_sequence):  # entences_sequence: 句子序列对应的编码, 若设定 batch_first 为 True,则批量输入的 sequence 的 shape 为(batch_size, sequence_length)hidden0 = torch.randn(self.num_layers * 2, self.batch_size, self.hidden_size)    # 初始化隐藏状态值cell0 = torch.randn(self.num_layers * 2, self.batch_size, self.hidden_size)    # 初始化cell状态值input_features = self.embedding(sentences_sequence) # 生成字向量, shape 为(batch, sequence_length, input_feature_size)【注:embedding cuda 优化仅支持 SGD 、 SparseAdam】# bilstm层输出如下内容:# 1, output:输出【shape 为(batch_size, sentence_length, hidden_size)】【顺序为设定 batch_first 为 True 情况, 若未设定则 batch_size 在第二位】# 2, hn:最后时间步的隐藏状态 【shape 为(num_layers * num_directions, batch_size, hidden_size)】# 3, cn:最后时间步的Cell状态【shape 为(num_layers * num_directions, batch_size, hidden_size)】output, (hn, cn) = self.bilstm(input_features, (hidden0, cell0))    # 将字向量与初始值(隐藏状态初始值 hidden0 , cell状态初始值 cell0 )传入 LSTM 结构中sequence_features = self.linear(output) # 将输出特征进行线性变换,转为 shape 为 (batch, sequence_length, tag_size) 大小的特征return sequence_features    # 输出线性变换为 tag 映射长度的特征【最终维度是tag的类型数量】# 工具函数:本函数实现将中文文本映射为数字化的张量【将句子中的每一个字符映射到码表中, 返回每一个字对应的编码, 类型为tensor】
def sentence_map(sentence_list, char_to_id, max_length):    # sentence: 待映射句子, 类型为字符串或列表; char_to_id: 完整版码表, 类型为字典, 格式为{"字1": 1, "字2": 2};sentence_list.sort(key=lambda c:len(c), reverse=True)   # 字符串按照逆序进行排序, 不是必须操作sentence_map_list = []  # 定义句子映射列表for sentence in sentence_list:sentence_id_list = [char_to_id[c] for c in sentence]    # 生成句子中每个字对应的 id 列表【序列化的句子】padding_list = [0] * (max_length-len(sentence)) # 计算所要填充 0 的长度sentence_id_list.extend(padding_list)   # 组合sentence_map_list.append(sentence_id_list)  # 将填充后的列表加入句子映射总表中【序列化的句子列表】return torch.tensor(sentence_map_list, dtype=torch.long)    # 返回句子映射集合, 转为张量【返回:序列化的句子列表】# 工具函数:创建字符-序号映射字典
def char2id(char_to_id, sentence_list):for sentence in sentence_list:for _char in sentence:if _char not in char_to_id:char_to_id[_char] = len(char_to_id)return char_to_idif __name__ == '__main__':# 参数1:句子集合sentence_list = ["确诊弥漫大b细胞淋巴瘤1年", "反复咳嗽、咳痰40年,再发伴气促5天。", "生长发育迟缓9年。", "右侧小细胞肺癌第三次化疗入院"]# 参数2:汉字与id对照码表char_to_id = {"<PAD>": 0}  # 初始化的码表# 参数3:句子长度SENTENCE_LENGTH = 20# 参数4:标签码表对照tag_to_id = {"O": 0, "B-dis": 1, "I-dis": 2, "B-sym": 3, "I-sym": 4}# 参数5:字向量维度EMBEDDING_DIM = 200# 参数6:隐层维度HIDDEN_DIM = 100# 参数7:批次大小BATCH_SIZE = 4# 参数8:堆叠 LSTM 层数NUM_LAYERS = 1# 1、构建汉字-序号对应码表char_to_id = char2id(char_to_id, sentence_list) # 创建char_to_id汉字与id对照码表# 2、根据char_to_id码表将字符串句子文本转为序列化表示sentence_sequence = sentence_map(sentence_list, char_to_id, SENTENCE_LENGTH)print("sentence_sequence.shpae = {0}----sentence_sequence = \n{1}\n".format(sentence_sequence.shape, sentence_sequence))# 3、实例化模型model = BiLSTM(vocab_size=len(char_to_id), tag_to_id=tag_to_id, input_feature_size=EMBEDDING_DIM, hidden_size=HIDDEN_DIM, batch_size=BATCH_SIZE, sentence_length=SENTENCE_LENGTH, num_layers=NUM_LAYERS)print("model: ", model)# 4、通过模型得到序列化句子中的每个汉字的tag表达概率【每个汉字在每个tag上的得分】sentence_features = model(sentence_sequence)print("sentence_features.shpae = {0}----sentence_features = \n{1}\n".format(sentence_features.shape, sentence_features))

输出结果:

ssh://root@47.93.247.255:22/root/anaconda3/bin/python -u /data/doctor_offline/ner_model/bilstm.py
sentence_sequence.shpae = torch.Size([4, 20])----sentence_sequence = 
tensor([[14, 15, 16, 17, 18, 16, 19, 20, 21, 13, 22, 23, 24, 25, 26, 27, 28, 29, 30,  0],[37, 38, 39,  7,  8, 40, 41, 42, 43, 44, 45, 46, 47, 48,  0,  0,  0,  0,  0,  0],[ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13,  0,  0,  0,  0,  0,  0,  0],[31, 32, 24, 33, 34, 35, 36, 13, 30,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0]])sentence_features.shpae = torch.Size([4, 20, 5])----sentence_features = 
tensor([[[-0.1389,  0.0338,  0.1532, -0.1136, -0.0621],[ 0.0874, -0.2526, -0.1340, -0.1838, -0.0949],[-0.0882, -0.1751, -0.0677, -0.1099,  0.0795],[-0.0999, -0.2465, -0.2183,  0.1657, -0.0813],[-0.0022, -0.0362, -0.0282, -0.1996,  0.1867],[-0.0015, -0.0256, -0.0995, -0.1907,  0.1462],[ 0.1290,  0.1615, -0.1282, -0.0963,  0.0899],[ 0.1446, -0.0498, -0.1076,  0.0376, -0.1198],[ 0.0719, -0.2300, -0.0392,  0.0354,  0.0492],[-0.0085, -0.0866,  0.0783,  0.1802,  0.0266],[-0.0266, -0.1050, -0.1074, -0.0714,  0.1258],[-0.2334, -0.1496,  0.0507, -0.0399,  0.1245],[-0.0549, -0.1062,  0.0189, -0.1190,  0.0947],[ 0.2127, -0.0431, -0.0334, -0.0583,  0.0572],[ 0.2617, -0.1306, -0.2478,  0.0185, -0.0254],[ 0.1582,  0.0231,  0.1112, -0.1882,  0.1195],[ 0.3084,  0.0007,  0.2140, -0.2551,  0.2404],[ 0.0784,  0.0823, -0.0452, -0.1004, -0.1108],[ 0.2645,  0.0199, -0.2783, -0.1242, -0.0405],[ 0.1166, -0.0279,  0.1355, -0.2478, -0.0557]],[[-0.0221, -0.0436, -0.1802,  0.0448,  0.0517],[ 0.0469, -0.0873, -0.1866, -0.1121, -0.0372],[ 0.1139,  0.0719, -0.0171, -0.1958,  0.0423],[ 0.1153, -0.0461, -0.0654, -0.1380, -0.1022],[ 0.0093, -0.0765, -0.1502, -0.1107,  0.0768],[ 0.1161,  0.0030, -0.2444, -0.2255,  0.0137],[ 0.0762,  0.0662,  0.0076, -0.0135,  0.1286],[-0.0092,  0.0267,  0.1190, -0.0239,  0.2241],[-0.0475,  0.1052, -0.0475,  0.0289,  0.1960],[-0.0270, -0.0362,  0.0288,  0.0927,  0.1177],[-0.0535,  0.0816,  0.0935,  0.2074, -0.0816],[-0.0061,  0.2228,  0.0605, -0.0023,  0.0238],[ 0.1011, -0.0206, -0.0435, -0.3221, -0.0308],[ 0.2295,  0.2631,  0.1293, -0.4822,  0.0822],[ 0.1453,  0.1953,  0.2544, -0.3759,  0.0442],[ 0.1539,  0.2058,  0.2637, -0.3136, -0.0085],[ 0.1598,  0.2140,  0.2518, -0.2772, -0.0306],[ 0.1591,  0.2153,  0.2263, -0.2427, -0.0502],[ 0.1438,  0.1987,  0.1815, -0.1906, -0.0859],[ 0.0954,  0.1449,  0.1361, -0.1113, -0.1260]],[[ 0.1831, -0.1770, -0.0104,  0.1610, -0.1085],[ 0.2623,  0.0652, -0.1827, -0.0236, -0.1678],[ 0.1192,  0.0590, -0.1336,  0.0076,  0.1512],[-0.0304,  0.1055, -0.1486, -0.0601,  0.0876],[-0.0663,  0.0646, -0.0286, -0.0374,  0.2744],[ 0.0619,  0.0144, -0.0481, -0.1420,  0.2053],[ 0.1240,  0.0207, -0.0548, -0.2478, -0.0184],[ 0.0130,  0.0061, -0.1453, -0.1595,  0.1096],[-0.0148,  0.2643, -0.0448, -0.1963,  0.0510],[-0.0912, -0.1276, -0.0617, -0.0942, -0.1681],[ 0.0496,  0.0565, -0.2059, -0.3369,  0.1429],[ 0.1273,  0.0750, -0.0227, -0.2329,  0.0736],[ 0.1588, -0.0276,  0.0487,  0.0212,  0.1070],[ 0.1745,  0.0976,  0.2603, -0.2484, -0.0521],[ 0.1703,  0.1468,  0.2871, -0.2596, -0.0569],[ 0.1752,  0.1775,  0.3019, -0.2583, -0.0519],[ 0.1785,  0.2040,  0.3153, -0.2497, -0.0447],[ 0.1776,  0.2328,  0.3282, -0.2260, -0.0351],[ 0.1653,  0.2622,  0.3342, -0.1599, -0.0290],[ 0.1273,  0.2633,  0.2943, -0.0550,  0.0185]],[[ 0.0722,  0.0339, -0.2793, -0.0150,  0.0826],[ 0.0678, -0.0308,  0.0347, -0.1229, -0.0095],[-0.0262, -0.0251, -0.0107, -0.1373,  0.0980],[ 0.0927, -0.1573, -0.1421, -0.0923,  0.1980],[ 0.0977,  0.0286,  0.0303,  0.0571,  0.2332],[ 0.1933,  0.0145, -0.1637,  0.1374,  0.3501],[ 0.1239, -0.0021,  0.0452, -0.0581,  0.0789],[ 0.1195,  0.0247,  0.0203,  0.1014,  0.1502],[ 0.4034,  0.0358, -0.2396, -0.1338,  0.0848],[ 0.2128,  0.1202,  0.2143, -0.2778,  0.0352],[ 0.1909,  0.1404,  0.2351, -0.2659, -0.0088],[ 0.1854,  0.1564,  0.2455, -0.2601, -0.0272],[ 0.1850,  0.1688,  0.2514, -0.2553, -0.0368],[ 0.1862,  0.1804,  0.2548, -0.2498, -0.0436],[ 0.1881,  0.1928,  0.2560, -0.2419, -0.0499],[ 0.1900,  0.2077,  0.2538, -0.2291, -0.0569],[ 0.1908,  0.2261,  0.2452, -0.2065, -0.0630],[ 0.1866,  0.2479,  0.2237, -0.1647, -0.0619],[ 0.1683,  0.2699,  0.1807, -0.0827, -0.0448],[ 0.1224,  0.2449,  0.0946,  0.0894, -0.0302]]],grad_fn=<AddBackward0>)Process finished with exit code 0

三、CRF介绍

CRF(全称Conditional Random Fields), 条件随机场. 是给定输入序列的条件下, 求解输出序列的条件概率分布模型.

下面举两个应用场景的例子:

  • 场景一: 假设有一堆日常生活的给小朋友排拍的视频片段, 可能的状态有睡觉、吃饭、喝水、洗澡、刷牙、玩耍等, 大部分情况, 我们是能够识别出视频片段的状态. 但如果你只是看到一小段拿杯子的视频, 在没有前后相连的视频作为前后文参照的情况下, 我们很难知道拿杯子是要刷牙还是喝水. 这时, 可以用到CRF模型.

  • 场景二: 假设有分好词的句子, 我们要判断每个词的词性, 那么对于一些词来说, 如果我们不知道相邻词的词性的情况下, 是很难准确判断每个词的词性的. 这时, 我们也可以用到CRF.

基本定义: 我们将随机变量的集合称为随机过程. 由一个空间变量索引的随机过程, 我们将其称为随机场. 上面的例子中, 做词性标注时, 可以将{名词、动词、形容词、副词}这些词性定义为随机变量, 然后从中选择相应的词性, 而这组随机变量在某种程度上遵循某种概率分布, 将这些词性按照对应的概率赋值给相应的词, 就完成了句子的词性标注.

1、马尔科夫假设(HMM) v.s. 条件随机场(CRF)

马尔科夫假设,:当前位置的取值只和与它相邻的位置的值有关, 和它不相邻的位置的值无关。应用到我们上面的词性标注例子中, 可以理解为当前词的词性是根据前一个词和后一个词的词性来决定的, 等效于从词性前后文的概率来给出当前词的词性判断结果.

条件随机场(CRF):现实中可以做如下假设,假设一个动词或者副词后面不会连接同样的动词或者副词, 这样的概率很高. 那么, 可以假定这种给定隐藏状态(也就是词性序列)的情况下, 来计算观测状态的计算过程. 本质上CRF模型考虑到了观测状态这个先验条件, 这也是条件随机场中的条件一词的含义。而隐马尔可夫模型(HMM)不考虑先验条件

2、转移概率矩阵

首先假设我们需要标注的实体类型有以下几类:

{"O": 0, "B-dis": 1, "I-dis": 2, "B-sym": 3, "I-sym": 4}

其中dis表示疾病(disease), sym表示症状(symptom), B表示命名实体开头, I表示命名实体中间到结尾, O表示其他类型.

因此我们很容易知道每个字的可能标注类型有以上五种可能性, 那么在一个句子中, 由上一个字到下一个字的概率乘积就有5 × 5种可能性, 具体见下图所示【其中的概率数值是通过模型在所给语料的基础上训练得到每个词的tag,然后统计出的结果】:

在这里插入图片描述
最终训练出来结果大致会如上图所示, 其中下标索引为(i, j)的方格代表如果当前字符是第i行表示的标签, 那么下一个字符表示第j列表示的标签所对应的概率值. 以第二行为例, 假设当前第i个字的标签为B-dis, 那么第i+1个字最大可能出现的概率应该是I-dis.

转移概率矩阵是行数、列数都为tag-size的方阵。

3、发射概率矩阵

发射概率, 是指已知当前标签的情况下, 对应所出现各个不同字符的概率. 通俗理解就是当前标签比较可能出现的文字有哪些, 及其对应出现的概率.

下面是几段医疗文本数据的标注结果:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

可以得到以上句子的转移矩阵概率如下(比如:其中 28 表示标记为 “O” 的汉字转移到所有标签汉字的总转移次数):

在这里插入图片描述

对应的发射矩阵可以理解为如下图所示结果(其中:29表示标记为O的所有汉字、符号的总数量):

在这里插入图片描述

在这里插入图片描述

四、BiLSTM+CRF模型代码解析【优化方案:Bert代替BiLSTM】

BiLSTM+CRF模型结构:

  1. 模型的标签定义与整体架构
  2. 模型内部的分层展开
  3. CRF层的作用

1、模型的标签定义与整体架构

假设我们的数据集中有两类实体-人名, 地名, 与之对应的在训练集中有5类标签如下所示:

B-Person, I-Person, B-Organization, I-Organization, O# B-Person: 人名的开始
# I-Person: 人名的中间部分
# B-Organization: 地名的开始
# I-Organization: 地名的中间部分
# O: 其他非人名, 非地名的标签

假设一个句子有5个单词构成。序列 ( w 0 , w 1 , w 2 , w 3 , w 4 ) (w_0, w_1, w_2, w_3, w_4) (w0,w1,w2,w3,w4) 中的每一个单元 w i w_i wi 都代表着由一个字。

其中字/词嵌入向量是随机初始化的, 字/词嵌入是通过数据训练得到的, 所有的字/词嵌入在训练过程中都会调整到最优解。

这些字嵌入或词嵌入作为BiLSTM+CRF模型的输入, 而输出的是句子中每个字 w i w_i wi 的标签.

在这里插入图片描述

2、模型内部的分层展开

整个模型明显有两层, 第一层是BiLSTM层(输出一个 w i w_i wi t a g j tag_j tagj 的输出发射矩阵), 第二层是CRF层(转移矩阵), 将层的内部展开如下图所示:

在这里插入图片描述

BiLSTM层的输出为每一个标签的预测分值(发射矩阵), 例如对于单词 w 0 w_0 w0, BiLSTM层输出是

1.5 (B-Person), 0.9 (I-Person), 0.1 (B-Organization), 0.08 (I-Organization), 0.05 (O)

这些分值将作为CRF层的输入.

3、CRF层的作用

如果没有CRF层, 也可以训练一个BiLSTM命名实体识别模型, 如下图所示:

在这里插入图片描述
由于BiLSTM的输出为单元的每一个标签分值, 我们可以挑选分值最高的一个作为该单元的标签.例如, 对于单词 w 0 w_0 w0, "B-Person"的分值-1.5是所有标签得分中最高的, 因此可以挑选"B-Person"作为单词 w 0 w_0 w0 的预测标签. 同理, 可以得到 w 1 w_1 w1 - “I-Person”, w 2 w_2 w2 - “O”, w 3 w_3 w3 - “B-Organization”, w 4 w_4 w4 - “O”

虽然在没有CRF层的条件下我们也可以只使用BiLSTM模型得到序列中每个单元的预测标签, 但是不能保证标签的预测每次都是正确的。如果出现下图的BiLSTM层输出结果, 则明显预测是错误的.

在这里插入图片描述

CRF层能从训练数据中获得约束性的规则:CRF层可以为最后预测的标签添加一些约束来保证预测的标签是合法的

在训练数据训练的过程中, 这些约束可以通过CRF层自动学习到。比如以下规则:

  1. 句子中的第一个词总是以标签"B-"或者"O"开始, 而不是"I-"开始.
  2. 标签"B-label1 I-label2 I-label3 …", 其中的label1, label2, label3应该属于同一类实体。比如, "B-Person I-Person"是合法的序列, 但是"B-Person I-Organization"是非法的序列.
  3. 标签序列"O I-label"是非法序列, 任意实体标签的首个标签应该是"B-“, 而不是"I-”。比如, "O B-label"才是合法的序列

有了上述这些约束, 标签序列的预测中非法序列出现的概率将会大大降低。

要怎样得到这个CRF转移矩阵呢?实际上,CRF转移矩阵是BiLSTM-CRF模型的一个参数。在训练模型之前,你可以随机初始化转移矩阵的分数。这些分数将随着训练的迭代过程被更新,换句话说,CRF层可以自己学到这些约束条件

在CRF层也可以人为添加规则来实现人工干预,比如模型上线后会有BadCase出现,通过分析BadCase,将新的规则添加的CRF层;

4、损失函数的定义

CRF损失函数由两部分组成:

  • 真实路径的分数
  • 所有路径的总分数

真实路径的分数应该是所有路径中分数最高的。

详细参考:NLP:BiLSTM+CRF 的损失函数【BiLSTM+CRF模型适用于:中文分词、词性标注、命名实体识别】

BiLSTM层的输出维度是tag_size, 也就是每个单词 w i w_i wi 映射到 各个tag的发射概率值。

  • 假设BiLSTM的输出是发射矩阵 P P P, 其中 P ( i , j ) P(i,j) P(i,j) 代表单词 w i w_i wi 映射到 t a g j tag_j tagj 的非归一化概率。
  • 对于CRF层, 假设存在一个转移矩阵 A A A,其中 A ( i , j ) A(i,j) A(i,j)代表 t a g j tag_j tagj 转移到 t a g i tag_i tagi 的概率.

对于输入序列 X X X 对应的输出tag序列 y y y, 定义分数如下(本质上就是发射概率转移概率的累加和):

S ( X , y ) = ∑ i = 1 n P i , y i + ∑ i = 0 n A y i , y i + 1 = 发射概率+转移概率 \color{red}{S(X,y)=\sum_{i=1}^nP_{i,y_i}+\sum_{i=0}^nA_{y_i,y_{i+1}}=\text{发射概率+转移概率}} S(X,y)=i=1nPi,yi+i=0nAyi,yi+1=发射概率+转移概率

利用softmax函数, 为每一个正确的tag序列 y y y 定义一个概率值, 在真实的训练中, 只需要最大化似然概率 p ( y ∣ X ) p(y|X) p(yX)即可, 具体使用对数似然如下:

p ( y ∣ X ) = 贝 叶 斯 概 率 公 式 S ( X , y ) S ( X ) = S ( X , y ) ∑ y ~ ∈ Y x S ( X , y ~ ) = 进 行 s o f t m a x 处 理 e S ( X , y ) ∑ y ~ ∈ Y x e S ( X , y ~ ) p(y|X)\xlongequal{贝叶斯概率公式}\cfrac{S(X,y)}{S(X)}=\cfrac{S(X,y)}{\sum_{\tilde{y}∈Y_x}S(X,\tilde{y})}\xlongequal{进行softmax处理}\cfrac{e^S(X,y)}{\sum_{\tilde{y}∈Y_x}e^{S(X,\tilde{y})}} p(yX)

这篇关于NLP-信息抽取-NER-2015-BiLSTM+CRF(一):命名实体识别【预测每个词的标签】【评价指标:精确率=识别出正确的实体数/识别出的实体数、召回率=识别出正确的实体数/样本真实实体数】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1128870

相关文章

变量与命名

引言         在前两个课时中,我们已经了解了 Python 程序的基本结构,学习了如何正确地使用缩进来组织代码,并且知道了注释的重要性。现在我们将进一步深入到 Python 编程的核心——变量与命名。变量是我们存储数据的主要方式,而合理的命名则有助于提高代码的可读性和可维护性。 变量的概念与使用         在 Python 中,变量是一种用来存储数据值的标识符。创建变量很简单,

阿里开源语音识别SenseVoiceWindows环境部署

SenseVoice介绍 SenseVoice 专注于高精度多语言语音识别、情感辨识和音频事件检测多语言识别: 采用超过 40 万小时数据训练,支持超过 50 种语言,识别效果上优于 Whisper 模型。富文本识别:具备优秀的情感识别,能够在测试数据上达到和超过目前最佳情感识别模型的效果。支持声音事件检测能力,支持音乐、掌声、笑声、哭声、咳嗽、喷嚏等多种常见人机交互事件进行检测。高效推

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

【测试】输入正确用户名和密码,点击登录没有响应的可能性原因

目录 一、前端问题 1. 界面交互问题 2. 输入数据校验问题 二、网络问题 1. 网络连接中断 2. 代理设置问题 三、后端问题 1. 服务器故障 2. 数据库问题 3. 权限问题: 四、其他问题 1. 缓存问题 2. 第三方服务问题 3. 配置问题 一、前端问题 1. 界面交互问题 登录按钮的点击事件未正确绑定,导致点击后无法触发登录操作。 页面可能存在

业务中14个需要进行A/B测试的时刻[信息图]

在本指南中,我们将全面了解有关 A/B测试 的所有内容。 我们将介绍不同类型的A/B测试,如何有效地规划和启动测试,如何评估测试是否成功,您应该关注哪些指标,多年来我们发现的常见错误等等。 什么是A/B测试? A/B测试(有时称为“分割测试”)是一种实验类型,其中您创建两种或多种内容变体——如登录页面、电子邮件或广告——并将它们显示给不同的受众群体,以查看哪一种效果最好。 本质上,A/B测

【北交大信息所AI-Max2】使用方法

BJTU信息所集群AI_MAX2使用方法 使用的前提是预约到相应的算力卡,拥有登录权限的账号密码,一般为导师组共用一个。 有浏览器、ssh工具就可以。 1.新建集群Terminal 浏览器登陆10.126.62.75 (如果是1集群把75改成66) 交互式开发 执行器选Terminal 密码随便设一个(需记住) 工作空间:私有数据、全部文件 加速器选GeForce_RTX_2080_Ti

EMLOG程序单页友链和标签增加美化

单页友联效果图: 标签页面效果图: 源码介绍 EMLOG单页友情链接和TAG标签,友链单页文件代码main{width: 58%;是设置宽度 自己把设置成与您的网站宽度一样,如果自适应就填写100%,TAG文件不用修改 安装方法:把Links.php和tag.php上传到网站根目录即可,访问 域名/Links.php、域名/tag.php 所有模板适用,代码就不粘贴出来,已经打

音视频入门基础:WAV专题(10)——FFmpeg源码中计算WAV音频文件每个packet的pts、dts的实现

一、引言 从文章《音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息》中我们可以知道,通过FFprobe命令可以打印WAV音频文件每个packet(也称为数据包或多媒体包)的信息,这些信息包含该packet的pts、dts: 打印出来的“pts”实际是AVPacket结构体中的成员变量pts,是以AVStream->time_base为单位的显

Linux命令(11):系统信息查看命令

系统 # uname -a # 查看内核/操作系统/CPU信息# head -n 1 /etc/issue # 查看操作系统版本# cat /proc/cpuinfo # 查看CPU信息# hostname # 查看计算机名# lspci -tv # 列出所有PCI设备# lsusb -tv

【小迪安全笔记 V2022 】信息打点9~11

第9天 信息打点-CDN绕过篇&漏洞回链8接口探针&全网扫指&反向件 知识点: 0、CDN知识-工作原理及阻碍 1、CDN配置-域名&区域&类型 2、CDN绕过-靠谱十余种技战法 3、CDN绑定-HOSTS绑定指向访问 CDN 是构建在数据网络上的一种分布式的内容分发网。 CDN的作用是采用流媒体服务器集群技术,克服单机系统输出带宽及并发能力不足的缺点,可极大提升系统支持的并发流数目,减少或避