# PyTorch 텐서 초보자 완전 해설
## 문제의 코드 다시 보기
```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])
```
**출력 결과:**
```
torch.Size([22162967]) torch.int64
tensor([1928, 2315, 0, 2105, 1658, 908, 0, 1987, 2555, 0, ...])
```
## 1. 텐서(Tensor)란 정확히 무엇인가?
### 일반 리스트와 텐서의 차이점
```python
# 일반 파이썬 리스트
normal_list = [1, 2, 3, 4, 5]
print(type(normal_list)) # <class 'list'>
# PyTorch 텐서
tensor_data = torch.tensor([1, 2, 3, 4, 5])
print(type(tensor_data)) # <class 'torch.Tensor'>
```
### "그냥 숫자 리스트인데 뭐가 다르지?"
이 질문이 핵심입니다! 겉보기엔 똑같아 보이지만 **내부 동작**이 완전히 다릅니다.
#### 일반 리스트의 한계
```python
# 일반 리스트로 계산하면...
list1 = [1, 2, 3]
list2 = [4, 5, 6]
# 이런 계산이 불가능!
# result = list1 + list2 # 이건 [1, 2, 3, 4, 5, 6] (연결)
# result = list1 * list2 # 에러 발생!
# 수학적 계산을 하려면 반복문 필요
result = []
for i in range(len(list1)):
result.append(list1[i] + list2[i])
print(result) # [5, 7, 9]
```
#### 텐서의 장점
```python
# 텐서로 하면 간단!
tensor1 = torch.tensor([1, 2, 3])
tensor2 = torch.tensor([4, 5, 6])
result = tensor1 + tensor2 # 바로 계산 가능!
print(result) # tensor([5, 7, 9])
# 더 복잡한 계산도 쉽게
print(tensor1 * tensor2) # tensor([4, 10, 18]) - 요소별 곱셈
print(tensor1 ** 2) # tensor([1, 4, 9]) - 제곱
print(torch.mean(tensor1.float())) # tensor(2.) - 평균
```
### 텐서가 필요한 진짜 이유
> [!important]
> **딥러닝에서는 수백만 개의 숫자를 동시에 계산해야 합니다!**
>
> 일반 리스트로는 불가능하고, 텐서만이 이런 대규모 계산을 효율적으로 처리할 수 있습니다.
## 2. .shape가 정확히 무엇인가?
### shape는 텐서의 "모양"을 알려주는 정보
```python
# 1차원 텐서 (벡터)
vector = torch.tensor([1, 2, 3, 4, 5])
print(vector.shape) # torch.Size([5])
# 의미: 5개 요소가 일렬로 배열
# 2차원 텐서 (행렬)
matrix = torch.tensor([[1, 2, 3], [4, 5, 6]])
print(matrix.shape) # torch.Size([2, 3])
# 의미: 2행 3열의 표 형태
# 3차원 텐서
cube = torch.tensor([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
print(cube.shape) # torch.Size([2, 2, 2])
# 의미: 2×2×2 크기의 정육면체
```
### 실제 예시로 이해하기
```python
# 우리의 경우
tokenized_data = torch.tensor([1928, 2315, 0, 2105, ...]) # 매우 긴 리스트
print(tokenized_data.shape) # torch.Size([22162967])
```
**해석:**
- `[22162967]`: 1차원 배열에 22,162,967개의 숫자가 들어있음
- 즉, 한국어 텍스트를 숫자로 바꾼 결과가 2천2백만 개의 토큰!
### shape의 활용
```python
# shape로 데이터 크기 파악
print(f"총 토큰 수: {tokenized_data.shape[0]}") # 22162967
# 훈련/테스트 분할할 때
total_size = tokenized_data.shape[0]
train_size = int(0.9 * total_size)
print(f"훈련 데이터: {train_size}개")
print(f"테스트 데이터: {total_size - train_size}개")
```
#### shape[0]이 정확히 무엇인가?
`shape[0]`에서 `[0]`은 **shape 튜플의 첫 번째 차원 크기**를 가져오는 인덱싱입니다.
```python
# shape는 튜플 형태로 저장됨
tokenized_data = torch.tensor([1928, 2315, 0, 2105, ...])
print(tokenized_data.shape) # torch.Size([22162967])
print(type(tokenized_data.shape)) # <class 'torch.Size'> (튜플과 비슷)
# 튜플처럼 인덱싱 가능
shape_info = tokenized_data.shape
print(shape_info[0]) # 22162967 (첫 번째 차원의 크기)
```
#### 차원별 shape[0]의 의미
```python
# 1차원 텐서 (벡터) - 우리의 경우
vector = torch.tensor([1, 2, 3, 4, 5])
print(f"1차원 shape: {vector.shape}") # torch.Size([5])
print(f"shape[0] = 요소 개수: {vector.shape[0]}") # 5
# 2차원 텐서 (행렬)
matrix = torch.tensor([[1, 2, 3], [4, 5, 6]])
print(f"2차원 shape: {matrix.shape}") # torch.Size([2, 3])
print(f"shape[0] = 행 개수: {matrix.shape[0]}") # 2
print(f"shape[1] = 열 개수: {matrix.shape[1]}") # 3
# 3차원 텐서 (예: 이미지 배치)
cube = torch.tensor([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
print(f"3차원 shape: {cube.shape}") # torch.Size([2, 2, 2])
print(f"shape[0] = 첫 번째 차원: {cube.shape[0]}") # 2
print(f"shape[1] = 두 번째 차원: {cube.shape[1]}") # 2
print(f"shape[2] = 세 번째 차원: {cube.shape[2]}") # 2
```
#### 실제 코드에서의 활용
```python
# 우리 데이터의 경우
tokenized_data.shape # torch.Size([22162967])
# ↑
# [0]번째 인덱스
total_size = tokenized_data.shape[0] # 22162967
```
**의미 해석:**
- `tokenized_data`는 1차원 텐서
- `shape`는 `[22162967]` (길이가 1인 튜플)
- `shape[0]`는 첫 번째(그리고 유일한) 차원의 크기
- 즉, "이 텐서에 몇 개의 요소가 들어있는가?" = 22,162,967개
#### 시각적 이해
```python
# 1차원 텐서 시각화
tensor_1d = torch.tensor([10, 20, 30, 40, 50])
# ↑ ↑ ↑ ↑ ↑
# 0 1 2 3 4 (인덱스)
# 총 5개 요소
print(f"shape: {tensor_1d.shape}") # torch.Size([5])
print(f"shape[0]: {tensor_1d.shape[0]}") # 5 (전체 요소 개수)
# 2차원 텐서 시각화
tensor_2d = torch.tensor([[1, 2, 3], # 첫 번째 행 (인덱스 0)
[4, 5, 6]]) # 두 번째 행 (인덱스 1)
# ↑ 2행 3열 ↑
print(f"shape: {tensor_2d.shape}") # torch.Size([2, 3])
print(f"shape[0] (행 수): {tensor_2d.shape[0]}") # 2
print(f"shape[1] (열 수): {tensor_2d.shape[1]}") # 3
```
#### 대체 방법들과 비교
```python
# 같은 결과를 얻는 다양한 방법
tokenized_data = torch.tensor([1928, 2315, 0, ...])
method1 = tokenized_data.shape[0] # 권장 방법
method2 = tokenized_data.size(0) # 동일한 결과
method3 = len(tokenized_data) # 1차원에서만 동일
method4 = tokenized_data.numel() # 전체 요소 수 (1차원에서 동일)
print(f"shape[0]: {method1}")
print(f"size(0): {method2}")
print(f"len(): {method3}")
print(f"numel(): {method4}")
print(f"모두 같은가? {method1 == method2 == method3 == method4}") # True
```
#### 실무에서의 활용 패턴
```python
# 패턴 1: 데이터 크기 확인
total_tokens = tokenized_data.shape[0]
print(f"처리할 토큰 수: {total_tokens:,}개") # 22,162,967개
# 패턴 2: 배치 크기 계산
batch_size = 32
num_batches = total_tokens // batch_size
print(f"배치 수: {num_batches}개")
# 패턴 3: 메모리 사용량 추정
bytes_per_token = 8 # torch.long은 8바이트
total_memory_mb = (total_tokens * bytes_per_token) / (1024 * 1024)
print(f"예상 메모리 사용량: {total_memory_mb:.1f}MB")
# 패턴 4: 진행률 표시
processed = 1000000 # 처리된 토큰 수
progress = (processed / total_tokens) * 100
print(f"진행률: {progress:.2f}%")
```
> [!important] shape[0] 핵심 정리
> - `shape[0]`는 **첫 번째 차원의 크기**를 의미
> - 1차원 텐서에서는 **전체 요소 개수**
> - 2차원 텐서에서는 **행의 개수**
> - 3차원 텐서에서는 **첫 번째 차원의 크기** (예: 배치 크기)
> - 데이터 크기 파악, 반복문 범위 설정, 메모리 계산 등에 필수적
## 3. .dtype이 중요한 이유
### dtype은 "어떤 종류의 숫자"인지 알려주는 정보
```python
# 정수형 텐서
int_tensor = torch.tensor([1, 2, 3], dtype=torch.long)
print(int_tensor.dtype) # torch.int64
# 실수형 텐서
float_tensor = torch.tensor([1.1, 2.2, 3.3], dtype=torch.float32)
print(float_tensor.dtype) # torch.float32
# 불린형 텐서
bool_tensor = torch.tensor([True, False, True], dtype=torch.bool)
print(bool_tensor.dtype) # torch.bool
```
### "왜 dtype이 중요해?"
#### 1. 메모리 사용량이 다름
```python
# 작은 숫자들 (0~255)
small_numbers = [10, 20, 30, 40, 50]
# 8비트로 저장 (메모리 절약)
tensor_int8 = torch.tensor(small_numbers, dtype=torch.int8)
print(f"int8 크기: {tensor_int8.element_size()} 바이트/요소") # 1바이트
# 64비트로 저장 (메모리 많이 사용)
tensor_int64 = torch.tensor(small_numbers, dtype=torch.int64)
print(f"int64 크기: {tensor_int64.element_size()} 바이트/요소") # 8바이트
```
#### 2. 표현할 수 있는 숫자 범위가 다름
```python
# int8: -128 ~ 127
small_tensor = torch.tensor([100, 200], dtype=torch.int8)
print(small_tensor) # tensor([100, -56]) - 200이 -56으로 바뀜! (오버플로우)
# int64: 매우 큰 숫자까지 OK
big_tensor = torch.tensor([100, 200, 10000, 2000000], dtype=torch.int64)
print(big_tensor) # tensor([100, 200, 10000, 2000000]) - 정상
```
### 우리 코드에서 torch.long을 사용한 이유
```python
# 한국어 문자가 2,701개
ko_vocab_size = 2701
# 토큰 ID는 0부터 2700까지의 정수
# torch.int8로는 127까지밖에 못 담음 → 에러!
# torch.long(int64)로는 2700도 충분히 담을 수 있음
tokenized_data = torch.tensor(token_encode(ko_text), dtype=torch.long)
print(f"최대 토큰 ID: {tokenized_data.max()}") # 2700 이하의 값
print(f"최소 토큰 ID: {tokenized_data.min()}") # 0 이상의 값
```
## 4. 텐서 vs 일반 리스트 - 실제 성능 비교
### 속도 비교
```python
import time
# 큰 리스트 생성
big_list1 = list(range(1000000)) # 100만 개
big_list2 = list(range(1000000))
# 일반 리스트로 덧셈 (느림)
start = time.time()
result_list = []
for i in range(len(big_list1)):
result_list.append(big_list1[i] + big_list2[i])
list_time = time.time() - start
# 텐서로 덧셈 (빠름)
tensor1 = torch.tensor(big_list1)
tensor2 = torch.tensor(big_list2)
start = time.time()
result_tensor = tensor1 + tensor2
tensor_time = time.time() - start
print(f"리스트 계산 시간: {list_time:.4f}초")
print(f"텐서 계산 시간: {tensor_time:.4f}초")
print(f"텐서가 {list_time/tensor_time:.1f}배 빠름!")
```
### GPU 사용 가능
```python
# CPU에서 계산
cpu_tensor = torch.tensor([1, 2, 3, 4, 5])
print(f"CPU 위치: {cpu_tensor.device}")
# GPU로 이동 (GPU가 있다면)
if torch.cuda.is_available():
gpu_tensor = cpu_tensor.cuda()
print(f"GPU 위치: {gpu_tensor.device}")
# GPU에서 계산은 훨씬 빠름!
result = gpu_tensor * 2
print(f"GPU 계산 결과: {result}")
```
## 5. 슬라이싱 [:100] 이해하기
### 기본 슬라이싱
```python
# 간단한 예시
numbers = torch.tensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
print(numbers[:3]) # tensor([0, 1, 2]) - 처음 3개
print(numbers[3:]) # tensor([3, 4, 5, 6, 7, 8, 9]) - 4번째부터 끝까지
print(numbers[2:7]) # tensor([2, 3, 4, 5, 6]) - 3번째부터 7번째까지
print(numbers[-3:]) # tensor([7, 8, 9]) - 뒤에서 3개
print(numbers[::2]) # tensor([0, 2, 4, 6, 8]) - 2칸씩 건너뛰기
```
### 우리 코드에서의 의미
```python
print(tokenized_data[:100])
# 의미: 2천만 개 토큰 중에서 처음 100개만 보여줘
# 왜?: 전체를 다 출력하면 화면이 너무 복잡해짐
```
### 다른 슬라이싱 예시
```python
# 중간 부분 확인
print("중간 1000개:", tokenized_data[1000:2000])
print("마지막 50개:", tokenized_data[-50:])
print("10개씩 건너뛰며 100개:", tokenized_data[:1000:10])
```
## 6. 전체 코드 흐름 다시 정리
### Step by Step 이해
```python
# 1단계: 원본 텍스트
ko_text = "안녕하세요 반갑습니다"
# 2단계: 문자 → 숫자 변환 (일반 리스트)
encoded_list = token_encode(ko_text)
print(f"리스트: {encoded_list}")
# [1909, 1169, 2546, 1770, 2008, 0, 1593, 908, ...]
# 3단계: 리스트 → 텐서 변환
tokenized_data = torch.tensor(encoded_list, dtype=torch.long)
print(f"텐서: {tokenized_data}")
# tensor([1909, 1169, 2546, 1770, 2008, 0, 1593, 908, ...])
# 4단계: 텐서 정보 확인
print(f"모양: {tokenized_data.shape}") # 몇 개인지
print(f"타입: {tokenized_data.dtype}") # 어떤 종류 숫자인지
print(f"일부: {tokenized_data[:10]}") # 처음 10개만 확인
```
### 변환 과정 시각화
```
원본 텍스트: "안녕하세요 반갑습니다"
↓ (문자 → 숫자 변환)
파이썬 리스트: [1909, 1169, 2546, 1770, 2008, 0, 1593, 908, ...]
↓ (torch.tensor)
PyTorch 텐서: tensor([1909, 1169, 2546, 1770, 2008, 0, 1593, 908, ...])
↓ (딥러닝 모델에 입력 가능!)
```
## 7. 실전 예시로 확실히 이해하기
### 작은 예시로 테스트
```python
# 간단한 텍스트로 테스트
test_text = "안녕"
test_encoded = token_encode(test_text)
print(f"1. 원본: '{test_text}'")
print(f"2. 인코딩: {test_encoded}")
test_tensor = torch.tensor(test_encoded, dtype=torch.long)
print(f"3. 텐서: {test_tensor}")
print(f"4. 모양: {test_tensor.shape}")
print(f"5. 타입: {test_tensor.dtype}")
# 역변환으로 검증
decoded = token_decode(test_tensor.tolist())
print(f"6. 복원: '{decoded}'")
print(f"7. 원본과 같은가? {test_text == decoded}")
```
### 배치 처리 예시
```python
# 여러 문장을 한 번에 처리
sentences = ["안녕", "하세요", "반갑습니다"]
batch_tensors = []
for sentence in sentences:
encoded = token_encode(sentence)
tensor = torch.tensor(encoded, dtype=torch.long)
batch_tensors.append(tensor)
print(f"'{sentence}' → {tensor.shape} → {tensor}")
# 결과:
# '안녕' → torch.Size([2]) → tensor([1909, 1169])
# '하세요' → torch.Size([3]) → tensor([2546, 1770, 2008])
# '반갑습니다' → torch.Size([5]) → tensor([1593, 908, 1658, 2151, 1283])
```
## 8. 자주 하는 실수와 해결법
### 실수 1: dtype 불일치
```python
# 잘못된 예시
try:
wrong_tensor = torch.tensor([1.5, 2.7], dtype=torch.long)
print(wrong_tensor) # tensor([1, 2]) - 소수점 부분 손실!
except:
print("에러 발생!")
# 올바른 예시
float_tensor = torch.tensor([1.5, 2.7], dtype=torch.float32)
int_tensor = torch.tensor([1, 2], dtype=torch.long)
```
### 실수 2: 차원 혼동
```python
# 1차원과 2차원 구분
vector = torch.tensor([1, 2, 3]) # 1차원
matrix = torch.tensor([[1, 2, 3]]) # 2차원 (1×3)
print(f"벡터 모양: {vector.shape}") # torch.Size([3])
print(f"행렬 모양: {matrix.shape}") # torch.Size([1, 3])
```
### 실수 3: 빈 데이터 처리
```python
# 빈 텍스트 처리
empty_text = ""
empty_encoded = token_encode(empty_text) # 빈 리스트 []
if empty_encoded: # 리스트가 비어있지 않으면
tensor = torch.tensor(empty_encoded, dtype=torch.long)
else: # 빈 리스트면
tensor = torch.tensor([], dtype=torch.long)
print(f"빈 텐서: {tensor}, 모양: {tensor.shape}")
```
## 9. 핵심 요약
> [!important] 텐서 vs 리스트 핵심 차이점
>
> **일반 리스트**:
> - 단순한 데이터 저장소
> - 수학 연산 불편함
> - GPU 사용 불가
>
> **PyTorch 텐서**:
> - 고속 수학 연산 가능
> - GPU 가속 지원
> - 딥러닝에 최적화
> - 자동 미분 지원
### shape의 의미
- `torch.Size([22162967])`: 1차원에 2천2백만 개 요소
- 데이터의 "모양"을 나타내는 중요한 정보
### dtype의 의미
- `torch.int64` (torch.long): 64비트 정수
- 큰 어휘 사전의 토큰 ID를 안전하게 저장
### 슬라이싱
- `[:100]`: 처음 100개만 확인
- 대용량 데이터를 부분적으로 확인할 때 유용
이제 "그냥 숫자 리스트와 뭐가 다른지" 확실히 이해되셨나요? 텐서는 단순한 숫자 저장소가 아니라 **고성능 계산을 위한 특별한 데이터 구조**입니다!