[Outbreak Breaker] 스테이지 타임라인 시스템 확장, 일시정지 메뉴 로직 연결, GA 폴더 정리 오늘은 커밋 세 개를 올렸다. 어제 레이아웃까지 만들어둔 일시정지 메뉴에 실제 로직을 붙였고, GameplayAbility 애셋 폴더 구조를 미리 정리했고, 가장 크게는 스폰 매니저를 BGM/아이템까지 포괄하는 통합 타임라인 시스템으로 확장했다. 1. GameplayAbility 애셋 폴더 구조 정리 본격적으로 무기별 특수 스킬을 늘려나가기 전에 어빌리티 폴더부터 정리했다. GA_ForcedMovement 를 GA_Knockback 으로 리네임했다. 넉백이랑 끌어당기기(풀)를 나중에 분리할 계획이라, 이름부터 역할을 명확히 해두는 게 맞다고 판단했다. 끌어당기는 쪽은 나중에 "블랙홀" 같은 연출의 별도 GA로 만들 예정이다. GameplayAbilites/ 밑에 CC/ (GA_Stun, GA_Knockback)랑 Player/ (GA_Dodge) 폴더를 새로 만들어서 분류했다. 무기 공용으로 쓰는 GA_Weapon_Equip 은 Weapon 폴더 루트에 그대로 두고, 무기별로 GravityHammer/ , PlasmaRifle/ , SpreadShotgun/ , DefenseDrone/ , MagnetMine/ 서브폴더를 새로 만들어서 각 무기의 Active/Passive 어빌리티를 담았다. 지금 당장 쓰는 건 아니지만, 나중에 무기별 "선택 상태" 전용 특수 스킬을 추가할 때 자리가 미리 있어야 헷갈리지 않을 것 같아서 먼저 손봐뒀다. 2. 일시정지 메뉴 로직 연결 및 키 리바인딩 안전장치 어제 만든 레이아웃 기준으로 실제 토글 로직을 붙였다. UOBOverlayWidgetController::TogglePauseMenu() 를 추가해서, 위젯이 이미 떠 있으면 제거 + 게임/BGM 언폰즈, 없으면 생성 + 게임/BGM 폰즈를 처리하도록 했다. 생성 시 Z-Order는 10으로 깔아서, 그 위에 뜨는 설정 창(Z-Order 20)이 항상 일시정지 메뉴보다 위에 보이도록 했다. IA_PauseMenu 인풋 액션에 "Trigger When Paused"를 켜서, 게임이 멈춰 있어도 ESC 입력이 제대로 들어오도록 했다. EOBOverlayWidgetType 에도 PauseMenu 항목을 추가했다. 겸사겸사 키 리바인딩 안전장치도 추가했다. UOBGameSettings::LockedKeys (Escape, Tab, LeftCommand, Tilde, LeftAlt)랑 IsKeyLocked() 를 만들어서 RebindKey() 맨 앞에서 체크하게 했다 — 다른 액션을 이 예약된 키들로 리바인딩하지 못하게 막는 용도다. Pause 액션 자체는 Player Mappable Key Settings에 등록돼 있지 않아서 리바인딩 UI에는 애초에 노출되지 않는데, 이번 보호 로직은 그것과는 별개로 "다른 액션이 예약된 키를 가로채는 것"을 막는 데 집중한 거다. 계속하기/설정/타이틀로 돌아가기/게임 종료 네 항목 전부 로직 연결은 끝냈다. UI 쪽은 일단 지금 쓰던 기본 UMG 방식 그대로 두고, 나중에 CommonUI를 기존에 쓰던 MVVM 패턴이랑 합쳐서 적용할 계획이라 그 부분만 미정이다. 3. 스폰 매니저 → 스테이지 타임라인 시스템으로 확장 (오늘 제일 크게 건드린 부분) AOBSpawnManager 를 AOBStageTimelineManager 로 리네임하면서 역할 자체를 확장했다. 기존엔 적 스폰만 담당하던 걸, BGM 스케줄이랑 아이템 스폰까지 포괄하는 시간 기반 중앙 타임라인으로 바꿨다. 핵심은 TotalElapsedTime 이라는 클럭 하나를 적 스폰/BGM/아이템 타임라인이 전부 공유한다는 것 — 세 타임라인이 서로 다른 시계를 쓰면서 미묘하게 어긋나는 걸 원천적으로 방지하는 설계다. BGM 스케줄 : FRuntimeBGMRow (TriggerTime + BGMType + bTriggered)를 만들어서 지정 시점에 BGM이 딱 한 번만 발동하도록 했다. CheckBGMTimelineLoop() 에서 매 틱 테이블을 돌면서 조건을 체크한다. 기존엔 HandleGameStart() 에서 고정적으로 Zone1 BGM을 트는 식이었는데, 이제는 테이블 기반으로 시점별 BGM 전환을 데이터로 관리할 수 있다. 아이템 스폰 : FRuntimeItemSpawnRow (StartTime + SpawnAmount)로 시점/수량을 정하고, 실제 아이템 종류는 FOBItemSpawnEntry (ItemPoolTag + Weight) 가중치 랜덤 풀에서 누적합 방식으로 뽑는다( GetRandomItemPool() ). 적 스폰과 아이템 스폰이 좌표를 뽑는 로직이 사실상 똑같아서, GetRandomSpawnLocation(UsedIndices) 로 공용 함수화했다. "같은 웨이브 안에서 좌표가 완전히 겹치는 것만 회피" 하는 로직을 그대로 재사용. 스테이지별 데이터 구성(스폰 테이블/BGM 테이블/아이템 테이블/아이템 풀)은 FOBStageTimelineData 구조체 하나로 묶어서 UOBGameSettings::StageTimelineMap ( TMap<EOBLevelType, FOBStageTimelineData> )에 데이터 기반으로 등록했다. AOBGameModeBase::GetSelectedStageType() 게터를 추가해서, InitializeStageTimelineManager() 에서 현재 스테이지에 맞는 테이블/풀을 Settings에서 가져와 캐싱하고 3개 테이블 전부 bTriggered / LastSpawnTime 을 리셋하도록 했다. DataTable Row 전용 구조체( FRuntimeBGMRow , FRuntimeItemSpawnRow 등)는 OBStageTimelineRows.h 로 따로 분리했다. Settings 쪽이 알 필요 없는, 매니저 내부에서만 쓰는 타입이라 분리해두는 게 맞다고 판단. 리네임 작업 자체는 CoreRedirect를 쓰는 대신, 레벨에 배치된 기존 액터를 지우고 새 클래스로 다시 배치하는 방식으로 처리했다. 덤으로 EOBBGMType BGMType 필드가 초기화 안 돼 있어서 에디터 시작할 때마다 뜨던 EnumProperty 에러도 기본값(Title)을 명시해서 같이 고쳤다. 다음에 할 것 일시정지 메뉴 UI는 지금 UMG 방식 그대로 유지하다가, CommonUI를 기존 MVVM 패턴이랑 합쳐서 적용할 예정이라 시기는 미정 스테이지 타임라인 시스템 기반으로 실제 Stage1 데이터(BGM 스케줄, 아이템 스폰 타이밍) 밸런싱 테스트 젬 경험치 레이스 컨디션은 계속 인게임에서 재발 여부 확인 중
오늘 한 것 최종 프로젝트(3) 오늘 배운 점 & 느낀 점 오늘 배운 점은 통신 부분을 InGameClientBase 같은 추상 컴포넌트로 분리하면 씬 인스펙터에서 Mock과 실제 서버 클라이언트를 갈아 끼울 수 있고, 스크립트는 여러 씬이 공유해서 이름을 바꾸면 모든 씬의 컴포넌트가 같이 바뀌며, 패킷 수신 등록은 Awake에서 먼저 해 두고 네트워크 콜백은 메인 스레드로 넘겨 처리해야 한다는 걸 배웠다. 작은 문제점이 있었는데 실제 서버에서는 로그인과 매칭이 끝나야 내 userId와 gameId가 정해지는데, 게임 상태를 Awake에서 바로 초기화하고 있어서 매칭이 끝날 때까지 시작을 미루는 구조(Begin On Awake)를 따로 만들어야 했다. 내일 할 일 최종 프로젝트(3) 복습
코딩테스트에서 좌표와 선분이 등장하면 기울기를 먼저 떠올리기 쉽다. 예를 들어 두 선분이 평행한지 확인하려면 각 선분의 기울기를 계산해 비교할 수 있다. double slope1 = (double) dy1 / dx1; double slope2 = (double) dy2 / dx2; 하지만 이 방식에는 몇 가지 문제가 있다. dx == 0 인 수직선을 별도로 처리해야 한다. 실수 연산을 사용하면 오차를 고려해야 한다. 이럴 때 사용할 수 있는 것이 외적(Cross Product) 이다. 외적은 단순히 두 벡터가 평행한지를 확인하는 것에서 끝나지 않는다. 외적의 부호 를 이용하면 세 점이 어느 방향으로 놓여 있는지를 판단할 수 있고, 이것이 CCW(Counter Clockwise) 알고리즘이다. 그리고 CCW를 이용하면 두 선분이 서로 교차하는지도 판별할 수 있다. 이 글에서는 다음 흐름으로 정리해본다. 외적 → CCW → 선분 교차 판정 1. 외적 복습 2차원 벡터 $\overrightarrow{a}=(x_1,y_1)$, $\overrightarrow{b}=(x_2,y_2)$가 있을 때 외적은 다음과 같이 계산할 수 있다. $$ \overrightarrow{a}\times\overrightarrow{b} = x_1y_2-y_1x_2 $$ 외적의 크기는 두 벡터가 만드는 평행사변형의 넓이와 같다. $$ \left|\overrightarrow{a}\times\overrightarrow{b}\right| = \left|\overrightarrow{a}\right| \left|\overrightarrow{b}\right| \sin\theta $$ 두 벡터가 평행하다면 두 벡터 사이의 각도는 $0^\circ$ 또는 $180^\circ$이다. $$ \sin 0^\circ = 0 $$ $$ \sin 180^\circ = 0 $$ 따라서 평행한 두 벡터의 외적은 0이다. $$ \overrightarrow{a}\times\overrightarrow{b}=0 $$ 코드에서는 다음처럼 확인할 수 있다. long cross = x1 * y2 - y1 * x2; if (cross == 0) { // 두 벡터는 평행 } 기울기를 나눠서 비교하지 않기 때문에 수직선이나 실수 오차를 별도로 처리할 필요가 없다. 하지만 외적이 알려주는 정보는 단순히 0인가 아닌가 에서 끝나지 않는다. 2. 외적의 부호는 방향을 나타낸다 두 벡터의 외적 결과에는 부호가 있다. 외적 결과 의미 > 0 반시계 방향 < 0 시계 방향 = 0 일직선 즉 외적은 두 벡터가 평행한지를 판별할 뿐 아니라, 한 벡터에서 다른 벡터로 이동할 때 어느 방향으로 꺾이는지도 알려준다. 이 성질을 세 점에 적용한 것이 CCW 다. 3. CCW란? CCW는 Counter Clockwise 의 약자다. 세 점 A , B , C 가 있을 때 A → B → C 순서로 이동하면 진행 방향이 다음 중 무엇인지 판별한다. 반시계 방향 시계 방향 일직선 예를 들어 다음과 같은 경우를 생각해보자. C / A ── B A → B 로 이동한 뒤 C로 향하려면 반시계 방향으로 꺾어야 한다. 반대로 다음과 같다면 시계 방향이다. A ── B \ C 4. CCW는 어떻게 계산할까? 세 점을 다음과 같이 두자. $A=(A_x,A_y)$ $B=(B_x,B_y)$ $C=(C_x,C_y)$ A를 기준점으로 두 개의 벡터를 만든다. $$ \overrightarrow{AB} = (B_x-A_x,\ B_y-A_y) $$ $$ \overrightarrow{AC} = (C_x-A_x,\ C_y-A_y) $$ 그리고 두 벡터를 외적한다. $$ \overrightarrow{AB}\times\overrightarrow{AC} $$ 좌표로 풀어쓰면 다음과 같다. $$ (B_x-A_x)(C_y-A_y) - (B_y-A_y)(C_x-A_x) $$ 결과의 부호에 따라 방향을 판단할 수 있다. CCW(A, B, C) > 0 → 반시계 방향 CCW(A, B, C) < 0 → 시계 방향 CCW(A, B, C) = 0 → 일직선 Java로 구현하면 다음과 같다. static long ccw(Point a, Point b, Point c) { return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); } 방향만 필요하다면 외적의 실제 값 대신 -1 , 0 , 1 만 반환하도록 만들 수도 있다. static int ccw(Point a, Point b, Point c) { long cross = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); return Long.compare(cross, 0); } 반환값의 의미는 다음과 같다. 1 → 반시계 0 → 일직선 -1 → 시계 CCW는 별개의 복잡한 공식이라기보다 외적의 부호를 세 점의 방향 관계에 적용한 것 이라고 볼 수 있다. 5. CCW로 선분 교차 판정하기 이제 네 점 A , B , C , D 가 있고 두 선분 AB , CD 가 있다고 하자. 두 선분이 서로 가로질러 교차하려면 다음 두 조건을 모두 만족해야 한다. C와 D가 직선 AB의 서로 반대쪽에 있어야 한다. A와 B가 직선 CD의 서로 반대쪽에 있어야 한다. 6. C와 D가 AB의 서로 반대편에 있는지 확인 직선 AB를 기준으로 C와 D의 방향을 각각 확인한다. ccw(A, B, C); ccw(A, B, D); C와 D가 AB의 서로 반대편에 있다면 두 CCW 결과의 부호도 서로 다르다. 따라서 다음 조건을 만족한다. $$ CCW(A,B,C)\times CCW(A,B,D)<0 $$ 예를 들어 다음과 같은 형태다. C | A --+-- B | D 7. 한쪽만 검사하면 안 되는 이유 처음에는 다음 조건만 확인하면 될 것처럼 보인다. ccw(A, B, C) * ccw(A, B, D) < 0 하지만 이것만으로는 부족하다. C와 D를 잇는 선분이 실제 선분 AB가 아니라, AB를 무한히 연장한 직선 과 만날 수도 있기 때문이다. 따라서 반대 방향에서도 확인해야 한다. 즉 A와 B가 직선 CD의 서로 반대편에 있는지도 검사한다. $$ CCW(C,D,A)\times CCW(C,D,B)<0 $$ 결국 일반적인 교차 조건은 다음 두 조건을 모두 만족하는 것이다. $$ CCW(A,B,C)\times CCW(A,B,D)<0 $$ $$ CCW(C,D,A)\times CCW(C,D,B)<0 $$ 코드로 표현하면 다음과 같다. boolean intersect = ccw(a, b, c) * ccw(a, b, d) < 0 && ccw(c, d, a) * ccw(c, d, b) < 0; 핵심은 두 선분 각각을 기준으로 상대 선분의 두 끝점이 서로 반대편에 있는지 검사한다는 것 이다. 8. 선분의 끝점이 닿는 경우 두 선분이 정확히 한 끝점에서 만나는 경우도 교차로 취급한다고 하자. A ------ B | C 이 경우 하나 이상의 CCW 결과가 0이 된다. 따라서 < 0 이 아니라 <= 0 으로 조건을 확장해야 한다. $$ CCW(A,B,C)\times CCW(A,B,D)\leq0 $$ $$ CCW(C,D,A)\times CCW(C,D,B)\leq0 $$ 하지만 여기서 새로운 예외가 생긴다. 9. 네 점이 일직선 위에 있는 경우 다음 두 선분을 생각해보자. A ----- B C ----- D 두 선분은 서로 떨어져 있다. 하지만 네 점이 모두 같은 직선 위에 있기 때문에 모든 CCW 결과가 0이 된다. ccw(a, b, c) == 0; ccw(a, b, d) == 0; ccw(c, d, a) == 0; ccw(c, d, b) == 0; 따라서 단순히 다음 조건만 검사하면 ab <= 0 && cd <= 0 두 선분이 교차한다고 잘못 판단하게 된다. 즉 CCW는 두 선분이 같은 직선 위에 있다는 사실까지만 알려줄 뿐, 실제 구간이 겹치는지는 알려주지 않는다. 그래서 두 선분이 모두 일직선 위에 있는 경우에는 실제 구간이 겹치는지 추가로 확인해야 한다. 10. 일직선 선분의 겹침 판정 두 선분을 좌표 순서대로 정렬했다고 생각해보자. A -------- B C -------- D 두 선분이 겹치려면 다음 조건을 만족해야 한다. A <= D C <= B 반대로 A ----- B C ----- D 처럼 B < C 라면 두 선분은 떨어져 있다. 2차원 좌표에서는 점을 (x, y) 의 사전식 순서로 비교할 수 있다. static int compare(Point a, Point b) { if (a.x != b.x) { return Long.compare(a.x, b.x); } return Long.compare(a.y, b.y); } 따라서 선분 교차 판정은 크게 두 단계로 생각할 수 있다. 1. CCW를 이용해 두 선분의 방향 관계를 확인한다. 2. 네 점이 모두 일직선이라면 실제 선분 구간이 겹치는지 추가로 확인한다. 11. 전체 Java 구현 class Point { long x; long y; Point(long x, long y) { this.x = x; this.y = y; } } public class Geometry { static int ccw(Point a, Point b, Point c) { long cross = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); return Long.compare(cross, 0); } static boolean intersects(Point a, Point b, Point c, Point d) { int abC = ccw(a, b, c); int abD = ccw(a, b, d); int cdA = ccw(c, d, a); int cdB = ccw(c, d, b); int ab = abC * abD; int cd = cdA * cdB; // 네 점이 모두 일직선 위에 있는 경우 if (ab == 0 && cd == 0) { if (compare(a, b) > 0) { Point temp = a; a = b; b = temp; } if (compare(c, d) > 0) { Point temp = c; c = d; d = temp; } return compare(a, d) <= 0 && compare(c, b) <= 0; } return ab <= 0 && cd <= 0; } static int compare(Point a, Point b) { if (a.x != b.x) { return Long.compare(a.x, b.x); } return Long.compare(a.y, b.y); } } 12. 왜 long 을 사용하는가? CCW 계산에서는 다음과 같은 곱셈이 발생한다. (b.x - a.x) * (c.y - a.y) 각 좌표가 int 범위 안에 있다고 해서 곱셈 결과도 int 범위 안에 있는 것은 아니다. 예를 들어 값이 100,000 정도라면 100,000 × 100,000 = 10,000,000,000 이 되어 이미 int 의 최대값을 넘어간다. 따라서 좌표 범위가 크다면 CCW 계산은 long 으로 처리하는 것이 안전하다. 또 위 구현처럼 ccw() 에서 실제 외적 값을 그대로 반환하지 않고 -1 , 0 , 1 로 정규화해 반환하면 이후 ccw(a, b, c) * ccw(a, b, d) 계산에서도 오버플로를 걱정할 필요가 없다. 13. 정리 처음에는 외적을 단순히 평행 여부를 판별하는 공식으로 생각할 수 있다. 외적 == 0 → 두 벡터가 평행 하지만 외적의 부호 까지 보면 더 많은 정보를 얻을 수 있다. 외적 > 0 → 반시계 방향 외적 < 0 → 시계 방향 외적 == 0 → 일직선 이 성질을 세 점에 적용한 것이 CCW 다. 그리고 CCW를 두 선분 각각의 관점에서 적용하면 선분 교차 여부를 판별할 수 있다. 외적 ↓ 방향 판별 ↓ CCW ↓ 선분 교차 판정 CCW와 선분 교차 판정을 각각 독립된 공식으로 외우기보다, 결국 모든 것이 다음 외적 계산에서 출발한다는 점을 이해하는 것이 중요하다. $$ (x_1,y_1)\times(x_2,y_2) = x_1y_2-y_1x_2 $$ 외적의 0 여부와 부호가 무엇을 의미하는지 이해하면 CCW와 선분 교차 판정까지 자연스럽게 연결된다. 특히 코딩테스트에서는 기울기를 직접 계산하는 것보다 외적을 사용하면 다음과 같은 장점이 있다. 나눗셈이 필요 없다. 수직선을 별도로 처리하지 않아도 된다. 실수 오차를 피할 수 있다. 좌표 문제에서 방향이나 교차 여부를 판단해야 한다면 기울기보다 외적과 CCW를 먼저 떠올려볼 수 있다.
토큰화와 임베딩을 따로 공부할 때는 각각의 역할만 봤는데, 이번 장에서는 둘이 LSTM과 연결된다. 리뷰 문장 하나가 들어와서 긍정 또는 부정이라는 결과로 나오는 흐름을 따라가 봤다. 이 글은 교재 코드를 읽고 정리한 리뷰다. 전체 데이터를 내려받아 학습을 실행한 실험 기록은 아니다. 전체 흐름부터 잡기 교재는 네이버 영화 리뷰의 긍정·부정 분류를 다룬다. 긍정은 1, 부정은 0이다. 중복과 결측치를 정리하고, 한글 중심으로 정제한 뒤 Mecab 토큰화와 불용어 제거를 수행한다. 이후 학습 데이터에서 단어 사전을 만들고 정수 인코딩한다. 이 장에서는 <PAD> 가 0, <UNK> 가 1이다. 앞서 본 예제와 번호가 다르므로 그대로 가정하면 안 되겠다. 리뷰는 길이 30으로 패딩하거나 자르고, Embedding → LSTM → Linear를 거쳐 두 클래스의 점수를 얻는다. 검증 손실이 가장 낮은 가중치를 저장한 뒤 테스트한다. 교재에 제시된 테스트 정확도는 84.92%다. WikiDocs 영화 리뷰 분류 챕터 이번에는 모델 이름을 외우는 것보다 각 단계에서 데이터가 어떤 모습인지 적어 보는 쪽이 더 도움이 됐다. 텐서 크기로 연결해 보기 배치 4개, 길이 7, 임베딩 크기 6, 은닉 크기 8인 작은 예를 따로 생각해 봤다. 패딩이 없는 동일 길이 입력의 구조 확인용 코드이며, 학습 성능을 측정하는 예제는 아니다. import torch import torch.nn as nn x = torch.tensor([ [2, 3, 4, 5, 6, 7, 8], [3, 4, 5, 6, 7, 8, 9], [4, 5, 6, 7, 8, 9, 2], [5, 6, 7, 8, 9, 2, 3], ], dtype=torch.long) embedding = nn.Embedding(10, 6) lstm = nn.LSTM(6, 8, batch_first=True) classifier = nn.Linear(8, 2) embedded = embedding(x) sequence_output, (h_n, c_n) = lstm(embedded) logits = classifier(h_n[0]) # 예상되는 크기 print(embedded.shape) # torch.Size([4, 7, 6]) print(sequence_output.shape) # torch.Size([4, 7, 8]) print(h_n.shape) # torch.Size([1, 4, 8]) print(logits.shape) # torch.Size([4, 2]) Embedding에서는 ID마다 벡터가 붙는다. LSTM의 sequence_output 에는 모든 시점의 은닉 상태가 있고, h_n 에는 마지막 은닉 상태가 있다. 위 코드는 단방향·1층이므로 h_n[0] 의 크기는 (4, 8) 이다. 이를 Linear에 넣으면 리뷰마다 두 점수를 얻는다. PyTorch Embedding 문서 , PyTorch LSTM 문서 여기서 “LSTM을 지나면 무조건 2차원이 된다”라고 기억하면 틀린다. 모든 시점의 출력과 마지막 상태를 구분해야 한다. 점수와 확률을 구분하기 logits 는 아직 확률로 정규화하지 않은 점수다. CrossEntropyLoss 에는 softmax를 미리 적용하지 않고 이 점수를 그대로 넣는다. 클래스 ID를 정답으로 사용하는 경우 정답은 배치 크기만큼의 정수 텐서다. labels = torch.tensor([1, 0, 1, 0], dtype=torch.long) loss = nn.CrossEntropyLoss()(logits, labels) predicted = logits.argmax(dim=1) dim=1 은 각 리뷰의 클래스 점수 두 개 중 큰 값의 위치를 찾는다는 뜻이다. (4, 2) 에서 리뷰별 예측 ID 네 개를 얻는다. 확률이 필요할 때는 별도로 softmax를 사용할 수 있지만, 손실 계산의 입력과는 구분해 둔다. PyTorch CrossEntropyLoss 문서 코드에서 더 확인하고 싶은 부분 교재는 뒤쪽에 패딩을 붙이고 최종 은닉 상태를 사용한다. 그런데 모델의 Embedding에는 padding_idx 가 없고, 예측 함수는 학습 때와 달리 동일한 정제·길이 맞춤을 하지 않는다. 이 차이는 직접 재현할 때 점검하고 싶다. 교재 코드 특히 padding_idx 를 지정하는 것과 LSTM이 패딩 시점을 건너뛰는 것은 별개다. 실제 길이를 전달하는 pack_padded_sequence 를 사용하면 가변 길이 시퀀스를 PackedSequence로 만들 수 있다. LSTM은 이런 입력도 받는다. 내 다음 실습에서는 패딩을 처리한 마지막 상태와 실제 문장 끝의 상태를 구분해 비교해 보려고 한다. PyTorch 패킹 문서 , LSTM 문서 다음 실습에서 남길 기록 이번 장을 읽고 나니 정확도 숫자 하나보다 확인할 항목이 먼저 보였다. 직접 실행할 때는 전처리 전후의 샘플, 사전에 없는 토큰의 비율, 잘리는 리뷰, 오분류 문장을 함께 기록하려고 한다. 학습이 돌아간다는 것과 내가 의도한 입력을 모델에 넣었다는 것은 별도로 확인해야겠다. 다음에는 긴 코드를 한 번에 따라 치기보다, 리뷰 하나가 토큰과 ID를 거쳐 점수로 바뀌는 과정을 먼저 출력해 보고 싶다.
Construction sites often involve more than simply moving materials from one point to another. Soil, aggregate, debris, concrete components, and other supplies may need to be lifted and placed at specific distances or heights. In these situations, the reach of loading equipment becomes an important consideration. A machine with suitable reach can position material without constant repositioning, which may be especially useful when working areas are crowded or loading points are difficult to approach directly. However, reach should always be considered alongside capacity, stability, site dimensions, and the type of material being handled. Understanding the Importance of Reach Reach describes how far a machine can extend its working equipment from its operating position. In practical construction work, this can affect where material can be placed and how frequently the machine needs to move. Consider a site where excavated material must be transferred into trucks positioned beside an excavation area. If the loading machine cannot reach the truck comfortably, it may need to reposition repeatedly. That extra movement can consume working time and make the site layout more complicated. Greater reach can therefore provide an advantage when the distance between the machine and the loading point is significant. The exact requirement depends on the site's layout and the task being performed. Situations Where Extended Reach Helps Several construction activities can benefit from equipment that provides greater working reach. Material may need to be loaded into trucks, transferred over barriers, placed into stockpiles, or moved around obstacles. On large development sites, the loading area may also be separated from the material source by temporary structures or other site activities. Reach can become particularly useful when there is limited room for repositioning. For example, if several machines are operating in the same area, repeatedly moving a loader may create unnecessary congestion. Equipment capable of reaching the required point from a stable position can make material handling more straightforward. However, extended reach should be selected because the project needs it, not simply because a machine offers a larger working range. Capacity Still Matters Reach alone does not determine whether a loader is suitable. As a load moves farther from the machine, operating conditions can change. The equipment must remain appropriate for the weight and type of material being handled at the required position. This means the maximum rated capacity should be examined alongside the expected load and working distance. Bucket size is another consideration. A larger bucket may move more material during each cycle, but its suitability depends on the machine's capacity and the density of the material. The goal is to find a combination of reach and capacity that corresponds with the actual operation. Examine the Site Layout Before arranging equipment, look carefully at where materials will be collected, loaded, and deposited. Identify the excavation zone, stockpile area, truck position, access routes, and other active work areas. This gives a clearer picture of how far the machine actually needs to reach. A site with plenty of open space may allow frequent repositioning without major inconvenience. A restricted site may benefit more from equipment that can cover a larger working area from one position. Temporary structures and stored materials should also be included in the assessment. Good site planning can sometimes reduce the amount of reach required simply by improving the positioning of materials and vehicles. Selecting Equipment for Abu Dhabi Conditions Construction projects in Abu Dhabi can differ considerably in scale, layout, and working environment. Equipment should therefore be selected according to the conditions of the specific project. For businesses arranging Boom Loader Rental in Abu Dhabi , the useful question is how the machine's working range corresponds with the planned loading operation. Consider the required reach, load weight, bucket or attachment configuration, ground conditions, available maneuvering space, and expected rental duration. These factors provide a clearer basis for selecting equipment than relying on a single specification. Transportation should also be discussed before booking, particularly when larger machinery needs to be delivered to a busy or restricted site. Think About Material Characteristics Not all loads behave in the same way. Loose soil, gravel, aggregate, debris, and packaged materials can have different weights and handling requirements. The machine configuration should reflect the material being moved. The size and shape of the material can influence bucket selection as well. Certain loads may require specialized attachments rather than a standard bucket. Before equipment arrives, identify the materials that will be handled most frequently and communicate this information to the rental provider. This can help ensure that the selected machine is equipped for the intended work. Consider the Working Height Reach is not limited to horizontal distance. The height at which materials need to be positioned can also influence equipment selection. Loading material into a high-sided truck, reaching an elevated stockpile, or placing material at a raised location may require a different working configuration from ground-level loading. Review both the required height and distance together. The machine should be capable of reaching the intended position while maintaining appropriate operating capacity. Looking only at maximum horizontal reach can result in an incomplete assessment. Coordinate With Trucks and Other Machinery Loading equipment rarely works independently. If an excavator supplies material to a loader, and the loader transfers that material into trucks, the timing of all three activities can affect productivity. Truck positioning is particularly important. Vehicles should be placed where the loading machine can reach them efficiently without interfering with other site operations. If the loader must repeatedly wait for trucks to arrive, its potential capacity is not being fully utilized. A coordinated loading area can reduce unnecessary movement and keep material flowing through the site more consistently. Assess Ground Conditions The surface on which loading machinery operates should provide suitable support for the planned activity. Uneven ground, loose soil, slopes, or recently disturbed surfaces can affect machine movement and positioning. Before selecting equipment, determine whether the work will take place on prepared ground, paved surfaces, compacted soil, or another type of terrain. If the working area needs preparation before machinery arrives, include that activity in the project schedule. Stable positioning is particularly important when the machine is handling substantial loads at extended reach. Look at Attachments and Configuration A loader's usefulness can depend heavily on how it is configured. Bucket dimensions, attachment type, hydraulic capabilities, and other machine characteristics should correspond with the material and operation. A standard configuration may be sufficient for moving loose soil, while another task could require a different attachment. Discuss the full scope of work before finalizing the rental. This allows the equipment provider to identify a configuration that supports the required activities. Avoid selecting attachments simply because they are available. Each component should have a practical purpose within the project. Estimate the Rental Period The expected duration of the loading operation should be established before equipment is booked. Short-term material movement may require only a few working days, while major earthmoving projects could need loading machinery for considerably longer. Consider the construction sequence and identify when the machine will actually be productive. If loading is delayed because excavation is incomplete or trucks are unavailable, the rental period may extend without equivalent productivity. A realistic schedule helps align machinery costs with actual site usage. Keep Transportation in Mind Equipment with greater capacity or reach may require more careful transportation planning. Confirm the machine's delivery requirements, unloading location, and access route before the rental begins. The site entrance should provide sufficient space for the transport vehicle and machinery. Once unloaded, there should also be a clear route to the working area. Planning these details ahead of time reduces the possibility of delivery delays. Collection arrangements should be considered as well, particularly when the equipment is needed only during a specific phase of the project. Use Reach Where It Provides Practical Value Greater loading reach can make certain construction operations more convenient by reducing unnecessary repositioning and allowing material to be placed farther from the machine. Its value, however, depends on the complete working situation. Load capacity, material characteristics, height, ground conditions, attachments, site layout, and machine positioning all contribute to effective performance. Before selecting equipment, map out the actual movement of materials and determine where the machine needs to work from. This makes it easier to identify the required reach rather than choosing a machine based on a specification alone. When equipment is matched closely with the loading environment, extended reach can become a practical part of a well-organized construction process, supporting smoother material movement while reducing avoidable repositioning around the site.
큰 HTTP 응답의 segmentation TCP/UDP를 직접 통신시켜 차이 체감하기 DNS Query/Response HTTP와 HTTPS 비교 Ethernet / IP / TCP / HTTP encapsulation 분석 Delay / RTT / Throughput / Packet Loss 측정하기
지난번에는 문장을 토큰으로 나누는 방법을 봤다. 이번에는 나눈 토큰을 모델에서 사용할 벡터로 바꾸는 단계다. 이름만 보면 복잡한 계산이 숨어 있을 것 같았는데, 먼저 테이블의 행을 꺼내는 과정으로 보니 이해하기 쉬웠다. 정수 번호와 임베딩 벡터는 다르다 이번 챕터는 사전 훈련된 벡터를 가져오는 대신, 임베딩 층을 만들고 학습하는 방법을 다룬다. 각 단어에 정수 ID를 부여하고, 그 ID에 해당하는 행을 임베딩 테이블에서 찾는다. ID 자체가 단어의 의미를 나타내는 벡터는 아니다. 벡터를 찾기 위한 번호라고 생각하면 된다. nn.Embedding 의 입력을 원-핫 벡터로 만들 필요도 없다. 정수 인덱스를 그대로 넣으면 해당 행의 밀집 벡터를 반환한다. 테이블의 값은 모델이 풀려는 작업의 학습 과정에서 갱신된다. WikiDocs nn.Embedding 챕터 두 숫자가 각각 무엇을 정하는가 embedding = nn.Embedding(num_embeddings=5, embedding_dim=3) 이 설정은 행이 5개이고, 각 행에 숫자가 3개 있는 테이블을 만든다. num_embeddings 는 등록할 항목 수, embedding_dim 은 벡터 하나의 크기다. 생성 직후의 일반 행은 무작위로 초기화된다. 따라서 층을 만들었다고 곧바로 의미 있는 단어 관계가 완성되는 건 아니다. PyTorch Embedding 공식 문서 작은 단어 사전으로 따라가 보기 동작과 크기를 확인하기 위한 예제를 따로 만들었다. 아래 주석의 shape는 구조상 예상되는 값이며, 학습 실험을 실행한 결과는 아니다. import torch import torch.nn as nn vocab = {"<unk>": 0, "<pad>": 1, "오늘": 2, "책": 3, "읽기": 4} embedding = nn.Embedding( num_embeddings=len(vocab), embedding_dim=3, padding_idx=vocab["<pad>"] ) tokens = ["오늘", "책", "커피"] ids = [vocab.get(token, vocab["<unk>"]) for token in tokens] x = torch.tensor(ids, dtype=torch.long) y = embedding(x) print(x) # tensor([2, 3, 0]) print(embedding.weight.shape) # torch.Size([5, 3]) print(y.shape) # torch.Size([3, 3]) 커피 는 사전에 없어서 직접 작성한 vocab.get() 처리에 의해 <unk> 의 ID가 된다. nn.Embedding 이 모르는 문자열을 알아서 바꾸는 것은 아니다. 이 층에 들어가기 전에 정수 인코딩을 끝내야 한다. WikiDocs nn.Embedding 챕터 입력의 각 자리에 벡터가 붙는다 입력이 (3,) 이면 출력은 (3, 3) 이다. 인덱스 세 개 각각에 길이 3짜리 벡터가 붙는다. 배치로 넣어도 같은 규칙이다. batch = torch.tensor([[2, 3, 1], [4, 2, 1]], dtype=torch.long) output = embedding(batch) print(output.shape) # torch.Size([2, 3, 3]) 입력 (2, 3) 뒤에 임베딩 차원 3 이 붙었다. 행렬을 다른 모양으로 펴는 view() 가 아니라, 각 ID에 대응하는 벡터를 가져오는 연산이다. padding_idx=1 로 지정한 행은 새 층에서 기본적으로 0 벡터이며, Embedding의 역전파에서는 그 행의 기울기가 발생하지 않는다. <unk> 와 달리 패딩용으로 따로 지정한 행이라는 점을 구분해 둔다. PyTorch Embedding 공식 문서 이번 장에서 기억할 것 내가 다시 코드를 읽을 때는 단어 사전, ID 목록, 테이블 크기, 출력 크기 순서로 확인하려고 한다. 이 네 가지를 따라가면 지금 어느 행을 꺼내고 있는지 놓치지 않을 것 같다. 특히 테이블의 전체 크기와 한 번 조회한 결과의 크기를 섞지 않기. 위 예제에서 테이블은 (5, 3) 이지만 단어 세 개를 넣은 결과는 (3, 3) 이다. 이제 임베딩 코드를 보면 숫자 값부터 보기보다, 어떤 ID가 어떤 행으로 연결됐는지 먼저 살펴봐야겠다.