JMK no matter what

L1 Approximation Problem

  • 업데이트: 내부적으로 GLPK 를 이용할 수 있는 옵션이 있어서 써보니까 훨씬 빠르다. 'ㅅ'
  • 업데이트: GLPK 에 2*n*(n+m) 크기의 행렬을 넘겨줘야 하는데 이게 너무 억울해서.. MathProg 를 쓰면 n*m 크기의 행렬만 넘겨줘도 되니까 빨라지겠지? 헤헷 하고 열심히 MathProg 를 배워서 모델 짜고 데이터 제너레이터 짜서 넣었더니... 안빨라져... orz 패러미터 설정하면서 갖고 놀거나, lp-solve 를 써보면 더 나아질 수도 있겠다 싶지만 귀찮아서 이만.

Convex by Boyd 을 보면, 많은 경우에 L2 optimized estimator 보다 L1 optimized estimator 가 더 robust 하다고 되어 있다. Outlier 에 훨씬 덜 민감하니까 당연한 노릇. 보면 LP 로 바꿔서 풀 수 있다고 되어 있다. 그래서 파이썬용 Convex Optimization 패키지인 cvxopt 로 풀어보았다.

파란 선을 중심으로 랜덤 샘플을 만들고 (가우시안 노이즈 추가) 적절한 개수로 엉뚱한 위치에 아웃라이어를 좀 던져둔다. 초록색 선이 L2 핏, 빨간색 선이 L1 핏이다. 훨씬 원래 분포에 가까운 결과를 보여줌.

cvxopt 샘플에 L1 approximation 이 샘플로 나와 있길래 좋아라 하면서 써먹어 보려 했는데 안돌아가... 행렬 크기가 안맞는대... 좀 검증해 보고 올리지 쩝. ㅠ.ㅠ 미워라 덕분에 무식하게 모델링해서 돌렸다.

속도를 재 보면:

초록색이 처음에 써 본 cvxopt 의 기본 solver 속도. 이건 뭔가 exponential 하게 올라가는 삘인데.. -_-;; 과연 현실적으로 쓸 수 있을까.. 하고 후덜덜했는데, GLPK 써보니 거의 linear 하게 올라가는 듯? 뭐.. 일단 지금은 무지하게 무식하게 짠 점도 있고,뭔가 problem structure 를 이용하면 좀 더 효율적으로 돌릴 수 있을 듯. 그러려면 저 Boyd 책을 봐야 하나.... -_- ..... 어쨌든 소스코드

lang:py
# A and b are numpy arrays.
def l1simple(A, b): 
    # n = number of samples
    # m = number of features
    n, m = A.shape

    # minimize 1^T t
    # subject to
    # -t <= Ax - b <= t
    # which translates to
    # -Ax - t <= -b
    # and
    # Ax - t <= b

    # We optimize [x,t]: m+n variables... oTL
    c = matrix(m*[0.0] + n*[1.0])
    G = matrix(0.0, (2*n, n+m))
    for i in xrange(n):
        for j in xrange(m):
            G[i,j], G[i+n,j] = -A[i,j], A[i,j]
        G[i,m+i], G[i+n,m+i] = -1.0, -1.0
    h = matrix(hstack((-b,b)))

    sol = solvers.lp(c, G, h)
    print sol["x"][:m]
    return sol["x"][:m]

def test(samples = 100, outliers = 10):
    global A, b
    import numpy.random
    x = array([1.436743232, -0.2371234])
    A = hstack((rand(samples,1) * 100, ones((samples,1))))
    b = dot(A, x) + array([numpy.random.normal() * 10 for i in xrange(samples)])
    for i in range(outliers):
        devX = numpy.random.normal() * 10
        devY = numpy.random.normal() * 10
        if numpy.random.random() < 0.5:
            A[i,0], b[i] = devX, devY + 150
        else:
            A[i,0], b[i] = 100+devX, devY

    clf()
    scatter(A[:,0], b)

    st = time.time()
    x1, _, __, ___ = linalg.lstsq(A,b)
    L2 = time.time() - st
    st = time.time()
    x2 = l1simple(A, b)
    L1 = time.time() - st
    px = array([min(A[:,0]),max(A[:,0])])
    plot(px, px*x[0]+x[1])
    plot(px, px*x1[0]+x1[1])
    plot(px, px*x2[0]+x2[1])
    legend(("underlying","L2","L1"))

    print "Solving Least Squares: %.8lf sec, Solving L1 Norm: %.8lf sec" % (L2, L1)
    return L1

패키지 자체는 알기 쉬워서 좋은데 입력을 column major order 로 주는 것 때문에 죽도록 헷갈림;;;;;; 대체 왜 이렇게 해놓은거야아아;;;;;

나중에 짜본 GLPK 용 모델. Mathprog 는 심지어 vim syntax file 도 찾기 힘든 마이너 언어...

# L1-norm linear fit

/* sets */
set SAMPLES;
set FEATURES;

/* parameters */
param FeatureValues { i in SAMPLES, j in FEATURES };
param TargetVariable { i in SAMPLES };

/* decision variable X is given as { x, t }
   x = weight of each feature
   t = residual of each sample */
var x { j in FEATURES };
var t { i in SAMPLES } >= 0;

/* objective function */
minimize obj: sum{ i in SAMPLES } t[i];

/* constraints */
s.t. AB{i in SAMPLES} : -t[i] - sum{j in FEATURES} FeatureValues[i,j] * x[j]
                         <= -TargetVariable[i];
s.t. BC{i in SAMPLES} : -t[i] + sum{j in FEATURES} FeatureValues[i,j] * x[j]
                         <= TargetVariable[i];

end;

샘플 데이터 파일.

data;
set SAMPLES := 1 2 3 4 5 6 7 8;
set FEATURES := 1 2;

param FeatureValues: 1 2 :=
1    1 1
2    2 1
3    3 1
4    4 1
5    5 1
6    6 1
7    7 1
8    9 1;

param TargetVariable :=
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 1;

glpsol 깔고 glpsol -m l1fit.mod -d l1fit.data -w output 식으로 돌린다.

세상에서 제일 무식한 파이썬 드라이버.

lang:py
def l1glpk(A, b):
    n, m = A.shape
    fp = open("l1glpk.data", "w")
    fp.write(
"""data;
set SAMPLES := %s;
set FEATURES := %s;

param FeatureValues: %s :=\n""" % (" ".join(map(str, xrange(1,n+1))),
                                   " ".join(map(str, xrange(1,m+1))),
                                   " ".join(map(str, xrange(1,m+1)))));
    for i in xrange(n):
        fp.write("%d\t%s\n" % (i+1, " ".join(["%g" % v for v in A[i]])));
    fp.write(";\n");

    fp.write("param TargetVariable :=\n");
    for i in xrange(n):
        fp.write("%d\t%g\n" % (i+1, b[i]))
    fp.write(";\n");
    fp.close()
    os.system("glpsol -m l1fit.mod -d l1glpk.data -w out")
    return [float(line.split()[1]) for line in open("out").readlines()[2*n+3:2*n+3+m]]

GLPK 도 파이썬 드라이버 있던데 담에 쓸일 있으면 그거나 써야겠다....

2010-01-23 12:50:15 | JM | /writings/cs/ | 0 Comments

Leave a comment