# 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개만 확인 - 대용량 데이터를 부분적으로 확인할 때 유용 이제 "그냥 숫자 리스트와 뭐가 다른지" 확실히 이해되셨나요? 텐서는 단순한 숫자 저장소가 아니라 **고성능 계산을 위한 특별한 데이터 구조**입니다!