for textmining

Conditional Random Fields

|

이번 글에서는 Conditional Random Fields에 대해 살펴보도록 하겠습니다. 이 글은 고려대 정순영 교수님 강의를 정리했음을 먼저 밝힙니다. 이밖에 다양한 자료를 참고하였는데요, 인용한 부분에 표시를 해 두었습니다. 비터비 알고리즘 관련 설명 그림은 제가 직접 그렸습니다. 제가 잘못 이해하고 있거나 미진한 점 있으시면 언제든 댓글로 알려주시면 바로 반영하겠습니다. 그럼 시작하겠습니다.

overview

CRF를 설명하는 데 있어 가장 유명한 그림 아닐까 싶습니다. 다음과 같습니다.

CRF, MEMM, HMM과의 차이점은 다음과 같습니다. 간결하고 직관적인 설명이어서 직접 인용을 해봤습니다. (출처 : Quora)

CRFs and MEMMS are discriminative sequence models whereas HMMs are generative sequence models. HMM essentially uses Bayes Rule as a local model over the transition and emission probabilities, whereas CRF and MEMM’s local models are MaxEnt models over transition and observable features. The chief difference between MEMM and CRF is that MEMM is locally renormalized and suffers from the label bias problem, while CRFs are globally renormalized.

CRF가 강점을 지니는 이유는 구성하기에 따라서 얼마든지 HMM 같은 구조로 바꿀 수 있다는 점입니다. 아래 그림과 같습니다. (출처 : C Sutton, “An Introduction to CRF”)

For example, in an HMM, a transition from state $i$ to state $j$ receives the same score, log $p(y_t = j$|$y_{t−1} = i)$, regardless of the input. In a CRF, we can allow the score of the transition $(i, j)$ to depend on the current observation vector, simply by adding a feature $1{y_t=j}$, $1{y_{t−1}=1}$, $1_{x_t=o}$.

CRF는 본질적으로 시퀀스 분류기이기 때문에 최근 주목받고 있는 Recurrent Neural Network와도 직, 간접적으로 연관을 맺고 있는 것 같습니다. 이와 관련한 설명 또한 인용해봤습니다. (출처 : Quora)

RNNs have a latent state that is never observed (e.g. the memory in a LSTM). In contrast, the CRF has a latent state that is observed for training data (the model has to learn how to recreate those latent states for test data). Both are similar in that there is a set of parameters that tell you how to evolve the latent state from one time step to the next.

수식과 파이썬 구현

CRF의 수식을 살펴보겠습니다. 수식만 살펴봐서는 되레 복잡하므로, 파이썬 코드와 함께 살펴보겠습니다. 파이썬 코드는 이곳을 참고해 대폭 손질하였습니다. (수식 이해를 돕기 위한 코드로 대단히 느립니다, 혹시 학습 용도로 필요하시다면 라이브러리 활용을 추천해 드립니다)

기본 구조

CRF를 그래피컬하게 나타낸 그림은 다음과 같습니다. 입력벡터 $x$의 위치에 상관없이 모두 활용하기 때문에 매우 유연한 구조입니다.

입력 시퀀스(예컨대 단어들) 벡터 $x$가 주어졌을 때 레이블 시퀀스(예컨대 품사) 벡터 $y$가 나타날 확률은 다음과 같이 정의됩니다. 최대엔트로피모델(로지스틱 회귀)와 완전히 같습니다만, 최대엔트로피가 single observation을 분류하는 모델이라면, CRF는 sequential classifer라는 점이 다릅니다.

[{ p }_{ \overrightarrow { \lambda } }\left( \overrightarrow { y } \overrightarrow { x } \right) =\frac { 1 }{ { Z }{ \overrightarrow { \lambda } }\left( \overrightarrow { x } \right) } \cdot exp\left( \sum _{ j=1 }^{ n }{ \sum _{ i=1 }^{ m }{ { \lambda }{ i }{ f }{ i }\left( { y }{ j-1 },{ y }_{ j },\overrightarrow { x } ,j \right) } } \right)]

위 식을 파이썬 코드로 구현하면 다음과 같습니다.

import math
def calc_prob_y_given_x(y_prime, x, all_labels, FeatureFunction, weights):
    n = len(y_prime)
    m = len(weights)
    nominator = 0
    for j in range(1, n):
        for i in range(1, m):
            nominator += weights[i] * FeatureFunction(y_prime, x, i, j)
    denominator = calc_Z(x, n, m, all_labels, FeatureFunction, weights)
    return math.exp(nominator) / denominator

Feature Functions

CRF는 최대엔트로피모델이나 최대엔트로피마코프모델처럼 Feature를 연구자가 유연하게 설정할 수 있습니다. 이를 파이썬 코드로 구현한 결과는 다음과 같습니다.

# x : words (observation sequence)
# y : lables (e.g: POS TAGS, label sequence)
def FeatureFunction(x, y, i, j):
    # f_1
    if i == 1 and y[j-1] == 'NN':
        return 1
    # f_2
    elif i == 2 and y[j-1] == 'VBZ':
        return 1
    # f_3
    elif i == 3 and x[0] == 'DT':
        return 1
    else:
        return 0

Label Bias 문제와 극복 방안

CRF는 Label Bias 문제를 극복하기 위해 제안된 기법입니다. Label Bias란 다음과 같은 문제를 가리킵니다.

  • Preference of states with lower number transitions over others

이를 해결하기 위해 확률값을 구할 때 global normalize를 합니다. 이를 구현한 파이썬 코드는 다음과 같습니다.

def calc_Z(x, n, m, all_labels, FeatureFunction, weights):
    Z = 0
    all_possible_Y = itertools.product(all_labels, repeat=n)
    for y_prime in all_possible_Y:
        tmpZ = 0
        for j in range(1, n):
            for i in range(1, m):
                tmpZ += weights[i] * FeatureFunction(y_prime, x, i, j)
        Z += math.exp(tmpZ)
    return Z

그런데 보시다시피 가능한 모든 조합의 레이블 시퀀스에 대한 확률을 구해야 합니다. 가령 품사 종류가 명사(NN), 동사(VB) 등 다섯 가지이고, 시퀀스 길이가 5(개 단어)라면 다음 표처럼 $5^5$=3125가지의 경우의 수를 고려해야 합니다.

$y_1$ $y_2$ $y_3$ $y_4$ $y_5$
NN NN NN NN NN
NN NN NN NN VBN
NN NN NN NN VBZ
NN NN NN NN IN
NN NN NN NN DT
NN NN NN VBN NN
NN NN NN VBN VBN
NN NN NN VBN VBZ
NN NN NN VBN IN
NN NN NN VBN DT

CRF에서 가장 계산량이 많은 부분이 바로 $Z$를 구하는 부분입니다. 위 파이썬 코드는 수식 이해 용도로 수식을 그대로 옮겨놓은 형태이지만, 실제로는 다이내믹 프로그래밍(dynamic programming) 등 최적화 기법을 쓴다고 합니다.

파라메터 학습 : 최대우도추정

CRF의 파라메터는 로지스틱 회귀의 파라메터를 추정하는 방식과 같이 최대우도추정법(Maximum Likelihood Estimation)으로 구합니다. 식이 매우 복잡한데, 저 또한 정리 용도로 남겨둡니다. CRF의 로그 우도함수는 다음과 같습니다. 식의 첫 줄 두번째 항은 과적합(overfitting) 방지를 위한 정규화(regularization) 항입니다.

위 로그 우도함수는 파라메터 $λ$로 편미분한 값이 0인 지점에서 최대값을 가집니다. 이를 3등분해서 각각 $λ$에 대해 편미분한 결과는 다음과 같습니다. 우선 $A$를 편미분한 결과입니다.

다음은 $B$를 파라메터 $λ$에 대해 편미분한 결과입니다.

마지막으로 $C$입니다.

$A$는 데이터의 empirical distribution으로 해석할 수 있습니다. 식과 파이썬 코드는 다음과 같습니다.

def calc_empirical_expectation_feature_i(train_data, FeatureFunction, i):
    empirical_expectation_feature_i = 0
    for x, y in train_data:
        n = len(y)
        for j in range(1, n):
            empirical_expectation_feature_i += FeatureFunction(y, x, i, j)
    return empirical_expectation_feature_i

$B$는 모델이 내놓는 distribution으로 해석할 수 있습니다. 식과 파이썬 코드는 다음과 같습니다.

import itertools
def calc_predicted_expectation_feature_i(train_data, FeatureFunction, 
									  all_labels, weights, i):
    predicted_expectation = 0
    for x, y in train_data:
        n = len(y)
        all_possible_Y = itertools.product(all_labels, repeat=n)
        for y_prime in all_possible_Y:
            predicted_expectation += \
                (calc_prob_y_given_x(y_prime, x, all_labels, FeatureFunction, weights)
                 * sum([FeatureFunction(x, y, i, j) for j in range(1, n)]))
            print(predicted_expectation)
    return predicted_expectation

로그우도 함수에 대한 편미분 식을 다시 적으면 다음과 같은데요. $A$와 $B$가 비슷할 수록 로그우도 함수의 도함수가 작아집니다. 데이터의 분포와 모델의 분포가 비슷할 수록, 즉 모델이 데이터의 분포를 잘 모사할 수록 학습이 잘 되었다는 이야기입니다.

이제 거의 다 왔습니다. 파라메터 $λ$를 랜덤 초기화한 뒤 이제까지 구한 로그우도 함수가 커지는 방향(그래디언트)로 파라메터를 조금씩 업데이트해 주면 됩니다(gradient ascent). 이를 파이썬 코드로 구현한 결과는 다음과 같습니다.

#train data set = {(x, y)}
def get_all_labels(train_data):
    available_labels = set()
    for x, y in train_data:
        available_labels.update(y)
    return list(available_labels)

# m = feature vector size
import random
def initial_weights(m):
    return [random.random() for _ in range(m)]
   
def train(train_data, FeatureFunctions, m, iterations=100, learning_rate=0.1):
    all_labels = get_all_labels(train_data)
    weights = initial_weights(m)
    for _ in range(iterations):
        for i in range(1, m):
            empirical_expectation = \
                calc_empirical_expectation_feature_i(train_data, FeatureFunction, i)
            predicted_expectation = \
                calc_predicted_expectation_feature_i(train_data, FeatureFunction,
                                                     all_labels, weights)
            weights[i] = weights[i] + \
            			learning_rate * (empirical_expectation - predicted_expectation)

비터비 알고리즘

시퀀스 분류를 하기 위해 이 먼길을 돌아왔습니다. CRF가 최적 상태열을 inference하는 기법인 비터비 알고리즘은 다음과 같이 동작합니다.

다음 과정입니다.

다음 과정입니다.

마지막으로 backtrace 과정입니다.

Comment  Read more

한국어 관형사절의 시간 표현

|

이번 글에서는 한국어 관형사절의 시간 표현에 대해 살펴보도록 하겠습니다. 이 글은 고려대 정연주 선생님 강의를 정리하였음을 먼저 밝힙니다. 참고로 말씀드리면 한국어 관형사절의 시간 표현을 이해하려면 시제 개념을 모두 알고 있어야 한다고 합니다(링크 참고). 그럼 시작하겠습니다.

관형사절?

관형사란 명사류(체언) 앞에서 그 명사류의 뜻을 분명하게 제한하는 품사입니다. 주어와 서술어가 함께 나타난 구성을 이라고 합니다. 관형사절이란 관형사가 들어갈 자리에서 관형사 역할을 하는 절을 가리킵니다. 다음 예문과 같습니다.

  • 관형사 :
  • 관형사절 : 철수가 본

관형사절을 만드는 어미를 관형사형 전성어미라고 합니다. 대표적으로 -ㄴ-ㄹ이 있습니다. -ㄴ은 현실화된 일을 나타내는 절에 결합하고, -ㄹ은 현실화되지 않고 아직 사고의 영역에 있는 일을 나타내는 절에 결합하는 경향이 있습니다. 예문을 보겠습니다.

  1. [쥐를 잡] 고양이가 낮잠을 잔다.
  2. [쥐를 잡] 고양이가 낮잠을 잔다.

(1)의 고양이는 이미 쥐를 잡았고, (2)의 고양이는 아직 잡지 않았다는 점에서 의미상 차이가 있습니다.

한국어 관형사절의 시간 표현

한국어 주절(모절)의 시제 체계는 다음과 같이 정리할 수 있습니다. 요컨대 한국어 주절의 시제는 -었-(과거), -는-(현재), -겠-(미래)의 3분 체계입니다.

한국어 관형사절의 시간 표현은 다음과 같습니다.

첫번째 표와 두번째 표를 비교해서 보면 주절의 시간 표현과 관형사절의 시간 표현이 이루는 체계가 사뭇 다른 점을 확인할 수 있습니다. 관형사형 전성어미 -ㄴ-ㄹ를 제거하면, 관형사형 시간 표현은 -더-(과거), -느-(현재), -∅-(미래)의 3분 체계인데요. 각각이 어떤 문법적 의미를 지니는지 확실하지 않습니다. 동사와 형용사의 아주 다른 체계를 이루고 있는 점도 주목할 점입니다.

이러한 세 가지 의문점을 중심으로 설명해 보겠습니다.

중세 한국어의 시간 표현

국어학자들의 연구에 따르면 중세 한국어의 시간 표현 체계는 주절이든 관형사절이든 상관없이 다음과 같았다고 합니다. 완망, 비완망 개념과 관련해서는 이곳을 참고하시면 좋을 것 같습니다.

그런데 17세기쯤 과거 표시 형태소 -었-(='-어 있-')이 등장하면서 기존의 -∅--더-와 경쟁 구도를 이루다가, 현대에 와서는 -었-이 완승에 가깝게 널리 쓰이고 있습니다. -∅-는 이 경쟁에 져서 사라지고, -더-는 새로운 의미를 가지게 되면서 겨우 살아남았습니다.

그런데 신기한 것은 -었-은 유독 관형사절에 침투하지 못했다는 점입니다. 그래서 현대 한국어의 관형사절에서만큼은 중세 한국어의 시간 표현 체계가 화석처럼 남았습니다. 다음 예문을 보겠습니다.

내가 먹

위 관형사절을 일반적인 문장으로 바꾸면 아래와 같이 비문이 됩니다. -더-라는 형태소가 과거 표시 기능을 하는 게 아니라 다른 새로운 의미를 가지게 됐다는 것이죠.

*내가 밥을 먹라.

관형사절은 한국어 시간 표현 체계 전체의 변화에서 제외된 원인과 관련해 다양한 의견이 제시되고 있지만 다음과 같은 설명도 가능할 것 같습니다.

  • 새로운 문법소(예컨대 -었-)는 주로 정보 전달의 초점에서 발달한다.
  • 관형사절은 정보 전달의 초점이 아니라 이미 전제된 내용이 언급되는 부분이다.
  • 따라서 관형사절에서는 기존의 오래된 문법소가 계속해서 쓰이는 경향이 있다.

동사 관형사절의 시간표현

-더-, -느-, -∅--ㄴ, -ㄹ의 조합으로 관형사절 시간표현 관련 어미들의 의미를 곱씹어볼 수 있습니다. 우선 현재에 대응하는 -는-부터 살펴보겠습니다.

-는은 ‘현재’와 ‘현실’의 조합으로 생각해볼 수 있겠습니다. 즉 현재 일어나고 있는 일을 나타냅니다. 다음 예문과 같습니다.

내가 먹

이번엔 과거에 대응하는 -은, -던, -었던을 살펴보겠습니다.

-은은 ‘과거 완망’과 ‘현실’의 조합으로 생각해볼 수 있습니다. 과거의 일인데 사태의 시작과 끝이 시야에 들어와 있음을 나타냅니다. 쉽게 말해 ‘끝난 과거’를 가리킵니다. 다음 예문과 같습니다.

내가 먹 밥 (밥을 다 먹었음)

꽃이 들판 (꽃이 핀 상태가 지속됨)

-던은 ‘과거 비완망’과 ‘현실’의 조합으로 생각해볼 수 있습니다. 과거의 일인데 화자가 사태의 내부를 들여다 보고 있음을 나타냅니다. 쉽게 말해 ‘끝나지 않은(혹은 끝났는지 모르는) 과거의 일’을 가리킵니다. 다음 예문과 같습니다.

내가 먹 밥 (밥을 먹었긴 했으나 다 먹었는지는 나타나지 않음)

-었던은 과거의 일이지만 현재와의 접점이 없는 걸 나타냅니다. 쉽게 말해 ‘단절된 과거’를 가리킵니다. 이는 주절의 시제 표현에서 -었었-과 비슷합니다.

꽃이 피었던 들판 (과거에 꽃이 피었으나 지금은 꽃이 피지 않음)

마지막으로 미래에 대응하는 -을을 살펴보겠습니다.

-을은 아직 현실화되지 않은 일을 나타냅니다. 다음 예문과 같습니다.

내가 먹 밥 (아직 밥을 먹지 않았으나 앞으로 먹을 예정)

형용사 관형사절의 시간 표현

형용사 관형사절은 중세 국어의 시간 표현 체계를 그대로 따르고 있습니다. 차례대로 살펴보겠습니다. 우선 -은은 형용사 관형사절에서 현재 성립하는 상태를 표현할 때 씁니다. 다음 표, 예문과 같습니다.

지지율이 높 정치인

-은은 과거 완망의 -∅-과 현실의 -ㄴ의 조합으로도 생각해 볼 수 있습니다. 그런데 ‘과거 완망’과 ‘현실’이 합쳐진 -은현재를 나타낸다는 게, 지금까지의 설명에 비추어봤을 때 다소 생소하긴 합니다. 이와 관련해서는 이런 견해가 있습니다.

형용사에는 시간과 끝이라는 개념이 없고 시간을 초월해서 펼쳐진 개념이다. 따라서 사태의 시작과 끝을 전제로 한 완망/비완망 개념과 애초에 양립이 불가능하다. 형용사의 과거를 나타낼 때 -더-를 늘 쓰다보니, -더-를 안쓰면 현재로 해석되게 되었다. 그래서 -∅-의 의미가 동사에서는 과거 완망, 형용사에서는 현재로 그 의미가 달라지게 된 것이다.

형용사 관형사절에서 과거의 상태를 표현할 때는 -던 또는 -었던을 씁니다. 둘 모두 과거 비완망의 -더-와 현실의 -ㄴ의 조합입니다.

다만 형용사 관형사절의 과거를 나타내는 -더-는 완망, 비완망 개념은 없고 순전히 과거의 의미만 나타냅니다. 아울러 형용사 관형사절의 -었던-던과 큰 의미 차이가 없습니다. 다음 예문과 같습니다.

지지율이 높 정치인

지지율이 높았던 정치인

마지막으로 형용사 관형사절의 미래의 상태, 즉 현실화되지 않은 상태를 나타낼 때는 -을을 씁니다. 다음 표, 예문과 같습니다.

지지율이 높 정치인

Comment  Read more

한국어의 어휘상

|

이번 글에서는 한국어의 상(aspect)에 대해 살펴보도록 하겠습니다. 이 글은 고려대 정연주 선생님 강의와 ‘한국어문법총론1(구본관 외 지음, 집문당 펴냄)’을 정리하였음을 먼저 밝힙니다. 그럼 시작하겠습니다.

‘상’이란

상이란 어떤 사태의 내적 시간 구성을 가리키는 문법 범주입니다. 한국어의 상에는 문법형태소로 표현되는 문법상(grammatical aspect), 동사 어휘로 나타나는 어휘상(lexical aspect)가 있습니다. 이 글에서는 어휘상을 중심으로 살펴보도록 하겠습니다. 이 글 역시 고려대 정연주 선생님 강의와 한국어문법총론(구본관 외 지음)을 정리하였음을 먼저 밝힙니다.

어휘상

한국어의 어휘상은 어떤 어휘가 상적 특성을 구성하는 자질을 어떻게 가지고 있느냐에 따라 분류됩니다. 그 자질로는 상태성, 순간성, 완성성 등이 있습니다.

[+상태성] : 성질이나 상태를 나타내는 형용사

[-상태성] : 행위나 작용을 나타내는 동사

[+순간성] : 해당 어휘가 나타내는 행위나 작용이 순간적으로 이뤄질 때

[-순간성] : 행위나 작용이 천천히 이뤄질 수 있을 때

[+완성성] : 행위나 작용이 다 끝나야 해당 어휘의 의미가 성립할 때

[-완성성] : 행위나 작용이 끝나지 않아도 의미가 성립할 때

한국어 어휘상의 분류는 다음과 같습니다.

범주 사례 자질
상태 동사 높다, 낮다, 작다… [+상태성, -순간성, -완성성]
과정 동사 걷다, 읽다, 울다… [-상태성, -순간성, -완성성]
완성 동사 닫다, 눕다, 먹다… [-상태성, -순간성, +완성성]
순간 동사 죽다, 차다, 잡다… [-상태성, +순간성, +완성성]
심리 동사 믿다, 느끼다, 알다… [-상태성, -순간성, -완성성]

과정 동사는 제한적 시간의 폭을 나타내는 ‘한 시간동안’과 같은 부사구와 아주 잘 어울리나 심리 동사는 그러한 말과 잘 어울리지 않습니다. 심리 동사는 형식적으로는 동사이지만 의미적으로는 상태의 지속성을 나타내는 형용사에 가까워 이러한 특성이 생긴 것입니다.

완성 동사와 순간 동사는 순간성 자질이 앞뒤 맥락에 따라 달리 해석될 수 있습니다. 예컨대 다음과 같습니다.

(1) 완성 동사이나 [+순간성] : 문을 쾅 닫았다

(2) 순간 동사이나 [-순간성] : 그 개는 서서히 죽어갔다

어휘상은 앞뒤 맥락에 따라 달리 해석될 여지가 있는 유동성이 존재합니다. 그래서 동사의 어휘상을 다룰 때는 목적어나 부사어와 함께 전체 동사구의 상을 고찰하는 것이 일반적입니다. 이렇게 고찰된 상적 의미를 상황유형(situation type)이라 합니다.

문법상과 어휘상의 연관성

상(문법상)은 어휘상과 밀접한 연관이 있습니다. 어휘 자체의 상적 의미와 상의 문법적 표현이 잘 어울려야 통사적 구성을 이룰 수 있기 때문입니다. 다음 예문을 보겠습니다.

(A) 진행상 : 순희가 공원을 걷고 있다

(B) 완료상 : 명희가 볼펜을 쥐고 있다

(A)의 ‘걷다’는 과정 동사입니다. 따라서 ‘-고 있다’가 결합하면 진행상의 의미로 해석하는 것이 자연스러워집니다.

이에 반해 (B)의 ‘쥐다’는 순간 동사입니다. 따라서 ‘-고 있다’가 결합할 때 진행상의 의미로 해석하기 어렵게 됩니다. 순간적으로 일어나는 행위는 시간의 폭이 너무나 짧기 때문입니다. 그래서 (B)의 ‘-고 있다’는 완료상으로 해석하게 됩니다.

다른 예문을 보겠습니다.

(ㄱ) 철수가 눈을 깜빡이고 있다

(ㄴ) 아이들이 공을 차고 있다

‘깜빡이다’와 ‘차다’는 모두 순간 동사입니다. 그리고 이들의 행위는 행위의 결과가 남지 않는 동사이기도 합니다. 그러므로 원칙적으로는 진행상의 ‘-고 있다’도 쓸 수 없고 완료상의 ‘-고 있다’도 쓸 수 없어야 합니다.

그러나 (ㄱ), (ㄴ) 모두 말이 됩니다. (ㄱ)은 반복적 행위를 뜻하고 있습니다. (ㄴ) 역시 어떤 아이가 딱 한 번 공을 찼을 때 ‘공을 차고 있다’라고 하지 않는다는 점에서 반복적 행위를 뜻함을 알 수 있습니다. 반복적 행위는 행위의 지속성과 의미가 상통하기 때문에 진행상의 ‘-고 있다’와 어울려 쓰일 수 있는 것입니다.

이처럼 문법상과 어휘상의 어울림은 상황 맥락에 따른 미세한 시간 해석에 의존하는 경우가 많습니다.

Comment  Read more

쉘정렬

|

이번 글에서는 쉘정렬(selection sort)에 대해 살펴보도록 하겠습니다. 이 글은 고려대 김황남 교수님 강의와 위키피디아를 정리하였음을 먼저 밝힙니다. 예시 그림과 파이썬 코드는 이곳을 참고하였습니다. 그럼 시작하겠습니다.

concepts

쉘정렬은 정렬되지 않은 배열을 정렬하는 데 많은 계산량이 드는 삽입정렬을 개선한 기법입니다. 다음과 같은 배열을 정렬한다고 칩시다.

54, 26, 93, 17, 77, 31, 44, 55, 20

쉘정렬에서는 gap이라는 개념이 있습니다. 데이터를 띄엄띄엄 봐서 정렬한다는 개념인데요. 우선 위 숫자들을 아래와 같이 세 개 서브리스트로 나눕니다(gap=3).

이번엔 서브리스트별로 삽입정렬을 적용해 정렬한 뒤 세 서브리스트를 합칩니다. 다음과 같습니다.

마지막으로 위 리스트(17, 26, 20, 44, 55, 31, 54, 77, 93 )에 삽입정렬을 수행하면 쉘 정렬이 끝납니다. 쉘 정렬에서 gap은 정렬 대상 리스트의 절반으로 시작해 iteration이 돌 때마다 반씩 줄여가게 됩니다.

파이썬 코드

쉘정렬을 파이썬으로 구현한 코드는 다음과 같습니다.

def shellSort(alist):
    sublistcount = len(alist)//2
    while sublistcount > 0:

      for startposition in range(sublistcount):
        gapInsertionSort(alist,startposition,sublistcount)

      print("After increments of size",sublistcount,
                                   "The list is",alist)

      sublistcount = sublistcount // 2

def gapInsertionSort(alist,start,gap):
    for i in range(start+gap,len(alist),gap):

        currentvalue = alist[i]
        position = i

        while position>=gap and alist[position-gap]>currentvalue:
            alist[position]=alist[position-gap]
            position = position-gap

        alist[position]=currentvalue

Comment  Read more

선택정렬

|

이번 글에서는 선택정렬(selection sort)에 대해 살펴보도록 하겠습니다. 이 글은 고려대 김황남 교수님 강의와 위키피디아를 정리하였음을 먼저 밝힙니다. 파이썬 코드는 이곳을 참고하였습니다. 그럼 시작하겠습니다.

concepts

선택정렬은 위치 변경 횟수를 줄여, 버블정렬을 일부 개선한 기법입니다. 선택정렬의 작동 원리는 다음과 같습니다.

버블정렬은 왼쪽에 있는 값이 비교 대상인 오른쪽에 있는 값보다 크면 자리를 바꿔줬는데 반해, 선택정렬은 일단 최대값(혹은 최소값)을 찾은 뒤에야 이 값을 정해진 위치로 보내주게 됩니다. 다시 말해 비교 횟수 측면에서는 버블정렬과 선택정렬이 같고 둘 모두 $O(n^2)$의 계산복잡성을 갖지만 자리이동(swap) 측면에서는 선택정렬이 효율적입니다. 위 그림 예시의 경우 9번의 iteration에서 9번의 자리이동(iteration당 1번의 swap)이 있었습니다.

파이썬 구현

선택정렬의 파이썬 코드는 다음과 같습니다.

def selectionSort(alist):
   for fillslot in range(len(alist)-1,0,-1):
       positionOfMax=0
       for location in range(1,fillslot+1):
           if alist[location]>alist[positionOfMax]:
               positionOfMax = location

       temp = alist[fillslot]
       alist[fillslot] = alist[positionOfMax]
       alist[positionOfMax] = temp

Comment  Read more