TF 텍스트를 사용한 BERT 전처리

TensorFlow.org에서 보기 Google Colab에서 실행 GitHub에서 보기 노트북 다운로드

개요

텍스트 전처리는 원시 텍스트를 모델의 정수 입력으로 종단 간 변환하는 것입니다. NLP 모델에는 종종 텍스트를 전처리하기 위해 수백(수천은 아닐지라도) 라인의 Python 코드가 수반됩니다. 다음과 같은 이유로 텍스트 사전 처리는 종종 모델에 대한 도전 과제입니다.

  • 훈련 서빙 스큐. 모델 입력의 사전 처리 논리가 모델 개발의 모든 단계(예: 사전 훈련, 미세 조정, 평가, 추론)에서 일관성을 유지하도록 하는 것이 점점 더 어려워지고 있습니다. 다른 초매개변수, 토큰화, 문자열 전처리 알고리즘을 사용하거나 단순히 다른 단계에서 모델 입력을 일관되지 않게 패키징하면 디버그하기 어렵고 모델에 치명적인 영향을 줄 수 있습니다.

  • 효율성과 유연성. 전처리는 오프라인으로 수행할 수 있지만(예: 처리된 출력을 디스크의 파일에 쓴 다음 입력 파이프라인에서 전처리된 데이터를 다시 사용하여) 이 방법은 추가 파일 읽기 및 쓰기 비용을 발생시킵니다. 동적으로 발생해야 하는 전처리 결정이 있는 경우 오프라인 전처리도 불편합니다. 다른 옵션을 실험하려면 데이터 세트를 다시 생성해야 합니다.

  • 복잡한 모델 인터페이스. 텍스트 모델은 입력이 순수한 텍스트일 때 훨씬 더 이해하기 쉽습니다. 입력에 추가 간접 인코딩 단계가 필요한 경우 모델을 이해하기 어렵습니다. 전처리 복잡성을 줄이는 것은 모델 디버깅, 제공 및 평가에서 특히 높이 평가됩니다.

또한 더 간단한 모델 인터페이스를 사용하면 탐색되지 않은 다른 데이터 세트에서 모델(예: 추론 또는 교육)을 보다 편리하게 시도할 수 있습니다.

TF.Text를 사용한 텍스트 전처리

TF.Text의 텍스트 전처리 API를 사용하여 사용자의 텍스트 데이터세트를 모델의 정수 입력으로 변환할 수 있는 전처리 기능을 구성할 수 있습니다. 사용자는 앞서 언급한 문제를 완화하기 위해 모델의 일부로 전처리를 직접 패키징할 수 있습니다.

이 튜토리얼은 "가면 LM 및 마스킹 절차"의에 설명 된 BERT 모델과 작업을 pretraining 마스킹 언어에 대한 입력에 대한 입력으로 텍스트 데이터를 변환하는 TF.Text 전처리 작전을 사용하는 방법을 보여줍니다 언어에 대한 깊은 양방향 변압기의 사전 교육 : BERT 이해 . 이 프로세스에는 텍스트를 하위 단어 단위로 토큰화하고, 문장을 결합하고, 콘텐츠를 고정 크기로 자르고, 마스킹된 언어 모델링 작업을 위한 레이블을 추출하는 작업이 포함됩니다.

설정

먼저 필요한 패키지와 라이브러리를 가져오겠습니다.

pip install -q -U tensorflow-text
import tensorflow as tf
import tensorflow_text as text
import functools

우리의 데이터는 두 개의 텍스트 기능을 포함하고 있으며 우리는 예를 생성 할 수 있습니다 tf.data.Dataset . 우리의 목표는 우리가 제공 할 수있는 기능을 만드는 것입니다 Dataset.map() 교육에 사용하기로합니다.

examples = {
    "text_a": [
      b"Sponge bob Squarepants is an Avenger",
      b"Marvel Avengers"
    ],
    "text_b": [
     b"Barack Obama is the President.",
     b"President is the highest office"
  ],
}

dataset = tf.data.Dataset.from_tensor_slices(examples)
next(iter(dataset))
{'text_a': <tf.Tensor: shape=(), dtype=string, numpy=b'Sponge bob Squarepants is an Avenger'>,
 'text_b': <tf.Tensor: shape=(), dtype=string, numpy=b'Barack Obama is the President.'>}

토큰화

첫 번째 단계는 문자열 전처리를 실행하고 데이터 세트를 토큰화하는 것입니다. 이는 사용하여 수행 할 수 text.BertTokenizer A는, text.Splitter 위한 subwords wordpieces 또는 문장으로 토큰 화 수 BERT 모델 로부터 생성 된 어휘 주어진 Wordpiece 알고리즘 . 당신은에서 TF.Text에서 사용할 수있는 다른 subword의 tokenizers에 대해 자세히 배울 수 여기를 .

어휘는 이전에 생성된 BERT 체크포인트에서 가져오거나 자신의 데이터에서 직접 생성할 수 있습니다. 이 예의 목적을 위해 장난감 어휘를 만들어 보겠습니다.

_VOCAB = [
    # Special tokens
    b"[UNK]", b"[MASK]", b"[RANDOM]", b"[CLS]", b"[SEP]",
    # Suffixes
    b"##ack", b"##ama", b"##ger", b"##gers", b"##onge", b"##pants",  b"##uare",
    b"##vel", b"##ven", b"an", b"A", b"Bar", b"Hates", b"Mar", b"Ob",
    b"Patrick", b"President", b"Sp", b"Sq", b"bob", b"box", b"has", b"highest",
    b"is", b"office", b"the",
]

_START_TOKEN = _VOCAB.index(b"[CLS]")
_END_TOKEN = _VOCAB.index(b"[SEP]")
_MASK_TOKEN = _VOCAB.index(b"[MASK]")
_RANDOM_TOKEN = _VOCAB.index(b"[RANDOM]")
_UNK_TOKEN = _VOCAB.index(b"[UNK]")
_MAX_SEQ_LEN = 8
_MAX_PREDICTIONS_PER_BATCH = 5

_VOCAB_SIZE = len(_VOCAB)

lookup_table = tf.lookup.StaticVocabularyTable(
    tf.lookup.KeyValueTensorInitializer(
      keys=_VOCAB,
      key_dtype=tf.string,
      values=tf.range(
          tf.size(_VOCAB, out_type=tf.int64), dtype=tf.int64),
      value_dtype=tf.int64),
      num_oov_buckets=1
)

하자의 구조 text.BertTokenizer 위의 어휘를 사용하고에 텍스트 입력 토큰 화 RaggedTensor .`.

bert_tokenizer = text.BertTokenizer(lookup_table, token_out_type=tf.string)
bert_tokenizer.tokenize(examples["text_a"])
<tf.RaggedTensor [[[b'Sp', b'##onge'], [b'bob'], [b'Sq', b'##uare', b'##pants'], [b'is'], [b'an'], [b'A', b'##ven', b'##ger']], [[b'Mar', b'##vel'], [b'A', b'##ven', b'##gers']]]>
bert_tokenizer.tokenize(examples["text_b"])
<tf.RaggedTensor [[[b'Bar', b'##ack'], [b'Ob', b'##ama'], [b'is'], [b'the'], [b'President'], [b'[UNK]']], [[b'President'], [b'is'], [b'the'], [b'highest'], [b'office']]]>

에서 텍스트 출력 text.BertTokenizer 우리가 텍스트를 토큰 화되고 있는지 볼 수 있지만이 모델은 정수 ID를 필요로한다. 우리는 설정할 수 token_out_type 으로 PARAM을 tf.int64 (어휘로 인덱스이다) 정수 ID를 얻는다.

bert_tokenizer = text.BertTokenizer(lookup_table, token_out_type=tf.int64)
segment_a = bert_tokenizer.tokenize(examples["text_a"])
segment_a
<tf.RaggedTensor [[[22, 9], [24], [23, 11, 10], [28], [14], [15, 13, 7]], [[18, 12], [15, 13, 8]]]>
segment_b = bert_tokenizer.tokenize(examples["text_b"])
segment_b
<tf.RaggedTensor [[[16, 5], [19, 6], [28], [30], [21], [0]], [[21], [28], [30], [27], [29]]]>

text.BertTokenizer 반환 RaggedTensor 형상 [batch, num_tokens, num_wordpieces] . 우리가 추가로 필요하지 않기 때문에 num_tokens 현재 사용 사례에 대한 치수를, 우리는 얻기 위해 마지막 두 차원을 병합 할 수 있습니다 RaggedTensor 모양 [batch, num_wordpieces] :

segment_a = segment_a.merge_dims(-2, -1)
segment_a
<tf.RaggedTensor [[22, 9, 24, 23, 11, 10, 28, 14, 15, 13, 7], [18, 12, 15, 13, 8]]>
segment_b = segment_b.merge_dims(-2, -1)
segment_b
<tf.RaggedTensor [[16, 5, 19, 6, 28, 30, 21, 0], [21, 28, 30, 27, 29]]>

콘텐츠 트리밍

BERT의 주요 입력은 두 문장의 연결입니다. 그러나 BERT는 입력이 고정된 크기와 모양이어야 하며 예산을 초과하는 콘텐츠가 있을 수 있습니다.

우리는 사용하여이 문제를 해결할 수 text.Trimmer (마지막 축을 따라 연결된 후) 소정의 크기로 콘텐츠 아래로 트림 할 수 있습니다. 다른있다 text.Trimmer 서로 다른 알고리즘을 사용하여 보존 할 콘텐츠를 선택 유형. text.RoundRobinTrimmer 예는 각 세그먼트에 대해 균등 용량 할당되지만 문장의 단부를 트리밍 할 수있다. text.WaterfallTrimmer 마지막 문장의 끝에서 시작 트림됩니다.

이 예를 들어, 우리가 사용 RoundRobinTrimmer 선택 항목 왼쪽에서 오른쪽으로의 각 세그먼트에서.

trimmer = text.RoundRobinTrimmer(max_seq_length=[_MAX_SEQ_LEN])
trimmed = trimmer.trim([segment_a, segment_b])
trimmed
[<tf.RaggedTensor [[22, 9, 24, 23], [18, 12, 15, 13]]>,
 <tf.RaggedTensor [[16, 5, 19, 6], [21, 28, 30, 27]]>]

trimmed 일괄 걸쳐 소자의 수가 8 개 요소 (축선 = -1 함께 연결될 때)이고 현재의 세그먼트를 포함한다.

세그먼트 결합

이제 우리는 세그먼트 손질 것을, 우리는 하나 얻기 위해 그들을 함께 결합 할 수 있습니다 RaggedTensor . BERT는 시작 (나타내는 특수 토큰을 사용하여 [CLS] 세그먼트 (중)과 단부 [SEP] ). 우리는 또한 필요 RaggedTensor 결합에있는 항목을 나타내는 Tensor 하는 세그먼트에 속하는를. 우리는 사용할 수 text.combine_segments() 이 두 얻기 위해 Tensor 삽입 특수 토큰.

segments_combined, segments_ids = text.combine_segments(
  [segment_a, segment_b],
  start_of_sequence_id=_START_TOKEN, end_of_segment_id=_END_TOKEN)
segments_combined, segments_ids
(<tf.RaggedTensor [[3, 22, 9, 24, 23, 11, 10, 28, 14, 15, 13, 7, 4, 16, 5, 19, 6, 28, 30, 21, 0, 4], [3, 18, 12, 15, 13, 8, 4, 21, 28, 30, 27, 29, 4]]>,
 <tf.RaggedTensor [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1]]>)

마스크 언어 모델 작업

이제 우리는 우리의 기본 입력을 가지고, 우리는 "마스크 LM 및 마스킹 절차"에 필요한 입력을 압축 해제 할 수 있습니다 작업에 설명 BERT : 언어 이해에 대한 깊은 양방향 변압기의 사전 교육

마스킹된 언어 모델 작업에는 (1) 마스킹을 위해 선택할 항목과 (2) 할당된 값의 두 가지 하위 문제가 있습니다.

아이템 선택

우리는 무작위로 마스킹을위한 항목을 선택할 것이기 때문에, 우리는 사용 text.RandomItemSelector . RandomItemSelector 임의로 주어진 제한 (에 배치 될 수있는 상품 선택 max_selections_per_batch , selection_rateunselectable_ids ) 반환 항목이 선택되었던 나타내는 부울 마스크.

random_selector = text.RandomItemSelector(
    max_selections_per_batch=_MAX_PREDICTIONS_PER_BATCH,
    selection_rate=0.2,
    unselectable_ids=[_START_TOKEN, _END_TOKEN, _UNK_TOKEN]
)
selected = random_selector.get_selection_mask(
    segments_combined, axis=1)
selected
<tf.RaggedTensor [[False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, True, True, True, False, False], [False, False, False, False, False, True, False, False, False, False, False, True, False]]>

마스킹된 값 선택

마스킹 값을 선택하기 위한 원래 BERT 문서에 설명된 방법론은 다음과 같습니다.

들어 mask_token_rate 시간의에 항목을 교체 [MASK] 토큰 :

"my dog is hairy" -> "my dog is [MASK]"

들어 random_token_rate 의 시간, 임의의 단어로 항목을 대체 :

"my dog is hairy" -> "my dog is apple"

들어 1 - mask_token_rate - random_token_rate 시간의 변경되지 않은 항목을 유지 :

"my dog is hairy" -> "my dog is hairy."

text.MaskedValuesChooser 이 논리를 캡슐화하고 우리의 전처리 기능을 사용할 수 있습니다. 여기에 무엇의 예 MaskValuesChooser 반환 주어진 mask_token_rate 80 %는 기본적 random_token_rate :

input_ids = tf.ragged.constant([[19, 7, 21, 20, 9, 8], [13, 4, 16, 5], [15, 10, 12, 11, 6]])
mask_values_chooser = text.MaskValuesChooser(_VOCAB_SIZE, _MASK_TOKEN, 0.8)
mask_values_chooser.get_mask_values(input_ids)
<tf.RaggedTensor [[1, 1, 1, 1, 1, 1], [1, 1, 1, 1], [1, 10, 1, 1, 6]]>

공급 받아 RaggedTensor 입력 text.MaskValuesChooser 반환 RaggedTensor 하나와 동일한 형상의 _MASK_VALUE (0), 랜덤 ID, 또는 동일한 ID를 변경.

마스킹된 언어 모델 작업에 대한 입력 생성

지금 우리가 가지고 RandomItemSelector 우리가 마스킹에 대한 항목을 선택하는 데 도움 text.MaskValuesChooser 값을 할당하기를, 우리가 사용할 수 있습니다 text.mask_language_model() 우리 BERT 모델이 작업의 모든 입력을 조립.

masked_token_ids, masked_pos, masked_lm_ids = text.mask_language_model(
  segments_combined,
  item_selector=random_selector, mask_values_chooser=mask_values_chooser)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/util/dispatch.py:206: batch_gather (from tensorflow.python.ops.array_ops) is deprecated and will be removed after 2017-10-25.
Instructions for updating:
`tf.batch_gather` is deprecated, please use `tf.gather` with `batch_dims=-1` instead.

하자 다이빙 깊고의 출력 검사 mask_language_model() . 출력 masked_token_ids 있다 :

masked_token_ids
<tf.RaggedTensor [[3, 22, 1, 24, 23, 1, 10, 28, 1, 15, 1, 7, 4, 16, 5, 19, 6, 28, 30, 21, 0, 4], [3, 18, 12, 15, 13, 1, 4, 21, 28, 30, 27, 1, 4]]>

우리의 입력은 어휘를 사용하여 인코딩된다는 것을 기억하십시오. 우리가 디코딩 경우 masked_token_ids 우리의 어휘를 사용하여, 우리는 얻을 :

tf.gather(_VOCAB, masked_token_ids)
<tf.RaggedTensor [[b'[CLS]', b'Sp', b'[MASK]', b'bob', b'Sq', b'[MASK]', b'##pants', b'is', b'[MASK]', b'A', b'[MASK]', b'##ger', b'[SEP]', b'Bar', b'##ack', b'Ob', b'##ama', b'is', b'the', b'President', b'[UNK]', b'[SEP]'], [b'[CLS]', b'Mar', b'##vel', b'A', b'##ven', b'[MASK]', b'[SEP]', b'President', b'is', b'the', b'highest', b'[MASK]', b'[SEP]']]>

일부 wordpiece 토큰 중 치환 된 알 [MASK] , [RANDOM] 또는 다른 ID 값. masked_pos 출력은 우리 교환 된 토큰 (각 일괄 처리)의 인덱스를 제공한다.

masked_pos
<tf.RaggedTensor [[2, 5, 8, 10], [5, 11]]>

masked_lm_ids 우리에게 토큰의 원래 값을 제공합니다.

masked_lm_ids
<tf.RaggedTensor [[9, 11, 14, 13], [8, 29]]>

여기서 ID를 다시 디코딩하여 사람이 읽을 수 있는 값을 얻을 수 있습니다.

tf.gather(_VOCAB, masked_lm_ids)
<tf.RaggedTensor [[b'##onge', b'##uare', b'an', b'##ven'], [b'##gers', b'office']]>

모델 입력 채우기

이제 우리의 모델에 대한 모든 입력이 있는지 우리 전처리의 마지막 단계는 고정 된 2 차원으로 패키지화하는 Tensor 의 패딩도 마스크 생성 Tensor 패드 값이되는 값을 표시한다. 우리는 사용할 수 text.pad_model_inputs() 이 작업에 우리를 도와.

# Prepare and pad combined segment inputs
input_word_ids, input_mask = text.pad_model_inputs(
  masked_token_ids, max_seq_length=_MAX_SEQ_LEN)
input_type_ids, _ = text.pad_model_inputs(
  masked_token_ids, max_seq_length=_MAX_SEQ_LEN)

# Prepare and pad masking task inputs
masked_lm_positions, masked_lm_weights = text.pad_model_inputs(
  masked_token_ids, max_seq_length=_MAX_PREDICTIONS_PER_BATCH)
masked_lm_ids, _ = text.pad_model_inputs(
  masked_lm_ids, max_seq_length=_MAX_PREDICTIONS_PER_BATCH)

model_inputs = {
    "input_word_ids": input_word_ids,
    "input_mask": input_mask,
    "input_type_ids": input_type_ids,
    "masked_lm_ids": masked_lm_ids,
    "masked_lm_positions": masked_lm_positions,
    "masked_lm_weights": masked_lm_weights,
}
model_inputs
{'input_word_ids': <tf.Tensor: shape=(2, 8), dtype=int64, numpy=
 array([[ 3, 22,  1, 24, 23,  1, 10, 28],
        [ 3, 18, 12, 15, 13,  1,  4, 21]])>,
 'input_mask': <tf.Tensor: shape=(2, 8), dtype=int64, numpy=
 array([[1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1]])>,
 'input_type_ids': <tf.Tensor: shape=(2, 8), dtype=int64, numpy=
 array([[ 3, 22,  1, 24, 23,  1, 10, 28],
        [ 3, 18, 12, 15, 13,  1,  4, 21]])>,
 'masked_lm_ids': <tf.Tensor: shape=(2, 5), dtype=int64, numpy=
 array([[ 9, 11, 14, 13,  0],
        [ 8, 29,  0,  0,  0]])>,
 'masked_lm_positions': <tf.Tensor: shape=(2, 5), dtype=int64, numpy=
 array([[ 3, 22,  1, 24, 23],
        [ 3, 18, 12, 15, 13]])>,
 'masked_lm_weights': <tf.Tensor: shape=(2, 5), dtype=int64, numpy=
 array([[1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1]])>}

검토

지금까지 우리가 가진 것을 검토하고 전처리 기능을 조립합시다. 여기 우리가 가진 것이 있습니다:

def bert_pretrain_preprocess(vocab_table, features):
  # Input is a string Tensor of documents, shape [batch, 1].
  text_a = features["text_a"]
  text_b = features["text_b"]

  # Tokenize segments to shape [num_sentences, (num_words)] each.
  tokenizer = text.BertTokenizer(
      vocab_table,
      token_out_type=tf.int64)
  segments = [tokenizer.tokenize(text).merge_dims(
      1, -1) for text in (text_a, text_b)]

  # Truncate inputs to a maximum length.
  trimmer = text.RoundRobinTrimmer(max_seq_length=6)
  trimmed_segments = trimmer.trim(segments)

  # Combine segments, get segment ids and add special tokens.
  segments_combined, segment_ids = text.combine_segments(
      trimmed_segments,
      start_of_sequence_id=_START_TOKEN,
      end_of_segment_id=_END_TOKEN)

  # Apply dynamic masking task.
  masked_input_ids, masked_lm_positions, masked_lm_ids = (
      text.mask_language_model(
        segments_combined,
        random_selector,
        mask_values_chooser,
      )
  )

  # Prepare and pad combined segment inputs
  input_word_ids, input_mask = text.pad_model_inputs(
    masked_input_ids, max_seq_length=_MAX_SEQ_LEN)
  input_type_ids, _ = text.pad_model_inputs(
    masked_input_ids, max_seq_length=_MAX_SEQ_LEN)

  # Prepare and pad masking task inputs
  masked_lm_positions, masked_lm_weights = text.pad_model_inputs(
    masked_input_ids, max_seq_length=_MAX_PREDICTIONS_PER_BATCH)
  masked_lm_ids, _ = text.pad_model_inputs(
    masked_lm_ids, max_seq_length=_MAX_PREDICTIONS_PER_BATCH)

  model_inputs = {
      "input_word_ids": input_word_ids,
      "input_mask": input_mask,
      "input_type_ids": input_type_ids,
      "masked_lm_ids": masked_lm_ids,
      "masked_lm_positions": masked_lm_positions,
      "masked_lm_weights": masked_lm_weights,
  }
  return model_inputs

우리는 이전에 건설 tf.data.Dataset 우리는 지금 우리의 조립 전처리 기능을 사용할 수 있습니다 bert_pretrain_preprocess()Dataset.map() . 이를 통해 원시 문자열 데이터를 정수 입력으로 변환하고 모델에 직접 공급하기 위한 입력 파이프라인을 생성할 수 있습니다.

dataset = tf.data.Dataset.from_tensors(examples)
dataset = dataset.map(functools.partial(
    bert_pretrain_preprocess, lookup_table))

next(iter(dataset))
{'input_word_ids': <tf.Tensor: shape=(2, 8), dtype=int64, numpy=
 array([[ 3, 22,  9,  1,  4, 16,  5, 19],
        [ 3, 18,  1, 15,  4,  1, 28, 30]])>,
 'input_mask': <tf.Tensor: shape=(2, 8), dtype=int64, numpy=
 array([[1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1]])>,
 'input_type_ids': <tf.Tensor: shape=(2, 8), dtype=int64, numpy=
 array([[ 3, 22,  9,  1,  4, 16,  5, 19],
        [ 3, 18,  1, 15,  4,  1, 28, 30]])>,
 'masked_lm_ids': <tf.Tensor: shape=(2, 5), dtype=int64, numpy=
 array([[24, 19,  0,  0,  0],
        [12, 21,  0,  0,  0]])>,
 'masked_lm_positions': <tf.Tensor: shape=(2, 5), dtype=int64, numpy=
 array([[ 3, 22,  9,  1,  4],
        [ 3, 18,  1, 15,  4]])>,
 'masked_lm_weights': <tf.Tensor: shape=(2, 5), dtype=int64, numpy=
 array([[1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1]])>}
  • BERT로 분류 텍스트 - 분류 텍스트에 pretrained BERT 모델을 사용하는 방법에 대한 튜토리얼. 이것은 BERT 모델에서 사용하는 입력을 사전 처리하는 방법에 익숙해졌기 때문에 좋은 후속 조치입니다.

  • TF 텍스트와 토큰 화 - 자습서 TF.Text에 존재 tokenizers의 다른 유형을 자세히 설명.

  • 와 텍스트를 처리 RaggedTensor - 사용을 만들고 조작하는 방법에 대한 자세한 안내 RaggedTensor 들.