1. 程式人生 > >聊一聊自然語言理解

聊一聊自然語言理解

語義理解(NLU)仍然是學界的一個難題!給你一篇文章或者一個句子,人們在理解這些句子時,頭腦中會進行上下文的搜尋和知識聯想。通常情況下,人在理解語義時頭腦中會搜尋與之相關的知識。知識圖譜的創始人人為,構成這個世界的是實體,而不是字串,這從根本上改變了過去搜索的體系。語義理解其實是基於知識,概念和這些概念間的關係。人們在解答問題時,往往會講述與這個問題相關的知識,這是語義理解的過程。這種機制完全不同於人對影象或者語音的認識。CNN在影象或者語音領域取得成果是不足為奇的,因為生物學家已經對人腦神經元在影象識別過程中的機制非常熟悉,但是對於人腦如何理解文字的神經元機制卻知之甚少,所以導致了目前nlp語義理解方面進展非常緩慢。很多人嘗試CNN引入nlp效果不佳,發現多層的CNN和單層的CNN幾乎沒有差別,原因得從人腦的神經元機制說起。生搬硬套是必然失敗的!深度學習的本質並不是神經元層數多這麼簡單,能夠從最基本的特徵,逐層抽取出高階特徵,最後進行分類,這是深度學習取得成功的關鍵。
  有一部分人質疑word2vector不是深度學習,說層數太淺達不到深度的級別,這是一種誤解。word2vector是地地道道的深度學習,能夠抽取出詞的高階特徵。他的成功,關鍵是基於他的核心思想:相同語境出現的詞語義相近。從第一層one-hot到embedding層,就是高階特徵抽取的過程。前面說過,層數多了不一定帶來效果的提升。詞embedding已經是高階特徵了,文字比影象要複雜很多,目前CNN在nlp中的引入,方向可能是錯誤的。必須深入研究人腦對文字理解的神經元機制,弄清楚生物學模型,然後才能從中抽象出數學模型,就像CNN一樣,否則nlp不會有長足的進展。目前來看,LSTM以及Attention Model是比較成功的,但是仍然基於形式化的,對於深層語義仍然沒有解決。
  目前來看,深度學習演算法LSTM,Attention Model等在nlp中的應用,僅限於上下文和詞,句子向量。計算一下句子相似度,聚類之類的,要想真正讓機器理解文字,還達不到。也就是說只在語義表示層做文章是遠遠不夠的,底層的知識圖譜是關鍵。Google提出的知識圖譜是一種變革,nlp是一個完整的生態圈,從最底層的儲存,GDB三元組(entry,relation,entry),到上層的語義表示(這個階段可以藉助深度學習直接在語義層進行訓練),比如(head,relation,tail)三元組表示的圖結構,表達了實體與實體間的關係,可以用深度學習訓練出一個模型:h + r = t,獲取語義表示。這樣在預測時,得到了兩個實體的語義表示,進行減法運算就可以知道兩者的關係。這個不同於word2vector,但是還是有共性的。word2vector的CBOW就是訓練x1 + x2 + …… = y這個模型。目前知網也在做這些事情。

  語義表示是深度學習在nlp應用中的重中之重。之前在詞embedding上word2vector獲取了巨大成功,現在主要方向是由詞embedding遷移到句子或者文章embedding。獲取句子的embedding,之前的部落格,siamese lstm已經有論述了,在2014~2015年間,國外的學者探索了各種方法,比如tree-lstm,convnet,skip-thougt,基於ma機構的siamese lstm來計算句子或者文章的相似度。目前從資料來看,基於ma結構的siamese lstm效果最好,最適應nlp的規律。在github上已經有了siamese lstm的實驗,進一步改進可是基於BiLSTM,至於增加層數是否能夠帶來準確率的提升,有待於進一步論證,個人持中立態度。本文主要探討word2vector。關於他的核心思想前面已經提到了,這是道的層面,具體推導,比如CBOW ,skip-gram的優化:negative sampleing和哈夫曼樹softmax,這是術的層面。現在上傳用tensorflow實現的word2vector程式碼:
  data-helper.py:

import collections
import os
import random
import zipfile
import numpy as np
import urllib.request as request
import tensorflow as tf

url = 'http://mattmahoney.net/dc/'

def maybe_download(filename,expected_bytes):
    if not os.path.exists(filename):
        filename,_ = request.urlretrieve(url+filename,filename)
    statinfo = os.stat(filename)
    if
statinfo.st_size == expected_bytes: print('Found and verified',filename) else: print(statinfo.st_size) raise Exception('Failed to verify' + filename + '.Can you get to it with a browser?') return filename def read_data(filename): with zipfile.ZipFile(filename) as f: data = tf.compat.as_str(f.read(f.namelist()[0])).split() return data vocabulary_size = 50000 def build_dataset(words): count = [['UNK',-1]] count.extend(collections.Counter(words).most_common(vocabulary_size - 1)) dictionary = dict(zip(list(zip(*count))[0],range(len(list(zip(*count))[0])))) data = list() un_count = 0 for word in words: if word in dictionary: index = dictionary[word] else: index = 0 un_count += 1 data.append(index) count[0][1] = un_count reverse_dictionary = dict(zip(dictionary.values(),dictionary.keys())) return data,reverse_dictionary,dictionary,count data_index = 0 def generate_batch(data,batch_size,num_skips,skip_window): filename = maybe_download('text8.zip', 31344016) words = read_data(filename) global data_index assert num_skips <= 2 * skip_window assert batch_size % num_skips == 0 span = 2 * skip_window + 1 batch = np.ndarray(shape=[batch_size],dtype=np.int32) labels = np.ndarray(shape=[batch_size,1],dtype=np.int32) buffer = collections.deque(maxlen=span) #初始化 for i in range(span): buffer.append(data[data_index]) data_index = (data_index + 1) % len(data) #移動視窗,獲取批量資料 for i in range(batch_size // num_skips): target = skip_window avoid_target = [skip_window] for j in range(num_skips): while target in avoid_target: target = np.random.randint(0,span - 1) avoid_target.append(target) batch[i * num_skips + j] = buffer[skip_window] labels[i * num_skips + j,0] = buffer[target] buffer.append(data[data_index]) data_index = (data_index + 1) % len(data) return batch,labels

w2vModel.py

import tensorflow as tf
import w2v.data_helper as da
import numpy as np
import math
filename = da.maybe_download(‘text8.zip’, 31344016)
words = da.read_data(“text8.zip”)
assert words is not None
data,reverse_dictionary,dictionary,count = da.build_dataset(words)

class config(object):
batch_size = 128
embedding_size = 128
skip_window = 1
num_skips = 2

valid_size = 16
valid_window = 100
valid_examples = np.random.choice(valid_window, valid_size, replace=False)
num_sampled = 64
vocabulary_size = 50000
num_steps = 10001

class w2vModel(object):
def init(self,config):
self.train_inputs = train_inputs = tf.placeholder(tf.int32, shape=[config.batch_size])
self.train_labels = train_labels = tf.placeholder(tf.int32, shape=[config.batch_size, 1])
self.valid_dataset = valid_dataset = tf.constant(config.valid_examples, dtype=tf.int32)

    with tf.device('/cpu:0'):
        embeddings = tf.Variable(
            tf.random_uniform(shape=[config.vocabulary_size, config.embedding_size], minval=-1.0, maxval=1.0))
        embed = tf.nn.embedding_lookup(embeddings, train_inputs)
        nce_weights = tf.Variable(
            tf.truncated_normal([config.vocabulary_size, config.embedding_size], stddev=1.0 / math.sqrt(config.embedding_size)))
        nce_bias = tf.Variable(tf.zeros([config.vocabulary_size]))

        loss = tf.reduce_mean(
            tf.nn.nce_loss(weights=nce_weights, biases=nce_bias, labels=train_labels, inputs=embed,
                           num_sampled=config.num_sampled, num_classes=config.vocabulary_size))
        optimizer = tf.train.GradientDescentOptimizer(1.0).minimize(loss)
        norm = tf.sqrt(tf.reduce_sum(tf.square(embeddings), 1, keep_dims=True))
        normalized_embeddings = embeddings / norm
        valid_embeddings = tf.nn.embedding_lookup(normalized_embeddings, valid_dataset)
        similarity = tf.matmul(valid_embeddings, normalized_embeddings, transpose_b=True)
        tf.add_to_collection("embedding",embeddings)
        self.saver = saver = tf.train.Saver(tf.global_variables())

w2vModel.py


import tensorflow as tf
import w2v.data_helper as da
import numpy as np
import math
#filename = da.maybe_download('text8.zip', 31344016)
words = da.read_data("text8.zip")
assert  words is not None
data,reverse_dictionary,dictionary,count = da.build_dataset(words)

class config(object):
    batch_size = 128
    embedding_size = 128
    skip_window = 1
    num_skips = 2

    valid_size = 16
    valid_window = 100
    valid_examples = np.random.choice(valid_window, valid_size, replace=False)
    num_sampled = 64
    vocabulary_size = 50000
    num_steps = 10001

class w2vModel(object):
    def __init__(self,config):
        self.train_inputs = train_inputs = tf.placeholder(tf.int32, shape=[config.batch_size])
        self.train_labels = train_labels = tf.placeholder(tf.int32, shape=[config.batch_size, 1])
        self.valid_dataset = valid_dataset = tf.constant(config.valid_examples, dtype=tf.int32)

        with tf.device('/cpu:0'):
            embeddings = tf.Variable(
                tf.random_uniform(shape=[config.vocabulary_size, config.embedding_size], minval=-1.0, maxval=1.0))
            embed = tf.nn.embedding_lookup(embeddings, train_inputs)
            nce_weights = tf.Variable(
                tf.truncated_normal([config.vocabulary_size, config.embedding_size], stddev=1.0 / math.sqrt(config.embedding_size)))
            nce_bias = tf.Variable(tf.zeros([config.vocabulary_size]))

            loss = tf.reduce_mean(
                tf.nn.nce_loss(weights=nce_weights, biases=nce_bias, labels=train_labels, inputs=embed,
                               num_sampled=config.num_sampled, num_classes=config.vocabulary_size))
            optimizer = tf.train.GradientDescentOptimizer(1.0).minimize(loss)
            norm = tf.sqrt(tf.reduce_sum(tf.square(embeddings), 1, keep_dims=True))
            normalized_embeddings = embeddings / norm
            valid_embeddings = tf.nn.embedding_lookup(normalized_embeddings, valid_dataset)
            similarity = tf.matmul(valid_embeddings, normalized_embeddings, transpose_b=True)
            tf.add_to_collection("embedding",embeddings)
            self.saver = saver = tf.train.Saver(tf.global_variables())