일단 필자가 아는 내용은, 파이썬 Ctypes에서는 2차원 배열(행렬)을 C로 넘겨줄 때, 1차원 배열(벡터)을 넘겨준다는 것이다.
넘겨받을 때는 다시 행렬로 쓸 수 있도록 해준다.이 문제 때문에 그 1차원 배열을 넘겨받은 C 함수에서 2차원 행렬 인자들을 가지고
벡터를 인덱싱을 하려면 곤란한 점이 발생한다. 이렇게 되면 정상적인 3차원 배열 인덱싱하는 것보다 훨씬 어려워진다. 3차원 공간에서 인자들의 값 변화를 살펴보며 그 규칙성을 찾기란 그리 어렵지 않은데, 2차원 배열의 4~5개 인덱스를 가지고 1차원 배열에서 규칙성을 찾아내려면 골이 조금 아프다. 그래도 불가능한 것은 아니니 한번 해보는 것도 재미있을 듯. 옛날처럼 담배를 폈더라면
편두통 때문에 끝을 보지 못했을 것 같은데, 집중력과 건강이 좋아진 것도 같다.
내일은 필자가 쓴 directConvolution3D1F( ) 파이썬 함수에 이 녀석을 붙여봐야겠다. 속도가 얼마나 빨라질는지.
사실, 합성곱을 공식대로 구하면, im2col 방식보다 많이 느리다는 선입견을 없애고 싶었다. C언어로 짜서 다년간 최적화된 numpy.dot와 numpy.transpose 함수로 대량 데이터를 한꺼번에 처리하는 것이, 느려터진 파이썬코드로 4~5중 for 문 돌리는 것보다 빠른 것은 당연하다. 기회는 균등해야 한다. Ctypes을 통해 C코드를 돌려보면 속도 차이는 크게 나지 않을 것이라 생각한다.
Ctypes에서 C 라이브러리를 다루는 데 오버헤드가 발생하겠지만 그래도 C코드이니, im2col 방식에 근접하지 않을까 하는 기대가 생긴다.
// directConvolve2d.c
// Writen by Don Han 2021.3.11 http://blog.naver.com/madrabbit7
// 2차원 배열(행렬)에 필터를 가해 공식대로 합성곱을 구하는, 파이썬 Ctypes를 위한 C 함수
// 파이썬 Ctypes에서 행렬을 넘겨주면 C에서는 1차원 배열로 읽히는 문제가 있다.
// 따라서 1차원 배열을 2차원 인자들로 복잡한 인덱싱을 하기가 쉽지 않다.
// 편두통 있으신 분들은 병원에 실려갈 수 있으니 조심하시고,
// 나도 참 집중력이 좋아졌다. 이걸 포기하지 않고 끝까지 잡도리를 하니...
#include <stdio.h>
void directConvolve2d(double *src, int xh, int xw, double *f, int fh, int fw,
double *dst, int stride) {
int oh = 1 + (xh-fh)/stride;
int ow = 1 + (xw-fw)/stride;
for (int i = 0; i < oh; i++) {
for (int j = 0; j < ow; j++) {
//start는 필터 좌측 상단에 닿는 입력 데이터(src가 포인팅하는 x)의 일차원 배열 인덱스이다.
//start에서 필터 너비만큼 우측으로, 아래측으로는 필터 높이만큼 내려가면서
// 그 합을 모두 더해 해당 쉘의 값을 구한다.
// stride를 떼고 보면 그나마 이해할 수 있을 것이다.
int start = i*stride * xw + j*stride;
//np.arange(0, N)으로 숫자를 발생시켜 나열하면 그 숫자가 곧 인덱스가 되므로,
// 연필로 그려가며 좌측 상단 인덱스를 확인할 수 있다.
// 일단 start 위치를 구하면 절반 이상 해결한 셈이다.
//printf("start = %d\n", start);
for (int k = 0; k < fh; k++) {
for (int l = 0; l < fw; l++) {
// output 값을 저장하는 dst의 인덱스는 0부터 하나씩 증가한다.
// 2차원 배열 인덱스를 조합하여 1차원을 인덱싱하는데, 나열해보면 0, 1, 2, 3... 식으로
// 그냥 증가하는 식이다. 그 값을 다른 식으로는 유추할 수 없기에 i와 j, 두 인덱스와 ow 값을 이용하는 것이다.
dst[i * ow + j] += src[start + l + xw * k] * f[k * fw + l];
}
}
}
}
}
#!/usr/bin/env python3
# numpy_3.py
# 제일 첫줄에 위처럼 적어두고, chmod +x 파일이름,을 해두면 파이썬 파일을 명령행에서
# 실행파일처럼 실행시킬 수 있다. 일일이 $ python 파일이름, 식으로 100번 정도 실행시키면 성질난다.
import ctypes
import numpy as np
import sys
# 지수 표현을 억제하는 옵션이다. 지수 표현이 나오면 한눈에 알아볼 수가 없다.
np.set_printoptions(precision=0, suppress=True)
# 일단 데이터는 9 x 9 행렬에, 3 x 3 필터, 혹은 2 x 2필터에 stride를 1, 2, 3식으로 다양하게 적용해보면서,
# scipy의 signal.correlate2d()함수와 필자가 만든 hcorrelate2d 함수, directConvolution3D1F()
# 함수들을 사용하여 값을 대조하여 정확성을 확인했다.
# float64임을 명시하여 애매한 점을 없앴다. 현실적으로 보면, 파이썬 기본 int 자료형이 int64로
# 기본 되어 있을 가능성이 있고, C에서 int32가 기본으로 되어 있을 가능성이 있기에 int형 자료형을 조심하자.
x = np.arange(0, 9*9, dtype=np.float64)
x = x.reshape(9, 9)
xh, xw = x.shape
filter = np.array([[1, 0, 1], [1, 0, 1], [1, -1, 0]], dtype=np.float64)
#filter = np.array([[1, 0], [0, 1]], dtype=np.float64)
fh, fw = filter.shape
# stride 설정은 correlate2d 함수에서 지원하지 않아서 값 대조는 필자가 짠 함수, directConvolution3D1F()
# 함수와 대조했다. directConvolution3D1F() 함수의 출력값은 '밑바닥 딥러닝' 책에 나온 Convolution 함수 출력값과
# 대조하여 정확성을 확인했다.
stride = 2
oh = 1 + int((xh-fh)/stride)
ow = 1 + int((xw-fw)/stride)
# oh, ow을 여기서 0으로 초기화 하고 있으므로 C 함수에서 굳이 0으로 다시 초기화할 필요는 없다.
out = np.zeros((oh, ow), dtype=np.float64)
c_lib = ctypes.CDLL("./directConvolve2d.so")
#c_lib.argtypes = [ctypes.POINTER(ctypes.c_double), \
# ctypes.POINTER(ctypes.c_double), ctypes.c_int, ctypes.c_int, ctypes.c_int]
#c_lib.restype = None
# double 형 자료형 세 가지 배열에 대해 Ctypes 타입 자료형을 적용한다. 적용해봤자 포인터지만.
src = x.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
f = filter.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
dest = out.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
# src는 원본 데이터에 대한 Ctypes의 포인터, xh, xw은 원본 데이터 shape, f는 필터 포인터,
# fh, fw은 필터의 shape, dest는 out을 저장할 배열 포인터, stride는 보폭이다.
c_lib.directConvolve2d(src, xh, xw, f, fh, fw, dest, stride)
# 처리를 마쳤으면 합성곱 결과가 out에 저장되어 있다.
print(x)
print("----------------")
print(out)
[실행 결과]
[[ 0. 1. 2. 3. 4. 5. 6. 7. 8.]
[ 9. 10. 11. 12. 13. 14. 15. 16. 17.]
[18. 19. 20. 21. 22. 23. 24. 25. 26.]
[27. 28. 29. 30. 31. 32. 33. 34. 35.]
[36. 37. 38. 39. 40. 41. 42. 43. 44.]
[45. 46. 47. 48. 49. 50. 51. 52. 53.]
[54. 55. 56. 57. 58. 59. 60. 61. 62.]
[63. 64. 65. 66. 67. 68. 69. 70. 71.]
[72. 73. 74. 75. 76. 77. 78. 79. 80.]]
-------------------
[[ 21. 29. 37. 45.]
[ 93. 101. 109. 117.]
[165. 173. 181. 189.]
[237. 245. 253. 261.]]