입력받은 2차원 배열 패턴과 필터간에 mac 연산을 통해 유사도 점수를 구해서 어떤 필터의 모양에 더 가까운지 계산하는 프로그램
파일은 main 과 npu, 2개로 이루어져있는데 main 은 상호작용과 출력같은 인터페이스 역할을 하고 npu 는 json 에서 필터나 패턴을 추출하거나 mac 연산을 하는 등 핵심 로직을 담당한다.
codyssey-e1-3/
├── main.py
├── npu.py
├── data.json
└── README.md
| 파일 | 역할 |
|---|---|
main.py |
메뉴, 사용자 입력, 결과 및 성능 표 출력 |
npu.py |
MAC 계산, 검증, JSON 분석, 패턴 생성, 성능 측정 |
data.json |
크기별 필터와 테스트 패턴 |
패턴과 필터의 같은 위치에 있는 값을 곱하고 그 결과를 모두 더하는 MAC(Multiply-Accumulate) 연산을 통해 점수를 계산한다
score = pattern[0][0] × filter[0][0]
+ pattern[0][1] × filter[0][1]
+ ...
3×3 Cross 패턴을 Cross 필터와 계산하면 점수는 5이고, X 필터와 계산하면 점수는 1이다. 패턴과 필터에 대한 두 점수를 각각 구한 다음 더 큰 쪽을 판정 결과로 사용한다. AI 모델은 큰 행렬에서 이런 MAC 연산을 수많이 반복하고, NPU는 여러 MAC 연산을 병렬로 처리하는 데 특화되어 있다.
그리고 실수는 컴퓨터 내부에서 정확히 표현되지 않을 수 있다.
그래서 두 점수의 차이가 1e-9보다 작으면 어느 한쪽이 더 크다고 보지 않고 UNDECIDED로 판정한다.
사용자 입력 모드에서는 같은 결과를 판정 불가라고 표시한다.
main.py, npu.py, data.json이 있는 프로젝트 폴더에서 실행한다.
python3 main.py세 가지 모드를 선택할 수 있는데 직접 입력, data.json 분석, 패턴 생성과 MAC 접근 방식 비교를 실행할 수 있다.
| 입력 | 모드 | 내용 |
|---|---|---|
1 |
사용자 입력 | 3×3 필터 A·B를 입력하고, 패턴은 직접 입력하거나 Cross/X로 자동 생성한다. |
2 |
JSON 분석 | data.json의 5×5, 13×13, 25×25 패턴을 일괄 분석한다. |
3 |
패턴 생성기 | 크기 N과 Cross/X를 입력해 패턴을 만들고 2차원·1차원 MAC 시간을 비교한다. |
0 |
종료 | 프로그램을 종료한다. |
각 모드의 실행이 끝나면 모드 선택 화면으로 돌아오므로, 종료할 때까지 여러 작업을 이어서 실행할 수 있다.
필터 A와 필터 B를 입력한 뒤 패턴 입력 방식을 선택한다.
패턴은 숫자 3개씩 직접 입력하거나, Cross·X 라벨을 입력해 3×3으로 자동 생성할 수 있다
직접 입력할 때는 각 행에 숫자 3개를 공백으로 구분해 적는다.
열 개수가 맞지 않거나 숫자가 아닌 값이 있으면 안내 메시지가 나오고, 입력 중이던 행렬을 처음부터 다시 받는다.
[패턴 입력 방식]
1. 직접 입력
2. Cross/X 자동 생성
선택: 2
패턴 종류 (Cross/X): X
아래처럼 A에 Cross 필터, B에 X 필터, 패턴에 X를 넣으면 A 점수는 1, B 점수는 5가 된다.
필터 A 필터 B 패턴
0 1 0 1 0 1 1 0 1
1 1 1 0 1 0 0 1 0
0 1 0 1 0 1 1 0 1
A 점수: 1.0000000000000000
B 점수: 5.0000000000000000
판정: B
마지막에는 입력 패턴과 필터 A의 mac()을 10회 측정해 3×3 MAC의 대표 시간을 출력한다.
필터 B도 점수와 판정을 구할 때 사용하지만 성능 측정에는 포함하지 않으며, 표의 연산 횟수는 필터 하나의 MAC을 기준으로 3² = 9이다.
data.json의 최상위 filters와 patterns를 읽는다.
패턴 키는 size_{N}_{idx} 형식이며, 여기서 꺼낸 N으로 size_N 필터를 찾는다.
JSON에 쓰인 표기는 계산 전에 다음 두 표준 라벨로 맞춘다.
| JSON의 값 | 프로그램 내부 라벨 |
|---|---|
expected의 +, 필터 키의 cross |
Cross |
expected와 필터 키의 x |
X |
같은 Cross를 expected에서는 +, 필터 키에서는 cross로 표현하므로 문자열을 그대로 비교하면 서로 다른 라벨로 처리될 수 있다.
이런 잘못된 FAIL을 막기 위해 계산과 정답 비교 전에 라벨을 Cross와 X로 통일한다.
패턴과 두 필터가 모두 N×N인지, 각 값이 숫자인지도 MAC 계산 전에 확인한다. JSON 모드의 경우 케이스의 키나 배열에 문제가 있으면 그 케이스만 FAIL로 기록하고 다음 케이스를 계속 처리한다.
케이스 하나가 처리되는 순서는 다음과 같다.
키에서 크기 확인 → 라벨 정규화 → 행렬 검증 → MAC 점수 계산
→ epsilon 기준 판정 → expected와 비교 → PASS/FAIL
같은 패턴 생성 함수는 사용자 입력 모드와 패턴 생성기 모드에서 함께 사용한다.
사용자 입력 모드에서는 3×3 패턴을 만들고 기존 A/B 점수·판정·성능 분석에 바로 사용한다.
패턴 생성기 모드에서는 1 이상의 크기 N과 Cross 또는 X를 입력한다.
Cross는 가운데 행과 열을 1로 만들고, X는 두 대각선을 1로 만든다.
짝수 크기의 Cross는 가운데가 한 칸으로 정해지지 않으므로 가운데 두 행과 두 열을 1로 만든다.
N = 3, Cross
0 1 0
1 1 1
0 1 0
생성한 2차원 패턴은 행 순서대로 읽어 길이 N²인 1차원 배열로 바꾼다.
0 1 0 1 1 1 0 1 0
그다음 같은 입력과 같은 반복 횟수로 기존 이중 반복문 MAC과 1차원 배열 MAC을 각각 측정한다.
이렇게 케이스별 크로스 & X 점수와 판정과 결과를 콘솔로 확인할 수 있다.
| 케이스 | Cross 점수 | X 점수 | 판정 | expected | 결과 |
|---|---|---|---|---|---|
size_5_1 |
0.9000000000000000 | 0.8999999999999999 | UNDECIDED |
X |
FAIL |
size_5_2 |
8.9000000000000004 | 0.1000000000000000 | Cross |
Cross |
PASS |
size_13_1 |
0.3000000000000000 | 14.7000000000000082 | X |
X |
PASS |
size_13_2 |
7.4999999999999973 | 7.5000000000000000 | UNDECIDED |
Cross |
FAIL |
size_25_1 |
4.9000000000000004 | 4.8999999999999986 | UNDECIDED |
X |
FAIL |
size_25_2 |
52.8999999999999986 | 0.1000000000000000 | Cross |
Cross |
PASS |
전체 6개 중 3개가 PASS이고 3개가 FAIL이다.
- 여섯 케이스 모두 키 형식과 행렬 크기가 맞아서 스키마 문제로 실패한 항목은 없다.
+,cross,x를 먼저 Cross와 X로 통일했기 때문에 표기 차이로 생긴 실패도 없다.- 나머지 세 케이스는 수학적으로 계산한 Cross와 X의 MAC 점수가 각각 0.9와 0.9, 7.5와 7.5, 4.9와 4.9로 같다.
- 하지만 Python의
float는 일부 소수를 정확히 표현하지 못하므로, 실제 합산 결과에는 약1e-16에서1e-15정도의 차이가 생긴다. - 이 차이는 모양의 차이가 아니라 부동소수점 표현 오차라서,
compare_scores()는 차이가1e-9보다 작으면UNDECIDED를 반환한다. data.json의 expected 원본은+또는x이고, 정규화하면 Cross 또는 X가 된다.- 동점인 세 케이스의 판정 결과는
UNDECIDED이지만,expected에는 Cross 또는 X가 지정되어 있어 일치하지 않아 모두 FAIL이 된다. - 세 FAIL은 MAC 계산이나 스키마 오류가 아니라 동점 판정과 정답 라벨 사이의 불일치에서 생겼다.
실패가 발생하면 검증 메시지, 계산된 점수, 두 점수의 차이 순서로 확인한다.
| 원인 | 판단 기준 | 확인하거나 고칠 부분 |
|---|---|---|
| 데이터·스키마 | 점수를 계산하기 전에 키, 라벨, 배열 크기 오류가 발생한다. | data.json의 키 형식, 필수 값, N×N 크기와 라벨을 수정한다. |
| 로직 | 검증은 통과했지만 점수나 판정 과정이 예상과 다르다. | 크기에 맞는 필터를 골랐는지, MAC 반복문과 Cross/X 연결이 맞는지 확인한다. |
| 수치 비교 | 두 점수의 차이가 epsilon보다 작아 UNDECIDED가 된다. |
부동소수점 오차인지 확인하고, epsilon과 expected의 동점 처리 기준이 서로 맞는지 검토한다. |
현재 세 FAIL은 세 번째 경우에 해당한다.
사용자 입력 모드와 JSON 분석 모드는 같은 measure_mac()으로 입출력을 제외한 필터 하나의 MAC을 10회 반복 측정한다.
구현 원리는 같고, 입력 데이터와 비교 범위만 다르다.
사용자 입력 모드는 방금 입력한 패턴과 필터 A의 3×3 MAC 시간을 측정하고, JSON 분석 모드는 준비된 대표 패턴과 Cross 필터로 여러 크기의 MAC 시간을 비교한다.
| 구분 | 사용자 입력 모드 | JSON 분석 모드 |
|---|---|---|
| 측정 목적 | 직접 입력하거나 자동 생성한 3×3 MAC 시간 확인 | 행렬 크기에 따른 MAC 시간 비교 |
| 측정 함수 | measure_mac() |
measure_mac() |
| 측정 데이터 | 직접 입력하거나 자동 생성한 패턴과 필터 A | 3×3 예제와 크기별 첫 번째 패턴·Cross 필터 |
| 입력 크기 | 3×3 고정 | 3×3, 5×5, 13×13, 25×25 |
| 출력 시간 | 필터 A MAC 10회 평균 | 크기별 Cross MAC 10회 평균 |
| 연산 횟수 기준 | 필터 하나의 N² |
필터 하나의 N² |
입력, 출력, 파일 읽기 시간을 제외하고 Cross 필터 하나의 mac()을 크기별로 10회 측정한 평균이다.
따라서 평균 시간과 연산 횟수 모두 필터 하나를 기준으로 하며, 연산 횟수는 N²이다.
| 크기 | 평균 MAC 시간(ms) | MAC 연산 횟수(N²) |
|---|---|---|
| 3×3 | 실행 환경에 따라 다름 | 9 |
| 5×5 | 실행 환경에 따라 다름 | 25 |
| 13×13 | 실행 환경에 따라 다름 | 169 |
| 25×25 | 실행 환경에 따라 다름 | 625 |
N×N 행렬에서 mac()은 모든 칸을 한 번씩 방문하므로 필터 하나의 연산 횟수는 N²이고 시간 복잡도는 O(N²)이다.
실제 판정은 Cross와 X 필터를 모두 계산하므로 위치별 연산이 2N²번 필요하지만, 과제의 성능 표는 필터 하나의 MAC과 N²을 기준으로 한다.
상수 2를 제외하면 전체 판정의 시간 복잡도도 O(N²)이다.
실제 측정 시간도 크기가 커질수록 증가했다.
현재 프로그램은 Python 반복문을 사용하므로 CPU가 행렬의 각 위치를 차례로 계산한다. 반면 NPU는 여러 MAC 연산을 동시에 처리하도록 만들어져 큰 행렬을 반복 계산할 때 유리하다.
다만 작은 행렬은 NPU로 데이터를 옮기고 연산을 준비하는 시간이 실제 계산 시간보다 더 클 수 있다. 큰 행렬에서도 메모리에서 데이터를 읽어 오는 속도가 계산 속도를 따라가지 못하면 병목이 생길 수 있다. NPU를 사용한다고 항상 빨라지는 것은 아니며 행렬 크기와 데이터 이동 비용도 함께 고려해야 한다.
행렬이 커지면 계산량은 N²에 비례하고, 패턴과 두 필터를 저장하는 메모리 사용량도 함께 증가한다.
현재 코드는 JSON 파일 전체를 한 번에 읽고 Python 반복문으로 계산하므로 매우 큰 데이터에는 비효율적일 수 있다.
대형 데이터를 처리하려면 다음과 같은 방법을 고려할 수 있다.
- 예를 들어 10,000×10,000 행렬 전체를 한꺼번에 읽지 않고 100행씩 읽어 부분 점수를 구한 뒤 합산한다.
- 여러 패턴을 처리할 때는 한 패턴의 계산이 끝날 때마다 해당 데이터를 정리해 메모리를 다음 패턴에 재사용한다.
- Python의 이중 반복문 대신 NumPy의 배열 곱셈과
sum()을 사용해 여러 위치의 MAC 연산을 한 번에 처리한다. - 여러 패턴에 같은 필터를 적용한다면 필터를 처음 한 번만 메모리에 올려 두고 반복해서 사용한다.
예를 들어 triangle을 추가한다면 다음 순서로 확장한다.
- 데이터에 크기별
triangle필터와triangle정답 라벨을 추가한다. - 대소문자가 달라도 같은 라벨로 처리하도록 표준 라벨에
Triangle을 등록한다. - 입력 패턴과
Triangle필터의 MAC 점수도 기존 라벨과 같은 방식으로 계산한다. - 두 라벨간의 비교가 아니라, 모든 라벨의 점수를 비교해 가장 높은 라벨을 선택하고, 비슷한 점수는
UNDECIDED로 처리한다. - 출력에 새 라벨의 점수가 표시되는지 확인하고 정상 판정, 필터 누락, 동점 상황을 테스트한다.
데이터 추가 → 라벨 인식 → 점수 계산과 판정 → 결과 확인