使用BERT和TensorFlow構建搜尋引擎

2021-01-14 相約機器人

作者 | Denis Antyukhov

來源 | Medium

編輯 | 代碼醫生團隊


基於神經概率語言模型的特徵提取器,例如與多種下遊NLP任務相關的BERT提取特徵。因此它們有時被稱為自然語言理解(NLU)模塊。

 

這些特徵還可以用於基於實例的學習,其依賴於計算查詢與訓練樣本的相似性。為了證明這一點,將使用BERT特徵提取為文本構建最近鄰搜尋引擎。

 

這個實驗的計劃是:

 

獲得預先訓練的BERT模型檢查點

提取針對推理優化的子圖

使用tf.Estimator創建特徵提取器

用T-SNE和嵌入式投影儀探索向量空間

實現最近鄰搜尋引擎

用數學加速最近鄰查詢

示例:構建電影推薦系統

 

問題和解答

本指南中包含哪些內容?

本指南包含兩個實現:BERT文本特徵提取器和最近鄰居搜尋引擎。

 

這個指南是誰?

本指南對於有興趣使用BERT進行自然語言理解任務的研究人員非常有用。它也可以作為與tf.Estimator API接口的工作示例。

 

需要做些什麼?

對於熟悉TensorFlow的讀者來說,完成本指南大約需要30分鐘。

 

相關代碼

這個實驗的代碼可以在Colab中找到。另外,查看為BERT實驗設置的存儲庫:它包含獎勵內容。

https://colab.research.google.com/drive/1ra7zPFnB2nWtoAc0U5bLp0rWuPWb6vu4

https://github.com/gaphex/bert_experimental

 

第1步:獲得預先訓練的模型

從預先訓練的BERT檢查點開始。出於演示目的,將使用由Google工程師預先訓練的無框架英語模型。

 

為了配置和優化圖形以進行推理,將使用令人敬畏的bert-as-a-service存儲庫。此存儲庫允許通過TCP為遠程客戶端提供BERT模型。

 

擁有遠程BERT伺服器在多主機環境中是有益的。但是在實驗的這一部分中,將專注於創建一個本地 (進程中)特徵提取器。如果希望避免客戶端 - 伺服器體系結構引入的額外延遲和潛在故障模式,這將非常有用。

 

現在下載模型並安裝包。

!wget https://storage.googleapis.com/bert_models/2019_05_30/wwm_uncased_L-24_H-1024_A-16.zip!unzip wwm_uncased_L-24_H-1024_A-16.zip!pip install bert-serving-server --no-deps


第2步:優化推理圖

通常要修改模型圖,必須進行一些低級TensorFlow編程。但是由於bert-as-a-service,可以使用簡單的CLI界面配置推理圖。

import osimport tensorflow as tf from bert_serving.server.graph import optimize_graphfrom bert_serving.server.helper import get_args_parser  MODEL_DIR = '/content/wwm_uncased_L-24_H-1024_A-16/' #@param {type:"string"}GRAPH_DIR = '/content/graph/' #@param {type:"string"}GRAPH_OUT = 'extractor.pbtxt' #@param {type:"string"}GPU_MFRAC = 0.2 #@param {type:"string"} POOL_STRAT = 'REDUCE_MEAN' #@param {type:"string"}POOL_LAYER = "-2" #@param {type:"string"}SEQ_LEN = "64" #@param {type:"string"} tf.gfile.MkDir(GRAPH_DIR) parser = get_args_parser()carg = parser.parse_args(args=['-model_dir', MODEL_DIR,                               "-graph_tmp_dir", GRAPH_DIR,                               '-max_seq_len', str(SEQ_LEN),                               '-pooling_layer', str(POOL_LAYER),                               '-pooling_strategy', POOL_STRAT,                               '-gpu_memory_fraction', str(GPU_MFRAC)]) tmpfi_name, config = optimize_graph(carg)graph_fout = os.path.join(GRAPH_DIR, GRAPH_OUT) tf.gfile.Rename(    tmpfi_name,    graph_fout,    overwrite=True)print("Serialized graph to {}".format(graph_fout))


有幾個參數需要注意。

 

對於每個文本樣本,BERT編碼層輸出一個形狀的張量[ sequence_len,encoder_dim ],每個標記有一個向量。如果要獲得固定的表示,需要應用某種池。

 

POOL_STRAT參數定義應用於編碼層編號POOL_LAYER的池策略。默認值' REDUCE_MEAN '平均序列中所有標記的向量。當模型未經過微調時,此策略最適用於大多數句子級別的任務。另一個選項是NONE,在這種情況下根本不應用池。這對於命名實體識別或POS標記等單詞級任務非常有用。有關這些選項的詳細討論,請查看韓曉的博文。

https://hanxiao.github.io/2019/01/02/Serving-Google-BERT-in-Production-using-Tensorflow-and-ZeroMQ/

 

SEQ_LEN影響模型處理的序列的最大長度。較小的值將幾乎線性地增加模型推理速度。

 

運行上述命令將把模型圖和權重成GraphDef將被序列化到一個對象pbtxt在文件GRAPH_OUT。該文件通常小於預先訓練的模型,因為將刪除訓練所需的節點和變量。這導致了一個非常便攜的解決方案:例如序列化後英語模型只需要380 MB。

 

第3步:創建特徵提取器

現在將使用序列化圖形來使用tf.Estimator API構建特徵提取器。需要定義兩件事:input_fnmodel_fn

 

input_fn管理將數據導入模型。這包括執行整個文本預處理管道和為BERT 準備feed_dict

 

首先,將每個文本樣本轉換為包含INPUT_NAMES 中列出的必要功能的tf.Example實例。該bert_tokenizer對象包含WordPiece詞彙和執行文本預處理。之後,示例將按照feed_dict中的功能名稱進行重新分組。

INPUT_NAMES = ['input_ids', 'input_mask', 'input_type_ids']bert_tokenizer = FullTokenizer(VOCAB_PATH) def build_feed_dict(texts):        text_features = list(convert_lst_to_features(        texts, SEQ_LEN, SEQ_LEN,         bert_tokenizer, log, False, False))     target_shape = (len(texts), -1)     feed_dict = {}    for iname in INPUT_NAMES:        features_i = np.array([getattr(f, iname) for f in text_features])        features_i = features_i.reshape(target_shape)        features_i = features_i.astype("int32")        feed_dict[iname] = features_i     return feed_dict


tf.Estimators有一個有趣的功能,可以在每次調用預測函數時重建並重新初始化整個計算圖。因此,為了避免開銷,將生成器傳遞給預測函數,並且生成器將在永無止境的循環中為模型生成特徵。

def build_input_fn(container):        def gen():        while True:          try:            yield build_feed_dict(container.get())          except StopIteration:            yield build_feed_dict(container.get())     def input_fn():        return tf.data.Dataset.from_generator(            gen,            output_types={iname: tf.int32 for iname in INPUT_NAMES},            output_shapes={iname: (None, None) for iname in INPUT_NAMES})    return input_fn class DataContainer:  def __init__(self):    self._texts = None    def set(self, texts):    if type(texts) is str:      texts = [texts]    self._texts = texts      def get(self):    return self._texts


model_fn包含模型的規範。在例子中,它是從上一步中保存的pbtxt文件加載的。功能通過input_map顯式映射到相應的輸入節點。

def model_fn(features, mode):    with tf.gfile.GFile(GRAPH_PATH, 'rb') as f:        graph_def = tf.GraphDef()        graph_def.ParseFromString(f.read())            output = tf.import_graph_def(graph_def,                                 input_map={k + ':0': features[k]                                             for k in INPUT_NAMES},                                 return_elements=['final_encodes:0'])     return EstimatorSpec(mode=mode, predictions={'output': output[0]})  estimator = Estimator(model_fn=model_fn)


現在幾乎擁有了進行推理所需的一切。開工吧!

def batch(iterable, n=1):    l = len(iterable)    for ndx in range(0, l, n):        yield iterable[ndx:min(ndx + n, l)] def build_vectorizer(_estimator, _input_fn_builder, batch_size=128):  container = DataContainer()  predict_fn = _estimator.predict(_input_fn_builder(container), yield_single_examples=False)    def vectorize(text, verbose=False):    x = []    bar = Progbar(len(text))    for text_batch in batch(text, batch_size):      container.set(text_batch)      x.append(next(predict_fn)['output'])      if verbose:        bar.add(len(text_batch))          r = np.vstack(x)    return r    return vectorize


可以在存儲庫中找到上述功能提取器的獨立版本。

https://github.com/gaphex/bert_experimental

 

>>> bert_vectorizer = build_vectorizer(estimator,build_input_fn)>>> bert_vectorizer(64 * ['sample text'])。shape (64,768 )


第4步:使用Projector探索向量空間

現在是時候進行演示了!

 

使用矢量化器,將為Reuters-21578基準語料庫中的文章生成嵌入。

為了在3D中可視化和探索嵌入向量空間,將使用稱為T-SNE的降維技術。

 

先來看一下嵌入文章吧。

from nltk.corpus import reuters nltk.download("reuters")nltk.download("punkt") max_samples = 256categories = ['wheat', 'tea', 'strategic-metal',               'housing', 'money-supply', 'fuel'] S, X, Y = [], [], [] for category in categories:  print(category)    sents = reuters.sents(categories=category)  sents = [' '.join(sent) for sent in sents][:max_samples]  X.append(bert_vectorizer(sents, verbose=True))  Y += [category] * len(sents)  S += sents  X = np.vstack(X) X.shape


嵌入式投影儀可以使用生成的嵌入的交互式可視化。

 

可以自己運行T-SNE或使用右下角的書籤加載檢查點(加載僅適用於Chrome)。

 

 

第5步:構建搜尋引擎

現在,假設擁有50k文本樣本的知識庫,需要快速回答基於此數據的查詢。如何從文本資料庫中檢索與查詢最相似的樣本?答案是最近鄰搜索。

 

在形式上,將解決搜索問題定義如下:

給定一組點的小號在向量空間中號,並查詢點Q ∈ 中號,發現在最近點小號到Q。有多種方法可以在向量空間中定義「最接近」,將使用歐幾裡德距離。

 

因此要為文本構建搜尋引擎,將遵循以下步驟:

 

矢量化來自知識庫的所有樣本 - 得到S

向量化查詢 - 給出Q.

計算Q和S之間的歐氏距離D.

按升序排序D - 提供最相似樣本的索引

從知識庫中檢索所述樣本的標籤


為了簡單地實現這一點將在純TensorFlow中實現。

 

首先,為Q和S創建佔位符

dim = 1024graph = tf.Graph()sess = tf.InteractiveSession(graph=graph) Q = tf.placeholder("float", [dim])S = tf.placeholder("float", [None, dim])


定義歐氏距離計算

squared_distance = tf.reduce_sum(tf.pow(Q - S, 2), reduction_indices=1)distance = tf.sqrt(squared_distance)


最後,獲得最相似的樣本索引

top_k = 3 top_neg_dists, top_indices = tf.math.top_k(tf.negative(distance), k=top_k)top_dists = tf.negative(top_neg_dists)


第6步:用數學加速搜索

現在已經設置了基本的檢索算法,問題是:  

可以讓它運行得更快嗎?通過一點點數學可以的。

 

對於一對向量p和q,歐氏距離定義如下:

 

 

這正是在第4步中計算它的方式。

 

但是,由於p和q是向量,可以擴展並重寫它:

 

 

其中⟨...⟩表示內在產品。

 

在TensorFlow中,這可以寫成如下:

Q = tf.placeholder("float", [dim])S = tf.placeholder("float", [None, dim]) Qr = tf.reshape(Q, (1, -1)) PP = tf.keras.backend.batch_dot(S, S, axes=1)QQ = tf.matmul(Qr, tf.transpose(Qr))PQ = tf.matmul(S, tf.transpose(Qr)) distance = PP - 2 * PQ + QQdistance = tf.sqrt(tf.reshape(distance, (-1,))) top_neg_dists, top_indices = tf.math.top_k(tf.negative(distance), k=top_k)


由於矩陣乘法運算是高度優化的,因此該實現的工作速度比前一個略快。

 

順便說一下,在上面的公式中,PP和QQ實際上是各個向量的L2範數的平方。如果兩個向量都是L2歸一化的,則PP = QQ = 1.這給出了內積與歐氏距離之間的有趣關係:

 

 

然而,進行L2歸一化會丟棄關於矢量幅度的信息,這在很多情況下是不合需要的。

 

相反,可能會注意到,只要知識庫沒有改變,PP,其平方向量範數也保持不變。因此,不是每次重新計算它,而是使用預先計算的結果,進一步加速距離計算。

 

現在把它們放在一起。

class L2Retriever:    def __init__(self, dim, top_k=3, use_norm=False, use_gpu=True):        self.dim = dim        self.top_k = top_k        self.use_norm = use_norm        config = tf.ConfigProto(            device_count={'GPU': (1 if use_gpu else 0)}        )        config.gpu_options.allow_growth = True        self.session = tf.Session(config=config)                self.norm = None        self.query = tf.placeholder("float", [self.dim])        self.kbase = tf.placeholder("float", [None, self.dim])                self.build_graph()     def build_graph(self):              if self.use_norm:            self.norm = tf.placeholder("float", [None, 1])         distance = dot_l2_distances(self.kbase, self.query, self.norm)        top_neg_dists, top_indices = tf.math.top_k(tf.negative(distance), k=self.top_k)        top_dists = tf.negative(top_neg_dists)         self.top_distances = top_dists        self.top_indices = top_indices     def predict(self, kbase, query, norm=None):        query = np.squeeze(query)        feed_dict = {self.query: query, self.kbase: kbase}        if self.use_norm:          feed_dict[self.norm] = norm                I, D = self.session.run([self.top_indices, self.top_distances],                                feed_dict=feed_dict)                return I, D      def dot_l2_distances(kbase, query, norm=None):    query = tf.reshape(query, (1, -1))        if norm is None:      XX = tf.keras.backend.batch_dot(kbase, kbase, axes=1)    else:      XX = norm    YY = tf.matmul(query, tf.transpose(query))    XY = tf.matmul(kbase, tf.transpose(query))        distance = XX - 2 * XY + YY    distance = tf.sqrt(tf.reshape(distance, (-1,)))        return distance


示例:電影推薦系統

對於此示例,將使用IMDB中的電影摘要數據集。使用NLU和Retriever模塊,將構建一個電影推薦系統,用於建議具有類似繪圖功能的電影。

 

首先,下載並準備IMDB數據集。

http://www.cs.cmu.edu/~ark/personas/

 

import pandas as pdimport json !wget http:!tar -xvzf MovieSummaries.tar.gz plots_df = pd.read_csv('MovieSummaries/plot_summaries.txt', sep='\t', header=None)meta_df = pd.read_csv('MovieSummaries/movie.metadata.tsv', sep='\t', header=None) plot = {}metadata = {}movie_data = {} for movie_id, movie_plot in plots_df.values:  plot[movie_id] = movie_plot  for movie_id, movie_name, movie_genre in meta_df[[0,2,8]].values:  genre = list(json.loads(movie_genre).values())  if len(genre):    metadata[movie_id] = {"name": movie_name,                          "genre": genre}    for movie_id in set(plot.keys())&set(metadata.keys()):  movie_data[metadata[movie_id]['name']] = {"genre": metadata[movie_id]['genre'],                                            "plot": plot[movie_id]}  X, Y, names = [], [], [] for movie_name, movie_meta in movie_data.items():  X.append(movie_meta['plot'])  Y.append(movie_meta['genre'])  names.append(movie_name)


使用BERT NLU模塊矢量化電影情節:

X_vect = bert_vectorizer(X, verbose=True)

 

最後,使用L2Retriever,找到與查詢電影最相似的繪圖向量的電影,並將其返回給用戶。

def buildMovieRecommender(movie_names, vectorized_plots, top_k=10):  retriever = L2Retriever(vectorized_plots.shape[1], use_norm=True, top_k=top_k, use_gpu=False)  vectorized_norm = np.sum(vectorized_plots**2, axis=1).reshape((-1,1))    def recommend(query):    try:      idx = retriever.predict(vectorized_plots,                               vectorized_plots[movie_names.index(query)],                               vectorized_norm)[0][1:]      for i in idx:        print(names[i])    except ValueError:      print("{} not found in movie db. Suggestions:")      for i, name in enumerate(movie_names):        if query.lower() in name.lower():          print(i, name)            return recommend


來看看!

 

>>> recommend = buildMovieRecommender(names, X_vect)>>> recommend("The Matrix")Impostor Immortel Saturn 3 Terminator Salvation The Terminator Logan's Run Genesis II Tron: Legacy Blade Runner


即使沒有監督,該模型也可以在幾個分類和檢索任務中充分執行。雖然使用監督數據可以進一步提高性能,但所描述的文本特徵提取方法為下遊NLP解決方案提供了堅實的基線。

 

以上是使用BERT和TensorFlow構建搜尋引擎的指南。


推薦閱讀

哈工大訊飛聯合實驗室發布基於全詞覆蓋的中文BERT預訓練模型


點擊「閱讀原文」圖書配套資源

相關焦點

  • TFX 最新博文:如何使用 TensorFlow 生態系統實現快速高效的 BERT...
    使用 TensorFlow Transform 為我們提供了一些實際的好處。一方面,我們可以在組織上劃分數據預處理和模型架構工作之間的職責。另一方面,我們可以方便地調試、測試和生成預處理輸出的統計信息。transform 組件輸出轉換後的訓練集,作為 TFRecords,這些訓練集易於檢查。
  • 【注意力機制】transformers之轉換Tensorflow的Checkpoints
    你可以通過使用convert_tf_checkpoint_to_pytorch.py將任意的BERT的Tensorflow的Checkpoints轉換為PyTorch格式(特別是由Google發布的預訓練模型(https://github.com/google-research
  • TensorFlow 攜手 NVIDIA,使用 TensorRT 優化 TensorFlow Serving...
    HTTP/REST API at:localhost:8501 …$ curl -o /tmp/resnet/resnet_client.py https://raw.githubusercontent.com/tensorflow/serving/master/tensorflow_serving/example/resnet_client.py
  • 深度解讀TensorFlow,了解它的最新發展!
    TensorFlow.js 可以為開發者提供高性能的、易於使用的機器學習構建模塊,允許研發人員在瀏覽器上訓練模型,或以推斷模式運行預訓練的模型。TensorFlow.js 不僅可以提供低級的機器學習構建模塊,還可以提供高級的類似 Keras 的 API 來構建神經網絡。
  • Keras 教程:BERT 文本摘要
    在SQuAD 數據集中,輸入由一個問題和一個上下文段落組成。目標是找到回答問題的段落的跨度。我們使用「精確匹配(Exact Match)」指標來評估我們在這些數據上的表現,它度量了精確匹配任何一個真實答案的預測的百分比。我們對一個BERT模型進行微調,如下所示:將上下文和問題作為輸入,輸入給BERT。
  • 作為TensorFlow的底層語言,你會用C++構建深度神經網絡嗎?
    在本文中,我將展示如何使用 TensorFlow 在 C++ 上構建深度神經網絡,並通過車齡、公裡數和使用油品等條件為寶馬 1 系汽車進行估價。目前,我們還沒有可用的 C++ 優化器,所以你會看到訓練代碼看起來不那麼吸引人,但是我們會在未來加入的。
  • TensorFlow極速入門
    Tensorflow 擁有易用的 python 接口,而且可以部署在一臺或多臺 cpu , gpu 上,兼容多個平臺,包括但不限於 安卓/windows/linux 等等平臺上,而且擁有 tensorboard這種可視化工具,可以使用 checkpoint 進行實驗管理,得益於圖計算,它可以進行自動微分計算,擁有龐大的社區,而且很多優秀的項目已經使用 tensorflow
  • 步履不停:TensorFlow 2.4新功能一覽!
    集合運算        https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/ops/collective_ops.py
  • 谷歌開放GNMT教程:如何使用TensorFlow構建自己的神經機器翻譯系統
    使用通用結構和訓練時間表訓練 seq2seq 模型,包括多種注意力機制和固定抽樣。使用 in-graph 集束搜索在 seq2seq 模型中進行推理。優化 seq2seq 模型,以實現在多 GPU 設置中的模型訓練。下文我們將簡要地介紹該 Github 教程項目。
  • TensorFlow 資源大全中文版
    循環神經網絡模型/工程圖片形態轉換 – 無監督圖片形態轉換的實現Show, Attend and Tell算法 -基於聚焦機制的自動圖像生成器Neural Style – Neural Style 算法的TensorFlow實現Pretty Tensor – Pretty Tensor提供了高級別的
  • TensorFlow極簡教程:創建、保存和恢復機器學習模型
    需求Python3 (https://www.python.org/)TensorFlow (https://www.tensorflow.org/)NumPy (http://www.numpy.org/)TensorFlow:保存/恢復和混合多重模型在第一個模型成功建立並訓練之後,你或許需要了解如何保存與恢復這些模型。
  • Keras和TensorFlow究竟哪個會更好?
    他給出的建議是先使用 Keras ,然後下載 TensorFlow 以獲取可能需要的任何特定功能。文本中,Rosebrock 展示了如何訓練使用 Keras 的神經網絡和使用直接構建在 TensorFlow 庫中的 Keras+TensorFlow 集成(具有自定義功能)的模型。
  • 在Windows中安裝Tensorflow和Kears深度學習框架
    建立Tensorflow的Anaconda虛擬環境在一臺計算機中,我們常常需要安裝很多軟體,但是每個軟體所需要的Python的關聯模塊或版本不相同。例如,我們要使用Python開發開發網站系統,安裝的網站框架可能需要Python2.X的版本,但是安裝Tensorflow需要Python3.5的版本,此時就會發生版本不一致的問題。
  • 基於RTX2060構建TensorFlow-gpu(keras)學習平臺
    開始菜單運行anaconda navigator檢查是否安裝了notebook(默認有安裝)三、安裝tensorflow/keras在激活的環境中安裝:1. 如果機器上有gpu,則安裝gpu版本,沒有GPU就安裝cpu版。
  • 玩轉TensorFlow?你需要知道這30功能
    地址是:tensorflow.org/tfx/?這意味著,如果不想使用低階的模型,那仍然可以用高階 API 的用戶友好性來實現圖形+模型的構建。2.0 版本將會有更多的功能!結果是在計算速度、內存使用和移動平臺的可移植性上都有改進。不過你先要有一個硬體加速器啊!
  • 如何使用TensorFlow Hub的ESRGAN模型來在安卓app中生成超分圖片
    儘管可以使用傳統的插值方法(如雙線性插值和雙三次插值)來完成這個任務,但是產生的圖片質量卻經常差強人意。深度學習,尤其是對抗生成網絡 GAN,已經被成功應用在超分任務上,比如 SRGAN 和 ESRGAN 都可以生成比較真實的超分圖片。那麼在本文裡,我們將介紹一下如何使用TensorFlow Hub上的一個預訓練的 ESRGAN 模型來在一個安卓 app 中生成超分圖片。
  • 關於TensorFlow,你應該了解的9件事
    它是 AlphaGo 和 Google Cloud Vision 的基礎,也會是屬於你的。TensorFlow 是開源的,你可以免費下載並立即開始使用。TensorFlow 下載地址:https://www.tensorflow.org/install/TensorFlow 初始教程:https://www.datacamp.com/community/tutorials/tensorflow-tutorial
  • 5個簡單的步驟掌握Tensorflow的Tensor
    在這篇文章中,我們將深入研究Tensorflow Tensor的細節。我們將在以下五個簡單步驟中介紹與Tensorflow的Tensor中相關的所有主題:第一步:張量的定義→什麼是張量?我們經常將NumPy與TensorFlow一起使用,因此我們還可以使用以下行導入NumPy:import tensorflow as tfimport numpy as np張量的創建:創建張量對象有幾種方法可以創建tf.Tensor對象。讓我們從幾個例子開始。
  • Tensorflow基礎教程15天之創建Tensor
    Tensor是Tensorflow中使用在計算圖中的最基本的數據單位,我們可以聲明Tensor為variable,或者為Tensor提供placeholer。但首先我們必須知道如何創建Tensor。序列TensorTensorflow允許我們定義數組Tensor。
  • tensorflow初級必學算子
    在之前的文章中介紹過,tensorflow框架的核心是將各式各樣的神經網絡抽象為一個有向無環圖,圖是由tensor以及tensor變換構成;雖然現在有很多高階API可以讓開發者忽略這層抽象,但對於靈活度要求比較高的算法仍然需要開發者自定義網絡圖,所以建議開發者儘量先學習tf1.x