대학원 공부/programming language

Python : TypeError: unhashable type: 'numpy.ndarray'

월곡동로봇팔 2024. 5. 15. 11:09

사실 TypeError: unhashable type: 'numpy.ndarray' error는 정말 다양한 곳에서 일어난다. 직역해보자면, numpy.array 형태는 hash가 안된다는 이야기다. 과연 나는 어디서 이런 error가 발생했는지 적어보려한다.

Main code : pygmo를 활용하여 hypervolume 계산

나는 pygmo라는 라이브러리를 활용하여 multi-objective optimization을 진행할 때 필요한, hypervolume을 계산하고자 하였다. 밑에가 내가 작성한 코드이다.

import functools
import pygmo
import numpy as np

# hypervolume을 계산하는 함수에 캐싱 적용
@functools.lru_cache(maxsize=None)
def calculate_hypervolume(obj_y:np.ndarray, reference:np.ndarray):
    y_points = obj_y.tolist() if isinstance(obj_y, np.ndarray) else obj_y
    reference = reference.tolist() if isinstance(reference, np.ndarray) else reference
    hv = pygmo.hypervolume(y_points)
    return hv.compute(reference)

# hypervolume을 계산하는 함수에 캐싱 적용
@functools.lru_cache(maxsize=None)
def calculate_contributions(obj_y:np.ndarray, reference:np.ndarray):
    y_points = obj_y.tolist() if isinstance(obj_y, np.ndarray) else obj_y
    reference = reference.tolist() if isinstance(reference, np.ndarray) else reference
    hv = pygmo.hypervolume(y_points)
    return hv.contributions(reference)

 

# 다양한 해를 담은 리스트 (각 해는 하나의 포인트로 표현됨)
true_obj_y = np.array([
    [-4, -1],
    [-3, -2],
    [-2, -3], 
    [-1, -4], 
    [-1, -1], 
    [-1, -2], 
    [-2, -2], 
    [-4, -0]
])


# Pareto pareto_front를 구성하는 해를 담은 리스트
pareto_front = np.array([
    [-5, -1], [-3, -2], [-2, -3], [-1, -4]
])

# hypervolume을 계산하기 위해 필요한 reference point
reference = np.array([0, 0])  # 임의로 설정한 reference point

# 캐시를 통해 이미 계산된 결과를 사용하거나, 새로 계산하여 결과를 반환
print("true_obj_y.shape", true_obj_y.shape)
print("reference.shape", reference.shape)
true_obj_y_volume = calculate_hypervolume(true_obj_y, reference)
pareto_front_volume = calculate_hypervolume(pareto_front, reference)
print("Hypervolume (solution):", true_obj_y_volume)
print("Hypervolume (pareto_front):", pareto_front_volume)

true_obj_y_contributions = calculate_contributions(true_obj_y, reference)
pareto_front_contributions = calculate_contributions(pareto_front, reference)
print("Hypervolume (solution):", true_obj_y_contributions)
print("Hypervolume (pareto_front):", pareto_front_contributions)

Error 명 : TypeError: unhashable type: 'numpy.ndarray'

Traceback (most recent call last):
  File "test_pygmo.py", line 127, in <module>
    true_obj_y_volume = calculate_hypervolume(true_obj_y, reference)
TypeError: unhashable type: 'numpy.ndarray'

 

lru_cache를 사용하려면 함수의 인자로 전달되는 값들이 해시 가능한 타입이어야 합니다. 그러나 numpy.ndarray 타입은 해시가 불가능하기 때문에 문제가 발생합니다. 이를 해결하기 위해 numpy.ndarray를 해시 가능한 타입으로 변환할 필요가 있습니다. 일반적으로 튜플로 변환하는 방법을 사용합니다.

해결책1 : lru_cache 삭제

import functools
import pygmo
import numpy as np

# hypervolume을 계산하는 함수에 캐싱 적용
# @functools.lru_cache(maxsize=None)
def calculate_hypervolume(obj_y:np.ndarray, reference:np.ndarray):
    y_points = obj_y.tolist() if isinstance(obj_y, np.ndarray) else obj_y
    reference = reference.tolist() if isinstance(reference, np.ndarray) else reference
    hv = pygmo.hypervolume(y_points)
    return hv.compute(reference)

# hypervolume을 계산하는 함수에 캐싱 적용
# @functools.lru_cache(maxsize=None)
def calculate_contributions(obj_y:np.ndarray, reference:np.ndarray):
    y_points = obj_y.tolist() if isinstance(obj_y, np.ndarray) else obj_y
    reference = reference.tolist() if isinstance(reference, np.ndarray) else reference
    hv = pygmo.hypervolume(y_points)
    return hv.contributions(reference)

 

해결책2 : array → tuple로 변환하는 함수 활용

def make_hashable(arr):
    """Convert numpy array to a hashable type (tuple)."""
    return tuple(map(tuple, arr))

@functools.lru_cache(maxsize=None)
def calculate_hypervolume(obj_y, reference):
    # Convert numpy arrays to hashable tuples
    y_points = make_hashable(obj_y) if isinstance(obj_y, np.ndarray) else obj_y
    reference = make_hashable(reference) if isinstance(reference, np.ndarray) else reference
    hv = pygmo.hypervolume(y_points)
    return hv.compute(reference)

@functools.lru_cache(maxsize=None)
def calculate_contributions(obj_y, reference):
    # Convert numpy arrays to hashable tuples
    y_points = make_hashable(obj_y) if isinstance(obj_y, np.ndarray) else obj_y
    reference = make_hashable(reference) if isinstance(reference, np.ndarray) else reference
    hv = pygmo.hypervolume(y_points)
    return hv.contributions(reference)

 

결과물

true_obj_y.shape (8, 2)
reference.shape (2,)
Hypervolume (solution): 10.0
Hypervolume (pareto_front): 11.0
Hypervolume (solution): [1. 1. 1. 1. 0. 0. 0. 0.]
Hypervolume (pareto_front): [2. 1. 1. 1.]