2017년 11월 2일 목요일

LSTM 네트워크 이해하기


 이 글은 http://colah.github.io/posts/2015-08-Understanding-LSTMs/ 를 번역하여 정리한 글입니다.
-------------------------------------------------------------------------
 RNN(Recurrent Neural Network)은 히든 노드가 방향을가진 엣지로 연결되어 순환 구조를 이루는 Neural Network의 한 종류이다. RNN이 기존 Neural Network와 다른 점은 지금 들어온 입력 데이터와 과거에 입력 받았던 데이터를 동시에 고려한다는 점이다.
순환 신경망은 내부에 정보가 지속되는 것을 돕는 순환 구조를 가진 네트워크이며, 과거의 데이터가 미래에 영향을 주는 구조를 가지고 있다.
[그림 1] RNN의 기본구조


 [그림 1]에서 신경망 A는 $X_t$를 입력 값으로 가지고 $h_t$를 결과 값으로 출력한다. 루프는 정보가 전 단계의 네트워크에서 그 다음 단계로 전달되도록 한다. 순환 신경망은 하나의 네트워크가 여러 개로 복사된 형태를 보이고 있는데, 각각의 네트워크는 현재 갖고있는 정보를 다음 단계로 넘겨준다.



[그림 2] 펼쳐진 순환 신경망
 
 [그림 2]는 [그림 1]의 순환 구조를 펼쳐 놓은 것이다. RNN의 사슬 같은 모양을 통해 RNN이 시퀀스와 리스트를 처리하는 것과 관련이 있음을 알 수 있다. 따라서 순환 신경망은 연속적인 이벤트, 리스트에 관한 문제를 해결하기 위한 알고리즘으로 적절하기 때문에 시계열 모형 수립에 활용할 수 있다.
순환 신경망의 단점 중 하나는 정보가 오래된 정보를 현재 정보에 반영할 수 없다는 점이다. 이를 해결하기 위해 나타난 것이 LSTM이다. LSTM의 기본 동작 원리는 정보를 오랫동안 기억하는 것이며 [그림 3]은 LSTM의 구조를 나타낸 그림이다.


[그림 3] LSTM의 구조

[그림 3]에서 보이는 것처럼 LSTM에는 특별한 방식으로 상호작용하는 네 개의 층이 있다. 그 중에서 [그림 4]에 보이는 다이어그램의 위쪽을 통과해 지나가는 수평선인 cell state는 LSTM의 핵심 아이디어이다.
[그림 4] Cell State
 
 Cell state는 이전 노드에서 발생한 정보를 전달하는 일종의 컨베이어 벨트라고 생각하면 된다. cell state를 통해 전달되는 정보는 약간의 선형 상호작용(Linear Interaction)을 거치기 때문에 정보가 크게 변형되지 않은채로 전달 된다. LSTM은 cell state의 정보를 추가하거나 지우는데, 이러한 작업은 게이트라 불리는 구조에서 조절되며, 게이트에 의해 선택된 정보들만 게이트를 통과할 수 있다. [그림 5]에서 보는 것처럼 게이트는 시그모이드 신경망 층과 각 요소 별 곱셈 연산으로 구성된다.

[그림 5] LSTM의 게이트

시그모이드 층의 아웃풋은 0에서 1사이의 값이다. 값이 1일 경우 모든 정보를 통과시키고 0일 경우 어떤정보도 통과시키지 않는다는 것을 의미한다. [표 1]은 LSTM에서 사용되는 기호들에 대해 나타낸 표이다. 

[1] LSTM에 사용되는 기호

기호
설명
$x_t$
입력 벡터
$h_t$
출력 벡터
$C_t$
Cell state
$f_t$
forget 게이트의 출력 값
$i_t$
input 게이트의 출력 값
$o_t$
output 게이트의 출력 값

 LSTM에는 cell state를 제어하기 위해 forget, input, output 세 종류의 게이트가 있다. [그림 6]은 LSTM의 첫 단계인 forget 게이트에 대한 그림이다.

[그림 6] LSTMforget 게이트

 forget 게이트는 이전 노드에서 넘어온 정보($h_(t-1)$과 $x_t$ )를 얼마나 반영할 지 결정하는 시그모이드 층이다. 이 시그모이드 값은 0에서 1사이의 값을 갖는다.

[그림 7] LSTMinput 게이트
[그림 7]은 LSTM의 두 번째 단계인 input 게이트에 대한 그림이다. input 게이트는 어떤 정보를 저장할 지 결정하는 역할을 하는데, 이 단계는 시그모이드 층과 tanh층 두 단계로 구성되어 있다. 시그모이드 층은 어떤 값을 갱신할지 결정하는 역할을 하고, tanh층에서는 cell state에 추가될 수 있는 후보 벡터인 $C_t$를 생성한다.

[그림 8] LSTM cell state 갱신

  이제 오래된 cell state $C_t-1$을 새로운 cell state $C_t$로 갱신할 차례이다. [그림 8]은 cell state를 갱신하는 과정을 나타낸 그림이다. 갱신 과정은 이전 노드에서 넘어온 cell state $C_t-1$에 forget 게이트의 출력 값인 $f_t$를 곱한다.  그 다음 input 게이트에서 계산된 cell state를 갱신하기 위한 값인 $i_t * C_t$를 더해주면 $C_t$로 갱신 된다.

[그림 9] LSTM 노드별 출력값 설정

 마지막 단계는 무엇을 출력할 지 결정하는 단계이다. [그림 9]는 LSTM 노드의 출력 값을 결정하는 단계이다. 출력 값은 cell state $C_t$의 여과된 값을 기준으로 한다. 먼저 시그모이드 층이 작동되고 cell state의 값을 $tanh$층에서 -1 ~ 1 의 갑승로 바꾼다. 그 다음 원하는 값만 출력할 수 있도록 $tanh$ 층의 출력 값과 시그모이드  층의 출력 값을 곱해주면 다음 노드로 넘겨 줄 $h_t$ 값이 나온다.

 

2017년 3월 3일 금요일

[알고리즘] 파이썬으로 Trie 구현하기(2)

 1편에 이어 2편이다. 지난 장에서는 trie의 간단한 개념과 생성과 입력 기능에 대해 알아보았다. 이번 편에서는 조회와 삭제에 대해 알아보도록 하겠다.

접두어로 검색


 Trie의 가장 큰 장점이라고 생각하는 접두어 검색 기능이다. 단어의 일부분으로 전체단어를 검색할 수 있다. 예를들어 trie에 gold, good, gerald를 저장한 다음 검색어에 g를 입력하면 gold, good, gerald를 모두 검색할 수 있다. 이런 기능은 검색어 자동완성에 쓰인다고 한다.
우선 검색 부분의 코드를 보도록 하겠다.

    def search_with_prefix(self, prefix):
        words = list()

        if prefix is None:
            raise ValueError('Requires not-Null prefix')

        top_node = self.head

        for letter in prefix:
            if letter in top_node.children:
                top_node = top_node.children[letter]
            else:
                return words

        if top_node == self.head:
            queue = [node for key, node in top_node.children.items()]
        else:
            queue = [top_node]

        while queue:
            current_node = queue.pop()

            if current_node.data is not None:
                words.append(current_node.data)

            queue = [node for key, node in current_node.children.items()] + queue

        return words

 조회 부분의 원리는 간단하다. 입력한 접두어가 저장되어 있는지 확인하고 해당 접두어가 있다면 그 접두어를 포함하고 있는 모든 단어를 반환해주는 함수이다.
 여기서 queue 부분이 이해가 안갈수도 있을것 같다. 일단 queue 부분은 items()를 이용해 dict의 key, value 값, 즉 노드의 정보를 list에 담아오는 코드이다. 즉, 검색어로 입력한 단어를 포함하는 모든 단어를 찾기위해 검색어로 입력한 이후부분에 해당하는 모든 노드를 찾기 위해 노드 정보를 가져오는 것이다. 그 다음 queue.pop()을 이용해 리스트에 담겨져있는 노드정보를 꺼내 current_node에 저장하고 current_node에 데이터가 있으면 words라는 리스트에 붙이는 과정이라고 이해하면 된다.

삭제



    def delete_word(self, word, depth=0):
        current_node = self.head

        for letter in word[:-1]:
            if letter not in current_node.children:
                return False
            current_node = current_node.children[letter]

        if current_node.NodeCount > 1:
            del current_node.children[word[-1]]
            return True

        if word[-1] in current_node.children:
            del current_node.children[word[-1]]
            self.delete_word(word[:-1], depth)
            return True

        return False


 그 다음은 삭제이다. 삭제는 삭제하고자 하는 단어를 입력하면 그 단어가 있는지 확인하고 공통 접두어가 있는 부분까지 삭제하는 방식이다. 이때 NodeCount가 사용이 되는데 NodeCount가 2 이상이면 자식 노드가 2개 이상이라는 뜻이기 때문에 그 노드에서 삭제를 멈추게하는 방식이다. 하지만 이 삭제 함수의 문제가 하나 있는데 stand와 standard를 구분하지 못한다는 것이다. standard를 삭제하면 stand는 남아있지만, stand를 삭제하면 standard도 삭제한다는 단점이 있다. 이 부분은 차후에 수정을...


if __name__ == '__main__':
    trie = Trie()

    trie.insert_word('good gerald gold')

    print(trie.search_with_prefix('g'))

    trie.delete_word('gold')
    print(trie.search_with_prefix('g'))

이제 작성한 코드를 확인해 볼 시간이다. good gerald gold라는 단어를 trie에 저장하고 g를 이용하여 검색을 해볼 것이다. 그다음 gold라는 단어를 삭제하고 다시 g로 검색을 하는 과정이다.


 출력 결과는 위와 같다. 처음에는 세 단어가 모두 출력되지만 gold를 삭제한 후에는 good과 gerald만 출력된다.



2017년 2월 28일 화요일

[책 리뷰] 지리의 힘


 이 책 원문 제목은 지리의 죄수들(Prisoners of Geography)이다. 음 그냥 영어로 읽었을 때 무슨 책 제목을 지리의 힘 따위로 번역하나 했는데 직역해보니 지리의 힘이 훨씬 나은 것 같다. 이 책에서 하는 얘기는 간단하다. 현재 일어나고 있는 국가 간의 분쟁, 국가의 흥망성쇠는 지리적인 영향 때문이라는 것이다. 미국이 세계 최고의 강대국이 된 것도, 중국이 현재 취하고 있는 국제적인 행동을 지리적인 관점에서 해석한 책이다. 물론 지리적인 영향이 있기전에 인간의 선택이 있기는 하다. 땅을 차지하고 나서보니 지리적인 이점을 누릴 수 있는 곳인 경우도 있고, 지리적 이점을 취하기 위해 그 땅을 차지한 경우도 있다.

 그리고 지리적인 요소에 가장 영향을 주는 것은 당연히도 '물'이다. 일차적으로는 강이 제일 중요한 요소고 그 다음은 바다이다. 강은 나라의 번영에도 그리고 자국 영토에도 매우 중요한 요소이다. 먼 과거뿐만 아니라 오늘날에도 말이다. 큰 강 유역을 끼고 있는 나라들은 전부 번성했다. 물론 수원지로부터 바다로 흘러가기 전까지 강 유역의 고도 변화가 심하지 않는다는 조건이 있지만.... 이 조건에 만족하지 못하는 나라는 큰 강을 끼고있어도 그 혜택을 누리지 못하는데 그 대표적인 예가 브라질이다. 브라질은 아마존이라는 큰 강을 갖고 있지만 바다에서 아마존 강을 이용해 내륙으로 들어가는 것이 힘들다고 한다. 그래서 브라질의 대도시는 해안가에 집중되어 있다고 한다. 인위적으로 브라질리아 같은 도시를 내륙에 만들기는 했지만 이런 지리적인 도움을 받지 않는 도시는 한계가 있다고 한다.

 그 다음 중요한 요소인 바다는 타국과의 교류에 필수적인 요소이다. 국경을 맞대고있는 나라가 있을수도 있지만 그 숫자는 한정적이고, 바다를 통해 타국과 교류가 가능한 나라는 자원이 부족한 국가에겐 자원을 수급할 수 있는 통로가 되고 자원이 넘치는 나라에는 그 자원을 통해 배를 불릴 수 있는 통로가 된다. 사실 바다 보다는 항구가 중요한 요소 인것 같다. 긴 해안선이 있다고 한들 배가 드나들 수 있는 항구가 없다면 아무 소용이 없기 때문이다. 그 대표적인 예가 러시아이다. 러시아는 부동항이 없어 항상 부동항을 확보하기위해 노력하지만 결과가 그닥 신통치 않다. 부동항이 있다고하더라고 그 항구들이 타국에 의해 봉쇄될 가능성이 높기 때문에 자유로운 출입이 가능한 부동항의 확보는 러시아의 숙원 사업이다. 중국 또한 마찬가지다. 부동항은 넘쳐나겠지만 대양으로 진출하는 것이 쉽지가 않다. 태평양으로 가는 길목은 일본이 지키고 있고 남쪽은 동남아시아 국가들에게 가로막혀 있어서 중국 또한 해로를 확보하기 위해 혈안이 되어있다고한다.

이 책을 읽고나니 세계 정세에 대해 더 잘 알게된것같다. 지정학적 요소가 모든 것을 설명해 주진 않지만 가장 영향력 있는 요소 중 하나인 것은 확실하기에 지정학적인 요소를 알고 있다는 것은 국제 정세를 이해하는데 큰 도움이 된다고 생각한다.

2017년 2월 24일 금요일

[알고리즘] 파이썬으로 Trie 구현하기(1)


 Trie는 이 구조를 구현한 라이브러리도 여럿있어서 파이썬으로 구현하기는 쉬울 것이다. 하지만 trie에 대해 더 잘 이해하고 연습도 할 겸 여기저기에서 참조해가며 직접 만들어 보았다. 이번에는 trie에 대한 간단한 개념과 문자열의 삽입과 조회 부분의 구현까지 진행할 것이다.

특징 


 Trie는 prefix tree라고도 불리는 트리 구조의 알고리즘이다. Trie는 다음과 같은 특징이 있다.

  1. 검색이 빠르고, 
  2. 문자열을 키로하는 동적 집합이나 연관 배열로 사용되고 
  3. 노드는 키를 갖지 않는 대신 노드의 위치가 키 역할을 하고 
  4. root가 빈 스트링이라는 특징이 있다.

시간 복잡도


 시간 복잡도는 알고리즘의 수행시간 분석결과를 나타내는 용어이다. 당연히 시간 복잡도가 낮을수록 좋으며 연산 횟수를 계산하고, 처리해야 할 데이터의 수 n에 대한 연산횟수 함수 T(n)을 구성하여 구한다.
 Trie의 시간 복잡도는 대표적인 트리 구조 중 하나인 이진 검색 트리(Binary Search Tree)와 비교를 해보도록 하겠다.


데이타 구조시간 복잡도
(정수/실수) 이진 검색 트리O(logN)
문자열 이진 검색 트리O(MlogN)
트라이O(M)

  문자열은 길이가 변하기 때문에 검색 시간이 많이 소요된다. 길이가 고정된 정수나 실수는 O(logN)의 시간이 걸리지만 문자열은 길이가 변하기 떄문에 문자열의 최대 길이 M을 곱한 O(MlogN)이 된는데 trie는 이러한 문제를 해결하기 위해 고안된 자료구조이다.

트라이의 구조



 알파벳만을 사용하여 trie를 구성할 경우 각 노드는 26개의 알파벳과 1개의 공백으로 구성된 27개의 포인터를 갖고있는 배열을 갖고 있다. 
 문자열 집합 : S = {"BE", "BET", "BUS", "TEA", "TEN"}을 trie로 구성한 그림은 다음과 같다.




 루트는 빈 노드이며 부모노드에 접미 문자를 하나씩 붙여 자식 노드를 구성한다. 노드와 노드를 연결하는 edge는 접미문자가 붙는 것을 나타낸다. 그리고 회색으로 표시된 노드는 종료 노드를 나타낸다. 루트에서 해달 종료 노드까지의 문자를 모으면 원하는 검색어를 발견할 수 있다.

구현


우선은 노드 부분에 대한 내용이다.

import collections


class Node:
    def __init__(self, label=None, data=None):
        self.label = label
        self.data = data
        self.children = collections.defaultdict(Trie)
        self.NodeCount = 0

    def add_child(self, key, data=None):
        if not isinstance(key, Node):  # key가 Node의 instance가 아니면
            self.children[key] = Node(key, data)
        else:
            self.children[key.label] = key

    def __getitem__(self, key):
        return self.children[key]

    def __str__(self, depth=0):
        s = []
        for key in self.children:
            s.append('{}{} {}'.format(' ' * depth, key or '#', '\n' 
                                      + self.children[key].__str__(depth + 1)))

        return ''.join(s)

 collections 라이브러리를 임포트하고 Node라는 클래스를 생성한다. Node 클래스의 생성자에 있는 label과 data는 각각 edge와 노드에 저장되는 데이터를 의미한다. NodeCount는 자식노드의 숫자를 의미한다. 막 생성된 노드는 자식이 없기 때문에 0으로 설정한다. add_child 함수는 말 그대로 부모노드에 자식노드를 추가하는 함수의 역할을 한다.

 __str__()은 trie 구조를 출력하는 용도로 사용된다. Trie를 출력하는 방식에 대해 찾아볼 때 대부분 중괄호({, })와 콤마(,) 그리고 _end_를 사용하여 나타내는 방식이었다. 개인적으로 출력되는 모양이 알아보기 힘들고 지저분해 보이던 와중에 위 코드와 같은 방식으로 나타내는 것을 발견하였는데 개인적으로 참 마음에드는 코드이다. 방식은 children에 저장된 모든 key값을 받아와서 띄어쓰기와 개행으로 trie를 표현했는데 각 문자열의 마지막에는 #을 추가해 줘서 내가 봤을때는 굉장히 구조를 파악하기 수월했다.


class Trie:
    def __init__(self):
        self.head = Node()

    def __getitem__(self, key):
        return self.head.children[key]

    def __str__(self, depth=0):
        return self.head.__str__()

    def add(self, word):
        current_node = self.head
        word_finished = True

        for i in range(len(word)):
            if word[i] in current_node.children:
                current_node = current_node.children[word[i]]
            else:
                word_finished = False
                break

        if not word_finished:
            while i < len(word):
                current_node.add_child(word[i])
                current_node.NodeCount += 1
                current_node = current_node.children[word[i]]
                i += 1

        current_node.add_child(None)
        current_node.NodeCount += 1
        current_node = current_node.children[None]
        current_node.data = word

    def insert_word(self, word):
        for word in word.split():
            self.add(word)



 이제 trie를 구현하는 부분이다. 원리는 간단하다. 기존에 있는 문자열이 있는지 확인하고 기존에 없는 문자열들만 새로 자식노드에 추가해주는 방식으로 문자열의 삽입이 진행된다.자식노드가 추가되면 NodeCount의 숫자를 증가시켜 해당 노드에 몇개의 자식이 있는지 파악한다. insert_word 함수는 입력받은 문장을 공백을 기준으로 단어별로 나누어서 add함수에 넣어주는 역할을 담당한다.


if __name__ == '__main__':
    trie = Trie()
    trie.insert_word('stan stem standard money')

    print(trie)

 마지막으로 실행 부분이다. insert_word 함수를 통해 stan, stem, standard, money를 입력한 결과를 출력하면



위 그림과 같은 결과가 나온다. stan과 standard을 보면 stan은 standard의 부분집합(?)과 같은 단어이다. #이 없으면 stan이 없으면 출력 화면상에서 stan이 삽입이 되어있는지 확인할 방법이 없을것이다. 하지만 #이 있기 때문에  두 단어가 구분이 된다.

---------------------------------------------------------------------------------------------------

출처

- https://nickstanisha.github.io/2015/11/21/a-good-trie-implementation-in-python.html
- http://clojure.or.kr/wiki/doku.php?id=study:algorithms:trie
- Laboratory Module D, TRIE TREES

2017년 2월 19일 일요일

[Linux] Vim 설정

[펌글]보통 많은 사람들이 Vim 을 그대로 사용하지 않고 여러가지 설정을 해준다. root 계정을 사용하고 있다면 vi /root/.vimrc 를 쳐서 다음과 같은 내용을 입력해준다. (물론 원하는 것만) 여기서 " 는 주석으로 꼭 입력할 필요는 없습니다. 

set tabstop=2  "탭 간격을 2 칸 으로 지정한다
set shiftwidth=2 " >>나 << 사용시 들여쓰기 간격을 지정한다
set expandtab " 탭 문자를 공백문자로 변환한다. 
set softtabstop=2 "탭 간격을 공백문자로 변환하면 두 칸 단위로 삭제한다 
set visualbell " 사용자 실수 경고시 비프음 대산 화면을 한 번 반짝인다. 
set nobackup "백업 파일을 생성하지 않는다
set cindent "C 언어 스타일의 들여쓰기를 사용합니다. 
set autoindent "자동 들여쓰기를 사용합니다.
set smartindent "좀 더 지능적인 들여쓰기를 사용합니다. 
set enc=euc-kr "인코딩을 한글로 지정합니다. 
set incsearch
"키워드 입력시 검색하는 점진 검색을 사용합니다.  (파이어폭스서 사용)

syntax on "구문 강조기능을 사용합니다
filetype on "파일 종류에 따라 구문을 강조합니다.
set background=dark "배경색을 어두운 색으로 설정합니다.
colorscheme evening "VI 색상 테마를 evening  으로 설정합니다
set backspace=eol,start,indent
"줄의 끝, 시작, 들여쓰기서 백스페이스 사용시 이전 줄과 연결
set history=1000 " VI  편집 기록을 1000개 까지 저장합니다. 
set hlsearch "검색어 강조 기능을 사용합니다. 
set ignorecase "검색, 편집, 치환시 대소문자를 구분하지 않습니다. 
set showmatch "() 과 {} 에서 한 괄호만 입력해도 일치하는 괄호를 보여줍니다

  Vim 을 시작하면 i 를 눌러서 편집모드로 들어갈 수 있고 Esc 나 Ctrl + [ 를 누르면 명령모드로 들어갈 수 있습니다. 명령모드에서는 앞서 말했듯이 저장, 복사, 치환 등의 행동을 할 수 있습니다. 예를들어서 명령모드서 :w 라 치면 이 파일이 저장됩니다. :q 라 치면 종료가 되며 (저장이 되어 있다면), :q! 라 치면 저장의 여부와는 상관 없이 강제 종료됩니다. 아래는 Vim 의 명령어들을 모은 표 입니다. (대부분의 명령어로 모두는 아니다. )

파일 작업시 명령어 
Vim 명령어결과 
:e 파일명새로운 파일을 연다. 커맨드 프롬프트 처럼 Tab 키를 통해 파일이름의 자동 완성 기능을 사용할 수 있다. 
:w 파일명파일을 저장한다. 만약 파일 이름을 지정하지 않는다면 그냥 원래 파일 이름으로 저장이 되고 파일 이름을 지정해 준다면 새로운 파일 이름으로 저장이 된다
:qVim 을 종료한다. 파일을 저장하지 않았자면 종료되지 않는다. 
:q!파일의 저장에 상관없이 종료한다. 
:wq파일을 저장한 후 종료한다. 
:x위의 :wq 와 거의 같지만 여기서 파일이 저장된 이후 기록된 사항이 있으면 저장하지 않고 파일이 저장된 이후 기록된 사항이 있으면 저장하고 종료한다. 
아래의 Vim 명령어는 Visual 모드, 터미널 모드 모두에서 사용 가능합니다. 
Vim 명령어결과
j or 방향키 ↑커서를 한 칸 위로 이동
k or 방향키↓커서를 한 칸 아래로 이동
l or 방향키 →커서를 한 칸 오른쪽으로 이동
h or 방향키 ←커서를 한 칸 왼쪽으로 이동
e단어의 끝으로 이동
E단어의 끝으로 이동
b단어의 첫 부분으로 이동
B단어의 첫 부분으로 이동
0문장의 첫 부분으로 이동
^공백문자가 아닌 문장의 첫 부분으로 이동
$문장의 끝 으로 이동
H화면의 첫번째 라인으로
M화면의 중간 라인으로 이동
L화면의 마지막 라인으로 이동
:nn 번째 라인으로 이동. 예를들어 :10 이면 10 번째 줄로 이동한다.
텍스트의 삽입 몇 편집
Vim 명령어결과
i커서 앞에 쓴다. 
I커서가 가리키는 라인의 앞에 쓴다
a커서 뒤에 추가한다. 
A커서가 가리키는 라인의 맨 뒤에 추가한다. 
o커서가 가리키는 라인 아래에 새 라인을 추가한다. 
O커서가 가리키는 라인 위에 새 줄을 추가한다. 
r한 글자를 바꿔쓴 후 다시 명령모드로 들어간다.
R삽입모드에 들어가지만 글자를 덧쓰면서 이미 쓰여진 글자를 지운다
ESC 삽입/수정 모드에서 빠져나온다. 
텍스트 지우기
Vim 명령어Action
x커서가 가리키는 글자를 지운다
X커서 앞의 글자를 지운다. 
dd or :d현재 커서가 가리키는 라인의 글자를 지운다
비주얼 모드 들어가기
Vim 명령어결과
v단어를 하이라이팅(Highlighting, 블록 설정) 한다. 텍스트를 하이라이팅 하기 위해서 보통의 이동 키를 사용한다. 
V현재 커서가 가리키는 라인을 하이라이팅 한다. 
The ESC key비주얼 모드에서 빠져나와 명령모드로 들어간다. 
블록 텍스트 편집하기 
아래의 명령어는 비주얼 모드에서 텍스트를 블록 설저한 후 명령모드로 들어와 사용할 수 있다. 물론 명령모드에서도 사용할 수 있긴 하다. 
Vim 명령어결과
~글자의 대소문자를 바꾼다 예를들어서 mOvL 은 MoVl 로 바뀐다. 비주얼 모드와 명령모드 둘다 사용할 수 있으며 비주얼 모드에서 블록 처리한 뒤 명령모드로 들어와 ~ 를 누르면 그 블록 설정된 부분의 대소관계가 바뀐다.
> (V)한 칸 오른쪽으로 민다
< (V)한 칸 왼쪽으로 민다.
c (V)블록처리된 텍스트를 바꾼다. 
y (V)블록처리된 텍스트를 복사(yank)한다. 
d (V)블록처리된 텍스트를 잘라내기한다. 
yy or :y or Y커서가 가리키는 라인을 복사한다. 블록 처리할 필요는 엎다. 
dd or :d커서가 가리키는 라인을 삭제한다. 블록 처리할 필요는 없다. 
p복사나 잘라내기 했던 텍스트를 붙여넣기(put) 한다. 
P커서 앞에 잘라내기 했던 텍스트를 붙여 넣는다. 
실행 취소 및 다시 실행
Vim 명령어결과
u실행 취소한다. 
U커서가 가리키고 있던 라인에서 했던 모든 실행들을 취소한다. 
Ctrl + r다시 실행한다. 
Vim 명령어결과
/patternpattern 이란 단어를 검색한다.
n다음 글자를 찾지만 아래방향으로 찾는다. 
N다음 글자를 찾지만 윗 방향으로 찾는다.
치환  (바꾸기) 
Vim 명령어결과
:rs/foo/bar/afoo 를 bar 로 치환환한다. r 은 치환하는 범위를 지정하고 a 는 인자를 지정한다. 아래에 r 과 a 의 종류에 대해 설명되어 있다. 
범위 (r) 의 종류에는
아무것도 쓰지 않을 때범위를 주지 않을 경우 현재 라인에서 치환한다. 
숫자를 쓸 때치환할 라인의 숫자를 쓴다. 
%전체 파일의 모든 라인에 대해 치환한다. 
인자 (a)의 종류에는
g한 라인의 모든 경우를 치환한다. 이 인자를 주지 않을 경우 처음 발견되는 것만을 치환한다.
(이 말은 g 인자를 주지 않고 Hello Hi Hello 에서 Hello bye 로 치환한다면 bye Hi bye  가 아닌 bye Hi Hello 가 된다) 
i검색된 문자들의 대소문자를 무시하고 치환한다. 
(즉, hello 를 hi 로 치환시 Hello, hEllo, HELLO 등과 같은 것들도 치환된다. )
I위 경우를 무시하지 않는다. ( 위 경우서 hello 만 치환된다 )
c모든 치환에 대해 확인한다. 만약 치환할 것이라면 y 를, 치환하지 않을 것이라면 n 을, 이하 모든 부분을 치환할 것이라면 a 를, 치환을 종료하겠다면 q 를 누르면 된다. 
예를들자면... 
:452s/foo/bar/ 452 번째 라인의 첫 번째 foo 를 bar 로 치환한다. 
:s/foo/bar/g현재 라인에 나타나는 모든 foo 를 bar 로 치환한다.
:%s/foo/bar/g전체 파일의 모든 foo 를 bar 로 치환한다. 
:%s/foo/bar/gi위와 같지만 Foo, FOO, FOo, foO 와 같은 것들도 모두 치환된다. 
:%s/foo/bar/gc위위와 같지만 각 치환에 대해 모두 검사를 수행한다.
:%s/foo/bar/c파일의 모든 라인에 대해 각 라인의 첫 번째 foo 만 bar 로 치환하며 각 치환에 대해 검사를 한다. 

---------------------------------------------------------------------------------------------------
출처
http://kevin0960.tistory.com/entry/VIM-Vi-iMproved-%EC%9D%98-%EB%AA%85%EB%A0%B9%EC%96%B4-%EB%AA%A8%EC%9D%8C%20