第4章 命名实体识别实战:HMM

命名实体识别实战:HMM

代码仓库:https://github.com/daiyizheng/NER-Set/tree/master/hmm
理论基础:https://blog.youkuaiyun.com/weixin_42486623/article/details/118122786

使用数据

  • MSRA
B-ORG
O
B-LOC
I-PER
I-ORG
B-PER
I-LOC
  • ontonotes5
I-GPE
B-ORDINAL
B-PERSON
B-PRODUCT
I-ORG
I-PRODUCT
I-LAW
B-MONEY
I-DATE
B-DATE
I-NORP
I-TIME
I-LANGUAGE
B-WORK_OF_ART
B-FAC
I-QUANTITY
B-LAW
B-LOC
I-FAC
B-QUANTITY
O
I-EVENT
B-LANGUAGE
B-EVENT
I-MONEY
I-CARDINAL
I-WORK_OF_ART
B-NORP
B-GPE
I-ORDINAL
B-CARDINAL
B-ORG
I-PERSON
B-PERCENT
I-LOC
I-PERCENT
B-TIME
  • resume
I-LOC
B-CONT
I-TITLE
B-LOC
B-NAME
I-PRO
B-EDU
I-RACE
B-RACE
B-TITLE
B-ORG
O
I-EDU
I-NAME
I-ORG
B-PRO
I-CONT
  • weibo
B-GPE
B-ORG
I-PER
B-PER
I-LOC
B-LOC
I-ORG
O
I-GPE

模型

class HMM(object):
    def __init__(self, hidden_status, logger):
        """
        :param hidden_status: int, 隐状态数
        """
        self.hmm_N = hidden_status
        ## 状态转移概率矩阵 A[i][j] 表示从i状态转移到j状态的概率
        self.hmm_A = torch.zeros(self.hmm_N, self.hmm_N)
        ## 初始状态概率 Pi[i] 表示初始时刻为状态i的概率
        self.hmm_pi = torch.zeros(self.hmm_N)
        ## 日志
        self.logger = logger

    def _build_corpus_map(self, sentences_list):
        """
        构建词典
        :param sentences_list: list, 句子数组
        """
        char2id = {}
        for sentence in sentences_list:
            for word in sentence:
                if word not in char2id:
                    char2id[word] = len(char2id)
        return char2id

    def _init_emission(self):
        """
        初始化观测矩阵
        """
        self.hmm_M = len(self.word2id)
        # 观测概率矩阵,B[i][j] 表示i状态下生成j观测的概率
        self.hmm_B = torch.zeros(self.hmm_N,self.hmm_M)

    def train(self, sentences_list, tags_list):
        """HMM的训练,即根据训练语料对模型参数进行估计,
           因为我们有观测序列以及其对应的状态序列,所以我们
           可以使用极大似然估计的方法来 估计 隐马尔可夫模型的参数
        参数:
            :param sentences_list: list,其中每个元素由字组成的列表,如 ['担','任','科','员']
            :param tags_list: list,其中每个元素是由对应的标注组成的列表,如 ['O','O','B-TITLE', 'E-TITLE']
        """
        start_time = time.time()
        logger = self.logger
        ## 查看句子数量与标签的数量是否相等
        assert len(sentences_list) == len(tags_list), "the lens of tag_lists is not eq to word_lists"
        logger.info("开始构建token字典。。。")
        self.word2id = self._build_corpus_map(sentences_list)
        self.tag2id = self._build_corpus_map(tags_list)
        self.id2tag = dict((id_, tag) for tag, id_ in self.tag2id.items())
        logger.info('训练语料总数:{}'.format(len(sentences_list)))
        logger.info('词典总数:{}'.format(len(self.word2id)))
        logger.info('标签总数:{}'.format(len(self.tag2id)))

        assert self.hmm_N == len(self.tag2id), "hidden_status is {}, but total tag is {}".format(self.hmm_N, len(self.tag2id))
        self._init_emission()
        logger.info('构建词典完成{:>.4f}s'.format(time.time()-start_time))
        logger.info('开始构建转移概率矩阵...')

        ## 估计转移概率矩阵
        for tags in tqdm(tags_list):
            seq_len = len(tags)
            for i in range(seq_len-1):
                current_tagid = self.tag2id[tags[i]] ## 当前tagid
                next_tagid = self.tag2id[tags[i+1]]  ## 下一个tagid
                self.hmm_A[current_tagid][next_tagid] += 1

        # 问题:如果某元素没有出现过,该位置为0,这在后续的计算中是不允许的
        # 解决方法:我们将等于0的概率加上很小的数
        self.hmm_A[self.hmm_A == 0.] = 1e-10  ## 位置为0 填补小数
        self.hmm_A = self.hmm_A / self.hmm_A.sum(axis=1, keepdims=True) # 计算概率
        logger.info('完成转移概率矩阵构建. {:>.4f}s'.format(time.time() - start_time))
        logger.info('开始构建观测概率矩阵...')

        ## 估计观测概率矩阵
        for tags, sentence in tqdm(zip(tags_list, sentences_list)):
            assert len(tags) == len(sentence), "the lens of tag_list is not eq to word_list"
            for tag, word in zip(tags, sentence):
                tag_id = self.tag2id[tag]# 当前标签
                word_id= self.word2id[word]
                self.hmm_B[tag_id][word_id] += 1
        self.hmm_B[self.hmm_B == 0.] = 1e-10
        self.hmm_B = self.hmm_B / self.hmm_B.sum(axis=1, keepdims=True)
        logger.info('完成观测概率矩阵构建. {:>.4f}s'.format(time.time() - start_time))
        logger.info('初始化初识状态概率...')

        # 估计初始状态概率
        for tags in tqdm(tags_list):
            init_tags = self.tag2id[tags[0]]
            self.hmm_pi[init_tags] += 1
        self.hmm_pi[self.hmm_pi == 0.] = 1e-10
        self.hmm_pi = self.hmm_pi / self.hmm_pi.sum()
        logger.info('完成初始状态概率构建. {:>.4f}s'.format(time.time() - start_time))

    def predict(self, sentences_list):
        """
        :param sentences_list list,其中每个元素由字组成的列表,如 [['担','任','科','员']]
        """
        logger = self.logger
        logger.info('启动HMM解码预测...')
        logger.info('预测句子总数:{}'.format(len(sentences_list)))
        pred_tag_lists = []
        for sentence in tqdm(sentences_list):
            pre_tag_list = self.decoding(sentence) ## 维特比算法函数解码
            pred_tag_lists.append(pre_tag_list)
        return pred_tag_lists

    def decoding(self, word_list):
        """
        :param sentence list,其中每个元素由字组成的列表,如 ['担','任','科','员']

        使用维特比算法对给定观测序列求状态序列, 这里就是对字组成的序列,求其对应的标注。
        维特比算法实际是用动态规划解隐马尔可夫模型预测问题,即用动态规划求概率最大路径(最优路径)
        这时一条路径对应着一个状态序列
        """
        A = torch.log(self.hmm_A)
        B = torch.log(self.hmm_B)
        Pi = torch.log(self.hmm_pi)

        # 初始化 维特比矩阵viterbi 它的维度为[状态数, 序列长度]
        seq_len = len(word_list)
        viterbi = torch.zeros(self.hmm_N, seq_len)

        ## 等解码的时候,我们用backpointer进行回溯,以求出最优路径
        backpointer = torch.zeros(self.hmm_N, seq_len).long()

        start_wordid = self.word2id.get(word_list[0], None)
        Bt = B.t()
        if start_wordid is None:
            # 如果字不在字典里, 则假设状态的概率分布是均匀的
            bt = torch.log(torch.ones(self.hmm_N)/self.hmm_N)
        else:
            bt = Bt[start_wordid]

        viterbi[:, 0] = Pi+ bt
        backpointer[:, 0] = -1

        for step in range(1, seq_len):
            wordid = self.word2id.get(word_list[step], None)
            # 处理字不在字典中的情况
            # bt是在t时刻字为word时,则假设状态分布式均匀的
            if wordid is None:
                # 如果字不再字典里,则假设状态的概率分布是均匀的
                bt = torch.log(torch.ones(self.hmm_N) / self.hmm_N)
            else:
                bt = Bt[wordid]  # 否则从观测概率矩阵中取bt

            for tag_id in range(len(self.tag2id)):
                max_prob, max_id = torch.max(
                    viterbi[:, step-1] + A[:, tag_id],
                    dim=0
                )
                viterbi[tag_id, step] = max_prob + bt[tag_id]
                backpointer[tag_id,step] = max_id

        # 终止, t=seq_len 即 viterbi[:, seq_len]中的最大概率,就是最优路径的概率
        best_path_prob, best_path_pointer = torch.max(
            viterbi[:, seq_len - 1], dim=0
        )
        # 回溯,求最优路径
        best_path_pointer  = best_path_pointer.item()
        best_path = [best_path_pointer]
        for back_step in range(seq_len-1, 0, -1):
            best_path_pointer = backpointer[best_path_pointer, back_step]
            best_path_pointer = best_path_pointer.item()
            best_path.append(best_path_pointer)

        # 将tag_id组成的序列转化为tag
        assert len(best_path) == len(word_list)
        tag_list = [self.id2tag[id_] for id_ in reversed(best_path)]

        return tag_list

评价指标

def model_metrics(true_labels, pre_labels, logger):
    """
    :param true_labels 真实标签数据 [O,O,B-OR, I-OR]
    :param pre_labels 预测标签数据 [O,O,B-OR, I-OR]
    :param logger 日志实例
    """
    start_time = time.time()
    acc = accuracy_score(true_labels, pre_labels)
    f1score = f1_score(true_labels, pre_labels, average='macro')
    report = classification_report(true_labels, pre_labels, digits=4)
    msg = '\nTest Acc: {0:>6.2%}, Test f1: {1:>6.2%}'
    logger.info(msg.format(acc, f1score))
    logger.info("\nPrecision, Recall and F1-Score...")
    logger.info("\n{}".format(report))
    time_dif = time.time() - start_time
    logger.info("Time usage:{0:>.6}s".format(time_dif))
f1scorehmm
weibo68.73
msra56.72
resume71.57
ontonotes553.45
内容概要:本文档详细介绍了在三台CentOS 7服务器(IP地址分别为192.168.0.157、192.168.0.158和192.168.0.159)上安装和配置Hadoop、Flink及其他大数据组件(如Hive、MySQL、Sqoop、Kafka、Zookeeper、HBase、Spark、Scala)的具体步骤。首先,文档说明了环境准备,包括配置主机名映射、SSH免密登录、JDK安装等。接着,详细描述了Hadoop集群的安装配置,包括SSH免密登录、JDK配置、Hadoop环境变量设置、HDFS和YARN配置文件修改、集群启动与测试。随后,依次介绍了MySQL、Hive、Sqoop、Kafka、Zookeeper、HBase、Spark、Scala和Flink的安装配置过程,包括解压、环境变量配置、配置文件修改、服务启动等关键步骤。最后,文档提供了每个组件的基本测试方法,确保安装成功。 适合人群:具备一定Linux基础和大数据组件基础知识的运维人员、大数据开发工程师以及系统管理员。 使用场景及目标:①为大数据平台建提供详细的安装指南,确保各组件能够顺利安装和配置;②帮助技术人员快速掌握Hadoop、Flink等大数据组件的安装与配置,提升工作效率;③适用于企业级大数据平台的建与维护,确保集群稳定运行。 其他说明:本文档不仅提供了详细的安装步骤,还涵盖了常见的配置项解释和故障排查建议。建议读者在安装过程中仔细阅读每一步骤,并根据实际情况调整配置参数。此外,文档中的命令和配置文件路径均为示例,实际操作时需根据具体环境进行适当修改。
在无线通信领域,天线阵列设计对于信号传播方向和覆盖范围的优化至关重要。本题要求设计一个广播电台的天线布局,形成特定的水平面波瓣图,即在东北方向实现最大辐射强度,在正东到正北的90°范围内辐射衰减最小且无零点;而在其余270°范围内允许出现零点,且正西和西南方向必须为零。为此,设计了一个由4个铅垂铁塔组成的阵列,各铁塔上的电流幅度相等,相位关系可自由调整,几何布置和间距不受限制。设计过程如下: 第一步:构建初级波瓣图 选取南北方向上的两个点源,间距为0.2λ(λ为电磁波波长),形成一个端射阵。通过调整相位差,使正南方向的辐射为零,计算得到初始相位差δ=252°。为了满足西南方向零辐射的要求,整体相位再偏移45°,得到初级波瓣图的表达式为E1=cos(36°cos(φ+45°)+126°)。 第二步:构建次级波瓣图 再选取一个点源位于正北方向,另一个点源位于西南方向,间距为0.4λ。调整相位差使西南方向的辐射为零,计算得到相位差δ=280°。同样整体偏移45°,得到次级波瓣图的表达式为E2=cos(72°cos(φ+45°)+140°)。 最终组合: 将初级波瓣图E1和次级波瓣图E2相乘,得到总阵的波瓣图E=E1×E2=cos(36°cos(φ+45°)+126°)×cos(72°cos(φ+45°)+140°)。通过编程实现计算并绘制波瓣图,可以看到三个阶段的波瓣图分别对应初级波瓣、次级波瓣和总波瓣,最终得到满足广播电台需求的总波瓣图。实验代码使用MATLAB编写,利用polar函数在极坐标下绘制波瓣图,并通过subplot分块显示不同阶段的波瓣图。这种设计方法体现了天线阵列设计的基本原理,即通过调整天线间的相对位置和相位关系,控制电磁波的辐射方向和强度,以满足特定的覆盖需求。这种设计在雷达、卫星通信和移动通信基站等无线通信系统中得到了广泛应用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

发呆的比目鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值