세상의 변화에 대해 관심이 많은 이들의 Tech Blog search

RNN Encoder-Decoder

|

Learning Phrase Representations using RNN Encoder-Decoder for Statistical Machine Translation

본 글은 Kyunghyun Cho, Bart van Merrienboer, Caglar Gulcehre, Dzmitry Bahdanau, Fethi Bougares, Holger Schwenk, Yoshua Bengio가 2014년에 Publish한 위 논문을 리뷰한 것이다.

Abstract
Encoder의 역할은 a sequence of symbols를 고정된 길이의 vector representation으로 나타내는 것이고, Decoder의 역할은 그 representation을 다시 sequence of symbols로 나타내는 것이다.

두 모델은 jointly train되는데, 그 목적은 source sequence가 주어졌을 때,
target sequence가 나타날 조건부 확률을 최대화하는 것이다.
= (Maximize the conditional probability of a target sequence given a source sequence.)

Introduction
본 논문은 SMT(Statistical Machine Translation)의 영역에서 모델을 분석하는 것을 주요 목적으로 하고 있다.

연구자들은 memor capacity와 학습 효율을 향상시키기 위해 다소 복잡한(sophiscated) Hidden Unit을 사용하였으며, 영어를 프랑스어로 번역하는 과정을 통해 그 성능을 평가하였다.

본 논문에서 제안한 RNN Encoder의 경우 phrase table에서 언어학적 규칙(regularities)을 잡아내는 역할을 수행하였으며 이는 사실 전반적인 번역 성능의 향상을 꾀하는 과정의 일부로 평가된다. Decoder의 경우 phrase의 continuous space representation을 학습하는데, 이는 phrase의 의미론적(semantic)이고 통사론적(syntactic) 구조를 저장하는 역할을 수행한다.

RNN Encoder-Decoder
기본적인 RNN은 sequence에서 다음 symbol을 예측하는 방향으로 학습됨으로써 sequence의 확률 분포를 학습한다.
이 때 시간t에 대한 output은 $ p(x_t | x_{t-1}, …, x_1) $와 같은 조건부 확률로 표현된다.
따라서 sequence x의 확률은 아래와 같이 표현할 수 있다.

[p(x) = \prod_{t=1}^T p(x_t x_{t-1}, …, x_1)]

(위 $p(x)$의 x는 x vector이며, 위와 같은 식으로 표현되는 이유는 곱셈정리에 의한 것임)
이전의 symbol들에 근거하여 다음 symbol을 예측한다고 볼 수 있다.

본 논문에서 제안하는 RNN Encoder-Decoder 모델은 다소 새로운 구조를 갖고 있다. Encoder는 input sequence x의 원소 symbol들을 연속적으로 읽어 들인다.
(reads each symbol of an input sequence x sequentially)

이 과정 속에서 시간 t의 hidden state는 아래와 같이 업데이트 된다.

[h_{<t>} = f(h_{<t-1>}, x_t)]

즉, 이전 hidden state와 시간t의 새로운 input $x_t$에 의해 업데이트 되는 것이다.
모든 reading이 끝나고 나서 나면 RNN의 hidden state는 모든 input sequence에 대한 summary c이다.

Decoder는 주어진 hidden state $h_{}$을 바탕으로 다음 symbol $ y_{} $를 예측함으로써 output sequence를 생성하도록 학습된다.

다만 여기서 주목할 점은, 기본 RNN과 달리 새로운 hidden state는 summary c
이전 output symbol $ y_{t-1} $에도 conditioned 되어 있다는 것이다.
즉 아래와 같이 표현될 수 있다.

[h_{<t>} = f(h_{<t-1>}, y_{t-1}, c)]

다음 symbol의 조건부 확률 분포는 아래와 같이 나타낼 수 있다.

[P(y_t y_{t-1}, …, y_1, c) = g(h_{<t>}, y_{t-1}, c)]

정리하자면, RNN Encoder-Decoder의 두 성분은 아래의 조건부 로그 가능도를 최대화하도록 결합하여 학습된다.

[\max_{\theta} {1 \over N} \sum_{n=1}^N log p_{\theta} (y_n x_n)]

여기서 $\theta$는 모델 parameter의 집합을 의미하며,
$y_n$은 output sequence를 $x_n$은 input sequence를 의미한다.

이렇게 학습된 RNN Encoder-Decoder는 크게 2가지 방법으로 활용될 수 있다.

  1. 주어진 input sequence에 대해 새로운 target sequence를 생성할 수 있다.
  2. input & output sequences 쌍의 적합성을 평가할 수 있다. (Score)

2.3절인 Hidden Unit that Adatively Remembers and Forgets부분은 LSTM Unit의 기본 형식을 따르고 있기 때문에 식에 대한 리뷰는 생략하겠다. 다만 사용된 용어만을 살펴 보면 아래과 같다.

Reset Gate $r_j$, Update Gate $z_j$, Proposed Unit $h_j^{t}$

효과에 대해 설명하자면,

  1. Reset Gate가 0에 가까워질 때, 시간 t의 Candidate hidden state는 이전 hidden state를 잊고(ignore, forget) 시간 t의 현재 input x로 Reset하게 된다.
  2. Updated Gate는 이전(시간 t-1) hidden state의 정보가 얼마나 현재(시간 t) hidden state에 영향을 줄 것인가를 결정한다. 이를 통해 lont-term 정보를 효과적으로 보존(rembember)한다.
  3. 각각의 hidden unit은 Reset/Update Gate를 각각 갖고 있기 때문에, different time scales에 나타나는 종속성(dependencies)를 포착(capture)하는 법을 학습하게 된다. short-term dependencies를 포착하는 법을 학습한 unit들의 Reset Gate는 자주 활성화 될 것이며, long-term dependencies를 포착하는 법을 학습한 unit들의 Update Gate는 자주 활성화될 것이다.

Statistical Machine Translation: SMT
앞에서도 설명하였듯이 SMT의 기본 목표는 주어진 source sentence에 대하여 translation을 찾는 것이고, 식으로 표현하자면 아래와 같다.

[p(f e) \propto p(e f) * p(f)]

일단 Phrase Pairs를 평가하는 측면에서 본 모델을 살펴보도록 하겠다.
RNN Encoder-Decoder를 학습시킬 때, 기존 말뭉치들에서 각각의 phrase pair들의 출현 빈도는 고려하지 않는다.
그 이유는 2가지로 풀이 된다.

  1. 계산량 감소를 위해서이다.
  2. 본 모델이 단순히 출현빈도 Rank에 영향을 받지 않게 하기 위함이다.

사실 phrase 속에 존재하는 translation probability는 이미 원래 corpus의 phrase pairs의 출현 빈도를 반영한다. 모델이 학습 과정 속에서 이러한 출현 빈도에 따른 어떤 규칙을 학습하는 것이 아니라, 언어학적 규칙(linguistic regularities)을 학습하도록 하는 것이 핵심이라고 할 수 있다.
(learning the manifold of plausible translations)

Experiments
대규모의 데이터가 구축되었지만 실제 학습을 위해서 본 논문의 저자는 source & target vocab을 가장 자주 등장한 15,000개의 단어로 한정하였다. 이는 전체 데이터셋의 93%를 커버한다고 밝혔다.

학습과정에 대한 자세한 사항은 논문을 참조하기 바란다.

Conclusion
결과적으로 RNN Encoder-Decoder는 phrase pairs 내에 있는 언어학적 규칙을 포착하고, 적절하게 구성된 target phrases 또한 제안하는 데에도 좋은 성능을 보이는 것으로 확인되었다.

Structure of Encoder
source phrase X와 Y의 형태는 아래와 같다.

[X = (x_1, x_2, … , x_N), Y = (y_1, y_2, … , y_N)]

X.shape = (N, K), Y.shape = (N, K)

물론 여기서 각 세로 벡터는 one-hot vector이다.
source phrase의 각 단어는 500차원의 벡터로 임베딩된다.
Encoder의 Hidden state는 1000개의 unit을 갖고 있다.
(시간 t의 hidden state $h_{}$의 shape = (1000, 1))

위 hidden state가 계산되는 과정을 살펴보면,

  1. Reset Gate
    \(r = \sigma(W_r e(x_t) + U_r h_{<t-1>})\)

(1000, 1) = (1000, 500) X (500, 1) + (1000, 1000) X (1000, 1)

  1. Update Gate
    \(z = \sigma(W_z e(x_t) + U_z h_{<t-1>})\)

shape은 위와 같다.

  1. Candidate
    \(\tilde{h}^{<t>} = tanh(W e(x_t) + U(r \odot h_{<t-1>} ))\)

(1000, 1) = (1000, 500)X(500, 1) + (1000, 1000)X(1000, 1)$\odot$(1000, 1)

  1. Hidden State
    \(h^{<t>} = z h^{<t-1>} + (1-z) \tilde{h}^{<t>}\)

  2. Representatino of the source phrase: 농축된 정보
    \(c = tanh(V h^{<t>})\)

Structure of Decoder
Soon to be updated

Comment  Read more

파이썬 정규표현식(re) 사용법 - 09. 기타 기능

|

파이썬 정규표현식(re) 사용법 - 01. Basic
파이썬 정규표현식(re) 사용법 - 02. 문자, 경계, flags
파이썬 정규표현식(re) 사용법 - 03. OR, 반복
파이썬 정규표현식(re) 사용법 - 04. 그룹, 캡처
파이썬 정규표현식(re) 사용법 - 05. 주석, 치환, 분리
파이썬 정규표현식(re) 사용법 - 06. 치환 함수, 양방탐색, 조건문
파이썬 정규표현식(re) 사용법 - 07. 예제(숫자)
파이썬 정규표현식(re) 사용법 - 08. 예제(단어, 행)
파이썬 정규표현식(re) 사용법 - 09. 기타 기능


이 글에서는 re 패키지에 포함된, 지금까지의 글에서 다루지 않았던 함수와 속성 등을 다루도록 하겠다.

본 글에서 정규표현식은 regex와 같이, 일반 문자열은 ‘regex’와 같이 표시하도록 한다.

파이썬 버전은 3.6을 기준으로 하나, 3.x 버전이면 (아마) 동일하게 쓸 수 있다.
2.7 버전은 한글을 포함한 비 알파벳 문자 처리가 다르다.


함수

re.escape(string)

re.escape 함수는 문자열을 입력받으면 특수문자들을 이스케이프 처리시켜 준다.

pattern = r'((\d)\2{4,})'
print(re.escape(pattern))

결과

\(\(\\d\)\\2\{4\,\}\)

re.purge()

사실 설명하지 않은 것이 있는데, re 패키지는 re.compile로 만들어 놓은 객체들을 cache에 저장해 둔다. 최대 100개까지라고 알려져 있으며, 그 수를 넘어갈 경우 초기화된다고 한다.
물론 여러분은 아마 한 프로그램 내에서 100개 이상의 다른 정규식을 쓸 일은 없으니 크게 신경 쓸 필요는 없다.

re.purge 함수는 이 cache를 초기화하는 함수이다.

re.purge()

결과

결과는 아무것도 출력되지 않는다.


속성

re.RegexFlag

이전 글에서 flags를 설명했었는데, 이 flag들이 어떤 것이 있는지 알려주는 객체가 re 안에 내장되어 있다.

for flag in re.RegexFlag:
    print(flag)

결과

RegexFlag.ASCII
RegexFlag.IGNORECASE
RegexFlag.LOCALE
RegexFlag.UNICODE
RegexFlag.MULTILINE
RegexFlag.DOTALL
RegexFlag.VERBOSE
RegexFlag.TEMPLATE
RegexFlag.DEBUG

re.TEMPLATE

아마 쓸 일이 없을 듯하므로 설명은 생략한다. (?)

다만 이런 것이 있다는 것만 소개한다.


re.DEBUG

reObj를 출력하면 컴파일한 정규식을 그대로 출력하던 것을 기억할 것이다. re.debug는 일종의 디버깅 모드로서, 정규식의 대략적인 구조를 알 수 있다.
말 그대로 디버깅용으로 쓰면 될 듯하다.

r = re.compile('\d{3,6}', re.DEBUG)
print(r)
print(r.findall('AS 123123 ars'))

결과

MAX_REPEAT 3 6
  IN
    CATEGORY CATEGORY_DIGIT
re.compile('\\d{3,6}', re.DEBUG)
['123123']

reObj의 사용법은 기본 compile된 객체와 완전히 같다.


re.error

re.error는 compile 함수에 전달된 문자열이 유효하지 않은 정규식일 때 발생하는 에러 타입이다. try-except 구문으로 처리하면 된다. 자세한 사용법은 아래 예시로만 보여도 충분할 듯 하다.

참고로 아래 코드의 phi는 원주율을 소수점 1만 자리까지 저장한 문자열이다.

regex_list = [
    r'((\d)\2{4,})',
    r'((\d)\1{4,})'
]

for regex in regex_list:
    try:
        reObj = re.compile(regex)
        print(list(map(lambda x: x[0], reObj.findall(phi))))
    except re.error:
        print("<Invalid regular expression %s>" % regex)
    finally:
        print('done')

결과

['999999']
done
<Invalid regular expression ((\d)\1{4,})>
done

무엇이 유효하지 않은지는 연습문제로 남겨두도록 하겠다.

조금 더 자세한 사용법은 여기를 참조한다.


이것으로 정규표현식에 대한 글을 마치도록 한다.
조금 더 복잡한 예제를 정리해 두면 좋겠지만, 그때그때 맞게 쓰는 것이 더 나을 것 같아서 굳이 따로 정리할 필요는 없을 것 같다.

Comment  Read more

GitHub 사용법 - 08. Conflict

|

주의: 이 글을 읽는 여러분이, 만약 git을 많이 써 봐서 익숙한 것이 아니라면, 반드시 손으로 직접 따라 칠 것을 권한다. 눈으로만 보면 100% 잊어버린다.

저번 글에서 작업하던 것을 이어서 한다. 저번 글에서는 diff, add, commit, .gitignore에 대해서 알아보았다.


Conflict

Conflict는 이름 그대로 충돌인데, 다음의 경우일 때 conflict가 생긴다.

같은 파일의 같은 부분을 동시에 두 곳(이상)에서 수정했을 때

이런 경우는 보통 여러 사람의 분업이 명확하게 이루어지지 않아 코드의 같은 부분을 수정할 때 일어난다.
물론 1인 팀에서도 코드 관리를 잘못하여, 혹은 여러 컴퓨터에서 작업하게 될 때 이러한 실수가 일어나기도 한다.

Conflict의 발생 및 해결 순서는 다음과 같다.

  1. 동시에 같은 파일의 같은 부분을 수정하고, merge 혹은 push를 할 때 일어난다. 이는 같은 파일을 수정했다 하더라도 명확히 다른 부분이 수정되었다면 git이 알아서 병합 과정을 처리해준다는 뜻이다.
    • 충돌이 일어났다면, git은 병합 과정을 진행하지 않고 충돌 상태를 그대로 둔다. 알아서 처리하는 대신 사용자가 충돌을 살펴보고 원하는 코드만 남길 때까지 기다린다.
  2. 사용자가 편집기에서 코드를 원하는 부분만 남긴다. 충돌이 일어난 부분은 git이 명확하게 표시를 해 준다.
    • 표시를 한다는 것은, 실제로 코드 파일을 git이 수정한다는 뜻이다. 물론 알아서 충돌을 해결한다는 뜻이 아니라, “여기 충돌 생겼어”하고 강력하게 표시를 한다는 뜻이다. 만약 코드 테스트를 한다면 틀림없이 이 부분에서 syntax error가 뜬다.
  3. 사용자가 직접 수정을 끝냈으면, commit을 한 다음 merge 혹은 push 작업을 완료한다.
    • 이때 따로 새로운 commit이 생기는 대신 원래 있어야 할 merge commit만 생성된다.

하나씩 살펴보자.


1. Conflict 발생시키기

일부러는 절대 해서는 안 되지만, 예시를 보여주어야 하기 때문에 고의로 conflict를 발생시켜 보겠다.

일단은 git_tutorial repo의 3rd-branch로 이동한 다음, git rebase master 명령을 실행한다.
2nd-branch부터 시작한 수정사항이 반영되어있지 않기 때문이다.

git checkout 3rd-branch
git rebase master

second.py의 마지막에 다음을 추가한다.

git_tutorial repo의 2nd-branch로 이동한 다음, first.py의 마지막에 다음을 추가한다.

git checkout 2nd-branch

print("Desired sentence in 2nd-branch")

commit을 한 뒤, master branch에서 2nd-branch의 내용을 merge한다.

git add first.py
git commit -m “Desired commit from 2nd-branch”
git checkout master
git merge 2nd-branch

그리고 3rd-branch로 이동하여 비슷하게 반복한다. 수정하는 파일은 당연히 first.py이다.

git checkout 3rd-branch

print("Unwanted sentence in 3nd-branch")

git add first.py
git commit -m “Unwanted commit from 2nd-branch”
git checkout master
git merge 3rd-branch

01_conflict

예상대로 conflict가 뜬다.


2. Conflict 해결하기

이제 편집기로 가서 first.py를 살펴보라. 메모장 코딩을 하는 것이 아니라면, 에러 표시줄이 여럿 보일 것이다.

02_file

파일을 살펴보면 확실히 어느 부분에서 conflict가 일어났는지 바로 확인이 가능하다.
참고로 필자와 빈 줄의 개수가 달라도 별 상관은 없다.

git이 수정해놓은 부분을 보면 다음과 갈은 구조로 되어 있다.

<<<<<<< HEAD
(현재 브랜치의 HEAD의 내용)
=======
(merge한 브랜치의 내용)
>>>>>>> (merge한 브랜치 내용)

여기서 각 브랜치의 내용 중 사용자가 원하는 부분만 남기고 모두 지우면 된다. 한쪽 브랜치의 내용만 남길 수도 있고, 양쪽 모두의 내용의 일부 혹은 전체를 남길 수도 있다.

수정을 마쳤으면 필요 없는 부분인 <<<<<<< HEAD, =======, >>>>>>> <branch> 등은 모두 제거하면 된다.

04_resolve

예상대로 남길 부분은 “Desired sentence”이므로 이 문장만 남기고 나머지 부분은 모두 삭제하면 된다.

IDE에 따라서는 다음과 같이 표시될 수도 있다. 이때는 조금 더 편하게 진행할 수 있다.
아래 예시는 Visual Studio Code의 경우이다.

03_vscode

수정하기 전 ‘변경 사항 비교’를 누르면 어떤 부분이 다른지를 양쪽에 나누어 보여준다.
내용을 확인한 뒤 ‘현재 변경 사항 수락’을 누르면 원하는 부분만 남겨지고 나머지는 알아서 삭제될 것이다. 물론 다른 부분을 남겨도 상관없다.


3. commit(merge) & push하기

그리고 수정한 파일을 git add 명령으로 추가한 뒤 commit한다. 정상적으로 처리되었는지 보기 위해 로그도 한번 출력해 보자.

git add first.py
git commit

05_commit

ESC 입력 후 :wq
git log –oneline

06_merge

이러면 conflict가 해결된 것이다. remote repo에 push하자.

git push

그리고 3rd-branch로 이동하여 rebase를 한다.

git checkout 3rd-branch
git rebase master


4. Conflict가 발생하지 않는 경우

조금 전 처음에 했던 것처럼 2nd-branch로 이동해 업데이트한다.

git checkout 2nd-branch
git rebase master

이번엔 first.py 파일 끝에 다음을 추가한다.

print("This is the 2nd sentence written in 2nd-branch.")

그리고 second.py의 내용은 다음 문장 빼고 모두 지운다.

print("This is the 1st sentence written in 2nd-branch.")

다시 비슷한 과정을 반복한다.

git add *.py
git commit -m “No-collision commit from 2nd-branch”
git checkout master
git merge 2nd-branch

git checkout 3rd-branch

다음으로는 3rd-branch로 이동하여, first.py 파일의 내용을 다음 문장 빼고는 모두 지운다.
지우기 전에, print("This is the 1st sentence written in 2nd-branch.") 문장은 없어야 정상이다. 있다면, checkout을 제대로 했는지 살펴보라.

print("Desired sentence in 2nd-branch")

git add first.py
git commit -m “No-collision commit from 3rd-branch”
git checkout master
git merge 3rd-branch

07_no_conflict

문제없이 잘 병합된 것을 확인할 수 있다. 다른 파일, 혹은 같은 파일을 수정했더라도 수정한 부분이 다르면 conflict가 일어나지 않는다.
위 예시의 경우 first.py를 2nd-branch에서는 파일의 끝 부분을, 3rd-branch에서는 파일의 시작 부분을 수정했기 때문에 문제가 일어나지 않았다.


5. 이유없이 conflict가 생기는 것 같은 경우

사실 이유가 없는 경우는 없지만, 간혹 두 branch 간 차이가 전혀 없어 보이고 파일 수정까지 끝마쳤는데도 conflict가 계속해서 발생하는 경우가 있다.

다른 원인일 수도 있지만, 정말로 아무 차이도 없어 보인다면 운영체제의 line-feed 문자의 차이로 인한 문제일 수 있다.
즉 Windows는 '\r\n'을, Linux나 Mac은 '\n'을 개행 문자로 사용하기 때문인데, 이 차이를 제대로 인식하지 못해 실패하는 경우가 있으니 참고하면 되겠다.

해결법은 다음과 갈다.

git config merge.renormalize true

그리고 merge를 시도하면 된다.


다음 글에서는 Git 전체 명령어 사용법을 다룬다.


Git 명령어

GitHub 사용법 - 00. Command List에서 원하는 명령어를 찾아 볼 수 있다.

Comment  Read more

BOJ 01000(A+B), 01001(A-B), 01002(터렛) 문제 풀이

|

참조

분류 URL
문제 A+B, A-B, 터렛
이 글에서 설명하는 코드 01000~01002

BOJ 1000(A+B)

개요

시간복잡도: $ O(1) $

공간복잡도: $ O(1) $

문제 풀이

단순 구현 문제이다. 이 문제는 여러분이 어떤 식으로 이 사이트에 제출해야 하는지를 시험할 수 있는 문제이다. 아래 구현과 같이 하면 된다.

구현

다음과 같다. 이 문제는 C 스타일로 구현하였다.

#include <stdio.h>

int main(){
    int a, b;
    scanf("%d%d", &a, &b);
    printf("%d", a+b);

    return 0;
}

BOJ 1001(A-B)

개요

시간복잡도: $ O(1) $

공간복잡도: $ O(1) $

문제 풀이

이 문제 역시 단순 구현 문제이다. 이 문제도 여러분이 어떤 식으로 이 사이트에 제출해야 하는지를 시험할 수 있는 문제이다. 아래 구현과 같이 하면 된다.
사실 바로 위 문제에서 딱 한 글자만 바꿔도 된다.

구현

다음과 같다. 이 문제는 C++ 스타일로 구현하였다.

#include <iostream>

using namespace std;

int main(){
    ios::sync_with_stdio(false);    cin.tie(0);
    int a,b;
    cin >> a >> b;
    cout << a-b;
}

참고로, cin, cout 함수는 scanfprintf에 비해 느리기 때문에, main 함수 첫 줄과 같은 구문을 써 주어야 한다.
ios::sync_with_stdio(false);를 썼을 때는 cincoutscanfprintf를 같이 쓰면 안 된다.


BOJ 1002(터렛)

개요

시간복잡도: $ O(1) $

공간복잡도: $ O(1) $

문제 풀이

이 문제도 구현 문제지만, 알고리즘 문제를 풀어본 경험이 별로 없으면 충분히 헷갈릴 수 있는 문제이다.

이 문제의 답의 종류는 4종류이다. 먼저 종이에 원 2개를 그려보고 답의 개수가 될 수 있는 가지수를 생각하면 답을 얻을 수 있다.

  1. 위치의 개수가 무한대인 경우는 터렛 2개가 같은 위치에 있는 경우이다. 사실은 문제 데이터가 잘못되지 않았다면 거리 r1과 r2도 같아야 한다. 답은 -1이다. 두 원이 완전히 일치하는 경우이다.
  2. 만약에 두 사람이 구한 거리의 합이 두 터렛의 거리보다 멀면 불가능하다. 비슷하게 거리의 차가 터렛의 거리보다 짧아도 불가능하다. 답은 0이다. 두 원이 만나지 않는 경우이다.
  3. 만약 터렛1, 터렛2, 마린이 일직선상에 있고 거리의 합 또는 차가 일치하면 답은 1이다. 두 원이 접하는 경우이다.
  4. 그 이외의 경우라면 답은 2이다. 두 원이 두 점에서 만나는 경우이다.

구현

다음과 같다. R과 r은 각각 거리의 합의 제곱 또는 차의 제곱이다.

#include <stdio.h>

using namespace std;

int TC;
int x1, y1, x2, y2;
int r1, r2;
int main(){
    scanf("%d",&TC);
    while(TC--){
        scanf("%d%d%d%d%d%d", &x1, &y1, &r1, &x2, &y2, &r2);
        int d_square = (x1-x2) * (x1-x2) + (y1-y2) * (y1-y2);
        int R_square = (r1+r2) * (r1+r2);
        int r_square = (r1-r2) * (r1-r2);
        if(x1==x2 && y1==y2 && r1==r2)
            puts("-1");
        else if(d_square > R_square || d_square < r_square)
            puts("0");
        else if(d_square==R_square || d_square==r_square)
            puts("1");
        else 
            puts("2");
    }
    return 0;
}
Comment  Read more

GitHub 사용법 - 07. diff, add, commit, .gitignore 중급

|

주의: 이 글을 읽는 여러분이, 만약 git을 많이 써 봐서 익숙한 것이 아니라면, 반드시 손으로 직접 따라 칠 것을 권한다. 눈으로만 보면 100% 잊어버린다.

저번 글에서 작업하던 것을 이어서 한다. 저번 글에서는 다른 local repo의 branch update까지 살펴보았다.


git add, git diff

다시 git_tutorial_clone 디렉토리 밖으로 빠져 나와서, 원래 git_tutorial repository로 돌아가자. 그리고 업데이트를 한다.

cd ../../git_tutorial
git pull

여기에서 git add 명령의 다양한 옵션을 설명했었다.
페이지를 옮겨다니기 귀찮을 것이므로 다시 한번 가져왔다.

명령어 Description
git add first.py first.py 파일 하나를 staging area에 추가한다.
git add my_directory/                               my_directory라는 이름의 디렉토리와 그 디렉토리 안의 모든 파일과 디렉토리를 staging area에 추가한다.
git add . 현재 폴더의 모든 파일과 디렉토리, 하위 디렉토리에 든 전부를 staging area에 추가한다. 규모가 큰 프로젝트라면 써서는 안 된다.
git add -p [<파일>] 파일의 일부를 staging하기
git add -i Git 대화 모드를 사용하여 파일 추가하기
git add -u [<경로>] 수정되고 추적되는 파일의 변경 사항 staging하기

사실은 위의 것 말고도 조금 다른 방법이 있다. 바로 와일드카드이다.

파일을 추가할 때 .py 파일을 전부 추가하고 싶다고 하자. 그러면 다음과 같이 쓸 수 있다.

git add first.py
git add second.py

그러나 이는 귀찮을 뿐더러 빠트리는 경우도 얼마든지 있을 수 있다. 이럴 땐 * 를 사용한다.

git add *.py

이를 사용하면 .py로 끝나는 모든 파일이 staging area에 추가된다.

표에서 위쪽 세 종류의 명령은 어려운 부분이 아니므로, 다른 옵션을 설명하겠다.

git diff

2nd-branch로 이동한다. master에는 직접 수정을 가하지 않는다.

git checkout 2nd-branch

그리고 second.py를 수정한다. 최종 결과물은 다음과 같다.

print('1st')
print("Why don't you answer me, git?")

print('2nd')
print("This is the 1st sentence written in 2nd-branch.")

print('3rd')
print('4th')

참고로 모든 파일의 마지막 줄에는 빈 줄은 추가해 두는 것이 commit log를 볼 때 편하다. 이유는 마지막에 빈 줄만 추가하고 staging시켜 보면, 마지막 줄의 내용을 삭제한 후 마지막 줄의 내용 그리고 빈 줄을 추가한 것처럼 나오기 때문이다.

07_diff

별로 깔끔하지 않기 때문에 빈 줄을 추가하라. commit log 볼 때뿐만 아니라 나중에 편집할 때에도 조금 더 편하다.
IDE에 따라서는 빈 줄이 없으면 경고를 띄워 주기도 한다.

여기서 다음 명령을 입력하면 지금까지 어떤 수정사항이 있었는지 볼 수 있다.

git diff

07_diff_2

git diff는 아무 옵션 없이 입력하면 staging area에 반영되지 않은 수정사항을 보여준다.
git diff HEAD와 기능이 같다.

diff 역시 많은 옵션이 있는데, 간략히 살펴보도록 하겠다.

local branch 간 비교는 git diff [<branch1>] <branch2>와 같이 한다. 브랜치명을 하나만 쓰면 현재 local branch와 비교한다.
물론 remote branch와의 비교도 가능하다.

git diff 3rd-branch
git diff 2nd-branch origin/2nd-branch

커밋간 비교도 가능하다. git diff [<commit1>] <commit2>
역시 첫번째를 생략하면 현재 상태와 비교한다.

git diff 317200f

다른 옵션들은 나중에 설명하도록 하겠다. 일단은 여기까지만 하자.

git add -p [<파일>]

그리고 다음 명령을 입력한다. 지금은 second.py만 수정했기 때문에 해당 파일만 추가한다.
조금 위의 표에서 봤듯이 -p 옵션은 파일의 일부만을 staging(staging area에 올리는 것)하는 과정이다.
-p--patch의 단축 옵션이다.

git add -p second.py

그러면 다음과 같이 뜬다.

08_patch

  • 초록색 줄은 추가된 줄, 빨간색 줄은 삭제된 줄이다.
  • 파일의 일부분만을 추가하는데, 모든 한 줄마다 따로 추가하는 것이 아니라 hunk라는 덩어리로 한 번에 staging area에 추가할지 말지를 결정한다. 만약에 git이 나눠준 hunk가 너무 크다면, s를 입력하여 더 잘게 쪼갠다. 위의 경우는 너무 크기 때문에, 잘게 쪼갤 것이다.
  • 만약에 무슨 옵션이 있는지 궁금하다면 ?를 입력하라. 도움말이 표시된다.
명령 설명
y stage this hunk
n do not stage this hunk
q quit; do not stage this hunk or any of the remaining ones
a stage this hunk and all later hunks in the file
d do not stage this hunk or any of the later hunks in the file
g select a hunk to go to
/ search for a hunk matching the given regex
j leave this hunk undecided, see next undecided hunk
J leave this hunk undecided, see next hunk
k leave this hunk undecided, see previous undecided hunk
K leave this hunk undecided, see previous hunk
s split the current hunk into smaller hunks
e manually edit the current hunk
? print help

s, y, n, y를 차례대로 입력한다. s를 입력하면 3개의 hunk로 분리되었다고 알려 준다.

09_hunk

참고:

  1. untracked files는 -p를 할 때 나오지 않는다. 새로 추가된 파일이라면 먼저 staging area에 올린 후, 수정한 파일만을 p 옵션으로 처리하라.
  2. 디버깅을 위해 넣어 놓은 print문 등을 제거하고 push할 때 유용하다.
  3. 파일명으로 추가하는 대신 *.를 쓰는 것도 가능하다.

git add -i [<파일>]

대화형으로 파일 수정사항을 staging area에 추가하는 방법이다. first.py를 수정하자.

print("Hello, git!") # instead of "Hello, World!"
print("Hi, git!!")

print('1st')

print("This is the 1st sentence written in 1st-branch.")
print("This is the 1st sentence written in 3rd-branch.")

print('2nd')

그리고 git add -i first.py를 입력한 다음, 다음 그림대로 따라 해 보자.
중간쯤에 아무것도 안하고 Enter만 입력한 곳이 있는데, 이렇게 하면 선택한 파일들이 staging area에 추가된다.

10_i

파란색으로 강조된 부분을 잘 따라가면 이해하기 어렵진 않을 것이다.

위와 같이 하면 first.py 파일이 staging area에 추가된다.

git add -u [<경로>]

git add .git add -u는 하는 일이 비슷하지만, 차이점은 다음과 같다.

  • git add .는 현재 디렉토리의 모든 변경사항을 staging area에 추가한다. untracked files를 포함한다.
  • git add -u는 업데이트(‘u’)된 파일, 즉 untracked files는 제외하고 staging area에 추가한다.

뒤에 경로를 추가하면 해당 디렉토리 혹은 파일들에 대해서만 위의 작업을 수행한다.

아무 파일이나 하나 추가한 다음 차이를 확인하자. first.py 파일 끝에 다음을 추가하고, dummy1.txt 파일을 생성만 하자.

print('test git add .')

11_short

-s 옵션을 줄 때 ‘??’는 untracked files를 의미한다.

이제 git add .로 staging area에 추가해 보자.

12_add

모두 추가되었다.

이제 조금 전과 비슷하게 second.py 파일의 끝에 다음을 추가하고 dummy2.txt 파일을 생성만 하자.

print('test git add -u')

13_u

untracked files 상태인 dummy2.txt는 여전히 추가되지 않은 상태로 남아 있는 것을 볼 수 있다.


git commit: 중급

git commit에도 옵션은 굉장히 많으나, 여기서는 -v 옵션과 tag 두 가지만 설명한다.

-v 옵션

-v 옵션은 git add-p와 비슷하다. 즉 수정사항을 미리 볼 수 있는데, git diff를 밑에 보여주는 것과 비슷하다.

git commit -v

15_v

i 입력 후, 커밋 메시지는 적당히 입력하고 ESC, :wq 를 입력하라. 무엇인지 까먹지 않았기를 바란다.

tag

commit에는 태그를 붙일 수 있다. 여러분이 블로그에서 볼 수 있는 그 태그와 같은 기능이다.

태그에는 두 종류가 있는데, 단순 태그 기능만 하는 Lightweight 태그와 태그를 만든 사람, 시간, 메시지, 서명 정보 등을 저장하는 Annotated 태그가 있다.

먼저 Lightweight 태그는 다음과 같이 붙일 수 있다. 뒤에 commit 코드를 명시하지 않으면 현재 commit에 붙는다.

git tag v0.7

버전 정보를 저장할 때 태그로 하면 편하다.

현 repo의 태그 목록을 확인하려면 다음을 입력한다.

git tag

태그의 정보를 자세히 확인하고 싶다면 다음과 같이 입력한다.

git show v0.7

14_tag

태그를 삭제하려면 -d 옵션을 붙인다.

git tag -d v0.7

Annotated 태그를 붙일 때에는 -a 옵션을 사용한다. 메시지를 입력하려면 -m 옵션을 붙인다.
메시지를 입력하지 않으면 일반적인 커밋 메시지를 쓰지 않았을 때처럼 vim 편집기가 열린다.

git tag -a v0.7 -m “git tutorial ver 0.7”

뒤에 commit 코드를 명시할 경우 이전 커밋에도 태그를 붙일 수 있다.

git tag v0.5 90ce4f2

당연하게도 여러분이 직접 커밋을 만들었다면 커밋 코드는 다를 것이다. git log로 먼저 확인 후 원하는 커밋에 태깅하도록 한다.

16_tag

그림에서 커밋 로그에 ‘tag: v0.5’ 등이 생겼음을 확인하라.

이제 push를 하자.

git push

그러나 태그는 git push명령에 자동으로 remote repo에 올라가지 않는다.
태그는 따로 올리면 된다.

git push origin v0.5
git push origin –tags

--tags 옵션은 모든 태그를 remote repo에 올린다.


.gitignore 중급

사실 .gitignore 사용법은 어렵지 않다. 파일을 제외하거나, 디렉토리를 제외하거나, 와일드카드를 사용하여 여러 파일을 staging area에 올라가는 것을 막는 것뿐이다. 각각은 다음과 같이 사용한다.
.gitignore 파일의 각각 다른 줄에 추가하면 된다.
이 내용은 .gitignore 파일에 추가하지 않아도 된다.

dummy_txt
data/
*.tar
data/raw/*
*dummy*

대신 한 가지 흔히 하는 실수를 다루도록 하겠다. 데이터 파일이나 설정 파일, IDE가 자동으로 생성한 파일 등은 git add .를 생각없이 사용하다 보면 어느새 remote repo에 올라가 있는 경우가 많은데, 이 파일은 나중에 .gitignore 파일에 추가해도 repo에서 자동으로 사라지지 않는다.

예제를 하나 갖고 왔다. 우선 git_tutorial 디렉토리를 PyCharm IDE로 열거나, 아니면 .idea/라는 디렉토리를 하나 만들어 보자.

PyCharm으로 열면 자동으로 .idea/라는 디렉토리가 생겨버린다.

18_status

이를 그냥 생각없이 추가하면 직접 생성하지도 않은 수많은 파일들이 추가된다.

19_add

그리고 한번 remote repo에 올려 보자.

git commit -m “Doong!”
git push

이건 그다지 좋은 상황이 아니기 때문에, remote repo에서 제거하려고 한다. 서둘러 .gitignore 파일에 다음을 추가한다.

.idea/
*dummy*

그리고 3종 세트를 입력하자.

git add .gitignore
git commit -m “edit .gitignore: remove .idea/ directory”
git push

20_browser

물론 앞서 말한 대로, 자동으로 지워지지 않는다.

이럴 때는 어디선가 본 듯한 명령을 사용한다.

git rm –cached .idea/ -r
git rm –cached *dummy*

디렉토리를 제거하려고 할 때는 -r 옵션을 사용한다.

21_rm

이제 push를 한다. git add 명령은 필요없다.

22_push

이와 같은 실수를 막기 위해서는, 아예 .gitignore 파일에 git에 올라가지 말아야 할 파일을 정리해 두는 것도 괜찮다.
추천하는 것은 데이터 파일, 설정 파일, 패키지, 압축 파일 등이다.

PyCharm 기준으로는 구글링을 하면 적당히 쓸 만한 설정 파일을 구할 수도 있다.


이제 master branch에 merge를 하자.

git checkout master
git merge 2nd-branch
git push


다음 글에서는 대망의 conflict에 대해서 알아본다. 여러 사람이 작업 분할을 충실하게 하지 않는다면 필히 만나게 될 것이다.
물론 혼자서도 만들 수도 있다.


Git 명령어

GitHub 사용법 - 00. Command List에서 원하는 명령어를 찾아 볼 수 있다.

Comment  Read more