政安晨:【Keras机器学习示例演绎】(四十五)—— 使用变换器进行命名实体识别

news2024/11/23 22:32:30

目录

简介

安装 HuggingFace 的开源数据集库

将 NER 模型类构建为 keras.Model 子类

从数据集库加载 CoNLL 2003 数据集并进行处理

制作 NER 标签查找表

编译和拟合模型

指标计算

结论


政安晨的个人主页政安晨

欢迎 👍点赞✍评论⭐收藏

收录专栏: TensorFlow与Keras机器学习实战

希望政安晨的博客能够对您有所裨益,如有不足之处,欢迎在评论区提出指正!

本文目标:使用来自 CoNLL 2003 共享任务的 Transformers 和数据进行 NER。

简介

命名实体识别(NER)是识别文本中命名实体的过程。命名实体的例子有"人物"、"地点"、"组织"、"日期 "等。NER 本质上是一种标记分类任务,每个标记都被归入一个或多个预定类别。

在本练习中,我们将训练一个基于 Transformer 的简单模型来执行 NER。我们将使用来自 CoNLL 2003 共享任务的数据。有关该数据集的更多信息,请访问数据集网站。不过,由于获取该数据需要额外的步骤,即获得免费许可证,因此我们将使用 HuggingFace 的数据集库,其中包含该数据集的处理版本。

安装 HuggingFace 的开源数据集库


我们还下载了用于评估 NER 模型的脚本。

!pip3 install datasets
!wget https://raw.githubusercontent.com/sighsmile/conlleval/master/conlleval.py
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.110.133, 185.199.111.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 7502 (7.3K) [text/plain]
Saving to: ‘conlleval.py’
conlleval.py        100%[===================>]   7.33K  --.-KB/s    in 0s      
2023-11-10 16:58:25 (217 MB/s) - ‘conlleval.py’ saved [7502/7502]
import os

os.environ["KERAS_BACKEND"] = "tensorflow"

import keras
from keras import ops
import numpy as np
import tensorflow as tf
from keras import layers
from datasets import load_dataset
from collections import Counter
from conlleval import evaluate

我们将使用这个精彩示例中的变压器实现。

让我们先定义一个 TransformerBlock 层:

class TransformerBlock(layers.Layer):
    def __init__(self, embed_dim, num_heads, ff_dim, rate=0.1):
        super().__init__()
        self.att = keras.layers.MultiHeadAttention(
            num_heads=num_heads, key_dim=embed_dim
        )
        self.ffn = keras.Sequential(
            [
                keras.layers.Dense(ff_dim, activation="relu"),
                keras.layers.Dense(embed_dim),
            ]
        )
        self.layernorm1 = keras.layers.LayerNormalization(epsilon=1e-6)
        self.layernorm2 = keras.layers.LayerNormalization(epsilon=1e-6)
        self.dropout1 = keras.layers.Dropout(rate)
        self.dropout2 = keras.layers.Dropout(rate)

    def call(self, inputs, training=False):
        attn_output = self.att(inputs, inputs)
        attn_output = self.dropout1(attn_output, training=training)
        out1 = self.layernorm1(inputs + attn_output)
        ffn_output = self.ffn(out1)
        ffn_output = self.dropout2(ffn_output, training=training)
        return self.layernorm2(out1 + ffn_output)

接下来,让我们定义一个令牌和位置嵌入层:

class TokenAndPositionEmbedding(layers.Layer):
    def __init__(self, maxlen, vocab_size, embed_dim):
        super().__init__()
        self.token_emb = keras.layers.Embedding(
            input_dim=vocab_size, output_dim=embed_dim
        )
        self.pos_emb = keras.layers.Embedding(input_dim=maxlen, output_dim=embed_dim)

    def call(self, inputs):
        maxlen = ops.shape(inputs)[-1]
        positions = ops.arange(start=0, stop=maxlen, step=1)
        position_embeddings = self.pos_emb(positions)
        token_embeddings = self.token_emb(inputs)
        return token_embeddings + position_embeddings

将 NER 模型类构建为 keras.Model 子类

class NERModel(keras.Model):
    def __init__(
        self, num_tags, vocab_size, maxlen=128, embed_dim=32, num_heads=2, ff_dim=32
    ):
        super().__init__()
        self.embedding_layer = TokenAndPositionEmbedding(maxlen, vocab_size, embed_dim)
        self.transformer_block = TransformerBlock(embed_dim, num_heads, ff_dim)
        self.dropout1 = layers.Dropout(0.1)
        self.ff = layers.Dense(ff_dim, activation="relu")
        self.dropout2 = layers.Dropout(0.1)
        self.ff_final = layers.Dense(num_tags, activation="softmax")

    def call(self, inputs, training=False):
        x = self.embedding_layer(inputs)
        x = self.transformer_block(x)
        x = self.dropout1(x, training=training)
        x = self.ff(x)
        x = self.dropout2(x, training=training)
        x = self.ff_final(x)
        return x

从数据集库加载 CoNLL 2003 数据集并进行处理

conll_data = load_dataset("conll2003")

我们将把这些数据导出为以制表符分隔的文件格式,以便于作为 tf.data.Dataset 对象读取。

def export_to_file(export_file_path, data):
    with open(export_file_path, "w") as f:
        for record in data:
            ner_tags = record["ner_tags"]
            tokens = record["tokens"]
            if len(tokens) > 0:
                f.write(
                    str(len(tokens))
                    + "\t"
                    + "\t".join(tokens)
                    + "\t"
                    + "\t".join(map(str, ner_tags))
                    + "\n"
                )


os.mkdir("data")
export_to_file("./data/conll_train.txt", conll_data["train"])
export_to_file("./data/conll_val.txt", conll_data["validation"])

制作 NER 标签查找表


NER 标签通常以 IOB、IOB2 或 IOBES 格式提供。请点击此链接了解更多信息:维基百科

请注意,我们的标签编号从 1 开始,因为 0 将保留用于填充。我们共有 10 个标签:其中 9 个来自 NER 数据集,1 个用于填充。

def make_tag_lookup_table():
    iob_labels = ["B", "I"]
    ner_labels = ["PER", "ORG", "LOC", "MISC"]
    all_labels = [(label1, label2) for label2 in ner_labels for label1 in iob_labels]
    all_labels = ["-".join([a, b]) for a, b in all_labels]
    all_labels = ["[PAD]", "O"] + all_labels
    return dict(zip(range(0, len(all_labels) + 1), all_labels))


mapping = make_tag_lookup_table()
print(mapping)
{0: '[PAD]', 1: 'O', 2: 'B-PER', 3: 'I-PER', 4: 'B-ORG', 5: 'I-ORG', 6: 'B-LOC', 7: 'I-LOC', 8: 'B-MISC', 9: 'I-MISC'}

获取训练数据集中所有标记的列表。这将用于创建词汇表。

all_tokens = sum(conll_data["train"]["tokens"], [])
all_tokens_array = np.array(list(map(str.lower, all_tokens)))

counter = Counter(all_tokens_array)
print(len(counter))

num_tags = len(mapping)
vocab_size = 20000

# We only take (vocab_size - 2) most commons words from the training data since
# the `StringLookup` class uses 2 additional tokens - one denoting an unknown
# token and another one denoting a masking token
vocabulary = [token for token, count in counter.most_common(vocab_size - 2)]

# The StringLook class will convert tokens to token IDs
lookup_layer = keras.layers.StringLookup(vocabulary=vocabulary)
21009

从训练数据和验证数据中创建 2 个新数据集对象。

train_data = tf.data.TextLineDataset("./data/conll_train.txt")
val_data = tf.data.TextLineDataset("./data/conll_val.txt")

打印出一行,确保看起来不错。该行的第一条记录是标记数。然后是所有标记符,接着是所有 ner 标记。

我们将使用以下 map 函数来转换数据集中的数据:

def map_record_to_training_data(record):
    record = tf.strings.split(record, sep="\t")
    length = tf.strings.to_number(record[0], out_type=tf.int32)
    tokens = record[1 : length + 1]
    tags = record[length + 1 :]
    tags = tf.strings.to_number(tags, out_type=tf.int64)
    tags += 1
    return tokens, tags


def lowercase_and_convert_to_ids(tokens):
    tokens = tf.strings.lower(tokens)
    return lookup_layer(tokens)


# We use `padded_batch` here because each record in the dataset has a
# different length.
batch_size = 32
train_dataset = (
    train_data.map(map_record_to_training_data)
    .map(lambda x, y: (lowercase_and_convert_to_ids(x), y))
    .padded_batch(batch_size)
)
val_dataset = (
    val_data.map(map_record_to_training_data)
    .map(lambda x, y: (lowercase_and_convert_to_ids(x), y))
    .padded_batch(batch_size)
)

ner_model = NERModel(num_tags, vocab_size, embed_dim=32, num_heads=4, ff_dim=64)

我们将使用一个自定义损失函数,该函数将忽略填充标记的损失。

class CustomNonPaddingTokenLoss(keras.losses.Loss):
    def __init__(self, name="custom_ner_loss"):
        super().__init__(name=name)

    def call(self, y_true, y_pred):
        loss_fn = keras.losses.SparseCategoricalCrossentropy(
            from_logits=False, reduction=None
        )
        loss = loss_fn(y_true, y_pred)
        mask = ops.cast((y_true > 0), dtype="float32")
        loss = loss * mask
        return ops.sum(loss) / ops.sum(mask)


loss = CustomNonPaddingTokenLoss()

编译和拟合模型

tf.config.run_functions_eagerly(True)
ner_model.compile(optimizer="adam", loss=loss)
ner_model.fit(train_dataset, epochs=10)


def tokenize_and_convert_to_ids(text):
    tokens = text.split()
    return lowercase_and_convert_to_ids(tokens)


# Sample inference using the trained model
sample_input = tokenize_and_convert_to_ids(
    "eu rejects german call to boycott british lamb"
)
sample_input = ops.reshape(sample_input, shape=[1, -1])
print(sample_input)

output = ner_model.predict(sample_input)
prediction = np.argmax(output, axis=-1)[0]
prediction = [mapping[i] for i in prediction]

# eu -> B-ORG, german -> B-MISC, british -> B-MISC
print(prediction)
Epoch 1/10
 439/439 ━━━━━━━━━━━━━━━━━━━━ 300s 671ms/step - loss: 0.9260
Epoch 2/10
 439/439 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.2909
Epoch 3/10
 439/439 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.1589
Epoch 4/10
 439/439 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.1176
Epoch 5/10
 439/439 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0941
Epoch 6/10
 439/439 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0747
Epoch 7/10
 439/439 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0597
Epoch 8/10
 439/439 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0534
Epoch 9/10
 439/439 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0459
Epoch 10/10
 439/439 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step - loss: 0.0408
tf.Tensor([[  988 10950   204   628     6  3938   215  5773]], shape=(1, 8), dtype=int64)
 1/1 ━━━━━━━━━━━━━━━━━━━━ 1s 600ms/step
['B-ORG', 'O', 'B-MISC', 'O', 'O', 'O', 'B-MISC', 'O']

指标计算


这里有一个计算指标的函数。该函数可计算整个 NER 数据集的 F1 分数以及每个 NER 标签的单个分数。

def calculate_metrics(dataset):
    all_true_tag_ids, all_predicted_tag_ids = [], []

    for x, y in dataset:
        output = ner_model.predict(x, verbose=0)
        predictions = ops.argmax(output, axis=-1)
        predictions = ops.reshape(predictions, [-1])

        true_tag_ids = ops.reshape(y, [-1])

        mask = (true_tag_ids > 0) & (predictions > 0)
        true_tag_ids = true_tag_ids[mask]
        predicted_tag_ids = predictions[mask]

        all_true_tag_ids.append(true_tag_ids)
        all_predicted_tag_ids.append(predicted_tag_ids)

    all_true_tag_ids = np.concatenate(all_true_tag_ids)
    all_predicted_tag_ids = np.concatenate(all_predicted_tag_ids)

    predicted_tags = [mapping[tag] for tag in all_predicted_tag_ids]
    real_tags = [mapping[tag] for tag in all_true_tag_ids]

    evaluate(real_tags, predicted_tags)


calculate_metrics(val_dataset)
processed 51362 tokens with 5942 phrases; found: 5659 phrases; correct: 3941.
accuracy:  64.49%; (non-O)
accuracy:  93.23%; precision:  69.64%; recall:  66.32%; FB1:  67.94
              LOC: precision:  82.77%; recall:  79.26%; FB1:  80.98  1759
             MISC: precision:  74.94%; recall:  68.11%; FB1:  71.36  838
              ORG: precision:  55.94%; recall:  65.32%; FB1:  60.27  1566
              PER: precision:  65.57%; recall:  53.26%; FB1:  58.78  1496

结论


在本练习中,我们创建了一个基于转换器的简单命名实体识别模型。我们在 CoNLL 2003 共享任务数据上对其进行了训练,并获得了约 70% 的总体 F1 分数。在 BERT 或 ELECTRA 等预训练模型的基础上进行微调的先进 NER 模型很容易获得更高的 F1 分数--在该数据集上达到 90-95% 之间,这是因为在预训练过程中使用了词的固有知识和子词标记化技术。

您可以使用 Hugging Face Hub 上的训练模型,并在 Hugging Face Spaces 上试用演示。


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

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

相关文章

PyTorch进行CIFAR-10图像分类

本节将通过一个实战案例来详细介绍如何使用PyTorch进行深度学习模型的开发。我们将使用CIFAR-10图像数据集来训练一个卷积神经网络。 神经网络训练的一般步骤如图5-3所示。 (1)加载数据集,并做预处理。 (2)预处理后的…

使用PageHelper分页插件,发现获取到的total总记录数量不对,无法获取到正确的total数量

目录 1.1、错误描述 1.2、解决方案 1.1、错误描述 周一在工作中,写了一个列表分页的接口,其中使用的是PageHelper分页依赖,原本想着挺简单的,也就是使用PageHelper.startPage(pageNum, pageSize);方法就可以了,代码…

讨论:WGS84与CGCS2000的坐标系怎么互转

前言: 今天我们要讨论一个问题:WGS84与CGCS2000的坐标系怎么互转? 对于有一定基础的朋友应该知道,WGS84和CGCS2000属于不同的椭球,如果进行严密的数学转换,是需要建立参数模型之后,再进行转换&…

视频素材哪里找?7个无版权视频素材网站

这篇文章为那些正在学习视频剪辑的新手提供了一份宝贵的资源清单,介绍了7个可以找到高质量且免费可商用的视频素材网站。每个网站都有其独特的资源库,可以帮助用户找到适合各种项目的视频素材,从生活vlog到专业旅行记录,都可以在这…

STM32存储左右互搏 USB接口FATS文件读写U盘

STM32存储左右互搏 USB接口FATS文件读写U盘 STM32的USB接口可以例化为Host主机从而对U盘进行操作。SD卡/MicroSD/TF卡也可以通过读卡器转换成U盘使用。这里介绍STM32CUBEIDE开发平台HAL库实现U盘FATS文件访问的例程。 USB接口介绍 常见的USB接口电路部分相似而有不同的连接器…

数据分离和混淆矩阵的学习

1.明确意义 通过训练集建立模型的意义是对新的数据进行准确的预测(测试集的准度高才代表good fit); 2.评估流程 3.单单利用准确率accuracy进行模型评估的局限性 模型一:一共1000个数据(分别为900个1和100个0&#x…

网站服务器备案及域名购买配置教程

一、阿里云服务备案准备工作 1.什么是备案? 备案是指向相关部门提交网站信息,以便监管和管理互联网信息服务,未经备案的网站可能面临罚款甚至被关闭的风险。备案主要看您的网站或App等互联网信息服务解析到的服务器是否在中国内地(大陆),如果服务器在中国内地(大陆),…

Postman基础功能-Collection集合和批量运行

一、Collection(集合)介绍 当我们对一个或多个系统中的很多接口用例进行维护时,首先想到的就是对接口用例进行分类管理,同时还希望对这批接口用例做回归测试。 在 Postman 中也提供了这样一个功能,就是 Collec…

Aim Web API 远程代码执行

摘要 漏洞类型:远程代码执行(RCE)产品:目标版本:> 3.0.0(afaik)受影响的端点: /api/runs/search/run/严重性:临界 描述 在aim项目中发现了一个关键的远程代码执行漏…

设计模式 六大原则之里氏替换原则

文章目录 概念替换逻辑行为不变 拆解小结 概念 子类对象能够替换程序中父类对象出现的任何地方,并且保证原来程序的逻辑行为不变及正确性不被破坏。 替换 替换的前提是面向对象语言所支持的多态特性,同一个行为具有多个不同表现形式或形态的能力。 逻…

js基础-数组-事件对象-日期-本地存储

一、大纲 一、获取元素位置 在JavaScript中,获取一个元素在页面上的位置可以通过多种方法实现。以下是一些常见的方法: getBoundingClientRect() getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置。它提供了元素的left、top、right和bo…

Tkinter组件:Text-显示和处理多行文本

Tkinter组件:Text Text(文本)组件用于显示和处理多行文本。在 Tkinter 的所有组件中,Text 组件显得异常强大和灵活,适用于多种任务。虽然该组件的主要目的是显示多行文本,但它常常也被用于作为简单的文本编…

【单片机调试】mcu调试bug记录

【单片机调试】mcu调试bug记录 2023.5-2023.11待输入 2023.12-2023.22024.3-至今1.spi通信问题 2023.5-2023.11 待输入 2023.12-2023.2 辞职阶段:【STM32调试】寄存器调试不良问题记录持续版 2024.3-至今 1.spi通信问题 现象说明: mcu与afe芯片为spi通…

为什么使用AI 在游戏中不犯法

使用AI在游戏中本身并不违法,甚至在很多情况下,游戏公司自己也会在游戏中集成AI来提高游戏体验,例如通过AI驱动的非玩家角色(NPC)来增加游戏的互动性和挑战性。然而,使用AI是否违法取决于AI的使用方式和目的…

设计一个游戏的基本博弈框架

设计一个游戏的基本博弈框架,玩家通过操作改变某个数值,这个数值的变动会引发一系列实时变化,并且当这些数值累计到特定阈值时,会导致游戏中出现其他变化,可以分为以下几个步骤: 1. 确定游戏类型和主题 首…

邮件地址采集软件有哪些-邮箱地址采集软件

邮件地址采集软件是帮助用户收集、管理和使用邮件地址的工具,它们在商业营销、市场调研、网络爬虫等领域有着广泛的应用。以下是一些常见的邮件地址采集软件: 易邮件地址搜索大师:易邮件地址搜索大师是一款搜索邮件地址和手机号码的软件&…

一篇文章拿下Redis 通用命令

文章目录 Redis数据结构介绍Redis 通用命令命令演示KEYSDELEXISTSEXPIRE RedisTemplate 中的通用命令 本篇文章介绍 Redis 的通用命令, 通用命令在 Redis 的所有数据类型下都使用, 学好通用命令可以让我们更好的使用 Redis. Redis数据结构介绍 Redis 是一个key-value的数据库&…

如何进行免杀

0x03 免杀思路总结 环境准备: 火绒(静态)、360、windowsdef(动态) 免杀的最基本思路就是去除其特征,这个特征有可能是特征码,也有可能是行为特征,只要在不修改其 原有功能的情况下…

基于C#开发web网页管理系统模板流程-登录界面

前言,首先介绍一下本项目将要实现的功能 (一)登录界面 实现一个不算特别美观的登录窗口,当然这一步跟开发者本身的设计美学相关,像蒟蒻博主就没啥艺术细胞,勉强能用能看就行…… (二&#xff09…

极验3滑块逆向分析

1、底图还原 下 断点&#xff0c;可以分析底图还原逻辑 2、跟W值 var Str_Unicodefunction(str){var unid\\u00;for(let i0,lenstr.length;i<len;i){if(i<len-1){unidstr.charCodeAt(i).toString(16)\\u00;}else if(ilen-1){unidstr.charCodeAt(i).toString(16);}}re…