精华内容
下载资源
问答
  • lstm命名实体识别代码
    2022-07-11 12:20:56

    这是第一次在CSDN上记录自己的学习过程,加油。

    本文是记录b站博主“手写AI”的命名实体识别系列课程的学习笔记,构建五个py文件,直接运行即可。

    目录

    一、前言

    二、数据处理

    数据案例

    读取数据

     构建label2index

    三、数据迭代器

    接下来看一下如何构建数据类

    对三个函数进行一一解释:

    四、模型

    训练和验证

    当label不为None的时候,就是训练: 

    当label为None的时候,就是验证: 

    五、训练

    六、预测 

    六、完整代码

    config.py

      utils.py

     model.py

    train.py 

    predict.py 


    一、前言

    文章比较简单,就是利用BERT+BiLSTM,然后后面直接连上Linear层进行分类,比较简单。

    二、数据处理

    数据案例

    高 B-NAME 勇 E-NAME : O 男 O , O 中 B-CONT 国 I-CONT 国 I-CONT 籍 E-CONT , O 无 O 境 O 外 O 居 O 留 O 权 O , O

    读取数据

    def read_data(filename):
        with open(filename, 'r', encoding='utf8') as f:
            all_data = f.read().split('\n')
    
        all_text = []  # 用来保存所有的文本
        all_label = [] # 用来保存所有的标签
        text = []      # 用来保存一段文本
        labels = []    # 用来保存一段文本的标签
        for data in all_data:
            if data == '':
                all_text.append(text)
                all_label.append(labels)
                text = []
                labels = []
            else:
                t, l = data.split(' ')
                text.append(t)
                labels.append(l)
        return all_text, all_label

     构建label2index

    def build_label_2_index(all_label):
        label_2_index = {'PAD': 0, 'UNK': 1}
        for labels in all_label:
            for label in labels:
                if label not in label_2_index:
                    label_2_index[label] = len(label_2_index)
        return label_2_index, list(label_2_index)

    因为会设置模型输出的最大长度,所有,当句子不够长的时候,我们需要对标签进行填充[PAD],当遇到不认识的标签时[UNK].

    返回值:

    label_2_index:是字典,类似于{'PAD': 0, 'UNK': 1}

    list(label_2_index):是列表,['PAD', 'UNK']

     

    三、数据迭代器

    在pytorch里面,Dataset和DataLoader这两个类很重要,可以将数据处理好,然后就可以直接读取了。具体的操作流程都是固定的,主要是以下三个函数:

    def __init__(self):  # 初始化函数
        pass
    def __getitem__(self, item): # 读取一个数据
        pass
    def __len__(self)    # 返回整个数据的长度
        pass

    接下来看一下如何构建数据类

    class Data(Dataset):
        def __init__(self, all_text, all_label, tokenizer, label2index, max_len):
            self.all_text = all_text
            self.all_label = all_label
            self.tokenizer = tokenizer
            self.label2index = label2index
            self.max_len = max_len
    
        def __getitem__(self, item):
            text = self.all_text[item]
            labels = self.all_label[item][:self.max_len]
    
            # 需要对text编码,让bert可以接受
            text_index = self.tokenizer.encode(text,
                                               add_special_tokens=True,
                                               max_length=self.max_len + 2,
                                               padding='max_length',
                                               truncation=True,
                                               return_tensors='pt',
                                               )
            # 也需要将label进行编码
            # 那么我们需要构建一个函数来传入label2index
            # labels_index = [self.label2index.get(label, 1) for label in labels]
            # 上面那个就仅仅是转化,我们需要将label和text对齐
            labels_index = [0] + [self.label2index.get(label, 1) for label in labels] + [0] + [0] * (
                    self.max_len - len(text))
    
            # 这里需要注意text_index.squeeze(),squeeze()是默认去掉维度为1的那个维度
            # text_index的原始维度是:batch_size,1,seq_len
            # 在后续操作的过程中,将输入数据喂入模型时,如果不做处理,就会报错
            # 这里多输出一个len(text)!目的是在验证的时候,用的上,后面会介绍用处
    
            return text_index.squeeze(), torch.tensor(labels_index), len(text)
    
        def __len__(self):
            return len(self.all_text)

    对三个函数进行一一解释:

    1、 def __init__(self, all_text, all_label, tokenizer, label2index, max_len)

    需要在初始化函数中传入需要的参数,比如:

    all_text和all_label:你读取的所有文本和标签(数据处理部分);

    tokenizer:因为要将文本传入BERT模型中,直接传入肯定是不行的,需要将文本转成数字(这是transformers封装好的,直接调用就行);

    label2index:与上面的tokenizer相似,也需要将标签转成数字,这里直接编写代码即可(数据处理部分)

    max_len:设置你想要的最大长度

    四、模型

    class MyModel(nn.Module):
        def __init__(self, class_num):
            super(MyModel, self).__init__()
            self.class_num = class_num
    
            self.bert = BertModel.from_pretrained(BERT_PATH)
    
            self.lstm = nn.LSTM(768,
                                768 // 2,
                                bidirectional=True,
                                batch_first=True)
    
            self.linear = nn.Linear(768, class_num)
            self.loss_fn = nn.CrossEntropyLoss()
    
        def forward(self, batch_text, batch_label=None):
            output = self.bert(batch_text)
            bert_out0, bert_out1 = output[0], output[1]
            output1, _ = self.lstm(bert_out0)
            pre = self.linear(output1)
    
            if batch_label is not None:
                loss = self.loss_fn(pre.reshape(-1, pre.shape[-1]), batch_label.reshape(-1))
                return loss
            else:
                return torch.argmax(pre, dim=-1)

     将输入数据喂入模型,然后得到输出。

    当模型有标签数据的时候,那么就会返回损失值,然后反向传播,更新,梯度清零;

    当模型没有标签数据的时候,那么就是预测了,模型的输出应该就是标签类别,所以要在初始化函数中传入整个类别数(len(label2index))

    注意:此时,这个标签值,应该是数字,后续还需要将其转换为真是标签进行计算。

    所以在初始化函数中:

    def __init__(self, class_num),设置了一class_num==整个类别数(len(label2index))

    训练和验证

    当label不为None的时候,就是训练: 

     if batch_label is not None:
                loss = self.loss_fn(pre.reshape(-1, pre.shape[-1]),batch_label.reshape(-1))
                return loss
      为什么要将pre和batch_label的维度进行改变?

    首先,需要看pre的原始维度:

     pre.shape == torch.Size([batch_size, max_len, class_num])

    batch_label.shape == torch.Size([batch_size, max_len])

    其次,loss_fn = nn.CrossEntropyLoss(),需要输入的向量维度是二维的,所以我们需要对维度进行改变!

    最后,

    pre.reshape(-1, pre.shape[-1]) == (batch_size*max_len, class_num)

    batch_label.reshape(-1) == (batch_size*max_len)

    当label为None的时候,就是验证: 

    直接返回:return torch.argmax(pre, dim=-1)。

    五、训练

    def train():
        
        # 读取训练文件夹
        train_filename = os.path.join('data', 'train.txt')
        # 返回训练数据的文本和标签
        train_text, train_label = read_data(train_filename)
    
        # 验证集
        dev_filename = os.path.join('data', 'dev.txt')
        dev_text, dev_label = read_data(dev_filename)
        # print(train_filename)
        
        # 得到label2index, index2label
        label2index, index2label = build_label_2_index(train_label)
    
        # 数据迭代器
        train_data = Data(train_text, train_label, tokenizer, label2index, MAX_LEN)
        train_loader = DataLoader(train_data, batch_size=32, shuffle=False)
    
        dev_data = Data(dev_text, dev_label, tokenizer, label2index, MAX_LEN)
        dev_loader = DataLoader(dev_data, batch_size=32, shuffle=False)
    
        # 模型
        model = MyModel(len(label2index)).to(DEVICE)
        optimizer = torch.optim.AdamW(model.parameters(), lr=LEARNING_RATE)
    
        # 训练
    
        for epoch in range(EPOCHS):
            model.train()
            for batch_idx, data in enumerate(train_loader):
                batch_text, batch_label, batch_len = data
                # 将数据放到GPU上
                loss = model(batch_text.to(DEVICE), batch_label.to(DEVICE))
                loss.backward()
    
                optimizer.step()
                optimizer.zero_grad()
    
                if batch_idx % 10 == 0:
                    print(f'Epoch: {epoch}, BATCH: {batch_idx}, Training Loss:  {loss.item()}')
            # torch.save(model, MODEL_DIR + f'model_{epoch}.pth')
    
            model.eval()
            
            # 用来存放预测标签和真实标签
            all_pre = []
            all_tag = []
            
            for batch_text, batch_label, batch_len in dev_loader:
                
                # 因为是预测,所以在模型输入的地方,没有加入batch_label
                pre = model(batch_text.to(DEVICE))
                
                # 将pre从GPU上读下来,转成list
                pre = pre.cpu().numpy().tolist()
                batch_label = batch_label.cpu().numpy().tolist()
    
                # 还有一点要注意, from seqeval.metrics import f1_score
                # 在使用 f1_score的时候,所需要的标签应该是完整的,而不是经过填充过的
                # 所以我们需要将填充过的标签信息进行拆分怎么做呢?
                # 就需要将最开始没有填充过的文本长度记录下来,在__getitem__的返回量中增加一个长度量,那样我们就能知道文本真实长度
                # 然后就此进行切分,因为左边增加了一个开始符,需要去掉一个即可;右边按照长度来切分
    
                for p, t, l in zip(pre, batch_label, batch_len):
                    p = p[1: l + 1]
                    t = t[1: l + 1]
    
                    pre = [index2label[j] for j in p]
                    tag = [index2label[j] for j in t]
                    all_pre.append(pre)
                    all_tag.append(tag)
            f1_score_ = f1_score(all_pre, all_tag)
            p_score = precision_score(all_pre, all_tag)
            r_score = recall_score(all_pre, all_tag)
            # f1_score(batch_label_index, pre)
            print(f'p值={p_score}, r值={r_score}, f1={f1_score_}')

    六、预测 

    就没有跑那么多了,直接保存模型,读取一条数据进行预测。

    def predict():
        train_filename = os.path.join('data', 'train.txt')
        train_text, train_label = read_data(train_filename)
    
        test_filename = os.path.join('data', 'test.txt')
        test_text, _ = read_data(test_filename)
        text = test_text[1]
    
        print(text)
    
        inputs = tokenizer.encode(text,
                                  return_tensors='pt')
        inputs = inputs.to(DEVICE)
        model = torch.load(MODEL_DIR + 'model_1.pth')
        y_pre = model(inputs).reshape(-1)  # 或者是y_pre[0]也行,因为y_pre是一个batch,需要进行reshape
    
        _, id2label = build_label_2_index(train_label)
    
        label = [id2label[l] for l in y_pre[1:-1]]
        print(text)
        print(label)
    
    
    if __name__ == '__main__':
        predict()
    

     

    六、完整代码

     完整代码分为5部分:config.py, utils.py, model.py, train.py, predict.py

    config.py

    import torch
    from transformers import BertModel, BertTokenizer
    from torch.utils.data import DataLoader, Dataset
    EPOCHS = 2
    BATCH_SIZE = 64
    LEARNING_RATE = 2e-5
    MAX_LEN = 50
    DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'  # 调用GPU
    
    BERT_PATH = r'BERT_MODEL\roberta'  # 你自己的bert模型地址
    
    tokenizer = BertTokenizer.from_pretrained(BERT_PATH)
    MODEL_DIR = 'model/'   # 这是保存模型的地址,建在你代码的同一级即可
    

      utils.py

     

    import torch
    from torch.utils.data import DataLoader, Dataset
    
    
    def read_data(filename):
        with open(filename, 'r', encoding='utf8') as f:
            all_data = f.read().split('\n')
    
        all_text = []
        all_label = []
        text = []
        labels = []
        for data in all_data:
            if data == '':
                all_text.append(text)
                all_label.append(labels)
                text = []
                labels = []
            else:
                t, l = data.split(' ')
                text.append(t)
                labels.append(l)
        return all_text, all_label
    
    
    def build_label_2_index(all_label):
        label_2_index = {'PAD': 0, 'UNK': 1}
        for labels in all_label:
            for label in labels:
                if label not in label_2_index:
                    label_2_index[label] = len(label_2_index)
        return label_2_index, list(label_2_index)
    
    
    class Data(Dataset):
        def __init__(self, all_text, all_label, tokenizer, label2index, max_len):
            self.all_text = all_text
            self.all_label = all_label
            self.tokenizer = tokenizer
            self.label2index = label2index
            self.max_len = max_len
    
        def __getitem__(self, item):
            text = self.all_text[item]
            labels = self.all_label[item][:self.max_len]
    
            # 需要对text编码,让bert可以接受
            text_index = self.tokenizer.encode(text,
                                               add_special_tokens=True,
                                               max_length=self.max_len + 2,
                                               padding='max_length',
                                               truncation=True,
                                               return_tensors='pt',
                                               )
            # 也需要将label进行编码
            # 那么我们需要构建一个函数来传入label2index
            # labels_index = [self.label2index.get(label, 1) for label in labels]
            # 上面那个就仅仅是转化,我们需要将label和text对齐
            labels_index = [0] + [self.label2index.get(label, 1) for label in labels] + [0] + [0] * (
                    self.max_len - len(text))
    
            return text_index.squeeze(), torch.tensor(labels_index), len(text)
    
        def __len__(self):
            return len(self.all_text)
    

     model.py

    import torch.nn as nn
    from config import *
    
    
    class MyModel(nn.Module):
        def __init__(self, class_num):
            super(MyModel, self).__init__()
            self.class_num = class_num
    
            self.bert = BertModel.from_pretrained(BERT_PATH)
    
            self.lstm = nn.LSTM(768,
                                768 // 2,
                                bidirectional=True,
                                batch_first=True)
    
            self.linear = nn.Linear(768, class_num)
            self.loss_fn = nn.CrossEntropyLoss()
    
        def forward(self, batch_text, batch_label=None):
            output = self.bert(batch_text)
            bert_out0, bert_out1 = output[0], output[1]
            output1, _ = self.lstm(bert_out0)
            pre = self.linear(output1)
    
            if batch_label is not None:
                loss = self.loss_fn(pre.reshape(-1, pre.shape[-1]), batch_label.reshape(-1))
                return loss
            else:
                return torch.argmax(pre, dim=-1)
    
    

    train.py 

    from utils import *
    from model import *
    from config import *
    from seqeval.metrics import f1_score, precision_score, recall_score
    import os
    
    
    def train():
    
        # 读取训练文件夹
        train_filename = os.path.join('data', 'train.txt')
        # 返回训练数据的文本和标签
        train_text, train_label = read_data(train_filename)
    
        # 验证集
        dev_filename = os.path.join('data', 'dev.txt')
        dev_text, dev_label = read_data(dev_filename)
        # print(train_filename)
    
        # 得到label2index, index2label
        label2index, index2label = build_label_2_index(train_label)
    
        # 数据迭代器
        train_data = Data(train_text, train_label, tokenizer, label2index, MAX_LEN)
        train_loader = DataLoader(train_data, batch_size=32, shuffle=False)
    
        dev_data = Data(dev_text, dev_label, tokenizer, label2index, MAX_LEN)
        dev_loader = DataLoader(dev_data, batch_size=32, shuffle=False)
    
        # 模型
        model = MyModel(len(label2index)).to(DEVICE)
        optimizer = torch.optim.AdamW(model.parameters(), lr=LEARNING_RATE)
    
        # 训练
    
        for epoch in range(EPOCHS):
            model.train()
            for batch_idx, data in enumerate(train_loader):
                batch_text, batch_label, batch_len = data
                # 将数据放到GPU上
                loss = model(batch_text.to(DEVICE), batch_label.to(DEVICE))
                loss.backward()
    
                optimizer.step()
                optimizer.zero_grad()
    
                if batch_idx % 10 == 0:
                    print(f'Epoch: {epoch}, BATCH: {batch_idx}, Training Loss:  {loss.item()}')
            # torch.save(model, MODEL_DIR + f'model_{epoch}.pth')
    
            model.eval()
    
            # 用来存放预测标签和真实标签
            all_pre = []
            all_tag = []
    
            for batch_text, batch_label, batch_len in dev_loader:
    
                # 因为是预测,所以在模型输入的地方,没有加入batch_label
                pre = model(batch_text.to(DEVICE))
    
                # 将pre从GPU上读下来,转成list
                pre = pre.cpu().numpy().tolist()
                batch_label = batch_label.cpu().numpy().tolist()
    
                # 还有一点要注意, from seqeval.metrics import f1_score
                # 在使用 f1_score的时候,所需要的标签应该是完整的,而不是经过填充过的
                # 所以我们需要将填充过的标签信息进行拆分怎么做呢?
                # 就需要将最开始没有填充过的文本长度记录下来,在__getitem__的返回量中增加一个长度量,那样我们就能知道文本真实长度
                # 然后就此进行切分,因为左边增加了一个开始符,需要去掉一个即可;右边按照长度来切分
    
                for p, t, l in zip(pre, batch_label, batch_len):
                    p = p[1: l + 1]
                    t = t[1: l + 1]
    
                    pre = [index2label[j] for j in p]
                    tag = [index2label[j] for j in t]
                    all_pre.append(pre)
                    all_tag.append(tag)
            f1_score_ = f1_score(all_pre, all_tag)
            p_score = precision_score(all_pre, all_tag)
            r_score = recall_score(all_pre, all_tag)
            # f1_score(batch_label_index, pre)
            print(f'p值={p_score}, r值={r_score}, f1={f1_score_}')
            # print(2*p_score*r_score/(p_score+r_score))
    
    
    if __name__ == '__main__':
        train()
    

    predict.py 

    from utils import *
    from model import *
    from config import *
    import os
    
    
    def predict():
        train_filename = os.path.join('data', 'train.txt')
        train_text, train_label = read_data(train_filename)
    
        test_filename = os.path.join('data', 'test.txt')
        test_text, _ = read_data(test_filename)
        text = test_text[1]
    
        print(text)
    
        inputs = tokenizer.encode(text,
                                  return_tensors='pt')
        inputs = inputs.to(DEVICE)
        model = torch.load(MODEL_DIR + 'model_1.pth')
        y_pre = model(inputs).reshape(-1)  # 或者是y_pre[0]也行,因为y_pre是一个batch,需要进行reshape
    
        _, id2label = build_label_2_index(train_label)
    
        label = [id2label[l] for l in y_pre[1:-1]]
        print(text)
        print(label)
    
    
    if __name__ == '__main__':
        predict()
    

     

     

     

    更多相关内容
  • pytorch lstm+crf、bilstm+crf 、LSTM CRF 命名实体识别代码 代码和数据可以直接运行
  • 【NLP】BiLSTM 命名实体识别 手写代码

    千次阅读 2022-04-23 17:57:53
    【参考:pytorch_BiLSTM 命名实体识别 手写代码_哔哩哔哩_bilibili】 【参考:shouxieai/nlp-bilstm_crf-ner: nlp-bilstm+crf-ner】 """ 2022/4/22 """ import os import numpy as np import torch import torch.nn...

    【参考:pytorch_BiLSTM 命名实体识别 手写代码_哔哩哔哩_bilibili

    【参考:shouxieai/nlp-bilstm_crf-ner: nlp-bilstm+crf-ner

    数据样例

    高 B-NAME
    勇 E-NAME
    : O
    男 O
    , O
    中 B-CONT
    国 M-CONT
    国 M-CONT
    籍 E-CONT
    , O
    无 O
    境 O
    外 O
    居 O
    留 O
    权 O
    , O
    
    """
    2022/4/22
    """
    import os
    
    import numpy as np
    import torch
    import torch.nn as nn
    from torch.utils.data import DataLoader, Dataset
    import torch.optim as optim
    from tqdm import tqdm
    
    
    def build_corpus(data_type, make_vocab=True, data_dir="data"):
        word_lists, tag_lists = [], []
        with open(os.path.join(data_dir, data_type + '.char.bmes'), 'r', encoding='utf-8') as f:
            word_list, tag_list = [], []
            for line in f:
                if line != '\n':  # 数据中每句话会使用一个换行隔开
                    word, tag = line.strip().split(" ")
                    word_list.append(word)
                    tag_list.append(tag)
                else:
                    word_lists.append(word_list)  # 添加每句话
                    tag_lists.append(tag_list)
                    word_list, tag_list = [], []  # 清空
    
        word_lists = sorted(word_lists, key=lambda x: len(x), reverse=False)
        tag_lists = sorted(tag_lists, key=lambda x: len(x), reverse=False)
    
        # 如果make_vocab为True,还需要返回word2id和tag2id
        if make_vocab:
            word2id = build_map(word_lists)
            tag2id = build_map(tag_lists)
            word2id['<UNK>'] = len(word2id)
            word2id['<PAD>'] = len(word2id)
            tag2id['<PAD>'] = len(tag2id)
            return word_lists, tag_lists, word2id, tag2id
    
        return word_lists, tag_lists
    
    
    def build_map(lists):
        """
        :param lists: 二维矩阵
        :return: 字典map
        """
        maps = {}
        for list in lists:
            for e in list:
                if e not in maps:
                    maps[e] = len(maps)  # 编号自动递增
        return maps
    
    
    class MyDataset(Dataset):
        def __init__(self, datas, tags, word2idx, tag2idx):
            self.datas = datas
            self.tags = tags
            self.word2idx = word2idx
            self.tag2idx = tag2idx
    
        def __getitem__(self, index):
            data = self.datas[index]
            tag = self.tags[index]
            # 如果word2idx中没有该词就用UNK代替
            data_index = [self.word2idx.get(i, self.word2idx["<UNK>"]) for i in data]  # 获取每句话所有字的索引
            tag_index = [self.tag2idx[i] for i in tag]
    
            return data_index, tag_index
    
        def __len__(self):
            return len(self.datas)
    
        def pro_batch_data(self, batch_datas):
            # 因为每句话的长度不一样,拼接数据维度不一致会导致错误,所以自己手动拼接 未完全理解
            datas = []
            tags = []
            batch_lens = []
    
            for data, tag in batch_datas:
                datas.append(data)
                tags.append(tag)
                batch_lens.append(len(data))
            batch_max_len = max(batch_lens)
    
            # 填充至相同长度
            datas = [i + [self.word2idx['<PAD>']] * (batch_max_len - len(i)) for i in datas]  # i是,每句话
            tags = [i + [self.tag2idx['<PAD>']] * (batch_max_len - len(i)) for i in tags]
    
            # return torch.IntTensor(datas), torch.LongTensor(tags)
            return torch.tensor(datas, dtype=torch.int64), torch.tensor(tags, dtype=torch.long)
    
    
    class Mymodel(nn.Module):
        def __init__(self, corpus_num, embedding_dim, hidden_num, class_num, bi=True):
            super(Mymodel, self).__init__()
            self.embedding = nn.Embedding(corpus_num, embedding_dim)
            self.lstm = nn.LSTM(input_size=embedding_dim,
                                hidden_size=hidden_num,
                                batch_first=True,
                                bidirectional=bi)
            if bi:
                self.classifier = nn.Linear(hidden_num * 2, class_num)
            else:
                self.classifier = nn.Linear(hidden_num, class_num)
    
        def forward(self, batch_data):
            x = self.embedding(batch_data)
            out, _ = self.lstm(x)
            predict = self.classifier(out)
            return predict
    
    
    if __name__ == "__main__":
        # 训练集
        train_data, train_tag, word2id, tag2id = build_corpus('train')
        id2tag = [i for i in tag2id]
        # 验证集
        dev_data, dev_tag = build_corpus('dev', make_vocab=False)
        corpus_num = len(train_data)
        class_num = len(train_tag)
    
        epochs = 20
        train_batch_size = 30
        dev_batch_size = 30
        embedding_dim = 100
        hidden_num = 107
        bi = True
        lr = 0.001
    
        train_dataset = MyDataset(train_data, train_tag, word2id, tag2id)
    
        train_dataloader = DataLoader(train_dataset, batch_size=train_batch_size, shuffle=False
                                      , collate_fn=train_dataset.pro_batch_data)
    
        dev_dataset = MyDataset(dev_data, dev_tag, word2id, tag2id)
    
        dev_dataloader = DataLoader(dev_dataset, batch_size=dev_batch_size, shuffle=False
                                    , collate_fn=dev_dataset.pro_batch_data)
    
        model = Mymodel(corpus_num, embedding_dim, hidden_num, class_num, bi)
        criterion = nn.CrossEntropyLoss()
        optim = optim.Adam(model.parameters(), lr=lr)
    
        if os.path.exists('model.pth') is False:
    
            for i in range(epochs):
                model.train()
                train_loss = 0
                for train_data, train_tag in tqdm(train_dataloader):
                    optim.zero_grad()
                    predict = model.forward(train_data)
                    # predict (5,4,3821) train_tag(5,4)
                    predict = predict.reshape(-1, predict.shape[-1])  # (20,3821)
                    train_tag = train_tag.reshape(-1)  # (20)
                    loss = criterion(predict, train_tag)
                    loss.backward()
                    optim.step()
    
                    train_loss += loss.item() / predict.size(0)
    
                train_loss = train_loss / len(train_dataloader.dataset)  # 计算平均loss
                print('Epoch: {} \tTraining Loss: {:.6f}'.format(i, train_loss))
            # 保存模型参数 【参考:[pytorch保存模型的两种方式_SCU-JJkinging的博客-CSDN博客](https://blog.csdn.net/qq_41845478/article/details/116023691)】
            torch.save({'model': model.state_dict()}, 'model.pth')
        else:
            # 读取模型
            # model = Mymodel()
            state_dict = torch.load('model.pth')
            model.load_state_dict(state_dict['model'])
    
        eval=True
        if eval:
        # 验证
            model.eval()
            with torch.no_grad():
                dev_loss = 0
                real_label = []
                predict_label = []
                for dev_data, dev_tag in tqdm(dev_dataloader):
                    predict = model.forward(dev_data)
                    # predict (5,4,3821) dev_tag(5,4)
                    predict = predict.reshape(-1, predict.shape[-1])  # (20,3821)
                    predcit_class = torch.argmax(predict, dim=1)
                    predict_label.append(predcit_class.numpy())
    
                    dev_tag = dev_tag.reshape(-1)  # (20,)
                    real_label.append(dev_tag.numpy())
    
                    loss = criterion(predict, dev_tag)
                    dev_loss += loss.item() / predict.size(0)
    
    
                real_label = np.concatenate(real_label)
                predict_label = np.concatenate(predict_label)
    
                acc = np.sum(real_label == predict_label) / len(predict_label)
                dev_loss = dev_loss / len(dev_dataloader.dataset)  # 计算平均loss
                print(f'dev_loss:{dev_loss},acc:{acc}')
                # dev_loss:0.11368986861924547,acc:0.9583595772972217
    
        while True:
            text = input("请输入:")
            text_index = [[word2id.get(i,word2id["<PAD>"]) for i in text]]
            text_index = torch.tensor(text_index,dtype=torch.int64)
            predict=model.forward(text_index)
            predict = torch.argmax(predict, dim=-1).reshape(-1)
            pre = [id2tag[i] for i in predict]
    
            print([f'{w}_{s}' for w,s in zip(text,pre)])
    

    效果不太理想

    请输入:一是加快和海口市政府及有关部门沟通衔接,就桂林洋校区的围墙边界、规划区土地使用等问题达成一致意见。二是组织地理与环境科学学院、生命科学学院、体育学院等有关教学单位实地查看,有效利用未开发的区域,作为培养学生的实训和实验基地。三是按照校园总体规划,进一步加强桂林洋校区基础设施的建设和管理工作,同时打造和谐向上的校园文化。
    ['一_O', '是_O', '加_O', '快_O', '和_O', '海_O', '口_M-ORG', '市_M-ORG', '政_M-ORG', '府_E-ORG', '及_O', '有_O', '关_O', '部_O', '门_O', '沟_O', '通_O', '衔_M-TITLE', '接_O', ',_O', '就_O', '桂_O', '林_O', '洋_O', '校_O', '区_O', '的_O', '围_O', '墙_O', '边_O', '界_O', '、_O', '规_B-ORG', '划_M-ORG', '区_M-ORG', '土_M-ORG', '地_M-ORG', '使_O', '用_O', '等_O', '问_O', '题_O', '达_O', '成_O', '一_O', '致_O', '意_O', '见_O', '。_O', '二_O', '是_O', '组_O', '织_O', '地_O', '理_O', '与_O', '环_O', '境_O', '科_O', '学_O', '学_O', '院_O', '、_O', '生_O', '命_M-ORG', '科_M-ORG', '学_M-ORG', '学_M-ORG', '院_E-ORG', '、_O', '体_B-ORG', '育_M-ORG', '学_M-ORG', '院_E-ORG', '等_O', '有_O', '关_O', '教_O', '学_O', '单_O', '位_O', '实_M-EDU', '地_M-EDU', '查_O', '看_O', ',_O', '有_O', '效_O', '利_O', '用_O', '未_O', '开_O', '发_O', '的_O', '区_O', '域_O', ',_O', '作_O', '为_O', '培_O', '养_O', '学_O', '生_O', '的_O', '实_O', '训_O', '和_O', '实_O', '验_O', '基_O', '地_O', '。_O', '三_O', '是_O', '按_O', '照_O', '校_O', '园_O', '总_O', '体_O', '规_O', '划_O', ',_O', '进_O', '一_O', '步_O', '加_O', '强_O', '桂_O', '林_O', '洋_O', '校_O', '区_O', '基_O', '础_O', '设_O', '施_E-TITLE', '的_O', '建_O', '设_O', '和_O', '管_O', '理_O', '工_O', '作_O', ',_O', '同_O', '时_O', '打_O', '造_O', '和_O', '谐_O', '向_M-ORG', '上_M-ORG', '的_O', '校_O', '园_E-ORG', '文_O', '化_E-TITLE', '。_O']
    请输入:会议指出,桂林洋校区基础设施建设已经取得重要进展,学生公寓、第二公共教学楼等设施相继投入使用,进一步改善了学生的学习和生活条件。为进一步落实省委“能力提升年”的安排部署,切实加快基础设施建设,更好服务全校师生。
    ['会_O', '议_O', '指_O', '出_O', ',_O', '桂_O', '林_M-ORG', '洋_M-ORG', '校_M-ORG', '区_O', '基_M-ORG', '础_M-ORG', '设_M-ORG', '施_M-ORG', '建_M-ORG', '设_M-ORG', '已_M-ORG', '经_M-ORG', '取_O', '得_O', '重_O', '要_O', '进_O', '展_O', ',_O', '学_O', '生_O', '公_O', '寓_O', '、_O', '第_B-ORG', '二_O', '公_M-ORG', '共_O', '教_O', '学_O', '楼_O', '等_O', '设_O', '施_O', '相_O', '继_M-ORG', '投_E-ORG', '入_O', '使_O', '用_E-TITLE', ',_O', '进_O', '一_O', '步_O', '改_O', '善_O', '了_O', '学_O', '生_O', '的_O', '学_O', '习_O', '和_O', '生_O', '活_O', '条_O', '件_O', '。_O', '为_O', '进_O', '一_O', '步_O', '落_O', '实_O', '省_O', '委_O', '“_O', '能_O', '力_O', '提_O', '升_O', '年_O', '”_O', '的_O', '安_O', '排_O', '部_O', '署_O', ',_O', '切_O', '实_O', '加_O', '快_O', '基_M-ORG', '础_O', '设_O', '施_M-TITLE', '建_E-ORG', '设_E-ORG', ',_O', '更_O', '好_O', '服_O', '务_O', '全_M-TITLE', '校_M-TITLE', '师_O', '生_O', '。_O']
    请输入:校领导过建春、刁晓平、陈险峰、李森、韩尚峰、刘汝兵、黄忆军、王任斌调研桂林洋校区基建工作并召开现场办公会,相关职能部门负责人参加调研
    ['校_O', '领_O', '导_O', '过_O', '建_O', '春_O', '、_O', '刁_O', '晓_M-ORG', '平_M-ORG', '、_O', '陈_M-ORG', '险_M-ORG', '峰_M-ORG', '、_O', '李_M-ORG', '森_M-ORG', '、_O', '韩_O', '尚_M-ORG', '峰_M-ORG', '、_M-ORG', '刘_M-ORG', '汝_M-ORG', '兵_E-NAME', '、_O', '黄_M-ORG', '忆_M-ORG', '军_M-ORG', '、_M-ORG', '王_M-ORG', '任_M-ORG', '斌_M-ORG', '调_M-ORG', '研_M-ORG', '桂_M-ORG', '林_M-ORG', '洋_M-ORG', '校_M-ORG', '区_M-ORG', '基_M-ORG', '建_M-ORG', '工_M-ORG', '作_M-ORG', '并_M-ORG', '召_M-ORG', '开_M-ORG', '现_O', '场_M-TITLE', '办_M-TITLE', '公_M-TITLE', '会_E-TITLE', ',_O', '相_O', '关_O', '职_O', '能_M-TITLE', '部_M-TITLE', '门_M-TITLE', '负_M-TITLE', '责_M-TITLE', '人_E-TITLE', '参_O', '加_O', '调_M-TITLE', '研_M-TITLE']
    
    
    展开全文
  • 国科大自然语言处理第三次作业
  • 下载即可运行 1、data文件夹(数据) 2、bilstm_crf_model.py(模型) 3、process_data.py(数据预处理文件) 4、train.py(模型训练文件) 5、val.py(测试文件) 6、model.png(模型结构图)
  • 利用LSTM命名实体识别

    千次阅读 2019-08-25 16:40:33
    在pytorch的官方文档里面...当然,官方的文档肯定存在一些新手在利用lstm命名实体识别过程中的一些不全面的地方,我在这里对这些代码进行了补全,但是关于他们的原理就不做多的介绍了。 首先是模型的lstm的搭建 ...

    在pytorch的官方文档里面,有关于LSTM做命名实体识别的介绍,https://pytorch.org/tutorials/beginner/nlp/sequence_models_tutorial.html

    当然,官方的文档肯定存在一些新手在利用lstm做命名实体识别过程中的一些不全面的地方,我在这里对这些代码进行了补全,但是关于他们的原理就不做多的介绍了。

    首先是模型的lstm的搭建

    import torch.nn as nn
    import torch.nn.functional as F
    
    
    class LSTM_Model(nn.Module):
        def __init__(self, vocabSize, embedDim, hiddenDim, tagSize):
            super(LSTM_Model, self).__init__()
            self.embeds = nn.Embedding(vocabSize, embedDim)
            self.lstm = nn.LSTM(embedDim, hiddenDim)
            self.hidden2tag = nn.Linear(hiddenDim, tagSize)
    
        def forward(self, sentSeq):
            embeds = self.embeds(sentSeq)
            output, hidden = self.lstm(embeds.view(len(sentSeq), 1, -1))
            tagSpace = self.hidden2tag(output.view(len(sentSeq), -1))
            result = F.log_softmax(tagSpace, dim=1)
            return result
    

    搭建完成后对模型进行训练,下面是训练模型的代码

    model = LSTM_Model(len(word2id), EMBEDDING_DIM, HIDDEN_DIM, len(tag2id))
    lossFunction = nn.NLLLoss()
    optimzer = optim.SGD(model.parameters(), lr=1e-1)
    
    for epoch in range(300):
        for wordList, tagList in zip(wordLists, tagLists):
            model.zero_grad()  # 清除积累梯度
            input = torch.tensor([word2id[word] for word in wordList])
            tagSeq = torch.tensor([tag2id[tag] for tag in tagList])
            tagScore = model(input)
            loss = lossFunction(tagScore, tagSeq)
            loss.backward()
            optimzer.step()

    看看训练后的结果

    with torch.no_grad():
        testText = ['欧', '美', '港', '台']
        testSeq = torch.tensor([word2id[word] for word in testText]).long()
        tags_scores = model(testSeq)
        print(tags_scores)
        _, predictId = torch.max(tags_scores, dim=1)
        id2tag = dict((id, tag) for tag, id in tag2id.items())
        tagList = [id2tag[id] for id in predictId.numpy()]
        printZip(testText, tagList)
    

    其中我在下面写了读取训练数据的代码filePath.py

    import sys
    path=sys.path[0].split('\\')
    path.pop(-1)
    basePath='/'.join(path)+'/data'

    loadText.py

    import time
    from os import listdir
    
    
    class loadData():
        def __init__(self):
            pass
    
        def loadLists(self, filename):
            print('loading data...')
            textLines = open(filename, encoding='utf-8').readlines()
            wordLists = []
            tagLists = []
            wordList = []
            tagList = []
            for textLine in textLines:
                if textLine != '\n':
                    word, tag = textLine.strip().split('\t')
                    wordList.append(word)
                    tagList.append(tag)
                else:
                    wordLists.append(wordList)
                    tagLists.append(tagList)
                    wordList = []
                    tagList = []
            print('loading done.')
            return wordLists, tagLists
    
        def getVocab(self, sentence):
            vocab = {}
            for word in sentence:
                if word not in vocab:
                    vocab[word] = len(vocab)
            return vocab
    
        def text2sentences(self, filename):
            textList = open(filename, encoding='utf-8').read().split('\n')
            sentences = []
            for text in textList:
                sentence = []
                for word in text:
                    if word != ' ':
                        sentence.append(word)
                sentences.append(sentence)
            return sentences
    
        def loadList(self, filename):
            """
            :return: wordList,tagList
            """
            wordList = []
            tagList = []
            textLines = open(filename, encoding='utf-8').readlines()
            for textLine in textLines:
                if textLine != '\n':
                    text_list = textLine.strip().split('\t')
                    wordList.append(text_list[0])
                    tagList.append(text_list[1])
            return wordList, tagList
    

    pprint.py

    def printZip(list1, list2):
        pairs = []
        for node1, node2 in zip(list1, list2):
            pairs.append(node1)
            pairs.append(node2)
        print(pairs)
    

    代码就这麽多,训练数据的格式就是这个样子的

    放在你的pycharm上就可以用stm模型实现命名实体识别了。

    展开全文
  • 首先利用SVM筛选出包含关键命名实体的句子,然后将正确包含此类实体的句子转化为字符级向量作为输入,构建适合财产纠纷裁判文书命名实体识别的BiLSTM-CRF深层神经网络模型.通过构建训练数据进行验证和对比,该模型比...
  • 首先使用BERT语言模型进行文本特征提取获取字粒度向量矩阵, BiLSTM用于上下文信息的提取, 同时结合CRF模型提取全局最优序列, 最终得到景点命名实体. 实验表明, 提出的模型性能提升显著, 在实际旅游领域内景点识别的...
  • 命名实体识别(Named Entity Recognition) 3.1 基本概念 定义: 提取出句子中的特殊名词,如地名、组织名、人名、时间、日期等信息 命名实体类型: 地名、组织名、日期、人名... 命名实体提取: 通过特殊的标记还表示...

    1.RNN与梯度消失

    1.1 RNN的优缺点

        RNN优点:

            (1)能捕捉长距离依赖关系

            (2)相比n-gram模型,使用更少的内存

            

        RNN缺点:

            (1)无法处理更长的序列

            (2)存在梯度消失和梯度爆炸问题

            

    1.2 梯度消失/梯度爆炸

        原因:

            正向传播:随着不断有新输入,前面的信息在传播过程中被逐步稀释,导致对最后结果几乎没有影响

               

            反向传播:由于链式求导的不断累乘,使得梯度(更新值)不断减小,导致浅层网络的权重几乎不被更新,模型没有提升

               

        解决方法:

            (1)使用ReLU激活函数:防止梯度消失

            (2)梯度裁剪:防止梯度爆炸,当梯度大于特定值后进行裁剪

            (3)残差连接:防止梯度消失,输入可跳过隐藏层,直达下一层,反之方向传播时深层梯度更容易传回浅层

            


    2.长短期记忆网络(Long Short-Term Memory Network,LSTM)

    2.1 基本概念

        定义:

            是RNN的一个变体,能有效解决RNN的梯度爆炸/消失问题;在GRU基础上引入新的内部状态c,使用三种门控制记忆和遗忘

           

        构成:

            c:内部状态,用于线性的循环信息传递

            h:外部状态,用于非线性的输出信息给隐藏层

            y:输出,用于输出当前结果

        基本结构:

            

        应用:

            字符预测、聊天机器人、音乐编曲、图像题注、语言识别...

            

    2.2 LSTM结构

        遗忘门:

            功能:决定上一状态传来的信息有多少需要遗忘

            计算:

                

            结构:

                

        输入门:

            功能:决定当前状态输入信息保留多少

            计算:

                

            结构:

                

        输出门:

            功能:决定多少信息输出到下一层

            计算:

                

            结构:

                

    2.3 计算流程

        流程:

            (1)利用上一时刻的外部状态h_{t-1}和当前时刻输入x_t,计算出三个门f_ti_to_t及候选状态\tilde{c_t}

            (2)通过遗忘门f_t和输入门i_t来更新记忆单元c_t

            (3)通过输出门o_t,将内部状态的信息传递给外部状态h_t

            


    3.命名实体识别(Named Entity Recognition)

    3.1 基本概念

        定义:

            提取出句子中的特殊名词,如地名、组织名、人名、时间、日期等信息

            

        命名实体类型:

            地名、组织名、日期、人名...

            

        命名实体提取:

            通过特殊的标记还表示不同的命名实体类型

            

        应用:

            高效搜索引擎、推荐系统、智能客服、自动交易...

            

    3.2 使用LSTM实现

        数据处理:

            (1)给每个实体类型进行编号

            (2)给每个单词进行编号

            

        文本填充(Token padding):

            通过使用<PAD>标识符,将所有文本处理成同样长度

            

        训练流程:

            (1)给每个输入和其对应编号建立一个张量

            (2)构成训练批

            (3)输入LSTM单元

            (4)输入全连接层

            (5)使用softmax进行预测

            

        模型结构:

            

        总结:

            

    3.3 模型评估

        流程:

            (1)将测试集输入模型

            (2)得到预测结果中值最大项

            (3)反填充,不考虑填充符

            (4)与真实标签比较,计算准确度

            

        代码:

            


    项目代码:https://github.com/Ogmx/Natural-Language-Processing-Specialization

    可将代码与数据下载至本地,使用jupyter notebook打开

    展开全文
  • 用BILSTM+CRF做医疗实体识别,框架为pytorch。 注意,代码已经修改了!! 由于pytorch-crf这个包在计算loss时会自动加上和的转移矩阵,所以我们不用再自己手动在样本和标签前后加这俩标记。 然后评估方法改为了...
  • 这份代码是基于深度神经网络的英文命名实体识别,主要算法是LSTM+CRF
  • 文章目录命名实体识别BiLSTM-CRF代码实现 -- 潘登同学的NLP笔记条件随机场概述直观的例子CRF的特征函数们举些例子对比逻辑回归对比HMMBilstm-CRF命名实体识别代码实现数据预处理训练模型模型应用(维特比算法) ...
  • ner-lstm, 基于多层双向LSTM命名实体识别 这里知识库包含实现以下Arxiv预编译中所述方法的代码: https://arxiv.org/abs/1610.09756,在 ICON-16 会议( http://aclweb.org/anthology/W/W16/W16-63.pd
  • 说到命名实体抽取,先要了解一下基于字标注的中文分词。 比如一句话 “我爱北京天安门”。 分词的结果可以是 “我/爱/北京/天安门”。 那什么是基于字标注呢? “我/O 爱/O 北/B 京/E 天/B 安/M 门/E...
  • 这是一篇2018年发表于 ACL(自然语言处理顶会) 的论文,文中提出了一种基于格子(Lattice)结构的LSTM模型.
  • 这是一个中文细粒度命名实体识别数据集,是基于清华大学开源的文本分类数据集THUCNEWS,选出部分数据进行细粒度标注得到的。该数据集的训练集、验证集和测试集的大小分别为10748,1343,1345,平均句子长度37.4字,...
  • Bert+LSTM+CRF命名实体识别 从0开始解析源代码。 理解原代码的逻辑,具体了解为什么使用预训练的bert,bert有什么作用,网络的搭建是怎么样的,训练过程是怎么训练的,输出是什么 调试运行源代码 NER目标 ...
  • 命名实体识别(NER)实战demo-LSTM+CRF

    千次阅读 2021-11-28 18:53:41
    命名实体识别(NER)实战demo:简介、项目资源、项目文件、代码运行过程的问题及解决方法
  • 数据来源获取数据标注效果模型搭建代码部分 数据来源 百度百科:一战的主要战争人物介绍。 获取 import requests import re import pandas as pd from lxml import html from lxml import etree url = '...
  • 本文章将通过pytorch作为主要工具实现不同的模型(包括HMM,CRF,Bi-LSTM,Bi-LSTM+CRF)来解决中文命名实体识别问题,文章不会涉及过多的数学推导,但会从直观上简单解释模型的原理,主要的内容会集中在代码部分。...
  • 本文较全面的介绍了命名实体识别(NER),包括NER定义、BiLSTM-CRF模型、Pytorch代码实现,未来将继续完善本文,以求涵盖NER众多方面。 文章目录命名实体识别任务(NER)定义BiLSTM-CRF模型模型输入LSTMCRF真实路径...
  • 欢迎大家来到我们的项目实战课,本期内容是《基于BiLSTM+CRF的命名实体识别实战》。所谓项目课,就是以简单的原理回顾+详细的项目实战的模式,针对具体的某一个主题,进行代码级的实战讲解。本次主题命名实体识别...
  • 对应这篇文章的数据集,小伙伴们自行取走: https://sito8.blog.csdn.net/article/details/123233758?spm=1001.2014.3001.5502
  • 命名实体识别任务:BiLSTM+CRF part1

    千次阅读 2020-05-21 22:49:42
    6.1 命名实体识别介绍 学习目标: 了解什么是命名实体识别 了解命名实体识别的作用 了解命名实体识别常用方法 了解医学文本特征 什么是命名实体识别: 命名实体识别(Named Entity Recognition,NER)就是...
  • 使用BERT + Bi-LSTM + CRF 实现命名实体识别

    千次阅读 多人点赞 2022-03-26 23:56:41
    本文主要分析本次实验的代码,讲解主要流程和代码含义,而关注参数的选择和模型的选择 后续可能还会更新 model 的原理 如果有问题,欢迎评论或私聊讨论 若分析过程出现错误,请及时指正,谢谢 2.数据预处理 原始...
  • 前阵子用crf++工具做了命名实体识别,这次用keras中的keras_contrib来试下,结合lstm。 一、操作系统 window10 二、版本 Python 3.6.5、TensorFlow 2.1.0、Keras 2.3.1 三、原始数据处理过程 同之前的处理,...
  • 基于BiLSTM-CRF的命名实体识别

    千次阅读 2021-06-08 11:00:27
    基于BiLSTM-CRF的命名实体识别1. 任务说明1.1 任务定义1.2 语料说明2. 实验环境3. 算法说明(按文件说明)3.1 model.py3.2 data.py3.3 predict.py3.4 evaluate.py3.5 run.py4. 实验结果5. 参考资料6. 源码 1. 任务...
  • 前文中文分词、词性标注、命名实体识别是自然语言理解中,基础性的工作,同时也是非常重要的工作。在很多NLP的项目中,工作开始之前都要经过这三者中的一到多项工作的处理。在深度学习中,有一种模型可以同时胜任这...
  • BERT-BiLSTM-CRF命名实体识别应用

    千次阅读 2020-05-18 14:06:16
    本文将采用BERT+BiLSTM+CRF模型进行命名实体识别(Named Entity Recognition 简称NER),即实体识别。命名实体识别,是指识别文本中具有特定意义的实体,主要包括人名、地名、机构名、专有名词等。 BERT...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,590
精华内容 1,036
关键字:

lstm命名实体识别代码