Writings/Linux kernel2017. 12. 11. 05:22

다음으로 소개할 방법은 cgroup을 이용하는 방법이다.

Cgroup (control group)은 리눅스 커널이 제공하는 시스템의 자원 사용률을 그룹별로 제어하기 위한 방법이다.

이 또한 sysfs 인터페이스를 통해 사용 가능한데, 기존 cgroup v1과 Linux 4.5.x 때부터(정확한지는 모르겠지만 이때쯤) 지원하기 시작한 cgroup v2 두 종류가 있다. 사용방법이 사뭇 다르므로 정확한 용법은 커널 도큐먼트(Documentation/cgroup-v2.txt)를 확인해 보는 것이 좋다. 

일단 아무데나 원하는 위치에 디렉토리를 만들고, 다음과 같이 cgroup sysfs를 마운트하자.


# mkdir cgroup

# mount -t cgroup2 none ./cgroup


해당 마운트포인트에서 ls -l 명령을 실행시키면 다음과 같은 결과가 나올 것이다.


-r--r--r-- 1 root root 0 Dec  9 03:20 cgroup.controllers

-rw-r--r-- 1 root root 0 Dec  9 03:20 cgroup.procs

-rw-r--r-- 1 root root 0 Dec  9 03:21 cgroup.subtree_control


여기서 cgroup.controllers는 현재 cgroup v2가 사용가능한 컨트롤러들을 뜻한다. 부트 파라미터를 통해 cgroup v1을 효과적으로 disable 했다면, (혹은 애초에 cgroup v1을 사용하지 않는다면) 다음과 같은 결과를 볼 수 있다.


# cat cgroup.controllers

io memory pids


I/O와 memory, 그리고 pid에 대한 컨트롤러를 제공하며, 이 말은 cgroup 인터페이스를 통해 원하는 그룹에 저 세 가지 자원에 대한 제한을 걸 수 있다는 뜻이 된다.

cgroup.procs 는 현재 해당 컨트롤 그룹에 속해있는 프로세스를 의미하며, 현재 시스템에 등록되어 동작하고 있는 대부분의 프로세스가 여기에 등록되어 있을 것이다. 즉, 마운트포인트의 루트 디렉토리는 시스템의 루트 컨트롤 그룹에 대한 정보가 등록되어 있는 것이다. 

cgroups.subtree_control 은 하위 컨트롤 그룹에 대해 어떤 컨트롤러를 사용할 것인지 지정하는 것이다. 현재는 아무런 정보도 없을 것이다.


이제 새로운 컨트롤 그룹을 생성할 시간이다. 아래의 명령을 입력해보자.


# mkdir cgroup_child


생성된 디렉토리 아래로 가게 되면, 부모 디렉토리와 마찬가지로 cgroup.controllers, cgroup.subtree_control, cgroup.procs, 그리고 cgroup.events 파일이 존재할 것이다. 

생성된 그룹에 프로세스를 추가하기 위해서는, 해당 프로세스의 pid를 알아내어 다음과 같이 입력하면 된다.


# echo "pid" > ./cgroup/cgroup_child/cgroup.procs


그리고 원하는 컨트롤러를 "부모" 디렉토리의 cgroup.subtree_control에 입력하자. 여기서는 페이지 캐시 사용량을 제한하려고 하니, memory 컨트롤러를 입력하면 된다.


# echo "+memory" > ./cgroup/cgroup.subtree_control


여기서 +는 해당 컨트롤러를 추가한다는 의미이며, -로 입력할 경우 해당 컨트롤러를 제거한다는 의미가 된다. 

해당 명령을 수행하면, 자녀 디렉토리에 다음과 같은 파일들이 추가로 생성된다.


-r--r--r-- 1 root root 0 Dec  9 03:21 cgroup.controllers

-r--r--r-- 1 root root 0 Dec  9 03:21 cgroup.events

-rw-r--r-- 1 root root 0 Dec  9 03:21 cgroup.procs

-rw-r--r-- 1 root root 0 Dec  9 03:21 cgroup.subtree_control

-r--r--r-- 1 root root 0 Dec  9 03:21 memory.current

-r--r--r-- 1 root root 0 Dec  9 03:22 memory.events

-rw-r--r-- 1 root root 0 Dec  9 03:21 memory.high

-rw-r--r-- 1 root root 0 Dec  9 03:22 memory.low

-rw-r--r-- 1 root root 0 Dec  9 03:21 memory.max

-r--r--r-- 1 root root 0 Dec  9 03:21 memory.stat

각각의 의미는 다음과 같다.


 File 

Description 

 memory.current

컨트롤 그룹의 현재 메모리 사용량 

 memory.high

메모리 사용량의 soft limit 

 memory.low

메모리 사용량의 하한 

 memory.max

메모리 사용량의 hard limit 

 memory.events 

low, high, max, oom 에 대한 이벤트 횟수

 memory.stat

컨트롤 그룹의 메모리 사용량 통계 


메모리 사용량이 soft limit(memory.high)에 도달할 경우, 해당 그룹의 프로세스들을 throttling 하며, memory 회수를 빈번하게 수행시키기 위한 압박을 주게 된다. 만약, 메모리 사용량이 hard limit (memory.max)를 넘어서게 될 경우, 커널은 OOM killer를 호출하여 해당 프로세스를 종료시키게 된다.

두 항목에 적절한 값을 설정하고, free 명령을 통해 메모리 사용량 변화의 추이를 살펴보자.

root@ubuntu:/home/ubuntu# free -c 10

              total        used        free      shared  buff/cache   available

Mem:       65722960      410052    59913708        9736     5399200    64511200

Swap:      33326076           0    33326076


              total        used        free      shared  buff/cache   available

Mem:       65722960      410612    59913580        9736     5398768    64510436

Swap:      33326076           0    33326076


              total        used        free      shared  buff/cache   available

Mem:       65722960      410908    59913108        9736     5398944    64510268

Swap:      33326076           0    33326076


              total        used        free      shared  buff/cache   available

Mem:       65722960      411000    59913268        9736     5398692    64510304

Swap:      33326076           0    33326076


              total        used        free      shared  buff/cache   available

Mem:       65722960      410824    59913176        9736     5398960    64510368

Swap:      33326076           0    33326076


              total        used        free      shared  buff/cache   available

Mem:       65722960      410348    59913612        9736     5399000    64510896

Swap:      33326076           0    33326076


              total        used        free      shared  buff/cache   available

Mem:       65722960      410980    59913340        9736     5398640    64509924

Swap:      33326076           0    33326076


              total        used        free      shared  buff/cache   available

Mem:       65722960      410580    59913672        9736     5398708    64510560

Swap:      33326076           0    33326076


              total        used        free      shared  buff/cache   available

Mem:       65722960      411080    59912944        9736     5398936    64509680

Swap:      33326076           0    33326076


              total        used        free      shared  buff/cache   available

Mem:       65722960      410504    59913820        9736     5398636    64510612

Swap:      33326076           0    33326076


모든 가용영역을 캐시용도로 쓰던 과거에서 벗어나, 제한된 만큼 사용하는 것을 확인할 수 있다.


엄밀히 따지자면, 이 방법은 페이지 캐시를 제한하기 위해 존재하는 방법은 아니다.

하지만 내 용도가 페이지 캐시를 제한하는 것 이었던 만큼 이 항목으로 포스팅을 남긴다.

'Writings > Linux kernel' 카테고리의 다른 글

페이지 캐시의 크기 제한걸기 (1/2)  (0) 2017.12.10
Enabling Cgroup v2  (0) 2017.12.09
Posted by 곰푼
Writings/Linux kernel2017. 12. 10. 00:10

리눅스 커널은 I/O 성능을 높이기 위해 페이지 캐시를 제공한다. 


페이지 캐시는 파일 시스템의 각 오퍼레이션들에 삽입되어 (엄밀히 말하자면 VFS 인터페이스겠지만,) 블록 디바이스의 데이터 페이지를 메모리에 캐싱해 둔다. 

이후 요청이 들어왔을 때 해당 블록 어드레스에 대한 페이지가 메모리 내에 있을 경우, 캐시 히트로 간주하여 디바이스에 대한 I/O를 발생시키지 않고 메모리에 캐싱되어 있는 페이지를 제공한다. 

Write가 발생했을 때도 이를 바로 블록 디바이스로 보내는 것이 아니라  메모리에 캐싱해두며, 특정 시점 혹은 fsync() 호출이 발생할 때까지 동일한 블록 어드레스에 대한 모든 write를 흡수한다. 


이런 캐시는 I/O가 빈번이 발생하는 경우에는 도움이 되겠지만, 커널의 메모리 사용량을 늘리는 결과를 야기시키기도 한다.


커널은 이를 제한할 수 있는 인터페이스를 sysfs를 통해 제공하게 되는데, 그 안을 보면 꽤 많은 항목들이 있다. 일단 경로는 다음과 같다.


/proc/sys/vm


이 디렉토리 중, 다음의 두 항목을 살펴보자.


/proc/sys/vm/drop_caches

/proc/sys/vm/vfs_cache_pressure


drop_caches는 입력에 따라 서로 다른 수준으로 시스템의 캐시를 비워주게 된다. 


1) echo 1 > /proc/sys/vm/drop_caches // 페이지 캐시만을 비운다.

2) echo 2 > /proc/sys/vm/drop_caches // dentry 캐시와 inode 캐시를 비운다.

3) echo 3 > /proc/sys/vm/drop_caches // inode, dentry, 페이지 캐시 모두를 비운다.


페이지 캐시는 실제 파일에 저장되는 데이터 페이지를 캐싱하며, dentry 및 inode 캐시는 파일시스템에서 사용하는 메타데이터를 캐싱한다. 

즉, 1번 명령은 데이터 캐시를 비우는 명령이며, 2번 명령은 메타데이터 캐시를 비우는 명령인 것이다.

참고로, 위의 명령들은 캐시를 디스크에 flush 시키는 명령이 아니기 때문에 먼저 sync를 실행시킨 뒤에 실행 시킬 것을 권장하고 있다. 


두번째는 vfs_cache_pressure 이다.

해당 항목에 대한 커널 도큐먼트의 설명을 살펴보면, 메모리 회수를 위해 inode 및 dentry 캐시에 압박을 준다고 되어 있다. 

100을 기준으로 그 위의 값은 일반적인 것보다 더 큰 압박을 주는 것이고, 그 아래의 값은 압박을 덜 줌으로써 좀더 메타데이터를 캐싱할 수 있도록 하는 것이다.

특별히, 0은 절대 주지 말라고 한다. 해당 메모리가 회수가 안되서 OOM 킬러에 의한 오작동이 나타날 수 있다고 한다.

'Writings > Linux kernel' 카테고리의 다른 글

페이지 캐시의 크기 제한걸기 (2/2)  (0) 2017.12.11
Enabling Cgroup v2  (0) 2017.12.09
Posted by 곰푼
Writings/Linux kernel2017. 12. 9. 03:29

Cgroup을 사용할 일이 생겨서 뒤적거리던 중, cgroup v2가 나왔단 사실을 알게 되었다.


지금 사용중인 커널 (4.8.x) 버전에서는 두 cgroup이 모두 탑재되어 있는데, 그냥 부팅하면 커널이 알아서 모든 cgroup 컨트롤러를 기존 cgroup에 줘버린다. 따라서 cgroup v2로는 할 수 있는 일이 없다.


이를 해결할 수 있는 방법은, 커널 파라미터를 통해 컨트롤러의 권한을 해제하는 것이다.


/etc/default/grub 파일을 열어서, GRUB_CMDLINE_LINUX_DEFAULT= 항목에 다음과 같이 파라미터를 작성하자.

GRUB_CMDLINE_LINUX_DEFAULT="cgroup_no_v1=all"


혹은 cpu, memory 와 같은 특정 컨트롤러를 지정해도 무방한 듯 하다.


참고자료

https://lkml.org/lkml/2016/2/11/603



Posted by 곰푼
Writings/Operating System2017. 10. 18. 15:31

클락업을 통한 싱글 코어의 발전이 power wall에 가로막힌 이후, CPU의 발전 방향은 코어의 갯수를 늘리는 쪽으로 선회하였다. 그에 따라 멀티코어, 매니코어에 이르는 다양한 변종들이 등장하게 되었으며, 이로 인해 애플리케이션 및 운영체제의 확장성(scalability)이 중요한 문제로 대두되게 되었다. 


확장성이란 사전적인 의미로 보았을 때 늘어난 수요에 맞춰 얼마나 유연하게 대응할 수 있는지를 나타내는 척도라고 할 수 있다. 애플리케이션을 뒷 받침 하는 시스템의 입장에서 확장성이란 단어를 협의적으로 해석하면 늘어난 자원에 맞추어(e.g., 코어 수) 성능이 얼마나 향상되는 지를 나타내는 척도라고 할 수 있다. 자원이 늘어남에 따라서 성능이 비례하여 향상되면 확장성이 좋다 할 수 있고, 성능이 그대로이거나 혹은 더 떨어지게 된다면 확장성이 나쁘다라고 할 수 있다.


이 포스팅에서는 운영체제를 주로 다룰 것이므로 운영체제와 관련된 문제만을 살펴보도록 할 것이다. 확장성을 저해하는 요소는 운영체제 내에 다양하게 산재해 있지만, 그 중에서도 주된 요인을 꼽아보자면 단연 동기화(synchronization) 메커니즘이라고 할 수 있다.


비단 멀티코어 환경이 아니더라도 CPU의 시분할을 통한 멀티 프로그램 기법으로 인해 하나의 공유 자원에 대해 여러 프로세스가 동시에 접근할 수 있게 되었다. 각 프로세스는 특정 코드를 실행시키는 동안 자신이 접근하는 자원이 최소한 그 코드 내에서는 독점적으로 사용할 것을 요구하며, 이 요구사항이 지켜지지 않을 경우 일관성(consistency) 문제가 발생하게 된다. 


다음의 예시를 보자.


int a = 0, b = 0;

void func (void) {
    b = a + 1;  
    a = b; 
}

void proc_a (void) {
    func();
}

void proc_b (void) {
    func();
}


8번째 줄의 proc_a와 12번째 줄의 proc_b가 각각 별개의 프로세스에서 실행되는 코드라고 가정하자. proc_aproc_b가 동시에 실행된 뒤 a와 b를 출력하면 어떤 결과가 나올까? 총 두 가지의 결과를 예측해 볼 수 있다. 첫째로는 (a = 2, b = 2), 둘째로는 (a = 1, b = 1).  func()이 진행 중일 때 타이머 인터럽트가 발생하여 5번째 줄을 실행하기 전에 컨텍스트 스위치가 발생할 경우 두 번째의 결과가 발생하게 된다. 이러한 결과를 얻어내기 까지 발생할 수 있는 절차의 가지 수는 더욱 많다. 


  • proc_a -> proc_b
  • proc_b -> proc_a
  • proc_a -> (scheduling) proc_b -> proc_a
  • proc_b -> (scheduling) proc_a -> proc_b


위와 같은 상황이 발생할 경우, 사용자 혹은 애플리케이션의 입장에서는 같은 코드를 실행시켰음에도 불구하고 나오는 결과가 매번 달라질 수 있게된다. 따라서, 운영체제에서는 위와 같은 상황을 방지하기 위해 동기화 메커니즘을 제공한다. 대부분의 경우 동기화 메커니즘은 특정 영역(이를 critical section이라 부른다)에 대한 접근을 직렬화(serialize)하는 것으로 이러한 문제를 해결한다. 


void proc_a (void) {
    synch_region {
        func();
    }
}

void proc_b (void) {
    sync_region {
        func();
    }
}

<caller-side synchronization>

직렬화란, 동시에 오직 한 프로세스만 특정 영역에 접근시키도록 하는 것을 의미한다. 동시에 여러 프로세스가 접근할 경우, 뒤늦게 접근하는 프로세스는 앞서 진입한 프로세스가 작업을 마칠 때 까지 기다려야만 한다. 코드에서 synch_region으로 표시된 영역이 바로 이러한 영역이 되겠다.

위와 같이 호출하는 코드에서 동기화 영역을 표시하는 경우도 있으며, 반대로 호출되는 코드에서 동기화를 시키는 경우도 있을 것이다. 

void func (void) {
    synch_region {
        b = a + 1;  
        a = b; 
    }
}

<callee-side synchronization>

코드에 표시된 synch_region은 동기화 메커니즘의 일종의 예시인 것을 기억하자. 여러 논문 및 구현들을 통하여 다양한 방식의 동기화 메커니즘이 제안되어 왔으며, 위와 비슷한 형태를 취할 수도, 혹은 다른 형태를 취하게 될 수도 있다.


운영체제는 일반적으로 여러 형태의 locking primitive들을 통하여 동기화 기능을 제공한다. 대표적으로 spinlock과 같은 busy-loop 기반의 locking primitive가 있으며, 이는 효과적인 동기화 방법을 제공하지만 확장성을 저해시킬 수도 있다. 예를 들어 여러 코어에서 동작하는 프로세스들이 임계 영역(critical section - 동기화 메커니즘에 의해 보호받는 영역)을 만날 경우, 앞서 진입한 프로세스가 필요한 작업을 다 수행할 때 까지 기다려야만 한다. 이 임계 영역이 길어질 경우, 그만큼 대기 시간이 길어지게 되므로 자연스럽게 대기중인 프로세스가 점유하고 있는 코어의 시간을 낭비하게 된다. 


따라서 spinlock과 같은 busy-loop 기반의 동기화 방식들은 임계 영역의 길이를 최대한 줄일 것을 전제로 하고 있으며, 여의치 않을 경우 mutexsemaphore등의 block-based locking primitive들을 쓸 것을 추천한다. 이러한 lock들은 기다리는 프로세스를 sleep 상태로 전환함으로써, 해당 프로세스가 점유하고 있는 컴퓨팅 자원을 다른 프로세스에게 양도할 수 있도록 한다. 이를 통해 시스템은 낭비되는 컴퓨팅 자원을 효율적으로 활용할 수 있게 된다.


위와 같은 방식 외에도, 보다 확장성을 고려한 형태의 locking primitive들도 많이 고안되었다. reader-writer lock, ticket lock, queue spinlock, mcs lock, seqlock 등이 바로 확장성을 고려한 locking primitive 들이다.


앞으로 이와 같은 locking primitive들에 대해 한번 알아보려고 한다.

Posted by 곰푼
Writings2011. 7. 27. 15:57
문서간의 유사도를 측정하는 방법을 알아보자.

먼저 앞서 소개한 방법 중, tf의 벡터로 각 문서들을 다루겠다.

   수도권  기상예보  강우량
 doc1 3 1 2
 doc2 1 1 5
 doc3 1 1 3

이를 3차원 벡터로 나타내면

doc1 = < 3, 1, 2 >
doc2 = < 1, 1, 5 >
doc3 = < 1, 1, 3 >


위와 같이 표기할 수 있을 것이다.

이렇게 표기된 문서들의 유사도를 어떻게 측정할 수 있을까? 직관적으로, 다음과 같은 방법을 쓸 수 있을 것이다.

similarity(doc1, doc2) = |(3 - 1)| + |(1 - 1)| + |(2 - 5)| = 2 + 0 + 3 = 5
similarity(doc1, doc3) = |(3 - 1)| + |(1 - 1)| + |(2 - 3)| = 2 + 0 + 1 = 3
similarity(doc2, doc3) = |(1 - 1)| + |(1 - 1)| + |(5 - 3)| = 0 + 0 + 2 = 2


문서간의 유사도는 doc2와 doc3가 가장 높고 그 다음이 doc1, doc3, 가장 유사하지 않은 문서는 doc1, doc2임을 알 수 있을 것이다. 이 계산법의 개념은 단순하다. 문서가 각 단어별 갯수로 표기될 때, 단어별 갯수차이를 구하여 가장 차이가 작은 문서들이 가장 유사한 문서라는 것이다. 이는 Manhattan Distance로 잘 알려진, 두 지점 간의 거리를 구하는 방법의 확장판이다. 단 두 지점간의 거리를 구할 때 2차원의 좌표값을 사용했다면, 여기서는 이를 3차원 벡터로 확장했다는 점이 다르다.

위와 같이 각 문서를 bag of words 모델로 표시했을 때, 이를 n차원의 어느 좌표를 향하는 벡터로 생각할 수 있다는 데서 이러한 표기법은 잇점을 갖는다. 더욱이 다음으로 소개하고자 하는 Cosine Similarity에서는 각 문서간의 유사도를 평가할 때, 두 문서의 각도를 척도로 삼는 다는 점에서 이 벡터 표기법이 더 큰 의미를 갖게된다.

Cosine Similarity는 다음과 같이 좌표평면상에 나타낸 벡터의 유사성으로 판별하는 방법이다.


이는 위에서 들었던 예제하고는 다른 예시이다. 어떤 문서에 gossip과 jealous라는 단어의 분포를 바탕으로 위와 같은 벡터성분을 표기하였을 때, 좌표평면상에 어느 지점을 나타내는지 알 수 있을 것이다. 여기에 주어진 질의어 q에 대해서도 vector로 변환하여 좌표평면상에 나타내면, 어느 문서와 가장 비슷한지 알 수 있을 것이다.(주어진 예시에서는 d2와 가장 유사함을 주목하라.)

위와 같이 표기하기 위해서는, 각 벡터를 단위 벡터:Unit Vector로 환산하는 것이 중요하다. 그래야만 문서에서 그 단어가 차지하는 비중을 위와 같이 나타낼 수 있다. 단위 벡터로 환산한다는 의미는 우리가 관심을 두는 것이 해당 단어가 얼마나 많이 나오냐(tf)가 아니라 해당 단어가 문서내에서 얼마만큼의 비중을 차지하느냐인 것으로 보면된다.

'Writings' 카테고리의 다른 글

IDF:Inverse Document Frequency  (0) 2011.07.27
TF:Term Frequency  (0) 2011.07.26
Posted by 곰푼
Writings2011. 7. 27. 00:02
2011/07/26 - [Literal] - TF:Term Frequency


앞서의 TF에 이어서 IDF를 소개해보겠다.
TF기반의 Scoring 산정은 몇 가지 문제를 포함하고 있다. 일단 질의어에 포함된 모든 term에 대해서 동일한 비중을 둔다는 것이다. 예를 들어, 기상예보를 검색하기 위해서 기상예보를 제공하는 웹 사이트를 검색한다고 가정 하자. 이곳에서 오늘의 기상예보를 찾기 위해 다음과 같은 질의어를 입력한다.

"오늘 수도권 날씨"

이 질의어에서 가장 중요한 단어가 무엇인지 분별할 수 있겠는가? 주어진 질의에 대해 다음과 같은 문서들이 검색될 수 있을 것이다.

1. "... 오늘 호남지방의 날씨는 어제 날씨와 마찬가지로 폭우가 이어질 것으로 전망됩니다. 이어서 내일의 날씨는 ..."

2. "... 오늘날씨입니다. 장마가 계속 이어지고 있는 가운데, 수도권의 강우량은 어제와 마찬가지로 150mm이상의 많은 강우량이 예상됩니다..."

TF의 방식으로 각 문서의 점수를 계산해보면 1번 문서는 1 + 0 + 3 = 4가 될 것이고, 2번 문서는 1 + 1 = 1 = 3이 될 것이다. 따라서 1번 문서가 2번 문서보다 더 상위에 랭크될 것이다. 하지만 두 문서 중 질의 결과로 더 적합한 문서는 2번 문서이다.

이러한 일이 생기는 원인은 검색을 수행한 문서 컬렉션이 특정 주제(여기서는 기상예보)에 대해 모인 문서들이기 때문이다. 즉 이 컬렉션에 속하는 문서는 모두 기상예보에 대한 문서들이며, 직관적으로 '날씨'와 같은 단어가 모든 문서에 포함되어 있으리라고 예측할 수 있을 것이다.

이와 같이 모든 문서에 고루 분포된 term들은 문서의 순위를 매기는데 도움이 되지 않는다. 그보다는 해당 컬렉션 내에서 특정 문서를 특징짓는 term이 검색에는 더 도움이 된다. 예시에서는 '수도권'과 같은 term이 그런 역할을 수행하고 있다. 이러한 term의 특징은 전체 컬렉션에 대해 그 term이 포함된 문서의 수가 적다는 것이다.

특정 term을 가진 문서에 대한 빈도수를 df:document frequency라고 하자. 전체 문서의 수가 N이라고 할때, 특정 term을 가진 문서의 수가 적을 수록 좋으므로, N을 df(t)(t는 특정 term)로 나눈다. 이렇게 되면 df에 대해 반비례한 관계식이 되므로, df의 값이 작을수록 결과 값은 커지게 될 것이다. 이에대해 밑을 10으로 하는 로그 스케일로 변경시킨 값을 idf value라고 하며 다음과 같이 표현한다.



그리고 tf-idf라는 score로 기존의 tf score를 대신한다.


결과적으로 tf-idf score는 term 빈도수가 높을 수록, 그리고 해당 term을 가진 문서가 적을 수록 높은 값을 가지게 된다. 이 tf-idf를 문서내의 모든 term에 대해 작성하여, term에 대한 vector로 표현한다.

<score(t_1,d), score(t_2,d), score(t_3, d), ... >


이 벡터 값에 대하여, 질의문의 term-vector와 내적을 구하면(간단히 말하면, 문서의 term들 중에 질의어의 term과 같은 것들을 따로 골라 위의 tf-idf 값을 더하는 것을 의미한다.) 질의문에 대한 해당 문서의 score가 구해지게 된다. 이 score를 기준으로 정렬을 하면 ranked-retrieval이 가능해질 것이다.


[footnote][/footnote]수식 출처 "An Introduction to Information Retrieval : Christopher D. Manning : Cambridge Press"

'Writings' 카테고리의 다른 글

Cosine Similarity  (0) 2011.07.27
TF:Term Frequency  (0) 2011.07.26
Posted by 곰푼
Writings2011. 7. 26. 21:15
Information Retrieval 에서 각 문서의 적합성을 평가할 때, 질의로 던진 키워드가 있느냐 없느냐로 우선적으로 판별하게 된다. 가장 단순한 Boolean Retrieval에서는 해당 키워드가 있으면 적합한 문서로 판단하고, 그렇지 않을때는 적합하지 않은 문서로 판단하게 된다.
이 경우는 문서의 갯수가 많지 않고, 사용자가 정확한 키워드를 정확한 용법에 맞추어 사용한다면 유효할 수 있는 방법이다.

하지만 현대의 웹 환경에서 문서의 갯수는 그야말로 기하급수적으로 증가하고 있고, 더불어 사용자가 검색에 할해할 시간은 더더욱 줄어들고 있다. 사용자는 정확한 용법을 배우고, 정확한 키워드를 찾아내어 검색하길 원치 않는다. 때문에 대부분의 검색 질의어는 한개의 단어 혹은 모호한 단어의 결합으로 이루어지기 마련이다.

TF는 이러한 문서들의 적합성을 평가하여 순위를 매길때 사용하는 문서의 수치적 표현중 하나이다. 문서를 여러 단어들의 조합으로 생각해보자. 가령,

"기상예보입니다. 오늘도 수도권에서는 어김없이 폭우가 쏟아질 예정입니다. 벌써 며칠째 계속된 폭우로 인해 곳곳에서 재산피해, 인명피해가 발생하고 있습니다. 수도권에 거주하시는 시민들께서는 각별히 폭우에 주의하시어 예방할 수 있는 사고에 피해입지 않도록 주의하시기 바랍니다."

와 같은 일기예보 문서가 있다고 하자. 이 문서를 각각의 토큰으로 나누고, 조사와 같은 것들을 제거하면 이 문장은 단어의 집합일 될 것이다.

"기상예보 오늘 수도권 어김없이 폭우 예정 며칠째 계속 폭우 곳곳 재산피해 인명피해 발생 수도권 거주 시민 폭우 주의 예방 사고 피해 주의"


이러한 각각의 단어들을 term이라고 표기하며, 이 문서는 다시 각 term당 출현 갯수로 다시 나타낼 수 있다.

"기상예보:1 오늘:1 수도권:2 어김없이:1 폭우:3 예정:1 ......."


이 문서에서 가장 빈도수가 높은 term은 무엇일까? 세번정도 나타난 '폭우'라고 할 수 있겠다. 이때 문서상에서 '폭우'라는 term의 tf 값은 3이 된다. 다른 term들은 대부분 1을 tf 값으로 갖는 term이 된다. 더불어 이경우, term간의 순서나 상관관계는 실질적으로 의미가 없어지게 된다. 이러한 모델을 "bag of words" 모델이라고 부르며, 각 단어의 빈도수를 term frequency:tf라고 부른다.

이러한 term frequency는 문서들을 각 term의 가중치로 표현할 때 쓰인다. 이때 사용자가 free text query를 질의어로 사용한다고 가정하자. (free text query는 우리가 흔히 사용하는 '모호한 형태의 키워드 모음' 형식의 질의를 의미한다.) 사용자는 서울 나들이를 가기 위해 오늘 오후 수도권의 기상예보를 검색하고자 한다.

 "오늘 오후 수도권 기상예보"

보통 위와 같은 형태로 검색을 수행할 것이다. 이때 질의어로부터 알 수 있는 것은, 질의어가 네 개의 term으로 나뉜다는 것이다. 위와 같은 term에 대응하여 각 문서의 term 집합과 비교를 수행한다. 이때, 검색 결과에 순위를 적용하기 위해서 점수를 계산한다고 하자. 점수는 위에서 표현한 tf 값을 더하는 것으로 계산할 수 있을 것이다. 그러면 위에서 예시로 든 문서의 점수는

"tf(오늘) + tf(오후) + tf(수도권) + tf(기상예보) = 1 + 0 + 2 + 1 = 4"

가 될 것이다. 만일 이 점수가 검색을 수행한 문서 컬렉션에서 가장 높은 점수라면, 이 문서는 검색결과의 최 상위에 랭크되어 나타날 것이다.


'Writings' 카테고리의 다른 글

Cosine Similarity  (0) 2011.07.27
IDF:Inverse Document Frequency  (0) 2011.07.27
Posted by 곰푼