마찬가지로 풀다가 살짝 막히거나(시간이 조금 걸렸거나), 기본 라이브러리를 다시 짚게 된 경우만을 기록.
또는 내가 생각치 못한 다른 방법?이 있는 경우에도 기록.
Level 0 - 피자 나눠 먹기 (1)
"""
7조각으로 잘라줌
모든사람이 한조각 이상 먹기
필요한 피자 판의 갯수
=> n 나누기 7 => 2.xx => 올림 => 3판
=> 나눠진 값을 올림하기
"""
import math
def solution(n):
answer = math.ceil(n / 7)
return answer
항상 반올림만을 쓰는 round를 사용했었기에, 올림은 뭐였지..? 하다가 검색.
내장 module math를 사용해서 올림 연산.
모든사람이 한 조각 이상을 먹어야 하므로 $\rightarrow$ 인수(n)를 7로 나누고, 이를 올림해주기.
Level 0 - 삼각형의 완성조건 (1)
"""
가장 긴 변의 길이는 다른 두 변의 길이의 합보다 작다
만들 수 있으면 1, 만들 수 없으면 2
"""
def solution(sides):
# sides를 sort 하고
sides.sort()
# 맨 끝의 원소(가장 긴 변)가 다른 두 원소의 합보다 크면 1, 아니면 2
# 배열의 길이는 3으로 고정 -> 배열 전체를 훑을 필요가 X
if sides[2] < sides[0] + sides[1]:
answer = 1
else:
answer = 2
return answer
PS쟁이들이 언제나 간결하게 적는거야 그렇다 치는데... 이 max 라는 걸 완전히 잊었었기 때문. C/C++이나 java에서 sort가 없었나? 해서 옛날이라면 나도 이런식으로 풀었을텐데... Python에 내장된 함수가 너무 간편하다 보니 다짜고짜 정렬부터 생각해버린 듯.
Level 0 - 점의 위치 구하기
처음 풀이...
def solution(dot):
# dot은 [n1, n2] 로 구성된 list
# 단순히 점 1개1개가 어디에 있는지를 판단?
answer = 0
# 1사분면 & 3사분면 => dot[0]이 양수
if dot[0] > 0:
# 1사분면
if dot[1] > 0:
answer = 1
# 3사분면
else:
answer = 4
# 2사분면 & 4사분면 => dot[0]이 음수
elif dot[0] > 0:
# 2사분면
if dot[1] > 0:
answer = 2
# 4사분면
else:
answer = 4
return answer
이렇게 풀었는데 에러가 나와서 보니, 0이 아님이 있어서 다시 수정.
def solution(dot):
# dot은 [n1, n2] 로 구성된 list
# 단순히 점 1개1개가 어디에 있는지를 판단?
# 문제 조건에 0이 아님이 있음
answer = 0
# 1사분면 & 4사분면 => dot[0]이 양수
if dot[0] > 0 and dot[0] != 0:
# 1사분면
if dot[1] > 0 and dot[1] != 0:
answer = 1
# 3사분면
elif dot[1] < 0 and dot[1] != 0:
answer = 4
# 2사분면 & 4사분면 => dot[0]이 음수
elif dot[0] > 0 and dot[0] != 0:
# 2사분면
if dot[1] > 0 and dot[1] != 0:
answer = 2
# 4사분면
elif dot[1] < 0 and dot[1] != 0:
answer = 4
return answer
했는데 또 오답이 있다.
결국 신입생 시절 C/C++ 로 일일히 분기했던 기억을 되삼아 그냥 단순무식하게 작성.
def solution(dot):
# dot은 [n1, n2] 로 구성된 list
# 단순히 점 1개1개가 어디에 있는지를 판단?
# 문제 조건에 0이 아님이 있음
answer = 0
# 1사분면 => dot[0]이 양수, dot[1]이 양수
if dot[0] > 0 and dot[1] > 0:
answer = 1
# 2사분면 => dot[0]이 음수, dot[1]이 양수
elif dot[0] < 0 and dot[1] > 0:
answer = 2
# 3사분면 => dot[0]이 음수, dot[1]이 음수
if dot[0] < 0 and dot[1] < 0:
answer = 3
# 4사분면 => dot[0]이 양수, dot[1]이 음수
elif dot[0] > 0 and dot[1] < 0:
answer = 4
return answer
단순한 문제인데... 너무 단순하게 생각해서 그런지 첫번째, 두번째 작성했던 걸 보니 예외가 걸리긴 한다.
다른사람들은 어떻게 풀었을까.. 하고 보니
def solution(dot):
x,y = dot
if x*y>0:
return 1 if x>0 else 3
else:
return 4 if x>0 else 2
Python에서 list 2개의 값을 그냥 저렇게 꺼내올 수 있다는 게 다시 기억났다.
(기억상 석사과정 당시 간간히 썼었던 것 같긴 하다)
+ 이렇게 하면 두 좌표값의 곱으로 양수/음수로 판단하고, 이에따라 몇사분면인지 판단을 할 수 있는 것.
언제나 배워간다.
Level 0 - 순서쌍의 개수
def solution(n):
# 순서쌍 -> 약수 pair를 구하기
answer = 0
number_list = []
# 주어진 수 까지 loop를 돌며 약수를 구하기
for i in range(1, n+1):
if (n % i) == 0:
number_list.append(i)
# pair는 2개씩 묶이므로 -> len(list) 자체가 pair의 개수
answer = len(number_list)
return answer
약수를 구하는 마땅한 함수?가 없는 걸로 기억.
주어진 수 까지 1부터 시작해서 loop를 돌리고, 주어진 수 n이 현재의 수 i와 나눠지는지 확인, 나눠진다면 이 i는 n의 약수이므로 append.
다른사람들의 풀이를 보니... 굳이 list를 선언 할 필요가 없기는 했다.
def solution(n):
answer = 0
for i in range(1, int(n ** 0.5) + 1):
if n % i == 0:
answer += 2
if i * i == n:
answer -= 1
return answer
하반기가 끝나고, 프로그래머스에서 일하고 있는 친구의 조언을 받아 Level 0부터 문제를 풀기 시작.
확실히 친구 조언대로 수치심을 버리고 Level 0부터 차근차근 풀어가는게 맞는듯..
뇌가 너무 딱딱해져있는 상태라... 생각하는데 시간이 조금이라도 걸렸던 문제들을 기록으로 남기고자 한다.
(쉽게 풀린건 따로 기록 X)
Level 0 - 양꼬치
"""
양꼬치 1인분 12000
음료수 2000
10인분 먹으면 음료수 1개 공짜
"""
def solution(n, k):
# # 10인분 먹으면 1개 공짜
# if n % 10 == 0:
# k = k-(n//10)
# print(k)
answer = 12000 * n + 2000 * (k - (n//10))
return answer
10인분이 1개 공짜 이므로
공짜로 주는 음료수 개수가 n//10 개가 되고
금액 계산할때 k에서 이를 빼주기
Level 0 - 짝수의 합
def solution(n):
evens = []
# n이하 정수를 생성하고
# 짝수만 저장
for numbers in range(n+1):
if numbers % 2 == 0:
evens.append(numbers)
# sum
answer = sum(evens)
return answer
n이하의 짝수가 필요하므로, for~range를 통해 정수 loop
짝수인 경우 (%2 == 0)엔 evens에 추가하면
evens엔 (0을 포함한) 짝수만 담겨져있으므로
sum()을 통해 전체 합 계산
Level 0 - 배열 뒤집기
def solution(num_list):
# answer = num_list.sort(reverse=True)
# answer = sorted(num_list, reverse=True) # 3번 case: [5,3,1,1,1,1,0] <=> [5,3,1,1,1,0,1]
# 역순 indexing?
# answer = num_list[-1:]
# 하나씩 pop 해서 새로운 list에 담기?
answer = []
for i in range(len(num_list)):
answer.append(num_list.pop())
return answer
sort를 하니, 같은 숫자가 같이 묶이는 문제.
list를 역순으로 indexing... 하면 될 거 같은데, python을 기초부터 배운게 아닌지라 감이 안잡혀서 다르게 생각.
하나씩 pop 해서 새로은 list에 담기
num_list[::-1]
num_list.reverse()
풀고 나서 다른 사람의 풀이 를 보니, 생각한 기능이 역시나...
Level 0 - 뒤집힌 문자열
def solution(my_string):
# string을 list로 변환
string_list = list(my_string)
# list reverse
string_list.reverse()
# 원소를 하나씩 결합해서 뒤집힌 문자열 생성
for string in string_list:
answer += string
return answer
앞 문제에서 익혔던 reverse() 사용
원소를 하나씩 answer에 결합
my_string[::-1]
answer = ''.join(my_list)
확실히 기본부터 배우질 않았어서, string도 list처럼 indexing이 되는 줄 몰랐다.
학부생때부터 자주 다녔던 & 랩실때부터 자주 다니는 오락실의 지인들과 함께 지인 분의 집에서 음주.
누군가의 집에 모여서 술을 먹는게 너무 오랫만이어서 그랬는지, 이날 어떻게 잠이 들었는지 기억이 나질 않았다 ㅋㅋ...
부모님과 함께 고대산 자연휴양림에 방문.
겸사겸사 고대산을 한번 등산했는데.... 직원분이 악산이라고 한 이유가 있었다.
산 높이 자체는 그렇게 안높았던 것 같은데, 올라가는 길 자체가 상당히 험난했었다.
휴양림에서 하루를 보내고, 돌아오는길에 연천 재인폭포를 들러서 간단한 관광.
오랫만에 등산을 하니 참 상쾌하긴 했는데... 다시 생각해봐도 올라가는 길이 너무 험난했었다.
큰형네 둘째 조카가 첫돌이 되어, 애니골 쪽의 우설화 라는 가게에서 온가족이 모여서 식사.
먹었던 고기가 정말 맛있었던 기억이 난다. 역시 고기류는 비싼 값어치를 하는 듯.
상반기 시즌이 시작되고, 잠시 정신없었던 기억도 난다.
중소/중견 기업 지원서도 당시에 처음 작성했었는데, 지원했던 모든 곳이 서류에서 탈락되어 좀 자신감이 많이 떨어졌던 기억이 난다.
그나마 운 좋게(?) KT 서류가 통과되어서, 또다시 인적성 공부를 하고 시험을 봤다. 시험 자체는 SK보다는 쉽고, CJ랑 엇비슷한 느낌이었는데, 마지막 도형 추리? 였는지 패턴 찾기가 조금 난해했는 듯. 연습문제 풀 당시에는 막힘없이 그래도 잘 풀리긴 했었는데, 실전을 보니 연습문제랑 도형/패턴들의 생김새? 가 많이 달랐나 그래서 5분정도 뇌정지가 왔었다. 시험을 보고 나서 아.. 이건 글렀다 했는데 역시는 역시나... ㅠ
자소서 문항들의 글자수가 참 빡빡해서 투덜대며 작성했었는데, 이게 통과되어서 많이 의아했던 기억이 난다.
본격적으로 순회를 돌면서 밑판을 다져서 그런지, 49가 잘 쳐지기 시작했었다. 새로운 클리어를 노려서 팝픈 클래스 98.00을 달성하고자 했는데, 결국 클리어가 되지 않아 기록 갱신으로 어찌저찌 98.00을 달성해서 정말 기뻤던 기억이 난다. 무엇보다 50을 하나도 안건드리고 달성한게 뭔가 참 감회가 새로웠는 듯. (https://youtu.be/oeMdD2KqyPo)
노래 자체는 참 ANTHEM 답기는 한데... 계단?폭타?가 참 어렵긴 하다.팝픈 클래스 98 달성 :)50을 하나도 안건드리고 달성해서 그런지, 뭔가 오랫만에 느껴보는 성취감이었다.
5월
부모님과 큰형&첫째조카와 함께 영월 망경대산 휴양림에 방문.
가는길에 치악산 쌀 막걸리 주조공장? 같은게 있어서 저렴하게 탁주를 구매. 국도 여행은 지나가면서 이런 숨겨진 요소를 하나씩 찾아가는 재미가 좋은 듯 하다.
돌아오는 길에 한반도 지형에 들러서 사진/영상으로만 보던 지형도 직접 보고, 뗏목도 한번 타봤다.
망경대산 휴양림에서 찍은 사진. 날씨가 너무 좋아서 그런지 풍경이 정말 이뻤다.
일본에서 일하고 있는 대학교 동아리 친구가 골든위크를 맞아, 여느때처럼 한국에 방문.
홍대에 에어비앤비 숙소를 잡고, 겸사겸사 동아리 OB 모임을 가졌다.
버튜퍼를 좋아하는 선배가 들고온 노엘 포션(진). 봄베이랑 큰 차이가 없는 느낌이었다.다음날 해장 겸 방문한 니시무라멘 라멘집. 색다른 느낌이라 맛있긴 했는데, 전날 너무 과음을 해버려서 많이 먹지를 못했다 ㅠ
그리고 여느때와 같은 음주생활 :)
사진첩을 돌아보니, 이때즈음 부터 유튜브에서 자주 봤던 샤와르마(shawarma) 라는 요리?에 많이 꽂혔던 것 같다.
동네 슈퍼에서 또띠아를 사서 직접 만들어 먹어보고, 트레이더스에서 포켓브레드를 사서 만들어 먹어보고...
친구가 선물로 보내줬던 보리소주. 술에서 보리향이 나는게 뭔가 참 색달랐다.나름(?) 유튜브 영상에서 봤던 기억대로 만들어 본 샤와르마. 대충 입맛대로 소스를 바르고, 야채/채소를 넣고, 고기를 넣고... 이색적인 느낌이 들어서 만들어 먹는 재미가 쏠쏠한 것 같다.
대학교 동아리에서, 난생 처음으로 단체 주문제작 티셔츠를 만든다 하여 하나를 구매.
뭔가 참... ㅋㅋㅋㅋㅋ 10년이 지나니 동아리도 꽤 커지면서 별걸 다 하는구나 싶었다.
오랫만에 연구실 후배로부터 함께 술 한잔 하자는 연락이 왔다.
당시에 교환 프로그램을 갔다가 (내가 졸업한 후에) 돌아와서 랩실을 그만둔 석박통합 후배도 함께 보자고 했다가, 오랫만에 만나는 김에 이친구 저친구도 같이 보자 했더니 어느샌가 랩실 생활때 같이 있었던 후배들 모두를 만나게 되었다.
고속터미널 인근에 모여서 오랫만에 얼굴들을 보니 정말 좋았고, 다들 박사과정도 하고 직장생활도 하는 모습을 보니 좋았다. 다들 참 열심히 살아가는구나... 를 느꼈는 듯.
술 먹으면서 미국에서 박사과정을 밟고있는 동기와도 오랫만에 화상통화로 서로 안부를 물어보고... 참 반가웠다.
저녁을 먹고 2차를 지나 3차로 방문한 술집. 너무 오랫만에들 만나서 좋았는데, 술까지 같이 들어가니 밑도 끝도 없이 먹었던 것 같다. 결국 첫차타고 귀가하긴 했지만, 그래도 오랫만에들 보니 다들 잘 지내고 있는 것 같아 기분이 좋았다.
게임적으로는 조금씩 트라우마도 극복하고, 계단을 한층 더 올라간 듯 하다.
자주 다니는 오락실에서 IIDX에 타건음 마이크를 설치해 준 덕분에, 입문시절 목표곡 중 하나인 Blue Rain 黑 채보를 영상으로 남길 수 있어서 정말 좋았다 :) (https://youtu.be/Ak-XzaJwBDs)
통풍 약을 처방받고 난 후, 공군사관학교에서 교수사관으로 지내고 있는 후배를 보러 동아리 선/후배, 동기친구와 함께 청주에 방문.
교수사관을 하고있는 후배가 예전부터 언제 술한번 먹으러 오라면서, 오면 술은 무한으로 드리겠다곤 했었다가 이번에 일정을 잡고 방문.
공군사관학교 내부에 있는 외래자 숙소의 방을 2개 잡고, 늦게까지 음주.
통풍에 걸렸다는 소식을 전하니 모두가 참 안쓰럽게 웃던 기억이 난다... ㅋㅋ
당시 교수사관 후배가 관사에서 들고온 술들. 피트를 좋아하는 애라 그런지 피트 위스키가 참 많았다.선배가 부탁해서 후배가 사왔던 일본 위스키. 난생 처음보는 위스키였는데, 맛은 잘 기억이 안나지만 상당히 맛있었던 거로 기억한다.정~~말 오랫만에 냉동도 먹었다. 오랫만에 먹어서 그런지 불량한 맛이 참 좋았는 듯.교수사관 후배가 안에 사슴이 많다곤 했었는데... 진짜로 저렇게 바로 보일줄은 진짜 상상도 못했다 ㅋㅋㅋ나와서 해장 겸 먹은 뼈대짬뽕? 뭔가 특이한 느낌이긴 했다.
여느때처럼 연신내에서 동아리 후배들과 놀다가, 다같이 술한잔을 하자고 해서 이사오기 전에 가끔씩 들렀던 족발집에서 음주.
통풍에 걸린것 때문에 평소처럼 1L 맥주를 파는 술집을 가지 못해서, 대안을 찾다가 족발집으로 결정.
이사 전부터 오랫동안 있었던 연신내 족발집. 정말 오랫만에 와도 가격이 참 저렴해서 좋았다.정신을 차리고 보니 어느샌가 모두가 너무나 과음을...
게임적으로는, 뭔가 한번 정체기가 온 느낌이었다.
팝픈뮤직을 주력으로 하다 보니, IIDX는 뭔가 처리력 보다는 중저렙/동치 판정만이 올라간 느낌.
팝픈뮤직에서도 고점이 잘 뚫리질 않아, 중저렙에서 쥬크박스를 돌리면서 47~48을 많이 한 것 같다.
스터디의 첫번째 스터디(배경지식 스터디)가 끝나고, 두번째 스터디(논문 review)의 시작 준비를 했다.
나와 학부연구생을 하고 있는 후배 이외에는 모두가 논문을 읽어본 경험이 없어서, 논문을 어떻게 읽는지 간략하게 정리하면서 예전에 deep-dive 했던 GCN, GraphSAGE, survey paper를 다시 훑어보며 스터디 페이지에 읽는법을 정리해두었다.
2년?정도가 지나서 review 했던 논문을 다시 보니... 뭔가 참 열정 넘치게 연구했었구나 라는걸 다시 한번 느꼈었다.
당시 스터디 페이지에 정리한 내용
추석에 맞춰서 올해도 시골로의 벌초가 예정되어 있었는데, 올 5월인가 6월에 미리 잡아둔 약속이 있어서 불가피하게 참석을 못했다.
일본에서 일하고 있는 동아리 동기 친구가, 9월달에 다시 한번 한국에 잠깐 온다고 하여 모일사람 모여서 먹고 마시자고 했기에... 이번에는 부모님도 그리 오래전에 잡아둔 약속이니 별수 없지라고 이해해 주셨다 :)
동아리 선배가 차를 끌고 오셔서 다함께 가나안덕에서 밥을 먹고, 마침 일산 호수공원에서 전통주 축제가 있어서 구경을 좀 하다가, 우리 집으로 돌아와 음주.
다음날엔 친구가 샤브올데이를 한번도 안가봤다 해서(나도 가본적이 없었지만), 삼송역 앞쪽에 새로 생긴 샤브올데이에서 점심을 먹었다.
샤브샤브에 간단한 바이킹 식 조촐한 뷔페로 구성되어 있었는데, 가격이 가격인지라 나는 굳이.. 라는 생각이 들었는 듯.
01234
가나안 덕에서 밥을 먹고, 호수공원에서 열린 전통주 축제도 구경. 이번에도 친구가 여러 오미아게를 가져다 주었다. 집앞 정육점에서 술안주거리를 사고, 음주하고, 다음 날 해장 겸 점심으로 간 샤브올데이.
01
아마 기억상 작년?즈음 일본여행을 다녀오고 나서 아주르레인 로손 콜라보가 열렸었다. 당시에 친구에게 하나 구매를 부탁했었는데, 서로 잊고지내다가 이번에 들고와달라 해서 건네받았다.
그리고 또다시 본격적인 하반기 시즌이 시작되었다.
직무가 조금 맞아보이는건 최대한 많이 지원해보면서, 자소서도 정신없이 쓰고...
이번에 처음으로 말로만 들었던 AI역검을 봤었다.
신기하게도 서류전형 다음에 AI역검을 보는게 아니라, 지원했던 곳 거의 대부분이 서류전형과 AI역검을 같이 봤었다.
경험담?은, 그래도 확실히 일반 대기업 인적성 검사(CJT, SKCT, KT인적성 등...)보다는 그래도 나름 재미(?)는 있었는 듯. 도대체 이걸 왜 공부하나... 싶은 일반 인적성 검사와는 다르게, 그래도 약간 게임식으로 진행하면서 영상면접도 체험해보고, 무엇보다 끝나고 나서 결과분석을 볼 수 있었던게 참 마음에 들었던 것 같다.
알바를 다니면서 여러군데 서류를 쓰고... 결과를 기다리면서 그래도 이번에는 좋은 결과가 나오겠지 하면서 조금은 조마조마 했던 것 같다.
음주도 여느때처럼 적당히 한 듯 하다.
마트를 둘러보다가, 삿포로 생70에 적혀있는 가타카나에 '퓨린' 이라는 글자에 '오프'라는 글자가 적혀있어서 도대체 뭘까 하고 찾아보니 퓨린질?을 70% 줄였다는 것을 알게되었다.
놀랍게도 3~5캔을 마셔도 다음날 발작이 하나도 없었던 것... 앞으로의 인생 맥주가 될 듯 하다 :)
확실히 다른 맥주들에 비해 맛?이 훨씬 덜 묵직하긴 한데, 그래도 제로맥주들 보다는 목넘김도 좋고 맛도 좋아서 대만족 :)
게임적으로 보면 팝픈뮤직을 정말 열심히 했었던 것 같다.
47~48레벨을 많이 플레이하면서, 자잘하게 AAA를 많이 찍어낸 듯. 48에서 노래도 정말 친숙하고 좋고 채보가 정말 재밌어서 자주 플레이하는 Little Prayer를 드디어 AAA 달성했었다. (https://youtu.be/z9-Z-b1Uyt8)
플레이 스타일이 36~39 즈음 부터 손풀면서 시작해서 한레벨씩 올라가는데, TЁЯRA 유닛에서 좋아하는 노래 중 하나를 드디어 S를 볼 수 있어서 참 기뻤다. (https://youtu.be/46xDt9KAths)
팝픈 뮤직을 참 열심히 해서 그런지, 침체기가 와서 그런지 IIDX 쪽은 참 열심히 안하긴 한 듯.
이벤트도 전부 완주하지 못한게 아마 군시절 가동했던 SINOBUZ, CANNON BALLERS 이후로 처음이다...
3번째 ★12 클리어. 점수와 판정은 많이 슬프긴 한데, 그래도 클리어가 하나 늘어서 기뻤다 :)
10월
8월달에 큰형네 가족과 광치휴양림으로 휴가를 갔을때, 큰형수가 베트남을 한번 가보자는 이야기를 하셨었다.
그래서 어찌저찌 예약이 되고, 일정이 잡히고... 어쩌다 보니 난생 처음으로 동남아 여행을 가게 되었다.
첫날엔 현지시간으로 00시? 즈음 깜란 국제공항에 도착. 확실히 날씨가 조금 덥기는 했었다.
형수가 콜밴 같은걸 예약해두었어서, 부모님과 형수와 조카들과 함께 차를 타고 잠깐 눈을 붙일 숙소로 이동.
묶었던 호텔에서의 조식. 반찬들이 뭔가 친숙한 맛이 났다.
그렇게 둘째날에는 형수가 예약했던 풀빌라?풀펜션?에 가기 전, 간단하게 시내 투어를 했다.
롱선사를 가보고, 롯데마트(!)에 가서 먹거리와 술을 사고, 담시장에 가서 점심을 먹고 시장 구경을 하고...
숙소에서 나와서 찍은 근처 사진. 뭔가 확실히 미디어에서만 보던 동남아 거리를 실제로 보니 느낌이 색달랐다.
012
숙소에서 전용 콜밴을 타고 처음 간 롱선사. 확실히 한국/일본/중곡의 사찰들과 비슷하면서도 조금은 다른 느낌이 들었다 // 롯데마트에서 찍은 신기한 초코파이 사진.
01234
형수가 근처 맛집을 찾아보라 해서 찾았던 가게. 확실히 현지식 느낌이 많이 났는데, 맛 자체는 거부감이 전혀 없어서 정말 배부르게 먹었던 기억이 난다. 부모님도 입맛에 잘 맞으셨다 해서 다행이었다 :) // 가게에서 밴을 타고 5분?내외로 가니 나온 담시장. 가서 모자도 사고, 크로스백도 사고, 물건들이 확실히 저렴해서 참 놀라웠다.
그렇게 볼일을 다 보고, 형수가 예약한 리조트? 로 이동.
가기 전까지는 주변이 뭔가 휑~ 해서 이런데에 있는게 맞는건가.. 싶었는데, 리조트 촌이라도 되었는지 인근에 비슷한 건물들이 많아서 느낌이 좀 색달랐다.
도착하고 보니, 형수가 예약을 일반등급? 으로 안하고 한단계 위인 LEVEL 등급? 으로 했다고 하여, LEVEL 라운지에서 체크인을 진행한다고 무슨 조그만한 밴(기억 상 톡톡? 으로 불렀던 것 같다)을 타고 다시 이동.
라운지에는 상시로 과일류와 간단한 먹거리, 술/음료가 무제한 제공이었는데, 특정 시간대(기억상 아마 오후4시~7시 였던 것 같다)에는 종류들이 늘어난다고 했다.
012
맨처음 도착했을때의 메인 로비, 리조트 안에서의 이동수단인 미니밴(톡톡?), LEVEL 라운지에 있었던 먹거리와 마실거리들.묶었던 숙소. 층도 2개에, 앞에 전용 풀이 있었다.
저녁식사는 간단하게 LEVEL 라운지에서 먹고, 숙소로 돌아와 쉬면서 롯데마트에서 사왔던 술들을 먹고 룸서비스를 시켰다.
여태까지 룸서비스 라는 것 자체를 시켜본 적이 (기억상) 없는데, 이런데를 따라오니 이런거도 거리낌없이 시켜볼 수 있어서 참 색다른 경험이었는 듯.
0123
특정 시간대가 되니 확실히 먹거리가 많이 늘었었다. 나름 조촐하게(?) 먹으면서, 술을 같이 먹으니 참 배부르게 먹었는 듯. // 마트에서 샀던 RTD와 룸서비스. 조카가 치킨이 먹고싶다 하여 한국식 치킨을 시켰는데, 생각보다 맛있어서 놀라웠다.라운지에서 특정 시간대에만 나오던 술 중 하나. 베트남에서 만든 진이라고 했는데, 상당히 맛있어서 이것만 먹었던 기억이 난다.
그렇게 한 3일 정도를 숙소에서 푹 쉬면서, 정말 말 그대로 '힐링 여행'을 잘 다녀온 듯 하다.
LEVEL 등급?만 사용할 수 있는 레스토랑에서 조식도 먹고, 리조트가 소유한 개인 해변?에도 가보고, 조카를 데리고 숙소의 풀장 & 리조트 내의 공용 풀장에서 같이 물놀이도 하고, 형수가 예약했던 마사지 샵에 가서 마사지도 받아보고...
무엇보다 많이 놀라웠던 건, 숙소에서 '배달음식'을 시켜먹을 수 있던게 신기했다.
배민같은 배달 어플이 따로 있어서, 그걸 설치하고 보니 현지식, 한식, 카페, 과일 등... 참 먹거리가 많았다.
물론, 음식은 메인 로비쪽에 있는 정문 출입구까지 나가야 했다 ㅋㅋㅋ 숙소에서 거리가 꽤 있어서, 갈때마다 톡톡?이라는 미니 밴을 타고 이동했다.
012
LEVEL 라운지만 사용할 수 있다는 레스토랑에서 먹은 조식. 신기하게도 (뷔페식으로 있는 음식 제외) 자리에서 직원이 주문을 받고, 요리사가 만든 후 자리까지 서빙해주었다. 에그베네딕트 라는 음식을 여기서 처음 먹은 듯... // 먹었던 반미. // 체크아웃 날 갔었던 공용 레스토랑. 확실히 사람이 참 많기는 했다.
012
숙소에 같이 딸려있는 개인 풀장, 리조트 공용 풀장. 날씨가 조금 습하고 더웠는데, 물 온도는 참 적당하게 시원했다. // 개인 해변?에서 찍어본 한장 :)숙소 발코니에서 찍은 한장. 스콜이 좀 있긴 했는데, 아침에 날씨도 좋고 뷰가 너무 좋아서 무의식중에 카메라를 꺼냈다.
그렇게 약 5일? 간의 여행을 다녀오고... 하반기를 마저 보냈다.
이곳저곳 지원을 했지만 역시 좋은 소식..은 도통 들려오지가 않았다 :(
그래도 작년에 지원했던 CJ 계열사의 동일한 직무가 있어서 9월에 지원서를 작성했었는데, 문항이 완전 똑같아서 내심 기대를 했더니 좋은 소식을 들려주어서 많이 기뻤다 :)
작년의 교훈을 토대로 삼아, 실물책을 구매하지 않고, 교보문고 e북을 구매해 태블릿으로 문제를 풀면서 준비를 했다.
그.....런데, 문제는 도표해석 부분이 작년과는 다르게 5분이 줄어들었었다.
도표해석 부분 문제를 풀면서 최대한 절반은 풀어보도록 했다.
시험 당일날, 문제를 풀다보니 그래도 작년보다는 많이 발전한 느낌은 들었는데, 도표해석 부분은 기억상 8문제 정도 풀었던 것 같다. 아무래도 시간 압박이 참 컸는 듯... 그리고 수리문제에서 수열추리가 한문제도 안나와서 배신감이 많이 들었던 것 같다 :(
다행히도 작년과 지원서 문항들이 거의 똑같아서, 수정을 거의 안하고 제출했었다. 그래도 어찌저찌 하반기 첫 서류통과라 그런지 기분이 좋았다.
여행을 다녀오고, 하반기도 마저 우당탕탕 보내고, 알바를 다니고... 뭔가 조금은 정신없이 10월을 보낸 듯 하다.
평소처럼 알바를 하고, 오락실을 가고... 뭔가 루틴이 고정되다 보니 시간이 엄청 빠르게 지나가는 느낌이 들었다.
어머니가 친구분과 함께 스페인 여행을 가시고, 아버지가 대학교 동창분들과 양평으로 짧게 여행을 가신 김에, 오랫만에 후배와 함께 술을 먹었다.
처음에는 우리 집에 불러서 음주를 하려고 했는데, 후배가 움직이기 귀찮다 하고 + 자기 집으로 오면 히비키를 먹여준다고 해서 당장 달려갔다 ㅋㅋㅋ
거진 1여년? 만에 봐서 그런지 너무 반가웠고, 늦게까지 수다떨면서 정신없이 먹었던 것 같다.
후배 집에서의 음주. 비싼 술은 확실히 맛있다.
어머니가 여행을 가신 동안, 큰형네와 작은형네가 집에 방문해서 어쩌다보니 어머니 없는 가족 외식을 했다.
아버지가 집 근처에 새로 생긴 고기집이 있다고 하셔서 갔는데, 세트 메뉴가 뭔가 조촐한 것 같으면서도 양이 꽤 많아서 배불리 먹었던 기억이 난다.
고기 자체의 양은 그렇게 많지 않았던 느낌인데, 종류가 다양하기도 하고 고기마다 맛이 참 좋았던 것 같다.
01234
가게에서 먹은 세트메뉴. 육회와 샐러드가 참 맛있었던 기억이 난다. 고기류는 맛있긴 한데, 양이 조금씩 여러개가 있어서 그런지 맛은 좋았는데 살짝 아쉬운 느낌이 많이 들었는 듯.
하반기가 얼추 지나가면서, 공고들도 많이 줄어들었지만 그래도 지원해볼 수 있는 분야는 최대한 지원해봤다.
CJ 외에 다른 기업들도 계속해서 불합격을 던져주던 사이, 한번 지원이나 해보자 했던 한 중소기업의 서류가 통과되었다는 소식을 받았다.
이번 하반기에 처음으로 넣어본 중소기업이긴 한데, 공고문에는 'AI 프로젝트 경험 5개 이상'이 자격 요건이었다. 석사과정 당시 진행했던 프로젝트가 3개 뿐이었지만 그래도 넣어나 보자 하는 마음에 지원을 했었는데, 이게 합격 소식을 들려줘서 참 당황스러우면서도 기분은 좋았었다 :)
다음 전형으로 코딩테스트를 봤는데... 1시간에 3문제가 나왔다.
누가 봐도 선택과 집중을 하는 것이긴 했는데.... 제일 만만해 보였던 1번 문제를 구현하다가, 석사과정 당시 졸업연구를 하면서 엄청 자주 썼었던 pandas 기능이 있었는데, 이걸 사용하지 못해서 docs를 뒤적거리다가 그냥 포기하고 나왔다 :(
결과는 뭐... 0솔인데 좋은 결과가 있을리가 😅
하반기에 처음 넣었던 중소기업에서의 합격 메일. 한번 찔러나보자 했는데 이게 얻어걸려줘서 뭔가 기분이 참 묘했었다.
크리스마스 날? 인가 연말에 갑작스레 SDVX가 버전 업데이트를 한다고 해서, 발키리 기체에서 플레이. 점수체계가 세분화 되어서 그런지, 어떤걸 골라서 클리어해도 볼포스가 참 많이 오르는게 신기했다.
이래서 아레나 돌리기가 진짜 싫다....그래도 좋은 노래를 다시 챙겨왔으니, Now and Forever가 나올때까지 절대 돌리지 말아야겠다.발키리 기체에서 하니 바로 AAA+가 나와서 좀 당황스러웠다.
글을 적으면서 되돌아보니 뭔가 참... 알바를 시작하면서 부터 시간이 빠르게 지나간 느낌이다.
작년처럼 가족과 보내는 시간도 많이 늘었고, 인생 처음으로 동남아 여행을 가서 제대로 힐링하고 오고....
연말에 작은 형네와 같이 살게되면서, 조용했던 집이 조금은 북적북적 해지니 (부모님 말을 빌리자면) 사람 사는 집 같은 느낌이 나기는 한다 ㅋㅋㅋ
커리어 적으로는 흠...
작년처럼 글쎄 정도는 아니고, 자소서에 문제가 좀 있는것 같은 느낌은 들었다.
작년과는 다르게 작성하는 시간을 조금은 줄이고자 ChatGPT나 Gemini에게 직무소개, 만들었던 CV, 프로젝트 기술서를 던져주고 초안을 한번 작성해봐라.. 한 다음에 작성한게 몇몇개 있기는 했는데, 결과는... ㅋㅋㅋㅋ
친구의 말을 들어보니, 요즈음 걸러내는 기술이 좋아져서 다 걸린다고 했다. 그래서 그런지 성의있게 밑에서부터 안쓰고 AI를 좀 많이 활용했다 싶은것들은 전부 떨어졌던 거 보면 ㅋㅋ;;;
이런 AI 활용도 조금 문제였던거 같긴 한데, 올해 석사를 졸업한 실적 괜찮은 동아리 후배도 1~2곳 빼고 전부 서류에서 탈락하는걸 보니 그만큼 시장이 얼어 붙은건지... 잘 모르겠다.
직무에서 몇명을 뽑는지도 모르고, 경쟁률이 대충 얼마나 되는지도 모르다보니...
위에서도 작성했는데, 눈여겨 보고 있는 스타트업 규모의 중소기업의 공고가 닫히기 전에(언제 닫힐지도 모르지만...), 프로젝트 포트폴리오를 다시 빠르게 만들어서 한번 지원해보려고 한다.
별 생각 없이 사이트를 돌아다니다가 발견한 것 치고는 사업영역도 상당히 마음에 들고, 업무-전공 직무도 거의 90%이상 fit 하고... 다만 흠이 있다면 위치가 경기도 구리시 인지라, 된다고 하면 자취방을 구해야 할듯 ㅋㅋ... 설레발을 많이 치기는 하고 있지만, 그래도 좋은 소식을 들려줬으면 좋겠다 ㅎㅎ...
공기관에서 일하고 있는 친구가 전해준 빨간약상/하반기 작성한 서류들. 상반기1개, 하반기 2개 뿐인게 뭔가 참..씁쓸하긴 하다.
게임적으로는 뭔가 만족?까지는 아니고, 그렇다고 불만족까지는 아닌 느낌. 중간과 만족의 사이인 듯 하다.
어찌저찌 IIDX에서도 만족한다 라는 성과(Level 3)를 보기는 했지만, 게임을 10년넘게 했는데 개전을 못땄던 충격이 아직도 잊혀지지가 않는다 ㅋㅋㅋ...
팝픈뮤직은 어느정도 만족스럽기는 한 듯. 50을 건들지 않고 팝픈클래스 98.00을 달성한건 참 기뻤는데, 그 후로 고점이 잘 뚫리지 않아서 47~48을 많이 하다보니 오히려 저점이 확실하게 오른게 좋기는 했다.
이왕이면 고점도 같이 눈에 띄게 올랐으면 더 좋았을듯...
IIDX에서 빨간약을 심하게 먹고 BMS를 다시 (이전보다는 자주) 건들면서, 그래도 sl7, ★13 까지는 얼추 비벼볼 수 있는게 참 장하게 느껴지긴 했다.
어찌저찌 2025년도 이렇게 우당탕탕 지나가게 되었다.
상반기-하반기를 정신없이 보냈다는걸 핑계로, 올해 블로그 포스팅이 4개밖에 안된게 뭔가 참... ㅋㅋㅋㅋㅋ 반성해야 할 듯.
26년에는 그래도 조금은 달라지고, 좋은 소식이 생겨서 가족과 친구들, 그리고 선/후배에게 한턱 크게 내는 날이 왔으면 좋겠다 ㅎㅎ..
당시 시간 상 Data Parallelism 이후 부분(Model Parallelism, Frameworks/Accelerators/Techniques 등)은 review를 하지 않았기에, Data Parallelism 파트를 마저 정리하고자 한다.
(당시 신경쓰던 내용이 mini-batch parallelism 이기도 했고...)
Data Parallelism
Graph partition parallelism 과 mini-batch parallelism 비교.
전통적인 DL(Deep Learning)에서의 data parallelism은 보통 입력된 data sample들을 mini-batch에 담아 병렬화를 수행했다.
이를 간략하게 짚고 넘어가면
각 worker(GPU)는 자신이 담당한 mini-batch(자신에게 할당된 data sample)를 처리.
부분적으로 model의 weight를 계산, parameter server(centralized distributed training) 또는 AllReduce(de-centralized distributed training)와 같은방법을 통해 다른 worker와 작업 내용을 동기화.
일반적인 Parameter Server - Worker Nodes 구조의 centralized distributed training모든 worker가 Reduce 연산을 수행하는 de-centralized distributed training 중 AllReduce 방식
전통적인 DL에서 data sample(ex. image)은 서로 independent 하다.
→ 즉 병렬화가 비교적 간단하고, 동기화는 model parameter를 갱신할때만 1번 해주면 된다.
하지만 GNN은 이것이 어렵고, 복잡하다.
왤까? 이전 포스팅이나 여러 포스팅에서도 언급했던 것 처럼, data sample 간 dependency(의존성)가 존재하기 때문이다.
dependency 문제 이외에도, input data 자체가 너무 거대한 경우도 존재한다.
(ex. 한 server에 올리기 힘들 정도의 크기를 가진 community graph 등...)
이런 경우엔 mini-batch 사용 여부 또는 방법에 관계없이, 하나의 server가 data를 전부 들고있을 수 없으므로, graph partition parallelism에 의존해야 하는 경우가 많다.
→ single large graph를 여러개의 sub-graph 로 분할하여, 각 server에 저장한 후 full-batch 또는 mini-batch training을 수행.
몇몇 graph는 250 billion개의 노드와 10 trillion개의 edge로 구성되있기도 하고, 각 node/edge에도 아주 큰 (차원 수가 매우 높은) feature vector가 붙어있을 수 있다. (OGB dataset 처럼.)
이런 경우엔 단일 서버 메모리에 데이터가 올라가지 않으므로, graph를 서로 다른 worker에 분산시켜야 한다.
본 논문에서는 이 방법을 graph partition parallelism이라 명명하는데, 그 이유는 이 접근법 자체가 graph partitioning과 mincut problem에 뿌리를 두고 있기 때문이다.
[graph partitioning] graph partitioning은 기본적으로 NP-complete problem → graph의 크기가 커질수록 최적의 분할을 찾는 시간은 지수적으로 증가. ⇒ 따라서 최적에 근접한 결과를 내놓는 heuristic한 접근을 해야한다. (참고링크: https://patterns.eecs.berkeley.edu/?page_id=571)
여러 graph partitioning 기법들.
위 그림처럼 graph partitioning의 방법을 나눠볼 수 있으며, 방법들을 서로 섞어서 사용할 수도 있다.
각각을 살펴보면 다음과 같다.
edge [structure] partitioning: edge를 구조적으로 나눠서 node들을 서로 다른 worker에 분산.
(probably) 가장 많이 쓰이는 방법이지만, 왜곡된 차수 분포(skewed degree distribution)를 갖는 graph를 나눌 때 많은 통신과 work imbalance가 발생한다. (but it comes with large communication and work imbalance when partitioning graphs with skewed degree distributions.) (skewed degree distribution은 long-tail distribution의 다른 말. node들의 degree distribution이 한 쪽으로 몰려있다는 의미. 이전에 작성한 Cora batch testing 포스팅(링크)에도 간략하게 설명해두었다.)
하나의 edge가 하나의 server에 할당, 하나의 edge가 서로 다른 server에 할당. → 즉 edge가 다른 server로 나눠짐으로써 개별 node가 서로 다른 worker에 분산된다.
edge partitioning보단 어느정도 여러 문제점을 커버할 수 있지만, 높은 degree를 가진 node가 많은 worker들 간에 분산되어 있다면 일관성 있게 분산된 node 상태를 유지하는데 큰 오버헤드가 발생한다. (but if a high-degree vertex is distributed among many workers, it may also lead to large overheads in maintaining a consistent distributed vertex state.)
하나의 node가 하나의 server에 할당, 하나의 node가 2개 이상의 server에 할당 → 즉 node가 다른 server로 나눠짐으로써, 해당 node와 연결된 edge들이 분산된다.
전통적인 분산 그래프 알고리즘에서 node/edge가 스칼라(상수) 값을 가지고 있으므로, feature partitioning은 graph processing 영역에서 별도로 다뤄지지 않았다.
edge feature vector, node feature vector가 나눠져서 서로 다른 server에 분산 → 말 그대로 feature vector를 분산시키는것. 단지 그 vector가 edge에 해당하는 feature vector인지, node에 해당하는 feature vector인지의 차이.
결국 partitioning은 graph의 일부분이 다른 server에 보관되어 server 간 communication이 발생할 수 밖에 없다.
즉, node 또는 edge가 분할된 경우 graph와 관련된 연산(Scatter, Aggregate)에서 communication이 발생하며, feature vector가 분할된 경우엔 신경망 관련 연산(UpdateEdge, UpdateVertex)에서 communication이 발생할 수 있다.
graph partitioning은 보통 full-batch training시, 큰 메모리 요구량을 다루기 위해 사용한다.
→ OGB나 Reddit 같은 single large graph의 경우 single GPU/CPU memory에 upload 하는 것 자체가 거의 불가능한 수준.
각 GNN layer에 있는 각 노드의 각 feature에대한 모든 activation을 저장해야 한다.
이를 위한 (널리 알려진) 방법 중 하나로, distributed memory를 가진 large-scale cluster를 사용하는 것인데, 이를 통해 graph partition parallelism과 함께 결합된 메모리에 방대한 양의 input data를 전달해줄 수 있다.
하지만 물론 치명적인 단점이 존재한다.
→cluster가 very expensive하고, 잦은 communication으로 인해 model convergence가 상당히 느려진다.
(cluster 환경을 구축하는 것 자체가 현실적으로 expensive...)
그래서 보통 graph partitioning을 한다 하면 full-batch training을 하지 않고, 주로 mini-batch training으로 접근한다.
Mini-batch Parallelism
data sample들이 independent graph라면, mini-batch parallelism은 traditional DL에서의 방법과 거의 유사하다.
본 논문에서는 이런 경우를 independent mini-batch parallelism이라 명명한다.
mini-batch는 이러한(independent한) graph sample의 집합으로, 서로 간의 dependency가 없다.
molecules data(분자구조 데이터)처럼 data sample들이 서로 다른 크기를 가질수 있는데, 이러한 경우에는 각 worker 간의 load imbalance가 발생할 수 있다.
graph들이 서로 다른 크기를 가질 수 있어도, 이들의 feature vector는 같은 차원을 가짐. (data를 build할때 feature vector의 차원을 정해두고 만들기 때문.)
대부분의 GNN computation에서 mini-batch parallelism이 어려운 이유는 크게 다음과 같다.
data sample 간 종속성이 존재 (dependent mini-batch parallelism)
load imbalance 존재: node들의 degree가 서로 다름
mini-batch를 형성할 때, target node(anchor node)를 선택함에 있어 information loss가 발생
traditional DL은 data sample을 random하게 선택해서 mini-batch를 형성한다.
GNN에선 anchor node를 random하게 선택하면 아주 낮은 accuracy가 나오기 쉽다. → node subset을 랜덤하게 고르게 되면, 이 subset들이 서로 연결이 안되어있을 수 있으며, → 매우 sparse하고 missing edge때문에 Aggregate/Scatter 연산 중에 graph 구조에 대한 많은 정보 손실이 발생하기 때문.
이런 정보 손실 문제를 피하고자, 초창기에 행해진 시도(GCN)는 full-batch training에서 시도가 되었지만, full-batch training은 수렴이 느리고 많은 메모리를 필요로 하는 문제가 있다.
따라서, 이런 문제들을 다루고자 최근(논문 작성되던 시기 기준. 2022년)에는 크게 2가지 접근법이 제안되고 있다
Support Vertices (Anchor node selection, Node-wise sampling, Neighborhood Sampling)
(당시 paper를 리뷰하며 정리해놨던 자료와, 이후 arXiv에 다시 수정되어 업로드 된 paper의 단어?구조가 조금씩 다르다. 당시에는 incorporating support vertices였는데 수정되어 업로드 된 paper에는 직관적으로 neighborhood sampling으로 표기되어 있다...)
neighborhood sampling(GraphSAGE, 좌) vs. graph-sampling(ClusterGCN, 우)
GraphSAGE로 부터 시작되어, support vertices는 샘플된 target node(==anchor node)들의 이웃 일부을 mini-batch에 추가한다. (one adds some neighbors of sampled target vertices as so called support vertices to the mini-batch.) (→ GraphSAGE에서 mini-batch를 어떻게 형성하는지에 대한 간략한 이야기. GraphSAGE에서 node들 중 anchor node를 선택, anchor node의 $k$-hop 거리의 이웃 node를 추출해 computation graph를 생성한다.)
support vertices(support vertex)는 tree(graph)에서 degree가 1인 leaf에 인접해있는 node를 의미하며, 차수가 1인 node에 바로 인접한 node로 정의된다. 여기서는 target node(anchor node)를 기준으로 sample된 node들을 의미.
이때 support vertices는 target node의 예측 정확도를 높이기 위해서만 사용되며, 해당 mini-batch에서 target node로 사용되지는 않는다.
→ 각각의 mini-batch에서, target node에 대해 Scatter/Aggregate kernel을 실행할 때, 미리 선택된 support vertices도 함께 고려하게 되고, 따라서 두 연산의 결과가 좀더 정확해진다.
각 target node $v$ 의 support vertices는 target node 1-hop 이웃 뿐만 아니라(그래프의 크기가 클 경우), $k$-hop이웃에서도 선택된다. (GraphSAGE에서 제안한 sampling 기법 이야기.)
이러한 support vertices selection은 각각 상황에 따라 달라지며, GraphSAGE에서는 실제 train을 하기 전에 각 layer마다 sample된다.
simple한 방법이긴 하지만, 물론 challange가 존재한다.
overhead of pre-selection: GraphSAGE의 경우, layer의 수 만큼 sampling step을 수행해 각 node와 각 layer에서 support vertices를 선택해야 한다.
즉, layer의 수 만큼 sampling step을 수행하므로 이 또한 overhead가 발생할 수 있다.
이 문제는 parallelization으로 완화할 수도 있지만, 곱셈 상수 인자에 의해 GNN 계산의 깊이를 증가시킨다. (it inherently increases the depth of a GNN computation by a multiplicative constant factor.) → GraphSAGE의 시간복잡도는sampling 하는 이웃의 수가 가장 큰 요인, $N^{K}VD^2$ ($N$은 샘플링 한 이웃의 수, $K$는 layer의 수, $V$는 전체 노드의 수, $D$는 feature vector의 차원 수)
neighborhood explosion
neighborhood explosion에 관한 설명. layer를 거칠수록 sample 되는 이웃의 수가 지수적으로 증가하는 문제.
또한, 수 많은 node를 계속 hold하고 있어야 하므로, memory overhead가 발생한다.
(최악의 경우) $H$-hop 까지의 모든 이웃을 계속 hold 할때, $O(kd^H)$ 의 상태를 계속 유지하고 있어야 한다.
몇몇 node들이 해당 mini-batch에서의 target node이고 이미 계속 hold 하고 있더라도, hop 수를 늘리면 target node가 차지하는 비율이 적어질 수 밖에 없다.
GraphSAGE는 모든 이웃을 hold 하고 있는 대신이웃의 일부를 (고정된 수 만큼) sampling하여 이 문제를 완화했지만,메모리 오버헤드memory overhead가 여전히 큼. (from GraphSAINT paper)
Appropriate Selection of Target Vertices (Sub-graph sampling)
최근의 GNN mini-batching 연구들은 mini batch를 형성할 target node를 어떻게 적절하게 선택할 것인지에 초점을 두고 있고, 이는 support vertices가 굳이 필요하지 않음을 이야기 하고 있다.
(More recent GNN mini-batching works focus on the appropriate selection of target nodes included in mini-batches, such that support vertices are not needed for high accuracy.)
(즉, 굳이 target node를 설정해서 그 이웃들을 sampling 할 필요가 없다는 의견)
한 예시로, ClusterGCN은 먼저 graph의 일부분을 cluster 한 다음, 해당된 cluster를 각각 mini-batch에 할당한다.
ClusterGCN의 접근법 (from CS224W)
이 방법은 정보가 손실되는 문제를 어느 정도 완화(완전히 없애는 것은 불가능.)할 수 있는데, 이는 mini-batch에 밀접하게 연결된 node community가 포함되기 때문이다. (because a mini-batch usually contains a tightly knit community of vertices.)
또한 이 방법은 clustering 하는 작업이 preprocessing 작업에 반드시 필요하지만, 이 방법은 parallel clustering routine을 통해 병렬적으로 처리하여 시간을 단축 시킬 수 있다.
(DistDGL/PyG에서 graph partitioning을 하는 것 처럼.)
Discussion
graph partition parallelism vs. mini-batch parallelism?
model weight를 update하는 시점의 차이: graph partition은 full batch 후에 update하고, mini-batch는 각 mini-batch마다 update한다.
주 목적 (primary objective): graph partition은 worker간 communication & work imbalance를 최소화 하는 것이 목적이며, mini-batch는 accuracy를 최대화 하는 target node를 선택하는 것이 목적이다.
각 node의 할당: graph partition은 모든 node가 각각 partition에 포함되며, mini-batch는 포함될 수도 있고 안 될수도 있다.
sample 간의 dependency: graph partition은 연결된 graph를 다루므로 항상 dependency가 발생하지만, mini-batch는 dependency가 없는 경우도 존재한다. (ex. molecule dataset)
Work-Depth Analysis : Full-batch vs. Mini-batch
좌측 복잡도가 work량, 우측 복잡도가 depth 정도.
$c$는 layer 마다 sampling 된 이웃의 수를 의미.
$k$는 input feature의 차원을 의미.
$n$과 $m$은 각각 node의 수, edge의 수를 의미.
$d$는 graph 안에서의 최대 degree를 의미.
$L$은 layer의 개수를 의미.
Work analysis
모든 방법은 공통적으로 $O(Lmk + Lnk^2)$ 의 복잡도를 가진다.
각각은 layer의 수 $L$에 각 layer에서 수행되는 작업의 수를 곱한 것.
$Lmk$ 는 layer에서 발생하는 sparse graph operation (Aggregate) → edge를 통한 feature의 message passing
$Lnk^2$ 는 layer에서 발생하는 dense nn operation (UpdateVertex) → passing 된 message(feature)를 통해 자신 feature와 mm(matrix multiplication)을 통한 갱신
Mini-batch의 경우는 추가적인 작업이 발생한다.
(전체 그래프에 대한 작업은 full-batch와 같은데, mini-batch에서 각각 실행하고 통합하는 작업이 발생.)
support vertices (node sampling): node를 선택하는 알고리즘(sampling algorithm)에 따른 작업량이 존재한다.
GraphSAGE, VR-GCN: $O(c^Lnk^2)$, 선택할 이웃의 수를 어떻게 설정할지 와 layer의 수에 따라 작업량이 결정되며, 이는 neighborhood explosion을 일으킬 수 있다.
FastGCN: $O(cLnk^2)$, 모든 layer에서 c개의 노드만을 샘플링해 explosion을 완화. (Importance sampling(참고링크)을 통해 explosion 문제를 어느정도 완화)
selecting target vertices (graph sampling): neighborhood explosion에 영향을 미치는 term이 존재하지 않지만, $W_{pre}$(전처리 과정)만큼의 추가 작업이 소요된다.
ClusterGCN: 추가 작업은 clustering 방법에 크게 영향을 받는다. 즉 input graph의 크기 $(n, m)$ 에 크게 영향을 받는다. (추가 작업량이 n, m에 따라 증가)
GraphSAINT: stochastic mini-batch selection을 통해 추가 작업이 그래프 크기에 따라 반드시 증가하지는 않는다. (추가 작업량이 n, m에 따라 반드시 증가하진 않음)
Depth analysis
모든 full-batch 방식의 접근은 layer의 수 $L$ 에 의존하고, 2가지 bottleneck이 존재한다.
$L\log{k}$ 는 layer에서 발생하는 dense nn operation(UpdateVertex : 행렬-벡터곱) → feature 작업을 위한 work로서, feature 크기 $k$만큼 소요.
$L\log{d}$ 는 layer에서 발생하는 sparse graph operation(Aggregate) → graph와 관련된 작업(message passing)을 위한 work로서, 전체 노드 수, 어떻게 보면 그래프에서 가장 높은 차수 $d$만큼의 작업이 소요.
mini-batch의 경우는 $\log{d}$ 대신 $\log{c}$ 가 적용된다.
→ Aggregate 연산이 full-batch처럼 전체 graph를 대상으로 하는 것이 아닌, sampling 된 $c$개의 이웃 node만을 대상으로 일어나기 때문이다.
ClusterGCN, GraphSAINT (graph sampling)의 경우는 $c$ 가 $d$ 로 바뀔 수 있는데, 이는 이웃의 수가 최대 $d$ 만큼이 될 수 있기 때문. (이웃 수를 정해진 수 만큼 sample 하는 것이 아니라, clustering 등의 다른 방법으로 주변 특정 노드들을 모으므로 얼마나 모일지가 모른다.)
또한 사용되는 sampling/clustering 기법에 따라 추가적인 $D_{pre}$ 만큼 작업이 소요된다. (선택하는 전처리 알고리즘, 즉 graph partitioning 알고리즘에 따라 전처리 시간이 달라진다는 것.)
mini-batch의 경우 support vertices 또는 target vertices select를 위한 추가 작업으로 인해 전체 작업시간이 더 걸릴 수 있지만, 빠른 수렴율과 상대적으로 적은 메모리 사용량을 가진다.
(mini-batch의 경우 node/graph sampling의 추가 작업으로 인한 오버헤드로 인해 전체 작업이 full-batch에 비해 더 커질 수 있지만, full-batch에 비해 수렴이 훨씬 더 빠르고 메모리 사용량이 훨씬 더 적은편이다.)
Trade-off Between Parallelism & Challenge
mini-batch 에서의 parallelism 양과 mini-batch 크기에 의해 결정되는 수렴 속도에는 항상 efficiency에 대한 trade-off가 존재한다.(traditional NN에서도 마찬가지.)
작은 크기의 mini-batch는 수렴 속도를 높일 수 있지만 병렬화가 힘들어지고, 큰 크기의 mini-batch는 수렴 속도가 느려질 수 있지만 병렬화가 비교적 쉬워진다.
GNN에서는 data sample간의 dependency 때문에 적절한 크기의 mini-batch를 결정하는 것이 매우 복잡하다.
거대한 mini-batch이지만, 서로 연결이 되지 않은 node들이 있다면 정확도가 낮아질 것이다.
작은 mini-batch 이지만, cluster를 형성하는 것과 같이 strongly connected node들이 많다면 정확도는 높아질 것이다.
이전 파트에 이어서 마저 작성하다 보니, 참 여러 추억(?)이 생각나기도 한다.
System 분야를 하나도 모르는 상태에서, 맨땅에 머리를 부딫혀가며 하나하나 차츰차츰 알아가고... 귀가하는 시간이 늦어지곤 했지만, 그래도 돌이켜보면 이전부터 관심이 조금이라도 있었던 분야를 알아가니 그 재미가 은근히 있었던 것 같다.
그리고 무엇보다 이쪽 연구(GNN, System)를 할때가 연구다운 연구를 했던 때 같긴 하다. 인용된 paper를 찾아가서 또 해당 부분을 읽어보고, 다른 자료(실제 code, 영상)들을 어찌저찌 찾아서 보고...
(연구비라던지, 적절한 지도 라던지는 별개이긴 했지만..^^)
이전 파트를 작성하고 난 후로 자잘한 가족여행도 몇번 다녀오고, 6월달에 열렸던 채용공고도 다시 한번 써보고...
작년이랑 문항이 거의 똑같긴 했었는데, 마지막 문항이 '직무 관련 경험' 에서 'AI 도구를 활용하여 성과를 낸 경험'으로 바뀌어서 상당히 당혹스러웠다ㅠ
ChatGPT, Bard가 기억상 3학기 즈음에 출시되었는데, 당시에는 인터넷으로 직접 찾는것 보다 너무 퀄리티가 떨어져서 사용을 하질 않았는데 😅... 어찌저찌 만들어서 적어내긴 헀지만 그래도 부족하긴 했나보다 ㅋㅋㅋ
여하튼,
GNN system survey paper의 리뷰(사실상 notion에 정리했던 걸 옮기는 작업)가 끝났기에,
아마..도 다음 paper 관련 포스팅은 당시에 이어서 리뷰했던 GNN system 관련 paper를 할 듯 하다.
대표적인 예시로 social science, bioinformatics, chemistry, medicine, cybersecurity, entertainment services, transportation 등 이 있다.
(사실상 data가 graph로 표현될 수 있는 field라면 얼마든지 활용이 가능한게 아닌가...싶은 생각이 들기도 한다.)
GNN의 특이한 점은, 전통적인 딥러닝(traditional deep learning)과 그래프 처리(graph processing)가 함께 섞인 형태라는 것이다.
우리가 흔히 접하는 일반적인 data(이미지와 같은 grid 형태)엔 잘 동작하지 못하며,
data 자체가 매우 복잡하고 불규칙적인 형태이며, 계산의 결과가 data-driven이다. (data 그 자체의 특성에 따라 결과가 나온다? 정도로 생각하면 될 듯 하다.)
또한 node와 edge가 복잡한 data와 이를 processing하는데 매우 긴밀하게 연관이 되어 있는데,
대다수 GNN의 연산은 각 node $i$는 $k$차원의 feature vector에 assign 되고, 이 feature vector는 node $i$의 이웃 node들의 vector와 또 결합되는 과정이 반복된다.
(간단히 말해 node마다 feature vector가 있고, 이 feature vector가 이웃 node들의 feature와 aggregate하여 feature learnig을 한다는 것.)
어떻게 보면 계산 과정이 PageRank의 label propagation algorithm과 거의 유시하지만, feature가 추가되어 계산 과정이 더욱 복잡해진다.
하지만 앞서 설명한 예시는 가장 간단한 GCN의 예시이고, 연산 과정이 더욱 복잡한 GNN들이 대다수 존재한다.
고 차원의 data가 모든 edge에 포함되어 있을 수 있고, 매 iteration 마다 복잡한 update를 통해 edge data가 갱신된다.
대표적인 예시로, GAT의 경우 하나의 edge $(i,j)$의 가중치(weight)를 계산하기 위해 두 node의 feature vector를 concat한 후, trainable한 weight vector와 dot product를 수행한다.
이처럼 계산 과정이 복잡해짐에 따라, 병렬(parallel) 및 분산(distributed) 처리가 계산 효율성을 위해 필수적인 요소가 되었다.
현대 컴퓨터 구조는 대부분 병렬 구조로서, core가 socket을 구성하고, socket은 NUMA(Non-Uniform Memory Access) compution node(blade, chassis, rack 등... 서버실에 있는 디스크로 생각하면 될 듯)를 구성한다.
거대한 memory bank를 통해 data의 분산 저장이 가능해지고, 이 구조들은 전부 병렬적으로 작업을 수행할 수 있다.
마찬가지로 단일 core 또한 vectorization, pipelining, instruction-level parallelism(ILP)를 제공하고 있다.
이러한 구조들은 대부분 heterogeneous(이질적. 서로 다른 구조)하며, processing unit이 CPU/GPU/FPGA 들 중 하나이다.
GNN workload에서 더 좋은 성능을 얻기 위해 이런 풍부한 자원들을 제대로 활용할 수 있는 방법들엔 무엇이 있을까?
본 논문에서는 이 질문에 대한 답으로,
GNN들의 다른 관점을 분석하며, 그 중에서도 parallelism과 distribution에 초점을 맞춘다.
GNN에서 사용되는 선형 대수 공식(model의 수식)에 초점을 맞추고, 관련된 tensor의 sparsity & density를 조사하고자 한다. $\rightarrow$ GNN computation 성능에 대한 추가적인 정보를 제공받을 수 있고, vectorization과 같은 병렬화 매커니즘을 쉽게 적용할 수 있도록 하는 길을 안내해줌으로써 더 효율적인 GNN computation을 개발 할 수도 있을 것이다.
GNN 병렬처리에 대한 심층적인 분류체계를 제공, 즉 GNN의 기본적인 병렬처리의 종류들을 구분할 것이다. (일부는 traditional deep learning에서의 방법과 거의 동일하지만, 일부는 GNN에 특화되어 있기 때문.)
GNN은 graph embedding 영역에도 속한다고 볼 수 있는데(embedding 구축을 위한 model), 본 논문에서는 random walk, graph kernel design과 같은 GNN-base가 아닌 방법들은 전부 제외한다.
Graph Neural Networks: Overview
주로 사용되는 symbol table
GNN Computation: High Level Summary
일반적인 GNN 연산과정
input은 graph dataset으로, single graph(1개의 large graph, 대표적으로 Reddit과 같은 social graph) 또는 여러개의 graph(크기가 작은 여러개의 graph, 대표적으로 PPI와 같은 화학분자구조 graph)가 될 수 있다.
input에선 마찬가지로 주어진 task에 대한 semantic 정보가 인코딩 된 input feature vector가 함께 구성된다.
즉, (대부분/일반적으로) input은 graph data + feature vector가 들어오는 셈이다.
간단한 예시로, citation network를 생각해보자.
여기서 node는 paper를 의미하고, edge는 paper간 인용(citation)관계를 나타내게 된다.
각 node마다 input feature vector가 따라올 것이고, 이는 주어진 paper를 요약한 one-hot bag-of-words encoding.
이후 GNN model은 graph의 구조와 input feature vector를 사용해 output feature vector를 생성한다.
input-output의 중간 과정에서 보통 hidden latent vector(잠재 벡터)가 생성되고, 이는 최소 1번 이상 갱신되는 값. (즉 학습하게 되는 vector/embedding)
결과로 나온 output feature vector를 이용해 downstream task(이 경우 대부분 node classification)를 수행한다.
단일 GNN layer의 구조
단일 GNN layer의 구조는 위 그림처럼 표현할 수 있으며, 연산 과정을 정리하면 다음과 같다.
graph-related operation을 수행. 즉 message passing & aggregation을 수행. (GCN의 예시로 node $v$의 모든 이웃의 feature vector를 aggregate하여 자기 자신의 feature vector와 summation.)
이후엔 전통적인 neural network operation을 수행. (MLP 또는 linear projection)
마지막으로는 linear activation 또는 normalization을 수행. (GCN의 경우 ReLU activation)
그렇다면 GNN이 일반적인 NN(Neural Network)과 다른 점이 무엇일까?
input으로 들어온 data sample에 dependency가 존재할 수 있고, 이러한 특성 때문에 GNN의 병렬화가 더욱 어려워진다.
GNN의 input으로 들어오는 data sample의 종류
위 그림과 같이, 하나의 sample은 node일 수도 있고, edge일 수도 있고, 작은 subgraph일 수도 있고, graph 그 자체가 될 수도 있다.
기본적으로 node와 edge는 서로 inter-dependency(상호의존성)가 존재한다.
즉, node들은 edge를 통해 연결되어 있고, edge는 몇몇 개의 node를 공유하고 있다.
(어찌 보면 당연한 이야기 긴 하지만... survey paper이다 보니 상세하게 알려주는 느낌)
data sample로 사용되는 graph들은 보통 independent한 경우가 대부분이고, 대표적인 task 예시로는 화학분자구조 의 분류(classification)가 있다.
이러한 특징(independent graph as data sample)은 image와 같은 traditional deep learning에 활용되는 data sample과 유사하며, sample(단일 image)들은 서로 간 명시적인 의존성(dependency)가 없다.
하지만 image와는 다르게 이러한 graph들은 단일 graph마다 크기가 다를 수 있으며, 여기서 load balancing issue가 생길 수 있다. (마치 data sample이 video인데, 이 video들의 길이가 서로 다른 것 처럼)
Input Datasets & Output Structures in GNNs
상단 TABLE 1 참조.
GNN Mathematical Models
GNN model은 adjacency matrix $A$와 input feature matrix $X$를 받아서 output feature matrix $Y$를 생성하는 수학적 변환을 정의한다.
즉, $AX \approx Y$를 정의하는 셈.
2개의 matrix $A$와 $X$를 기반으로 $Y$를 구성하는 방법은 활발한 연구분야(matrix approximation 또는 low-rank approximation)이며, 이를 토대로 아래 그림과 같이 다양한 GNN들이 제안되어 왔다.
categories of GNN models
병렬 및 분산 처리에서 중요한 것은 2가지로, LC 또는 GL을 통해 대부분의 GNN 모델은 공식화 할 수 있다는 점이다. (GNN model을 아래의 2가지 범주의 수식으로 표현할 수 있다는 의미)
Local(LC) GNN Formulations
단일 node(edge)에서 동작하는 함수를 기반으로 하는 공식화이다.
(single node(edge)-level에서 동작하는 함수를 통해 model을 구성)
즉, 이 경우엔 vector - vector (또는 dense matrix - dense vector) 단위로 for loop을 통해 계산한다.
주어진 node $i$에 대한 latent feature vector $\mathbf{h}_i$는 해당 node의 이웃 집합의 feature vector들에 대해 permutation invariant aggregator function $\oplus$ (대표적으로 sum, max)를 적용해 구할 수 있다.
또한 node $i$의 각 이웃 node의 feature vector는 추가적으로 함수 $\psi$를 통해 변환될 수 있고, $\oplus$를 거쳐서 나온 결과는 또 다시 $\phi$ 함수를 통해 변환될 수 있다.
이 일련의 과정이 하나의 GNN layer를 구성하게 되며, 이를 기반으로 한 GNN 모델을 local(LC)로 명명하며, 위의 과정을 식으로 나타내면 다음과 같다. node i에서 다음 layer의 feature vector를 계산하는 식.
함수 $\psi$에 따라 GNN 유형을 3개로 분류가 가능하며, 이는 Conv GNN(C-GNNs), Attentional GNN(A-GNNs), Message-Passing GNN(MP-GNNs)이다.
이 경우 함수 $\phi$는 보통 fixed scalar, scalar를 반환하는 learnable function, vector를 반환하는 learnable function이 된다.
GCN을 예시로 생각해보자. $\oplus$는 자신과 모든 이웃 node를 aggregate 하는 function이 되고, $\psi$는 각 이웃의 feature vector마다 곱해주는 scalar 값($1/\sqrt{d_id_j}$)이 되며, $\phi$는 trainable parameter matrix $W$와 함께 ReLU를 취하는 linear projection이 되며, 따라서 GCN을 LC 형태로 나타내면 다음처럼 표현할 수 있다. GCN의 propagation rule의 vector representation
(GCN paper에서는 matrix 형태로 기술하지만, 여러 강의자료나 paper를 보면 이처럼 vector 형태로 기술하기도 한다.)
Global(GL) GNN Formulations
모든 node(edge)와 관련된 vector를 그룹화 하여 matrix 연산에 기반한 방법이다. (global node(edge)-level에서 vector를 쌓은 matrix로 matrix 연산을 통해 model을 구성)
즉, 이 경우엔 vector - matrix 또는 matrix - matrix 단위로 MM(Matrix Multiplication)을 통해 계산하며, 직전의 LC를 좀 더 일반화 한 셈으로 볼 수 있다.
많은 GNN model은 또한 matrix $\mathbf{X}$, $\mathbf{X}$, $\mathbf{X}$ 들의 matrix 연산 형태로 표현할 수도 있고, 이 형태를 기반으로 한 GNN model을 global(GL) 으로 명명하고자 한다.
GCN을 예시로 생각해보자. 앞선 LC 형태의 수식을 matrix 형태로 표현하면 다음과 같다. GCN propagation rule의 matrix representation
이때 $\hat{A}$는 self-loop이 포함된 adjacency matrix가 normalized 된 형태로, $\hat{A} = \tilde{D}^{-1/2}\tilde{A}\tilde{D}^{-1/2}$ 이며, 이 normalization은 LC 형태에서의 scalar 값 $1/\sqrt{d_id_j}$를 내포하고 있다. (GCN formula와 관련된 상세 내역은 해당 포스팅(링크) 참고.)
대부분의 GL 형태의 model은 adjacency matrix $A$의 더 높은 거듭제곱을 사용하는데, 이는 한 node를 기준으로 더 떨어진 hop 정보를 얻기 위함이다. (graph theory 에서 나오는 정의 로 알고있지만... 상세한 내용은 graph theory를 제대로 공부하지 않아 그렇다 라는 정의 정도로만 알고 있다.)
이에 따라 $A$를 1번만 사용한 linear model이 될 수도 있고, 여러번 사용한 polynomial model이 될 수도 있고, rational power를 곱한(분수 제곱형태) rational model이 될 수도 있다.
또한 연산에 있어 sparse matrix, dense matrix가 모두 사용되며, 이런 matrix에 대한 작업의 성능 패턴이 크게 다르기 때문에 parallelization routine에 대한 가능성이 존재하는 것이 특징이다.
GNN Inference vs. GNN Training
해당 부분은 일반적인 NN에 대한 내용과 거의 동일하기에 기술하지 않음.
GNN Programming Models and Operators
최근(2022년) 연구들을 통해 시스템 관련 커뮤니티에서 programming model & execution model을 제공하고 있다.
programming model은 특정 코드 패턴에 연결된 실행 모델로, Spark를 생각해보면 된다. Java가 기본 언어이고, Spark가 프로그래밍 모델인 셈.
execution model은 언어 요소의 동작을 지정하는 모델로, 이를 적용하면 해당 프로그래밍 언어로 작성된 프로그램의 동작을 생성할 수 있다. 즉 Spark 플랫폼이 특정 어플리케이션을 처리하는 방식을 생각해보면 된다.
이런 model들은 아래 그림과 같이 LC formulation, GL formulation 형태로 GNN의 주요 function을 구현할 수 있도록 도움을 준다:
GNN model formulation과 어떻게 이를 GNN operator로 변환하는지에 관한 그림. 즉 framework의 동작 과정으로 볼 수 있다.
가장 널리 쓰이는 programming model은 SAGA(Scatter-ApplyEdge-Gather-ApplyVertex) 이며, LC formuation으로 보면 다음과 같다:
Scatter를 통해 feature vector를 concat, ApplyEdge(UpdateEdge)를 통해 변환. $\rightarrow$ 즉, 함수 $\psi$를 정의하는 동작이고, target node와 인접한 1개 node간의 message passing을 수행한다.
Gather를 통해 앞선 작업(Scatter & ApplyEdge)의 출력을 aggregate. $\rightarrow$ 즉, 함수 $\oplus$를 정의하는 동작이고, target node와 (선택된) 인접한 모든 node간의 message를 AGGREGATE한다.
ApplyVertex를 통해 aggregated vector에 특정 연산을 적용. $\rightarrow$ 즉, 함수 $\phi$를 정의하는 동작이고, target node의 새 feature를 target node의 이전 feature와 AGGREGATE한 feature를 이용해 update한다.
(기억상 PyG docs에서 이 SAGA와 관련된 그림이 그려진 example을 봤던 기억이 있는데... 몇년 새에 업데이트 되어서 그런지 보이지가 않는다 😥)
그럼 왜 이렇게 programming model을 정의할까?
당연히 programming 적인 이점도 있지만, 이러한 연산 하나하나를 병렬화 하여 acceleration을 할 수 있기 때문이다.
(자세한 내용은 논문 Section 4 - Model Parallelism을 참고.)
Taxonomy of Parallelism in GNNs
일반적으로 traditional DL에서의 병렬화 방법은 2가지가 존재한다: data parallelism, model parallelism.
Data Parallelism은 서로 다른 worker로 data sample이나 weight를 분할&전달하여 training하는 것이고,
Model Parallelism은 서로 다른 NN layer가 병렬적으로 실행되거나(pipeline parallelism), 단일 operator가 병렬적으로 실행되는 것(operator parallelism)이다.
GNN에서의 parallelism 또한 동일하게 2가지로 분류될 수 있는데, 앞서 언급한 traditional DL과 명확한 차이점이 존재한다.
Overview of GNN parallelism
Data Parallelism은 크게 2가지로 분류된다:
Mini-batch parallelism: mini-batch execution을 병렬화하여, 각 execution 후에 weight update.
dependent mini-batch parallelism은 sample 간 서로 dependency가 있는 경우
independent mini-batch parallelism은 sample 간 서로 dependency가 없는 경우로, 기존 traditional DL에서의 data parallelism과 거의 유사.
Graph [partition] parallelism: 한 worker에 batch를 담을수가 없는 경우, batch 자체를 병렬화 하여 전체 batch execution 후에 weight update
말 그대로 graph를 partition하는 parallelism으로, 단일 mini-batch가 너무 거대한 경우에도 적용이 가능.
Model Parallelism은 크게 3가지로 분류된다:
Pipeline parallelism: 실제 GNN layer 자체를 pipelining(macro-pipeline)하거나, 단일 GNN layer에서의 sample processing 과정을 pipelining(micro-pipeline).
operator parallelism: 단일 feature vector의 update를 병렬적으로 수행(feature parallelism)하거나, 서로 다른 neighbor를 다른 worker에 할당하여 병렬적으로 갱신(graph [structure] parallelism).
ANN-parallelism: UpdataEdge & UpdateVertex 이후의 dense NN operation에 기존의 traditional한 NN parallelism을 적용.
뭐.. 단순히 보면 기존 DL의 parallelism과 (아마도) 거의 동일하지만,
단지 graph data임에 따라 dependency가 생기므로, 이를 고려한 기법이 상세화 되는 것으로 볼 수 있다.
Parallel and Distributed Models and Algorithms
본 논문에서 어떤 기준으로 parallel/distributed model/algorithm을 평가할 것인지를 기술한다.
single-machine (with shared memory)에선 work-depth (WD) analysis를 사용하도록 하고, 이는 병렬 알고리즘의 bounding runtime을 분석하는데 있어 쳬계가 잡힌 접근 방식.
지난번 포스팅에서 PyG의 mini-batch training을 위한 방법 중 하나인 NeighborLoader를 간략하게 살펴보았다.
이번엔 NeighborLoader와 간단한 dataset을 활용하여
이 neighbor sampling (또는 node-wise sampling)의 특징(?)을 알기 위해 간단하게 진행했던 실험에 대해 정리하고자 한다.
작성 편의상 sampling을 수행하는 기준 node, 즉 sampling을 수행하였을 때 최 상단에 위치하게 되는(기준이 되는) node를 anchor node로 작성하였다.
Cora dataset & EDA
여러 초장기 GNN paper에 자주 등장한 dataset이다.
PyG에선 torch_geometric.datasets.Planetoid 에 함께 포함되어 있다.(공식 doc 링크)
여러 GNN paper에 기재된 듯이, 비교적 작은 규모의 citation network 이다.
(전체 node의 수는 2708, edge 수는 5429, feature vector의 차원이 1433.)
description (from papers with code)
그럼 그래프가 어떻게 생겼을까?
살펴보면 다음과 같다. (이미지 출처는 이미지에 포함)
class 별로 coloring 된 cora dataset.
직접 plotting해본 것도 있지만... 아무래도 이렇게 이쁘게 그려주신 분이 있다면 :)
추가적인 EDA를 위해 필요한 라이브러리를 import 한다.
import torch
import networkx as nx
import matplotlib.cm as cm
import matplotlib.pyplot as plt
import pandas as pd
from torch_geometric.datasets import Planetoid
from torch_geometric.loader import NeighborLoader
from torch_geometric.seed import seed_everything
from torch_geometric.utils import to_networkx, unbatch_edge_index
seed_everything(62)
dataset = Planetoid(root='dataset', name='cora')
data = dataset[0]
del dataset
data.n_id = torch.arange(data.num_nodes)
그럼 batch 안에 들어가는 실제 computation graph는 어떻게 생성되는지 확인해보자.
sample_batch = subgraph_list[INDEX_NUM]
# edge_batch = sample_graph[sample_graph.edge_index[0]]
# print(sample_graph.edge_index[0]) # source->target 에서 target에 해당하는 index만을 챙기기.
### batch vector에서 실제로 어떤 node가 어떤 sub-graph에 할당되어 있는지 확인.
### 즉, 기존 edge_index랑 이 값이랑 비교하면 어떤 앵커가 몇개를 샘플링했는지 확인가능.
# print(sample_batch.edge_index.numpy())
# print(sample_batch.batch[sample_batch.edge_index[0]].tolist())
# batch안에 있는 개별 anchor node마다의 computation graph를 추출할 수 있음.
sample_graph_edge_index = sample_batch.batch[sample_batch.edge_index[0]]
for i in range(BATCH_SIZE):
edge_index_for_i = sample_batch.edge_index[:, sample_graph_edge_index == i]
print(edge_index_for_i.numpy())
print된 결과물(첫번째 batch에 포함된 computation graph들)을 보면 다음과 같다.
현재 batch에서의 생성된 computation graph
모든 node마다 2-hop sampling을 수행하였고, 1-hop에서는 2개의 node를, 2-hop에서는 4개의 node를 sampling 했다.
앞서 수행했던 data.n_id = ~~ 부분을 통해 edge_index에 있는 값 (0~15)과 실제 graph에서의 id값인 n_id의 매칭을 통해 각 배치마다 어떤 node가 computation graph를 어떻게 형성하는지 확인 가능하다.
앞서 지정한 BATCH_SIZE 개수 만큼의 computation graph가 생성된 것을 확인할 수 있는데,
눈여겨 볼 점은 각 computation graph마다의 크기가 다른 점이다.
상단 Cora dataset의 graph plot을 봐도 알 수 있듯, 각 node의 degree가 long-tail distribution의 형태를 따르고 있기에
sampling을 수행할 때 고립된(isolated) node가 있다면 서로간의 sampling만을 수행하고 종료되는 것을 짐작할 수 있다.
그렇다면 무엇이 문제일까?
비교분석을 위해 간단한 코드를 작성해서 chart로 확인을 해보자.
# ## x축은 각 batch id, y축은 그 길이 값 -> bar chart
graph_info_tuples = list(zip(nodes_in_subgraph, edges_in_subgraph))
df = pd.DataFrame(graph_info_tuples, columns=['node_num', 'edge_num'])
ax = df.plot(kind='bar', rot=0, xlabel='batch_id', ylabel='value', title=f'batch_size={BATCH_SIZE} \n num_neighbors={NUM_NEIGHBORS}', figsize=(12, 8))#, cmap=cmap)
df.plot(kind='line', xlabel='batch_id', ylabel='value', ax=ax)#, cmap=cmap)
for c in ax.containers:
ax.bar_label(c, fmt='%.0f', label_type='edge')
ax.margins(y=0.1)
ax.legend(title='columns', bbox_to_anchor=(1, 1.02), loc='upper left')
plt.show()
먼저 batch_size를 다르게 (batch마다 들어가는 anchor node 수를 다르게) 한 경우를 살펴보면 다음과 같다.
batch마다 들어가는 anchor node 수를 다르게 한 경우, 한 개 batch마다의 전체 computation graph들의 크기가 증가하는 경향을 볼 수 있다.
즉, batch_size를 크게 잡으면(한 batch에 넣을 anchor node수를 늘리면) 1개 batch당 computation graph 수 증가하게 되고, batch당 계산량의 차이가 더 심해질 수 있다.
다음은 num_neighbors를 다르게 (고려하는 hop을 다르게, 고려하는 이웃 수를 다르게) 한 경우는 다음과 같다.
고려하는 hop 수를 늘리거나, 고려하는 이웃의 수를 늘리는 경우 (앞서 살펴본 경우와 비슷하게) 한 개 batch마다의 전체 computation graph의 깊이/크기 차가 심하게 증가하는 경향을 확인할 수 있다.
즉 batch당 computation graph의 깊이(depth)/크기가 증가하는 것을 확인할 수 있고, 그 차이의 폭(gap)이 더욱 커지는 것을 바로 확인할 수 있다.
특히 위 사진의 3사분면과 4사분면에서 그 차이가 가장 크게 두드러지는 것을 확인할 수 있는데,
고려하는 이웃의 수가 많으며 고려하는 hop 수가 늘어남에 따라 computation graph의 깊이/크기가 지수적으로 폭발하는 현상을 neighborhood explosion이라고 한다.
(여러 paper에서 언급되는 용어 이기도 하고, PyG 공식 docs에서도 간략하게 소개하고 있다.)
Consideration
neighborhood sampling은 random sampling을 통해 정말 간단하면서도 상당히 안정적인 mini-batch training을 가능하게 한다.
하지만 앞서 진행한 간단한 mini-batch test를 통해 확인한 것 처럼, 무턱대고 sampling을 수행하게 되면 batch마다의 computation graph 크기가 달라지게 되고, neighborhood explosion 문제가 발생할 수 있다.
그렇다면 이게 왜 문제가 될까?
실제 산업현장이 아닌 이상, 대부분 single GPU로 시간을 들여서 이런 문제는 충분히 커버할 수 있는 수준일 것이다.
(위처럼 Cora 같은 dataset이나, Reddit 같은 dataset이라면...)
하지만 실제 산업현장(실세계, real world)에서 등장하는 대부분의 데이터들은 규모가 정말 방대한 경우가 많고,
single GPU로는 턱 없이 부족한 경우가 많다.
(당장 Jure 교수님 산하의 stanford 팀에서 개발/운영중인 OGB(Open Graph Benchmark)를 봐도 large-scale challenge가 있고, 제공중인 dataset에도 상당히 큰 규모의 dataset이 있다.)
결국 Multi-GPU를 통한 distributed training이 필요한데, 보다시피 graph data의 특성 상 이게 상당히 힘들다.
(즉 large graph로부터 mini-batch를 어떻게 잘 만들 것 인지가 문제.)
GPU마다의 load balance를 맞추기 위해선 각 GPU마다 담당하는 양, 계산량이 비슷해야 synchronize가 잘 되어서 이상적인 distributed training이 될 것인데...
앞서 보았듯, mini-batch 마다 그 안에 들어있는 graph data의 크기가 제각각이니 synchronization도 잘 이뤄지지 않게 되고, GPU마다 계산량이 달라지게 되며 결국 놀게되는 GPU가 생겨날 수 밖에 없다.
(이렇게 놀게되는 GPU가 생겨서 작업 시간이 비효율적?이게 되는 문제를 straggler problem이라고도 한다.)
결국 mini-batch를 어떻게 잘 만들어 볼 것이냐 가 관건일 것인데, 단순히 GraphSAGE에서 제안한 random sampling 만으로는 mini-batch를 어떻게 적절하게 잘 만들기가 상당히 골치아프다는 점을 짚고 넘어갈 수 있다.
Conclusion
물론 다양한 paper들이 large-scale graph를 어떻게 효율적으로 distributed training 할 수 있는지 그 방법을 제안하기도 했고, Tiktok의 개발사?로도 알려진 ByteDance에서도 관련된 paper를 내기도 했다.
(당시 review했던 paper들은 기회가 된다면 천천히 posting 하는 것으로...)
당시 GNN 공부와 distributed training을 학습하며 정리했던 notion 페이지 일부
글을 작성하며 예전 notion에 정리했던 내용들을 천천히 다시 둘러보니... 참 여러 생각이 든다.
돈 한푼 없이 정말 말 그대로 재미?로 시작해서, 다른 연구실 분과 협업도 해보고, 실험을 하면서 이런 현상이/문제가 정말로 있구나 하는걸 직접 확인하고, (비록 국내 학술대회 였지만) 첫 논문도 작성해보고...
단지 프로젝트가 상당히 길어질 것 같아 3학기를 마친 후 방학때 물러나게 되었지만, 기회가 된다면 다시 한번 진득하게 보고 싶은 field 이기도 하다.
지금은 뭐... 연구실의 다른 후배 분이 이어서 해주시고 있는지, 프로젝트가 그냥 무산났을지도 모르겠다.
당시에도 연구실에서 이 프로젝트를 하던 인원이 나 밖에 없었고, 4학기때 새로 들어온 후배 분에게 간략?한 인수인계를 해드리긴 했는데... 어떻게 진행중인지는 ^^;;
이전 PyG 포스팅 이후 딱 하반기 시즌이 열리는 바람에 하반기에 참 치이고 데이고,
정신없이 지내다가 한숨 돌리고 다시 기록해둬야지 하고 보니 어느새 거진 반년이 지나버리긴 했지만..
마음 속 응어리가 조금이나마 줄어든 기분은 든다. :)
다음 번 이쪽(Programming) 포스팅이 아...마 있을지는 잘 모르겠지만, 포스팅을 하게 된다면 당시 비교실험 등 을 진행했던 코드를 작성하며 review 해보고자 한다.
(집의 환경이 multi-GPU가 아니기에, 아마 single-GPU 코드를 작성하게 될 것 같긴 하다.)
18년 전역 당시, 데이터 사이언스 공부를 혼자 시작해보며 설치했던 anaconda 와 여러 패키지들.
연구실에 다니던 시절, 아직 원격 접속을 세팅하지 않았을 때,
집에서라도 조금씩 해둘걸 해두자 라는 마음에 연구실과 비슷한 환경으로 다시 설치를 하고자 했었다.
하지만 연구실에서 사용하던 데스크탑과 (현재까지도 사용 중인) 집 데스크탑의 HW 차이는 조..금 많이 심했다.
연구실에선 아마 기억상 3060ti?를 사용중이었는데, 집 데스크탑은 1060 3GB...
당시엔 그래서 환경을 따로 만들어 pytorch_cpu를 설치해서 하다가 도저히 참을수가 없어서 빠르게 원격 세팅을 마쳤던 기억이 난다🤣
이미 졸업을 하기도 했고, colab은 뭔가 불편했던 기억이 많이 남아있기에
그냥 마음편히 집의 데스크탑을 조금 더 굴려보기로 결정했다.
현재 집 데스크탑의 구성은 다음과 같다:
CPU: Intel(R) Core(TM) i5-4670 CPU @ 3.40GHz
RAM: 16.0GB DDR3
GPU: NVIDIA GeForce GTX 1060 3GB
그렇다. 상당히 오래됐다...
기억상 고3 졸업 or 새내기 시절에 작은형이 블레이드&소울을 한다고 구매하고 쓰던걸 넘겨받고 계속 쓰는 중이다.
10년은 진즉에 넘었지 싶다.
친구들도 컴퓨터 좀 그만 괴롭히고 놔주라고 하는데, 백수가 돈이 어디있겠는가 ㅎㅎ
그래서 우선 설치되어있던 CUDA, Anaconda를 삭제했다.
설치되어 있던 CUDA 10.1 관련 프로그램을 전부 삭제하고...
설치되어있던 Anaconda를 삭제했다.
근데 문제아닌(?) 문제 가 생겼다.
Anaconda 삭제가 도저히 끝나질 않았다 ㅋㅋㅋ
기다리고 기다려보니 거의 1시간 ~ 1시간 10분 정도 걸린 듯 하다.
기다리는 동안 친구들과 수다
18년 당시 Python을 처음 접해보며 시작했을 땐 가상환경이라는 것도 잘 몰랐고, 전부 base에 설치해서 했던지라
진짜 온갖 패키지가 다 있어서 삭제에 시간이 많이 걸린게 아닌가 싶긴 하다.
(졸업 프로젝트때 설치한 Django, Flask 같은 웹 관련 패키지, 공부할때 설치한 pytorch, tensorflow, keras, xgboost 같은 ML, DL 관련 패키지, plotly 같은 시각화 패키지 등...)
그렇게 온전한 삭제를 마치고, pytorch와 pyg를 설치하기 위해 우선 CUDA부터 설치하기로 했다.
windows에서 설치를 했던게 아..마 19년에 설치했던 것으로 기억하는데, 현재 사용중인 GTX 1060이 호환이 될까 싶어 인터넷 검색을 하던 중 마침 (비교적 최근에 올라왔던) 동일한 GPU를 사용하시는 분의 글을 발견: https://breakthedays.tistory.com/354
"For older GPUs you can also find the last CUDA version that supported that compute capability. For example, if you had a cc 3.5 GPU, you could determine that CUDA 11.x supports that GPU (still) whereas CUDA 12.x does not."
즉 나의 소중한 GTX 1060은 여전히 CUDA 11.x의 support를 받을 수 있다는 의미😊
그렇게 CUDA 11.8을 다운받아 설치하고...
cuDNN도 다운받아서 CUDA 설치 경로(C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8)에 복사를 하고...
사볼에선 예전에 엄~청 쥐약이었던 Xepher GRV를 S 클리어 할 수 있었다. 시간이 지나니 노트 처리력이 많이 늘은 것 같아 감회가 새로웠는 듯. (https://youtu.be/6G1xrvn2MVs)
7월
인생 처음이자 마지막인 (학생 예비군이 아닌) 동원 예비군 참석. 하필 장소가 대학교때 갔던 대화 훈련장이어서 어색한 느낌은 많이 들지 않았다.
더군다나 비가 왔던 날이라 교육도 전부 실내교육으로 진행하고, 사격 등 평가도 전부 개인별 평가로 했었다.
동반입대를 같이 했었던 고등학교 때 친구를 불러서 집에서 음주. 이전엔 대조동에 살았었던 친구인데, 이사를 풍무 쪽으로 가버리는 바람에 만날 기회가 적었지만 오랫만에 봐서 정말 재밌었던 기억이 난다.
5월달에 봤던 일본에서 생활중인 대학교 동기 친구가 다시 놀러와서 홍대에서 모였다.
홍대의 작은 룸을 하나 빌려서 밤새도록 음주. 이날 처음으로 천사의 유혹 이라는 고구마 소주를 처음 먹어봤다가 신기한 맛에 깜짝 놀랐던 기억이 난다.
그날 비웠던 술. 친구가 사온 우메슈도 정말 맛있었다.
외에도 역시 남겨진 사진은 집에서 먹은 술 사진... 이 정도면 알코올 중독이 아닌가 싶기도 하다.
밤 11시에 음주를 시작해서 유튜브로 디제잉 영상을 보면서 계속 먹다보니 아침 6시에 찍은 사진도 있다. 다시봐도 참 대단했는 듯.
게임적으로는 꾸준히 올라간 듯 하다. 팝픈에서 가장 많은 성과를 봤었는 듯.
팝픈에서는 48에 남아있던 노래들을 이것 저것 치울 수 있었다. 특히 레슨을 잡아서 다행이었다... (https://youtu.be/nHmdDaUQReo) 그리고 팝픈 입문 때 부터 꿈꿔왔던 노래인 Vinculum stellarum을 처음 클리어 해봤다. 투덱에서 NZM를 하드클 했을 때 만큼 정~~말정말 기뻤던 기억이 난다. (https://youtu.be/YqM-NKdGjFM) 추가적으로 49 짠게이지 노래도 처음 클리어해봤다. 아마 기억상 이 날 49를 8개나 클리어했었는 듯 하다.
팝픈에서의 비중이 가장 큰 듯 하다. 팝클래스가 이 한달동안 97.48에서 97.69까지 올라왔다.
제일 놀라웠던 영덤프. 예전에 친구의 추천으로 3번 플레이 한 후에 전혀 건들질 않고 있었는데, 묵혀두고 플레이 하니 클리어가 되어서 많이 놀라웠다.
9월
시골로 온 가족이 추석 전 벌초를 하러 갔다.
밤 늦게 출발해서 새벽에 도착한 후, 새벽 5시~6시 즈음 기상해서 벌초를 마치고...
저녁은 이전때와 마찬가지로 마당에 친척들이 모두 모여서 고기 파티(?)를 했다.
다음 날 형들은 먼저 귀가하고, 나는 부모님과 함께 문경-수안보로 들러서 온천호텔에서 하룻밤을 더 묶고 귀가.
고모가 일하는 상감한우 고기. 언제나 먹어도 정말 맛있다.아마 작년?제작년?즈음 부터 시골집에 눌러 앉은 고양이. 사람 손을 타서 그런지 피하지도 않고 만져도 별로 싫어하는 눈치를 주지 않아서 정말 귀엽다.
아버지께서 이전에 친구분들과 다녀오셨던 삼팔패키지 라는 것을 큰형네와 가게 되었다.
알고 보니 삼팔횟집이 현역 시절 군부대 바로 앞에 있던 횟집.... 많이 신선했다.
하조대에 도착해서 정~말 오랫만에 하조대짬뽕을 먹고, 조카를 데리고 해수욕장에서 한두시간 정도 놀고, 숙소에서 쉰 후 횟집에서 모듬회를 먹었다. 숙소숙박+모듬회식사 패키지로 다녀왔는데, 정말 만족스러웠다.
다녀오는 길에는 광치산 자연휴양림에 들러서 1박을 더 하고, 양구재래식손두부 라는 곳에서 밥을 먹고 충주댐을 구경한 후 귀가했다.
삼팔패키지에서 저녁에 먹었던 모듬회. 무한 제공이 가능하다곤 하는데, 양이 양인지라 리필은 조금 힘들었던 기억이 난다.양구재래식손두부. 원래 이런 류의 음식은 별로 내켜하질 않는데, 맛이 상당히 맛있어서 밥을 리필해서 먹었을 정도로 기억에 남는다.
언제나 있는 음주사진 ^^;;
그리..고 이때부터 하반기가 시작되어 본격적인 취준도 함께한 기억이 난다.
LG도 열리고, SK도 열리고, 한투도 열리고, CJ도 열리고...
바쁘지 않던 졸업 직전이나 올해 초 즈음에 어학 성적을 받아놨어야 했는데, 부랴부랴 OPIc을 신청하고 시험 전날에 유튜브를 보며 (순수 시간만 따지면 대략 6시간?) 공부를 한 후 시험을 봤다.
게임적으로는 약간의 변화(?)가 한번 있었다.
거의 한달 정도 투덱을 하지 않았다가, 설정을 바꿔보자 한 후에 리프트&서든을 바꾸고, 녹숫을 바꾸고... 다행히도 한번 바꾼 후에 바로 시선이 맞았다. 덕분에 Fly Above도 인생 최고점수...가 나오긴 했었다. (https://youtu.be/7geONeiJcvM)
팝픈에서는 49 마플럼을 클리어 했던게 가장 기억에 남는 듯 하다. 항상 후반에서 피 유지가 안되었는데, 이때는 어찌저찌 유지가 잘 되었었는 듯. (https://youtu.be/LLz0vega_nI)
11월
아버지 생신 때 온 가족이 모여서 저녁을 먹고, 일본 여행때 사온 닷사이23을 전부 비우고 천사의유혹도 어느정도 먹었던 기억이 난다.
솔직히 닷사이를 이날 전부 비울 줄은 생각을 못했다... ㅋㅋ
아버지 생신 때 먹었던 술.
부모님과 함께 산음자연휴양림에 다녀오고, 돌아오는 길에 포천 신북온천에서 하룻밤을 묶었다.
산음자연휴양림 가는 길에 먹었던 돼지불백이 정~~말 맛있었던 기억이 난다.
마찬가지로 가는 길에 홍천강 막걸리 양조장이 있어서, 제일 큰 패트병을 4통에 1만원에 사오기도 했다.
포천 가는길엔 화적연도 처음 가보고, 포천 시내에서 닭강정을 사면서 시내도 처음 잠깐동안 구경해봤다.
돌아오는 날엔 이동갈비를 먹고, 국립수목원도 처음 들러서 여러 구경을 했다.
식당 가기 직전에 보였던 막걸리 양조장. 딱 이 3종류만을 판매하고 있었다. 가격은 아마 기억상 4개/6개/8개에 1만원 이었던 것으로 기억한다.식당에서 먹었던 돼지불백. 상당히 맛있었는데, 밑반찬도 정말 맛있었다.돌아오는 날 포천에서 먹은 이동갈비. 아마 초등학교때 인가 이후로 처음 먹는 듯 했다.
취준 쪽으로는 좋은 소식이 없었다. 아무래도 SK와 CJ에 모든 운이 다 들어간게 아니었을까... 하는 생각이 들 정도였다 ㅋㅋㅋㅋ
합격했던 서류를 알맞게 수정한다고 하긴 했는데 아무래도 내가 놓친 부분이 좀 많았는지, 많이 부족하게 적었는지 작성했던 서류들이 모두 떨어진건 좀 씁쓸하긴 했다 😓
11월 말까지 났던 발표들.
작은 형수님 생일에 부모님과 둘째 형네와 함께 삼송역 근처 흑염소 집에서 식사를 했다.
항상 가던 원당 흑염소 집이랑은 뭔가 색다른 맛 이었는데, 맛이 없는건 아니었고 다른 느낌이라 맛있었다.
식사 후에 갑자기 작은 형수님이 노래방 이야기를 꺼내셔서, 가족끼리 거의 10년?만에 노래방을 간 듯 하다.
전골과 함께 먹었던 수육. 방문한 날 주방 이모께서 사정이 생겨 사장님 혼자서 주문을 준비해주셨다.
그리고 언제나 있는 음주사진. 그래도 오락실을 올해 초에 가던 것 만큼 자주 가질 않아서 그런가, 사진의 개수가 많이 줄어든 것 같긴 하다.
대전에 내려가는 같은 시기 대학원을 졸업한 동아리 후배 겸 친구(?)가 불러서 신도림에서 저녁 겸 술 한잔을 했다.
이 날 번화가 같은 곳을 다닐 때 보이던 생마차 라는 가게를 처음 들어가봤는데, 되게 일본에서 갔던 술집 느낌이 나서 신기했던 기억이 난다.
닭날개 튀김이 1개에 990원? 인가 하는데, 당연히 낱개로는 팔지 않고 10/20개 단위로 팔았던 것 같다.
해가 넘어가기 전 동아리 후배 두명과 함께 주안에서 만나서 오랫만에 재밌게 놀았다.
후배가 항상 이야기하는 돈까스 집이 있어서 가보려고 했는데, 하필 토요일이 휴무일이어서 가보지 못한게 좀 아쉬웠다. 다음에 가는 것으로 :)
아마 6월인가에 들어왔던 세금 환급액으로 여태 버티고 있었는데, 드디어 바닥이 보이기 시작.
결국 코로나 전에 친구와 한번 가봤던 고양 쿠팡으로 하루 단기 알바를 다녀왔다.
IB로 들어가서 물건을 채워넣고, 구내식당에서 밥을 먹고, 물건을 채워 넣고...
신기했던 점은 그 당시엔 셔틀버스가 없었던 것으로 기억하는데, 이번에 갔을 땐 출/퇴근 시 셔틀버스가 바로 집 근처에 세워주어서 출퇴근이 많이 편했던 기억이 난다.
크리스마스 전에 부모님이 집을 잠시 비우시는 것을 기회로, 동아리의 후배 두명을 불러서 음주 파티를 했다.
조금 남아있던 생명수도 맛 보여주고, 조금씩 남아있던 위스키들도 비우고...
함께 유튜브를 보면서 술을 먹고 수다떨다 보니 어느새 아침 6시였다 ㅋㅋㅋㅋㅋ
다음날(이브) 동아리 선배가 맥주 한잔 할 사람들은 만나서 놀다가 맥주한잔 하자고 하셨는데... 숙취가 너무 심해서 하루 종일 누워있었다 😥
이날 먹었던 술. 다시봐도 정말 많이 먹은 듯 하다.
취준 쪽으로도 한 차례 벽(?)/현실을 맛본 듯 했다.
이전에 넣었던 서류도 마저 떨어졌고, 뭐 그럼 그렇지 하는 기분이 들었다.
조금 충격이었던 점은 상시 채용으로 열려있던 넥슨이었다. 학부 졸업 당시엔 서류-과제 전부 통과했다가 인생 첫 면접이었던 지라 많이 긴장해서 떨어졌고, 작년 10~11월 즈음엔 서류는 통과하고 과제에서 너무 새로운(?) 모델을 사용했다가 떨어졌었다. 그래도 2차례 모두 서류는 되었었으니 한층 더 보완하고 다듬어서 뚫어야지 하며 주변 친구들의 피드백도 많이 받고 제출하고 당연히 서류는 통과 되겠지 했는데, 떨어졌다 😥 상시는 역시 상시구나... 라는 느낌이 많이 들었다.
해가 넘어가기 전 그래도 아직 열려있는 공고가 있어서 일단 작성해두긴 했는데, 1월 초에 나오는 결과가 어떨지 참 기대가 된다. 어짜피 될 거 같은 느낌은 들지 않지만... ㅋㅋㅋㅋㅋ
게임적으로는 뭔가 저점을 잘 끌어올린 듯 했다.
팝픈에서는 49 새로운 클리어가 도저히 늘어나질 않고, 남은 것들이 거의 짠게이지 악곡이라 그런지 클리어가 정말 힘들어 이전에 클리어 했던 노래들을 다시 플레이하면서 점수를 잘 높혔다. (https://youtu.be/pEAMEWCBwwI)