cyan- 23학번, 유일한 "ps러"(= 문제가풀릴때도파민을느끼며 ps하다가과제레이트를한적있고 코드포스티어정병이라는개념을이해함), 코포계정생성9개월차, 레이팅 1400, 2주 전 serin이못풀었던 딥2 D 컨스를 풀고서주변의 기대를 한몸에받고있던상태(왜그랬지풀지말걸), azber라는 분이랑 무슨 사이인지는잘모르겟는데 밤에 디코로 영통하면서 그림판으로 문제풀이를 듣는다는 소문이있음...
놀랍게도 백준이 없어지기 전에 팀연습도 했다. 2번의 팀연습을 한 결과로 알게 된 건 골드를 다 풀고 내가 플레하위를 진짜 운좋게풀면 초고점이고 대충 머릿속으로 골드2 이하를 다 푸는 걸 목표로하자고 생각햇따. 그러나 정말 무서운 사실을 깨달았는데 팀연습 때 대학 대회 특성상 자주 등장하는 실버~골드하위의 게임이론과 컨스를 그 누구도 좋아하지않아서 결국 전부 다 내가 짬처리를 했고 같이 고민하도록 시켜보기도 했는데 결국엔 내가 풀었다(아무래도 코테에 잘 나오진 않긴 하죠). 개인적으로 스스로의 직관력이 쓰레기라고 생각하는데 그래도 내가 세 명 중에는 코드포스A,B를 가장많이풀어봤을것같긴해서어쩔수없다.... 또한 앞서 말했듯 내가 팀 내의 유일한 ps 경력자이기 때문에 웬만해서는 내가 버스를 운전해야 할 그림이었기 때문에 부담이 더 컸던 것 같다.
그래서 전날밤에 호달달 떨면서 최악의 시나리오를 상상한다.
lyj가 구현에서 절어서 디버깅하느라 한참을 헤맴, 그래서 내가 디버깅 도와주느라 다른문제들을 보지 못함,cyan이 긴장해서 뇌가굳어서 풀어야하는 쉬운컨스에서 아무런관찰도하지못하고 멘탈이터짐, 3시간쯤 지나서 드디어 fixed가 문제를 풀었는데 키보드를 뺏어오지 못해서 브론즈 두개 실버 하나 골드 한개 정도 풀고 끝나는 시나리오
근데곧알아보겠지만 정말이게거의 그대로 일어났다....
대회 전
대회가 시작하기에 앞서 각자 팀노트를 뽑아오기로 했다. 포스캣 동방 프린터의 사용 방법을 아는 사람이 나밖에없어서 보내주시면 뽑아주겠다고 했는데, fixed가 보내준 파일을 열어봤더니 다크모드로 되어 있어서 악플을 좀 달았다. 심지어 파이썬이야
이건 좀 아니잖아
각자 뽑은 팀노트는 '팀'노트라고 불러도되는지에 대해 깊생을 하다 보니 과방 안마의자에서 잠들었다. 그래서 헐레벌떡 제일 늦게 왔더니 친절하게 컴퓨터 앞 자리를 비워주셨길래 강제로 스타트를 끊게 되었다. 할 게 없어서 한컴타자연습을 하면서 긴장을 풀었다.
그리고 팀노트를 합쳐보니 25장 제한인데 26장이길래 잠시 소동이 있었는데, fixed가 표지를 버리면서 해결했다.
대회 중
대회를 망한 후에 정병이 와서 슼보 사진을 찍어두지 않았다. 따라서 대략적인 타임라인이다.
그리고 오픈콘 전에 슼보가 노출되어버려서 순서를 섞었다는데 이 후기는 오픈콘 전에 쓰여졌기 때문에 본대회 기준 번호임. 내가 주석으로 오픈콘 기준 번호도 적어뒀으니 참고바람
~0:10 A AC (+2)
일단 무지성으로 종이 더미를 반으로 갈라서 왼쪽에 있는 lyj에게 앞쪽 절반, 오른쪽에 있는 fixed에게 뒤쪽 절반을 던져주고 나는 템플릿을 짰다. 그리고 포스캣 사람들로 구성된 2퍼플 1블루팀, 1블루 1민트팀이 1,2등으로 유력한 상태여서 그 팀 위주로 보면 될 것이라 생각했다
lyj가 A번이 쉬워보인다고 던져주길래 봤더니 브론즈였고, 내가 풀었는데 너무 긴장해서 등호를 빼먹어서 틀린다. 그래서 어 뭐지...? 하고 lyj를 불러서 도와달라 해서 고쳤는데 또 틀려서 2WA를 받는 사건이 일어나버린다. (사실 여기서부터 말아먹었음을 직감했다) 다행히 고쳐서 맞았다.
이후 cyan은 C, D, lyj는 B? C? E? (사실 모르겠음), fixed는 I를 본다. 근데 I (=오와 열)는 개레전드로 어려운 문제였기 때문에 아무런 성과도 이루어지지 않았고 B (=Classical postech problem, cpppp 나오는 거) 우리 기준 어려운문제였다...
일단 C (=POSTECH Programming Colosseum, 오픈콘 H) 트리라는 단어를 읽고 격자 bfs인가...? 하고 머리가 복잡해져서 그냥 넘겼던 것 같고 (좀만더자세히읽을걸) D (=부분수열의 개수, 오픈콘 J) 이상한 DP로 풀어보려다가 개삽질을 했다. 사실 일반적인 상태의 나
관찰 1. N범위를 보고 O(N log N)까지 돌 테니까 일단 정렬ㄱ?
관찰 2. 그러면 이웃한 것들의 관계를 어쩌고 저쩌고
정도의 사고는 가능했겠으나 이미 A를 두 번 틀리고 정신이 나가버렸다. so very classic Div2 B material로 느껴지는데 이걸 왜 못했지... 1시간이 지나도록 아무 성과도 없는데 막 3솔한 팀들도 많아서 정병이 왔다.
~1:00 J AC (=포스텍 도수체조, 오픈콘 K)
그러다가 J가 풀리고 있는 것 같은데 너무 고능해서 잘 모르겠다고 (보통 팀원들이 컨스 나한테 던질 때 쓰는 템플런임) fixed가 던져줬고 예제에 있는
1 5
5 7
을 보고 역시 kwoncycle 선배님 이라고 생각했다.
보자마자 3분만에 J가 90% 정도 풀리는 관찰을 찾았다고 주장하며 캬역시나야 하면서 짜기 전에 팀원들에게 검증을 받으려고 했는데 설명하다 보니 내가 틀렸다는 걸 깨닫고 머쓱해졌다. 그래서 15?분 정도 더 관찰한 결과
관찰 1. 도미노를 만들 수만 있으면 무한복사가능
관찰 2. 대각선은 서로에게 닿을수없어...
생각 2-1. 이걸 어떻게 수식화하지? 택시거리가 홀수이면 닿을수있다
생각 2-2. 그러면 인덱스의 차이로 아하
로 풀이가 나왔고 구현도 내가 구체화했다. fixed랑 같이 화면을 보며 불안형 여친마냥 나 맞지? 이거 맞겠지?하며 검증하면서 짜서 AC.
~2:00
lyj가 옆에서 C(=오픈콘 H)는 달팽이 배열을 짜면 된다고 외쳤는데, 항상 2개인 경우만 되는지 확실하지 않아서 증명해야 할 것 같다면서 구현을 시작하지 못한 상태였다. 그러다가 일단 해 보겠다고 해서 키보드를 줘서 짜기 시작했는데 45분 넘게 못 짜고 헤맸다. 원래 멘탈이 터진 cyan에게 괜찮다는 말을 해주던 lyj가 멘탈이 완전히 터져버렸다. 그래서 내가 역으로 괜찮으니까 제발 멘탈 잡아달라고 하는 사태가 일어나버림
뭔가 이대로 뒀다가는 망할 것 같았고 C는 거의 모든 팀이 풀었기 때문에 내가 나서서 책임져야겠다고 생각했다. 나는 구현을 정말 정말 못하지만 그래도 고등학교 내신에서 달팽이 배열을 짜 본 적은 있었고, 다른 팀들도 다 풀었는데 내가 못할 게 뭐야? 라는 근거 없는 믿음이 있었다. 하지만 단순한 달팽이가 아니었고 따져야 할 조건들이 너무 많았다. 처음부터 lyj의 설명을 듣고 난 상태였어서 다른 모양을 떠올릴 생각조차 못했고 여기서 C를 다시 읽어보려는 시도라도 했더라면 좋았을 텐데 못해서 너무 아쉽다. 사실 모두가 풀었다는 말은 더 쉬운 풀이가 있다는 뜻이었을 거라는 오만한 생각이라도 했을걸....
대충 이 상황과 병렬적으로 fixed는 E(=포닉스의 학점은?, 오픈콘 D) 가 쉬운 것 같다면서 (실제로 거의 모든 팀이 풀었었음) 그냥 비교하면 되는 거 아님? 이라고 했는데 예제2가 왜 도는지 모르겠다고 나를 불렀다. 근데 일단 뭐가 수식이 너무 많고 지문도 너무 길고 비직관적이고 정말 읽기 싫게 생긴 문제였다. 예제를 읽고 설명시켜주는 데에는 성공했으나 뭘 하라는 건지 모르겠어서 멍을 좀 때렸다.
결국 E는 버리고, 내가 쉬지 않고 반례를 던지고 디버깅을 도와줘서 문제의 조건에 맞는 달팽이를 제대로 출력하는 데에 성공했다. 그런데도 WA를 받았다. 가능한 모든 상황에 대한 예제가 다 돌았고 이유를 전혀 알 수 없었다. lyj는 '답이 2개가 아닐 수 있나?'에 다시 빠져버렸고, fixed도 망했어요... 하면서 간헐적으로 멍을 때리기 시작했다.
그래도 lyj와 fixed가 힘을 합쳐서 E를 맞게 된다. 나는 지금도 문제 상황을 모르겠다.for, if, 부등호만으로 해결할 수 있는 브론즈라는데아직도 문제를 이해하지 못했다.진짜 저능해졌나??
이 쯤에서 내가 인터랙티브였던 H(=Classical Interactive Problem, 오픈콘 C)도 풀어보려고 시도했다. 사실 살면서 인터랙티브를 한 번도 풀어본 적은 없었지만, 예비소집 때 인터랙티브를 푸는 방법을 배웠고, (그냥 endl만 붙여주면 된다고 했다) 많이 어려워보이지 않아서 해 보려고 했다.
생각 1. 쿼리가 최대 2N개니까, N+N으로 구성해보자. 일단 차이 N개를 구하고...?
생각 2. 3개씩 해볼까? 그러면 대소관계를 알 수 있네?
근데 너무 저능했던 나머지 이것만으로 확정이 된다는 생각을 못 해서 빙글빙글 돌기만 했다.
~3:00 C AC (=오픈콘 H)
이제 뭘 풀지...? 하고 돔저지에 다시 들어가봤다. 근데 클릭을 잘못해서 C의 WA 결과를 눌렀는데 warning:무슨뭐가 초기화가 안됐을수있음 이라고 떴다. 알고 보니 로컬에서 돌릴 때는 어떤 변수가 쓰레기값으로 초기화가 되었는데 채점기에서는 0으로 초기화가 돼서 틀린 거였다. 그래서 C에서 AC를 받았다.
30분 남은 시점에서 F (=학교를 쌓아올리는 포닉스, 오픈콘 E)를 봤는데 대충 아주 간단한 투포인터 문제로 보였다. 그 전에 그 문제 자체를 왜 못 봤는지 모르겠다. 다른 사람이 들고 있던 건가....? 이미 충분히 많은 팀들이 풀었는데, 사실 나도 멘탈이 나가서 슼보를 보고 남들이 푼 걸 풀어야겠다는 생각 자체도 못했던 것 같다. 내가 키보드를 잡은 상태에서 다 같이 F를 보기로 했는데, fixed가 멘탈이 나가 있을 동안 lyj랑 내가 병렬적으로 짰던 것 같은데 결국 둘 다 풀이를 못 냈다. 근데 사실 나도 건물 높이 1을 쌓는 것을 width번의 작업을 해야하는 걸로 생각하고 막 누적합을 짜네 마네 하고 있었는데 그 누구도 틀렸다고 지적을 하지 못한 것을 보면 다 같이 멘탈이 나갔던 것 같다. 내가 5분 정도를 남기고 구현 방향이 잘못되었음을 깨닫고 나중에 풀었던 정해에 가까운 풀이 방향을 떠올렸는데 (포인터 방향을 거꾸로 해서 바이토닉하게 만드는 것) 시간 내에 구현을 구체화할 수 없을 것 같아 결국 키보드를 내려놓았다.
결과
특별상을 받아서 총 30만원을 받는 데 성공했다! 시작 전에 긴장 풀기용 농담으로 X염색체의 수가 가장 많은 팀으로 기준을 정하면 상을 받을 수 있다고 주장했었는데 이건 아니었고 개삽질한 후에 AC를 받았던 C가 특정 제출 시간에 가장 근접했다고 한다.
순위 자체는 12/14등을 했다. (12/13등이었나?) 당연히 3등상은 받을 거라고 많은 사람들이 말했어서 이 정도의 성적을 받을 줄은 몰랐고 솔직히 너무 부끄러워서 숨고 싶었다. 그래서 특별상을 받았을 때도 사실 그냥 막 포닉스 굿즈. 시프트업 굿즈. 이런 거 줄 줄 알고 정말 앞에 나가고 싶지 않았는데 옆에서 누군가가 상금 인당 10만원이라고 알려줘서 개놀라고 '혹시 수상소감도 말해야 하나요?'라고 말했음 어이엑스
대회 이후
대회가 끝나자마자 티스토리에 올릴 후기의 일부를 작성했다. 아래는 그 내용의 일부이다.
사실 내가 읽은 모든 문제들은 실제로 내가 풀이에 아주 근접했고 충분히 풀었어야 하는 난이도였는데 하나도 못 풀었다는 생각에 많은 자괴감이 들었다.
일단 대충 은퇴선언을 박고 양꼬치를 먹으러 갔다. 1,2등 팀이 우리 테이블에 앉았는데, B번이 어쩌네 L번이 정해가 뭐냬 하는 동안 나는 멍 때리면서 소주를 들이켰고 주량에 근접한 양을 마셨다. 이 때 후배들에게 빙홍차 소주 칵테일을 말아주고 '살면서 자기가 먹어본 소주 화합물 중 가장 소주 맛이 안 난다' 라는 극찬을 들었음 칵테일 동아리 3년 경력이 괜히 있는 게 아님
그러고 나서는 잔뜩 취해서 그 날 저녁에 있던 Div.2 코드포스를 친다는 후배들한테 나도 할까 같은 헛소리를 조금 했는데 내가 맛이 좀 많이 가 보였는지 괜히 레이팅 떨구지 말고 들어가시라는 소리를 들었다... (꼬장 부리는 선배라 미안해)
근데 방에 가기 싫어서 과방ㄱㄱ을했다 돔저지가 아직 닫혀 있지 않은 상태였기 때문에 제출이 가능했다. 일단 구현하다 말았던 F번(학교를 쌓아올리는 포닉스)을 짰다. 그 다음은 D번(부분수열의 개수)을 들여다봤는데, 슬슬 술이 깨면서 숙취에 대가리가 깨질 것 같았는데 난 이것도 못 할만큼 멍청하지 않아! 하고 눈이 감길 때마다 스스로의 대가리를 풀스윙으로 때렸고 과방에 모르는 사람도 있었는데 진짜 미친줄알았을거다 20분도 지나지 않아 정렬한 후 upper_bound로 2배 이하인 첫 번째 지점을 찾는 아이디어를 떠올려서 구현까지 마쳤다. 다음은 인터랙티브 H번도 봤는데, 이것도 차분하게 생각해보면 대소관계가 결정되면 수열이 결정된다는 걸 알 수 있었다. 사실 그 때 a, n+1-a가 둘 다 된다는 조건을 읽지 않았거나 이걸 어떻게 쓸지 몰랐던 것 같다.
이 때 정말 여러 가지 감정이 교차했는데, 아니 걍 만취에 가까운 상태에서 사실상 D, F, H를 혼자서 다 푼 건데 불과 몇 시간 전이었던 대회 때 맨정신으로 이걸 떠올리지 못했다는 게 어이가 없었고 동시에 '나는 대회를 망친 것 뿐이지 ps 자체를 개못하는 건 아니구나' 라는 안도감도 들었음 아직 접을 때는 아니구나... 그래 아무리 민트를 뽀록으로 찍었대도 이 정도는 아니야
여튼 그래서 접겠다는 주장을 철회하고 달콤한 잠에 들었다💤
팀원 회식
처음으로 돌아가 보자. PPC를 나간 것은 동아리 부원들과의 친목 목적이 가장 컸고 (여담이지만 PPC 작년 2등, 올해 1등을 한 chansolpark7이 팀이 없다고 같이 나가자는 제안을 받았었는데 이미 팀이 있어서 거절했다.) 실제로도 내가 친해지고 싶던 사람들과 편해진 것 같아서 기분이 좋았다. 그래서 특별상도 받았겠다 마지막으로 회식을 했다!
웰노운 연어 맛집
lyj: 백준 부활하면 연락ㄱㄱ 코테 준비할 때 필요함
fixed: 역시 ps는 재미없다. 가서 선배님들 문제 푸는 거 구경하는 게 멋있었다.
또 나만 ps에 진심이었지
하지만 재밌었죠 <3
마무리
역시 온사이트 대회는 정말 재밌는 것 같다. 코드포스라는 티어가 걸려 있고 시간 내에 문제를 풀어야 하는 환경을 경험해봤기 때문에, 대회도 마찬가지일 것이라고 생각했는데 팀 대회라 그런지, 제일 큰 대회여서 그런지, 부담감이 차원이 다르다...
마지막으로 우리 팀원 미소녀(fixed)와 운영진 미소녀(kyungdain)의 사진을 첨부하며 마무리한다. 이번에 정말 백준 섭종도 그렇고 여러모로 운영진 입장에서 진행하기 어려웠을텐데 대회가 성공적으로 끝나서 다행인 것 같고 정말 수고 많았다고 해 주고 싶다.
잠이 안 오는 새벽 세 시 반이다. 정수론 과제 소문제 하나에 1시간 반을 매달렸지만 결국 반쯤 이해한 채로 gpt의 풀이를 베껴서 내고, 인간관계에 대한 깊생도 들고, 해석학 시험은 얼마 남지 않았고, 최근 교내 ps대회도 말아먹고서 생각이 많아진 그런 날이라고 할까.
침대에서 뒹굴거리며 핸드폰을 들여다봤고, 2020년부터 사용했던 내 트위터 계정을 구경하며 나름의 추억팔이를 하다 '학교에서 수행평가로 나왔던 확통 문제인데 어떻게 푸는지 잘 모르겠다'는 내 질문글을 보게 되었다. 필자는 인과영 확통 내신에서 B-(석차로 따지면 9등급제 기준 8등급 정도)를 받았다. 중학교 2학년 때 잠시 중등 KMO를 준비할 때도 조합론 문제는 단 한 개도 맞추지 못했다. 조합론은 근사(not approximation yes cool)하고 재밌지만 묘하게 재능을 타며, 아쉽게도 나에게는 그 재능이 없다.
당시 설컴을 다니고 경곽 같은 데 다니던 멋진 트친 분들이 답장을 해 주신 걸 구경하는데 꽤나 흥미로웠다. 근데 읽다가 되게 많이 나오는 DP 문제 아님? 이라는 생각이 들었다. 그래서 풀어봤는데 이게 맞네?
일단 문제가 대칭적이기 때문에 첫 번째를 1로 고정하고 나중에 4를 곱할 것이다. i번째에 고른 수가 처음에 고정한 수와..
A[i]: 같은 경우 (ex. 1)
B[i]: 반대인 경우 (ex. -1)
C[i]: 상관없는 경우 (ex. i, -i)
그러면 점화식을 세울 수 있는데
A[i+1] = A[i]+C[i]
B[i+1] = B[i]+C[i]
C[i+1] = 2*A[i]+2*B[i]+C[i]
초깃값은 A[0]=1, B[0]=C[0]=0이다.
마지막도 반대이면 안 된다. 따라서 4*(A[n-1]+C[n-1])이 답이 된다.
여기에 식을 구하고 싶다면 A[i]+B[i]+C[i]=3^i (임의의 순간에 길이 i+1의 sequence를 만드는 방법은 직전 꺼를 제외한 나머지 3개이고 시작은 3^0=1이다)을 이용해서 식정리를 해주면 된다. 점화식을 풀면 길이 n에 대해 3^n+(-1)^n+2 라는 closed form을 얻을 수 있다. 악필이라 ㅈㅅ
고등학생 때의 내가 못 풀어서 결국 풀이를 외워버렸던 확통 문제를, 대학생이 되어 알고리즘 문제풀이를 시작하고 DP 문제로 바꿔서 풀어냈다는 점에서 스스로의 발전이 뿌듯하게 느껴졌다. ps가 절대적인 발상력을 높여주는 건 아니지만 가능한 발상의 폭을 넓혀주긴 하는 것 같다. 대학교 와서 뭘 했지? 라는 생각을 아주 많이 했지만, 사실은 나도 모르게 조금씩 성장했을지도 모른다. (ㅈㅅ 제가 씹덕이라 성장, 결실 같은 키워드에 환장함)
+ LLM에게 풀이의 유효성을 검증받았더니 4-state graph에서 길이 n의 walk를 계산하는 문제로 환원할 수 있다고 해서 신기했다. 뭔진 잘 모르겠지만 만약 알고리즘에 대한 내 관심이 계속된다면 그래프의 성질도 공부해보면 재밌을 것 같다. (뭐든 쉽게 질리는 성격 탓에 그럴진 모르겠긴 함)
+ 백준에 동일한 문제가 있을까? 하고 검색해보려고 했는데 아 백준이 없어졌구나... 라는 걸 깨달았다.주변인들에게 물어봤더니 실버~골드하위 DP이고 식까지 구하는 건 골드 상위~플레 하위라는데 그럼 난 실버랑 골드를 풀고 신나서 블로그 글을 쓴 거구나(!) 좀 부끄러워졌을지도?
오늘은 제 인생 첫 PS 대회입니다. 기대 반 걱정 반 (기대 10% 걱정 90%)로 포항행 기차에 몸을 싣었습니다.
이하 잡설이 많습니다. 또한 POSTECH 참가자들끼리 뒷풀이를 진행하며 음주 상태로 작성되고 있으며 퇴고를 거치지 않았기 때문에 문장 coherence가 부족할 수 있는데 틀린 내용이 있으면 알려주세요.
시작도 전부터 2가지 정도의 억까를 겪어버립니다!
1. 서울-포항 기차 없음 이슈로 8시 기차를 타야 해서 7시 반에 일어났습니다. 보통 3시 정도에 규칙적으로 자는 사람으로서, 7시 반 기상으로 인해 다소 피폐해져서 커피 두 잔과 박카스 한 잔을 도핑합니다.
2. 충전기를 본가에 놓고 왔습니다. UDPC를 치다 말고 컴퓨터가 꺼지면 어떡하지 같은 근들갑에 잠시 정신이 사로잡혔지만 에이 설마 3시간은 버티겠지 같은 생각으로 마음을 가다잡았습니다.
이건Div3코포다이건Div3코포다이건Div3코포다이건Div3코포다이건Div3코포다괜찮다긴장하지말자제발긴장ㄴㄴ 문제를 풀어 봅시다.
A (대회 장소 정하기)
(0:45 AC)
UDPC는 난이도순이라는 말을 분명하게 들었습니다. 나름 0917ba가 홍보할 때도 뉴비 친화적인 대회라는 이야기를 했었습니다. 그러니 아마 A번은 대충 브론즈 사칙연산이겠죠? 모든 재앙의 시작
음 뭔가 생각보단 케이스가 많을 지도 모르겠습니다. 대충 선을 일직선으로 편 후에 구간에 따라 나누면 될 것 같고, 위치에 따라 나눠서 하면 될 듯 합니다. 그러니까 min(거리, N-거리) 이런 쌍을 잔뜩 만들어서 케이스워크를 하면 될 것 같습니다. 문제는 그것을 어떻게 하느냐입니다. 이것은 능지를 요구하는 정신 나간 케웍 문제이며 저는 능지가 없습니다. 막 모든 케이스를 다 나눠서 하면 될 것 같은데 실수하면 끝장 아님? 내가 첫 ps 대회에서 0솔을 하면 어떡하지? 여기서부터 멘탈이 갈리기 시작합니다.
그나마 다행히도 멘탈이 덜 갈리게 된 점은, 15분이 경과했는데 아직까지 A를 푼 사람이 한 명(인가 두 명인가)밖에 없었습니다. 이 문제는 아무래도 A번 난이도가 아닌 것 같습니다. B로 넘어가 봅시다.
B (포닉스와 달구 2)
(+1 1:25 AC)
그래서 B를 읽기 시작합니다. 흐으음... B도 무슨 게임이론 construction처럼 생긴 문제입니다. 진짜 죽일까(not 출제진 yes 문제)? 그래도 A 같은 케이스워크를 하다가 이미 예제가 안 나오는 상태였던지라 이거라도 풀어야겠다는 생각이 들었습니다. 이걸 못 풀면 진짜 0솔을 할지도 모릅니다.
직관적으로 0...0 과 같이 대칭인 0의 쌍이 있으면 선공이 비대칭을 만들 수 있고, 1...0과 같이 비대칭이고 한 쪽을 채울 수 있는 게 있다면 이건 후공 입장에서 언젠가 해결해야 하는 숙제입니다. 그러나 원래 일퀘라는 건 하루에 하나만 할 수 있는 것이기에, 후공도 마찬가지로 이 숙제를 하나만 할 수 있습니다.
따라서 선공이 1...0 쌍을 항상 2개 이상으로 유지하면 무조건 이기..나? 이렇게 생각하고 코드를 짜봤는데 엥? 예제가 안 돌아요. 당연히 이렇게 간단할 리가 없습니다. 대충 이런 관찰을 몇 개 더 하면 풀리겠죠?
대칭이라는 건 홀짝성에 따라 가운데가 있을 수 있습니다. 가운데가 아직 채워져 있지 않은 경우는 뭔가 여분의 수?가 하나 생깁니다.
선공이 여기에다가 두면, 아무 일도 일어나지 않지만 후공이 복구할 0...0 쌍을 안 만드는? 효과가 있습니다. 근데 후공의 턴이 오지 않으면 승리한다고 합니다. 그 말은 1...0 쌍 <2 인 경우를 분리해서 생각해줘야한다는 건데, 1 이하인 경우 무조건 후공이 이긴다고 대충 생각하고 제출을 눌렀는데 WA.
안 되곘습니다. 저는 이런 고능한 게임이론 문제를 풀기에는 너무 저능합니다.
차라리 A는 가능한 모든 경우를 해 보면 될 것도 같으니 A로 다시 돌아가 봅시다. {시계, 반시계}, {ans, N-ans}를 나눠서 모든 경우의 min()해버리면 되지 않을까 같은 생각으로 케웍을 했고 다행히 예제가 돌아서(?) AC!
(주석: A번의 경우 나중에 뒷풀이에서 Equinox 군의 이야기를 들어보니까, n<=100이기 때문에 그냥 스트레스를 짜도 O(n^4)인데 간당간당하게 1초 안에 돈다고 합니다. 역시 ps 짬바가 있으면 이러한 발상도 가능한 듯)
휴! 이젠 B를 좀 풀어야겠습니다.
하루에 숙제를 두 개 이상 못한다는 이론을 일단 붙잡아보기로 했습니다. 이걸 좀 더 세분화하면 되지 않을까요?
근데 사실 두 개 이상이 아니라 세 개 이상일지도 모릅니다. 왜냐하면 [가운데를 채우기]라는 스트릭프리즈가 있기 때문이죠.
1...0 쌍이 3개 이상이면 절대 안 됩니다.
1...0 쌍이 없는 경우는 0...0 쌍이 있고 가운데가 비어 있으면 됩니다.
1...0 쌍이 한 개인 경우, 0...0 쌍이 없으면 후공 차례가 안 와서 선공이 이기게 됩니다. 0...0 쌍이 있으면 후공이 채우면 됩니다.
1..0 쌍이 두 개인 경우, 0...0 쌍이 없으면 후공이 대칭으로 항상 만들 수 있습니다. 대신 가운데가 비어 있으면 가운데를 채워서 턴을 벌 수가 있으므로 이 경우는 제외합니다.
직접 예제를 몇 개 만들어봤을 때 다 되는 것 같길래 믿음의 도약으로 제출을 눌렀고 AC를 받았습니다. 이 케이스워크를 하기에 너무 저능해서 좀 오래 걸렸습니다.
어케했지? 케웍이나 construction은 풀 때마다 내가 빼먹은 끔찍한 케이스가 있지 않을까 같은 불안감을 떨칠 수가 없는 것 같는데 이거 어떻게 해결하나요
C (U-DeeP-See)
(1:35 AC)
제 닉네임이 드디어 슼보에 보이기 시작합니다. 이 때부터 좆됐다는 생각이 좀 사라지면서 긴장이 풀리기 시작합니다.
C번도 construction입니다. 저는 ad-hoc, construction, greedy에 약하고 DP, graph에 강한데 (물론 골드 중하위의 문제 따위를 풀며 제가 이딴 소리를 하는 것도 좀 웃기긴 합니다.) 아직까지는 제가 못하는 것만 한가득입니다.
(주석: PS를 처음 시작한(?) 시점에서 반 년 정도 지났는데, 골드 이하의 문제를 보면 대략적인 태그가 예측 가능합니다. 고슈분들의 블로그를 보면 '이건 애드혹이니까 qwer에게 던지고 이건 자료구조 비빔밥 같으니까 asdf에게 던졌습니다' 같은 말들이 있어서 저도 태그를 안 상태에서 팀원에게 문제를 던지고 싶다는 생각을 자주 하는데, 문제는 저에겐 던질 팀원이 없으며 제가 풀 능력도 없으니 큰 의미는 없습니다.)
2d grid sum (있는 말인가?)는 근본적으로 convolution 이라는 생각이 들었습니다. 참고로 이 토픽은 2023 UDPC 셋을 돌 때도 나온 적 있는 토픽인데, 당시 ijkxyz의 늪에 빠져서 구현에 실패하고 나중에 업솔빙을 했었습니다.
다행히 UDPC는 인터넷 검색이 가능합니다! 바로 convolution 공식이라고 구글에 검색했는데 합성곱 컨볼루션이 먼저 뜹니다. 아니 CNN 컨볼루션 말하는 건데; 그리고 Google AI Search가 그 밑에 뜹니다. 이러다가는 생성형 AI 사용으로 실격을 당해버리면 슬플 것 같습니다.
아니 생각해보니까output kernel 공식은 이게 뭐 검색할 정도의 것도 아닌데 왜 검색했지?
빠르게 창을 닫았습니다.
홀수일 때는
-1
-1 +4 -1
-1
이런 격자를 만들면 되겠지? 그렇다면 짝수일 때는 일반화가 가능할까요?
아니 근데 그냥 각 n_output마다
-1 -1 -1 -1
1 1 1 1
이런 걸 하면 합은 보존되는 것 아님? 제출을 하고 빠르게 AC를 받습니다. 뭐지 나 construction 잘하나?
D (카드 섞기 게임)
(+1 1:59 AC)
a[i]를 s[a[i]]로 매핑하는 operation을 k번 했을 때 사전순으로 최소가 되는 문자열을 짜면 됩니다. 아니 난이도 순이라며 이게 A보다 쉬운 것 같은 건 기분 탓임? 제가 수학을 잘 못해서 이게 수학적으로 용어가 맞는진 모르겠는데, f^m(x)=x가 되는 주기 m이 있을 거고 k%m을 해서 원래 문자열의 합성을 O(n^2) 안에 구할 수 있습니다. a[i]를 s[a[i]]로 매핑하는 형태의 문제는 코드포스 같은 곳에서 많이 봤기에 거의 보자마자 짤 수 있었습니다.
그러나 예제조차 돌지 않는 맞왜틀의 저주에 빠져버리고 맙니다. 하아.... 아무리 봐도 제 코드에 틀린 게 없습니다.
혹시 s[a[i]]를 ans[a[i]]로 반대로 매핑했나?? 혹시 array 초기화를 잘못했나?하고 모든 프로토콜을 돌려봅니다. 이게 뭐 풀이가 복잡한 문제도 아니고 틀릴 리가 없습니다. 2차 멘탈 붕괴가 옵니다.
이 시점에서 스코어보드가 프리즈 됩니다. 이 시점에선 다행히 6등이었는데 10등까지 수상이니까 수상권 밖으로 밀려날 것 같다는 걱정이 들었습니다.
다음으로 풀 문제를 찾아야 합니다. E (풍선줄 자르기), G (송신탑 설치) 정도가 후보입니다. G번은 예전에 코포에서 봤던 문제인 것 같습니다. 그래서 규정에 따르면 이미 짠 코드를 사용할 수 있다고 했기 때문에 코포 제출 내역을 다 뒤져봤는데 그 문제가 보이지 않습니다. 뭐지 꿈에서 풀었나?
아니 다시 생각해보니까 D에서 처음에 매핑을 시작하기 전에 시작 string을 정렬을 했어야 되는데 안 했네요. D를 못 풀었다는 생각에 너무 답답했었는데 이걸 찾아냈다는 생각에 막 손이 떨렸고 수정 제출을 눌렀는데 너무 흥분한 나머지
1. E에다가 제출을 (두 번이나) 누르고,
2. 이걸 깨닫고 다시 D에다가 제출을 했는데 cout<<ans를 쓰지 않고 제출을 하는
블런더를 저질러서 E와 D에 WA를 하나씩 쌓는 레전드 사건이 일어납니다... 진짜 얼탱
E (풍선줄 자르기)
(+1 2:57 AC)
이제 시간이 약 20분 정도 남았습니다.
E를 자세히 읽어보니 그냥 흔한 경로 찾기 문제입니다. 남겨놓은 선의 길이의 최댓값을 구하는 문제로 생각하고 MST 문제라고 착각을 해 버리고 맙니다.... 그래서 일단 다급하게 제 union find 템플릿을 찾습니다. UDPC의 경우 어떤 팀노트도 사용 가능하고, 인터넷 검색도 자유롭지만, 코드를 복사를 하면 안 된다는 규정이 존재합니다. 그래서 이 순간부터 저는 인간 복사기가 되어 제 5nn타의 초스피드 영타로 템플릿을 치기 시작합니다.
근데 다시 읽어보니까 풍선의 높이들의 합의 최소를 구하는 거잖아? 아니 시1ㅂ 다익스트라잖아 ㅋㅋㅋ 다익스트라 템플릿을 찾습니다. 이 시점에서 시간이 10분 정도 남았습니다. 이제부턴 내가 과연 10분 안에 다익스트라 템플릿을 짤 수 있을까?에 대한 문제입니다. 정말 다행히도 딱히 변형할 게 없어서 그냥 그대로 쳤고
버저비터로 AC를 받을 수 있었습니다! 캬
손이 덜덜 떨리고 토할 것 같아서 끝나자마자 화장실로 달려갔는데 이렇게 손 떨리는 경험은 대학교 입시 이후로 처음이었습니다.
카페인 때문이었을까요 대회 때문이었을까요? 몰?루
결과: 5솔로 6등(4등상)을 받았습니다!
여담으로 슼보 깔 때 진행자가3x3x173은 얼마일까요? 계산을 해 보겠습니다 하고 계산기를 냅다 켜더니 1557입니다. 이래서 좀 웃겼음
후기
팀노트도 되고 인터넷 검색도 되기 때문에 사실상 코드포스 Div.3를 치는 기분이었는데 실전 대회라는 압박감이 엄청납니다. 사실 저는 평소에 B 같은 복잡한 게임이론 construction을 짧은 시간 안에 깔끔하게 잘 떠올리지 못하는데, 이걸 해냈다는 게 역대급 고점을 띄워서 기분이 좋습니다.
그리고 뭔가 난이도 순이라고 해도 내가 느끼는 난이도와 진짜 난이도는 다를 수도 있겠다고 생각했습니다. 저에게는 A가 너무 어려웠어서 이 때 멘탈이 나갈 뻔했는데, N 범위 보고 브루트포스 같이 말렸을 때 뚫을 만한 좋은 방법들도 생각해두면 좋을 듯?
rs1 에 있는 address에 있는 값을 rd register destination에 저장한다는 점에선 일반적 read랑 똑같음
다만 lr.w 는 “나 여기다가 곧 write할래”라고 예약을 해놓는 것.
🔹 sc.w rd, rs2, (rs1) — Store-Conditional
addressrs1 에다가 rs2 에 있는 값을 저장한다.
lr.w 이후로 다른 store 요청이 없었으면 성공 → rd = 0을 입력
이외의 모든 경우 rd ≠ 0
Coherence vs consistency
Coherence: local ordering— 하나의 memory location에 read/write할 때.
Consistency: global ordering— 서로 다른 Memory location의 연산 순서가 Memory model을 consistent하게 따른다.
Sequential consistency
예를 들어 이건 실행 순서에 따라 (0,0)이 될수도, (0,1)이 될수도, (1,0)이 될수도 있다.
SC의 정의
프로세서들이 memory operation들을 global memory에 요청해서 처리할 때, 처리 순서는 랜덤할 수 있으나 program order는 유지되어야 한다. 이렇게 모든 연산을 하나의 global ordering으로 나열할 수 있으면 그 시스템은 Sequential Consistency를 따른다고 한다.
SC의 조건
어떤 thread 안에 있는 메모리 연산은 항상 순서대로 실행해야 함
서로 다른 프로세서에 있는 메모리 연산은 임의로 섞일 수 있다. (interleaved arbitrarily)
각 실행에 대해서 모든 thread는 global ordering, 즉 같은 순서로 메모리를 연산을 인식해야 한다.
프로그램에서 어떤 address에 있는 data와 instruction을 사용했으면 조만간 비슷한 곳에서 사용할 가능성이 높다
Temporal Locality: 최근에 reference했으면 또 사용할 가능성이높다. (ex. loop)
Spatial Locality: 근처 주소에 있는 것을 또 reference할 가능성이 높다
Cost amortization
DRAM에 한 번 access해서 데이터를 가져온 다음에 cache에 저장을 하면 된다.
One-time-overhead = DRAM access 시간
Per-unit-cost = cache access 시간
Cache
Access time이 상대적으로 짧은 컴퓨터 메모리로, 최근에 사용했거나 자주 사용한 instruction/data 등을 저장해놓는 것이다.
Memory Hierarchy
자동으로 관리할 수도 있고, 수동으로 관리할 수도 있다. GPU, special-purpose processor 등에서는 수동으로 관리하기도 하지만 너무 번거로워서 잘 하지 않음.
Register file → L1 cache → L2 cache → L3 cache → Main memory(DRAM) → Hard disk/SSD
Register spilling: Register file은 CPU 안에 있어서 매우 빠르다. 만약 필요한 variable이 register보다 많은 경우, 컴파일러가 일부분을 L1 cache로 “spill”한다. 이는 하드웨어가 아닌 compiled code에 의해서 관리된다.
Automatic cache management: L1, L2, L3 cache 사이에 움직이는 것은 하드웨어가 처리한다.
Automatic demand paging: 만약 프로그램이 DRAM에 없는 메모리를 access하려고 할 경우 paging이 일어난다. OS가 자동으로 필요한 page를 memory로 로딩한다.
Memory Hierarchy with a 5-stage pipeline
L1 cache에 대해서는 I-cache랑 D-cache를 분리
L2, L3, DRAM에 대해서는 unified 형태
cf. Cache Hierachy: CPU에 가까울수록 high (ex. L1 cache)
SRAM/DRAM:
SRAM (static random access memory)- 접근 속도가
Hierarchical Performance Analysis
각 memory hierarchy level에서의 access time이 t_i이라고 하자.
Ti=hi⋅ti+mi⋅(ti+Ti+1)
hi: hit rate, 이 때 access time: ti
mi: miss rate, 이 때 access time: ti+Ti+1
Cache as Request Filter
BR: Bandwidth requested
BP: Bandwidth provided
각각의 cache level을 일종의 필터처럼 생각할 수가 있다.
Hierarchy Design Considerations
DRAM (Dynamic RAM)
used for large memory
optimized for Capacity/$
SRAM (Static RAM)
Static RAM, cache나 register file에 사용된다.
Capacity, latency, Capacity/$ 등 다양한 compromise를 고려
Working set: 어떤 process에서 현재 사용하고 있는 데이터
Cache design basics
Cache interface
Basic operation: cache는 어떻게 작동하는가?
Address가 주어지면, cache에서 그 address를 찾는다
Hit → data를 리턴한다
Miss → Cache에 저장할 새로운 주소를 정한다
Update cache
Basic Cache Parameters
M-bit address: Address space의 전체 크기 (ex. 4GB address space라면 32-bit)
C (capacity): cache의 총 데이터 사이즈 (ex. 16KB = 2^16 bits)
G = 2g (granularity): 한 번에 몇 byte를 가져올 것인가? (4 bytes per word)
Direct-mapped cache
Tag bank: 주소 중에서 tag 비트를 저장한다.
Data bank: 실제 데이터를 저장하는 역할 (각 줄당 G bytes)
Valid bit: cache에 있는 데이터가 초기화된 상태인지 확인
m-bit의 주소를 다음과 같이 나눈다.
idx: 특정 cache block 선택
tag: cache hit인지 아닌지 확인
bo (block offset): 만약 hit이라면, 해당 block 안에서 가져올 byte를 선택
g: 요청한 byte 수 (ex. lw: 4byte)
Cache hit logic
Address에서 index를 확인해서 맞는 line을 찾는다.
address에 있는 tag와 tag bank에 있는 tag를 비교한다.
valid bit를 확인한다 (dirty, 즉 데이터가 초기화되지 않은 상태이면 안 되기 때문에)
→ 이 모든 게 다 맞으면 cache hit이다!
용어 정리를 하고 가자
page: virtual memory system에 있는, 고정된 사이즈의 memory block.
cache block/line: CPU가 cache하는 데이터의 단위.
set: 같은 index를 가지는 cache block/line들의 모임.
Block size & Tag storage overhead
Block size가 작을 경우 tag space를 쓸데없이 낭비하게 된다.
Block size를 늘리면 어떻게 될까?
아래 그림의 cache 에서, 1 row = 1 cache block = 1 cache line
가로 길이가 block size, 세로 길이가 block의 개수라고 생각하면 된다.
오른쪽과 같이 block size를 늘리게 되면 한 번에 가져오는 data가 더 많아진다고 생각하면 됨.
따라서 tag가 전체 cache에서 차지하는 공간이 적어지게 된다.
tag: 현재 cache에 있는 address
idx: cache 안에서의 line number를 선택
bo (block offset): block 안에서 단어를 선택
g: granularity (byte offset inside word)
Block size and miss rate
block size를 늘릴 때의 장점 (Total capacity C가 고정되어 있을 때)
Spatial locality가 올라감 (한 번에 multi-word block을 fetch한다는 거 자체가 주변 block까지 가져온다는 뜻이기 때문에)
Miss penalty는 block당 발생하기 때문에 페널티가 줄어들게 됨
tag overhead 감소
block size를 늘릴 때의 단점:
capacity 낭비
latency 증가 (아래 써있음)
block수가 줄어들기 때문에 cache 내부에서 conflict가 발생할 가능성이 올라감
Block size and Ti+1 (total access time)
block이 커지면 loading time도 늘어남 (예를 들어 block의 맨 끝에 있는 단어를 load해야 될 경우 그 block 전체가 loading 될 때까지 기다려야 함!)
이를 해결하기 위한 2가지 방법이 있음
Conflicts in direct-mapped cache
= direct-mapped cache에서는 필연적으로 conflict miss가 발생하게 된다!
다음과 같은 Direct mapped cache 그림을 다시 생각해 보자. address가 다르더라도 index가 같으면 같은 cache block으로 매핑되게 된다.
예를 들어, cache size C = 16KB = 2^14 B 이고 block size B = 64B 라고 생각해보면 index는 log_2^C/B = 256개가 된다. 따라서 256번째 memory block마다 collision이 일어난다.
Set-associative cache
📌 Key Point
Direct-mapped cache는 하나의 index당 저장할 수 있는 위치가 하나만 존재해서 conflict가 일어나게 된다.
그렇다면 여러 개의 direct-mapped cache를 묶어서 쓰면 하나의 index가 여러 위치에 저장되어, collision을 최소화할 수 있지 않을까?
📌 기본적인 구조
Cache를 ”set”으로 나눠서, 각각의 set는 a개의 cache line으로 구성되어 있다.
하나의 index가 하나의 set으로 매핑되는데, 각각의 set 안에서는 a개 중 어디에나 들어갈 수 있게 된다.
set당 여러 개의 line으로 매핑된다
Field
Bits
Role
Tag
remaining bits
compared to identify the cache line(block)
Index
log₂(# sets)
selects the set
Offset
log₂(B)
selects byte within cache line(block)
Replacement policy for set-associative cache
LRU (Least Recently Used): 제일 나중에 쓰던 memory block부터 제거한다
Fully associative cache
Block 개수가 C/B개인 cache이다.
각 row마다 tag, valid bit, data가 존재한다.
주소가 주어지면 모든 block에서 tag를 비교해서, 맞으면 hit,
저 삼각형 모양들이 사실 하나의 큰 mux이며, C/B개의 cache line이 input이고 tag 일치 여부 && valid bit 이 control signal이다.
32x32x3 image가 있다고 가정하자. (보통 3차원 이미지라함은 2차원 평면에 있고 컬러 이미지의 경우 R, G, B여서 깊이가 3인 것으로 많이 본다) 그러면 우리는 지금까지 이 image를 3072x1로 납작하게 바꾼 후에 weight를 곱해 왔다.
lesson 2에서 배웠던 내용 참고
이렇게 되면 사진 전체의 내용을 고려해줘야 한다. 여기에는 단점이 2가지가 있는데
1. 이미지 전체를 다 계산해야 하므로 계산량이 많아진다.
2. 이미지 내에서 관계없는 내용까지 학습되게 된다. (예를 들어서 동물을 분류하는 classifier를 만들고 싶을 때 고양이 뒤에 있는 배경은 전혀 중요하지 않지만 함께 학습되게 된다.
이러한 문제점들을 해결하기 위해서 나온 것이 convolutional neural network (CNN)인 것이다!
Convolutional layer의 가장 큰 차이점은, 차원을 보존한다는 것이다.
작은 필터를 (ex. 5x5x3) 이용해서 주어진 spatial location에 대해서 그 filter에 해당하는 element에 대한 dot product를 해주는 것이다. sliding하면서 지나가는 것 같은 이 과정을 convolving이라고 한다. (포스텍 23학번 일반전형 면접 문제에 나왔기 때문에 나에게는 제법 익숙한 내용이다. 그만큼 고등학생도 이해할 수 있다는 뜻이라고 할 수 있다) 마치 사진을 스윽 스캔하는 사람의 눈 같은 느낌이다.
Convolution 필터의 깊이는 항상 이미지의 깊이와 같아야 한다.
dot product한 결과에 해당하는 행렬을 activation map이라고 한다. activation map의 개수는 filter의 개수와 같다.
각 layer마다 여러 개의 filter가 있ㄷ고 이 filter가 activation map을 만든다.
ConvNet
ConvNet은 Convolution layer들의 sequence이며, 이 사이사이에 activation function이 들어가게 된다.
아니
참고로 쓸데없는 지식을 좀 자랑해 보자면 저 사진은 VGGNet의 모습이다. VGG-16은 16개의 layer를 쌓았다는 뜻이고 Conv3_2라고 써 있는 건 3번째 레이어의 2번째 필터라는 뜻이다.
여러 개의 filter를 쌓을 때는 hierarchy가 있는데, 덜 복잡한 것부터 (low-level이라고 부른다) 쌓아 올린다. 순차적으로 앞에 있는 layer들은 Low-level feature부터 학습하고 (edges), Mid-level features (corner, blob) 등을 학습하다가 점차 high-level features (실제 이미지에 가까운 것)을 학습하는 것을 볼 수 있다.
왜 이러한 구조가 되어야만 하는지는 직관적으로 생각해보면 쉬운데, 처음에 사진의 아주 작은 영역에 filter를 convolve해봤자 픽셀 자체가 몇 개 없어서 정보가 많이 없기 때문이다. 이후에 배울 pooling과 stacking을 이용하면 점차 복잡한 정보도 이해할 수 있게 된다.
[cs231n] Lecture 4. Backpropagation and neural networks
2025. 3. 10. 18:10
저번 시간까지 배운 내용
그리고 gradient를 계산하는 방법으로는 analytic gradient(직접 미분)과 numerical gradient(f(x+h)-f(x-h)/2h로 계산) 방법이 있다. 보통 코딩할 때는 numerical gradient를 더 많이 쓴다.
Computational graph를 이용하면 복잡한 함수를 간단하게 나타낼 수 있게 된다. 특히 CNN 등의 network에서는 복잡한 함수들이 많기 떄문에 도움이 된다.
Backpropagation
backpropagation은 간단하게 설명하면 chain rule을 연속해서 적용시키는 것이다.
A simple example
f라는 함수에 대한 x의 영향을 알고 싶다고 할 때, backprop의 핵심은 local gradient만 계산하면 전채 gradient를 알 수 있다는 것이다.
chain rule을 생각하면 너무나도 당연한 결과이기 때문에 이게 뭐가 특별하냐고 생각할 수도 있겠지만, 이후에 이 아이디어를 통해 복잡한 함수를 간단하게 나타낼 수 있다는 것을 알게 될 것이다.
Backpropagation은 두 단계로 나눌 수 있는데 forward pass와 backward pass이다.
Forward pass
정상적으로 식의 값을 계산해 나가는 과정이다.
Backward pass
뒤에서부터 순차적으로 식의 gradient를 계산하는 과정이다. local gradient와 upstream gradient의 곱으로 나타낼 수 있다.
다양한 함수들의 gradient
max(x1,x2)의 경우 x1<x2일 때 x1의 gradient는 0, x2의 gradient는 1이다.
직관적으로 계산에 영향을 주는 값만 남는 것이 당연하다.
add gate: gradient distributor
max gate: gradient router
mul gate: gradient switcher
정도로 볼 수 있겠다.
Backpropagation (vectorized)
실제 머신러닝 데이터를 다룰 때는 대부분 array 또는 벡터 형식으로 된 데이터를 다루게 된다.
따라서 gradient도 자코비안으로 나타나게 된다
이런 식으로 vectorized된 예시에 대해서도 똑같이 계산할 수 있음
Example code
class ComputationalGraph(object):
def forward(inputs):
# 1. pass inputs
# 2. forward pass
for gate in self.graph.nodes_topologically_sorted():
gate.forward()
return loss
def backward():
for gate in reversed(self.graph.nodes_topologically_sorted()):
gate.backward()
return inputs_gradients
Neural Networks
linear function은 여러 개 합성해도 결국은 하나의 linear function으로 납작하게 바뀌게 된다.
따라서 score function 계산할 때 non-linearity을 적용시켜주는 게 중요하다. intermediate variable에 non-linear function을 적용해준다. (이건 max, sigmoid, relu, 등등등.... 나중 가서 배우겠지만 많은 것들이 가능하다) 이를 통해서 더 복잡한 non-linear function을 만드는 것이다.
3강에서 Image recognition을 위해 썼던 loss function을 되짚어 보자. weight matrix의 행이 각 input에서 어떤 것을 찾고 있는지에 대한 template 역할을 한다.
image recognition에서의 문제점은 template이 하나만 있기 때문에, 한 가지 category를 대표하는 이미지에 가중치를 주고 싶을 때 다양성이 떨어진다. 따라서 layer 개수를 늘리면 더 다양한 template에 대한 가중치를 부여할 수 있게 된다.
예를 들어, 이 식에서
intermediate variable h는 w1에 포함되어 있는지 각 template을 얼만큼 포함하고 있는가에 대한 값이고
w2는 이 intermediate score을 weighted sum해서 최종적인 class 분류를 하는 것이다.
이렇게 점점 deep한 neural network를 만들 수 있게 된다.
지금 한 번 colab으로 간단한 2-layer neural network를 직접 만들어 보자.
Margin between correct and incorrect scores will double. But this means that it still has zero loss.
Problem: inconsistency. How is it going to choose between all the W's?
Regularization term
Regularization term이라는 건 왜 필요할까?
training data에 맞춰서 training을 하다 보면 test data에는 맞지 않는 overfitting이 발생할 수 있음 (classifier가 너무 복잡해지게 됨) 따라서 모델을 가능한 simple하게 만드는 것이 중요하다.
Bias-variance tradeoff라는 말이 있다.
Bias: 참값과 추측값의 차이를 의미한다. Training dataset에 대한 예측의 정확도를 의미하기도 한다.
Variance: 주어진 모델을 기반으로 한 prediction들간의 차이를 의미한다. Variance가 클수록 test/validation 데이터에 대한 정확도가 떨어지는 것이라고 볼 수 있다. (high testing/validation error)
regularization term은 bias를 늘리고 variance는 줄인다. 이 regularization을 얼마나 해 줄건지가 $\lambda$ = regularization strength (hyperparameter) 이다.
이게 무슨 말인지 이해가 안 될 수 있는데, 예시를 들어보자. 2차원 평면에 찍혀 있는 2종류의 점들을 분류하는 classifier를 만든다고 생각해보자. 각 점 사이를 모두 연결해서 복잡하게 decision boundary를 만들면 training data에는 완벽하게 맞겠지만 새로운 점이 추가되면 안 맞음!
"Occam's Razor": 여러 가설 중에 가장 간단한 것이 가장 좋은 것이다 (무슨 12세기의 과학자가 한 말인데 대개 딥러닝에도 적용된다.)
Penalizes the complexity of the model.
ex)
$ x = [1, 1, 1, 1] $
$ w_1 = [1,0,0,0] $
$ w_2 = [0.25, 0.25, 0.25, 0.25] $
L2 regularization: $w_1$ 선호
L1 regularization: $w_2$ 선호
즉, L2 regularization은 큰 weight가 여러 개 있는 것을 선호하고 L1 regularization은 반대로 큰 weight가 하나라도 있는 것에 대해 큰 페널티를 준다고 할 수 있다.
그렇다면 근본적으로 왜 regularization term을 추가하는 것이 모델의 complexity에 영향을 줄까? 엄청 복잡한 10차 함수로 decision boundary를 정했는데 weight 값은 엄청 작을 수도 있는 거 아니냐?라는 의문이 들 수 있다. 경험적으로 regression을 할 때 딱 맞추려면 weight값이 커지는 게 당연하다고 볼 수도 있지만, neural network를 이용해서 조금 직관적으로 그 이유를 알아보자.
W가 0으로 갈수록 hidden layer들은 존재하지 않는 것이나 마찬가지가 될 수 있다. (왜냐면 각 layer는 wx+b 꼴로 계산되기 때문이다)
따라서 neural network 자체가 단순해지기 때문에 overfitting을 막는 효과가 있다고 한다.
하지만 수학적으로 오버피팅을 완벽하게 정의할 수 있는 방법 자체가 많이 없기 때문에 어쩔 수 없는 것 같기도 하다
Softmax classifier
어떤 값이 주어졌을 때 그 값에다가 exponential 씌우고 normalize하는 함수를 softmax function이라고 한다.
여기서 negative log likelihood를 최소화하는 것이 우리의 목표이다.
Optimization
gradient: 각 차원에서의 partial derivative
slope: dot product of the direction with the gradient
direction of steepest descent is the negative gradient.
아까 봤던 loss function을 적당히 잘 미분하면 analytic gradient를 얻을 수 있다.
실제로 숫자를 대입해서 numerical gradient를 항상 계산할 필요는 없다는 것이다.
이것이 그 유명한 gradient descent 기법이다.
가장 단순하게 gradient descent의 코드를 짜보자면 다음과 같은 형태로 나오게 된다
Input: image → Output: Assign image to one of a fixed set of categories
Challenges in image classification
1. semantic gap
- 사람은 고양이를 보고 직관적으로 고양이라는 것을 알 수가 있음. 하지만 컴퓨터는 눈이 있는게 아니기 때문에 그냥 [0,255]의 값을 가지는 숫자들의 grid라고 픽셀들의 집합 정도로만 생각하게 됨.
- 사람은 똑같은 고양이를 다른 각도에서 찍어도 같은 고양이라는 것을 알아볼 수 있음.
but 컴퓨터 입장에서는 모든 픽셀 값이 바뀌기 떄문에 같은 것이라는 걸 알기가 힘듦
2. intraclass variation (같은 고양이끼리도 종에 따라서 다르게 생겼기 때문에)
3. 고양이를 종별로 구별하고 싶은 경우
4. background clutter (배경색이 같은 경우) Occlusion (detect하고 싶은 물체가 매우 일부분만 보이는 상태라면?)
machine learning: data-driven approach
데이터를 통해 "learn"할 수 있는 알고리즘을 만드는 것이 목표이다.
collect a dataset of images and labels
use machine learning to train a classifier
evaluate the classifier on new images
def train(images, labels)
→ return model
def predict(model, test_images)
→ 앞에서 train한 model을 바탕으로 label prediction
Datasets for image classification
MNIST (숫자 쓰는 dataset)
가장 흔한 데이터셋. 10 classes (0-9), 28x28 grayscale 매우 단순한 데이터셋이기 때문에 MNIST에서 high performance를 보여주는 알고리즘이어도 다른 데이터에 대해서는 안 그럴 수가 있음
CIFAR10
10 classes, 32x32 RGB images
cs231n 과제에서 사용한다고 함
ImageNet
1000 classes, ~1.3M training images
Perfomance metric: top 5 accuracy, 알고리즘이 predict한 5개의 label 중 하나만 맞으면 됨 image classification에서 반드시 좋은 성능을 보여줘야만함
기타) Omniglot: Characters from 50 different alphabets (162 categories)
Only 20 images per category -> few-shot learning 알고리즘을 만들 때 많이 이용
예를 들어 한 번 해보자...
Linear classifier를 실제 코드로 implement하려면 과연 어떻게 해야 하는 것인가...
먼저 image들을 전부 loading시켜 준다.
# Xtr (size 50000 * 32 * 32 * 3) holds all the images in the dataset
# Ytr (1d array of length 50000 holding all the training labels)
Xtr, Ytr, Xte, Yte = load_CIFAR10('data/cifar10/')
2개의 이미지가 얼마나 semantically similar한가? (test image - training image) 픽셀값의 차를 계산한 후 L1 distance를 계산해서 pixel-wise absolute value difference를 구한다.
Nearest neighbor classfier (code)
import numpy as np
class NearestNeighbor:
def __init__(self):
pass
def train(self, X, y):
*** X is N x D where each row is an example, Y is 1-dimension of size N ***
# simply memorize all the training data
self.Xtr = X
self.Ytr = y
def predict(self, X):
num_test = X.shape[0]
Ypred = np.zeros(num_test, dtype = self.ytr.dtype)
for i in xrange(num_test):
# find the nearest training image to the i'th test image using the L1 distance
distances = np.sum(np.abs(self.Xtr - X[i,:]), axis = 1)
min_index = np.argmin(distances) # get the index with the smallest distance
Ypred[i] = self.ytr[min_index] # predict the label of the nearest example
return Ypred
Q. Nearest Neighbor의 training 시간 복잡도? -> O(1)
data를 다 기억하기만 하면 되기 때문에
Q. Nearest Neighbor의 testing 시간 복잡도? -> O(N)
training example과 testing example을 매번 비교해야 함
testing은 빨라야 하고 training에는 시간이 오래 걸려도 상관없기 때문에 안 좋음.
k-nearest neighbors에서 k의 값이 늘어날수록 outlier가 줄어든다.
해결을 위해서 L1 대신 L2 distance를 이용하기도 함
with right choice of distance metric, we can apply K-Nearest neighbor to any type of data
K-nearest neighbor에서의 hyperparameters
k, distance의 종류, 등등등...
data를 training set, validation set, test set으로 나눠서 검증해야 한다.
(validation set이 새로운 데이터의 역할을 한다)
Cross-validation: 데이터의 크기가 작을 때 주로 활용, 데이터를 여러 가지로 분리한 후에 validation으로 사용하고 결과의 평균치를 계산한다 (딥러닝에서는 자주 사용되지 않음!)
Curse of dimentionality
Linear classifier
Parametric approach
어떤 이미지가 픽셀로 주어져 있다. (32x32x3개의 숫자로 이루어진 array)
이 이미지를 10개의 class 중 하나로 분류를 해야 한다.
이거를 긴 column vector로 stretch한다 → x
Score function
f(x,W) = Wx + b
W: parameter/weight (가중치) 행렬
x: 이미지의 픽셀들을 column 한 줄로 stretch한 것
b: bias vector (데이터를 보정해주는 역할)
template matching approach→ 이 행렬에 있는 각각의 열이 이미지의 어떤 템플릿에 대응된다.
linear classifier의 한계: 하나의 template만 배우기 때문에, 만약 어떤 물체가 여러 가지 형태로 나타난다면 linear classifier로 training할 수가 없다.
Interpreting a linear classifier
하나의 이미지를 high-dimensional space에 있는 point로 생각할 수 있고, decision boundary가 n차원 면이 된다고 생각할 수 있다.
Hard cases for a linear classifier
decision boundary가 linear하지 않은 경우 원하는대로 잘 되지 않음.
(kernel trick, feature engineering, neural networks with activation functions 등 다양한 방법이 있지만 아직 여기서는 다루지 않은 듯 하다