기초적인 텐서 연산에 대해 공부했다.
곱, 더하기, 아마다르 곱, 리덕션, 내적을 배웠으며
실제 사용되는 텐서연산은 보다 다양한 종류가 있다고 한다.
여기서 말하는 곱은 텐서끼리의 곱이 아닌, 텐서와 스칼라의 곱이다.
스칼라는 텐서의 모양을 유지하면서 모든 요소에 더해지거나 곱해진다.
X = np.array([[25, 2], [5, 26], [3, 7]])
X_pt = torch.tensor(X)
X_pt2 = torch.tensor([[25, 2], [5, 26], [3, 7]])
# 스칼라는 텐서의 모양을 유지하면서 모든 요소에 더해지거나 곱해진다.
print(X * 2)
print(X + 2)
print(X * 2 + 2)
print(X_pt)
print(X_pt2)
print(X_pt * 2 + 2) # 파이썬의 *, + 연산자가 오버로드된다.
print(torch.add(torch.mul(X_pt, 2), 2)) # 위 코드랑 같은 코드
위와 같이 *나 +연산자를 사용하여 간단하게 텐서연산을 사용할 수 있으며,
이게 가능한 이유는 위 연산자가 파이썬에서 오버로드되기 때문에
텐서와 스칼라 연산에 맞게 그 기능이 변경되어서 그렇다고 한다.
겉보기엔 그냥 곱하기와 더하기 기호지만 torch.add()나 torch.mul()과 같은 기능을 하고 있다는 것이다.
동일한 크기의 텐서끼리 각 위치에 대응하는 원소를 곱해주는 연산.
위 코드에서 이어서 아래와 같이 작성해서 테스트했다.
A = X + 2
print(A + X)
print(A * X) # 아다마르 곱 연산, 행렬의 각 원소끼리의 곱
A_pt = X_pt + 2
print(A_pt * X_pt) # PyTorch에서의 아다마르 곱 연산
위 코드를 보면 X와 shape가 같은 A를 만들어서 *연산자로 둘을 곱해준다.
그러면 numpy에서 둘의 모양이 같은 것을 인식하고 아다마르 곱 기능을 오버라이드 하는 것으로 보인다.
shape가 다르면 애초에 오류가 뜬다.
그러나 이전에 numpy 배열에 대해 공부했던 글에서 알아본 broadcasting 기술이 여기서 사용될 수 있다.
(1, 3)shape의 텐서 A와 (3, 1)shape의 텐서 B를 * 연산자로 곱해주면 아래와 같은 결과가 나온다.
C = np.array([[1, 2, 3]]) # (1, 3)
D = np.array([[4], [5], [6]]) # (3, 1)
print(f"* 결과 : \n{C * D}")
"""
* 결과 :
[[ 4 8 12]
[ 5 10 15]
[ 6 12 18]]
"""
모양이 달라도 broadcasting 조건을 충족하기 때문에 stretched 되어서 배열 모양이 자동으로 맞춰진 것이다.
C는 [[1, 2, 3], [1, 2, 3], [1, 2, 3]]으로
D는 [[4, 4, 4], [5, 5, 5], [6, 6, 6]]으로 brocasting 되어
같은 모양이 됐으므로 아다마르 곱이 가능해진다.
이 또한 NumPy님의 은혜겠지요...