openlang

행이 하나인 행렬의 sum(X)

관측 행렬을 받아 각 열을 X ./ sum(X)로 정규화하는 함수가 있다. 행은 관측, 열은 특성이다. 오래 문제없이 돌았다. 그러다 행이 하나뿐인 입력이 들어왔다.

sum은 크기가 1이 아닌 첫 번째 차원을 따라 더한다. 1행 n열 행렬이면 그게 둘째 차원이다. 열 합계로 된 행 대신 숫자 하나가 나온다. 오류도 경고도 없다.

X = [1 2 3; 4 5 6];
sum(X)       % [5 7 9]
X = [1 2 3];
sum(X)       % 6, not [1 2 3]
sum(X, 1)    % [1 2 3]

나눗셈은 조용히 지나갔다. 스칼라로 나누는 건 언제나 허용된다. 모든 열이 1로 나와야 했다. 실제로 나온 건 합이 1이 되도록 줄인 행이었다. 0과 1 사이의 숫자라 멀쩡해 보였다.

고치는 법은 차원을 명시하는 것, sum(X, 1)이다. mean을 비롯해 데이터를 보고 차원을 고르는 함수들도 마찬가지다. 결과의 모양이 우연히 들어온 행 수에 따라 달라지면 안 된다. 알았다, 이제 큰 행렬만 받는 호출까지 전부 차원을 적는다.

이어지는 대화 6

서로의 근거를 따라
  1. 고유 링크

    포트란은 선언할 때 랭크가 정해진다. 랭크가 2인 배열에 sum(a, dim=1)을 쓰면 크기가 어떻든, 행이 하나여도 랭크 1이다. 여기서 버그는 크기를 보고 차원을 고르는 데 있다. 차원은 매번 넘긴다. 보조 함수에서도.

  2. 고유 링크

    max에는 이걸 더 나쁘게 만드는 두 번째 출력이 있어요. 행렬에서 [m, i] = max(X)는 열마다 최댓값이 있는 행을 알려 줘요. 행이 하나인 X에서는 최댓값이 하나만 나오고 i는 열 번호가 돼요. 같은 변수인데 뜻이 바뀌는 거예요. 다음 줄이 i로 관측을 고른다면 이제는 특성을 고르게 되고요. [m, i] = max(X, [], 1)이라고 쓰면 i는 계속 행 번호라서, 행이 하나면 열마다 1이 하나씩 든 벡터가 나와요.

  3. 고유 링크

    줄리아는 정반대로 정했어요. sum(X; dims=1)은 X가 한 행이어도 늘 행이 하나인 행렬을 돌려줘서 모양이 살아남고, 대신 그냥 벡터를 원할 때는 vec을 한 번 불러야 해요.

  4. 고유 링크

    그러니까 결과 모양을 호출하는 쪽이 보낸 데이터가 정한다는 거네요. 반환 타입이 운에 달린 함수라니 멋지네요. 그래도 공정하게 말하면 sum(X, 1)은 세 글자 더 쓰는 거고, 어느 차원을 고르는지는 문서에 분명히 나와 있어요.

  5. 고유 링크

    max 얘기는 정확하다. 같은 함수에 다른 문제도 있었다. sum(X, 1)을 흉내 내서 max(X, 1)이라고 써 놨다. 이건 차원 지정이 아니라 1과의 원소별 최댓값이고, 그래서 1보다 작은 값은 전부 1로 끌어올려졌다. 오류도 경고도 없고 모양도 같다. 맞는 호출은 둘째 인자 자리를 비워 둔 max(X, [], 1)이다. 코드에서 인자가 두 개인 max(와 min(을 찾아보니 세 군데였다. 둘은 진짜 원소별 비교였고 하나는 같은 실수였다.

  6. 고유 링크

    작은 테스트 하나면 두 문제를 한꺼번에 잡았을 것 같아요. 1보다 작은 값으로 된 한 행짜리 입력을 넣고 결과의 모양과 값을 확인하는 거예요. 거기서 max(X, 1)도 sum(X)도 바로 드러나요. 함수가 무엇을 돌려줘야 하는지 정하고 나면 행이 없는 입력도 넣을 수 있고요. 일단은 한 행짜리 경우만으로도 지금까지 나온 두 버그는 다 잡힐 거예요.

같은 질문의 다른 갈래

  1. 전부 NA인 그룹에 na.rm = TRUE

    그룹별 합계를 tapply(x, g, sum, na.rm = TRUE)로 냈더니 한 그룹이 0으로 나왔다. 그 그룹의 값은 전부 NA다. 측정값이 하나도 없는데 표에는 합계가 0이라고 적혀 있다. 하나씩 보면 다 변명할 수…

    답글 4r
  2. 열마다 새로 할당하던 루프

    Julia에서 슬라이싱은 복사를 해요. 알고 있었는데 루프 안에서는 깜빡했어요. 행렬의 열을 돌면서 j마다 s += sum(A[:, j])를 하는 함수를 예로 들어 볼게요. A[:, j]가 매번 새 벡터를 만들고, 더하고,…

    답글 4julia
  3. f2py가 C 순서 배열을 호출마다 복사한다

    f2py로 NumPy 배열을 Fortran 서브루틴에 넘긴다. 배열은 평소처럼 np.zeros((n, m))로 만들었으니 C 순서다. Fortran은 열이 연속으로 놓여 있길 원한다. f2py도 그걸 알고, 일반 입력 인자라…

    답글 3fortran