<iframe src ="https://docs.google.com/spreadsheets/d/e/2PACX-1vRX6AsKXx_b1ur_AatGFSBWqInctN0A3YGGfLfXcVq7tfaJpucxR5HRaFNCFR1-R3PX81S0HescBxg-/pubhtml?gid=94715327&single=true" width="100%" height="640px"> </iframe>
# 1장 - NLP의 과거와 오늘
## 학습 자료
### 원본 자료
- [1단원 NLP 역사 PDF 교재](https://my-study-for-jake.s3.us-east-1.amazonaws.com/NLP%E1%84%8B%E1%85%B4+%E1%84%87%E1%85%A1%E1%86%AF%E1%84%8C%E1%85%A5%E1%86%AB+%E1%84%8B%E1%85%A7%E1%86%A8%E1%84%89%E1%85%A1.pdf)
### 추가 조사 자료
- [[XOR 문제와 비선형성 이해]]
- [[미분과 역전파에서 비선형성의 필요성]]
---
# 2장 - GPT
## 2.1 Data
### 데이터셋 설치 및 로드
```python
%pip install -q datasets
```
```python
from datasets import load_dataset
dataset = load_dataset("daekeun-ml/naver-news-summarization-ko")
```
### 데이터셋 구조 확인
```python
data = dataset
data
```
>[!Jake 설명]
> 이 명령어를 통해서 아래와 같이 데이터 칼럼을 확인할 수 있고, Train 데이터는 22,194개의 행이 있는 것을 알 수 있다. 이걸 이해하는 게 중요한데, 뉴스 기사 본문이 있는 Document를 한번에 전부 다 불러와서 학습을 시킬 것이기 때문이다.
**출력:**
```
DatasetDict({
train: Dataset({
features: ['date', 'category', 'press', 'title', 'document', 'link', 'summary'],
num_rows: 22194
})
validation: Dataset({
features: ['date', 'category', 'press', 'title', 'document', 'link', 'summary'],
num_rows: 2466
})
test: Dataset({
features: ['date', 'category', 'press', 'title', 'document', 'link', 'summary'],
num_rows: 2740
})
})
```
### 샘플 데이터 확인
```python
data["train"]["document"][0]
```
>[!Jake 설명]
>Data는 Dataset을 가져오고, 이것은 딕셔너리 형태이다. []을 붙여서 Key를 호출하는데, 첫번째 Key는 Train이고, 여기에서 다시 document를 호출한다. 두번째 Key가 되는 거다. 그러고선 [0]을 호출하는데, 이는 첫번째 문서를 가져오는 명령어이다. 아래와 같이 첫번째 본문 하나가 호출 되는 거다.
>딕셔너리 형태에서 [ ] 로 Key를 입력하고, 값을 가져오고, 마지막에는 [0]으로 인덱싱을 하는 거다. 왜냐하면, 리스트 형태로 Value가 많이 들어 있기 때문이다.
**출력:**
```
'앵커 정부가 올해 하반기 우리 경제의 버팀목인 수출 확대를 위해 총력을 기울이기로 했습니다. 특히 수출 중소기업의 물류난 해소를 위해 무역금융 규모를 40조 원 이상 확대하고 물류비 지원과 임시선박 투입 등을 추진하기로 했습니다. 류환홍 기자가 보도합니다. 기자 수출은 최고의 실적을 보였지만 수입액이 급증하면서 올해 상반기 우리나라 무역수지는 역대 최악인 103억 달러 적자를 기록했습니다. 정부가 수출확대에 총력을 기울이기로 한 것은 원자재 가격 상승 등 대외 리스크가 가중되는 상황에서 수출 증가세 지속이야말로 한국경제의 회복을 위한 열쇠라고 본 것입니다. 추경호 경제부총리 겸 기획재정부 장관 정부는 우리 경제의 성장엔진인 수출이 높은 증가세를 지속할 수 있도록 총력을 다하겠습니다. 우선 물류 부담 증가 원자재 가격 상승 등 가중되고 있는 대외 리스크에 대해 적극 대응하겠습니다. 특히 중소기업과 중견기업 수출 지원을 위해 무역금융 규모를 연초 목표보다 40조 원 늘린 301조 원까지 확대하고 물류비 부담을 줄이기 위한 대책도 마련했습니다. 이창양 산업통상자원부 장관 국제 해상운임이 안정될 때까지 월 4척 이상의 임시선박을 지속 투입하는 한편 중소기업 전용 선복 적재 용량 도 현재보다 주당 50TEU 늘려 공급하겠습니다. 하반기에 우리 기업들의 수출 기회를 늘리기 위해 2 500여 개 수출기업을 대상으로 해외 전시회 참가를 지원하는 등 마케팅 지원도 벌이기로 했습니다. 정부는 또 이달 중으로 반도체를 비롯한 첨단 산업 육성 전략을 마련해 수출 증가세를 뒷받침하고 에너지 소비를 줄이기 위한 효율화 방안을 마련해 무역수지 개선에 나서기로 했습니다. YTN 류환홍입니다.'
```
### 문자 집합 생성
```python
print(sorted(list(set(data["train"]["document"][0]))))
```
>[!Jake 설명]
>이번에는 함수가 세번 나온다. sorted는 오름차순 정렬이고, list는 값을 리스트 형태로 변환, set은 중복을 제거하는 거다.(사실 [[set 함수와 list 함수의 중복적 기능|set 함수만으로도 중복 제거하고 충분]]한데, 글자를 하나씩 분리하여 '아', '나' 형태로 보여준다. 그러나 더 명시적으로 보여주고 싶어서 list 함수를 썼다. data에서 첫번째 기사 본문을 가져온 다음, 중복 제거 후 리스트로 보여주고, 정렬을 했다.
>큰 틀에서 보면, 이렇게 텍스트를 하나씩 쪼개서 정렬하는 것은 글자를 숫자로 변환하기 위해서다. 인덱스 번호를 생성하기 위한 전처리 과정이다.
**출력:**
```
[' ', '.', '0', '1', '2', '3', '4', '5', 'E', 'N', 'T', 'U', 'Y', '가', '개', '것', '겠', '격', '견', '겸', '경', '고', '공', '과', '관', '국', '규', '극', '금', '급', '기', '까', '나', '난', '너', '높', '는', '늘', '니', '다', '단', '달', '담', '당', '대', '도', '되', '될', '뒷', '들', '등', '때', '또', '라', '략', '량', '러', '려', '력', '련', '로', '록', '롯', '류', '를', '리', '린', '마', '만', '말', '면', '모', '목', '무', '물', '박', '반', '받', '방', '버', '벌', '보', '복', '본', '부', '비', '산', '상', '서', '선', '성', '세', '소', '속', '쇠', '수', '스', '습', '승', '시', '실', '악', '안', '액', '앵', '야', '양', '억', '업', '에', '엔', '여', '역', '연', '열', '였', '올', '외', '용', '우', '운', '울', '원', '월', '위', '육', '율', '융', '으', '은', '을', '응', '의', '이', '인', '임', '입', '있', '자', '장', '재', '적', '전', '정', '제', '조', '주', '줄', '중', '증', '지', '진', '참', '창', '책', '척', '첨', '체', '초', '총', '최', '추', '출', '침', '커', '케', '크', '통', '투', '특', '팀', '팅', '편', '표', '하', '한', '할', '합', '해', '했', '현', '호', '홍', '화', '확', '환', '황', '회', '획', '효', '히']
```
### 전체 문서의 문자 집합 구성
```python
# 이것은 텍스트에 나타나는 모든 문자 집합을 가져온 것입니다.
# 그것들은 고유한 것들만 남겨서 sorted로 정렬을 하면 아래와 같은 결과를 볼 수 있습니다.
ko_text = "".join(data["train"]["document"])
ko_chars = sorted(list(set((ko_text))))
ko_vocab_size = len(ko_chars)
print("총 글자 수 :", ko_vocab_size)
```
>[!Jake 설명]
>"".join 메소드가 나온다. [[02 코어 프로젝트/LLM FineTuning/References/Python 문자열 처리 - join과 set 메서드|join 메소드]]에 대해서 확인해보면, 글자를 모두 붙여주는 메소드이고, "".은 구분자이다. 즉, 여기서는 공백없이 붙인다. 자세히 보면, 이번에는 [0]과 같이 인덱싱이 없다. 즉, document에 있는 22,194 행의 본문을 전부 가져오는 거다. 엄청난 데이터고, 이걸 조인으로 하나의 거대한 한글 덩어리로 만드는 건데, 구분자는 없다.
>ko_chars에서는 전에 봤던 것처럼 전부 나열을 한다.
>ko_vacab_size에서는 ko_chars의 글자를 센다. 즉 몇 개의 고유한 글자가 있는 지를 세어보는 거고, 출력 결과 2,701개의 고유 글자가 있는 것으로 나탄다. 여기서 [[Python len() 함수 완벽 가이드|len 함수]]에 대해서도 이해해보자. 리스트 자료형에서 len은 길이를 잴 때, 요소의 갯수를 세는 것이다. 텍스트 덩어리라면, 글자수를 세고, 딕셔너리라면 key 값의 수를 센다.
>
**출력:**
```
총 글자 수 : 2701
```
### 문자 집합 일부 확인
```python
print(ko_chars[2000:2100])
```
**출력:**
```
['왓', '왔', '왕', '왜', '왠', '외', '왹', '왼', '요', '욕', '욘', '욜', '욤', '욥', '용', '우', '욱', '운', '욷', '울', '움', '웁', '웃', '웅', '워', '웍', '원', '월', '웜', '웠', '웡', '웨', '웬', '웰', '웸', '웹', '웻', '위', '윅', '윈', '윌', '윔', '윕', '윗', '윙', '유', '육', '윤', '율', '윱', '윳', '융', '으', '윽', '은', '을', '음', '읍', '읏', '응', '의', '읠', '이', '익', '인', '일', '읽', '잃', '임', '입', '잇', '있', '잉', '잊', '잎', '자', '작', '잔', '잖', '잘', '잠', '잡', '잣', '잤', '장', '잦', '재', '잭', '잰', '잼', '잽', '잿', '쟁', '쟈', '쟝', '쟤', '저', '적', '전', '절']
```
### 토크나이저 함수 생성
```python
character_to_ids = {char:i for i, char in enumerate(ko_chars)}
ids_to_character = {i:char for i, char in enumerate(ko_chars)}
token_encode = lambda s:[character_to_ids[c] for c in s]
token_decode = lambda l: "".join([ids_to_character[i] for i in l])
print(token_encode("안녕하세요 함께 인공지능을 공부하게 되어 반가워요."))
print(token_decode(token_encode("안녕하세요 함께 인공지능을 공부하게 되어 반가워요.")))
```
>[!Jake 설명]
>여기서부터 살짝 복잡하다.
>먼저 큰 개념부터 보자. 문자를 숫자로 변환(인코딩), 숫자를 다시 문자로 변환(디코딩)하는 함수를 구현하는 거다. 개념상으로는 [[Python 기초 문법 - 컴프리헨션과 메서드|컴프리헨션]], [[Python 기초 문법 - 컴프리헨션과 메서드|enumerate 함수]], [[Python Lambda 함수 완전 가이드|Lambda 함수]]를 이해해야 한다.
>**컴프리헨션**은 한줄로 리스트, 딕셔너리를 만드는 함수이다. 여기서는 전체 한글 텍스트에 대해서 enumerata로 글자:숫자 배열을 하는데, 괄호가 {}이다. 즉, 딕셔너리라는 뜻. 그러므로, char:i의 의미는 key가 char, value가 i로 된 딕셔너리를 만드는 것이다. 이 반대로 만든 것이 ids_to_character이다.
>**Lambda** 함수는 한줄짜리 간단한 함수를 만드는 함수이다. 위의 예시에서는 s가 매개변수이다. 즉, tokent_encode(s)를 넣을 때, s가 매개변수이고, : 이후로 나오는 함수가 바로 반환될 값이다. 여기서는 character_to_ids 변수에서 [c]를 인덱싱 해서, 숫자를 가져오는 거다. for c in s 구문이 있으므로, s라는 자료에 대해, 즉, 매개변수로 들어온 모든 텍스트에 대해 전부 순회하며 반환을 하는 것이다. 그 결과 텍스트를 입력해서 token_encode를 하게 되면, 딕셔너리로 만든 다음, 순회하며 i 값을 가져오게 된다.
>마찬가지로 **token_decode**에서는 숫자를 텍스트로 가져오게 되는데, 이때, 가,나,다, 형식으로 나타날 수 있어서. 구분자 없이 조인을 시키는 "".join이 있다.
**출력:**
```
[1909, 1169, 2546, 1770, 2008, 0, 2551, 1061, 0, 2064, 977, 2157, 1209, 2055, 0, 977, 1658, 2546, 949, 0, 1283, 1942, 0, 1593, 908, 2024, 2008, 2]
안녕하세요 함께 인공지능을 공부하게 되어 반가워요.
```
### 데이터 토큰화
```python
import torch
tokenized_data = torch.tensor(token_encode(ko_text), dtype=torch.long)
print(tokenized_data.shape, tokenized_data.dtype)
print(tokenized_data[:100])
```
>[!Jake 설명]
> torch 함수에서 [[PyTorch 텐서 초보자 완전 해설|tensor]]에 대해서 이해해야 한다. list와 같은 형식으로 자료를 배열하지만 연산법이 다르다. 리스트 간의 덧셈, 곱셉이 가능해서, 고차원의 계산을 해야 할 때 유리하다. 데이터의 타입도 long으로 지정하면 64비트 정수형으로 설정이 되서, 숫자 크기가 커도 계산이 가능하다.
> 위의 코드에서, .tensor 메소드로 ko_text 파일의 숫자를 tensor로 변환했고, 데이터 타입을 long으로 설정해서 int64로 지정했다.
> .shape 메소드로 저장된 tokenized_data의 size를 확인하는데, 그 결과는 22,162,967, 즉, 총 숫자가 2천2백만개 가량이다. 이중 100개까지만 인덱싱해서 확인을 했다.
**출력:**
```
torch.Size([22162967]) torch.int64
tensor([1928, 2315, 0, 2105, 1658, 908, 0, 1987, 2555, 0, 2546, 1593,
1028, 0, 2015, 1485, 0, 965, 2107, 2060, 0, 1617, 2465, 1542,
2064, 0, 1808, 2273, 0, 2603, 1236, 1477, 0, 2037, 2555, 0,
2263, 1430, 2055, 0, 1028, 2019, 2062, 1028, 1441, 0, 2562, 1841,
1213, 1221, 2, 0, 2451, 2650, 0, 1808, 2273, 0, 2142, 1787,
1028, 1950, 2060, 0, 1558, 1468, 1119, 0, 2555, 1787, 1477, 0,
2037, 2555, 0, 1553, 1967, 1024, 2051, 0, 1015, 1541, 1477, 0,
7, 3, 2117, 0, 2026, 0, 2062, 1740, 0, 2603, 1236, 2546,
968, 0, 1558, 1468])
```
### 훈련/테스트 데이터 분할
```python
#이제 본격적으로 코드를 작성하기에 앞서서 train_data와 val_data로 나누는 작업을 진행하겠습니다.
n = int(0.9 * len(tokenized_data))
train_dataset = tokenized_data[:n]
test_dataset = tokenized_data[n:]
```
### 배치 데이터 생성 예시
```python
block_size = 8
train_dataset[:block_size]
```
**출력:**
```
tensor([1928, 2315, 0, 2105, 1658, 908, 0, 1987])
```
### 언어 모델링 입력-타겟 관계 확인
```python
x = train_dataset[:block_size]
y = train_dataset[1:block_size+1]
for time in range(block_size):
context = x[:time+1]
target = y[time]
print(f"입력 텐셔 : {context}")
print(f"타켓 글자 : {target}")
```
>[!Jake 노트]
>핵심은 입력텐서를 넣으면, 다음 글자를 예측하는 거다.
>블록 사이즈 8로 텐서를 출력했다. x, y의 값을 인덱싱하는데, x는 처음부터 블록사이즈까지이고, y는 x보다 시작점이 한글자 다음이다. 즉, 바로 다음 글자까지 더 포함하도록 해서 한칸씩 밀린 구조이다.
>for 구문에서 인덱싱을 하면, 0부터 시작하면 x는 0+1의 글자까지를 전부 가져오고, y는 정확히 0 한글자만 인덱싱하는데 이미 y는 변수 할당부터 x보다 한글자씩 밀려 있는 상황이다. 따라서 x가 '안녕하세요'이면, y는 '녕하세요 '를 가지고 있고, x가 2까지 인덱싱을 하면 입력텐서는 '안녕'를 반환(끝 글자를 포함 안하니까)하고 타겟 텐서는 2를 반환해야 하므로, '하'를 반환한다. [[GPT 언어모델 입력-타겟 쌍 생성 원리|자세한 원리]]
**출력:**
```
입력 텐셔 : tensor([1928])
타켓 글자 : 2315
입력 텐셔 : tensor([1928, 2315])
타켓 글자 : 0
입력 텐셔 : tensor([1928, 2315, 0])
타켓 글자 : 2105
입력 텐셔 : tensor([1928, 2315, 0, 2105])
타켓 글자 : 1658
입력 텐셔 : tensor([1928, 2315, 0, 2105, 1658])
타켓 글자 : 908
입력 텐셔 : tensor([1928, 2315, 0, 2105, 1658, 908])
타켓 글자 : 0
입력 텐셔 : tensor([1928, 2315, 0, 2105, 1658, 908, 0])
타켓 글자 : 1987
입력 텐셔 : tensor([1928, 2315, 0, 2105, 1658, 908, 0, 1987])
타켓 글자 : 2555
```
### 배치 생성 함수
```python
torch.manual_seed(1234)
batch_size = 4
block_size = 8
def batch_function(mode):
dataset = train_dataset if mode == "train" else test_dataset
idx = torch.randint(len(dataset) - block_size, (batch_size,))
x = torch.stack([dataset[index:index+block_size] for index in idx])
y = torch.stack([dataset[index+1:index+block_size+1] for index in idx])
return x, y
example_x, example_y = batch_function("train")
print("inputs : ", example_x.shape)
print("")
print("example_x의 실제 값")
print(example_x)
print("-----------------------")
print("targets : ", example_y.shape)
print("")
print("example_y의 실제 값")
print(example_y)
print("-----------------------")
for size in range(batch_size):
for t in range(block_size):
context = example_x[size, :t+1]
target = example_y[size, t]
print(f"input : {context}, target : {target}")
print("-----------------------")
print("-----------------------")
```
>[!Jake 해설]
> 1. 먼저 .manual_seed(1234) 소개. 랜덤으로 값을 생성할 때 시드값이라는 게 있다. 첫 출발값인데, 랜덤으로 난수를 생성할 때 특정 공식이 들어가는데, 그 시작 값을 시드값이라고 한다. 1234라는 값을 시드값으로 넣어줌으로써 다음에도 이 난수 생성을 재현할 수 있게 된다. [[PyTorch 랜덤 시드와 텐서 shape 완벽 이해|랜덤 시드에서 (1234) 의미]]에 대해서 자세히 읽어보자.
> 2. 배치 사이즈가 있다. 4로 되어 있는데, 언어 모델링을 4개로 만드는 것이고, 이 과정에서 2차원의 행렬로 생성해서 GPU가 동시에 처리할 수 있도록 데이터를 준비하는 거이다.
> 3. [[PyTorch 랜덤 시드와 텐서 shape 완벽 이해|.randint(992,(3,)의 설명]]도 자세히 읽어보자. 일단(3,)이 이해가 안됐는데 tensor에서는 튜플로 데이터의 인덱싱을 받기 때문에 (3,)으로 입력한다. 1차원일 때는 (3,), 2차원이면 (3,2) 이런 식이다. .randint에서는 (시작점, 끝점, 갯수)로 구조가 되어 있는데 시작점을 제외하고 위와 같이, 끝점, 갯수만 입력할 수도 있다. 1,000개의 데이터가 있다고 하면, 블록 사이즈인 8을 빼서 992가 된다. 거기에서 배치 사이즈개의 난수를 생성하는 거다. 즉, 0~991까지의 숫자에서 4개의 난수를 추출한다. 이렇게 추출된 난수는 x, y변수를 만들 때, 시작점이 된다. 랜덤한 시작점.
> 4. example_x, example_y = batch_function("train") 이 부분은 def에서 반환한 x, y값이 지역변수라면 이걸 전역 변수로 바꾸는 과정이다. 전역변수가 되면 어디서든 쓸 수 있다. 이런 구조로 def로 정의한 함수를 전역변수로 쓴다는 것을 알게 됐다.
> 5. x = torch.stack([dataset[index:index+block_size] for index in idx]), y = torch.stack([dataset[index+1:index+block_size+1] for index in idx]) 이 함수 구조를 잘 이해해야 한다. []리스트 형태가 있다. 즉, 컴프리헨션이다.( ) 값 안에 [ ] 가 있으면 리스트 구조로 값을 넣는 한줄 컴프리헨션이다. stack이라는 메소드가 있는데, [[PyTorch stack 메서드 완벽 가이드|.stack 메소드]]는 값을 차원으로 쌓는다. 이어 붙이는 게 아니라, 행렬 구조로 만든다. 4개의 배치에 따라 만들어진 난수를 기준으로 만든 x 값을 새로운 차원으로 이어 붙임으로써, 한번의 명령으로 4개의 데이터를 처리하는 것이다.
> 6. [[GPT 배치 이중 for 루프 완벽 해부|이중 for 구문 처리 방식]]에 대해서도 처음 이해했는데, 복습 해두자.
**출력:**
```
inputs : torch.Size([4, 8])
example_x의 실제 값
tensor([[1764, 2555, 0, 1236, 2248, 0, 2017, 1976],
[ 0, 1966, 2157, 0, 1951, 2062, 0, 2548],
[ 0, 1304, 1485, 1586, 0, 1907, 2450, 0],
[ 3, 2, 6, 5, 1, 0, 5, 3]])
-----------------------
targets : torch.Size([4, 8])
example_y의 실제 값
tensor([[2555, 0, 1236, 2248, 0, 2017, 1976, 2546],
[1966, 2157, 0, 1951, 2062, 0, 2548, 2289],
[1304, 1485, 1586, 0, 1907, 2450, 0, 2480],
[ 2, 6, 5, 1, 0, 5, 3, 5]])
-----------------------
input : tensor([1764]), target : 2555
input : tensor([1764, 2555]), target : 0
input : tensor([1764, 2555, 0]), target : 1236
input : tensor([1764, 2555, 0, 1236]), target : 2248
input : tensor([1764, 2555, 0, 1236, 2248]), target : 0
input : tensor([1764, 2555, 0, 1236, 2248, 0]), target : 2017
input : tensor([1764, 2555, 0, 1236, 2248, 0, 2017]), target : 1976
input : tensor([1764, 2555, 0, 1236, 2248, 0, 2017, 1976]), target : 2546
-----------------------
-----------------------
input : tensor([0]), target : 1966
input : tensor([ 0, 1966]), target : 2157
input : tensor([ 0, 1966, 2157]), target : 0
input : tensor([ 0, 1966, 2157, 0]), target : 1951
input : tensor([ 0, 1966, 2157, 0, 1951]), target : 2062
input : tensor([ 0, 1966, 2157, 0, 1951, 2062]), target : 0
input : tensor([ 0, 1966, 2157, 0, 1951, 2062, 0]), target : 2548
input : tensor([ 0, 1966, 2157, 0, 1951, 2062, 0, 2548]), target : 2289
-----------------------
-----------------------
input : tensor([0]), target : 1304
input : tensor([ 0, 1304]), target : 1485
input : tensor([ 0, 1304, 1485]), target : 1586
input : tensor([ 0, 1304, 1485, 1586]), target : 0
input : tensor([ 0, 1304, 1485, 1586, 0]), target : 1907
input : tensor([ 0, 1304, 1485, 1586, 0, 1907]), target : 2450
input : tensor([ 0, 1304, 1485, 1586, 0, 1907, 2450]), target : 0
input : tensor([ 0, 1304, 1485, 1586, 0, 1907, 2450, 0]), target : 2480
-----------------------
-----------------------
input : tensor([3]), target : 2
input : tensor([3, 2]), target : 6
input : tensor([3, 2, 6]), target : 5
input : tensor([3, 2, 6, 5]), target : 1
input : tensor([3, 2, 6, 5, 1]), target : 0
input : tensor([3, 2, 6, 5, 1, 0]), target : 5
input : tensor([3, 2, 6, 5, 1, 0, 5]), target : 3
input : tensor([3, 2, 6, 5, 1, 0, 5, 3]), target : 5
-----------------------
-----------------------
```
### 데이터 크기 확인
```python
ko_vocab_size, example_x.shape, example_y
```
**출력:**
```
(2701,
torch.Size([4, 8]),
tensor([[2555, 0, 1236, 2248, 0, 2017, 1976, 2546],
[1966, 2157, 0, 1951, 2062, 0, 2548, 2289],
[1304, 1485, 1586, 0, 1907, 2450, 0, 2480],
[ 2, 6, 5, 1, 0, 5, 3, 5]]))
```
## 2.3 언어모델 만들기
### 2.3.1 ~ 2.3.2 라이브러리 설명 & __init__ 함수
```python
import torch
import torch.nn as nn
from torch.nn import functional as F
class semiGPT(nn.Module):
def __init__(self, vocab_length):
super().__init__()
self.embedding_token_table = nn.Embedding(vocab_length, vocab_length)
def forward(self, inputs, targets):
logits = self.embedding_token_table(inputs)
return logits
model = semiGPT(ko_vocab_size)
output = model(example_x, example_y)
print(output.shape)
```
**출력:**
```
torch.Size([4, 8, 2701])
```
### 임베딩 인덱스 범위 오류 예시
```python
#에러가 발생되도록 설정한 코드
embedding = nn.Embedding(4, 4)
embedding(torch.tensor([[0, 1, 2, 10]]))
```
**출력:**
```
IndexError: index out of range in self
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-61-cdf876adb83c> in <cell line: 3>()
1 #에러가 발생되도록 설정한 코드
2 embedding = nn.Embedding(4, 4)
----> 3 embedding(torch.tensor([[0, 1, 2, 10]]))
/usr/local/lib/python3.10/dist-packages/torch/nn/modules/module.py in _wrapped_call_impl(self, *args, **kwargs)
1551 return self._compiled_call_impl(*args, **kwargs) # type: ignore[misc]
1552 else:
-> 1553 return self._call_impl(*args, **kwargs)
/usr/local/lib/python3.10/dist-packages/torch/nn/modules/module.py in _call_impl(self, *args, **kwargs)
1560 or _global_backward_pre_hooks or _global_backward_hooks
1561 or _global_forward_hooks or _global_forward_pre_hooks):
-> 1562 return forward_call(*args, **kwargs)
1563
1564 try:
/usr/local/lib/python3.10/dist-packages/torch/nn/modules/sparse.py in forward(self, input)
162
163 def forward(self, input: Tensor) -> Tensor:
--> 164 return F.embedding(
165 input, self.weight, self.padding_idx, self.max_norm,
166 self.norm_type, self.scale_grad_by_freq, self.sparse)
/usr/local/lib/python3.10/dist-packages/torch/nn/functional.py in embedding(input, weight, padding_idx, max_norm, norm_type, scale_grad_by_freq, sparse)
2265 # remove once script supports set_grad_enabled
2266 _no_grad_embedding_renorm_(weight, input, max_norm, norm_type)
-> 2267 return torch.embedding(weight, input, padding_idx, scale_grad_by_freq, sparse)
2268
2269
IndexError: index out of range in self
```
### 손실 함수 추가 시 차원 오류
```python
#에러가 발생되도록 세팅된 코드
import torch
import torch.nn as nn
from torch.nn import functional as F
class semiGPT(nn.Module):
def __init__(self, vocab_length):
super().__init__()
self.embedding_token_table = nn.Embedding(vocab_length, vocab_length)
def forward(self, inputs, targets):
logits = self.embedding_token_table(inputs)
loss = F.cross_entropy(logits, targets)
return logits, loss
model = semiGPT(ko_vocab_size)
output, loss = model(example_x, example_y)
print(output)
```
**출력:**
```
RuntimeError: Expected target size [4, 2701], got [4, 8]
---------------------------------------------------------------------------
RuntimeError Traceback (most recent call last)
<ipython-input-62-ee5a1ffe9b4a> in <cell line: 18>()
16
17 model = semi_GPT(ko_vocab_size)
---> 18 output, loss = model(example_x, example_y)
19 print(output)
/usr/local/lib/python3.10/dist-packages/torch/nn/modules/module.py in _wrapped_call_impl(self, *args, **kwargs)
1551 return self._compiled_call_impl(*args, **kwargs) # type: ignore[misc]
1552 else:
-> 1553 return self._call_impl(*args, **kwargs)
/usr/local/lib/python3.10/dist-packages/torch/nn/modules/module.py in _call_impl(self, *args, **kwargs)
1560 or _global_backward_pre_hooks or _global_backward_hooks
1561 or _global_forward_hooks or _global_forward_pre_hooks):
-> 1562 return forward_call(*args, **kwargs)
1563
1564 try:
<ipython-input-62-ee5a1ffe9b4a> in forward(self, inputs, targets)
12 logits = self.embedding_token_table(inputs)
13
---> 14 loss = F.cross_entropy(logits, targets)
15 return logits, loss
16
/usr/local/lib/python3.10/dist-packages/torch/nn/functional.py in cross_entropy(input, target, weight, size_average, ignore_index, reduce, reduction, label_smoothing)
3102 if size_average is not None or reduce is not None:
3103 reduction = _Reduction.legacy_get_string(size_average, reduce)
-> 3104 return torch._C._nn.cross_entropy_loss(input, target, weight, _Reduction.get_enum(reduction), ignore_index, label_smoothing)
3105
3106
RuntimeError: Expected target size [4, 2701], got [4, 8]
```
## 2.3.3 forward 메서드
### 올바른 차원 변환
```python
import torch
import torch.nn as nn
from torch.nn import functional as F
class semiGPT(nn.Module):
def __init__(self, vocab_length):
super().__init__()
self.embedding_token_table = nn.Embedding(vocab_length, vocab_length)
def forward(self, inputs, targets):
logits = self.embedding_token_table(inputs)
batch, seq_length, vocab_length = logits.shape
logits = logits.view(batch * seq_length, vocab_length)
targets = targets.view(batch*seq_length)
loss = F.cross_entropy(logits, targets)
print("logits의 shape는 : ", logits.shape, "입니다.")
print("targets의 shape는 : ", targets.shape, "입니다.")
return logits, loss
model = semiGPT(ko_vocab_size)
logits, loss = model(example_x, example_y)
print(loss)
```
**출력:**
```
logits의 shape는 : torch.Size([32, 2701]) 입니다.
targets의 shape는 : torch.Size([32]) 입니다.
tensor(8.2693, grad_fn=<NllLossBackward0>)
```
### 입력과 타겟 차원 확인
```python
example_x.shape, example_y.shape
```
**출력:**
```
(torch.Size([4, 8]), torch.Size([4, 8]))
```
## 2.3.4 generate 메서드
### 텍스트 생성 기능 추가
```python
import torch
import torch.nn as nn
from torch.nn import functional as F
class semiGPT(nn.Module):
def __init__(self, vocab_length):
super().__init__()
self.embedding_token_table = nn.Embedding(vocab_length, vocab_length)
def forward(self, inputs, targets=None):
logits = self.embedding_token_table(inputs)
if targets is None:
loss = None
else:
batch, seq_length, vocab_length = logits.shape
logits = logits.view(batch * seq_length, vocab_length)
targets = targets.view(batch*seq_length)
loss = F.cross_entropy(logits, targets)
return logits, loss
def generate(self, inputs, max_new_tokens):
for _ in range(max_new_tokens):
logits, loss = self.forward(inputs)
logits = logits[:, -1, :]
print(logits.shape)
probs = F.softmax(logits, dim=-1)
next_inputs = torch.multinomial(probs, num_samples=1)
inputs = torch.cat((inputs, next_inputs), dim=1)
return inputs
model = semiGPT(ko_vocab_size)
logits, loss = model(example_x, example_y)
print(loss)
token_decode(model.generate(torch.zeros((1,1),
dtype=torch.long),
max_new_tokens=10)[0].tolist())
```
**출력:**
```
tensor(8.5209, grad_fn=<NllLossBackward0>)
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
' 엿입拓빤쌩슝찮찡펭屬'
```
### 로짓 슬라이싱 예시
```python
import torch
logits = torch.tensor(
[
[
[0.1, 0.2, 0.3, 0.4],
[0.2, 0.3, 0.4, 0.1],
[0.3, 0.4, 0.1, 0.2]
]
]
)
result = logits[:,-1,:]
print("선택되는 값 : ", result)
print("결과에 대한 size 값 : ", result.size())
```
**출력:**
```
선택되는 값 : tensor([[0.3000, 0.4000, 0.1000, 0.2000]])
결과에 대한 size 값 : torch.Size([1, 4])
```
## 2.4 optimizer 추가하기
### 옵티마이저 설정
```python
learning_rate = 1e-2
model = semiGPT(ko_vocab_size)
optimizer = torch.optim.AdamW(model.parameters(), lr=learning_rate)
```
### 학습 루프
```python
from tqdm.auto import tqdm
batch_size = 32
for steps in tqdm(range(10000)):
example_x, example_y = batch_function("train")
logits, loss = model(example_x, example_y)
# 옵티마이저 초기화
optimizer.zero_grad(set_to_none=True)
# 역전파 계산
loss.backward()
# 가중치 업데이트
optimizer.step()
print(loss.item())
```
**출력:**
```
0%| | 0/10000 [00:00<?, ?it/s]
3.477691411972046
```
### 학습 후 텍스트 생성
```python
print(token_decode(model.generate(torch.zeros((1,1), dtype=torch.long), max_new_tokens=10)[0].tolist()))
```
**출력:**
```
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
torch.Size([1, 2701])
협력에 오를 것이
```
### 2.4.1 GPU로 데이터를 옮기는 방법
```python
device = "cuda" if torch.cuda.is_available() else "cpu"
```
```python
def batch_function(mode):
dataset = train_dataset if mode == "train" else test_dataset
idx = torch.randint(len(dataset) - block_size, (batch_size,))
x = torch.stack([dataset[index:index+block_size] for index in idx])
y = torch.stack([dataset[index+1:index+block_size+1] for index in idx])
x, y = x.to(device), y.to(device) # .to 를 추가
return x, y
```
### 2.4.2 Loss 함수 만들기
```python
@torch.no_grad()
def compute_loss_metrics():
out = {}
model.eval()
for mode in ["train", "eval"]:
losses = torch.zeros(eval_iteration)
for k in range(eval_iteration):
inputs, targets = batch_function(mode)
logits, loss = model(inputs, targets)
losses[k] = loss.item()
out[mode] = losses.mean()
model.train()
return out
```
### 2.4.3 전체 코드 복습
```python
import torch
import torch.nn as nn
import torch.nn.functional as F
batch_size = 32
block_size = 8
max_iteration = 50000
eval_interval = 300
learning_rate = 1e-2
device = "cuda" if torch.cuda.is_available() else "cpu"
eval_iteration = 200
def batch_function(mode):
dataset = train_dataset if mode == "train" else test_dataset
idx = torch.randint(len(dataset) - block_size, (batch_size,))
x = torch.stack([dataset[index:index+block_size] for index in idx])
y = torch.stack([dataset[index+1:index+block_size+1] for index in idx])
x, y = x.to(device), y.to(device) # .to 를 추가
return x, y
@torch.no_grad()
def compute_loss_metrics():
out = {}
model.eval()
for mode in ["train", "eval"]:
losses = torch.zeros(eval_iteration)
for k in range(eval_iteration):
inputs, targets = batch_function(mode)
logits, loss = model(inputs, targets)
losses[k] = loss.item()
out[mode] = losses.mean()
model.train()
return out
class semiGPT(nn.Module):
def __init__(self, vocab_length):
super().__init__()
self.embedding_token_table = nn.Embedding(vocab_length, vocab_length)
def forward(self, inputs, targets=None):
logits = self.embedding_token_table(inputs)
if targets is None:
loss = None
else:
batch, seq_length, vocab_length = logits.shape
logits = logits.view(batch * seq_length, vocab_length)
targets = targets.view(batch*seq_length)
loss = F.cross_entropy(logits, targets)
return logits, loss
def generate(self, inputs, max_new_tokens):
for _ in range(max_new_tokens):
logits, loss = self.forward(inputs)
logits = logits[:, -1, :]
probs = F.softmax(logits, dim=-1)
next_inputs = torch.multinomial(probs, num_samples=1)
inputs = torch.cat((inputs, next_inputs), dim=1)
return inputs
model = semiGPT(ko_vocab_size).to(device)
optimizer = torch.optim.AdamW(model.parameters(), lr=learning_rate)
for step in range(max_iteration):
if step % eval_interval == 0 :
losses = compute_loss_metrics()
print(f'step : {step}, train loss : {losses["train"]:.4f}, val loss : {losses["eval"]:.4f}')
example_x, example_y = batch_function("train")
logits, loss = model(example_x, example_y)
optimizer.zero_grad(set_to_none=True)
loss.backward()
optimizer.step()
inputs = torch.zeros((1,1), dtype=torch.long, device=device)
print(token_decode(model.generate(inputs, max_new_tokens=100)[0].tolist()))
```
**출력:** (학습 과정)
```
step : 0, train loss : 8.3225, val loss : 8.3266
step : 300, train loss : 6.0857, val loss : 6.0748
step : 600, train loss : 4.7943, val loss : 4.7822
step : 900, train loss : 4.2353, val loss : 4.2261
...
step : 49800, train loss : 3.3914, val loss : 3.4028
가 실적극 EMe '에서 이 밝혔다하면 기 없는 식으로 검출발생산 기항 이 옆면 농가정 발생각각종을 우려한국환경단 4. 개 5년가격에서는 칸훤눠콩반분에 3%를 롯데이 SSRSKAI
```