精华内容
下载资源
问答
  • crf命名实体识别代码
    2022-05-30 10:51:10

    方法1:基于CRF++实现NER

    CRF++是著名的条件随机场的开源工具,也是目前综合性能最佳的CRF工具,采用C++语言编写而成。其最重要的功能我认为是采用了特征模板。这样就可以自动生成一系列的特征函数,而不用我们自己生成特征函数,我们要做的就是寻找特征,比如词性等。

    官网地址:http://taku910.github.io/crfpp/

    实践2:基于sklearn_crfsuite实现NER

    sklearn-crfsuite是基于CRFsuite库的一款轻量级的CRF库。该库兼容sklearn的算法,因此可以结合sklearn库的算法设计实体识别系统。sklearn-crfsuite不仅提供了条件随机场的训练和预测方法还提供了评测方法。

    https://sklearn-crfsuite.readthedocs.io/en/latest/#

    安装:pip install sklearn-crfsuite

    sklearn-crfsuite安装的一系列问题:

    1.打开Anaconda Prompt,先将环境切换到pytorch下面(相关环境配置都在此)

    activate pytorch
    

    2.采用pip安装,报错

     3.参考大佬的博客,增加清华源pip清华源(清华大学国内镜像)安装地址_奋斗乌托邦的博客-CSDN博客_清华源

    pip install -i https://pypi.tuna.tsinghua.edu.cn/simple 包名
    

    更多相关内容
  • 国科大自然语言处理第三次作业
  • pytorch lstm+crf、bilstm+crf 、LSTM CRF 命名实体识别代码 代码和数据可以直接运行
  • 使用 CRF++ 训练命名实体识别模型
  • 下载即可运行 1、data文件夹(数据) 2、bilstm_crf_model.py(模型) 3、process_data.py(数据预处理文件) 4、train.py(模型训练文件) 5、val.py(测试文件) 6、model.png(模型结构图)
  • 首先使用BERT语言模型进行文本特征提取获取字粒度向量矩阵, BiLSTM用于上下文信息的提取, 同时结合CRF模型提取全局最优序列, 最终得到景点命名实体. 实验表明, 提出的模型性能提升显著, 在实际旅游领域内景点识别的...
  • 文章目录命名实体识别BiLSTM-CRF代码实现 -- 潘登同学的NLP笔记条件随机场概述直观的例子CRF的特征函数们举些例子对比逻辑回归对比HMMBilstm-CRF命名实体识别代码实现数据预处理训练模型模型应用(维特比算法) ...

    命名实体识别BiLSTM-CRF代码实现 – 潘登同学的NLP笔记

    条件随机场概述

    条件随机场(Conditional Random Fields),是在给定一组输入随机变量条件下另外一组输出随机变量的条件概率分布模型,它是一种判别式的概率无向图模型,既然是判别式,拿就是对条件概率分布建模;

    之前也在BiLSTM-CRF中聊过,判别式模型就是给定x计算y,生成式模型就是计算联合分布的参数;

    设有线性链结构的随机变量序列 X = ( X 1 , X 2 , . . . , X n ) , Y = ( Y 1 , Y 2 , . . . , Y N ) X=(X_1,X_2,...,X_n),Y=(Y_1,Y_2,...,Y_N) X=(X1,X2,...,Xn),Y=(Y1,Y2,...,YN),在给定观察序列 X X X 的条件下,随机变量序列 Y Y Y 的条件概率分布为 P ( Y ∣ X ) P(Y|X) P(YX),若其满足马尔科夫特性,即
    P ( Y i ∣ X , Y 1 , Y 2... Y n ) = P ( Y i ∣ X , Y i − 1 , Y i + 1 ) P(Yi|X,Y1,Y2...Yn)=P(Yi|X,Yi−1,Yi+1) P(YiX,Y1,Y2...Yn)=P(YiX,Yi1,Yi+1)
    这时 P ( Y ∣ X ) P(Y|X) P(YX) 则为线性链条件随机场

    条件随机场(ConditionalRandom Field,CRF)是经典 NER 的主流模型。

    • 它的目标函数不仅考虑输入的状态特征函数,而且还包含了标签转移特征函数。
    • 在训练时可以使用 SGD 学习模型参数。
    • 在已知模型时,给输入序列求预测输出序列即求使目标函数最大化的最优序列,是一个动态规划问题,可以使用 Viterbi 算法解码来得到最优标签序列。
    • CRF 的优点在于其为一个位置进行标注的过程中可以利用丰富的内部及上下文特征信息。

    直观的例子

    假设你有许多小明同学一天内不同时段的照片,从小明提裤子起床到脱裤子睡觉各个时间段都有(小明是照片控!)。现在的任务是对这些照片进行分类。比如有的照片是吃饭,那就给它打上吃饭的标签;有的照片是跑步时拍的,那就打上跑步的标签;有的照片是开会时拍的,那就打上开会的标签。问题来了,你准备怎么干?

    一个简单直观的办法就是,不管这些照片之间的时间顺序,想办法训练出一个多元分类器。就是用一些打好标签的照片作为训练数据,训练出一个模型,直接根据照片的特征来分类。例如,如果照片是早上 6:00 拍的,且画面是黑暗的,那就给它打上睡觉的标签;如果照片上有车,那就给它打上开车的标签。这样可行吗?

    乍一看可以!但实际上,由于我们忽略了这些照片之间的时间顺序这一重要信息,我们的分类器会有缺陷的。举个例子,假如有一张小明闭着嘴的照片,怎么分类?显然难以直接判断,需要参考闭嘴之前的照片,如果之前的照片显示小明在吃饭,那这个闭嘴的照片很可能是小明在咀嚼食物准备下咽,可以给它打上吃饭的标签;如果之前的照片显示小明在唱歌,那这个闭嘴的照片很可能是小明唱歌瞬间的抓拍,可以给它打上唱歌的标签。所以,为了让我们的分类器能够有更好的表现,在为一张照片分类时,我们必须将与它相邻的照片的标签信息考虑进来。这——就是条件随机场(CRF)大显身手的地方!

    CRF的特征函数们

    我们正式地定义一下什么是 CRF 中的特征函数,所谓特征函数,就是这样的函数,它接受四个参数:

    • 句子 s s s(就是我们要标注词性的句子)
    • i i i,用来表示句子 s s s 中第 i i i 个单词
    • l i l_i li,表示要评分的标注序列给第 i i i 个单词标注的词性
    • l i − 1 l_i-1 li1,表示要评分的标注序列给第 i − 1 i-1 i1 个单词标注的词性
      它的输出值是 0 或者 1,0 表示要评分的标注序列不符合这个特征,1 表示要评分的标注序列符合这个特征

    注意: 这里,我们的特征函数仅仅依靠当前单词的标签和它前面的单词的标签对标注序列进行评判,这样建立的 CRF 也叫作线性链 CRF,这是 CRF 中的一种简单情况。为简单起见,本文中我们仅考虑线性链 CRF

    最终算法的预测值 y i ^ \hat{y_i} yi^受很多个特征函数的影响,写为数学表达式
    P ( y ∣ x ) = P R e a l P a t h T o t a l P a t h = e S i Z ( x ) = 1 Z ( x ) exp ⁡ ( ∑ i , k λ k ⋅ t k ( y i , y i − 1 , x , i ) + ∑ i , l μ k ⋅ e l ( y i , x , i ) ) \begin{aligned} P(y|x) &= \frac{P_{RealPath}}{TotalPath} = \frac{e^{S_i}}{Z(x)} \\ &= \frac{1}{Z(x)}\exp(\sum_{i,k} \lambda_k \cdot t_k (y_i,y_{i-1},x,i) + \sum_{i,l} \mu_k \cdot e_l (y_i,x,i)) \end{aligned} P(yx)=TotalPathPRealPath=Z(x)eSi=Z(x)1exp(ikλktk(yi,yi1,x,i)+ilμkel(yi,x,i))

    其中 x x x s s s表达意思一致, λ k \lambda_k λktransition scores矩阵的一个元素, t k t_k tk为特征函数,特征函数个数与 λ k \lambda_k λk都是 l a b e l 2 label^2 label2个; 既然前面是transition scores的一个表达,那么后面自然是Emission scores的表达, e l e_l el也理解为特征函数;exp中的是某一条路径中的表达式; Z ( x ) Z(x) Z(x)则是所有路径的加和…

    举些例子

    f 1 ( s , i , l i , l i − 1 ) = 1 f_1(s,i,l_i,l_{i-1}) = 1 f1(s,i,li,li1)=1
    l i l_i li 是“副词”并且第 i i i 个单词以“ly”结尾时,我们就让 f 1 = 1 f_1 = 1 f1=1,其他情况 f1 为 0。不难想到, f 1 f_1 f1 特征函数的权重 λ 1 \lambda_1 λ1 应当是正的。而且 λ 1 \lambda_1 λ1 越大,表示我们越倾向于采用那些把以“ly”结尾的单词标注为“副词”的标注序列;

    f 2 ( s , i , l i , l i − 1 ) = 1 f_2(s,i,l_i,l_{i-1}) = 1 f2(s,i,li,li1)=1
    如果 i = 1 i=1 i=1 l i = 动 词 l_i=动词 li=,并且句子 s s s 是以“?”结尾时, f 2 = 1 f_2=1 f2=1,其他情况 f 2 = 0 f_2=0 f2=0。同样, λ 2 \lambda_2 λ2 应当是正的,并且 λ 2 \lambda_2 λ2 越大,表示我们越倾向于采用那些把问句的第一个单词标注为“动词”的标注序列;

    f 3 ( s , i , l i , l i − 1 ) = 1 f_3(s,i,l_i,l_{i-1}) = 1 f3(s,i,li,li1)=1
    l i − 1 l_{i-1} li1 是介词, l i l_i li 是名词时, f 3 = 1 f_3 = 1 f3=1,其他情况 f 3 = 0 f_3=0 f3=0 λ 3 \lambda_3 λ3 也应当是正的,并且 λ 3 \lambda_3 λ3 越大,说明我们越认为介词后面应当跟一个名词;

    f 3 ( s , i , l i , l i − 1 ) = 1 f_3(s,i,l_i,l_{i-1}) = 1 f3(s,i,li,li1)=1
    如果 l i l_i li l i − 1 l_{i-1} li1 都是介词,那么 f 4 f_4 f4 等于 1,其他情况 f 4 = 0 f_4=0 f4=0。这里,我们应当可以想到 λ 4 \lambda_4 λ4 是负的,并且 λ 4 \lambda_4 λ4 的绝对值越大,表示我们越不认可介词后面还是介词的标注序列;

    对比逻辑回归

    CRF的概率形式与softmax很像,softmax又是逻辑回归的推广
    p ( l ∣ s ) = exp ⁡ [ ∑ j = 1 m ∑ i = 1 n f j ( l i , l i − 1 , s , i ) ] ∑ exp ⁡ [ ∑ j = 1 m ∑ i = 1 n f j ( l i , l i − 1 , s , i ) ] p(l|s) = \frac{\exp{[\sum_{j=1}^m \sum_{i=1}^n f_j (l_i,l_{i-1},s,i)}]}{\sum \exp{[\sum_{j=1}^m \sum_{i=1}^n f_j (l_i,l_{i-1},s,i)]}} p(ls)=exp[j=1mi=1nfj(li,li1,s,i)]exp[j=1mi=1nfj(li,li1,s,i)]
    那是因为 CRF 确实基本上是逻辑回归的序列版本,鉴于逻辑回归是一个对数线性模型用于分类,CRF 是一个对数线性模型用于序列打标签

    对比HMM

    HMM 采用生成式方法去打标签,数学表达为
    p ( l , s ) = p ( l 1 ) ∏ i p ( l i ∣ l i − 1 ) p ( w i ∣ l i ) p(l,s) = p(l_1)\prod_i p(l_i|l_{i-1})p(w_i|l_i) p(l,s)=p(l1)ip(lili1)p(wili)
    CRF 更强大,它可以做 HMM 可以完成的一切,并且还能做 HMM 不能做的, 我们将HMM的表达式往CRF上靠
    log ⁡ p ( l , s ) = log ⁡ p ( l 1 ) + ∑ i log ⁡ p ( l i ∣ l i − 1 ) + ∑ i log ⁡ p ( w i ∣ l i ) \log p(l,s) = \log p(l_1) + \sum_i\log p(l_i|l_{i-1}) + \sum_i\log p(w_i|l_i) logp(l,s)=logp(l1)+ilogp(lili1)+ilogp(wili)
    如果我们考虑这些对数概率为相对应的二元的转化和发射指标特征的权重。即,我们可以构建一个 CRF 等同于任意 HMM, 对于每个HMM的转移概率 log ⁡ p ( l i = y ∣ l i − 1 = x ) \log p(l_i=y|l_{i-1}=x) logp(li=yli1=x),可以理解为只有两个输入的特征函数,每个特征函数的权重为 w x , y = log ⁡ p ( l i = y ∣ l i − 1 = x ) w_{x,y} = \log p(l_i=y|l_{i-1}=x) wx,y=logp(li=yli1=x)每个特征函数的输出都是1;

    类似地,对于每个 HMM 的发射概率 log ⁡ p ( w i ∣ l i ) \log p(w_i|l_i) logp(wili),也可以视作特征函数,每个特征函数的权重为 w x , z = log ⁡ p ( w i = z ∣ l i = x ) w_{x,z} = \log p(w_i=z|l_{i}=x) wx,z=logp(wi=zli=x);

    因此,由 CRF 计算的分数 p(l|s)使用这些特征函数是精确地成比例的对应于 HMM 计算的分数,所以每一个 HMM 是等同于一些 CRF的特征函数;

    CRFs 可以建模更丰富的标签分布,这里有两个原因:

    1. CRFs 可以定义更大的特征集合。然后 HMMs 必然是局部在本质上(因为它们被限制到二元的转换和发射特征函数,强迫每个单词仅依赖于当前的标签和每个标签仅依赖于之前的标签),CRFs 可以使用更多全局的特征。例如,一个特征在我们的 POS 标签的概率增长,一个句子首个单词标注为动词如果句子最后包含一个问号符号。
    2. CRFs可以有任意的权重 。 鉴于 HMM 的概率必须满足某些限制, 例如 0 < = p ( w i ∣ l i ) < = 1 , ∑ w P ( w i = w ∣ l i ) = 1 0<=p(w_i|l_i)<=1,\sum_w P(w_i=w|l_i)=1 0<=p(wili)<=1,wP(wi=wli)=1,但CRF 的权重是不限制的;

    HMM模型存在两个严格假设:

    • 输出观察值之间严格独立(之前概率图中的head-to-tail)
    • 状态的转移过程中当前状态只与前一状态有关(一阶马尔科夫模型)

    除此之外,HMM还会出现标注偏置问题:

    • 假设有三个状态(标签)a,b,c
    • 在语料库训练中,a转移到b的概率,大于a转移到c的概率(统计出来的),所以很受语料库统计的影响,可能会造成HMM在测试的时候只出现a到b的状态

    Bilstm-CRF命名实体识别代码实现

    数据预处理

    在这里插入图片描述

    训练模型

    在这里插入图片描述

    模型应用(维特比算法)

    在这里插入图片描述

    展开全文
  • 对应这篇文章的数据集,小伙伴们自行取走: https://sito8.blog.csdn.net/article/details/123233758?spm=1001.2014.3001.5502
  • Bert+LSTM+CRF命名实体识别 从0开始解析源代码。 理解原代码的逻辑,具体了解为什么使用预训练的bert,bert有什么作用,网络的搭建是怎么样的,训练过程是怎么训练的,输出是什么 调试运行源代码 NER目标 ...

    Bert+LSTM+CRF命名实体识别

    从0开始解析源代码。

    1. 理解原代码的逻辑,具体了解为什么使用预训练的bert,bert有什么作用,网络的搭建是怎么样的,训练过程是怎么训练的,输出是什么

    2. 调试运行源代码

    NER目标

    NER是named entity recognized的简写,对人名地名机构名日期时间专有名词等进行识别。

    结果输出标注方法

    采用细粒度标注,就是对于每一个词都给一个标签,其中连续的词可能是一个标签,与原始数据集的结构不同,需要对数据进行处理,转化成对应的细粒度标注形式。

    数据集形式修改

    形式:

    {
    	"text": "浙商银行企业信贷部叶老桂博士则从另一个角度对五道门槛进行了解读。叶老桂认为,对目前国内商业银行而言,",
    	"label": {
    		"name": {
    			"叶老桂": [
    				[9, 11],
    				[32, 34]
    			]
    		},
    		"company": {
    			"浙商银行": [
    				[0, 3]
    			]
    		}
    	}
    }
    

    修改后数据集对应格式:

    sentence: ['温', '格', '的', '球', '队', '终', '于', '又', '踢', '了', '一', '场', '经', '典', '的', '比', '赛', ',', '2', '比', '1', '战', '胜', '曼', '联', '之', '后', '枪', '手', '仍', '然', '留', '在', '了', '夺', '冠', '集', '团', '之', '内', ',']
    label: ['B-name', 'I-name', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'B-organization', 'I-organization', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O']
    

    数据预处理

    对于一个句子不进行分词,原因是NER为序列标注任务,需要确定边界,分词后就可能产生错误的分词结果影响效果(B-x,I-x这种连续性,分词后会影响元意思表达)。

    def preprocess(self, mode):
            """
            params:
                words:将json文件每一行中的文本分离出来,存储为words列表
                labels:标记文本对应的标签,存储为labels
            examples:
                words示例:['生', '生', '不', '息', 'C', 'S', 'O', 'L']
                labels示例:['O', 'O', 'O', 'O', 'B-game', 'I-game', 'I-game', 'I-game']
            """
    np.savez_compressed(output_dir, words=word_list, labels=label_list)
    

    保存的文件也还是一句是一句的,所以后续处理中只有CLS,不需要终止符。

    数据集分集与分batch
    def dev_split(dataset_dir):
        """split dev set"""
        data = np.load(dataset_dir, allow_pickle=True)#加载npz文件
        words = data["words"]
        labels = data["labels"]
        x_train, x_dev, y_train, y_dev = train_test_split(words, labels, test_size=config.dev_split_size, random_state=0)
        return x_train, x_dev, y_train, y_dev
    

    调用train_test_split实现分train和dev的数据集。

    将数据转化形式,用idx表示,构造NERDataset类表示使用数据集
        def __init__(self, words, labels, config, word_pad_idx=0, label_pad_idx=-1):
            self.tokenizer = BertTokenizer.from_pretrained(config.bert_model, do_lower_case=True)#调用预训练模型
            self.label2id = config.label2id#字典                                                 
            self.id2label = {_id: _label for _label, _id in list(config.label2id.items())}##字典
            self.dataset = self.preprocess(words, labels)#数据集预处理
            self.word_pad_idx = word_pad_idx
            self.label_pad_idx = label_pad_idx
            self.device = config.device
    
        def preprocess(self, origin_sentences, origin_labels):
            """
            Maps tokens and tags to their indices and stores them in the dict data.
            examples: 
                word:['[CLS]', '浙', '商', '银', '行', '企', '业', '信', '贷', '部']
                sentence:([101, 3851, 1555, 7213, 6121, 821, 689, 928, 6587, 6956],
                            array([ 1,  2,  3,  4,  5,  6,  7,  8,  9]))
                label:[3, 13, 13, 13, 0, 0, 0, 0, 0]
            """
            data = []
            sentences = []
            labels = []
            # eg. i am cutting tokenize: cutting->[cut,'##ing']自动修改形式变成单数或者恢复原型
            for line in origin_sentences:
                # replace each token by its index
                # we can not use encode_plus because our sentences are aligned to labels in list type
                words = []
                word_lens = []
                for token in line:
                    words.append(self.tokenizer.tokenize(token))
                    word_lens.append(len(token))#如果含有英文会出现上面的情况,中文没有分词一般是1
                    #>> [1]*9
                # 变成单个字的列表,开头加上[CLS]
                words = ['[CLS]'] + [item for token in words for item in token]
                token_start_idxs = 1 + np.cumsum([0] + word_lens[:-1])# np.array:[1,2,3]  自动广播机制 每个+1  a[1,2,3] a[:-1]->[1,2] 求出每个词在没加【cls】的句首字母idx
                # 这里计数tokens在words中的索引,第一个起始位置+1(加了cls)了,所以每一个+1
                sentences.append((self.tokenizer.convert_tokens_to_ids(words), token_start_idxs))
                #单词转化成idx,直接调用函数即可
            for tag in origin_labels:
                label_id = [self.label2id.get(t) for t in tag] #单个句子的tag idx
                labels.append(label_id)
            for sentence, label in zip(sentences, labels):
                data.append((sentence, label))#句子编码、token在words中的位置、对应的label(一个token可能占用多个word(cutting->cut+ing)
            return data
    
    

    preprocess处理token和word,记录每个token在word中的起始位置用于后续的对齐,对于每个单词进行tokennize(中文无变化,英文可能会有,但数据处理过程中将单词分成字母,所以无影响),然后在句首加上开始字符,因为生成第一个单词也需要概率因此句首不能省略,然后就是将字符转化成idx存储,tag也转化成idx;

    类中的功能函数
    def __getitem__(self, idx):#class使用索引
        """sample data to get batch"""
        word = self.dataset[idx][0]
        label = self.dataset[idx][1]
        return [word, label]
    def __len__(self):#class 使用长度
        """get dataset size"""
        return len(self.dataset)
    

    可以索引访问与访问长度。

    encode_plus可以直接编码,但这里不能使用:align限制

    因为单词要和标签对应,直接tokennize后编码,不能确定与标签的对应关系;

    tokennize()

    对于英文一个token通过tokennize会得到多个word:cutting->cut+##ing;

    np.cumsum(a)累计计数
    [1,1,1]--->[1,2,3]
    

    模型架构

    首先要明确,是继承bert基类,然后自定义forward函数就建好网络了,基本结构试:

    class Module(nn.Module):
        def __init__(self):
            super(Module, self).__init__()
            # ......
           
        def forward(self, x):
            # ......
            return x
    data = .....  #输入数据
    # 实例化一个对象
    module = Module()
    # 前向传播
    module(data)  
    # 而不是使用下面的
    # module.forward(data)  
    
    关于forward的解释

    nn.module中实现时就在call函数中定义了调用forward,然后传参就自动调用了。

    定义__call__方法的类可以当作函数调用,具体参考Python的面向对象编程。也就是说,当把定义的网络模型model当作函数调用的时候就自动调用定义的网络模型的forward方法。nn.Module 的__call__方法部分源码如下所示:

    def __call__(self, *input, **kwargs):
    result = self.forward(*input, **kwargs)
    
    BERT模式:选择对应,在代码的不同部分都有切换(model.eval();model.train())
    • train
    • eval
    • predict
    nonezero()函数
    a = mat([[1,1,0],[1,1,0],[1,0,3]])
    print(a.nonzero())
    #>>(array([0, 0, 1, 1, 2, 2], dtype=int64), array([0, 1, 0, 1, 0, 2], dtype=int64))
    
    squeeze()函数介绍

    去掉为1的维度,如[[0,1,2],[1,2,3]]dim(1,2,3)-->squeeze(1)--->[[0,1,2].[1,2,3]]

    CRF层训练

    训练目标:lstm输出分数+转移分数+前面序列的累计转移分数也就是 emission Score和transition Score(ref),函数使用,初始设置只需要标签数目,后续forward需要batch;如果想要知道结果需要使用decode函数

    >>> import torch
    >>> from torchcrf import CRF
    >>> num_tags = 5  # number of tags is 5
    >>> model = CRF(num_tags)
    emissions = torch.randn(seq_length, batch_size, num_tags) #初始输入
    >>> model(emissions, tags, mask=mask)
    tensor(-10.8390, grad_fn=<SumBackward0>)#得到这个句子的概率
    #没有tag预测
    >>> model.decode(emissions)
    [[3, 1, 3], [0, 1, 0]]
    

    引用这个图:

    img

    模型构造:

    class BertNER(BertPreTrainedModel):
        def __init__(self, config):
            super(BertNER, self).__init__(config)
            self.num_labels = config.num_labels
    
            self.bert = BertModel(config)#第一层
            self.dropout = nn.Dropout(config.hidden_dropout_prob)#非线性层
            self.bilstm = nn.LSTM(#LSTM层
                input_size=config.lstm_embedding_size,  # 1024
                hidden_size=config.hidden_size // 2,  # 1024 因为是双向LSTM,隐藏层大小为原来的一半
                batch_first=True,
                num_layers=2,
                dropout=config.lstm_dropout_prob,  # 0.5 非线性
                bidirectional=True
            )
            self.classifier = nn.Linear(config.hidden_size, config.num_labels) #得到每个词对于所有tag的分数
            self.crf = CRF(config.num_labels, batch_first=True)#CEF层
    
            self.init_weights()#初始化权重,先全部随机初始化,然后调用bert的预训练模型中的权重覆盖
    
    

    直接使用pytorch已经实现的函数,设置好bert层,后面通过droupout非线性层随机失活,然后使加上双向LSTM,注意双向的隐藏层是将两个方向的直接拼接,因此每个的长度设置为总的隐藏层输出长度的一半;然后接线性层,得到的是对于这些tag的每一个的分数,对于每一个位置,都给出是n钟tag的分数,这些分数作为crf层得到输入;然后进入crf层;

    初始化权重:对于预训练模型,已经有的参数直接加载,没有的参数将随机初始化。

    设置前向传播训练,:

    def forward(self, input_data, token_type_ids=None, attention_mask=None, labels=None,
                position_ids=None, inputs_embeds=None, head_mask=None):
        input_ids, input_token_starts = input_data
        outputs = self.bert(input_ids,
                            attention_mask=attention_mask,
                            token_type_ids=token_type_ids,
                            position_ids=position_ids,
                            head_mask=head_mask,
                            inputs_embeds=inputs_embeds)
        sequence_output = outputs[0]
        # 去除[CLS]标签等位置,获得与label对齐的pre_label表示
        origin_sequence_output = [layer[starts.nonzero().squeeze(1)]
                                  for layer, starts in zip(sequence_output, input_token_starts)]
        # 将sequence_output的pred_label维度padding到最大长度
        padded_sequence_output = pad_sequence(origin_sequence_output, batch_first=True)
        # dropout pred_label的一部分feature
        padded_sequence_output = self.dropout(padded_sequence_output)
        lstm_output, _ = self.bilstm(padded_sequence_output)
        # 得到判别值
        logits = self.classifier(lstm_output)
        outputs = (logits,)
        if labels is not None:#如果标签存在就计算loss,否则就是输出线性层对应的结果,这样便于通过后续crf的decode函数解码得到预测结果。
            loss_mask = labels.gt(-1)
            loss = self.crf(logits, labels, loss_mask) * (-1)
            outputs = (loss,) + outputs
    
            # contain: (loss), scores
            return outputs
    

    如果标签存在就计算loss,否则就是输出线性层对应的结果,这样便于通过后续crf的decode函数解码得到预测结果。在train.py/evaluate()里面用到了:

                batch_output = model((batch_data, batch_token_starts),
                                     token_type_ids=None, attention_mask=batch_masks)[0]
                #没有标签只会得到线性层的输出
                # (batch_size, max_len - padding_label_len)
                batch_output = model.crf.decode(batch_output, mask=label_masks)#得到预测的标签
    
    

    各个层的作用为:

    bert

    提供词的嵌入表示,通过大规模训练,得到的结果泛化性更强,因此使用预训练模型,然参数有个比较好的初始化值。

    lstm

    从这里开始是正式的模型内容,这里是双向lstm,能够学习句子的上下文内容,从而给出每个字的标注。

    crf

    由于原始句法约束,lstm没有学习到原始的句法约束,因此使用条件随机场crf层来限制句法要求,从而加强结果。loss为发射分数和转移分数统一的分数,越小越好

    验证

    使用f1 score,兼顾了分类模型的精确率和召回率,最大为1,最小为0,越大越好。

    模型训练

    训练时采用patience_counter策略,如果连续patience_counter次f1值没有提升,而且已经达到了最小训练次数,训练停止,代码实现为:

    def train(train_loader, dev_loader, model, optimizer, scheduler, model_dir):
        """train the model and test model performance"""
        # reload weights from restore_dir if specified
        if model_dir is not None and config.load_before:
            model = BertNER.from_pretrained(model_dir)
            model.to(config.device)
            logging.info("--------Load model from {}--------".format(model_dir))
        best_val_f1 = 0.0#最小值
        patience_counter = 0#超过这个次数 f1值连续没有提升而且已经过了最小训练次数就终止
        # start training
        for epoch in range(1, config.epoch_num + 1):
            train_epoch(train_loader, model, optimizer, scheduler, epoch)
            val_metrics = evaluate(dev_loader, model, mode='dev')#验证
            val_f1 = val_metrics['f1']#得到f1值
            logging.info("Epoch: {}, dev loss: {}, f1 score: {}".format(epoch, val_metrics['loss'], val_f1))
            improve_f1 = val_f1 - best_val_f1#控制精度连续提升
            if improve_f1 > 1e-5:
                best_val_f1 = val_f1
                model.save_pretrained(model_dir)
                logging.info("--------Save best model!--------")
                if improve_f1 < config.patience:
                    patience_counter += 1
                else:
                    patience_counter = 0
            else:
                patience_counter += 1
            # Early stopping and logging best f1
            if (patience_counter >= config.patience_num and epoch > config.min_epoch_num) or epoch == config.epoch_num:
                logging.info("Best val f1: {}".format(best_val_f1))
                break
        logging.info("Training Finished!")
    
    
    参数更新,学习率衰减

    采用学习率分离,adamW优化采纳数,动态调整学习率的策略。

    设置控制系数不衰减的项,然后optimizer_grouped_parameters要将全部的参数都写进去,注意写法的不同:crf层的参数学习率更高,而且写法不同是直接的parameters,见下文写法:

        if config.full_fine_tuning: 
            # model.named_parameters(): [bert, bilstm, classifier, crf]
            # 模型是哪个层中的参数
            bert_optimizer = list(model.bert.named_parameters())
            lstm_optimizer = list(model.bilstm.named_parameters())
            classifier_optimizer = list(model.classifier.named_parameters())
            no_decay = ['bias', 'LayerNorm.bias', 'LayerNorm.weight'] #控制系数不衰减的项
            optimizer_grouped_parameters = [#其他参数在优化过程权重衰减
                {'params': [p for n, p in bert_optimizer if not any(nd in n for nd in no_decay)], #bert中衰减项参数
                 'weight_decay': config.weight_decay},
                {'params': [p for n, p in bert_optimizer if any(nd in n for nd in no_decay)],
                 'weight_decay': 0.0},#不衰减的也要写出来
                {'params': [p for n, p in lstm_optimizer if not any(nd in n for nd in no_decay)],#lstm层的系数
                 'lr': config.learning_rate * 5, 'weight_decay': config.weight_decay},
                {'params': [p for n, p in lstm_optimizer if any(nd in n for nd in no_decay)],
                 'lr': config.learning_rate * 5, 'weight_decay': 0.0},
                {'params': [p for n, p in classifier_optimizer if not any(nd in n for nd in no_decay)],#线性层参数
                 'lr': config.learning_rate * 5, 'weight_decay': config.weight_decay},
                {'params': [p for n, p in classifier_optimizer if any(nd in n for nd in no_decay)],
                 'lr': config.learning_rate * 5, 'weight_decay': 0.0},
                {'params': model.crf.parameters(), 'lr': config.learning_rate * 5}#crf层的参数学习率更高,而且写法不同是直接的parameters
            ]
        # only fine-tune the head classifier 如果不微调也就是bert层全部使用原本的权重,不会根据数据集微调
        # 问题:预训练模型的参数只包含bert的?那么这里的lstm层为什么不训练;预训练模型,对照表,给定单词(有一个初始顺序)给出编码然后进入后续模型
        else:
            param_optimizer = list(model.classifier.named_parameters())
            optimizer_grouped_parameters = [{'params': [p for n, p in param_optimizer]}]
        optimizer = AdamW(optimizer_grouped_parameters, lr=config.learning_rate, correct_bias=False)
        train_steps_per_epoch = train_size // config.batch_size
        scheduler = get_cosine_schedule_with_warmup(optimizer,
                                                    num_warmup_steps=(config.epoch_num // 10) * train_steps_per_epoch,
                                                    num_training_steps=config.epoch_num * train_steps_per_epoch)
    
        # Train the model
        logging.info("--------Start Training!--------")
        train(train_loader, dev_loader, model, optimizer, scheduler, config.model_dir)
    

    源代码这里不微调逻辑存有问题,原github已提交issue,暂时没有回应(没用到)

    结果分析

    f1score最终为0.79;

    在书籍、公司、游戏、政府、人名上f1 score都大于0.8,效果较好;

    原数据:

    模型BiLSTM+CRFRoberta+SoftmaxRoberta+CRFRoberta+BiLSTM+CRF
    address47.3757.5064.1163.15
    book65.7175.3280.9481.45
    company71.0676.7180.1080.62
    game76.2882.9083.7485.57
    government71.2979.0283.1481.31
    movie67.5383.2383.1185.61
    name71.4988.1287.4488.22
    organization73.2974.3080.3280.53
    position72.3377.3978.9578.82
    scene51.1662.5671.3672.86
    overall67.4775.9079.3479.64

    这里使用的是bert预训练模型,可以看到从预训练模型上说,和roberta在各个数据上稍微差一些,但最后的差值和原本实验结果相近。

    实验test时的bad—case分析

    image-20220326173244442

    枪手这里系统错判为组织;

    image-20220326173318645

    教委错判为政府;

    image-20220326173338909

    彩票监管部门认为是政府,实际是组织;

    image-20220326173402877

    中材中心认为是公司,实际是组织;

    image-20220326173436677枪手错判;

    image-20220326173505293一些景点和地名分不清;

    image-20220326173654093以及这种

    可以看出由于有了条件随机场的限制,没有明显的B-peron后面跟I-name这种错误,出现的错误大都是内容上的,即使是人也不一定分清,可见这个模型的强大。

    参考

    是对文章里面不涉及的部分的进一步解析,适合小白开箱使用。

    源码为:传送门

    展开全文
  • 用BILSTM+CRF做医疗实体识别,框架为pytorch。 注意,代码已经修改了!! 由于pytorch-crf这个包在计算loss时会自动加上和的转移矩阵,所以我们不用再自己手动在样本和标签前后加这俩标记。 然后评估方法改为了...
  • 这份代码是基于深度神经网络的英文命名实体识别,主要算法是LSTM+CRF
  • 最近在看命名实体识别相关的模型,实验室正好有中医典籍文本的命名实体标注数据集,拿来练练构建一个简单的CRF模型,顺便记录下来,代码可以作为一个参考,手中有标注数据集就可以使用这段代码来训练自己的CRF模型。...

    写在前面

    最近在看命名实体识别相关的模型,实验室正好有中医典籍文本的命名实体标注数据集,拿来练练构建一个简单的CRF模型,顺便记录下来,代码可以作为一个参考,手中有标注数据集就可以使用这段代码来训练自己的CRF模型。本次实验用到了sklearn_crfsuite库,这是一个轻量级的CRF库,不仅提供了训练预测方法,还提供了评估方法。数据集的格式大致如下图所示:

    每行包含一个字和对应的标注,用空行来分隔开每句话。采用了四个符号(B、I、O、S),分别表示实体的起始字、实体的剩余部分、非实体、单字实体。但语料文本中还有一个E符号,表示实体的结束,感觉意义不大,为了降低复杂度,就将所有的E符号转换为“I”。


    数据预处理

    这部分要为后面的模型来准备数据,包括特征和标注序列。需要实现以下函数:

    (1)读取所有语料数据并返回

        def __init__(self):
            self.file_path="中医语料.txt"
    
        def read_file(self):
            f=open(self.file_path,encoding="utf-8")
            line=f.readlines()
            f.close()
            return line

    (2)将每句话放到一个list中,形成一个二维列表。

        def pre_process(self):
            lines=self.read_file()
            new_lines=[]
            list = []
            for line in lines:
                line=line.strip()
                if len(line) == 0:
                    new_lines.append(list)
                    list=[]
                else:
                    list.append(line)
            return new_lines

    (3)将每句话的字序列和标记序列分别存储到两个二维序列中,这里的word_list就是上面pre_process函数返回的二维列表。并且在每句话的前后加上“BOS”和“EOS”标记。

        def init(self,word_list):
            self.word_seq=[[u'<BOS>']+[word.split(" ")[0] for word in words]+[u'<EOS>'] for words in word_list]
            self.tag_seq=[[word.split(" ")[1].replace("E","I") for word in words] for words in word_list]

    在此次实验中,特征用的是简单的N-gram模型,所以要实现一个滑动窗口和特征抽取函数。

    (4)实现滑动窗口函数,每三个字形成一个片段gram。这里的word_list是上面init函数生成的字序列。

        def segment_by_window(self,word_list):
            words=[]
            begin,end=0,3
            for _ in range(1,len(word_list)):
                if end >len(word_list):
                    break
                words.append(word_list[begin:end])
                begin+=1
                end+=1
            return words

    (5)特征抽取函数,使用每句话的字序列生成的gram,利用tri-gram模型抽取特征。

        def extract_features(self,word_grams):
            features,features_list=[],[]
            for index in range(len(word_grams)):
                for i in range(len(word_grams[index])):
                    word_gram=word_grams[index][i]
                    feature={u'w-1':word_gram[0],u'w':word_gram[1],u'w+1':word_gram[2],
                              u'w-1:w':word_gram[0]+word_gram[1],u'w:w+1':word_gram[1]+word_gram[2],
                              u'bias':1.0}
                    features_list.append(feature)
                features.append(features_list)
                features_list=[]
            return features

    (6)组合CRF模型的输入数据。这个函数将滑动窗口函数和特征抽取函数组合起来,并形成最后输入到CRF模型中的数据。

        def generator(self):
            # word_gram此时是三维的,句子->片段->字
            word_grams = [self.segment_by_window(word_list) for word_list in self.word_seq]
            features = self.extract_feature(word_grams)
            return features, self.tag_seq

    模型构建

    设置CRF模型的初始化参数。algorithm表示优化算法;c1表示L1正则化系数;c2表示L2正则化系数;max_iteration表示最大迭代次数;model_path表示模型的保存路径;然后初始化语料。

        def __init__(self):
            self.algorithm="lbfgs"
            self.c1=0.1
            self.c2=0.2
            self.max_iterations=100
            self.model_path="TCM_model.pkl"
            self.corpus=init_process()
            self.corpus_text=self.corpus.pre_process()
            self.corpus.init(self.corpus_text)
            self.model=None
    
        def init_model(self):
            algorithm=self.algorithm
            c1=float(self.c1)
            c2=float(self.c2)
            max_iterations=self.max_iterations
            self.model=sklearn_crfsuite.CRF(algorithm=algorithm,c1=c1,c2=c2,max_iterations=max_iterations,all_possible_transitions=True)

    模型训练

    初始化模型及语料后,划分数据集和测试集,生成输入数据并对模型进行训练,使用metrics模块来评估模型效果。

        def train(self):
            self.init_model()
            x,y=self.corpus.generator()
            x_train,y_train=x[1000:],y[1000:]
            x_test,y_test=x[:1000],y[:1000]
            self.model.fit(x_train,y_train)
            labels=list(self.model.classes_)
            labels.remove("O")
            y_predict=self.model.predict(x_test)
            metrics.flat_f1_score(y_test,y_predict,average="weighted",labels=labels)
            sorted_labels=sorted(labels,key=lambda name:(name[1:],name[0]))
            print(metrics.flat_classification_report(y_test,y_predict,labels=sorted_labels,digits=3))
            self.save_model()

    模型预测

    训练好模型后,就可以使用模型来进行预测了,但预测函数输出的结果并不直观,还需要做一些处理。

        def predict(self,sentence):
            self.load_model()
            word_lists=[[u'BOS']+[word for word in sentence]+[u'EOS']]
            word_gram=[self.corpus.segment_by_window(word_list) for word_list in word_lists]
            print(word_lists)
            features=self.corpus.extract_features(word_gram)
            y_predict=self.model.predict(features)
            print(y_predict)
            entity=""
            for index in range(len(y_predict[0])):
                if y_predict[0][index] != u'O':
                    entity += sentence[index]
                    if index<len(y_predict[0])-1 and y_predict[0][index][-2:] != y_predict[0][index+1][-2:]:
                        entity+=" "
            return entity

    结果分析

    模型训练的结果如下图所示,对于大部分标签,其精确率和召回率都算不错,support表示标签出现的次数。

    使用模型来对这样一个新句子进行命名实体识别: “服药五日,渐变神昏谵语,胸腹满痛,舌干不饮水,小便清长。”命名实体识别的结果如下:


    程序源代码 

    import sklearn_crfsuite
    from sklearn_crfsuite import metrics
    import joblib
    
    class init_process(object):
    
        def __init__(self):
            self.file_path="中医语料.txt"
    
        def read_file(self):
            f=open(self.file_path,encoding="utf-8")
            line=f.readlines()
            f.close()
            return line
    
        def pre_process(self):
            lines=self.read_file()
            new_lines=[]
            list = []
            for line in lines:
                line=line.strip()
                if len(line) == 0:
                    new_lines.append(list)
                    list=[]
                else:
                    list.append(line)
            return new_lines
    
        def extract_features(self,word_grams):
            features,features_list=[],[]
            for index in range(len(word_grams)):
                for i in range(len(word_grams[index])):
                    word_gram=word_grams[index][i]
                    feature={u'w-1':word_gram[0],u'w':word_gram[1],u'w+1':word_gram[2],
                              u'w-1:w':word_gram[0]+word_gram[1],u'w:w+1':word_gram[1]+word_gram[2],
                              u'bias':1.0}
                    features_list.append(feature)
                features.append(features_list)
                features_list=[]
            return features
    
        def segment_by_window(self,word_list):
            words=[]
            begin,end=0,3
            for _ in range(1,len(word_list)):
                if end >len(word_list):
                    break
                words.append(word_list[begin:end])
                begin+=1
                end+=1
            return words
    
        def init(self,word_list):
            self.word_seq=[[u'<BOS>']+[word.split(" ")[0] for word in words]+[u'<EOS>'] for words in word_list]
            self.tag_seq=[[word.split(" ")[1].replace("E","I") for word in words] for words in word_list]
    
        def generator(self):
            word_grams=[self.segment_by_window(word) for word in self.word_seq]
            features=self.extract_features(word_grams)
            return features,self.tag_seq
    
    class ner(object):
    
        def __init__(self):
            self.algorithm="lbfgs"
            self.c1=0.1
            self.c2=0.2
            self.max_iterations=100
            self.model_path="TCM_model.pkl"
            self.corpus=init_process()
            self.corpus_text=self.corpus.pre_process()
            self.corpus.init(self.corpus_text)
            self.model=None
    
        def init_model(self):
            algorithm=self.algorithm
            c1=float(self.c1)
            c2=float(self.c2)
            max_iterations=self.max_iterations
            self.model=sklearn_crfsuite.CRF(algorithm=algorithm,c1=c1,c2=c2,max_iterations=max_iterations,all_possible_transitions=True)
    
        def train(self):
            self.init_model()
            x,y=self.corpus.generator()
            x_train,y_train=x[1000:],y[1000:]
            x_test,y_test=x[:1000],y[:1000]
            self.model.fit(x_train,y_train)
            labels=list(self.model.classes_)
            labels.remove("O")
            y_predict=self.model.predict(x_test)
            metrics.flat_f1_score(y_test,y_predict,average="weighted",labels=labels)
            sorted_labels=sorted(labels,key=lambda name:(name[1:],name[0]))
            print(metrics.flat_classification_report(y_test,y_predict,labels=sorted_labels,digits=3))
            self.save_model()
    
        def predict(self,sentence):
            self.load_model()
            word_lists=[[u'BOS']+[word for word in sentence]+[u'EOS']]
            word_gram=[self.corpus.segment_by_window(word_list) for word_list in word_lists]
            print(word_lists)
            features=self.corpus.extract_features(word_gram)
            y_predict=self.model.predict(features)
            print(y_predict)
            entity=""
            for index in range(len(y_predict[0])):
                if y_predict[0][index] != u'O':
                    entity += sentence[index]
                    if index<len(y_predict[0])-1 and y_predict[0][index][-2:] != y_predict[0][index+1][-2:]:
                        entity+=" "
            return entity
    
        def save_model(self):
            joblib.dump(self.model,self.model_path)
    
        def load_model(self):
            self.model=joblib.load(self.model_path)
    
    NER=ner()
    NER.train()
    print(NER.predict("服药五日,渐变神昏谵语,胸腹满痛,舌干不饮水,小便清长。"))

     

    展开全文
  • # 中文命名实体识别 基于条件随机场(Conditional Random Field, CRF)的NER模型 ## 数据集 数据集用的是论文ACL 2018[Chinese NER using Lattice LSTM](https://github.com/jiesutd/LatticeLSTM)中收集的简历数据...
  • 命名实体识别(NER)实战demo-LSTM+CRF

    千次阅读 2021-11-28 18:53:41
    命名实体识别(NER)实战demo:简介、项目资源、项目文件、代码运行过程的问题及解决方法
  • 前文介绍了【NLP】命名实体识别——IDCNN-CRF论文阅读与总结,【NLP】基于Pytorch lightning与BiLSTM-CRF的NER实现 也实现了相关模型。在GitHub看了一圈,IDCNN基本上都是Tensorflow实现了,现在我来实现一波,看看...
  • 这是一个中文细粒度命名实体识别数据集,是基于清华大学开源的文本分类数据集THUCNEWS,选出部分数据进行细粒度标注得到的。该数据集的训练集、验证集和测试集的大小分别为10748,1343,1345,平均句子长度37.4字,...
  • 前阵子用crf++工具做了命名实体识别,这次用keras中的keras_contrib来试下,结合lstm。 一、操作系统 window10 二、版本 Python 3.6.5、TensorFlow 2.1.0、Keras 2.3.1 三、原始数据处理过程 同之前的处理,...
  • BERT-BiLSTM-CRF命名实体识别应用

    千次阅读 2020-05-18 14:06:16
    本文将采用BERT+BiLSTM+CRF模型进行命名实体识别(Named Entity Recognition 简称NER),即实体识别。命名实体识别,是指识别文本中具有特定意义的实体,主要包括人名、地名、机构名、专有名词等。 BERT...
  • 每天给你送来NLP技术干货!来自:ChallengeHub作者:致Great完整代码 https://www.heywhale.com/home/competition/6216f7457...
  • 首先利用SVM筛选出包含关键命名实体的句子,然后将正确包含此类实体的句子转化为字符级向量作为输入,构建适合财产纠纷裁判文书命名实体识别的BiLSTM-CRF深层神经网络模型.通过构建训练数据进行验证和对比,该模型比...
  • 欢迎大家来到我们的项目实战课,本期内容是《基于BiLSTM+CRF命名实体识别实战》。所谓项目课,就是以简单的原理回顾+详细的项目实战的模式,针对具体的某一个主题,进行代码级的实战讲解。本次主题命名实体识别...
  • Bi-LSTM-CRF命名实体识别实战

    千次阅读 2020-04-07 22:36:59
    本项目的数据集来自于天池——基于糖尿病临床指南和研究论文的实体标注构建(瑞金医院MMC人工智能辅助构建知识图谱大赛第一赛季)。即提供与糖尿病相关的学术论文以及糖尿病临床指南,要求在学术论文和临床指南的...
  • 数据来源获取数据标注效果模型搭建代码部分 数据来源 百度百科:一战的主要战争人物介绍。 获取 import requests import re import pandas as pd from lxml import html from lxml import etree url = '...
  • 命名实体识别(1)——CRF

    千次阅读 2019-06-09 22:38:44
    本文主要介绍一些命名实体识别(Name Entity Recognition,NER)相关的基本概念、发展历程和最新的研究进展,并着重介绍基于CRF命名实体识别原理及实现。后续也会以BiLSTM-CRF、BERT-BiLSTM-CRF为例进行展开。 1....
  • 基于BERT的中文数据集下的命名实体识别(NER) 基于tensorflow官方代码修改。 环境 Tensorflow:1.13 的Python:3.6 tensorflow2.0会报错。 搜狐比赛 在搜狐这个文本比赛中写了一个基准,使用了bert以及bert + ...
  • 人工智能-项目实践-实体识别-基于tensorflow深度学习的中文的命名实体识别 一个中文的实体命名识别系统 当前版本基于双向循环神经网络(BiRNN) + 条件随机场(CRF)来完成实体的标注。 基本思路是利用深度神经...
  • 基于BiLSTM-CRF命名实体识别

    千次阅读 2021-06-08 11:00:27
    基于BiLSTM-CRF的命名实体识别1. 任务说明1.1 任务定义1.2 语料...  基于train.txt和train_TAG.txt数据训练一个BiLSTM-CRF命名实体识别模型,进而为test.txt进行序列标注,输出标签文件,标签文件输出格式与train_TA
  • 本篇解读了基于BERT+CRF做中文NER这篇文章中的代码,在该篇作者的GitHub上可以下载源码:源代码。这段代码对BERT模型的实现较为简洁,删掉了谷歌源代码中我们可能用不到的部分,保留了核心部分。对于那些想要快速...
  • 说到命名实体抽取,先要了解一下基于字标注的中文分词。 比如一句话 “我爱北京天安门”。 分词的结果可以是 “我/爱/北京/天安门”。 那什么是基于字标注呢? “我/O 爱/O 北/B 京/E 天/B 安/M 门/E...
  • 之前写过使用Bi-LSTM-CRF模型进行NER任务,但在实际工程中当要考虑速度时还会考虑这个模型——IDCNN-CRF模型。因为我们知道,LSTM模型序列化模型,即后面的输入依赖于前面的结果,无法做到并行化,无论是模型训练...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,347
精华内容 938
热门标签
关键字:

crf命名实体识别代码

友情链接: 3985122.rar