Sign inSign up

deliah/bert-as-service

By deliah

•Updated over 6 years ago

Image
1

836

deliah/bert-as-service repository overview

⁠bert-as-service

Using BERT model as a sentence encoding service, i.e. mapping a variable-length sentence to a fixed-length vector.

GitHub stars ⁠ Pypi package ⁠ ReadTheDoc ⁠ PyPI - Downloads ⁠ GitHub issues ⁠ GitHub license ⁠ Twitter ⁠

Highlights⁠ • What is it⁠ • Install⁠ • Getting Started⁠ • API⁠ • Tutorials⁠ • FAQ⁠ • Benchmark⁠ • Blog⁠

⁠Made by Han Xiao • :globe_with_meridians: https://hanxiao.github.io⁠

⁠What is it

BERT is a NLP model developed by Google⁠ for pre-training language representations. It leverages an enormous amount of plain text data publicly available on the web and is trained in an unsupervised manner. Pre-training a BERT model is a fairly expensive yet one-time procedure for each language. Fortunately, Google released several pre-trained models where you can download from here⁠.

Sentence Encoding/Embedding is a upstream task required in many NLP applications, e.g. sentiment analysis, text classification. The goal is to represent a variable length sentence into a fixed length vector, e.g. hello world to [0.1, 0.3, 0.9]. Each element of the vector should "encode" some semantics of the original sentence.

Finally, bert-as-service uses BERT as a sentence encoder and hosts it as a service via ZeroMQ, allowing you to map sentences into fixed-length representations in just two lines of code.

⁠Highlights

  • :telescope: State-of-the-art: build on pretrained 12/24-layer BERT models released by Google AI, which is considered as a milestone in the NLP community.
  • :hatching_chick: Easy-to-use: require only two lines of code to get sentence/token-level encodes.
  • :zap: Fast: 900 sentences/s on a single Tesla M40 24GB. Low latency, optimized for speed. See benchmark⁠.
  • :octopus: Scalable: scale nicely and smoothly on multiple GPUs and multiple clients without worrying about concurrency. See benchmark⁠.
  • :gem: Reliable: tested on multi-billion sentences; days of running without a break or OOM or any nasty exceptions.

More features: XLA & FP16 support⁠; mix GPU-CPU workloads; optimized graph; tf.data friendly; customized tokenizer; flexible pooling strategy; build-in HTTP server⁠ and dashboard; async encoding⁠; multicasting⁠; etc.

⁠Install

Install the server and client via pip. They can be installed separately or even on different machines:

pip install bert-serving-server  # server
pip install bert-serving-client  # client, independent of `bert-serving-server`

Note that the server MUST be running on Python >= 3.5 with Tensorflow >= 1.10 (one-point-ten). Again, the server does not support Python 2!

:point_up: The client can be running on both Python 2 and 3 for the following consideration⁠.

⁠Getting Started

⁠1. Download a Pre-trained BERT Model

Download a model listed below, then uncompress the zip file into some folder, say /tmp/english_L-12_H-768_A-12/

List of released pretrained BERT models (click to expand...)
BERT-Base, Uncased⁠12-layer, 768-hidden, 12-heads, 110M parameters
BERT-Large, Uncased⁠24-layer, 1024-hidden, 16-heads, 340M parameters
BERT-Base, Cased⁠12-layer, 768-hidden, 12-heads , 110M parameters
BERT-Large, Cased⁠24-layer, 1024-hidden, 16-heads, 340M parameters
BERT-Base, Multilingual Cased (New)⁠104 languages, 12-layer, 768-hidden, 12-heads, 110M parameters
BERT-Base, Multilingual Cased (Old)⁠102 languages, 12-layer, 768-hidden, 12-heads, 110M parameters
BERT-Base, Chinese⁠Chinese Simplified and Traditional, 12-layer, 768-hidden, 12-heads, 110M parameters

Optional: fine-tuning the model on your downstream task. Why is it optional?⁠

⁠2. Start the BERT service

After installing the server, you should be able to use bert-serving-start CLI as follows:

bert-serving-start -model_dir /tmp/english_L-12_H-768_A-12/ -num_worker=4 

This will start a service with four workers, meaning that it can handle up to four concurrent requests. More concurrent requests will be queued in a load balancer. Details can be found in our FAQ⁠ and the benchmark on number of clients⁠.

Below shows what the server looks like when starting correctly:

Alternatively, one can start the BERT Service in a Docker Container (click to expand...)
docker build -t bert-as-service -f ./docker/Dockerfile .
NUM_WORKER=1
PATH_MODEL=/PATH_TO/_YOUR_MODEL/
docker run --runtime nvidia -dit -p 5555:5555 -p 5556:5556 -v $PATH_MODEL:/model -t bert-as-service $NUM_WORKER
⁠3. Use Client to Get Sentence Encodes

Now you can encode sentences simply as follows:

from bert_serving.client import BertClient
bc = BertClient()
bc.encode(['First do it', 'then do it right', 'then do it better'])

It will return a ndarray (or List[List[float]] if you wish), in which each row is a fixed-length vector representing a sentence. Having thousands of sentences? Just encode! Don't even bother to batch, the server will take care of it.

As a feature of BERT, you may get encodes of a pair of sentences by concatenating them with ||| (with whitespace before and after), e.g.

bc.encode(['First do it ||| then do it right'])

Below shows what the server looks like while encoding:

⁠Use BERT Service Remotely

One may also start the service on one (GPU) machine and call it from another (CPU) machine as follows:

# on another CPU machine
from bert_serving.client import BertClient
bc = BertClient(ip='xx.xx.xx.xx')  # ip address of the GPU machine
bc.encode(['First do it', 'then do it right', 'then do it better'])

Note that you only need pip install -U bert-serving-client in this case, the server side is not required. You may also call the service via HTTP requests.⁠

⁠Server and Client API

▴ Back to top⁠

ReadTheDoc

The best way to learn bert-as-service latest API is reading the documentation⁠.

⁠Server API

Please always refer to the latest server-side API documented here.⁠, you may get the latest usage via:

bert-serving-start --help
bert-serving-terminate --help
bert-serving-benchmark --help
ArgumentTypeDefaultDescription
model_dirstrRequiredfolder path of the pre-trained BERT model.
tuned_model_dirstr(Optional)folder path of a fine-tuned BERT model.
ckpt_namestrbert_model.ckptfilename of the checkpoint file.
config_namestrbert_config.jsonfilename of the JSON config file for BERT model.
graph_tmp_dirstrNonepath to graph temp file
max_seq_lenint25maximum length of sequence, longer sequence will be trimmed on the right side. Set it to NONE for dynamically using the longest sequence in a (mini)batch.
cased_tokenizationboolFalseWhether tokenizer should skip the default lowercasing and accent removal. Should be used for e.g. the multilingual cased pretrained BERT model.
mask_cls_sepboolFalsemasking the embedding on [CLS] and [SEP] with zero.
num_workerint1number of (GPU/CPU) worker runs BERT model, each works in a separate process.
max_batch_sizeint256maximum number of sequences handled by each worker, larger batch will be partitioned into small batches.
priority_batch_sizeint16batch smaller than this size will be labeled as high priority, and jumps forward in the job queue to get result faster
portint5555port for pushing data from client to server
port_outint5556port for publishing results from server to client
http_portintNoneserver port for receiving HTTP requests
corsstr*setting "Access-Control-Allow-Origin" for HTTP requests
pooling_strategystrREDUCE_MEANthe pooling strategy for generating encoding vectors, valid values are NONE, REDUCE_MEAN, REDUCE_MAX, REDUCE_MEAN_MAX, CLS_TOKEN, FIRST_TOKEN, SEP_TOKEN, LAST_TOKEN. Explanation of these strategies can be found here⁠. To get encoding for each token in the sequence, please set this to NONE.
pooling_layerlist[-2]the encoding layer that pooling operates on, where -1 means the last layer, -2 means the second-to-last, [-1, -2] means concatenating the result of last two layers, etc.
gpu_memory_fractionfloat0.5the fraction of the overall amount of memory that each GPU should be allocated per worker
cpuboolFalserun on CPU instead of GPU
xlaboolFalseenable XLA compiler⁠ for graph optimization (experimental!)
fp16boolFalseuse float16 precision (experimental)
device_maplist[]specify the list of GPU device ids that will be used (id starts from 0)
show_tokens_to_clientboolFalsesending tokenization results to client
⁠Client API

Please always refer to the latest client-side API documented here.⁠ Client-side provides a Python class called BertClient, which accepts arguments as follows:

ArgumentTypeDefaultDescription
ipstrlocalhostIP address of the server
portint5555port for pushing data from client to server, must be consistent with the server side config
port_outint5556port for publishing results from server to client, must be consistent with the server side config
output_fmtstrndarraythe output format of the sentence encodes, either in numpy array or python List[List[float]] (ndarray/list)
show_server_configboolFalsewhether to show server configs when first connected
check_versionboolTruewhether to force client and server to have the same version
identitystrNonea UUID that identifies the client, useful in multi-casting
timeoutint-1set the timeout (milliseconds) for receive operation on the client

A BertClient implements the following methods and properties:

MethodDescription
.encode()Encode a list of strings to a list of vectors
.encode_async()Asynchronous encode batches from a generator
.fetch()Fetch all encoded vectors from server and return them in a generator, use it with .encode_async() or .encode(blocking=False). Sending order is NOT preserved.
.fetch_all()Fetch all encoded vectors from server and return them in a list, use it with .encode_async() or .encode(blocking=False). Sending order is preserved.
.close()Gracefully close the connection between the client and the server
.statusGet the client status in JSON format
.server_statusGet the server status in JSON format

⁠:book: Tutorial

▴ Back to top⁠

ReadTheDoc

The full list of examples can be found in example/⁠. You can run each via python example/example-k.py. Most of examples require you to start a BertServer first, please follow the instruction here⁠. Note that although BertClient works universally on both Python 2.x and 3.x, examples are only tested on Python 3.6.

Table of contents (click to expand...)
⁠Building a QA semantic search engine in 3 minutes

The complete example can be found example8.py⁠.

As the first example, we will implement a simple QA search engine using bert-as-service in just three minutes. No kidding! The goal is to find similar questions to user's input and return the corresponding answer. To start, we need a list of question-answer pairs. Fortunately, this README file already contains a list of FAQ⁠, so I will just use that to make this example perfectly self-contained. Let's first load all questions and show some statistics.

prefix_q = '##### **Q:** '
with open('README.md') as fp:
    questions = [v.replace(prefix_q, '').strip() for v in fp if v.strip() and v.startswith(prefix_q)]
    print('%d questions loaded, avg. len of %d' % (len(questions), np.mean([len(d.split()) for d in questions])))

This gives 33 questions loaded, avg. len of 9. So looks like we have enough questions. Now start a BertServer with uncased_L-12_H-768_A-12 pretrained BERT model:

bert-serving-start -num_worker=1 -model_dir=/data/cips/data/lab/data/model/uncased_L-12_H-768_A-12

Next, we need to encode our questions into vectors:

bc = BertClient(port=4000, port_out=4001)
doc_vecs = bc.encode(questions)

Finally, we are ready to receive new query and perform a simple "fuzzy" search against the existing questions. To do that, every time a new query is coming, we encode it as a vector and compute its dot product with doc_vecs; sort the result descendingly; and return the top-k similar questions as follows:

while True:
    query = input('your question: ')
    query_vec = bc.encode([query])[0]
    # compute normalized dot product as score
    score = np.sum(query_vec * doc_vecs, axis=1) / np.linalg.norm(doc_vecs, axis=1)
    topk_idx = np.argsort(score)[::-1][:topk]
    for idx in topk_idx:
        print('> %s\t%s' % (score[idx], questions[idx]))

That's it! Now run the code and type your query, see how this search engine handles fuzzy match:

⁠Serving a fine-tuned BERT model

Pretrained BERT models often show quite "okayish" performance on many tasks. However, to release the true power of BERT a fine-tuning on the downstream task (or on domain-specific data) is necessary. In this example, I will show you how to serve a fine-tuned BERT model.

We follow the instruction in "Sentence (and sentence-pair) classification tasks"⁠ and use run_classifier.py to fine tune uncased_L-12_H-768_A-12 model on MRPC task. The fine-tuned model is stored at /tmp/mrpc_output/, which can be changed by specifying --output_dir of run_classifier.py.

If you look into /tmp/mrpc_output/, it contains something like:

checkpoint                                        128
eval                                              4.0K
eval_results.txt                                  86
eval.tf_record                                    219K
events.out.tfevents.1545202214.TENCENT64.site     6.1M
events.out.tfevents.1545203242.TENCENT64.site     14M
graph.pbtxt                                       9.0M
model.ckpt-0.data-00000-of-00001                  1.3G
model.ckpt-0.index                                23K
model.ckpt-0.meta                                 3.9M
model.ckpt-343.data-00000-of-00001                1.3G
model.ckpt-343.index                              23K
model.ckpt-343.meta                               3.9M
train.tf_record                                   2.0M

Don't be afraid of those mysterious files, as the only important one to us is model.ckpt-343.data-00000-of-00001 (looks like my training stops at the 343 step. One may get model.ckpt-123.data-00000-of-00001 or model.ckpt-9876.data-00000-of-00001 depending on the total training steps). Now we have collected all three pieces of information that are needed for serving this fine-tuned model:

  • The pretrained model is downloaded to /path/to/bert/uncased_L-12_H-768_A-12
  • Our fine-tuned model is stored at /tmp/mrpc_output/;
  • Our fine-tuned model checkpoint is named as model.ckpt-343 something something.

Now start a BertServer by putting three pieces together:

bert-serving-start -model_dir=/pretrained/uncased_L-12_H-768_A-12 -tuned_model_dir=/tmp/mrpc_output/ -ckpt_name=model.ckpt-343

After the server started, you should find this line in the log:

I:GRAPHOPT:[gra:opt: 50]:checkpoint (override by fine-tuned model): /tmp/mrpc_output/model.ckpt-343

Which means the BERT parameters is overrode and successfully loaded from our fine-tuned /tmp/mrpc_output/model.ckpt-343. Done!

In short, find your fine-tuned model path and checkpoint name, then feed them to -tuned_model_dir and -ckpt_name, respectively.

⁠Getting ELMo-like contextual word embedding

Start the server with pooling_strategy set to NONE.

bert-serving-start -pooling_strategy NONE -model_dir /tmp/english_L-12_H-768_A-12/

To get the word embedding corresponds to every token, you can simply use slice index as follows:

# max_seq_len = 25
# pooling_strategy = NONE

bc = BertClient()
vec = bc.encode(['hey you', 'whats up?'])

vec  # [2, 25, 768]
vec[0]  # [1, 25, 768], sentence embeddings for `hey you`
vec[0][0]  # [1, 1, 768], word embedding for `[CLS]`
vec[0][1]  # [1, 1, 768], word embedding for `hey`
vec[0][2]  # [1, 1, 768], word embedding for `you`
vec[0][3]  # [1, 1, 768], word embedding for `[SEP]`
vec[0][4]  # [1, 1, 768], word embedding for padding symbol
vec[0][25]  # error, out of index!

Note that no matter how long your original sequence is, the service will always return a [max_seq_len, 768] matrix for every sequence. When using slice index to get the word embedding, beware of the special tokens padded to the sequence, i.e. [CLS], [SEP], 0_PAD.

⁠Using your own tokenizer

Often you want to use your own tokenizer to segment sentences instead of the default one from BERT. Simply call encode(is_tokenized=True) on the client slide as follows:

texts = ['hello world!', 'good day']

# a naive whitespace tokenizer
texts2 = [s.split() for s in texts]

vecs = bc.encode(texts2, is_tokenized=True)

This gives [2, 25, 768] tensor where the first [1, 25, 768] corresponds to the token-level encoding of "hello world!". If you look into its values, you will find that only the first four elements, i.e. [1, 0:3, 768] have values, all the others are zeros. This is due to the fact that BERT considers "hello world!" as four tokens: [CLS] hello world! [SEP], the rest are padding symbols and are masked out before output.

Note that there is no need to start a separate server for handling tokenized/untokenized sentences. The server can tell and handle both cases automatically.

Sometimes you want to know explicitly the tokenization performed on the server side

Tag summary

Content type

Image

Digest

Size

1.7 GB

Last updated

over 6 years ago

docker pull deliah/bert-as-service