np.dot이 왜 정확하지 않습니까? (n 차원 배열)


15

np.dot두 개의 'float32'2D 배열을 사용 한다고 가정 합니다.

res = np.dot(a, b)   # see CASE 1
print(list(res[0]))  # list shows more digits
[-0.90448684, -1.1708503, 0.907136, 3.5594249, 1.1374011, -1.3826287]

번호. 제외하고는 다음을 변경할 수 있습니다.


사례 1 : 슬라이스a

np.random.seed(1)
a = np.random.randn(9, 6).astype('float32')
b = np.random.randn(6, 6).astype('float32')

for i in range(1, len(a)):
    print(list(np.dot(a[:i], b)[0])) # full shape: (i, 6)
[-0.9044868,  -1.1708502, 0.90713596, 3.5594249, 1.1374012, -1.3826287]
[-0.90448684, -1.1708503, 0.9071359,  3.5594249, 1.1374011, -1.3826288]
[-0.90448684, -1.1708503, 0.9071359,  3.5594249, 1.1374011, -1.3826288]
[-0.90448684, -1.1708503, 0.907136,   3.5594249, 1.1374011, -1.3826287]
[-0.90448684, -1.1708503, 0.907136,   3.5594249, 1.1374011, -1.3826287]
[-0.90448684, -1.1708503, 0.907136,   3.5594249, 1.1374011, -1.3826287]
[-0.90448684, -1.1708503, 0.907136,   3.5594249, 1.1374011, -1.3826287]
[-0.90448684, -1.1708503, 0.907136,   3.5594249, 1.1374011, -1.3826287]

인쇄 된 슬라이스에 정확히 같은 수를 곱한 결과도 달라집니다.


CASE 2 : 평평 a의 1D 버전을 가지고 b, 다음 조각 a:

np.random.seed(1)
a = np.random.randn(9, 6).astype('float32')
b = np.random.randn(1, 6).astype('float32')

for i in range(1, len(a)):
    a_flat = np.expand_dims(a[:i].flatten(), -1) # keep 2D
    print(list(np.dot(a_flat, b)[0])) # full shape: (i*6, 6)
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]

사례 3 : 더 강력한 통제; 관련되지 않은 전체를 0으로 설정 : a[1:] = 0CASE 1 코드에 추가하십시오 . 결과 : 불일치가 지속됩니다.


사례 4 : 이외의 색인을 확인 [0]; 에서와 같이 [0]결과는 생성 지점에서 고정 된 수의 배열 확대를 안정화하기 시작합니다. 산출

np.random.seed(1)
a = np.random.randn(9, 6).astype('float32')
b = np.random.randn(6, 6).astype('float32')

for j in range(len(a) - 2):
    for i in range(1, len(a)):
        res = np.dot(a[:i], b)
        try:    print(list(res[j]))
        except: pass
    print()

따라서 2D * 2D의 경우 결과가 다르지만 1D * 1D와 일치합니다. 내 독서 중 일부에서 이것은 간단한 덧셈을 사용하여 1D-1D에서 비롯된 것으로 보이지만 2D-2D는 덜 정확한 성능 향상 돋보이는 'fancier'를 사용합니다 (예 : pairwise 덧셈은 반대입니다). 그럼에도 불구하고, 나는 1 번 a이 정해진 '임계 값'을 지나서 얇아 질 때 불일치가 사라지는 이유를 이해할 수 없습니다 . 큰 a하고 b, 나중에이 임계 값은 거짓말로 보이지만 항상 존재한다.

np.dotND-ND 어레이에 대해 왜 부정확하고 일관성이 없는가? 관련 힘내


추가 정보 :

  • 환경 : Win-10 OS, Python 3.7.4, Spyder 3.3.6 IDE, Anaconda 3.0 2019/10
  • CPU : i7-7700HQ 2.8GHz
  • Numpy v1.16.5

가능한 원인 라이브러리 : NumPy와 MKL - 또한 브래스 라이브러리; 주목 해 주신 Bi Rico 에게 감사합니다


스트레스 테스트 코드 : 언급 한 바와 같이, 더 큰 어레이의 주파수에서 불일치가 악화된다. 위의 내용을 재현 할 수없는 경우 아래 값을 지정해야합니다 (그렇지 않은 경우 더 큰 값을 입력하십시오). 내 결과

np.random.seed(1)
a = (0.01*np.random.randn(9, 9999)).astype('float32') # first multiply then type-cast
b = (0.01*np.random.randn(9999, 6)).astype('float32') # *0.01 to bound mults to < 1

for i in range(1, len(a)):
    print(list(np.dot(a[:i], b)[0]))

문제 심각도 : 불일치가 '소규모'이지만 수십억 개의 숫자가 몇 초 동안 곱해지고 전체 런타임에서 수조에 이르는 신경망에서 작동하는 경우 더 이상 그렇지 않습니다. 보고 된 모델 정확도는 이 스레드 당 전체 10 %에 따라 다릅니다 .

아래는 기본적으로 a[0]w / len(a)==1vs. 모델에 공급하여 얻은 배열의 gif입니다 len(a)==32.


Paul 의 테스트 덕분에 다른 플랫폼 결과 :

사례 1 재생산 (일부) :

  • Google Colab VM-Intel Xeon 2.3 G-Hz-Jupyter-Python 3.6.8
  • Win-10 Pro Docker Desktop-Intel i7-8700K-Jupyter / Scipy-notebook-Python 3.7.3
  • 우분투 18.04.2 LTS + 도커-AMD FX-8150-jupyter / scipy-notebook-Python 3.7.3

참고 : 위의 것보다 훨씬 낮은 오류가 발생합니다. 첫 번째 행의 두 항목은 다른 행의 해당 항목에서 최소 유효 숫자가 1만큼 줄어 듭니다.

사례 1이 재현되지 않음 :

  • 우분투 18.04.3 LTS-인텔 i7-8700K-IPython 5.5.0-파이썬 2.7.15+ 및 3.6.8 (2 테스트)
  • 우분투 18.04.3 LTS-인텔 i5-3320M-IPython 5.5.0-파이썬 2.7.15+
  • 우분투 18.04.2 LTS-AMD FX-8150-IPython 5.5.0-Python 2.7.15rc1

참고 사항 :

  • 연결 Colab 노트북 및 jupyter 환경 내 시스템에서 관찰되는 것보다 (단지 처음 두 행에) 훨씬 적은 차이를 보여줍니다. 또한 사례 2는 (아직도) 부정확성을 나타내지 않았습니다.
  • 이 매우 제한된 샘플 내에서 현재 (Dockerized) Jupyter 환경은 IPython 환경보다 더 취약합니다.
  • np.show_config()게시하기에는 너무 길지만 요약하면 IPython 환경은 BLAS / LAPACK 기반입니다. Colab은 OpenBLAS 기반입니다. IPython Linux 환경에서 BLAS 라이브러리는 시스템에 설치됩니다. Jupyter 및 Colab에서는 / opt / conda / lib에서 제공됩니다.

업데이트 : 허용 된 답변은 정확하지만 광범위하고 불완전합니다. 코드 수준 에서 동작을 설명 할 수있는 사람 , 즉에 의해 사용되는 정확한 알고리즘 np.dot과 위의 결과에서 관찰 된 '일관된 불일치'를 설명하는 방법에 대한 질문은 여전히 ​​열려 있습니다 (의견 참조). 내 해독을 넘어서는 직접 구현이 있습니다 : sdot.c - arraytypes.c.src


의견은 긴 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
Samuel Liew

ndarrays일반적으로 숫자 정밀도 손실을 무시 하는 일반적인 알고리즘입니다 . 단순성 때문에 reduce-sum각 축을 따라 작업 순서가 최적의 순서가 아닐 수 있습니다 ... 정밀 오차를 염두에두면 다음을 사용할 수도 있습니다.float64
Vitor SRG

내일 검토 할 시간이 없을 수도 있으므로 지금 현상금을 수여하십시오.
Paul

@Paul 그것은 어쨌든 가장 높은 투표를받은 답변에 자동으로 수여 될 것입니다. 그러나 알림, 감사합니다
OverLordGoldDragon

답변:


7

피할 수없는 숫자 부정확 한 것처럼 보입니다. 설명한 바와 같이, 여기서 , NumPy와 행렬 승산을위한 고도로 최적화 신중하게 조정 BLAS 방법을 사용 . 이것은 아마도 행렬의 크기가 변할 때 2 개의 행렬을 곱한 일련의 연산 (합계 및 곱)이 변경됨을 의미합니다.

더 명확하게하기 위해, 수학적으로 결과 행렬의 각 요소는 두 벡터의 등가 길이 (등 길이의 숫자 시퀀스) 로 계산 될 수 있다는 것을 알고 있습니다 . 그러나 이것은 NumPy가 결과 행렬의 요소를 계산하는 방법 이 아닙니다 . 사실 Strassen 알고리즘 과 같이 행 열 내적을 직접 계산하지 않고 동일한 결과를 얻는 더 효율적이지만 복잡한 알고리즘이 있습니다.

소자하더라도 같은 알고리즘을 사용하는 경우 C의 IJ 결과적인 행렬에서의 C가 = AB는 수학적의 내적으로 정의되는 i 번째 행 과 j 번째 열에 B 에는 승산 매트릭스 경우 A2는 데 동일한 제 i 같은 행 행렬과 B2가 동일 갖는 j 번째 같은 열 B는 , 소자 C2는 IJ는 실제로 전체의 동작에 의존하는 다른 서열 (아래 계산한다 A2B2를 다른 숫자 오류로 이어질 수 있습니다.

그 이유의,해도 수학적 C IJ = C2 IJ (케이스 1에서와 같이), 동작의 다른 시퀀스는 상이한 수치의 오차 리드 (인해 행렬의 크기 변화) 계산의 알고리즘 하였다. 수치 오류는 또한 환경에 따라 약간 다른 결과를 설명하며 경우에 따라 일부 환경에서는 수치 오류가 없을 수도 있습니다.


2
링크에 감사드립니다. 관련 정보가 포함되어있는 것 같습니다. 그러나 질문 아래에 의견을 표현한 것이므로 답변이 더 자세 할 수 있습니다. 예를 들어, 연결된 SO는 직접 C코드를 보여주고 알고리즘 수준의 설명을 제공하므로 올바른 방향으로 향하고 있습니다.
OverLordGoldDragon 2

문제의 하단에 도시 된 바와 같이, 또한, "불가피"아니다 - 및 부정확성의 범위는 설명되지 않은 남아있는 환경에서 변화
OverLordGoldDragon

1
@OverLordGoldDragon : (1) 더하기 : take number n, 마지막 가수 가수 k의 정밀도보다 낮은 수를 취하는 간단한 예입니다 k. 파이썬의 네이티브 수레 n = 1.0k = 1e-16작동합니다. 이제하자 ks = [k] * 100. 그 참조 sum([n] + ks) == n하면서, sum(ks + [n]) > n즉, 요약 순서가 중요. (2) 최신 CPU에는 FP (floating-point) 연산을 병렬로 실행할 수있는 여러 장치가 있으며 a + b + c + d, 명령 a + bc + d머신 코드에서 선행 하더라도 CPU에서 계산 되는 순서는 정의되지 않습니다 .
9000

1
@OverLordGoldDragon 프로그램에서 처리하도록 요청하는 대부분의 숫자는 부동 소수점으로 정확하게 표현할 수 없습니다. 시도하십시오 format(0.01, '.30f'). 간단한 숫자조차도 0.01NumPy 부동 소수점으로 정확하게 표현할 수 없다면, NumPy 행렬 곱셈 알고리즘의 깊은 세부 사항을 알 필요가 없습니다. 즉, 시작 행렬다르면 연산 순서달라지기 때문에 수치 적으로 인해 수학적으로 동일한 결과가 조금씩 다를 수 있습니다.
mmj

2
@OverLordGoldDragon re : 흑 마법. 몇 가지 CS MOOC에서 읽을 수있는 논문이 있습니다. 내 기억이 그렇게 좋지는 않지만, 이것이 생각합니다 : itu.dk/~sestoft/bachelor/IEEE754_article.pdf
Paul
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.