Bert基础(十八)--Bert实战:NER命名实体识别

1、命名实体识别介绍

1.1 简介

命名实体识别(NER)是自然语言处理(NLP)中的一项关键技术,它的目标是从文本中识别出具有特定意义或指代性强的实体,并对这些实体进行分类。这些实体通常包括人名、地名、组织机构名、日期、时间、专有名词等。NER在许多实际应用中都非常重要,如信息提取、文本挖掘、机器翻译、自动摘要等。
NER的任务主要分为两部分:

  1. 实体的边界识别:这部分任务是要确定文本中实体的起始和结束位置,即在文本中准确地定位出实体的边界。
  2. 确定实体的类型:在识别出实体的边界之后,还需要确定每个实体的具体类型,如人名、地名、机构名等。

例如,在处理文本“马云在杭州创建了阿里巴巴”时,NER系统需要识别出“阿里巴巴”是一个组织机构名,“马云”是一个人名,“杭州”是一个地名。

NER的技术实现通常涉及机器学习、深度学习等方法,通过训练模型来识别和分类文本中的实体。随着深度学习技术的发展,NER的准确率和效率有了显著提高,成为NLP领域研究和应用的热点之一。我们今天使用transformers库来实现一下。

1.2 标注方法

序列标注的方法中有多种标注方式:BIO、BIOSE、IOB、BILOU、BMEWO,其中前三种最为常见。

  • BIO:标识实体的开始,中间部分和非实体部分

    • b代表“开始”(表示命名实体的开始,即NE)
    • I代表“内部”(表示该词在NE内部)
    • o代表‘outside’(表示这个单词只是一个NE之外的普通单词)
  • BIOSE:增加S单个实体情况的标注和增加E实体的结束标识

    • b代表“开始”(表示一个NE的开始)
    • I代表“内部”(表示该词在NE内部)
    • o代表‘outside’(表示这个单词只是一个NE之外的普通单词)
    • e代表‘end’(表示这个词是一个NE的结尾)
    • s代表“singleton”(表示单个单词是一个NE)
  • IOB (即IOB-1):三位序列标注法(B-begin,I-inside,O-outside),IOB与BIO字母对应的含义相同,其不同点是IOB中,标签B仅用于两个连续的同类型命名实体的边界区分,不用于命名实体的起始位置,这里举个例子:

    • 词序列:(word)(word)(word)(word)(word)(word)
      IOB标注:(I-loc)(I-loc)(B-loc)(I-loc)(o)(o)
      BIO标注:(B-loc)(I-loc)(B-loc)(I-loc)(o)(o)
      在命名实体识别(NER)中,BIO和IOB都使用B、I、O三种标签来标注实体,但是它们在使用B标签的方式上有所不同。
      在BIO标注方法中,B标签用于表示一个实体的开始,I标签用于表示实体的内部,而O标签用于表示非实体词。每个实体都由一个B标签开始,后面跟着零个或多个I标签。
      IOB标注方法与BIO类似,但是在IOB中,B标签有特殊的用途。它仅用于标记两个连续的同类型命名实体的边界,而不是用于标记一个实体的开始。这意味着,如果一个实体紧接着另一个同类型的实体,那么第二个实体不会以B标签开始,而是以I标签开始。
      在这个例子中,前两个词是同一个实体的组成部分,因此在IOB标注中,它们都以I-loc开始。当遇到一个新的同类型实体时,使用B-loc来标记它们的边界。而在BIO标注中,每个实体的开始都使用B标签。
      总的来说,IOB和BIO的主要区别在于如何处理连续的命名实体。IOB在处理这种情况时更加精细,但是在实际应用中,BIO因为其简单性和直观性而被更广泛地使用。

    • 因为IOB的整体效果不好,所以出现了IOB-2,约定了所有命名实体均以B tag开头。这样IOB-2就与BIO的标注方式等价了。

      • I表示实体内部
      • B表示实体开始
      • O表示实体外部
      • 标记说明
        B-Person人名开始
        I- Person人名中间
        B-Organization组织名开始
        I-Organization组织名中间
        O非命名实体

1.3 评价指标

  • 精准率:度量模型的精确度/准确度。 它是正确识别的正值(真正)与所有识别出的正值之间的比率。 精准率指标显示正确标记的预测实体的数量。
    Precision = #True_Positive / (#True_Positive + #False_Positive)

  • 召回率:度量模型预测实际正类的能力。 这是预测的真正值与实际标记的结果之间的比率。 召回率指标显示正确的预测实体的数量。
    Recall = #True_Positive / (#True_Positive + #False_Negatives)

  • F1 分数:F1 分数是精准率和召回率的函数。 在精准率和召回率之间进行平衡时,需要用到它。
    F1 Score = 2 * Precision * Recall / (Precision + Recall)

举例说明

词组Gold标签Predict标签
B-PERB-PER
I-PERI-PER
OO
B-LOCB-LOC
I-LOCI-LOC
OO
OO
OO
B-ORGB-ORG
I-ORGI-ORG
I-ORGO
I-ORGO

例子中一共有三个实体,

  • 预测了三个实体,预测对了2个,所以精准率= 2 3 \frac{2}{3} 32
  • 样本中实际游3个实体,预测对了2个,召回率= 2 3 \frac{2}{3} 32
  • F1 Score = 2 * Precision * Recall / (Precision + Recall) = 2* 2 3 \frac{2}{3} 32* 2 3 \frac{2}{3} 32/( 2 3 \frac{2}{3} 32+ 2 3 \frac{2}{3} 32) = 2 3 \frac{2}{3} 32

2 实战

基本步骤:

1 加载数据集
2 数据预处理
3 创建模型
4 创建评估函数
5 创建训练器
6 训练模型
7 评估
8 预测

2.1 加载数据集

打开hugging face,
在这里插入图片描述
我们这里使用msra_ner数据集

ner_datasets = load_dataset("msra_ner", cache_dir="./data")
ner_datasets
DatasetDict({
    train: Dataset({
        features: ['id', 'tokens', 'ner_tags'],
        num_rows: 45001
    })
    test: Dataset({
        features: ['id', 'tokens', 'ner_tags'],
        num_rows: 3443
    })
})

查看数据

print(ner_datasets["train"][0])
{'id': '0', 'tokens': ['当', '希', '望', '工', '程', '救', '助', '的', '百', '万', '儿', '童', '成', '长', '起', '来', ',', '科', '教', '兴', '国', '蔚', '然', '成', '风', '时', ',', '今', '天', '有', '收', '藏', '价', '值', '的', '书', '你', '没', '买', ',', '明', '日', '就', '叫', '你', '悔', '不', '当', '初', '!'], 'ner_tags': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}

这个数据集的第一条好像没有被标注,我们来看下,这个数据集的标注类型

ner_datasets["train"].features
{'id': Value(dtype='string', id=None),
 'tokens': Sequence(feature=Value(dtype='string', id=None), length=-1, id=None),
 'ner_tags': Sequence(feature=ClassLabel(names=['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC'], id=None), length=-1, id=None)}

label_list = ner_datasets["train"].features["ner_tags"].feature.names
label_list
['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC']

这个是IOB-2类型

2.2 数据预处理

tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese")
tokenizer(ner_datasets["train"][0]["tokens"], is_split_into_words=True) 

{'input_ids': [101, 2496, 2361, 3307, 2339, 4923, 3131, 1221, 4638, 4636, 674, 1036, 4997, 2768, 7270, 6629, 3341, 8024, 4906, 3136, 1069, 1744, 5917, 4197, 2768, 7599, 3198, 8024, 791, 1921, 3300, 3119, 5966, 817, 966, 4638, 741, 872, 3766, 743, 8024, 3209, 3189, 2218, 1373, 872, 2637, 679, 2496, 1159, 8013, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}  

前面我们查看数据时,看到数据其实已经分词了, 对于已经做好tokenize的数据,要指定is_split_into_words参数为True

# 借助word_ids 实现标签映射
def process_function(examples):
    tokenized_exmaples = tokenizer(examples["tokens"], max_length=128, truncation=True, is_split_into_words=True)
    labels = []
    for i, label in enumerate(examples["ner_tags"]):
        word_ids = tokenized_exmaples.word_ids(batch_index=i)
        label_ids = []
        for word_id in word_ids:
            if word_id is None:
                label_ids.append(-100)
            else:
                label_ids.append(label[word_id])
        labels.append(label_ids)
    tokenized_exmaples["labels"] = labels
    return tokenized_exmaples

这段代码是一个处理函数,用于对给定的例子进行标记映射。它接受一个包含文本和相应命名实体识别(NER)标签的字典作为输入,并返回一个包含标记化文本和映射标签的字典。以下是该函数的详细解释:

  1. tokenizer:这是一个标记器函数,用于将文本转换为标记。它接受文本、最大长度、截断标志和是否按单词分割的标志作为输入。
  2. tokenized_exmaples = tokenizer(examples["tokens"], max_length=128, truncation=True, is_split_into_words=True):这行代码使用标记器函数对输入文本进行标记化处理,并将结果存储在tokenized_exmaples变量中。最大长度设置为128,如果文本长度超过128,则进行截断。同时,标记器会按单词进行分割。
  3. labels = []:这是一个空列表,用于存储映射后的标签。
  4. for i, label in enumerate(examples["ner_tags"])::这行代码遍历输入字典中的NER标签,并为每个标签分配一个索引i
  5. word_ids = tokenized_exmaples.word_ids(batch_index=i):这行代码获取标记化文本中的单词ID。word_ids是一个列表,其中包含与每个标记对应的单词ID。如果标记是一个子单词,则其单词ID与其前一个标记的单词ID相同;如果标记是一个特殊标记(如CLS或SEP),则其单词ID为None。
  6. label_ids = []:这是一个空列表,用于存储映射后的标签ID。
  7. for word_id in word_ids::这行代码遍历word_ids列表中的每个单词ID。
  8. if word_id is None::这行代码检查单词ID是否为None。如果是,说明当前标记是一个特殊标记,我们将标签ID设置为-100。
  9. else::如果单词ID不是None,说明当前标记是一个单词的一部分。我们将输入标签中的相应标签ID添加到label_ids列表中。
  10. labels.append(label_ids):这行代码将label_ids列表添加到labels列表中。
  11. tokenized_exmaples["labels"] = labels:这行代码将labels列表添加到标记化示例字典中。
  12. return tokenized_exmaples:这行代码返回包含标记化文本和映射标签的字典。

总之,这个处理函数使用标记器对输入文本进行标记化处理,并将输入标签映射到标记化文本上。映射后的标签将用于后续的命名实体识别任务。

tokenized_datasets = ner_datasets.map(process_function, batched=True)
tokenized_datasets
DatasetDict({
    train: Dataset({
        features: ['id', 'tokens', 'ner_tags', 'input_ids', 'token_type_ids', 'attention_mask', 'labels'],
        num_rows: 45001
    })
    test: Dataset({
        features: ['id', 'tokens', 'ner_tags', 'input_ids', 'token_type_ids', 'attention_mask', 'labels'],
        num_rows: 3443
    })
})

找一个数据看一下

print(tokenized_datasets["train"][5])
{'id': '5', 'tokens': ['我', '们', '是', '受', '到', '郑', '振', '铎', '先', '生', '、', '阿', '英', '先', '生', '著', '作', '的', '启', '示', ',', '从', '个', '人', '条', '件', '出', '发', ',', '瞄', '准', '现', '代', '出', '版', '史', '研', '究', '的', '空', '白', ',', '重', '点', '集', '藏', '解', '放', '区', '、', '国', '民', '党', '毁', '禁', '出', '版', '物', '。'], 'ner_tags': [0, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 4, 0, 0, 0, 0, 0, 0], 'input_ids': [101, 2769, 812, 3221, 1358, 1168, 6948, 2920, 7195, 1044, 4495, 510, 7350, 5739, 1044, 4495, 5865, 868, 4638, 1423, 4850, 8024, 794, 702, 782, 3340, 816, 1139, 1355, 8024, 4730, 1114, 4385, 807, 1139, 4276, 1380, 4777, 4955, 4638, 4958, 4635, 8024, 7028, 4157, 7415, 5966, 6237, 3123, 1277, 510, 1744, 3696, 1054, 3673, 4881, 1139, 4276, 4289, 511, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 'labels': [-100, 0, 0, 0, 0, 0, 1, 2, 2, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 4, 0, 0, 0, 0, 0, 0, -100]}

2.3 创建模型

# 对于所有的非二分类任务,切记要指定num_labels,否则就会device错误
model = AutoModelForTokenClassification.from_pretrained("bert-base-chinese", num_labels=len(label_list))

2.4 创建评估函数

seqeval = evaluate.load("seqeval")
seqeval

这里使用seqeval进行计算,我们使用开头那个例子来看一下

词组Gold标签Predict标签
B-PERB-PER
I-PERI-PER
OO
B-LOCB-LOC
I-LOCI-LOC
OO
OO
OO
B-ORGB-ORG
I-ORGI-ORG
I-ORGO
I-ORGO
references = [["B-PER", "I-PER", "O", "B-LOC", "I-LOC", "O", "O", "O", "B-ORG", "I-ORG", "I-ORG", "I-ORG"]]
predictions = [["B-PER", "I-PER", "O", "B-LOC", "I-LOC", "O", "O", "O", "B-ORG", "I-ORG", "O", "O"]]

results = seqeval.compute(predictions=predictions, references=references)

{'LOC': {'precision': 1.0, 'recall': 1.0, 'f1': 1.0, 'number': 1},
 'ORG': {'precision': 0.0, 'recall': 0.0, 'f1': 0.0, 'number': 1},
 'PER': {'precision': 1.0, 'recall': 1.0, 'f1': 1.0, 'number': 1},
 'overall_precision': 0.6666666666666666,
 'overall_recall': 0.6666666666666666,
 'overall_f1': 0.6666666666666666,
 'overall_accuracy': 0.8333333333333334}

创建评估函数

import numpy as np

def eval_metric(pred):
    predictions, labels = pred
    predictions = np.argmax(predictions, axis=-1)

    # 将id转换为原始的字符串类型的标签
    true_predictions = [
        [label_list[p] for p, l in zip(prediction, label) if l != -100]
        for prediction, label in zip(predictions, labels) 
    ]

    true_labels = [
        [label_list[l] for p, l in zip(prediction, label) if l != -100]
        for prediction, label in zip(predictions, labels) 
    ]

    result = seqeval.compute(predictions=true_predictions, references=true_labels, mode="strict", scheme="IOB2")

    return {
        "f1": result["overall_f1"]
    }
    

这段代码定义了一个评估指标函数eval_metric,用于评估序列标注模型在命名实体识别(NER)任务上的性能。函数的输入是一个元组pred,其中包含模型预测的分数和真实的标签。代码使用numpy库来处理数组运算,并使用seqeval库来计算评估指标。
以下是代码的详细解释:

  1. import numpy as np:导入numpy库,用于执行高效的数学运算。
  2. def eval_metric(pred)::定义一个名为eval_metric的函数,它接受一个参数pred,这是一个包含模型预测分数和真实标签的元组。
  3. predictions, labels = pred:将pred元组解包为两个变量predictionslabels,分别存储模型预测的分数和真实的标签。
  4. predictions = np.argmax(predictions, axis=-1):使用numpyargmax函数沿最后一个轴(即类别轴)找到每个样本的最大预测分数的索引,这些索引对应于预测的标签ID。
  5. true_predictions = [...]:这是一个列表推导式,用于将预测的标签ID转换为原始的字符串类型的标签。对于每个预测和标签序列,它遍历它们并创建一个新的列表,其中只包含标签不是-100的元素。label_list[p]用于将标签ID转换为字符串标签。
  6. true_labels = [...]:这是另一个列表推导式,与true_predictions类似,但它用于转换真实的标签ID为字符串标签。
  7. result = seqeval.compute(predictions=true_predictions, references=true_labels, mode="strict", scheme="IOB2"):这行代码使用seqeval库的compute函数来计算评估指标。true_predictions是模型的预测,true_labels是真实的标签。mode参数设置为"strict",表示严格评估模式,scheme参数设置为"IOB2",表示使用IOB2标签格式。
  8. return {"f1": result["overall_f1"]}:函数返回一个字典,其中包含整体的F1分数,这是NER任务中常用的评估指标。
    总之,这个函数用于评估模型在NER任务上的性能,它将模型输出的分数转换为标签,并使用seqeval库来计算F1分数。

2.5 创建训练器

args = TrainingArguments(
    output_dir="models_for_ner",
    per_device_train_batch_size=64,
    per_device_eval_batch_size=128,
    evaluation_strategy="epoch",
    save_strategy="epoch",
    metric_for_best_model="f1",
    load_best_model_at_end=True,
    logging_steps=50,
    num_train_epochs=1,
    report_to=['tensorboard']
)

为了节省时间我们只训练一遍num_train_epochs=1

2.6 模型训练

trainer = Trainer(
    model=model,
    args=args,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["test"],
    compute_metrics=eval_metric,
    data_collator=DataCollatorForTokenClassification(tokenizer=tokenizer)
)
trainer.train()

在这里插入图片描述
F1达到了0.946

2.7 评估

trainer.evaluate(eval_dataset=tokenized_datasets["test"])

{'eval_loss': 0.02092777192592621,
 'eval_f1': 0.9463030643800956,
 'eval_runtime': 16.7905,
 'eval_samples_per_second': 205.056,
 'eval_steps_per_second': 1.608,
 'epoch': 1.0}

2.8 预测

# 如果模型是基于GPU训练的,那么推理时要指定device
# 对于NER任务,可以指定aggregation_strategy为simple,得到具体的实体的结果,而不是token的结果
ner_pipe = pipeline("token-classification", model=model, tokenizer=tokenizer, device=0, aggregation_strategy="simple")

res = ner_pipe("马云在杭州创建了阿里巴巴")
res

[{'entity_group': 'PER',
  'score': 0.9968899,
  'word': '马 云',
  'start': 0,
  'end': 2},
 {'entity_group': 'LOC',
  'score': 0.99767697,
  'word': '杭 州',
  'start': 3,
  'end': 5},
 {'entity_group': 'ORG',
  'score': 0.98138344,
  'word': '阿 里 巴 巴',
  'start': 8,
  'end': 12}]

完整代码

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

新闻 | 电子系协同智能中心与昌平区未来高教园及多所高校开展交流,共话智能无人平台建设

2024年4月8日,清华大学电子工程系在北京昌平两岸共盈科技产业园电子系地空协同智能无人平台基地成功举办“美团杯”智能无人机挑战赛,清华大学电子系党委书记沈渊、昌平区未来城管委会校城融合处处长熊玉川、清华大学团委副书记黄峰等出席。此外来自昌平…

【面经】汇总

面经 Java基础集合都有哪些面向对象的三大特点ArrayList和LinkedList的区别?ArrayList底层扩容是怎么实现的?讲一讲HashMap、以及put方法的过程讲一讲HashMap的扩容过程Hashmap为什么要用红黑树而不用其他的树?Java8新特性有哪些LoadFactor负…

Scala 03 —— Scala OOP Extension

Scala 2.1 —— Scala OOP Extension 一、正则 文章目录 Scala 2.1 —— Scala OOP Extension一、正则1.1 Java正则和Scala正则的区别1.2 Java正则和Scala正则的的基本知识点Java正则Scala正则 1.3 练习练习一:使用正则表达式解析日志方法一:使用findAl…

99AI3.3二开稳定版(NineAi内核升级)免授权无后门AI系统源码部署及详细安装教程

99AIv3.3.0是基于 NineAI 二开的可商业化 AI Web 应用(免授权,无后门,非盗版,已整合前后端,支持快速部署)。未编译源码暂不开源,相比稳定版,开发版进度更快一些。前端改进&#xff1…

【Python】全面掌握 Collections Deque:队列与栈的高效实现及动态内存管理指南

文章目录 第一章:deque 的定义和特性1. 什么是双端队列(deque)2. deque 与普通列表(list)的性能差异 第二章:构造函数1. 如何创建一个 deque2. 可选参数 maxlen 的作用和使用场景 第三章:添加和…

vue3使用echarts做树图tree

vue3使用echarts做树图tree 1.安装echarts npm install echarts --save2.在main.js引入 import * as echarts from echarts // 全局方法 app.config.globalProperties.$echarts echarts3.使用 <div id"myChart" :style"{ width: 1000px, height: 1000px …

如何设置“mumu模拟器”使用fiddler抓取APP包?

1、打开fiddler-->tools-->optinons,设置如下信息https信息和connections 2、下载证书tools-->optinons-->https-->actions->Export Root Certificate to Desktop到桌面 3、mumu模拟器&#xff0c;安装证书 1)mumu进入桌面有个文件共享&#xff0c;打开后将桌…

python—字符串与正则表达式

1、编写程序&#xff0c;生成一个由15个不重复的大小写字母组成的列表。 &#xff08;1&#xff09;源代码&#xff1a; import random import string list1 [] while len(list1) < 15: x random.choice(string.ascii_letters) if x not in list1: list1.append(x) print…

pycharm-ieda-phpstorm超级好用插件,一键解释代码

功能&#xff1a;解释你看不懂的代码 当你在写python和Java代码的时候&#xff0c;总有你看不懂的代码&#xff0c;怎么办&#xff1f;csdn搜&#xff1f;那不麻烦&#xff0c;直接插件解决。 来安装&#xff1a;文件-设置 点击插件-Marketplace-搜索通义灵码 安装完成后&…

Qt Creator中变量与函数的注释 - 鼠标悬浮可显示

Qt Creator中变量与函数的注释 - 鼠标悬浮可显示 引言一、变量注释二、函数注释三、参考链接 引言 代码注释在软件开发中起着至关重要的作用。它们不仅有助于开发者理解和维护代码&#xff0c;还能促进团队协作&#xff0c;提高代码的可读性和可维护性。适当的注释应该是简洁明…

VoxAtnNet:三维点云卷积神经网络

VoxAtnNet:三维点云卷积神经网络 摘要IntroductionProposed VoxAtnNet 3D Face PAD3D face point cloud presentation attack Dataset (3D-PCPA) VoxAtnNet: A 3D Point Clouds Convolutional Neural Network for 摘要 面部生物识别是智能手机确保可靠和可信任认证的重要组件。…

16册 | 移动机器人(自动驾驶)系列

此文档整理推荐了16本移动机器人&#xff08;自动驾驶&#xff09;相关的书籍&#xff0c;内容包括&#xff1a;ROS、机器人基础开发、分布式机器人控制、集群机器人控制、嵌入式机器人、多传感器融合等等。 学习&#xff0c;切勿急于求成&#xff0c;读书自学&#xff0c;需多…

栈和队列总结

文章目录 前言一、栈和队列的实现1.栈的具体实现2.循环顺序队列的具体实现 二、栈和队列总结总结 前言 T_T此专栏用于记录数据结构及算法的&#xff08;痛苦&#xff09;学习历程&#xff0c;便于日后复习&#xff08;这种事情不要啊&#xff09;。所用教材为《数据结构 C语言版…

【启明智显技术分享】ESP32系列WiFi无线空中抓包指南

前言&#xff1a; 本文档旨在介绍 windows10 系统下网卡抓包工具(AC-1200)的驱动安装过程、Omnipeek 软件安装过程及Omnipeek软件与网卡抓包工具配合抓包的演示过程。 1、抓包工具(AC-1200)驱动安装 1.1 准备好抓包工具及厂家提供的抓包工具驱动文件 1.2 插上 USB 网卡&…

Linux——socket套接字与udp通信

目录 一、理解IP地址与端口 二、socket套接字 三、TCP与UDP的关系 四、网络字节序 五、socket编程 1.socket()创建套接字 2.填充sockaddr_in 结构体 3.bind() 绑定信息 4.recvfrom()接收消息 5.sendto()发送消息 六、UdpServer代码 一、理解IP地址与端口 IP地址是In…

Leetcode—1256. 加密数字【中等】Plus(bitset、find_first_not_of、erase)

2024每日刷题&#xff08;120&#xff09; Leetcode—1256. 加密数字 实现代码 class Solution { public:string encode(int num) {string ans;num 1;while(num ! 0) {ans to_string(num & 1);num num >> 1;}if(ans.empty()) {return "";} else {stri…

17 如何设计一锤子买卖的SDK

在前三个模块里&#xff0c;我将微服务根据目的性划分为三大类&#xff1a;读、写与扣减类&#xff0c;并针对每一大类涉及的各项技术问题讲解了应对方案。其实&#xff0c;每一类微服务除了本身业务特点涉及的技术问题外&#xff0c;在纯技术维度也有很多共性问题&#xff0c;…

房产中介小程序高效开发攻略:从模板到上线一站式服务

对于房产中介而言&#xff0c;拥有一个高效且用户友好的小程序是提升业务、增强客户黏性的关键。而采用直接复制模板的开发方式&#xff0c;无疑是实现这一目标的最佳途径&#xff0c;不仅简单快捷&#xff0c;而且性价比极高。 在众多小程序模板开发平台中&#xff0c;乔拓云网…

docker容器通俗理解

前言 如果大家没使用过Docker,就在电脑上下载一个VMware Workstation Pro&#xff0c;创建一个虚拟机安装一个windows操作一下感受一下,为什么我的电脑上还以再安装一台windows主机&#xff1f;其实你可以理解为Docker就是Linux系统的一个虚拟机软件。 我的Windows也可以安装…

WMS仓库库存管理软件如何优化工厂的仓库管理-亿发

如果一家工厂没有专业的WMS仓储软件支撑&#xff0c;管理原材料、辅料、半成品和产成品等环节可能会面临诸多问题。 在仓库管理方面&#xff0c;缺乏安全库存的管理会导致库存不足或过剩&#xff0c;而没有及时的缺货分析可能会导致生产中断。全凭人工核算剩余库存和订单质检的…
最新文章