【中文分词系列】 4. 基于双向LSTM的seq2seq字标注
By 苏剑林 | 2016-08-22 | 466734位读者 |关于字标注法 #
上一篇文章谈到了分词的字标注法。要注意字标注法是很有潜力的,要不然它也不会在公开测试中取得最优的成绩了。在我看来,字标注法有效有两个主要的原因,第一个原因是它将分词问题变成了一个序列标注问题,而且这个标注是对齐的,也就是输入的字跟输出的标签是一一对应的,这在序列标注中是一个比较成熟的问题;第二个原因是这个标注法实际上已经是一个总结语义规律的过程,以4tag标注为为例,我们知道,“李”字是常用的姓氏,一半作为多字词(人名)的首字,即标记为b;而“想”由于“理想”之类的词语,也有比较高的比例标记为e,这样一来,要是“李想”两字放在一起时,即便原来词表没有“李想”一词,我们也能正确输出be,也就是识别出“李想”为一个词,也正是因为这个原因,即便是常被视为最不精确的HMM模型也能起到不错的效果。
关于标注,还有一个值得讨论的内容,就是标注的数目。常用的是4tag,事实上还有6tag和2tag,而标记分词结果最简单的方法应该是2tag,即标记“切分/不切分”就够了,但效果不好。为什么反而更多数目的tag效果更好呢?因为更多的tag实际上更全面概括了语义规律。比如,用4tag标注,我们能总结出哪些字单字成词、哪些字经常用作开头、哪些字用作末尾,但仅仅用2tag,就只能总结出哪些字经常用作开头,从归纳的角度来看,是不够全面的。但6tag跟4tag比较呢?我觉得不一定更好,6tag的意思是还要总结出哪些字作第二字、第三字,但这个总结角度是不是对的?我觉得,似乎并没有哪些字固定用于第二字或者第三字的,这个规律的总结性比首字和末字的规律弱多了(不过从新词发现的角度来看,6tag更容易发现长词。)。
双向LSTM #
关于双向LSTM,理解的思路是:双向LSTM是LSTM的改进版,LSTM是RNN的改进版。因此,首先需要理解RNN。
笔者曾在拙作《从Boosting学习到神经网络:看山是山?》说到过,模型的输出结果,事实上也是一种特征,也可以作为模型的输入来用,RNN正是这样的网络结构。普通的多层神经网络,是一个输入到输出的单向传播过程。如果涉及到高维输入,也可以这样做,但节点太多,不容易训练,也容易过拟合。比如图像输入是1000x1000的,难以直接处理,这就有了CNN;又或者1000词的句子,每个词用100维的词向量,那么输入维度也不小,这时候,解决这个问题的一个方案是RNN(CNN也可以用,但RNN更适合用于序列问题。)。
RNN的意思是,为了预测最后的结果,我先用第一个词预测,当然,只用第一个预测的预测结果肯定不精确,我把这个结果作为特征,跟第二词一起,来预测结果;接着,我用这个新的预测结果结合第三词,来作新的预测;然后重复这个过程;直到最后一个词。这样,如果输入有n个词,那么我们事实上对结果作了n次预测,给出了n个预测序列。整个过程中,模型共享一组参数。因此,RNN降低了模型的参数数目,防止了过拟合,同时,它生来就是为处理序列问题而设计的,因此,特别适合处理序列问题。
LSTM对RNN做了改进,使得能够捕捉更长距离的信息。但是不管是LSTM还是RNN,都有一个问题,它是从左往右推进的,因此后面的词会比前面的词更重要,但是对于分词这个任务来说是不妥的,因为句子各个字应该是平权的。因此出现了双向LSTM,它从左到右做一次LSTM,然后从右到左做一次LSTM,然后把两次结果组合起来。
在分词任务中的应用 #
关于深度学习与分词,很早就有人尝试过了,比如下列文章:
http://blog.csdn.net/itplus/article/details/13616045
https://github.com/xccds/chinese_wordseg_keras
http://www.leiphone.com/news/201608/IWvc75oJglAIsDvJ.html
这些文章中,不管是用简单的神经网络还是LSTM,它们的做法都跟传统模型是一样的,都是通过上下文来预测当前字的标签,这里的上下文是固定窗口的,比如用前后5个字加上当前字来预测当前字的标签。这种做法没有什么不妥之处,但仅仅是把以往估计概率的方法,如HMM、ME、CRF等,换为了神经网络而已,整个框架是没变的,本质上还是n-gram模型。而有了LSTM,LSTM本身可以做序列到序列(seq2seq)的输出,因此,为什么不直接输出原始句子的序列呢?这样不就真正利用了全文信息了吗?这就是本文的尝试。
LSTM可以根据输入序列输出一个序列,这个序列考虑了上下文的联系,因此,可以给每个输出序列接一个softmax分类器,来预测每个标签的概率。基于这个序列到序列的思路,我们就可以直接预测句子的标签。
Keras实现 #
事不宜迟,动手最重要。词向量维度用了128,句子长度截断为32(抛弃了多于32字的样本,这部分样本很少,事实上,用逗号、句号等天然分隔符分开后,句子很少有多于32字的。)。这次我用了5tag,在原来的4tag的基础上,加上了一个x标签,用来表示不够32字的部分,比如句子是20字的,那么第21~32个标签均为x。
在数据方面,我用了Bakeoff 2005的语料中微软亚洲研究院(Microsoft Research)提供的部分。代码如下,如果有什么不清晰的地方,欢迎留言。
# -*- coding:utf-8 -*-
import re
import numpy as np
import pandas as pd
s = open('msr_train.txt').read().decode('gbk')
s = s.split('\r\n')
def clean(s): #整理一下数据,有些不规范的地方
if u'“/s' not in s:
return s.replace(u' ”/s', '')
elif u'”/s' not in s:
return s.replace(u'“/s ', '')
elif u'‘/s' not in s:
return s.replace(u' ’/s', '')
elif u'’/s' not in s:
return s.replace(u'‘/s ', '')
else:
return s
s = u''.join(map(clean, s))
s = re.split(u'[,。!?、]/[bems]', s)
data = [] #生成训练样本
label = []
def get_xy(s):
s = re.findall('(.)/(.)', s)
if s:
s = np.array(s)
return list(s[:,0]), list(s[:,1])
for i in s:
x = get_xy(i)
if x:
data.append(x[0])
label.append(x[1])
d = pd.DataFrame(index=range(len(data)))
d['data'] = data
d['label'] = label
d = d[d['data'].apply(len) <= maxlen]
d.index = range(len(d))
tag = pd.Series({'s':0, 'b':1, 'm':2, 'e':3, 'x':4})
chars = [] #统计所有字,跟每个字编号
for i in data:
chars.extend(i)
chars = pd.Series(chars).value_counts()
chars[:] = range(1, len(chars)+1)
#生成适合模型输入的格式
from keras.utils import np_utils
d['x'] = d['data'].apply(lambda x: np.array(list(chars[x])+[0]*(maxlen-len(x))))
def trans_one(x):
_ = map(lambda y: np_utils.to_categorical(y,5), tag[x].reshape((-1,1)))
_ = list(_)
_.extend([np.array([[0,0,0,0,1]])]*(maxlen-len(x)))
return np.array(_)
d['y'] = d['label'].apply(trans_one)
#设计模型
word_size = 128
maxlen = 32
from keras.layers import Dense, Embedding, LSTM, TimeDistributed, Input, Bidirectional
from keras.models import Model
sequence = Input(shape=(maxlen,), dtype='int32')
embedded = Embedding(len(chars)+1, word_size, input_length=maxlen, mask_zero=True)(sequence)
blstm = Bidirectional(LSTM(64, return_sequences=True), merge_mode='sum')(embedded)
output = TimeDistributed(Dense(5, activation='softmax'))(blstm)
model = Model(input=sequence, output=output)
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
batch_size = 1024
history = model.fit(np.array(list(d['x'])), np.array(list(d['y'])).reshape((-1,maxlen,5)), batch_size=batch_size, nb_epoch=50)
#转移概率,单纯用了等概率
zy = {'be':0.5,
'bm':0.5,
'eb':0.5,
'es':0.5,
'me':0.5,
'mm':0.5,
'sb':0.5,
'ss':0.5
}
zy = {i:np.log(zy[i]) for i in zy.keys()}
def viterbi(nodes):
paths = {'b':nodes[0]['b'], 's':nodes[0]['s']}
for l in range(1,len(nodes)):
paths_ = paths.copy()
paths = {}
for i in nodes[l].keys():
nows = {}
for j in paths_.keys():
if j[-1]+i in zy.keys():
nows[j+i]= paths_[j]+nodes[l][i]+zy[j[-1]+i]
k = np.argmax(nows.values())
paths[nows.keys()[k]] = nows.values()[k]
return paths.keys()[np.argmax(paths.values())]
def simple_cut(s):
if s:
r = model.predict(np.array([list(chars[list(s)].fillna(0).astype(int))+[0]*(maxlen-len(s))]), verbose=False)[0][:len(s)]
r = np.log(r)
nodes = [dict(zip(['s','b','m','e'], i[:4])) for i in r]
t = viterbi(nodes)
words = []
for i in range(len(s)):
if t[i] in ['s', 'b']:
words.append(s[i])
else:
words[-1] += s[i]
return words
else:
return []
not_cuts = re.compile(u'([\da-zA-Z ]+)|[。,、?!\.\?,!]')
def cut_word(s):
result = []
j = 0
for i in not_cuts.finditer(s):
result.extend(simple_cut(s[j:i.start()]))
result.append(s[i.start():i.end()])
j = i.end()
result.extend(simple_cut(s[j:]))
return result
我们可以用model.summary()看一下模型的结构。
>>> model.summary()
_______________________________________________________
Layer (type) Output Shape Param # Connected to
=======================================================
input_2 (InputLayer) (None, 32) 0
_______________________________________________________
embedding_2 (Embedding) (None, 32, 128) 660864 input_2[0][0]
_______________________________________________________
bidirectional_1 (Bidirectional) (None, 32, 64) 98816 embedding_2[0][0]
_______________________________________________________
timedistributed_2 (TimeDistribute) (None, 32, 5) 325 bidirectional_1[0][0]
=======================================================
Total params: 760005
_______________________________________________________
最终的模型结果如何?我不打算去对比那些评测结果了,现在的模型在测试上达到90%以上的准确率不是什么难事。我关心的是对新词的识别和对歧义的处理。下面是一些测试结果(随便选的):
RNN 的 意思 是 , 为了 预测 最后 的 结果 , 我 先 用 第一个 词 预测 , 当然 , 只 用 第一个 预测 的 预测 结果 肯定 不 精确 , 我 把 这个 结果 作为 特征 , 跟 第二词 一起 , 来 预测 结果 ; 接着 , 我 用 这个 新 的 预测 结果 结合 第三词 , 来 作 新 的 预测 ; 然后 重复 这个 过程 。
结婚 的 和 尚未 结婚 的
苏剑林 是 科学 空间 的 博主 。
广东省 云浮市 新兴县
魏则西 是 一 名 大学生
这 真是 不堪入目 的 环境
列夫·托尔斯泰 是 俄罗斯 一 位 著名 的 作家
保加利亚 首都 索非亚 是 全国 政治 、 经济 、 文化中心 , 位于 保加利亚 中 西部
罗斯福 是 第二次世界大战 期间 同 盟国 阵营 的 重要 领导人 之一 。 1941 年 珍珠港 事件发生 后 , 罗斯 福力 主对 日本 宣战 , 并 引进 了 价格 管制 和 配给 。 罗斯福 以 租 借 法案 使 美国 转变 为 “ 民主 国家 的 兵工厂 ” , 使 美国 成为 同 盟国 主要 的 军火 供应商 和 融资 者 , 也 使得 美国 国内 产业 大幅 扩张 , 实现 充分 就业 。 二战 后期 同 盟国 逐渐 扭转 形势 后 , 罗斯福 对 塑造 战后 世界 秩序 发挥 了 关键 作用 , 其 影响 力 在 雅尔塔 会议 及 联合国 的 成立 中 尤其 明显 。 后来 , 在 美国 协助 下 , 盟军 击败 德国 、 意大利 和 日本 。
可以发现,测试结果是很乐观的。不论是人名(中国人名或外国人名)还是地名,识别效果都很好。关于这个模型,目前就说到这里,以后会继续深入的。
最后 #
事实上本文是提供了一个框架,能够直接通过双向LSTM对序列进行标注,给出完整的标注序列。这种标注的思路,可以用于很多任务,如词性标注、实体识别,因此,基于双向LSTM的seq2seq标注思路,有很广的应用,值得研究。甚至最近热门的深度学习的机器翻译,都是用这种序列到序列的模型实现的。
转载到请包括本文地址:https://spaces.ac.cn/archives/3924
更详细的转载事宜请参考:《科学空间FAQ》
如果您还有什么疑惑或建议,欢迎在下方评论区继续讨论。
如果您觉得本文还不错,欢迎分享/打赏本文。打赏并非要从中获得收益,而是希望知道科学空间获得了多少读者的真心关注。当然,如果你无视它,也不会影响你的阅读。再次表示欢迎和感谢!
如果您需要引用本文,请参考:
苏剑林. (Aug. 22, 2016). 《【中文分词系列】 4. 基于双向LSTM的seq2seq字标注 》[Blog post]. Retrieved from https://spaces.ac.cn/archives/3924
@online{kexuefm-3924,
title={【中文分词系列】 4. 基于双向LSTM的seq2seq字标注},
author={苏剑林},
year={2016},
month={Aug},
url={\url{https://spaces.ac.cn/archives/3924}},
}
March 1st, 2018
你好,既然都是seq2seq了,为什么还加一维特比呢?
seq2seq和维特比没有矛盾之处呀。
这里的seq2seq是对齐的序列标注模型,维特比的作用是排除不合理的输出,如bbbb~
一般的seq2seq,如机器翻译模型,也是要可以语言模型辅助解码,从而提升效果的。seq2seq虽然是端到端,这主要是指训练过程。为了得到更合理的输出,还是有很多trick的
原来是这样啊,谢了啊。
还有就是pandas的apply真的好慢,求序列的时候,我是用 collection.Counter,map组合pad_sequences, to_categorical来实现的,套了两层map。
当文本数据很大的时候(如10TB)有没有其他的方式来存放他们呢?一定要用 list,pd.DataFrame, np.array这样的数据结构吗?
March 7th, 2018
您好,我用了'人们常说生活是一部教科书'做测试,结果发现输出就是['人', '们', '常', '说', '生', '活', '是', '一', '部', '教', '科', '书'],
我发现在维特比这层
`
ss -14.390171525957895
sb -21.71949910927088
bm -13.732722757342172
be -0.6984371072192611
sss -15.114626042460824
bes -1.42289162372219
ssb -18.56299638367238
beb -4.871261964933748
sbm -34.62351703262868
bmm -26.63674068069997
sbe -40.30714702225271
bme -32.320370670323996
ssss -15.895922420420385
sbes -41.08844340021227
sssb -20.008103957273697
sbeb -45.200624937065584
ssbm -22.694860691078638
sbmm -38.755381340034944
ssbe -22.545460456856226
sbme -38.60598110581253
sssss -23.38002562133555
ssbes -30.029563657771394
ssssb -16.5993689734671
ssbeb -23.24890700990294
sssbm -25.39920197782998
ssbmm -28.08595871163492
sssbe -32.360512841324855
ssbme -35.047269575129796
ssssss -34.986041301651355
sssbes -43.96652852164066
sssssb -46.52039265052831
sssbeb -55.500879870517615
ssssbm -28.793341082310736
sssbmm -37.59317408667361
ssssbe -17.292544526239954
sssbme -26.092377530602832
sssssss -35.6792364054953
ssssbes -17.9857396300839
ssssssb -52.32101892654724
ssssbeb -34.627522151135835
sssssbm -64.20384477798767
ssssbmm -46.47679320977009
sssssbe -57.159446470187376
ssssbme -39.4323949019698
ssssssss -36.39513799074909
sssssbes -57.875348055441165
sssssssb -40.176172963267774
sssssbeb -61.65638302795985
ssssssbm -61.47678588860133
sssssbmm -73.35961174004176
ssssssbe -69.89152740471461
sssssbme -81.77435325615504
sssssssss -37.133981482008025
ssssssbes -70.63037089597354
ssssssssb -46.01399775675826
ssssssbeb -79.51038717072377
sssssssbm -44.0706941977828
ssssssbmm -65.37130712311635
sssssssbe -46.43449806130819
ssssssbme -67.73511098664174
ssssssssss -38.19045270870292
sssssssbes -47.490969288003086
sssssssssb -39.02137030551994
sssssssbeb -48.3218868848201
ssssssssbm -54.83857270221078
sssssssbmm -52.895269143235325
ssssssssbe -53.270518026154875
sssssssbme -51.32721446717942
sssssssssss -40.838822257503224
ssssssssbes -55.91888757495518
ssssssssssb -39.852755737289144
ssssssssbeb -54.9328210547411
sssssssssbm -40.45259238479967
ssssssssbmm -56.26979478149052
sssssssssbe -46.59223402737017
ssssssssbme -62.40943642406101
ssssssssssss -45.66607273625043
sssssssssbes -51.41948450611737
sssssssssssb -55.77250135944989
sssssssssbeb -61.525913129316834
ssssssssssbm -51.37540216253903
sssssssssbmm -51.97523881004956
ssssssssssbe -40.5620704337379
sssssssssbme -41.16190708124843
`
出现了这样的数据,导致最终结果就是
{'ssssssssssbe': -40.5620704337379,
'ssssssssssbm': -51.37540216253903,
'sssssssssssb': -55.77250135944989,
'ssssssssssss': -45.66607273625043}
请问是不是我在训练的时候出的问题?
训练数据是您提供的数据
`
0 [人, 们, 常, 说, 生, 活, 是, 一, 部, 教, 科, 书] [b, e, s, s, b, e, s, s, s, b, m, e] [8, 45, 322, 90, 38, 200, 7, 2, 43, 165, 126, ... [[[0.0, 1.0, 0.0, 0.0, 0.0]], [[0.0, 0.0, 0.0,...
1 [而, 血, 与, 火, 的, 战, 争, 更, 是, 不, 可, 多, 得, 的, 教, ... [s, s, s, s, s, b, e, s, s, b, m, m, e, s, b, ... [109, 851, 83, 637, 1, 264, 408, 281, 7, 12, 1... [[[1.0, 0.0, 0.0, 0.0, 0.0]], [[1.0, 0.0, 0.0,...
2 [她, 确, 实, 是, 名, 副, 其, 实, 的, ‘, 我, 的, 大, 学, ’] [s, b, e, s, b, m, m, e, s, s, s, s, b, e, s] [398, 414, 56, 7, 169, 504, 142, 56, 1, 1493, ... [[[1.0, 0.0, 0.0, 0.0, 0.0]], [[0.0, 1.0, 0.0,...
3 [心, 静, 渐, 知, 春, 似, 海] [s, s, s, s, s, s, s] [120, 1105, 1155, 312, 800, 961, 204, 0, 0, 0,... [[[1.0, 0.0, 0.0, 0.0, 0.0]], [[1.0, 0.0, 0.0,...
4 [花, 深, 每, 觉, 影, 生, 香] [s, s, s, s, s, s, s] [511, 308, 375, 864, 400, 38, 421, 0, 0, 0, 0,... [[[1.0, 0.0, 0.0, 0.0, 0.0]], [[1.0, 0.0, 0.0,...
`
训练时的指标是
`
Epoch 48/50
289458/289458 [==============================] - 57s 196us/step - loss: 0.0563 - acc: 0.9819
Epoch 49/50
289458/289458 [==============================] - 57s 196us/step - loss: 0.0546 - acc: 0.9825
Epoch 50/50
289458/289458 [==============================] - 57s 196us/step - loss: 0.0530 - acc: 0.9831
`
看起来比较正常
我也没仔细检查到底哪里错了,就是按照您的思路把Viterbi函数重写了一下
`
def viterbi(nodes):
paths = {'b':nodes[0]['b'], 's':nodes[0]['s']}
for l in range(1,len(nodes)):
paths_ = paths.copy()
paths = {}
for i in nodes[l].keys():
nows = []
for j in paths_.keys():
tk = j[-1]+i
if zy.get(tk):
nows.append((j+i,paths_[j]+nodes[l][i]+zy[tk]))
k = np.argmax(np.array(nows)[:,1])
paths[nows[k][0]] = nows[k][1]
return list(paths.keys())[np.argmax(paths.values())]
`
'结婚的和尚未结婚的' -> ['结', '婚的和尚未结', '婚', '的']
结果大致上可以看了,我重新调整下转移概率这块吧
动态规划算法应该不会有错的。
你把model.predict的结果输出来看看,看看各个字的标签概率分布是怎样的?
当时的结果找不到了,但是我记得当时model.predict的结果没什么太违和的地方。可能是我在复制代码并改成python3的时候出的问题
March 14th, 2018
history = model.fit(np.array(list(d['x'])), np.array(list(d['y'])).reshape((-1,maxlen,5)), batch_size=batch_size, nb_epoch=50)
执行出来是错误的,请问如何修改,ValueError: cannot reshape array of size 9182688 into shape (32,5)
已经修改了代码。讨论区其他人也曾提供过解决方案。
另外,提醒一句:这是由于python2的代码直接用到python3所导致的,即便是现在,python3也不是一个绝对标准的选择,因此当你决定用python3时,应该学会怎么将python2的代码迁移到python3,再去做其它事情~
好的,谢谢老师
May 12th, 2018
博主,你好,“#统计所有字,跟每个字编号”这个是对训练语料所有词构建词典,我想问问如果对新的样本进行预测,新的样本中出现了字典中没有出现的词,新的样本生成向量的时候会对应的维度上就没有值,是null,但是模型对其预测的时候程序就会报错,这种情况该如何处理呢?
你是真的遇到过报错了,还是你想象中会报错?
我这里明明已经用0 id来代替未登录字了,理论上不会报错了呀。
恩,看到博主predict那行代码了,执行是没问题的
May 12th, 2018
请问我想做时间实体的解析,能否用此方法去做呢?就是给一个时间类型的字符串比如说是“发布时间:2018-3-6 20:12:04”找到年、月、日、时、分、秒?难道将时间字符串分割成一个个字符吗?
如果有如此规律,为什么不用正则表达式?
主要是时间类型有很多种,很不规律,想用一个模型去进行识别。May 24, 2015 2015.12.28 00:00再或者20180503再或者2017-02-21T08:40:13Z等等大概几十种,并且还有一些未知的时间类型,想已知的这些时间类型训练一个模型,然后对已知的和未知的时间实体进行准确识别,这个该如何处理呢?
在解析网页的时候遇到过这个问题,大部分都可以用一个正则来提取
July 24th, 2018
[...]本例子主要介绍如何使用 TensorFlow 来一步一步构建双端 LSTM 网络(听名字就感觉好腻害的样子),并完成序列标注的问题。先声明一下,本文中采用的方法主要参考了【中文分词系列】 4. 基于双向LSTM的seq2seq字标注这篇文章。该文章用 keras 框架来实现的双端 LSTM,在本例中,实现思路和该文章基本上一样,但是用 TensorFlow 来实现的。这个例子中涉及到的知识点比较多[...]
July 24th, 2018
我运行到:
d['x'] = d['data'].apply(lambda x: np.array(list(chars[x])+[0]*(maxlen-len(x))))报错了。
提示错误在.../keras/backend/tensorflow_backend.py,line 2967,in rnn
maximum_iterations=input_length)
TypeError: while_loop() got an unexpected keyword argument 'maximum_iterations'
谷歌上没有找到解决方案,不知道什么地方错了
你这报的是tf的错,但是这句代码跟tf没有半点关系呀。
是啊,正在找原因,怎么会到tensorflow里去了
July 26th, 2018
小白求问,在维特比算法中,这一步 nows[j+i]= paths_[j]+nodes[l][i]+zy[j[-1]+i]为何用的是加法,而不是用乘法,即 nows[j+i]= paths_[j]*nodes[l][i]*zy[j[-1]+i]?
因为概率已经取了对数,乘法变成加法
谢谢苏神的解答!!!
July 26th, 2018
您好,我运行这个文件的时候,会报错ValueError: Error when checking input: expected input_1 to have shape (32,) but got array with shape (1,),应该是 history = model.fit(np.array(list(d['x'])), np.array(list(d['y'])).reshape((-1,maxlen,5)), batch_size=batch_size, nb_epoch=50)这里报的错,请问有什么好的解决办法吗,我是在python3.5上运行的,盼复,多谢
我估计你是用python3的,将所有的map()替换为list(map())就行了。
emmmmm,我改了一下,还是不好用呀.....苏神还有什么别的建议么,就是如果是版本问题的话,因为报错还是没有变~
你检查一下np.array(list(d['x']))的shape和具体内容,理论上它应该是(num_data, 32)的shape,里边都是正数id
July 28th, 2018
博主,你好。最近一直阅读您的分词系列。这个程序让我受益匪浅,谢谢您的工作。有一个问题想请问一下。r = model.predict(np.array([list(chars[list(s)].fillna(0).astype(int))+[0]*(maxlen-len(s))]), verbose=False)[0][:len(s)]。这一句在预测的时候用到了chars,这个需要调用训练库来生成。如果我加载保存好的模型,希望下次预测的话是不是每次都需要先调用测试库来生成词典?谢谢您的回答。
chars肯定是需要事先保存下来,后面再加载调用的。
嗯明白了,非常感谢,保存到txt文档,以后需要加载就行了。谢谢。