OPTIMIZING BYTE-LEVEL REPRESENTATION FOR END-TO-END ASR, Apple 2024

Introduction

End-to-end (E2E) neural networks are flexible and accurate models for multilingual automatic speech recognition (ASR). The output of such a multilingual model is often unions of characters or subwords of the supported languages. However, as the number of languages increases, the size of the output layer increases, which can negatively affect compute, memory usage and asset size. This problem is more prominent when the system supports languages that have large character sets, such as Chinese, Japanese and Korean (CJK). To tackle this problem, previous work proposed the use of byte level representation for E2E ASR [1, 2]. By using UTF-8 [3] codewords as the underlying base tokens, the output vocabulary is no longer constrained by the character sets of each language, allowing developers to choose a vocabulary size based on compute, and memory constraints. One well-known multilingual ASR system that uses UTF-8 subwords is Whisper [4].

UTF-8 aims to represent all the characters used in major languages. The encoding and decoding processes are designed to be simple and efficient. UTF-8 is a variable length prefix code where each character is represented by one to four bytes. Most byte sequences are not valid UTF-8 strings, and the UTF-8 decoder needs to detect invalid sequences. UTF-8 also provides backward compatibility, where ASCII characters are represented by a single byte and they are the same as the ASCII encoding. While UTF-8 has proven to be an effective output representation for ASR, it is unclear whether it is optimal. For example, characters with similar pronunciations or meaning are not guaranteed to share the same prefixes. In addition, the large number of invalid byte sequences means the model needs to identify valid UTF-8 strings, an additional burden.

 

UTF-8 BASED REPRESENTATION

UTF-8 based models have been proposed for natural language processing (NLP) [5] [6] [7]. The idea is to convert text to a sequence of variable-length UTF-8 codewords, and to have the model predict one byte at each decoding step. The advantages of byte-level representation are compactness and universality, as any combination of languages may be represented with an output dimension of only 256. However, a sequence represented at byte level is often longer than its characterlevel counterpart, especially for CJK languages [8]. This is because while Latin characters are represented by a single byte, many CJK characters and accented characters are represented by multiple bytes. As a result, a byte-level model can be error-prone since it needs to make multiple predictions for many single characters, and each prediction might make a mistake.

To compensate for the drawback of making byte level mistakes, [1, 2] propose byte-level subwords for E2E ASR. The idea is to apply byte pair encoding (BPE) [9] to UTF-8 codeword sequences to create UTF-8 subwords. As subwords are in general longer than byte-level tokens, this approach reduces the number of steps required by the decoding process. However, BPE does not guarantee that the output will be a valid UTF-8 sequence. To repair an invalid byte sequence, [1] proposes a dynamic programming algorithm to recover as many characters as possible given any byte sequence. While this dynamic programming approach ensures the output sequence is always valid, it optimizes for the number of valid characters, not ASR quality.

 

Reference

[1] Bo Li, Yu Zhang, Tara Sainath, Yonghui Wu, and William Chan, “Bytes are all you need: End-to-end multilingual speech recognition and synthesis with bytes,” in Proceedings of the IEEE International Conference on Acoustics, Speech, and Signal Processing, 2019, pp. 5621–5625.

[2] L. Deng, R. Hsiao, and A. Ghoshal, “Bilingual endto-end ASR with byte-level subwords,” in Proceedings of the IEEE International Conference on Acoustics, Speech, and Signal Processing, 2022.

[8] Changhan Wang, Kyunghyun Cho, and Jiatao Gu, “Neural machine translation with byte-level subwords,” in Proceedings of the AAAI Conference on Artificial Intelligence, 2020, pp. 9154–9160.

[9] Rico Sennrich, Barry Haddow, and Alexandra Birch, “Neural machine translation of rare words with subword units,” in Proceedings of the Annual Meeting of the Association for Computational Linguistics, 2016, pp. 1715–1725.

"안녕, teacher 의성"이라는 문장을 UTF-8 인코딩byte-level BPE를 활용해 처리하는 과정을 설명해 볼게요.

1. UTF-8 인코딩 과정

UTF-8은 유니코드 문자를 바이트(byte)로 변환하는 방식입니다. 예를 들어, "안녕, teacher 의성" 문장을 UTF-8로 변환하면 각 글자가 바이트 시퀀스로 표현됩니다.

  • "안": EC 95 88
  • "녕": EB 85 95
  • ",": 2C (ASCII 문자)
  • " ": 20 (공백)
  • "t": 74
  • "e": 65
  • "a": 61
  • "c": 63
  • "h": 68
  • "e": 65
  • "r": 72
  • " ": 20 (공백)
  • "의": EC 9D 98
  • "성": EC 84 B1

이렇게 UTF-8 인코딩을 사용하면 각 문자와 기호가 바이트 시퀀스로 변환됩니다.

2. Byte-Level BPE 과정

Byte-Level BPE는 이 바이트 시퀀스를 이용해 자주 등장하는 바이트 쌍을 합치는 과정을 통해 토큰화를 수행합니다. 아래는 이를 통해 진행되는 과정입니다:

  1. 초기 토큰화:
    • 각 문자는 UTF-8 바이트로 변환된 상태에서 개별 바이트 단위로 나눠집니다.
    • 예를 들어, "안녕, teacher 의성"은 다음과 같이 표현됩니다.
      [EC, 95, 88, EB, 85, 95, 2C, 20, 74, 65, 61, 63, 68, 65, 72, 20, EC, 9D, 98, EC, 84, B1]
  2. 바이트 쌍 빈도 계산:
    • 각 인접한 바이트 쌍의 빈도를 계산합니다. 예를 들어, EC95, 9588 같은 인접한 쌍들의 빈도를 셉니다.
    • 예를 들어, EC 95가 가장 자주 나타나는 경우 이를 하나의 새로운 토큰으로 합칩니다.
  3. 쌍 합치기:
    • 가장 빈도가 높은 쌍을 합쳐 새로운 토큰을 만듭니다.
    • 예를 들어, EC 95가 합쳐지면, 이를 EC95라는 새로운 토큰으로 저장하고, 원래 시퀀스에서도 해당 부분을 대체합니다.
    • 이 과정을 반복하여 가장 자주 나타나는 쌍을 합쳐 나가면서 최종적으로 원하는 토큰 수에 도달할 때까지 계속 진행합니다.
  4. 최종 토큰화 결과:
    • 예를 들어, "안녕"과 같은 자주 등장하는 단어들은 하나의 토큰으로 합쳐질 수 있습니다.
    • Byte-Level BPE는 결국 자주 등장하는 바이트 쌍을 기준으로 텍스트를 효과적으로 압축한 토큰 리스트를 생성합니다.

요약

  • "안녕, teacher 의성" 문장은 UTF-8 인코딩을 통해 바이트 시퀀스로 변환됩니다.
  • Byte-Level BPE는 이 바이트 시퀀스를 사용해 자주 등장하는 바이트 쌍을 결합하여 토큰을 만듭니다.
  • 이 과정은 다양한 문자나 특수기호를 처리할 때 특히 유용하며, 언어 간 일관된 처리가 가능합니다.

EC 95 88UTF-8 인코딩 형식의 바이트 시퀀스를 의미합니다. 이 포맷은 각 유니코드 문자를 16진수로 표현한 바이트 시퀀스로 나타냅니다.

상세 설명

  • UTF-8은 유니코드 문자를 1바이트에서 최대 4바이트까지 가변 길이로 인코딩합니다.
  • EC 95 88은 16진수(헥사) 코드로, UTF-8에서 한글 문자 하나를 표현할 때 사용됩니다.
  • 예를 들어, EC 95 88은 유니코드 문자 U+C548에 해당하며, 이는 한글 "안"을 의미합니다.

해석 과정

  1. 유니코드 값 찾기:
    • UTF-8에서 EC 95 88이라는 16진수 코드를 유니코드 값으로 변환하면, 이는 U+C548입니다.
  2. 유니코드 맵핑:
    • U+C548은 유니코드에서 "안"이라는 한글 글자를 나타냅니다.
  3. UTF-8 바이트 시퀀스:
    • UTF-8은 유니코드 값 U+C548을 3바이트 시퀀스인 EC 95 88로 인코딩한 것입니다.

요약

  • EC 95 88UTF-8 바이트 시퀀스로, "안"이라는 한글을 표현합니다.
  • 이 포맷은 16진수(헥사) 표현이며, 한글과 같은 유니코드 문자를 UTF-8에서 인코딩할 때 사용됩니다.

256이라는 숫자는 바이트(byte)와 관련된 값으로, 8비트의 최대값을 의미합니다. UTF-8 인코딩과 바이트 기반 처리에서 256의 의미를 설명하면 다음과 같습니다:

1. 바이트와 256의 관계

  • 바이트(byte)는 8비트(bit)로 구성된 데이터 단위입니다.
  • 1바이트는 0부터 255까지의 값을 가질 수 있습니다.
    • 0에서 255까지 총 256개의 서로 다른 값을 표현할 수 있는 이유는, 8비트로 나타낼 수 있는 수의 조합이 2^8 = 256이기 때문입니다.

2. UTF-8과 바이트

  • UTF-8은 유니코드 문자를 1바이트에서 최대 4바이트로 인코딩합니다.
    • 예를 들어, 영어 알파벳이나 ASCII 문자들은 1바이트(0~127)로 표현됩니다.
    • 한글과 같은 유니코드 문자는 2바이트 이상이 필요하며, 이때 각 바이트는 0~255 사이의 값을 갖습니다.

3. Byte-Level BPE와 256

  • Byte-Level BPE에서 바이트 단위를 사용해 텍스트를 토큰화할 때, 각 바이트가 0~255 범위 내의 값을 가집니다.
  • 이를 통해 모든 가능한 바이트 조합을 다룰 수 있으며, 256개의 개별 바이트는 다양한 유니코드 문자의 조합을 표현할 수 있습니다.

요약

  • 256은 바이트가 표현할 수 있는 값의 개수를 나타내며, 0부터 255까지 총 256가지입니다.
  • UTF-8에서 각 바이트는 0~255 사이의 값을 갖고, 이를 통해 다양한 문자를 표현할 수 있습니다.
  • Byte-Level BPE에서는 이 256개의 바이트 값을 토큰화의 기본 단위로 사용하여, 유니코드 문자와 그 조합을 다루게 됩니다.

UTF-8의 이론적인 표현 가능 문자 수는 유니코드의 설계와 관련이 있습니다. 유니코드 자체는 전 세계의 모든 문자를 포함하기 위해 설계되었고, UTF-8은 이 유니코드 문자를 바이트 단위로 인코딩합니다. UTF-8이 이론적으로 표현할 수 있는 문자 수는 다음과 같이 계산할 수 있습니다:

1. UTF-8의 구조

UTF-8은 1바이트에서 4바이트까지 가변 길이 인코딩 방식을 사용합니다. 각 바이트는 특정한 비트 패턴을 통해 인코딩되며, 이 패턴은 유니코드 코드 포인트 범위에 따라 달라집니다.

  • 1바이트 (7비트 사용): 0x00~0x7F (ASCII와 동일, 128개)
  • 2바이트 (11비트 사용): 0x0080~0x07FF (약 2,048개)
  • 3바이트 (16비트 사용): 0x0800~0xFFFF (약 65,536개)
  • 4바이트 (21비트 사용): 0x010000~0x10FFFF (약 1,112,064개)

2. 유니코드 코드 포인트 범위

  • 유니코드의 코드 포인트U+0000에서 U+10FFFF까지 정의되어 있습니다.
  • 이 범위는 약 1,114,112개의 코드 포인트를 포함합니다. 그러나, 실제로 사용 가능한 문자 수는 이보다 적습니다.

3. 실제 표현 가능한 문자 수

  • 모든 코드 포인트가 문자를 나타내지는 않습니다. 일부 코드 포인트는 제어 문자특수 용도로 예약되어 있으며, 문자로 직접적으로 사용되지 않습니다.
  • 현재 유니코드 표준(유니코드 15.0 기준)에서는 약 149,186개의 문자가 정의되어 있습니다. 이 숫자는 유니코드의 다양한 언어 스크립트와 기호를 포함합니다.

4. 이론적인 UTF-8 표현 가능 수

  • 이론적으로 UTF-8은 0x0000에서 0x10FFFF까지의 유니코드 범위를 모두 표현할 수 있기 때문에, 약 1,114,112개의 코드 포인트를 지원합니다.
  • 그러나, 실제로 유니코드 표준에서 정의한 문자 수는 이보다 적고, 추가로 제어 문자와 기타 예약된 코드 포인트를 고려해야 합니다.

요약

  • 이론적으로 UTF-8은 약 1,114,112개의 코드 포인트를 표현할 수 있습니다.
  • 실제로 현재 유니코드에서 정의된 문자는 약 149,186개이며, 유니코드는 계속해서 확장되고 있습니다.
  • UTF-8은 유니코드의 모든 코드 포인트를 바이트 시퀀스로 인코딩할 수 있는 유연한 방식입니다.

0x0080과 같은 표현은 16진수(Hexadecimal)를 사용해 숫자를 나타내는 방식입니다. 이 표현은 컴퓨터 과학과 프로그래밍에서 많이 사용되며, 특히 메모리 주소, 유니코드 코드 포인트, 바이트 데이터 등을 다룰 때 유용합니다. 아래에 0x0080과 같은 표현에 대해 자세히 설명해 드릴게요.

1. 16진수(Hexadecimal) 표현

  • 16진수0~9A~F를 사용하는 숫자 체계입니다. 이는 기수 16의 체계로, 한 자리 수가 최대 16가지 값을 가질 수 있습니다.
  • 예를 들어:
    • 0x0 = 0 (십진수)
    • 0xA = 10 (십진수)
    • 0xF = 15 (십진수)
    • 0x10 = 16 (십진수)
  • 16진수 표현에서 앞에 붙는 0x는 이 값이 16진수임을 나타내는 표기입니다. 즉, 0x0080은 16진수 80을 의미하며, 십진수로는 128에 해당합니다.

2. 0x0080의 의미

  • 0x0080은 16진수로 표현된 숫자 128입니다.
    • 00 부분은 자리수를 맞추기 위한 것이며, 실제 값은 80입니다.
    • 이는 십진수로 변환하면 128이 됩니다.
  • 이 값은 컴퓨터 메모리에서 바이트를 표현하거나, 유니코드 코드 포인트, 색상 코드, 메모리 주소 등을 나타낼 때 자주 사용됩니다.

3. 유니코드와 16진수

  • 유니코드의 코드 포인트는 보통 16진수 형식으로 표현됩니다.
    • 예를 들어, 유니코드 문자 U+0080은 유니코드 표준에서 128번째 코드 포인트를 나타냅니다.
    • UTF-8 인코딩에서, 0x0080에 해당하는 문자는 여러 바이트로 표현될 수 있습니다.
  • 0x0080은 유니코드의 코드 포인트로 보면, ASCII 확장 영역에 해당합니다. ASCII는 0x0000부터 0x007F(0~127)까지 1바이트로 표현되며, 그 이후의 0x0080(128) 이상의 값들은 2바이트 이상을 사용해 표현됩니다.

4. 16진수 사용의 장점

  • 메모리 주소 표현: 메모리 주소와 바이트 데이터를 표현할 때 16진수를 사용하면, 메모리의 크기를 더 쉽게 계산하고 확인할 수 있습니다.
  • 컴퓨터 친화적: 컴퓨터 하드웨어는 이진수(0과 1)로 데이터를 처리하지만, 이를 사람이 읽기 쉽게 표현하기 위해 2진수16진수로 변환하여 사용합니다. 16진수는 4비트를 한 자리로 표현할 수 있어 간결합니다.
    • 예: 0x10 (16진수)은 0001 0000 (2진수)로 표현할 수 있습니다.

요약

  • 0x008016진수 표기법으로, 십진수로는 128에 해당합니다.
  • 16진수 표기는 메모리 주소, 바이트 값, 유니코드 코드 포인트 등을 다룰 때 유용하며, 컴퓨터 과학에서 자주 사용됩니다.
  • 유니코드에서 0x0080은 ASCII 확장 영역에 해당하며, 이를 UTF-8 인코딩으로 표현할 때는 여러 바이트로 나타낼 수 있습니다.

2진수(바이너리)를 16진수(헥사)로 변환하는 방법은 비교적 간단합니다. 2진수01의 조합으로 구성되고, 16진수0-9와 A-F의 조합으로 이루어져 있습니다. 변환 방법을 설명하고 예시를 통해 보여드리겠습니다.

1. 2진수에서 16진수로 변환하는 방법

  • 2진수를 16진수로 변환하려면 4비트씩 묶어서 각 묶음을 16진수로 변환하면 됩니다.
  • 16진수는 4비트로 표현할 수 있기 때문에, 2진수의 비트들을 4비트 그룹으로 나누면 쉽게 변환할 수 있습니다.

2. 변환 과정

  1. 4비트씩 그룹화:
    • 2진수를 오른쪽부터 4비트씩 묶습니다. 만약 비트 수가 4의 배수가 아니라면 왼쪽에 0을 추가해서 4비트로 맞춥니다.
  2. 각 4비트를 16진수로 변환:
    • 각 4비트 그룹은 다음과 같이 16진수로 변환할 수 있습니다:
2진수 16진수
0000 0
0001 1
0010 2
0011 3
0100 4
0101 5
0110 6
0111 7
1000 8
1001 9
1010 A
1011 B
1100 C
1101 D
1110 E
1111 F
  1. 각 그룹을 대응되는 16진수로 변환하여 결합합니다.

3. 예시

예시 1: 2진수 11011011을 16진수로 변환

  1. 2진수를 4비트씩 그룹화합니다:
    1101 1011
  2. 각 그룹을 16진수로 변환합니다:
    • 1101D
    • 1011B
  3. 따라서, 11011011의 16진수 표현은 0xDB입니다.

예시 2: 2진수 101010을 16진수로 변환

  1. 2진수를 4비트씩 그룹화합니다. 이 경우 6비트이므로, 앞에 00을 추가해 4의 배수로 맞춥니다:
    0010 1010
  2. 각 그룹을 16진수로 변환합니다:
    • 00102
    • 1010A
  3. 따라서, 101010의 16진수 표현은 0x2A입니다.

4. 요약

  • 2진수에서 16진수로 변환하려면 4비트씩 묶어서 각 묶음을 변환하면 됩니다.
  • 4비트는 16진수의 한 자리수에 해당하므로, 이를 사용해 효율적으로 변환할 수 있습니다.
  • 예시: 110110110xDB, 1010100x2A.

이 과정을 통해 2진수와 16진수 간의 변환을 쉽게 수행할 수 있습니다.

+ Recent posts