8NER實(shí)戰(zhàn)-(3)BiLSTM+CRF

# embedding層單詞和分詞信息
embedding = self.embedding_layer(self.word_inputs, self.seg_inputs, config)
# lstm輸入層
lstm_inputs = tf.nn.dropout(embedding, self.dropout)
# lstm輸出層
lstm_outputs = self.biLSTM_layer(lstm_inputs, self.lstm_dim, self.lengths)
# 投射層
self.logits = self.project_layer(lstm_outputs)
# 損失
self.loss = self.crf_loss_layer(self.logits, self.lengths)

tf.nn.embedding_lookup的作用就是找到要尋找的embedding data中的對(duì)應(yīng)的行下的vector痹籍。


image.png
def embedding_layer(self, word_inputs, seg_inputs, config, name=None):
    """
    :param word_inputs: one-hot編碼.其實(shí)所有字的one_hot編碼
    :param seg_inputs: 分詞特征
    :param config: 配置
    :param name: 層的命名
    :return:  shape = [word_inputs,word_dim+seg_dim]
    """
    embedding = []
    with tf.variable_scope("word_embedding" if not name else name), tf.device('/cpu:0'):
        self.word_lookup = tf.get_variable(
            name="word_embedding",
            shape=[self.num_words, self.word_dim],
            initializer=self.initializer
        )
        embedding.append(tf.nn.embedding_lookup(self.word_lookup, word_inputs))

        if config['seg_dim']:
            with tf.variable_scope("seg_embedding"), tf.device('/cpu:0'):
                self.seg_lookup = tf.get_variable(
                    name="seg_embedding",
                    shape=[self.num_sges, self.seg_dim],
                    initializer=self.initializer
                )
                embedding.append(tf.nn.embedding_lookup(self.seg_lookup, seg_inputs))
        embed = tf.concat(embedding, axis=-1)
    return embed
image.png
def biLSTM_layer(self, lstm_inputs, lstm_dim, lengths, name=None):
    """
    :param lstm_inputs: [batch_size, num_steps, emb_size]
    :param lstm_dim:
    :param name:
    :return: [batch_size, num_steps, 2*lstm_dim]
    為何返回是2*lstm_dim粉渠,因?yàn)槠涫请p向的lstm。每個(gè)方向的輸出為lstm_dim
    """
    with tf.variable_scope("word_biLSTM" if not name else name):
        lstm_cell = {}
        for direction in ['forward', 'backward']:
            with tf.variable_scope(direction):
                lstm_cell[direction] = rnn.CoupledInputForgetGateLSTMCell(
                    lstm_dim,
                    use_peepholes=True,
                    initializer=self.initializer,
                    state_is_tuple=True
                )
        outputs, final_status = tf.nn.bidirectional_dynamic_rnn(
            lstm_cell['forward'],
            lstm_cell['backward'],
            lstm_inputs,
            dtype=tf.float32,

            sequence_length=lengths
        )
    # 因?yàn)閱蜗虻膌stm輸出的格式為[batch_size, num_steps,lstm_dim]凌外。
    # 2表示在lstm_dim這個(gè)維度進(jìn)行拼接佑女。
    # 個(gè)人覺(jué)得outputs的輸出格式為[[batch_size, num_steps,lstm_dim],[batch_size, num_steps,lstm_dim]]
    # 即是一個(gè)list。list里面的每一個(gè)元素是單向的lstm的輸出
    return tf.concat(outputs, axis=2)
def project_layer(self, lstm_outputs, name=None):
    """
    :param lstm_outputs: [batch_size, num_steps, emb_size]
    個(gè)人覺(jué)得lstm_outputs: [batch_size, num_steps, lstm_dim * 2]  num_steps表示每個(gè)句子里面字的數(shù)量歉备。即每個(gè)批次的句子長(zhǎng)度
    :param name:
    :return: [batch_size,num_steps, num_tags]
    """
    with tf.variable_scope('project_layer' if not name else name):
        with tf.variable_scope('hidden_layer'):
            W = tf.get_variable(
                "W",
                shape=[self.lstm_dim * 2, self.lstm_dim],
                dtype=tf.float32,
                initializer=self.initializer
            )
            b = tf.get_variable(
                "b",
                shape=[self.lstm_dim],
                dtype=tf.float32,
                initializer=tf.zeros_initializer()
            )
            out_put = tf.reshape(lstm_outputs, shape=[-1, self.lstm_dim * 2])  # 得到所有的字,將所有的字最后編碼為lstm_dim長(zhǎng)度
            hidden = tf.tanh(tf.nn.xw_plus_b(out_put, W, b))

        with tf.variable_scope('logits'):
            W = tf.get_variable(
                "W",
                shape=[self.lstm_dim, self.num_tags],
                dtype=tf.float32,
                initializer=self.initializer
            )
            b = tf.get_variable(
                "b",
                shape=[self.num_tags],
                dtype=tf.float32,
                initializer=tf.zeros_initializer()
            )
            # 最后將每個(gè)字編碼為num_tags匪燕。即最后想要得到每個(gè)字屬于每個(gè)tag的概率
            pred = tf.nn.xw_plus_b(hidden, W, b)
    #  返回原始的shape蕾羊。即batch_size,num_setps,num_tags
    return tf.reshape(pred, [-1, self.num_setps, self.num_tags])

+CRF

def crf_loss_layer(self, project_logits, lenghts, name=None):
    """
    # 個(gè)人覺(jué)得是[-1, self.num_setps, self.num_tags]
    :param project_logits: [1, num_steps, num_tages]
    :param lenghts:
    :param name:
    :return: scalar loss
    聽(tīng)說(shuō)下面是固定的寫法
    """
    with tf.variable_scope('crf_loss' if not name else name):
        small_value = -10000.0
        # 下面是對(duì)于一個(gè)字。但是最后一維帽驯,比原來(lái)的標(biāo)簽長(zhǎng)度多了一個(gè)元素
        start_logits = tf.concat(
            [
                small_value * tf.ones(shape=[self.batch_size, 1, self.num_tags]),
                tf.zeros(shape=[self.batch_size, 1, 1])
            ],
            axis=-1
        )

        pad_logits = tf.cast(
            small_value *
            tf.ones(shape=[self.batch_size, self.num_setps, 1]),
            dtype=tf.float32
        )

        # 貌似是在列的位置最后拼接一個(gè)元素.所以此時(shí)project_layer層輸出的每個(gè)字最后一層多了一個(gè)元素
        # 即在最后一個(gè)維度填充了一個(gè)元素
        logits = tf.concat(
            [project_logits, pad_logits],
            axis=-1
        )
        # 此時(shí)相當(dāng)于在每個(gè)批次的龟再,每個(gè)句子開(kāi)頭位置添加了一個(gè)字
        logits = tf.concat(
            [start_logits, logits],
            axis=1
        )
        # 因?yàn)閟elf.targets.shape = [batch_size,num_steps].所以下面的操作,類似于在每個(gè)句子前面添加了一個(gè)字
        # 所以此時(shí)就和上面的填充的形狀tf.concat([start_logits, logits],axis=1)
        # 對(duì)應(yīng)了起來(lái)
        targets = tf.concat(
            [tf.cast(
                self.num_tags * tf.ones([self.batch_size, 1]),
                tf.int32
            ),
                self.targets
            ]
            ,
            axis=-1
        )
        # 每個(gè)狀態(tài)之間的轉(zhuǎn)移矩陣
        self.trans = tf.get_variable(
            "transitions",
            shape=[self.num_tags + 1, self.num_tags + 1],
            initializer=self.initializer
        )

        log_likehood, self.trans = crf_log_likelihood(
            inputs=logits,
            tag_indices=targets,
            transition_params=self.trans,
            sequence_lengths=lenghts + 1  # 因?yàn)樯厦嬖诰渥拥拈_(kāi)頭位置添加了一個(gè)字
        )
        return tf.reduce_mean(-log_likehood)

用F1值來(lái)評(píng)估

def evaluate(sess, model, name, manager, id_to_tag, logger):
    logger.info('evaluate:{}'.format(name))
    ner_results = model.evaluate(sess, manager, id_to_tag)
    eval_lines = model_utils.test_ner(ner_results, FLAGS.result_path)
    for line in eval_lines:
        logger.info(line)
    f1 = float(eval_lines[1].strip().split()[-1])

    if name == "dev":
        best_test_f1 = model.best_dev_f1.eval()
        if f1 > best_test_f1:
            tf.assign(model.best_dev_f1, f1).eval()
            logger.info('new best dev f1 socre:{:>.3f}'.format(f1))
        return f1 > best_test_f1
    elif name == "test":
        best_test_f1 = model.best_test_f1.eval()
        if f1 > best_test_f1:
            tf.assign(model.best_test_f1, f1).eval()
            logger.info('new best test f1 score:{:>.3f}'.format(f1))
        return f1 > best_test_f1

關(guān)于調(diào)參:

  • Validation loss vs Training Loss
    如果validation loss < Training Loss, 可能就過(guò)擬合了尼变。這樣就需要嘗試著降低網(wǎng)絡(luò)大小network size 或者 提高dropout的值利凑,比如0.5,0.6依次嘗試。

  • 用mini batch的方法嫌术,把數(shù)據(jù)集劃分成很若干個(gè)小一點(diǎn)的集合哀澈。來(lái)調(diào)整參數(shù):如embedding_dim, lstm_dim, learning=rate(3e-4)

這里用CRF++先跑了一遍,速度很快度气,準(zhǔn)確率在0.8左右割按,recall在0.87左右,f1在0.87多磷籍。然后用BiLSTM后接softmax來(lái)跑loss一下子降到很低适荣,感覺(jué)很容易局部過(guò)擬合现柠。BiLSTM+CRF后, loss穩(wěn)定變小弛矛,到0.15時(shí)候準(zhǔn)確率變化已經(jīng)比較少了够吩,比不接CRF的更快擬合≌擅ィ總體準(zhǔn)確率比無(wú)CRF的更高废恋。另外,迭代次數(shù)調(diào)高后扒寄,準(zhǔn)確率也會(huì)提高一點(diǎn)鱼鼓。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市该编,隨后出現(xiàn)的幾起案子迄本,更是在濱河造成了極大的恐慌,老刑警劉巖课竣,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嘉赎,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡于樟,警方通過(guò)查閱死者的電腦和手機(jī)公条,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)迂曲,“玉大人靶橱,你說(shuō)我怎么就攤上這事÷放酰” “怎么了关霸?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)杰扫。 經(jīng)常有香客問(wèn)我队寇,道長(zhǎng),這世上最難降的妖魔是什么章姓? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任佳遣,我火速辦了婚禮,結(jié)果婚禮上凡伊,老公的妹妹穿的比我還像新娘零渐。我一直安慰自己,他們只是感情好窗声,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布相恃。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拦耐。 梳的紋絲不亂的頭發(fā)上耕腾,一...
    開(kāi)封第一講書(shū)人閱讀 51,698評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音杀糯,去河邊找鬼扫俺。 笑死,一個(gè)胖子當(dāng)著我的面吹牛固翰,可吹牛的內(nèi)容都是我干的狼纬。 我是一名探鬼主播,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼骂际,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼疗琉!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起歉铝,我...
    開(kāi)封第一講書(shū)人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤盈简,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后太示,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體柠贤,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年类缤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了臼勉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡餐弱,死狀恐怖宴霸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情岸裙,我是刑警寧澤猖败,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站降允,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏艺糜。R本人自食惡果不足惜剧董,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望破停。 院中可真熱鬧翅楼,春花似錦、人聲如沸真慢。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)黑界。三九已至管嬉,卻和暖如春皂林,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蚯撩。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工础倍, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人胎挎。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓沟启,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親犹菇。 傳聞我的和親對(duì)象是個(gè)殘疾皇子德迹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容