知识图谱(BILSTM_CRF项目完整实现)【第七章】

一、BILSTM、BILSTM_CRF介绍

BiLSTM 和 BiLSTM-CRF 都是 NLP(自然语言处理)里处理序列数据的经典模型,主要用来做序列标注(给句子里的每个词打标签)。

1.1BiLSTM 模型(双向长短期记忆网络)

作用: 提取上下文特征,理解词语在句子中的语义。

结构:由两个 LSTM组成

前向 LSTM:从左到右读句子,捕捉过去的信息

后向 LSTM:从右到左读句子,捕捉未来的信息

输出:把两个方向的结果拼接,得到完整上下文表示


核心优势

解决传统 RNN 的梯度消失问题,能记住长距离依赖

双向理解,能判断歧义(如 "苹果" 是水果还是公司)
缺点
只能独立预测每个词的标签,不考虑标签之间的逻辑约束

容易输出非法序列(如:人名中间突然接地名开头)

1.2BiLSTM-CRF 模型(BiLSTM + 条件随机场)

作用: 在 BiLSTM 基础上,保证标签序列的合法性,是序列标注(NER)的黄金标准。

结构

Embedding 层:词→向量

BiLSTM 层:提取上下文特征

全连接层:输出每个词对应各标签的发射分数

CRF 层:

学习标签转移矩阵(如:B-PER → I-PER 概率高,B-PER → B-LOC 概率低)

用Viterbi 算法找出全局最优、合法的标签序列

核心优势

BiLSTM:强大的上下文语义提取

CRF:强制标签符合语法规则,杜绝非法序列

比纯 BiLSTM准确率更高、结果更合理

1.3两者对比

BiLSTM:只看单个词,不管前后标签是否合理

BiLSTM-CRF:全局看整个句子,保证标签连贯合法

1.4典型应用场景

命名实体识别(NER):识别人名、地名、机构名

例:[B-PER]李[I-PER]明[O]在[B-LOC]北[I-LOC]京

词性标注(POS):给每个词标词性(名词、动词等)

分词 / 语义角色标注

总结:BiLSTM 是基础特征提取器,BiLSTM-CRF 是工业界最常用、效果最好的序列标注模型。这个项目我们使用BILSTM作为基线模型,BILSTM_CRF作为项目使用的序列标注模型。

二、CRF介绍

第六章中我们详细介绍了BILSTM模型,这里我详细介绍一下CRF模型

线性层的输出:(D*num_laycrs,seq_len,tag_size)

CRF接收的数据类型分为训练、测试

  • 训练时候的输入:线性层的输出、mask、真实标签
  • 测试时候的输入:线性层的输出、mask

mask的作用:因为神经网络同一批次的数据要求长度相同,所以需要使用mask把补齐序列使用的无效字符转换为0,避免影响到预测结果。

训练CRF模型的时候最重要的是计算损失(先使用CRF得出预测标签,再和真实标签计算损失)

CRF的输出:(D*num_layces,seq_len)+预测的标签序列

三、BiLSTM模型预测序列

BiLSTM_CRF.py

复制代码
pip install TorchCRF

import torch
import torch.nn as nn
from TorchCRF import CRF

from P03_NER.LSTM_CRF.config import Config
from P03_NER.LSTM_CRF.utils.data_loader import word2id, get_data


class NERLSTM_CRF(nn.Module):
    def __init__(self, embedding_dim, hidden_dim, tag2id, word2id, dropout=0.2):
        '''
        模型初始化
        :param embedding_dim: 嵌入层维度 300
        :param hidden_dim: BiLSTM输出时的维度
        :param tag2id: tag2id字典
        :param word2id: word2id字典
        :param dropout: 随机失活比例
        '''
        super(NERLSTM_CRF, self).__init__()
        self.name = "BiLSTM_CRF"
        self.embedding_dim = embedding_dim
        self.hidden_dim = hidden_dim
        self.tag_size = len(tag2id)
        self.vocab_size = len(word2id)

        # 词嵌入层
        self.embedding = nn.Embedding(self.vocab_size, self.embedding_dim)
        # BiLSTM层
        self.lstm = nn.LSTM(input_size=self.embedding_dim, hidden_size=self.hidden_dim//2, bidirectional=True, batch_first=True)
        # 随机失活
        self.dropout = nn.Dropout(dropout)
        # 线性层
        self.hidden2tag = nn.Linear(self.hidden_dim, self.tag_size)
        # CRF层
        self.crf = CRF(self.tag_size)

    def get_emission_score(self, input_ids, attention_mask):
        # print(f'input_ids: {input_ids.shape}')
        # print(f'attention_mask: {attention_mask.shape}')
        # 送入词嵌入层
        word_embeds = self.embedding(input_ids)
        # 送入BiLSTM层
        lstm_out, _ = self.lstm(word_embeds)
        # 送入随机失活层
        dropout_out = self.dropout(lstm_out)
        # print(f'dropout_out: {dropout_out.shape}')
        # 对位相乘,只保留有效位置的输出结果,将pad的部分置成0
        attention_mask = attention_mask.unsqueeze(-1)
        multi_out = dropout_out * attention_mask  # 自动广播并相乘
        # print(f'multi_out: {multi_out.shape}')
        # 送入线性层
        final_out = self.hidden2tag(multi_out)
        return final_out

    # 使用forward方法实现预测
    def forward(self, input_ids, attention_mask):
        # 获取发射得分
        emission_score = self.get_emission_score(input_ids, attention_mask)
        # 使用viterbi解码获取标签路径
        predict_result = self.crf.viterbi_decode(emission_score, attention_mask)
        return predict_result

    # 计算损失的函数
    def log_likelihood(self, input_ids, labels, attention_mask):
        # 获取发射得分
        emission_score = self.get_emission_score(input_ids, attention_mask)
        # 计算损失【将发射分数、真实标签、attention_mask直接输入给模型,就可以得到对数似然损失,加上负号就可以得到真正的需求求解的损失函数】
        loss = - self.crf(emission_score, labels, attention_mask.bool())
        return loss


if __name__ == '__main__':
    conf = Config()
    model = NERLSTM_CRF(conf.embedding_dim, conf.hidden_dim, conf.tag2id, word2id, conf.dropout)
    print(model)

    train_dataloader, val_dataloader = get_data()
    for input_ids, labels, attention_mask in train_dataloader:
        result = model(input_ids, attention_mask)
        print(f'result: {result}')
        # 计算损失
        loss = model.log_likelihood(input_ids, labels, attention_mask)
        print(f'loss: {loss}')
        break

执行结果:每个字符预测的标签对应的id;

可以很明显的发现预测的序列长度是不一样的,这是因为mask把之前补齐的长度变成了无效长度,帮助BILSTM只预测有效的标签。

相关推荐
liudanzhengxi29 分钟前
GitSubmodule避坑全攻略
人工智能·新人首发
用户4252108006031 分钟前
Claude Code Linux 服务器部署与配置
人工智能
OJAC11133 分钟前
学过Python却不敢投AI岗,他最后拿下12K offer
人工智能
Bigger34 分钟前
因为看不懂小棉袄的画,我写了个 AI 程序帮我“翻译”她的世界
前端·人工智能·ai编程
CeshirenTester36 分钟前
LangChain的工具调用 vs 原生Skill API:性能差在哪儿?
java·人工智能·langchain
爱问的艾文1 小时前
八周带你手搓AI应用-第二周-让AI更像人-第1天-流式输出改造
人工智能
多年小白1 小时前
【周末消息面汇总】2026年5月10日(周日)
人工智能·科技·机器学习·ai·金融
码农小白AI1 小时前
宠物用品耐磨检测走向标准化新阶段:IACheck让AI报告审核更无忧更稳定
人工智能
用户223586218201 小时前
MCP 是 Claude 的 什么? - claude-10
人工智能·claude·vibecoding
MediaTea1 小时前
Scikit-learn:从数据到结构——无监督学习的最小闭环
人工智能·学习·算法·机器学习·scikit-learn