사전 학습 ( Pre-training )
사전학습은 대규모 데이터셋을 사용해서 모델을 처음부터 훈련시키는 과정이다. 일반적인 능력을 향상시키는 데 목적을 두고, 이 단계에서는 모델이 일반적인 패턴을 학습해서 다양한 작업을 수행할 수 있는 강력한 기본 표현을 형성하는 데 중점을 둔다.
과정을 살펴보면, 사전학습은 일반적으로 대규모 데이터셋이 필요하다. 특히 BERT 모델은 파라미터가 상당히 많기 때문에 학습하기 위한 데이터셋이 크게 요구되는 편이다. 이 데이터셋들은 특정 작업에 국한되지 않고, 예를들어 자연어 처리의 경우에는 수백 혹은 수십억 개의 문장으로 구성돼서 데이터셋을 일반적 형태로 나타내게 된다.
특징을 알아보자. 특징을 살펴보면 알겠지만 사전학습 모델의 목적은 일반 언어의 이해다. 그래서 분류나 감성어 분석, 생성 등 특정 작업에 대해서는 목적을 두지 않고 있다.
BERT의 사전 학습 예시를 살펴보자.
- Masked Language Modeling (MLM)
일반적으로 BERT에는 주요 작업을 수행해서 사전 학습이 진행되는데, MLM이라고 하는 Masked Language Modeling을 처음에 수행한다. 이 기법은 문장 내에서 일부 단어를 마스킹하고, 이 마스킹된 단어를 모델이 예측하게 한다. BERT 모델의 핵심은 문장을 양방향으로 고려한다는 것이다. 한 방향이 아닌 양방향이라는 점이 예전의 일반적인 언어 모델과의 차별점이었다. 이러한 특징 덕분에 문장의 앞부분과 뒷부분을 모두 참고해서 단어를 예측하는 게 가능해졌다.
- Next Sentence Prediction (NSP)
문장 간의 관계를 학습하는 방법이다. 주어진 두 문장이 연속적인 문장인지, 해당 부분을 판단하게 된다. 문장 사이에 연결성을 이해하는 데 중요한 역할을 하게 된다.
사전학습된 모델이 있으면, 우리는 특정 작업에 맞게 추가로 학습을 시키게 된다. 일반적으로 이 과정에서는, 일반적인 패턴을 학습한 모델이 주어졌다고 가정하고, 해당 모델을 특정 작업, 우리가 다뤄본 예시로는 감정분석, 질의응답, 텍스트 생성 등 특정 작업에서 더 잘 동작할 수 있도록 모델의 가중치를 미세 조정한다.
사전 학습된 모델의 가중치는 Fine-Tuning 모델의 초기값이 된다. 학습은 모델의 특정 작업에 맞게 수행된다. 이 과정에서는 특정 작업의 목표, 예를 들어 분류 정확도, F1 스코어 등 다양한 방식이 사용될 수 있는데, 목적에 맞게 사용되기만 하면 된다. Fine-Tuning이 완료된 모델은 특정 작업에서 높은 성능을 보일 수 있도록 최적화된다. 하지만 이 모델은 일반적인 작업보다는 특정 작업에서 더 뛰어난 성능을 발휘하게 된다.
특징을 살펴보자.
1. 작업 특화
일반적인 작업이 아니라, 특정 작업에 맞춰 모델의 파라미터를 미세조정, 즉 최적화하는 과정이 Fine-Tuning이다.
2. 사전 학습 가중치 활용
사전 학습 가중치를 초기값으로 사용한다. 이미 사전학습된 모델은 언어 이해 능력이 훌륭하다. 이 능력을 바탕으로 새로운 작업에 적응만 시켜주는 것이다. 그리고 언어의 이해 능력이 이미 있기 때문에, 적은 데이터만으로도 훌륭한 성능을 보일 수 있다.
사전 학습(Pre-training)은 일반적인 능력을 기르기 위해, 대규모 데이터에서 모델을 미리 학습시키는 것이다. 이 과정에서는 특정 작업에 대해서 타겟팅하지는 않는다.
파인 튜닝(Fine-Tuning)에서는 특정 작업에 모델을 최적화시키는 작업을 포함하기 때문에, 사전 학습 결과를 바탕으로 특정 작업에 최적화된 모델을 만들 수 있다.
이 두 단계는 우리가 최신 모델을 활용할 때 굉장히 중요한 부분이다. 최신 모델은 그 규모가 크고, 대규모 데이터셋에서 학습된 사전 학습 모델이 제공될 가능성이 높기 때문에, 우리는 이 부분을 심도있게 보는 것을 추천한다.
강의 외부에서 추가로 알게 된 내용
- re.findall() 함수
re.findall(r'\d+', my_string)은 Python의 정규 표현식(re 모듈)을 사용해 문자열 my_string에서 연속된 숫자들을 찾아 리스트로 반환하는 명령이다.
re.findall() 함수는 정규 표현식 패턴에 맞는 모든 부분을 찾아 리스트로 반환한다. 만약 찾는 패턴이 없다면 빈 리스트를 반환한다.
여기서 쓰인 정규 표현식 패턴에 대해 알아보자.
- r'...' : 문자열 앞에 r을 붙이면 "raw string"(raw 문자열)으로, 백슬래시 \를 특수한 문자로 인식하지 않고 그대로 사용하게 한다. 이를 통해 \d를 "백슬래시와 d"가 아닌 "정규 표현식에서 숫자를 의미하는 \d"로 인식하게 된다.
- \d : 숫자(0에서 9 사이의 모든 숫자) 하나를 의미한다.
- + : +는 앞의 패턴(\d)이 하나 이상 연속되는 부분을 찾게 만든다.
이 re.findall(r'\d+', my_string)이 어떻게 동작하는지 예를 통해 살펴보자.
# 예시 1
my_string = "aAb1B2cC34oOp"
# 결과
['1', '2', '34']
\d+는 하나 이상의 연속된 숫자 부분을 찾으므로, 이 문자열에서는 "1", "2", 그리고 "34"를 찾아내어 리스트로 반환한다.
# 예시 2
my_string = "123abc456def"
# 결과
['123', '456']
여기서는 연속된 숫자 덩어리 "123"과 "456"을 찾아내어 리스트로 반환한다.
이 결과 리스트는 숫자로 구성된 문자열들로 이루어져 있다.
- itertools 모듈의 permutations 함수
주어진 iterable(리스트, 문자열 등)에서 가능한 모든 순열을 생성하는 데 사용된다. 이 함수는 조합(combination)과는 달리, 선택한 요소들의 순서도 고려하여 모든 가능한 배열을 생성한다.
주어진 원소들로 만들 수 있는 모든 배열의 나열이다. 예를 들어, 원소가 ['A', 'B', 'C']라면 가능한 순열은 ['A', 'B', 'C'], ['A', 'C', 'B'], ['B', 'A', 'C'], ['B', 'C', 'A'], ['C', 'A', 'B'], ['C', 'B', 'A']의 총 6가지다.
사용 방법을 알아보자. 먼저 모듈을 import해와야 한다.
from itertools import permutations
기본 사용법은 아래와 같다.
result = permutations(iterable, r)
# iterable: 순열을 생성할 대상이다.(예: 리스트, 문자열 등)
# r: 선택할 원소의 개수입니다. 이 인자를 생략하면 iterable의 길이만큼의 순열을 생성한다.
permutations 함수는 반환값으로는 순열을 튜플 형태로 생성하여 반환한다. 이를 리스트로 변환하거나, 필요한 형태로 처리해야 한다.
예시를 통해 다시 살펴보자.
# 기본 사용 예시
from itertools import permutations
data = ['A', 'B', 'C']
# 모든 순열 생성
result = list(permutations(data))
print(result)
# 출력
[('A', 'B', 'C'), ('A', 'C', 'B'), ('B', 'A', 'C'),
('B', 'C', 'A'), ('C', 'A', 'B'), ('C', 'B', 'A')]
이 예시는 ['A', 'B', 'C']의 모든 순열을 생성하여 리스트로 반환한다.
# 선택 개수를 지정하는 예시
from itertools import permutations
data = ['A', 'B', 'C']
# 2개를 선택하여 순열 생성
result = list(permutations(data, 2))
print(result)
# 출력
[('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')]
이 예시는 ['A', 'B', 'C']에서 2개를 선택한 순열을 생성합니다. 즉, 원소를 2개만 사용하여 만들 수 있는 모든 배열을 반환한다.
주의할 점은 다음과 같다. 순열의 개수는 원소의 개수에 따라 기하급수적으로 증가한다. 예를 들어, n개의 원소가 있을 때, 순열의 수는 n!n! (n factorial)이다. 예를 들어, 4개의 원소가 있으면 4! = 24개의 순열이 존재한다.
메모리 사용량과 성능에 주의해야 하며, 큰 입력에서는 효율적인 방법으로 대체할 수 있는지를 고려해야 한다.
활용 예시는 다음과 같다. 순열은 조합 문제, 일정한 규칙을 따른 다양한 배열을 생성하는 데 유용하게 사용된다. 특히 백트래킹 알고리즘, 암호 생성, 게임에서의 가능한 움직임을 계산하는 데 많이 사용된다.
- 재귀 CTE(공통 테이블 식)
SQL에서 재귀적으로 데이터를 쿼리할 수 있게 해주는 기능이다. 이를 통해 트리 구조와 같이 계층적인 데이터를 쉽게 처리할 수 있다.
CTE는 WITH 절을 사용하여 정의되는 임시 결과 집합이다. CTE는 주 쿼리 내에서 사용되며, 가독성을 높이고 쿼리를 더 간단하게 만들 수 있다. 일반적인 CTE는 하나의 쿼리 결과를 포함하지만, 재귀 CTE는 자신을 참조하여 반복적인 처리를 수행할 수 있다.
구조를 살펴보자. 재귀 CTE는 두 부분으로 나뉘어 있다. 기본 쿼리는 재귀 호출의 시작점이 되는 초기 데이터를 정의한다. 재귀 쿼리는 기본 쿼리에서 시작하여 자신을 재귀적으로 호출하여 데이터를 반복적으로 생성한다. 재귀 CTE의 기본 구조는 다음과 같다.
WITH RECURSIVE cte_name AS (
-- 기본 쿼리: 재귀의 시작점
SELECT initial_column1, initial_column2, ...
FROM some_table
WHERE condition
UNION ALL
-- 재귀 쿼리: 자기 자신을 참조하여 반복
SELECT next_column1, next_column2, ...
FROM cte_name
WHERE another_condition
)
SELECT *
FROM cte_name;
작동 방식을 살펴보자.
초기 쿼리 실행 : 기본 쿼리가 먼저 실행되어 초기 결과 집합을 생성한다.
재귀 쿼리 실행 : 초기 결과를 바탕으로 재귀 쿼리가 실행되어 새로운 결과를 생성한다. 이 과정은 더 이상 결과가 생성되지 않을 때까지 반복된다.
최종 결과 반환 : 최종적으로 쿼리의 마지막 SELECT 문을 통해 결과가 반환된다.
예를 통해 다시 알아보자. 계층적인 데이터를 가진 직원 테이블에서 특정 직원의 모든 하위 직원을 찾고 싶다면, 다음과 같은 재귀 CTE를 사용할 수 있다.
WITH RECURSIVE employee_hierarchy AS (
-- 기본 쿼리: 특정 직원을 선택
SELECT employee_id, name, manager_id
FROM employees
WHERE employee_id = 1 -- 예: 직무 ID가 1인 직원
UNION ALL
-- 재귀 쿼리: 하위 직원을 찾기
SELECT e.employee_id, e.name, e.manager_id
FROM employees e
JOIN employee_hierarchy eh ON e.manager_id = eh.employee_id
)
SELECT *
FROM employee_hierarchy;
위의 내용을 다시 요약해보겠다. 재귀 CTE는 SQL 쿼리 내에서 반복적으로 데이터를 처리할 수 있는 강력한 도구이다. 계층적인 구조를 처리하는 데 유용하며, 이해하기 쉽고 가독성이 뛰어난 쿼리를 작성할 수 있게 도와준다. 올바른 사용을 통해 데이터베이스에서 복잡한 계층적 쿼리를 간편하게 수행할 수 있다.
이러한 방식으로 재귀 CTE를 활용하면 다양한 데이터 집합을 쉽게 탐색하고 결과를 효율적으로 처리할 수 있다.