[chapter 3] 벡터 응용 : 데이터분석에서의 벡터
3-1
두 벡터를 입력으로 받아 피어슨 상관계수, 코사인 유사도를 출력하는 파이썬 함수를 작성하기.
def corrAndCosine(x, y):
num = np.dot(x, y) # 내적
den = np.linalg.norm(x) * np.linalg.norm(y) # 두 벡터 노름의 곱
cos = num / den
xm = x - np.mean(x)
ym = y - np.mean(y)
num = np.dot(xm, ym)
den = np.linalg.norm(xm) * np.linalg.norm(ym)
cor = num / den
return cor, cos
a = np.random.randn(15)
b = np.random.randn(15)
r, s = corrAndCosine(a, b)
print(r, s, np.corrcoef(a,b)[0,1])
3-2
상관관계와 코사인 유사도의 차이를 살펴보는 문제
0~3까지의 정수를 가진 변수와 이 변수에 특정 오프셋을 더한 두 번째 변수를 만들고, 시스템적으로 오프셋을 -50에서 50까지 변경하는 시뮬레이션을 만들기
두 변수간의 상관관계와 코사인 유사도를 계산하고, 평균 오프셋이 상관 관계와 코사인 유사도에 어떤 영향을 주는 그래프로 파악하기.
즉, 코사인 유사도는 평균 변화에 민감하고, 피어슨 상관계수는 평균 변화에 둔감하다는 사실을 증명하기 위한 문제
# create the variables
a = np.arange(4,dtype=float)
offsets = np.arange(-50,51)
results = np.zeros((len(offsets),2))
for i in range(len(offsets)):
results[i,:] = corrAndCosine(a,a+offsets[i])
# plot the results!
plt.figure(figsize=(8,4))
h = plt.plot(offsets,results)
h[0].set_color('k')
h[0].set_marker('o')
h[1].set_color([.7,.7,.7])
h[1].set_marker('s')
plt.xlabel('Mean offset')
plt.ylabel('r or c')
plt.legend(['Pearson','Cosine sim.'])
plt.savefig('Figure_03_02.png',dpi=300) # write out the fig to a file
plt.show()
3-3
피어슨 상관계수를 계산하는 함수 pearsonr, SciPy의 stats 모듈의 소스코드를 열어서 파이썬 구현을 살펴보기
(생략)
3-4
상관관계 직접 구하는 함수를 만들어서 NumPy의 corrcoef와 걸리는 시간 비교해보기
from scipy.stats import pearsonr
def corrAndCosine(x,y):
xm = x-np.mean(x)
ym = y-np.mean(y)
num = np.dot(xm,ym) # numerator
den = np.linalg.norm(xm) * np.linalg.norm(ym) # denominator
cor = num / den
return cor
import time
numIters = 1000
varLength = 500
tic = time.time()
for i in range(numIters):
v = np.random.randn(varLength, 2)
corrAndCosine(v[:,0], v[:,1])
t1 = time.time() - tic
tic = time.time()
for i in range(numIters):
x = np.random.randn(varLength, 2)
np.corrcoef(x[:,0], x[:,1])
t2 = time.time() - tic
print(f'My function took {t1*1000:.2f} ms') # My function took 64.32 ms
print(f' corrcoef took {t2*1000:.2f} ms') # corrcoef took 120.09 ms
3-5
에지 검출기의 커널은 [-1 +1] (여기서 엣지는 값이 갑자기 변하는 지점, 신호의 기울기가 큰 부분을 말한다.)
1. 두 시계열을 생성하는 코드 구현
import numpy as np
import matplotlib.pyplot as plt
kernel = np.array([-1, 1])
signal = np.zeros(30)
signal[10:20] = 1
_, axs = plt.subplots(1,2,figsize=(12, 4))
axs[0].plot(kernel, 'ks-')
axs[0].set_title('Kernel')
axs[0].set_xlim([-15, 15])
axs[1].plot(signal, 'ks-')
axs[1].set_title('Time series signal')
plt.savefig('Figure_03_04ab.png',dpi=300)
plt.show()
코드를 실행하면 아래의 이미지가 나온다.

2. 신호의 시점에 대해 for 루프를 작성한다. 각 시점에서 커널과 길이가 같은 시계열 데이터 조각과 커널 사이의 내적을 계산한다.
에지 검출기는 신호가 평평할 때 0, 신호가 상승할 때 +1, 신호가 하강할 때 -1을 반환하는 것을 확인하기
featureMap = np.zeros(len(signal))
for t in range(1, len(signal) - 1):
featureMap[t] = np.dot(kernel,signal[t-1:t+1])
_,axs = plt.subplots(1,2,figsize=(12,4))
axs[0].plot(kernel,'ks-')
axs[0].set_title('Kernel')
axs[0].set_xlim([-15,15])
axs[1].plot(signal,'ks-',label='Signal',linewidth=3)
markers,stemlines,_ = axs[1].stem(range(len(featureMap)),featureMap,
basefmt=' ',linefmt='',markerfmt='o',
label='Edge detection')
plt.setp(stemlines,'color',[.7,.7,.7])
plt.setp(markers,'color',[.7,.7,.7])
axs[1].legend()
plt.savefig('Figure_03_04c.png',dpi=300)
plt.show()

엣지는 변화가 큰 부분을 말하는데, 내적을 통해 구할 수 있다.
- 커널이 [-1 1]이고 신호가 [0 0] 이면 -1 * 0 + 1 * 0 = 0
- 커널이 [-1 1]이고 신호가 [0 1] 이면 -1 * 0 + 1 * 1 = 1
즉 신호가 [0 1]일 때 변화가 일어나 내적이 커졌고, 이를 엣지로 볼 수 있다.
3-6
목표는 울퉁불퉁한 시계열을 매끄럽게 만드는 것
시계열은 가우스 분포에서 생성된 100개의 난수이고, 커널은 가우스 함수에 근사하는 종 모양의 함수로 숫자 [0, .1, .3, .8, 1, .8, .3, .1, 0]으로 정의된다. 커널 합이 1이 되도록 조정되어야 한다.
필터링된 신호는 원래 신호의 매끄러운 형태 = 저주파 필터링
import numpy as np
import matplotlib.pyplot as plt
kernel = np.array([0, .1, .3, .8, 1, .8, .3, .1, 0]) # 제시된 커널
kernel = kernel / np.sum(kernel) # 중앙평균화
Nkernel = len(kernel)
halfKrn = Nkernel//2
Nsignal = 100
timeseries = np.random.randn(Nsignal) # 난수
filtsig = timeseries.copy() # # make a copy of the signal for filtering
for t in range(halfKrn+1,Nsignal-halfKrn):
filtsig[t] = np.dot(kernel,timeseries[t-halfKrn-1:t+halfKrn])
_,axs = plt.subplots(1,2,figsize=(12,4))
axs[0].plot(kernel,'ks-')
axs[0].set_title('Kernel')
axs[0].set_xlim([-1,Nsignal])
axs[1].plot(timeseries,color='k',label='Original',linewidth=1)
axs[1].plot(filtsig,'--',color=[.6,.6,.6],label='Smoothed',linewidth=2)
axs[1].legend()
plt.savefig('Figure_03_06c.png',dpi=300)
plt.show()

3-7
3-6에서 커널의 중앙의 1을 -1로 바꾸고 중앙평균화 한다.
그리고 다시 필터링하고 그래프를 그리면 더 날카로운 신호가 된다.
이 커널은 부드러운(낮은 주파수) 특징을 감쇄하고 빠르게 변화하는(높은 주파수) 특징을 강조하는 고주파 필터이다.
(코드 생략)

3-8
새로운 데이터를 생성하지 않고 k = 3을 사용해 k-평균 코드를 여러 번 실행하고 클러스터 결과가 유사한지 확인하기
1. 먼저 예제 데이터를 생성한다.
import numpy as np
import matplotlib.pyplot as plt
# 예제 데이터 생성
nPerClust = 50 # 각 클러스터에 50개 샘플
blur = 1 # 클러스터 주변으로 랜덤 퍼짐 정도(표준편차)
# 클러스터 중심 좌표
A = [1, 1]
B = [-3, 1]
C = [3, 3]
# 클러스터별 데이터 생성
a = [ A[0]+np.random.randn(nPerClust)*blur , A[1]+np.random.randn(nPerClust)*blur ]
b = [ B[0]+np.random.randn(nPerClust)*blur , B[1]+np.random.randn(nPerClust)*blur ]
c = [ C[0]+np.random.randn(nPerClust)*blur , C[1]+np.random.randn(nPerClust)*blur ]
# 데이터 합치기
# a,b,c 클러스터를 열 기준으로 이어붙임
# 총 샘플 수 = 50*3 = 150
# shape = (150,2)
data = np.transpose( np.concatenate((a,b,c),axis=1) )
# 시각
plt.plot(data[:,0],data[:,1],'ko',markerfacecolor='w')
plt.title('Raw (preclustered) data')
plt.xticks([])
plt.yticks([])
plt.show()

2. k-평균 클러스터링을 실행한다.
k = 3 # extract three clusters
ridx = np.random.choice(range(len(data)),k,replace=False)
centroids = data[ridx,:]
for iteri in range(3):
# step 1: 거리 계산
dists = np.zeros((data.shape[0],k))
for ci in range(k):
dists[:,ci] = np.sum((data-centroids[ci,:])**2,axis=1)
# step 2: 최소 거리로 그룹화
groupidx = np.argmin(dists,axis=1)
# step 3: 데이터들의 평균으로 중심을 교체
for ki in range(k):
centroids[ki,:] = [ np.mean(data[groupidx==ki,0]), np.mean(data[groupidx==ki,1]) ]
시각화하면 아래 이미지가 된다.

3-9
k=2로 바꾸면, 즉 클러스터링을 2개로 하면 아래의 이미지가 된다.

중앙 클러스터가 사라지고 왼쪽/오른쪽 데이터들이 각각 가까운 그룹으로 묶였다.
k=4로 바꾸면 아래의 이미지가 된다.

이렇게 k값이 2, 3, 4로 변함에 따라 아래의 사실을 알 수 있다.
- k < 실제 클러스터 수 이면, 데이터가 병합되고 잘못 할당이 될 수 있다.
- k = 실제 클러스터 수 이면, 가장 자연스러운 군집화가 이루어진다.
- k < 실제 클러스터 수 이면, 일부 centroid가 의미 없는 위치에 할당되어 군집이 쪼개진다.
즉, k를 적절히 설정하는 것이 매우 중요하다.
'수학 > 선형대수' 카테고리의 다른 글
| [개발자를 위한 실전 선형대수학] 4.3 표준 행렬 곱셈, 4.4 행렬 연산: 전치, 4.5 행렬 연산: LIVE EVIL(연산 순서), 4.6 대칭 행렬 (0) | 2025.12.05 |
|---|---|
| [개발자를 위한 실전 선형대수학] 4.1 NumPy에서 행렬 생성과 시각, 4.2 행렬 수학:덧셈, 스칼라 곱셈, 아다마르곱 (0) | 2025.12.02 |
| [개발자를 위한 실전 선형대수학] 3.1 상관관계와 코사인 유사도, 3.2 시계열 필터링과 특징 탐지, 3.3 k-평균 클러스터링 (0) | 2025.11.26 |
| [개발자를 위한 실전 선형대수학] Chapter 2 연습문제 (0) | 2025.11.21 |
| [개발자를 위한 실전 선형대수학] 2.4 부분공간과 생성, 2.5 기저 (0) | 2025.11.19 |