torch 내부에 있는 함수 끌어쓰기. 특히 torch.view와 size는 많이 쓰므로 알아두는것이 좋음.
unsqueeze같은것도 가끔보임. 하지만 이것들 모두 외울 필요는 없고 느낌(vibe)만 가져다 주면 좋을것임.
torch.randperm(n)0 ~ n-1 까지의 정수들을 무작위로 섞은 텐서 생성import torch
idx = torch.randperm(5)
print(idx) # 예: tensor([2, 4, 0, 1, 3])
torch.full(size, fill_value)size 모양의 텐서를 만들고 모든 값을 fill_value로 채움x = torch.full((2, 3), 7)
print(x)
# tensor([[7, 7, 7],
# [7, 7, 7]])
torch.clamp(input, min, max)[min, max] 범위로 잘라줌x = torch.tensor([1.5, -0.3, 4.2])
x_clamped = torch.clamp(x, min=0, max=1)
print(x_clamped)
# tensor([1.0, 0.0, 1.0])
torch.exp(input)x = torch.tensor([1.0, 2.0, 3.0])
print(torch.exp(x))
# tensor([2.7183, 7.3891, 20.0855])
torch.zeros(size)x = torch.zeros((2, 2))
print(x)
# tensor([[0., 0.],
# [0., 0.]])
torch.tensor(data)x = torch.tensor([[1, 2], [3, 4]])
print(x)
# tensor([[1, 2],
# [3, 4]])
x.view(shape)x = torch.tensor([[1, 2], [3, 4]])
y = x.view(4)
print(y) # tensor([1, 2, 3, 4])
⚠️
view()는 contiguous memory여야 함. 그렇지 않으면.contiguous().view(...)필요함.
x.size()torch.Size 객체)x = torch.tensor([[1, 2], [3, 4]])
print(x.size()) # torch.Size([2, 2])
x.shape도 같음:x.shape == x.size()
좋지! unsqueeze()랑 squeeze()는 텐서의 차원(dimension) 을 다룰 때 자주 쓰이는 함수야. 특히 batch 처리를 위해 차원을 추가하거나 제거할 때 유용해.
torch.unsqueeze(tensor, dim)(C, H, W) → (1, C, H, W)처럼 배치 차원 추가할 때dim은 어디에 차원을 추가할지 지정 (0이면 맨 앞, -1이면 맨 뒤)x = torch.tensor([1, 2, 3])
print(x.shape) # torch.Size([3])
x_unsq = torch.unsqueeze(x, 0) # 차원 추가 (맨 앞에)
print(x_unsq) # tensor([[1, 2, 3]])
print(x_unsq.shape) # torch.Size([1, 3])
x_unsq2 = torch.unsqueeze(x, 1) # 각 원소마다 1차원 추가됨
print(x_unsq2)
# tensor([[1],
# [2],
# [3]])
print(x_unsq2.shape) # torch.Size([3, 1])
⏩
x[None, :]==x.unsqueeze(0)과 동일
torch.squeeze(tensor, dim=None)x = torch.tensor([[[1], [2], [3]]])
print(x.shape) # torch.Size([1, 3, 1])
x_sq = torch.squeeze(x)
print(x_sq) # tensor([1, 2, 3])
print(x_sq.shape) # torch.Size([3])
x_sq2 = torch.squeeze(x, dim=0)
print(x_sq2.shape) # torch.Size([3, 1])
x_sq3 = torch.squeeze(x, dim=2)
print(x_sq3.shape) # torch.Size([1, 3])
크기가 1이 아닌 차원은 절대 삭제되지 않음!
| 함수 | 기능 | 예시 입력 | 출력 |
|---|---|---|---|
unsqueeze(x, 0) | 앞에 차원 추가 | [3] | [1, 3] |
unsqueeze(x, 1) | 중간에 추가 | [3] | [3, 1] |
squeeze(x) | 모든 1 제거 | [1, 3, 1] | [3] |
squeeze(x, dim=2) | 특정 차원만 제거 | [1, 3, 1] | [1, 3] |
x = torch.tensor([[1, 2, 3],
[4, 5, 6]]) # shape: [2, 3]
즉, x.shape == torch.Size([2, 3])
→ 이건 2차원 텐서고 dim=2는 존재하지 않음 ❌
torch.squeeze(x, dim=2) 하면?torch.squeeze(x, dim=2)
# RuntimeError: Dimension out of range (expected to be in range of [-2, 1], but got 2)
squeeze(x, dim=0) 처럼 dim을 줄 때는:
예를 들어:
x = torch.zeros(2, 1, 3) # shape: [2, 1, 3]
x_squeezed = torch.squeeze(x, dim=1)
print(x_squeezed.shape) # torch.Size([2, 3])
| 조건 | squeeze(x, dim=2) 결과 |
|---|---|
x.shape에 dim=2 없음 | ❌ 에러 발생 |
x.shape[2] != 1 | ❌ 에러 발생 |
x.shape[2] == 1 | ✅ 해당 차원 제거됨 |
완전 정확하게 캐치했어!
x.size()와 np.shape()는 거의 같은 기능이야 — 텐서나 배열의 shape 정보를 반환한다는 점에서!
x.size() vs x.size(dim)| 사용법 | 설명 | 예시 |
|---|---|---|
x.size() | 전체 shape 반환 (ex: [2, 3]) | torch.Size([2, 3]) |
x.size(1) | 특정 차원의 크기 반환 | 3 (예: 두 번째 축) |
x = torch.tensor([[1, 2, 3],
[4, 5, 6]]) # shape: [2, 3]
print(x.size()) # torch.Size([2, 3])
print(x.size(0)) # 2 (첫 번째 차원)
print(x.size(1)) # 3 (두 번째 차원)
즉,
x.size(dim)은x.shape[dim]과 같아!
torch vs numpy| 기능 | PyTorch | NumPy |
|---|---|---|
| 전체 shape | x.size() 또는 x.shape | x.shape |
| 특정 dim 크기 | x.size(0) | x.shape[0] |
| 반환 타입 | torch.Size 객체 (튜플처럼 작동) | tuple |
import numpy as np
a = np.array([[1, 2], [3, 4]])
print(a.shape) # (2, 2)
print(a.shape[1]) # 2
x.shape는 PyTorch에서도 튜플처럼 바로 접근 가능해서,x.shape[1] 쓰는 것도 완전 가능해!x.size() == x.shape 라고 보면 돼 (기능적으론 동일)좋아, 이번엔 torch.arange랑 torch.expand에 대해 설명할게!
둘 다 shape 조작이나 텐서 생성에서 엄청 자주 쓰이는 애들이야.
torch.arange(start, end, step)start부터 end 미만까지 step 간격으로 텐서를 생성 range()나 NumPy의 np.arange() 와 유사x = torch.arange(0, 5)
print(x) # tensor([0, 1, 2, 3, 4])
y = torch.arange(1, 10, 2)
print(y) # tensor([1, 3, 5, 7, 9])
dtype=torch.float 지정 가능z = torch.arange(0, 1, 0.1, dtype=torch.float)
print(z) # tensor([0.0, 0.1, ..., 0.9])
torch.expand(*sizes)x = torch.tensor([[1, 2, 3]]) # shape: [1, 3]
x_exp = x.expand(5, 3) # 5개의 같은 row로 확장
print(x_exp)
# 출력:
# tensor([[1, 2, 3],
# [1, 2, 3],
# [1, 2, 3],
# [1, 2, 3],
# [1, 2, 3]])
복사 아님! – 똑같은 메모리를 바라보고 있어 → 효율적임
(단, 쓰기 작업에는 주의 필요)
x = torch.tensor([[1, 2, 3], [4, 5, 6]]) # shape: [2, 3]
x.expand(5, 3) # ❌ RuntimeError 발생!
# 이유: 첫 번째 차원이 2이므로, 확장 불가능 (1이어야 가능)
expand() vs repeat() 비교| 기능 | expand | repeat |
|---|---|---|
| 메모리 공유 | ✅ O | ❌ X (복사됨) |
| 속도 | 더 빠름 | 느림 |
| 제한 조건 | 1인 차원만 확장 | 모든 차원 가능 |
| 쓰기 가능 여부 | ❌ 불가 (읽기 전용) | ✅ 가능 |
expand는 broadcasting처럼 작동하지만, 메모리 효율적임repeat은 진짜 복사, 데이터 조작 시 필요완벽하게 비교해볼게! torch.repeat()과 torch.expand()는 모양은 비슷하게 만들 수 있지만 원리, 메모리 사용 방식, 속도, 제약조건이 완전히 다름!
torch.expand() vs torch.repeat()| 항목 | torch.expand | torch.repeat |
|---|---|---|
| 💡 목적 | 크기 1인 차원을 메모리 공유 방식으로 확장 | 데이터 복제로 새 텐서 생성 |
| 🧠 메모리 | 💾 공유 (No 복사) | 💾 복사 발생 (메모리 증가) |
| 🔁 동작 방식 | broadcasting처럼 보이게 만듦 | 실제로 값을 복제함 |
| ⚠️ 제약 조건 | 확장되는 차원의 크기가 반드시 1이어야 함 | 아무 차원이든 반복 가능 |
| ✍️ 쓰기 가능 여부 | ❌ 원본 수정 불가 (읽기 전용) | ✅ 수정 가능 |
| 🚀 속도 | 더 빠름 (메모리 효율적) | 느림 (복사 오버헤드 있음) |
expand 예시:x = torch.tensor([[1, 2, 3]]) # shape: [1, 3]
x_exp = x.expand(4, 3) # 1st dim을 4로 확장 (원본의 dim=1이므로 가능)
print(x_exp)
# tensor([[1, 2, 3],
# [1, 2, 3],
# [1, 2, 3],
# [1, 2, 3]])
메모리 공유! 값은 반복되지만, 실제 복사된 건 아님.
repeat 예시:x = torch.tensor([[1, 2, 3]]) # shape: [1, 3]
x_rep = x.repeat(4, 1) # 각 행을 4번 복제
print(x_rep)
# tensor([[1, 2, 3],
# [1, 2, 3],
# [1, 2, 3],
# [1, 2, 3]])
모양은
expand와 같지만, 진짜 데이터가 4배 복제됨 → 메모리 낭비
x = torch.tensor([[1, 2, 3]])
x_rep = x.repeat(4, 1)
x_rep[0][0] = 999
x_exp = x.expand(4, 3)
# x_exp[0][0] = 999 # ❌ RuntimeError: cannot write to expanded tensor
print(x_rep) # ✅ 수정됨
# tensor([[999, 2, 3],
# [ 1, 2, 3],
# [ 1, 2, 3],
# [ 1, 2, 3]])
언제 expand()? | 언제 repeat()? |
|---|---|
| 차원 크기가 1이고, 메모리 효율이 중요할 때 | 실제 복제된 데이터가 필요할 때 |
모델 입력 텐서 조정 (ex. [1, C, H, W]) | 데이터 증강처럼 완전한 복제 필요할 때 |
| 연산 속도가 중요하고 읽기 전용이면 좋을 때 | 수정하거나 저장하는 용도면 repeat 사용 |
네! 이번엔 더 자세하게, 그리고 예시까지 들어서 설명해드릴게요.
is_eos = completion_ids == self.processing_class.eos_token_id
device = self.accelerator.device
eos_idx = torch.full((is_eos.size(0),), is_eos.size(1), dtype=torch.long, device=device)
# 각 배치(문장)별로 EOS토큰이 하나라고 있으면 True 없으면 False
# 각 배치별로 처음 등장하는 EOS토큰의 인덱스 반환 , EOS토큰이 있는 문장에 대해서만 인덱스를 실제 EOS위치로 바꿔줌
eos_idx[is_eos.any(dim=1)] = is_eos.int().argmax(dim=1)[is_eos.any(dim=1)]
#시퀀으 인덱스를 만들어줌 [0,1,2,....L-1]을 배치 크기만큼 복제해서 (B,L)텐서로 만듦
sequence_indices = torch.arange(is_eos.size(1), device=device).expand(is_eos.size(0), -1)
completion_mask = (sequence_indices <= eos_idx.unsqueeze(1)).int()
이 코드는 생성된 토큰 시퀀스(completion_ids)에서
처음 등장하는 EOS(End Of Sequence, 시퀀스 끝) 토큰 이후의 모든 토큰을 마스킹(무시)하기 위한 마스크(completion_mask)를 만드는 과정입니다.
즉,
is_eos = completion_ids == self.processing_class.eos_token_id
completion_ids: (B, L) — 배치 크기 B, 시퀀스 길이 Lself.processing_class.eos_token_id: EOS 토큰의 idis_eos는 (B, L) 크기의 bool 텐서device = self.accelerator.device
eos_idx = torch.full((is_eos.size(0),), is_eos.size(1), dtype=torch.long, device=device)
eos_idx[is_eos.any(dim=1)] = is_eos.int().argmax(dim=1)[is_eos.any(dim=1)]
is_eos.any(dim=1): is_eos.int().argmax(dim=1): [is_eos.any(dim=1)]: sequence_indices = torch.arange(is_eos.size(1), device=device).expand(is_eos.size(0), -1)
[0, 1, 2, ..., L-1]을 배치 크기만큼 복제해서 (B, L) 텐서로 만듦completion_mask = (sequence_indices <= eos_idx.unsqueeze(1)).int()
.unsqueeze(1)로 (B, 1)로 만들어 브로드캐스팅completion_ids = [
[10, 20, 99, 30, 40, 50], # 3번째(인덱스2)에 EOS
[11, 22, 33, 44, 55, 66] # EOS 없음
]
is_eos =
[
[False, False, True, False, False, False],
[False, False, False, False, False, False]
]
eos_idx = [6, 6] # (B,) 모두 6으로 시작
첫 번째 문장: EOS 있음 → True
두 번째 문장: EOS 없음 → False
is_eos.int().argmax(dim=1)
→ [2, 0] (첫 번째 문장은 2번째 위치에 EOS, 두 번째 문장은 없음→0)
is_eos.any(dim=1)
→ [True, False]
적용 결과:
eos_idx = [2, 6]
sequence_indices =
[
[0, 1, 2, 3, 4, 5],
[0, 1, 2, 3, 4, 5]
]
completion_mask =
[
[1, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 1]
]