PyTorch로 구현하는 Tacotron2 - Encoder 텍스트를 음성으로 변환하는 첫 단계
이전 글에서 우리는 Tacotron2 모델을 위한 데이터셋 처리 과정에 대해 살펴보았습니다. 이제 데이터를 준비했으니, 모델의 인코더(Encoder) 부분을 깊이 있게 다루어 보겠습니다. Tacotron2는 텍스트 데이터를 음성으로 변환하는 모델로, 이 과정에서 인코더는 매우 중요한 역할을 담당합니다. 인코더는 입력된 텍스트를 숨겨진 특징(hidden features)으로 변환하여 디코더가 이를 음성 스펙트로그램으로 변환할 수 있게 해줍니다.
Tacotron2의 Encoder 구현코드 원본 살펴보러 가기 [링크]
1. Tacotron2 인코더 개요
Tacotron2의 인코더는 문자 시퀀스를 숨겨진 특징 벡터로 변환하는 모듈입니다. 인코더는 텍스트를 처리하고, 이를 디코더에서 사용할 수 있는 형태로 변환하는 데 필요한 모든 기능을 갖추고 있습니다. 인코더는 세 가지 주요 구성 요소로 나눌 수 있습니다:
- 문자 임베딩: 텍스트 데이터를 숫자로 표현하여 모델이 처리할 수 있는 형태로 변환합니다.
- 컨볼루션 층: 여러 개의 1D 컨볼루션 필터를 사용하여 텍스트에서 더 긴 맥락을 학습합니다.
- 양방향 LSTM: 마지막으로 컨볼루션 층의 출력을 받아 시퀀스의 양방향(앞에서 뒤, 뒤에서 앞) 정보를 모두 학습합니다.
이 과정을 통해 인코더는 텍스트에서 중요한 정보를 추출하여 디코더가 사용할 수 있는 특징 벡터를 생성합니다.
2. 문자 임베딩 (Character Embedding)
텍스트 데이터를 모델이 처리할 수 있는 숫자 벡터로 변환하는 것이 문자 임베딩(Character Embedding)입니다. 문자 임베딩은 각 문자를 고정된 차원의 벡터로 매핑하여, 텍스트 데이터를 숫자로 표현하게 합니다. Tacotron2에서는 이를 통해 각 문자가 고유한 벡터로 변환되며, 인코더는 이 벡터를 바탕으로 특징을 학습합니다.
음소 시퀀스에 대해 padding을 더해주고 각 시퀀스의 값 하나하나를 256차원으로 확장하면 아래와 같은 3차원 텐서의 모습을 띄게 됩니다. 아래의 그림에서는 간결성을 위해 14차원을 모두 표현하진 않고, 시퀀스의 최대 길이를 8로 재설정하였습니다.
1
2
3
4
5
# __init__ 내에서 선언
self.embedding = nn.Embedding(n_symbol, symbol_embedding_dim)
# forward 내에서 선언 - Convolution 연산을 위해 미리 transpose !!
embedded_inputs = self.embedding(tokens).transpose(1, 2)
nn.Embedding
: PyTorch에서 제공하는 임베딩 레이어로, 각 문자를 고정된 차원의 벡터로 변환합니다.n_symbol
: 사용할 문자(심볼)의 개수. 이는 모델이 처리할 수 있는 총 문자 수를 의미합니다.symbol_embedding_dim
: 각 문자를 매핑할 벡터의 차원입니다. Tacotron2에서는 이 임베딩 차원이 512로 설정되어 있습니다. 즉, 각 문자는 512차원 벡터로 변환됩니다.
tokens
: 입력 문자를 토큰으로 변환한 것입니다. 즉, 각 문자는 고유한 숫자(토큰)로 매핑되며, 이 숫자는 임베딩 레이어를 통해 벡터로 변환됩니다.transpose(1, 2)
: 임베딩된 입력 벡터의 차원을 변경하여 [배치 크기, 시퀀스 길이, 임베딩 차원] 형태로 맞춥니다. 이는 이후에 컨볼루션 층에서 처리하기 위한 형식으로 변환하는 과정입니다.
Tacotron2의 문자 임베딩은 문자 데이터를 벡터로 표현하여, 텍스트의 의미를 벡터 공간에서 표현할 수 있게 합니다. 이 벡터 표현은 인코더의 다음 처리 단계에서 중요한 역할을 하며, 모델이 텍스트 정보를 효과적으로 학습할 수 있도록 돕습니다.
3. 인코더 구성 요소 살펴보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class _Encoder(nn.Module):
"""
인코더는 문자 시퀀스를 숨겨진 특징 표현으로 변환한다.
"""
def __init__(
self,
encoder_embedding_dim: int,
encoder_n_convolution: int,
encoder_kernel_size: int,
) -> None:
super().__init__()
self.convolutions = nn.ModuleList()
for _ in range(encoder_n_convolution):
conv_layer = nn.Sequential(
_get_conv1d_layer(
encoder_embedding_dim,
encoder_embedding_dim,
kernel_size=encoder_kernel_size,
stride=1,
padding=int((encoder_kernel_size - 1) / 2),
dilation=1,
w_init_gain="relu",
),
nn.BatchNorm1d(encoder_embedding_dim),
)
self.convolutions.append(conv_layer)
self.lstm = nn.LSTM(
encoder_embedding_dim,
int(encoder_embedding_dim / 2),
1,
batch_first=True,
bidirectional=True,
)
self.lstm.flatten_parameters()
def forward(self, x: Tensor, input_lengths: Tensor) -> Tensor:
for conv in self.convolutions:
x = F.dropout(F.relu(conv(x)), 0.5, self.training)
x = x.transpose(1, 2)
input_lengths = input_lengths.cpu()
x = nn.utils.rnn.pack_padded_sequence(x, input_lengths, batch_first=True, enforce_sorted=False)
outputs, _ = self.lstm(x)
outputs, _ = nn.utils.rnn.pad_packed_sequence(outputs, batch_first=True)
return outputs
컨볼루션 층 (Convolutional Layers)
텍스트 데이터는 긴 맥락을 고려하여 처리해야 합니다. 컨볼루션 층은 이런 역할을 담당하며, 각 컨볼루션 필터는 5개의 문자를 아우르는 형태(아래 그림에서는 3개의 문자를 아우르는 형태로 묘사)로 설계됩니다. 즉, 입력된 텍스트 시퀀스를 일정 크기의 필터로 나누어 더 긴 맥락 정보를 학습합니다.
이 코드는 3개의 컨볼루션 층을 사용하여 텍스트 시퀀스를 처리하며, 각 층은 512개의 필터를 가지고 있습니다. 또한, 각 컨볼루션 층 뒤에는 배치 정규화(Batch Normalization)와 ReLU 활성화 함수가 적용됩니다.
(위) 배치 중 1개의 데이터에 대해서만 컨볼루션 연산의 진행과정을 나타낸 것
(아래) 배치 데이터에 대해 컨볼루션 연산의 진행과정을 나타낸 것
1
2
3
4
5
6
7
8
9
10
11
12
13
14
for _ in range(encoder_n_convolution):
conv_layer = nn.Sequential(
_get_conv1d_layer(
encoder_embedding_dim,
encoder_embedding_dim,
kernel_size=encoder_kernel_size,
stride=1,
padding=int((encoder_kernel_size - 1) / 2),
dilation=1,
w_init_gain="relu",
),
nn.BatchNorm1d(encoder_embedding_dim),
)
self.convolutions.append(conv_layer)
양방향 LSTM (Bidirectional LSTM)
컨볼루션 층을 통과한 텍스트 데이터는 이제 양방향 LSTM을 거칩니다. LSTM은 순차적 데이터를 처리하는데 뛰어난 성능을 보이는 구조로, 시퀀스의 양방향(순방향과 역방향)을 동시에 처리할 수 있습니다.
1
2
3
4
5
6
7
self.lstm = nn.LSTM(
encoder_embedding_dim,
int(encoder_embedding_dim / 2),
1,
batch_first=True,
bidirectional=True,
)
이 LSTM 층은 각 방향에 256개의 유닛을 가지고 있으며, 양방향으로 총 512개의 유닛을 통해 텍스트 시퀀스를 인코딩합니다. 이를 통해 텍스트의 앞뒤 문맥을 모두 반영한 특징 벡터를 추출할 수 있습니다.
LSTM의 성능을 최적화하기 위해 self.lstm.flatten_parameters()
를 호출하여 파라미터들이 메모리에서 인접하게 배치되도록 합니다. 이는 학습 속도를 높이는 데 도움이 됩니다. [참고]
순전파 과정 (Forward Process)
순전파 과정에서는 입력된 텍스트 데이터를 차례로 컨볼루션 층과 LSTM 층을 통과시켜 최종 숨겨진 특징 벡터를 생성합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
def forward(self, x: Tensor, input_lengths: Tensor) -> Tensor:
for conv in self.convolutions:
x = F.dropout(F.relu(conv(x)), 0.5, self.training)
x = x.transpose(1, 2)
input_lengths = input_lengths.cpu()
x = nn.utils.rnn.pack_padded_sequence(x, input_lengths, batch_first=True, enforce_sorted=False)
outputs, _ = self.lstm(x)
outputs, _ = nn.utils.rnn.pad_packed_sequence(outputs, batch_first=True)
return outputs
- 컨볼루션 층 통과: 입력된 텍스트는 각 컨볼루션 층을 거치며 ReLU 활성화 함수와 드롭아웃(0.5 확률) 적용을 통해 활성화됩니다.
- 양방향 LSTM 통과: 컨볼루션 층을 통과한 특징 벡터는 양방향 LSTM에 전달되어 시퀀스의 양쪽 맥락을 학습합니다.
- 출력: 마지막으로 LSTM의 출력은 패딩된 상태로 복원되고, 숨겨진 특징 벡터를 반환합니다. [참고]
4. _get_conv1d_layer: 컨볼루션 층 생성
컨볼루션 층을 생성하는 _get_conv1d_layer
함수는 주어진 하이퍼파라미터를 사용해 1D 컨볼루션 필터를 설정합니다. 이때 필터의 크기, 스트라이드, 패딩 등 다양한 설정을 할 수 있으며, 초기 가중치는 Xavier 초기화를 통해 설정됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def _get_conv1d_layer(
in_channels: int,
out_channels: int,
kernel_size: int = 1,
stride: int = 1,
padding: Optional[Union[str, int, Tuple[int]]] = None,
dilation: int = 1,
bias: bool = True,
w_init_gain: str = "linear"
) -> torch.nn.Conv1d:
if padding is None:
if kernel_size % 2 != 1:
raise ValueError("kernel_size must be odd")
padding = int(dilation * (kernel_size - 1) / 2)
conv1d = torch.nn.Conv1d(
in_channels,
out_channels,
kernel_size=kernel_size,
stride=stride,
padding=padding,
dilation=dilation,
bias=bias
)
torch.nn.init.xavier_uniform_(conv1d.weight, gain=torch.nn.init.calculate_gain(w_init_gain))
return conv1d
5. 결론
이 글에서는 Tacotron2의 인코더에 대해 자세히 알아보았습니다. 인코더는 텍스트 데이터를 모델이 처리할 수 있는 숨겨진 특징 벡터로 변환하는 중요한 역할을 합니다. 컨볼루션 층을 통해 텍스트의 긴 맥락을 학습하고, 양방향 LSTM을 통해 시퀀스의 앞뒤 문맥을 모두 고려한 특징 벡터를 생성합니다.
Tacotron2 모델의 인코더는 텍스트 데이터를 효과적으로 처리하여 디코더가 이를 기반으로 음성 스펙트로그램을 예측할 수 있도록 돕습니다. 이 인코더 구조는 텍스트에서 음성으로 변환하는 작업에서 매우 중요한 역할을 합니다. 다음 글에서는 이 인코더에서 추출된 특징을 기반으로 디코더가 어떻게 음성 스펙트로그램을 생성하는지 살펴보겠습니다.
이 코드와 설명을 통해 Tacotron2의 인코더가 어떻게 텍스트를 처리하고, 숨겨진 특징 벡터를 생성하는지 이해할 수 있었길 바랍니다! 😊