10wk-2: 추천시스템 (1) – optimizer 사용 고급, MF-based 추천시스템

Author

최규빈

Published

May 8, 2024

1. 강의영상

2. Imports

import torch
import pandas as pd
import matplotlib.pyplot as plt

3. 예비학습: optimizer 사용 고급

- 주어진 자료가 아래와 같다고 하자.

torch.manual_seed(43052)
x,_ = torch.randn(100).sort()
x = x.reshape(-1,1)
ones= torch.ones(100).reshape(-1,1)
X = torch.concat([ones,x],axis=-1)
ϵ = torch.randn(100).reshape(-1,1)*0.5
y = 2.5+ 4*x + ϵ
plt.plot(x,y,'o')

- 문제1: 아래와 같이 최초의 직선을 생성하였다.

What = torch.tensor([[-5.0],[10.0]],requires_grad=True)
plt.plot(x,y,'o')
plt.plot(x,X@What.data,'--')

torch.optim.SGD를 이용하여 What을 update하라. 학습률은 0.1로 설정하고 30회 update하라.

# net  
loss_fn = torch.nn.MSELoss()
optimizr = torch.optim.SGD([What],lr=0.1)
#--#
for epoc in range(30):
    # step1 
    yhat = X@What 
    # step2
    loss = loss_fn(yhat,y)
    # step3 
    loss.backward()
    # step4 
    optimizr.step()
    optimizr.zero_grad()
plt.plot(x,y,'o')
plt.plot(x,X@What.data,'--')

- 문제2: 아래와 같이 최초의 직선을 생성하였다.

w = torch.tensor(10.0,requires_grad=True)
b = torch.tensor(-5.0,requires_grad=True)
plt.plot(x,y,'o')
plt.plot(x,(x*w + b).data,'--')

torch.optim.SGD를 이용하여 What을 update하라. 학습률은 0.1로 설정하고 30회 update하라.

# net  
loss_fn = torch.nn.MSELoss()
optimizr = torch.optim.SGD([w,b],lr=0.1)
#--#
for epoc in range(30):
    # step1 
    yhat = x*w+b 
    # step2
    loss = loss_fn(yhat,y)
    # step3 
    loss.backward()
    # step4 
    optimizr.step()
    optimizr.zero_grad()
plt.plot(x,y,'o')
plt.plot(x,(x*w + b).data,'--')

4. MF-based 추천시스템

ref: https://namu.wiki/w/나는%20SOLO

A. Data: 나는 SOLO

- Data

df_view = pd.read_csv('https://raw.githubusercontent.com/guebin/DL2024/main/posts/solo.csv',index_col=0)
df_view
영식(IN) 영철(IN) 영호(IS) 광수(IS) 상철(EN) 영수(EN) 규빈(ES) 다호(ES)
옥순(IN) NaN 4.02 3.45 3.42 0.84 1.12 0.43 0.49
영자(IN) 3.93 3.99 3.63 3.43 0.98 0.96 0.52 NaN
정숙(IS) 3.52 3.42 4.05 4.06 0.39 NaN 0.93 0.99
영숙(IS) 3.43 3.57 NaN 3.95 0.56 0.52 0.89 0.89
순자(EN) 1.12 NaN 0.59 0.43 4.01 4.16 3.52 3.38
현숙(EN) 0.94 1.05 0.32 0.45 4.02 3.78 NaN 3.54
서연(ES) 0.51 0.56 0.88 0.89 3.50 3.64 4.04 4.10
보람(ES) 0.48 0.51 1.03 NaN 3.52 4.00 3.82 NaN
하니(I) 4.85 4.82 NaN 4.98 4.53 4.39 4.45 4.52

- 데이터를 이해할 때 필요한 가정들 – 제가 마음대로 설정했어요..

  • 궁합이 잘맞으면 5점, 잘 안맞으면 0점 이다.
  • MBTI 성향에 따라서 궁함의 정도가 다르다. 특히 I/E의 성향일치가 중요하다.
  • 하니는 모든 사람들과 대체로 궁합이 잘 맞는다.
  • 하니는 I성향의 사람들과 좀 더 잘 맞는다.

B. Fit / Predict

- 목표: NaN을 추정

- 수동추론: 그럴듯한 숫자를 추정해보자.

  • 옥순(IN),영식(IN)의 궁합은? \(\to\) 둘다 IN 이므로 잘 맞을듯 \(\to\) 4.0 정도?
  • 영자(IN),다호(ES)의 궁합은? \(\to\) 잘 안맞을듯
  • 하니(I),영호(IS)의 궁합은? \(\to\) 하니는 모두 좋아하므로 기본적으로 4.5 정도 + 하니는 I성향이므로 더 잘 맞을듯 \(\to\) 거의 4.9 아닐까?

- 좀 더 체계적인 추론 전략: (1) 사람들이 가지고 있는 성향 (2) 사람자체의 절대매력을 수치화 하자.

  • 옥순(IN)의 IN성향, 옥순(IN)의 매력 = (1.22, 0.49), 1.21
  • 영식(IN)의 IN성향, 영식(IN)의 매력 = (1.20, 0.50), 1.20
  • 영자(IN)의 IN성향, 영자(IN)의 매력 = (1.17 ,0.44), 1.25
  • 다호(ES)의 IN성향, 다호(ES)의 매력 = (-1.22 ,-0.60), 1.15
  • 하니(I)의 IN성향, 하니(I)의 매력 = (0.20,0.00), 3.60
  • 영호(IS)의 IS성향, 영호(IS)의 매력 = (1.23 , -0.7), 1.11

(1) 옥순(IN)과 영식(IN)의 궁합 \(\approx\) 옥순의I성향\(\times\)영식의I성향 \(+\) 옥순의N성향\(\times\)영식의N성향 \(+\) 옥순의매력 \(+\) 영식의매력

옥순성향 = torch.tensor([1.22,0.49]).reshape(1,2)
옥순매력 = torch.tensor(1.21)
영식성향 = torch.tensor([1.20,0.5]).reshape(1,2)
영식매력 = torch.tensor(1.2)
((옥순성향*영식성향).sum() + 옥순매력 + 영식매력) # 옥순과 영식의 궁합: a ∘ b 로 내적구함 + 이후에 매력을 더함 
(옥순성향 @ 영식성향.T + 옥순매력 + 영식매력) # 옥순과 영식의 궁합: a.T @ b 로 내적구함 + 이후에 매력을 더함
tensor([[4.1190]])

(2) 영자(IN)와 다호(ES)의 궁합 \(\approx\) 영자I성향\(\times\)다호I성향 \(+\) 영자N성향\(\times\)다호의N성향 \(+\) 영자의매력 \(+\) 다호의매력

영자성향 = torch.tensor([1.17,0.44]).reshape(1,2)
영자매력 = torch.tensor(1.25).reshape(1,1)
다호성향 = torch.tensor([-1.22,-0.6]).reshape(1,2)
다호매력 = torch.tensor(1.15).reshape(1,1)
((영자성향*다호성향).sum() + 영자성향 + 다호성향)
(영자성향 @ 다호성향.T + 영자매력 + 다호매력)
tensor([[0.7086]])

(3) 하니(I)와 영호(IS)의 궁합 \(\approx\) 하니I성향\(\times\)영호I성향 \(+\) 하니N성향\(\times\)영호의N성향 \(+\) 하니의매력 \(+\) 영호의매력

하니성향 = torch.tensor([0.2,0]).reshape(1,2)
하니매력 = torch.tensor(3.6)
영호성향 = torch.tensor([1.23,-0.7]).reshape(1,2)
영호매력 = torch.tensor(1.11)
((하니성향*영호성향).sum() + 하니매력 + 영호매력)
(하니성향 @ 영호성향.T + 하니매력 + 영호매력)
tensor([[4.9560]])

전체적으로 그럴싸함

- 전체 사용자의 설정값

옥순성향 = torch.tensor([1.22,0.49]).reshape(1,2)
영자성향 = torch.tensor([1.17,0.44]).reshape(1,2)
정숙성향 = torch.tensor([1.21,-0.45]).reshape(1,2)
영숙성향 = torch.tensor([1.20,-0.50]).reshape(1,2)
순자성향 = torch.tensor([-1.20,0.51]).reshape(1,2)
현숙성향 = torch.tensor([-1.23,0.48]).reshape(1,2)
서연성향 = torch.tensor([-1.20,-0.48]).reshape(1,2)
보람성향 = torch.tensor([-1.19,-0.49]).reshape(1,2)
하니성향 = torch.tensor([0.2,0]).reshape(1,2)
W = torch.concat([옥순성향,영자성향,정숙성향,영숙성향,순자성향,현숙성향,서연성향,보람성향,하니성향])
b1 = torch.tensor([1.21,1.25,1.10,1.11,1.12,1.13,1.14,1.12,3.6]).reshape(-1,1) 
W,b1
(tensor([[ 1.2200,  0.4900],
         [ 1.1700,  0.4400],
         [ 1.2100, -0.4500],
         [ 1.2000, -0.5000],
         [-1.2000,  0.5100],
         [-1.2300,  0.4800],
         [-1.2000, -0.4800],
         [-1.1900, -0.4900],
         [ 0.2000,  0.0000]]),
 tensor([[1.2100],
         [1.2500],
         [1.1000],
         [1.1100],
         [1.1200],
         [1.1300],
         [1.1400],
         [1.1200],
         [3.6000]]))
영식성향 = torch.tensor([1.20,0.5]).reshape(1,2)
영철성향 = torch.tensor([1.22,0.45]).reshape(1,2)
영호성향 = torch.tensor([1.23,-0.7]).reshape(1,2)
광수성향 = torch.tensor([1.21,-0.6]).reshape(1,2)
상철성향 = torch.tensor([-1.28,0.6]).reshape(1,2)
영수성향 = torch.tensor([-1.24,0.5]).reshape(1,2)
규빈성향 = torch.tensor([-1.20,-0.5]).reshape(1,2)
다호성향 = torch.tensor([-1.22,-0.6]).reshape(1,2)
M = torch.concat([영식성향,영철성향,영호성향,광수성향,상철성향,영수성향,규빈성향,다호성향]) # 각 column은 남성출연자의 성향을 의미함
b2 = torch.tensor([1.2,1.10,1.11,1.25,1.18,1.11,1.15,1.15]).reshape(-1,1)
M,b2
(tensor([[ 1.2000,  0.5000],
         [ 1.2200,  0.4500],
         [ 1.2300, -0.7000],
         [ 1.2100, -0.6000],
         [-1.2800,  0.6000],
         [-1.2400,  0.5000],
         [-1.2000, -0.5000],
         [-1.2200, -0.6000]]),
 tensor([[1.2000],
         [1.1000],
         [1.1100],
         [1.2500],
         [1.1800],
         [1.1100],
         [1.1500],
         [1.1500]]))

- 아래의 행렬곱 관찰

W @ M.T + (b1 + b2.T)
tensor([[4.1190, 4.0189, 3.4776, 3.6422, 1.1224, 1.0522, 0.6510, 0.5776],
        [4.0740, 3.9754, 3.4911, 3.6517, 1.1964, 1.1292, 0.7760, 0.7086],
        [3.5270, 3.4737, 4.0133, 4.0841, 0.4612, 0.4846, 1.0230, 1.0438],
        [3.5000, 3.4490, 4.0460, 4.1120, 0.4540, 0.4820, 1.0700, 1.0960],
        [1.1350, 0.9855, 0.3970, 0.6120, 4.1420, 3.9730, 3.4550, 3.4280],
        [1.0940, 0.9454, 0.3911, 0.6037, 4.1724, 4.0052, 3.5160, 3.4926],
        [0.6600, 0.5600, 1.1100, 1.2260, 3.5680, 3.4980, 3.9700, 4.0420],
        [0.6470, 0.5477, 1.1093, 1.2241, 3.5292, 3.4606, 3.9430, 4.0158],
        [5.0400, 4.9440, 4.9560, 5.0920, 4.5240, 4.4620, 4.5100, 4.5060]])

—저거 따져보자—

\({\bf W} = \begin{bmatrix} 1.2200 & 0.4900 \\ 1.1700 & 0.4400 \\ 1.2100 & -0.4500 \\ 1.2000 & -0.5000 \\ -1.2000 & 0.5100 \\ -1.2300 & 0.4800 \\ -1.2000 & -0.4800 \\ -1.1900 & -0.4900 \\ 0.2000 & 0.0000 \end{bmatrix}\)

\({\bf M}^\top = \begin{bmatrix} 1.2000 & 1.2200 & 1.2300 & 1.2100 & -1.2800 & -1.2400 & -1.2000 & -1.2200 \\ 0.5000 & 0.4500 & -0.7000 & -0.6000 & 0.6000 & 0.5000 & -0.5000 & -0.6000 \end{bmatrix}\)

\({\bf W} @ {\bf M}^\top = \begin{bmatrix} 1.7090 & 1.7089 & 1.1576 & 1.1822 & -1.2676 & -1.2678 & -1.7090 & -1.7824 \\ 1.6240 & 1.6254 & 1.1311 & 1.1517 & -1.2336 & -1.2308 & -1.6240 & -1.6914 \\ 1.2270 & 1.2737 & 1.8033 & 1.7341 & -1.8188 & -1.7254 & -1.2270 & -1.2062 \\ 1.1900 & 1.2390 & 1.8260 & 1.7520 & -1.8360 & -1.7380 & -1.1900 & -1.1640 \\ -1.1850 & -1.2345 & -1.8330 & -1.7580 & 1.8420 & 1.7430 & 1.1850 & 1.1580 \\ -1.2360 & -1.2846 & -1.8489 & -1.7763 & 1.8624 & 1.7652 & 1.2360 & 1.2126 \\ -1.6800 & -1.6800 & -1.1400 & -1.1640 & 1.2480 & 1.2480 & 1.6800 & 1.7520 \\ -1.6730 & -1.6723 & -1.1207 & -1.1459 & 1.2292 & 1.2306 & 1.6730 & 1.7458 \\ 0.2400 & 0.2440 & 0.2460 & 0.2420 & -0.2560 & -0.2480 & -0.2400 & -0.2440 \end{bmatrix}\)

\(\begin{align*} bias =~& \begin{bmatrix} 1.2100 \\ 1.2500 \\ 1.1000 \\ 1.1100 \\ 1.1200 \\ 1.1300 \\ 1.1400 \\ 1.1200 \\ 3.6000 \end{bmatrix} +\begin{bmatrix} 1.2000 & 1.1000 & 1.1100 & 1.2500 & 1.1800 & 1.1100 & 1.1500 & 1.1500 \end{bmatrix}\\ \\ =~& \begin{bmatrix} 2.4100 & 2.3100 & 2.3200 & 2.4600 & 2.3900 & 2.3200 & 2.3600 & 2.3600 \\ 2.4500 & 2.3500 & 2.3600 & 2.5000 & 2.4300 & 2.3600 & 2.4000 & 2.4000 \\ 2.3000 & 2.2000 & 2.2100 & 2.3500 & 2.2800 & 2.2100 & 2.2500 & 2.2500 \\ 2.3100 & 2.2100 & 2.2200 & 2.3600 & 2.2900 & 2.2200 & 2.2600 & 2.2600 \\ 2.3200 & 2.2200 & 2.2300 & 2.3700 & 2.3000 & 2.2300 & 2.2700 & 2.2700 \\ 2.3300 & 2.2300 & 2.2400 & 2.3800 & 2.3100 & 2.2400 & 2.2800 & 2.2800 \\ 2.3400 & 2.2400 & 2.2500 & 2.3900 & 2.3200 & 2.2500 & 2.2900 & 2.2900 \\ 2.3200 & 2.2200 & 2.2300 & 2.3700 & 2.3000 & 2.2300 & 2.2700 & 2.2700 \\ 4.8000 & 4.7000 & 4.7100 & 4.8500 & 4.7800 & 4.7100 & 4.7500 & 4.7500 \end{bmatrix} \end{align*}\)

\({\bf W} @ {\bf M}^\top + bias = \begin{bmatrix} 4.1190 & 4.0189 & 3.4776 & 3.6422 & 1.1224 & 1.0522 & 0.6510 & 0.5776 \\ 4.0740 & 3.9754 & 3.4911 & 3.6517 & 1.1964 & 1.1292 & 0.7760 & 0.7086 \\ 3.5270 & 3.4737 & 4.0133 & 4.0841 & 0.4612 & 0.4846 & 1.0230 & 1.0438 \\ 3.5000 & 3.4490 & 4.0460 & 4.1120 & 0.4540 & 0.4820 & 1.0700 & 1.0960 \\ 1.1350 & 0.9855 & 0.3970 & 0.6120 & 4.1420 & 3.9730 & 3.4550 & 3.4280 \\ 1.0940 & 0.9454 & 0.3911 & 0.6037 & 4.1724 & 4.0052 & 3.5160 & 3.4926 \\ 0.6600 & 0.5600 & 1.1100 & 1.2260 & 3.5680 & 3.4980 & 3.9700 & 4.0420 \\ 0.6470 & 0.5477 & 1.1093 & 1.2241 & 3.5292 & 3.4606 & 3.9430 & 4.0158 \\ 5.0400 & 4.9440 & 4.9560 & 5.0920 & 4.5240 & 4.4620 & 4.5100 & 4.5060 \end{bmatrix}\)

- \({\bf W} @ {\bf M}^\top + bias\) 의 (1,1)의 원소값을 계산해보면 아래와 같다.

  • 옥순의I성향\(\times\)영식의I성향 \(+\) 옥순의N성향\(\times\)영식의N성향 \(+\) 옥순의매력 \(+\) 영식의매력 = 4.1190
  • \(1.220 \times 1.2000 + 0.4900 \times 0.5000 + 1.2100 + 2.4100 = 4.1190\)

- 궁합매트릭스: \({\bf W} @ {\bf M}^\top + bias\)를 계산하면 (9,8) 인 행렬이 나올텐데 이 행렬의 \((i,j)\)의 원소는 \(i\)-th 여성출연자와 \(j\)-th 남성출연자가 얼마나 잘 맞는지를 나타내는 숫자가 된다. (숫자가 높을수록 잘 맞음) 편의상 이 수업에서는 이 매트릭스를 “궁합매트릭스” 라고 정의하자.

- 주어진 자료와 우리가 임의로 만든 궁합매트릭스를 비교해보자.

print(f"주어진자료:\n{np.array(df_view)}")
print(f"궁합매트릭스:\n{np.array(W @ M.T + b1 + b2.T).round(2)}")
주어진자료:
[[ nan 4.02 3.45 3.42 0.84 1.12 0.43 0.49]
 [3.93 3.99 3.63 3.43 0.98 0.96 0.52  nan]
 [3.52 3.42 4.05 4.06 0.39  nan 0.93 0.99]
 [3.43 3.57  nan 3.95 0.56 0.52 0.89 0.89]
 [1.12  nan 0.59 0.43 4.01 4.16 3.52 3.38]
 [0.94 1.05 0.32 0.45 4.02 3.78  nan 3.54]
 [0.51 0.56 0.88 0.89 3.5  3.64 4.04 4.1 ]
 [0.48 0.51 1.03  nan 3.52 4.   3.82  nan]
 [4.85 4.82  nan 4.98 4.53 4.39 4.45 4.52]]
궁합매트릭스:
[[4.12 4.02 3.48 3.64 1.12 1.05 0.65 0.58]
 [4.07 3.98 3.49 3.65 1.2  1.13 0.78 0.71]
 [3.53 3.47 4.01 4.08 0.46 0.48 1.02 1.04]
 [3.5  3.45 4.05 4.11 0.45 0.48 1.07 1.1 ]
 [1.14 0.99 0.4  0.61 4.14 3.97 3.46 3.43]
 [1.09 0.95 0.39 0.6  4.17 4.01 3.52 3.49]
 [0.66 0.56 1.11 1.23 3.57 3.5  3.97 4.04]
 [0.65 0.55 1.11 1.22 3.53 3.46 3.94 4.02]
 [5.04 4.94 4.96 5.09 4.52 4.46 4.51 4.51]]

- 우리의 전략

  1. \({\bf W} @ {\bf M}^\top + bias\)의 값과 df_view 의 값이 nan을 제외한 곳에서 거의 비슷하게 되도록 \({\bf W}\), \({\bf M}\), \(bias\)를 잘 때려맞추면 되는것 아니야?

  2. 1을 만족하는 \({\bf W}\), \({\bf M}\), \(bias\)를 찾았으면 그 숫자들을 이용하여 df_viewnan 을 추정한다.

- 따라서 모형은 아래와 같이 볼 수 있다.

\[{\tt df\_view} \approx {\bf W}@{\bf M}^\top + bias\]

- 아래의 정보를 참고하여 위의 수식을 다시 정리하면..

W M 여성특징 남성특징 I궁합 N궁합 bias yhat y
옥순(IN) 영철(IN) 1.22, 0.49 1.22, 0.45 1.48841 0.22052 2.313 4.0189 4.02
옥순(IN) 영호(IS) 1.22, 0.49 1.23, -0.7 1.15764 -0.34235 2.326 3.4776 3.45
하니(I) 영식(IN) 0.20, 0.00 1.20, 0.5 0.24 0 4.8 5.04 4.85
  • 1 1.22 *1.22 = 1.4884

  • 2 0.49 * 0.45 = 0.2205

  • 3 1.21+1.1 = 2.31

  • 4 1.22 *1.23 = 1.5006

  • 5 0.49 * -0.7 = -0.3423

  • 6 1.21+1.11 = 2.32

    • 걱정1: 5.0이 넘는 값도 있네? 잘못잡으면 음수가 나올지도?
    • 걱정2: 저러한 yhat (4.0189, 3.4776, 5.04)을 만드는게 꼭 저 조합만 있는게 아님. 당장에 남성의 바이어스에 일괄적으로 -2를 넣고 여성의 바이어스에 일괄적으로 +2를 해도 성립함.

    - (걱정은 뒤로 하고) yhat \(\approx\) y 를 만족하도록 해보자! (1) 아무 yhat 을 구한다. (2) yhaty가 비슷한 정도를 측정한다. (3) 더 적당한 yhat값을 update한다.

    • yhat은 어떻게 구하지? (여성특징\(\otimes\)남성특징).sum() + bias?
    • 그럼 여성특징,남성특징,여성bias(=여성매력),남성bias(=남성매력)는 어떻게 구하지?? 생각해보니까 데이터에서 주어진건 아니잖아??

    - 여성특징,남성특징, 여성bais,남성bais 를 어떻게 만들지?

    • 그전엔 어떻게 했지?? W을 보고 적당히 특징을 상상하고 여성특징,여성bias의 값을 때려넣음 + M를 보고 적당히 특징을 상상하고 남성특징, 남성bias의 값을 채워 넣음.
    • 자동화하려면? W \(\to\) 여성특징, W \(\to\) 여성bias, M \(\to\) 남성특징, M \(\to\) 남성bias 인 함수를 만들자.

    - 앞으로 할일1: 아래와 같은 함수들을 만들자.

    • 옥순 \(\to\) 옥순의 특징 = (1.22, 0.49)
    • 옥순 \(\to\) 옥순의 매력 = 1.22
    • 영철 \(\to\) 영철의 특징 = (1.22, 0.45)
    • 영철 \(\to\) 영철의 매력 = 1.22 …

    - 앞으로 할일2: 우리가 익숙한 셋팅 (step1~4)

    1. 여성특징, 여성bias, 남성특징, 남성bias \(\to\) yhat 를 수행
    2. y \(\approx\) yhat 인지 체크: loss = loss_fn(yhat,y)
    3. loss.backward()
    4. 더 나은 여성특징, 여성bias, 남성특징, 남성bias 로 update!

    C. 할일1의 구현

    - dataframe의 변형

    df_train = df_view.stack().reset_index().set_axis(['W','M','y'],axis=1)
    df_train
    W M y
    0 옥순(IN) 영철(IN) 4.02
    1 옥순(IN) 영호(IS) 3.45
    2 옥순(IN) 광수(IS) 3.42
    3 옥순(IN) 상철(EN) 0.84
    4 옥순(IN) 영수(EN) 1.12
    ... ... ... ...
    58 하니(I) 광수(IS) 4.98
    59 하니(I) 상철(EN) 4.53
    60 하니(I) 영수(EN) 4.39
    61 하니(I) 규빈(ES) 4.45
    62 하니(I) 다호(ES) 4.52

    63 rows × 3 columns

    - 이름을 숫자화

    w = {'옥순(IN)':0, '영자(IN)':1, '정숙(IS)':2, '영숙(IS)':3, '순자(EN)':4, '현숙(EN)':5, '서연(ES)':6, '보람(ES)':7, '하니(I)':8}
    m = {'영식(IN)':0, '영철(IN)':1, '영호(IS)':2, '광수(IS)':3, '상철(EN)':4, '영수(EN)':5, '규빈(ES)':6, '다호(ES)':7}
    df_train['X1']= df_train['W'].map(w)
    df_train['X2']= df_train['M'].map(m)
    df_train
    W M y X1 X2
    0 옥순(IN) 영철(IN) 4.02 0 1
    1 옥순(IN) 영호(IS) 3.45 0 2
    2 옥순(IN) 광수(IS) 3.42 0 3
    3 옥순(IN) 상철(EN) 0.84 0 4
    4 옥순(IN) 영수(EN) 1.12 0 5
    ... ... ... ... ... ...
    58 하니(I) 광수(IS) 4.98 8 3
    59 하니(I) 상철(EN) 4.53 8 4
    60 하니(I) 영수(EN) 4.39 8 5
    61 하니(I) 규빈(ES) 4.45 8 6
    62 하니(I) 다호(ES) 4.52 8 7

    63 rows × 5 columns

    - 텐서화 + one_hot-인코딩

    X1 = torch.tensor(df_train['X1'])
    X2 = torch.tensor(df_train['X2'])
    E1 = torch.nn.functional.one_hot(X1).float()
    E2 = torch.nn.functional.one_hot(X2).float()
    print(f"y.shape: {y.shape},\t y.dtype: {y.dtype}")
    print(f"X1.shape: {X1.shape},\t X1.dtype: {X1.dtype} // X1.unique: {X1.unique()}")
    print(f"X2.shape: {X2.shape},\t X2.dtype: {X2.dtype} // X2.unique: {X2.unique()}")
    print(f"E1.shape: {E1.shape},\t E1.dtype: {E1.dtype} -- shape에서 9는 여성이 9명이라는 의미")
    print(f"E2.shape: {E2.shape},\t E2.dtype: {E2.dtype} -- shape에서 8은 남성이 8명이라는 의미")
    y.shape: torch.Size([63, 1]),    y.dtype: torch.float32
    X1.shape: torch.Size([63]),  X1.dtype: torch.int64 // X1.unique: tensor([0, 1, 2, 3, 4, 5, 6, 7, 8])
    X2.shape: torch.Size([63]),  X2.dtype: torch.int64 // X2.unique: tensor([0, 1, 2, 3, 4, 5, 6, 7])
    E1.shape: torch.Size([63, 9]),   E1.dtype: torch.float32 -- shape에서 9는 여성이 9명이라는 의미
    E2.shape: torch.Size([63, 8]),   E2.dtype: torch.float32 -- shape에서 8은 남성이 8명이라는 의미

    - X1 -> 여성특징, X1 -> 여성bias, X2 -> 남성특징, X2 -> 남성bias 구현

    l1 = torch.nn.Linear(in_features=9, out_features=2, bias=False) # "E1->여성특징"인 함수 
    b1 = torch.nn.Linear(in_features=9, out_features=1, bias=False) # "E1->여성bias"인 함수 
    l2 = torch.nn.Linear(in_features=8, out_features=2, bias=False) # "E2->남성특징"인 함수 
    b2 = torch.nn.Linear(in_features=8, out_features=1, bias=False) # "E2->남성bias"인 함수 

    D. 할일2의 구현 – step1~4 수행

    - step1: yhat을 구하자.

    torch.manual_seed(43052)
    l1 = torch.nn.Linear(in_features=9, out_features=2, bias=False) # "E1->여성특징"인 함수 
    b1 = torch.nn.Linear(in_features=9, out_features=1, bias=False) # "E1->여성bias"인 함수 
    l2 = torch.nn.Linear(in_features=8, out_features=2, bias=False) # "E2->남성특징"인 함수 
    b2 = torch.nn.Linear(in_features=8, out_features=1, bias=False) # "E2->남성bias"인 함수 
    W_features = l1(E1) # l1(onehot(X1))
    W_bias = b1(E1) # b1(onehot(X1))
    M_features = l2(E2) # l2(onehot(X2))
    M_bias = b2(E2) # b2(onehot(X2))
    sig = torch.nn.Sigmoid()
    score = (W_features*M_features).sum(axis=1).reshape(-1,1) + W_bias + M_bias
    yhat = sig(score)*5

    - step2: 손실계산

    loss_fn = torch.nn.MSELoss()
    loss = loss_fn(yhat,y)

    - step3: 미분

    (미분전)

    l1.weight.data, b1.weight.data, l2.weight.data, b2.weight.data
    (tensor([[-0.1156, -0.2823,  0.1201,  0.3112,  0.1358, -0.2962,  0.1086, -0.2071,
              -0.0864],
             [ 0.2082,  0.3231, -0.1724, -0.2224,  0.0670,  0.1536, -0.0552,  0.2843,
              -0.1426]]),
     tensor([[-0.0210,  0.3022, -0.0259,  0.1251, -0.2812,  0.2052,  0.1129, -0.2435,
               0.2790]]),
     tensor([[ 0.1425,  0.0621,  0.1415, -0.0449,  0.2502, -0.2042, -0.0980,  0.3091],
             [-0.1902, -0.0075,  0.2070,  0.2928,  0.1697,  0.0928,  0.0474, -0.2111]]),
     tensor([[ 0.3028, -0.1884,  0.2234, -0.1377, -0.3145,  0.0662, -0.2995,  0.1305]]))
    l1.weight.grad, b1.weight.grad, l2.weight.grad, b2.weight.grad
    (None, None, None, None)

    (미분)

    loss.backward()

    (미분후)

    l1.weight.data, b1.weight.data, l2.weight.data, b2.weight.data
    (tensor([[-0.1156, -0.2823,  0.1201,  0.3112,  0.1358, -0.2962,  0.1086, -0.2071,
              -0.0864],
             [ 0.2082,  0.3231, -0.1724, -0.2224,  0.0670,  0.1536, -0.0552,  0.2843,
              -0.1426]]),
     tensor([[-0.0210,  0.3022, -0.0259,  0.1251, -0.2812,  0.2052,  0.1129, -0.2435,
               0.2790]]),
     tensor([[ 0.1425,  0.0621,  0.1415, -0.0449,  0.2502, -0.2042, -0.0980,  0.3091],
             [-0.1902, -0.0075,  0.2070,  0.2928,  0.1697,  0.0928,  0.0474, -0.2111]]),
     tensor([[ 0.3028, -0.1884,  0.2234, -0.1377, -0.3145,  0.0662, -0.2995,  0.1305]]))
    l1.weight.grad, b1.weight.grad, l2.weight.grad, b2.weight.grad
    (tensor([[ 0.0125, -0.0184,  0.0226,  0.0183,  0.0050,  0.0058,  0.0141,  0.0276,
              -0.0295],
             [-0.0172,  0.0171, -0.0282, -0.0106,  0.0072,  0.0231,  0.0107, -0.0233,
              -0.0265]]),
     tensor([[ 0.1172,  0.0943, -0.0167,  0.1680, -0.0704,  0.1955,  0.1052, -0.0075,
              -0.5161]]),
     tensor([[-0.0145, -0.0087, -0.0224, -0.0196,  0.0313,  0.0104, -0.0032,  0.0270],
             [ 0.0383,  0.0184,  0.0313,  0.0375, -0.0164,  0.0008,  0.0108, -0.0092]]),
     tensor([[ 0.1859, -0.1176,  0.2194, -0.0823, -0.1017, -0.0458, -0.0408,  0.0526]]))

    - step4: update

    (옵티마이저 선언)

    params = list(l1.parameters())+list(l2.parameters())+list(b1.parameters())+list(b2.parameters())
    optimizr = torch.optim.Adam(params, lr=0.1) 

    (update전)

    l1.weight.data, b1.weight.data, l2.weight.data, b2.weight.data
    (tensor([[-0.1156, -0.2823,  0.1201,  0.3112,  0.1358, -0.2962,  0.1086, -0.2071,
              -0.0864],
             [ 0.2082,  0.3231, -0.1724, -0.2224,  0.0670,  0.1536, -0.0552,  0.2843,
              -0.1426]]),
     tensor([[-0.0210,  0.3022, -0.0259,  0.1251, -0.2812,  0.2052,  0.1129, -0.2435,
               0.2790]]),
     tensor([[ 0.1425,  0.0621,  0.1415, -0.0449,  0.2502, -0.2042, -0.0980,  0.3091],
             [-0.1902, -0.0075,  0.2070,  0.2928,  0.1697,  0.0928,  0.0474, -0.2111]]),
     tensor([[ 0.3028, -0.1884,  0.2234, -0.1377, -0.3145,  0.0662, -0.2995,  0.1305]]))
    l1.weight.grad, b1.weight.grad, l2.weight.grad, b2.weight.grad
    (tensor([[ 0.0125, -0.0184,  0.0226,  0.0183,  0.0050,  0.0058,  0.0141,  0.0276,
              -0.0295],
             [-0.0172,  0.0171, -0.0282, -0.0106,  0.0072,  0.0231,  0.0107, -0.0233,
              -0.0265]]),
     tensor([[ 0.1172,  0.0943, -0.0167,  0.1680, -0.0704,  0.1955,  0.1052, -0.0075,
              -0.5161]]),
     tensor([[-0.0145, -0.0087, -0.0224, -0.0196,  0.0313,  0.0104, -0.0032,  0.0270],
             [ 0.0383,  0.0184,  0.0313,  0.0375, -0.0164,  0.0008,  0.0108, -0.0092]]),
     tensor([[ 0.1859, -0.1176,  0.2194, -0.0823, -0.1017, -0.0458, -0.0408,  0.0526]]))

    (update)

    optimizr.step()

    (update후)

    l1.weight.data, b1.weight.data, l2.weight.data, b2.weight.data
    (tensor([[-0.2156, -0.1823,  0.0201,  0.2112,  0.0358, -0.3962,  0.0086, -0.3071,
               0.0136],
             [ 0.3082,  0.2231, -0.0724, -0.1224, -0.0330,  0.0536, -0.1552,  0.3843,
              -0.0426]]),
     tensor([[-0.1210,  0.2022,  0.0741,  0.0251, -0.1812,  0.1052,  0.0129, -0.1435,
               0.3790]]),
     tensor([[ 0.2425,  0.1621,  0.2415,  0.0551,  0.1502, -0.3042,  0.0020,  0.2091],
             [-0.2902, -0.1075,  0.1070,  0.1928,  0.2697, -0.0072, -0.0526, -0.1111]]),
     tensor([[ 0.2028, -0.0884,  0.1234, -0.0377, -0.2145,  0.1662, -0.1995,  0.0305]]))
    l1.weight.grad, b1.weight.grad, l2.weight.grad, b2.weight.grad
    (tensor([[ 0.0125, -0.0184,  0.0226,  0.0183,  0.0050,  0.0058,  0.0141,  0.0276,
              -0.0295],
             [-0.0172,  0.0171, -0.0282, -0.0106,  0.0072,  0.0231,  0.0107, -0.0233,
              -0.0265]]),
     tensor([[ 0.1172,  0.0943, -0.0167,  0.1680, -0.0704,  0.1955,  0.1052, -0.0075,
              -0.5161]]),
     tensor([[-0.0145, -0.0087, -0.0224, -0.0196,  0.0313,  0.0104, -0.0032,  0.0270],
             [ 0.0383,  0.0184,  0.0313,  0.0375, -0.0164,  0.0008,  0.0108, -0.0092]]),
     tensor([[ 0.1859, -0.1176,  0.2194, -0.0823, -0.1017, -0.0458, -0.0408,  0.0526]]))

    (zero_grad)

    optimizr.zero_grad()

    (zero_grad후)

    l1.weight.data, b1.weight.data, l2.weight.data, b2.weight.data
    (tensor([[-0.2156, -0.1823,  0.0201,  0.2112,  0.0358, -0.3962,  0.0086, -0.3071,
               0.0136],
             [ 0.3082,  0.2231, -0.0724, -0.1224, -0.0330,  0.0536, -0.1552,  0.3843,
              -0.0426]]),
     tensor([[-0.1210,  0.2022,  0.0741,  0.0251, -0.1812,  0.1052,  0.0129, -0.1435,
               0.3790]]),
     tensor([[ 0.2425,  0.1621,  0.2415,  0.0551,  0.1502, -0.3042,  0.0020,  0.2091],
             [-0.2902, -0.1075,  0.1070,  0.1928,  0.2697, -0.0072, -0.0526, -0.1111]]),
     tensor([[ 0.2028, -0.0884,  0.1234, -0.0377, -0.2145,  0.1662, -0.1995,  0.0305]]))
    l1.weight.grad, b1.weight.grad, l2.weight.grad, b2.weight.grad
    (None, None, None, None)

    E. 코드정리

    df_view = pd.read_csv('https://raw.githubusercontent.com/guebin/DL2024/main/posts/solo.csv',index_col=0)
    df_train = df_view.stack().reset_index().set_axis(['W','M','y'],axis=1)
    w = {'옥순(IN)':0, '영자(IN)':1, '정숙(IS)':2, '영숙(IS)':3, '순자(EN)':4, '현숙(EN)':5, '서연(ES)':6, '보람(ES)':7, '하니(I)':8}
    m = {'영식(IN)':0, '영철(IN)':1, '영호(IS)':2, '광수(IS)':3, '상철(EN)':4, '영수(EN)':5, '규빈(ES)':6, '다호(ES)':7}
    X1 = torch.tensor(df_train['W'].map(w)) # length-n vector 
    X2 = torch.tensor(df_train['M'].map(m)) # length-n vector 
    E1 = torch.nn.functional.one_hot(X1).float()
    E2 = torch.nn.functional.one_hot(X2).float()
    y = torch.tensor(df_train['y']).float().reshape(-1,1)
    #--#
    torch.manual_seed(43052)
    l1 = torch.nn.Linear(in_features=9, out_features=2, bias=False)
    b1 = torch.nn.Linear(in_features=9, out_features=1, bias=False)
    l2 = torch.nn.Linear(in_features=8, out_features=2, bias=False)
    b2 = torch.nn.Linear(in_features=8, out_features=1, bias=False)
    sig = torch.nn.Sigmoid()
    loss_fn = torch.nn.MSELoss()
    params = list(l1.parameters())+list(b1.parameters())+list(l2.parameters())+list(b2.parameters())
    optimizr = torch.optim.Adam(params, lr=0.1) 
    #--#
    for epoc in range(100):
        ## step1 
        W_features = l1(E1)
        W_bias = b1(E1)
        M_features = l2(E2) 
        M_bias = b2(E2)
        score = (W_features * M_features).sum(axis=1).reshape(-1,1) + W_bias + M_bias
        yhat = sig(score) * 5 
        ## step2 
        loss = loss_fn(yhat,y)
        ## step3 
        loss.backward()
        ## step4 
        optimizr.step()
        optimizr.zero_grad()
    torch.concat([yhat,y],axis=1)[::4] # 꽤 잘맞음
    tensor([[4.0679, 4.0200],
            [0.9554, 1.1200],
            [3.9950, 3.9900],
            [0.9580, 0.9600],
            [4.1490, 4.0500],
            [0.9765, 0.9900],
            [0.5392, 0.5600],
            [1.1028, 1.1200],
            [4.1009, 4.1600],
            [0.9950, 1.0500],
            [3.9835, 3.7800],
            [0.9398, 0.8800],
            [3.9916, 4.0400],
            [0.9030, 1.0300],
            [4.8949, 4.8500],
            [4.5048, 4.3900]], grad_fn=<SliceBackward0>)

    F. 모형의 해석 – 쉬운경우

    df_match = pd.DataFrame((W_features*M_features).data).set_axis(['잠재특징궁합1','잠재특징궁합2'],axis=1)
    df_bias = pd.DataFrame(torch.concat([W_bias, M_bias],axis=1).data).set_axis(['여성bias','남성bias'],axis=1)
    df_features = pd.concat([df_train.loc[:,'W':'M'],df_match,df_bias],axis=1)
    df_features[:56]
    W M 잠재특징궁합1 잠재특징궁합2 여성bias 남성bias
    0 옥순(IN) 영철(IN) 0.302724 1.411065 -0.983853 0.705386
    1 옥순(IN) 영호(IS) 0.743988 0.439198 -0.983853 0.618197
    2 옥순(IN) 광수(IS) 0.776415 0.382274 -0.983853 0.545838
    3 옥순(IN) 상철(EN) -0.992729 -0.430939 -0.983853 0.886856
    4 옥순(IN) 영수(EN) -0.942706 -0.539754 -0.983853 1.032223
    5 옥순(IN) 규빈(ES) -0.569901 -1.434720 -0.983853 0.853764
    6 옥순(IN) 다호(ES) -0.556357 -1.467226 -0.983853 0.944640
    7 영자(IN) 영식(IN) 0.323436 1.356050 -0.930421 0.651731
    8 영자(IN) 영철(IN) 0.324665 1.285471 -0.930421 0.705386
    9 영자(IN) 영호(IS) 0.797912 0.400107 -0.930421 0.618197
    10 영자(IN) 광수(IS) 0.832689 0.348249 -0.930421 0.545838
    11 영자(IN) 상철(EN) -1.064682 -0.392583 -0.930421 0.886856
    12 영자(IN) 영수(EN) -1.011034 -0.491713 -0.930421 1.032223
    13 영자(IN) 규빈(ES) -0.611208 -1.307021 -0.930421 0.853764
    14 정숙(IS) 영식(IN) 0.659914 0.304652 -0.846774 0.651731
    15 정숙(IS) 영철(IN) 0.662421 0.288796 -0.846774 0.705386
    16 정숙(IS) 영호(IS) 1.627999 0.089888 -0.846774 0.618197
    17 정숙(IS) 광수(IS) 1.698955 0.078238 -0.846774 0.545838
    18 정숙(IS) 상철(EN) -2.172296 -0.088198 -0.846774 0.886856
    19 정숙(IS) 규빈(ES) -1.247061 -0.293637 -0.846774 0.853764
    20 정숙(IS) 다호(ES) -1.217423 -0.300290 -0.846774 0.944640
    21 영숙(IS) 영식(IN) 0.628118 0.407719 -0.884238 0.651731
    22 영숙(IS) 영철(IN) 0.630504 0.386498 -0.884238 0.705386
    23 영숙(IS) 광수(IS) 1.617095 0.104707 -0.884238 0.545838
    24 영숙(IS) 상철(EN) -2.067629 -0.118037 -0.884238 0.886856
    25 영숙(IS) 영수(EN) -1.963442 -0.147842 -0.884238 1.032223
    26 영숙(IS) 규빈(ES) -1.186974 -0.392978 -0.884238 0.853764
    27 영숙(IS) 다호(ES) -1.158764 -0.401881 -0.884238 0.944640
    28 순자(EN) 영식(IN) -0.553524 -0.133968 -1.260765 0.651731
    29 순자(EN) 영호(IS) -1.365535 -0.039528 -1.260765 0.618197
    30 순자(EN) 광수(IS) -1.425051 -0.034405 -1.260765 0.545838
    31 순자(EN) 상철(EN) 1.822081 0.038784 -1.260765 0.886856
    32 순자(EN) 영수(EN) 1.730267 0.048578 -1.260765 1.032223
    33 순자(EN) 규빈(ES) 1.046011 0.129124 -1.260765 0.853764
    34 순자(EN) 다호(ES) 1.021151 0.132050 -1.260765 0.944640
    35 현숙(EN) 영식(IN) -0.521171 -0.265791 -1.371327 0.651731
    36 현숙(EN) 영철(IN) -0.523151 -0.251958 -1.371327 0.705386
    37 현숙(EN) 영호(IS) -1.285721 -0.078422 -1.371327 0.618197
    38 현숙(EN) 광수(IS) -1.341758 -0.068258 -1.371327 0.545838
    39 현숙(EN) 상철(EN) 1.715582 0.076948 -1.371327 0.886856
    40 현숙(EN) 영수(EN) 1.629135 0.096378 -1.371327 1.032223
    41 현숙(EN) 다호(ES) 0.961466 0.261986 -1.371327 0.944640
    42 서연(ES) 영식(IN) -0.218469 -1.394457 -1.191920 0.651731
    43 서연(ES) 영철(IN) -0.219299 -1.321879 -1.191920 0.705386
    44 서연(ES) 영호(IS) -0.538959 -0.411439 -1.191920 0.618197
    45 서연(ES) 광수(IS) -0.562449 -0.358112 -1.191920 0.545838
    46 서연(ES) 상철(EN) 0.719152 0.403702 -1.191920 0.886856
    47 서연(ES) 영수(EN) 0.682914 0.505639 -1.191920 1.032223
    48 서연(ES) 규빈(ES) 0.412847 1.344040 -1.191920 0.853764
    49 서연(ES) 다호(ES) 0.403035 1.374491 -1.191920 0.944640
    50 보람(ES) 영식(IN) -0.269182 -1.233859 -1.178461 0.651731
    51 보람(ES) 영철(IN) -0.270204 -1.169639 -1.178461 0.705386
    52 보람(ES) 영호(IS) -0.664067 -0.364054 -1.178461 0.618197
    53 보람(ES) 상철(EN) 0.886088 0.357208 -1.178461 0.886856
    54 보람(ES) 영수(EN) 0.841439 0.447405 -1.178461 1.032223
    55 보람(ES) 규빈(ES) 0.508681 1.189248 -1.178461 0.853764
    df_features[56:]
    W M 잠재특징궁합1 잠재특징궁합2 여성bias 남성bias
    56 하니(I) 영식(IN) 0.209820 0.540235 2.115016 0.651731
    57 하니(I) 영철(IN) 0.210617 0.512117 2.115016 0.705386
    58 하니(I) 광수(IS) 0.540183 0.138738 2.115016 0.545838
    59 하니(I) 상철(EN) -0.690681 -0.156401 2.115016 0.886856
    60 하니(I) 영수(EN) -0.655878 -0.195893 2.115016 1.032223
    61 하니(I) 규빈(ES) -0.396503 -0.520703 2.115016 0.853764
    62 하니(I) 다호(ES) -0.387079 -0.532500 2.115016 0.944640

    G. 모형의 해석 – 어려운 경우

    - 적합을 시켜보자.

    df_view = pd.read_csv('https://raw.githubusercontent.com/guebin/DL2024/main/posts/solo.csv',index_col=0)
    df_train = df_view.stack().reset_index().set_axis(['W','M','y'],axis=1)
    w = {'옥순(IN)':0, '영자(IN)':1, '정숙(IS)':2, '영숙(IS)':3, '순자(EN)':4, '현숙(EN)':5, '서연(ES)':6, '보람(ES)':7, '하니(I)':8}
    m = {'영식(IN)':0, '영철(IN)':1, '영호(IS)':2, '광수(IS)':3, '상철(EN)':4, '영수(EN)':5, '규빈(ES)':6, '다호(ES)':7}
    X1 = torch.tensor(df_train['W'].map(w)) # length-n vector 
    X2 = torch.tensor(df_train['M'].map(m)) # length-n vector 
    E1 = torch.nn.functional.one_hot(X1).float()
    E2 = torch.nn.functional.one_hot(X2).float()
    y = torch.tensor(df_train['y']).float().reshape(-1,1)
    #--#
    torch.manual_seed(8)
    l1 = torch.nn.Linear(in_features=9, out_features=2, bias=False)
    b1 = torch.nn.Linear(in_features=9, out_features=1, bias=False)
    l2 = torch.nn.Linear(in_features=8, out_features=2, bias=False)
    b2 = torch.nn.Linear(in_features=8, out_features=1, bias=False)
    sig = torch.nn.Sigmoid()
    loss_fn = torch.nn.MSELoss()
    params = list(l1.parameters())+list(b1.parameters())+list(l2.parameters())+list(b2.parameters())
    optimizr = torch.optim.Adam(params, lr=0.1) 
    #--#
    for epoc in range(100):
        ## step1 
        W_features = l1(E1)
        W_bias = b1(E1)
        M_features = l2(E2) 
        M_bias = b2(E2)
        score = (W_features * M_features).sum(axis=1).reshape(-1,1) + W_bias + M_bias
        yhat = sig(score) * 5 
        ## step2 
        loss = loss_fn(yhat,y)
        ## step3 
        loss.backward()
        ## step4 
        optimizr.step()
        optimizr.zero_grad()

    - 잘 맞추는듯

    torch.concat([yhat,y],axis=1)[::4] # 꽤 잘맞음
    tensor([[4.0386, 4.0200],
            [0.9623, 1.1200],
            [3.9990, 3.9900],
            [0.9883, 0.9600],
            [4.0799, 4.0500],
            [0.9734, 0.9900],
            [0.5064, 0.5600],
            [1.0738, 1.1200],
            [4.1248, 4.1600],
            [0.9569, 1.0500],
            [4.0001, 3.7800],
            [0.8943, 0.8800],
            [4.0257, 4.0400],
            [0.8481, 1.0300],
            [4.8558, 4.8500],
            [4.5425, 4.3900]], grad_fn=<SliceBackward0>)

    - 해석을 해보자.

    df_match = pd.DataFrame((W_features*M_features).data).set_axis(['잠재특징궁합1','잠재특징궁합2'],axis=1)
    df_bias = pd.DataFrame(torch.concat([W_bias, M_bias],axis=1).data).set_axis(['여성bias','남성bias'],axis=1)
    df_features = pd.concat([df_train.loc[:,'W':'M'],df_match,df_bias],axis=1)
    df_features[:56]
    W M 잠재특징궁합1 잠재특징궁합2 여성bias 남성bias
    0 옥순(IN) 영철(IN) 0.302724 1.411065 -0.983853 0.705386
    1 옥순(IN) 영호(IS) 0.743988 0.439198 -0.983853 0.618197
    2 옥순(IN) 광수(IS) 0.776415 0.382274 -0.983853 0.545838
    3 옥순(IN) 상철(EN) -0.992729 -0.430939 -0.983853 0.886856
    4 옥순(IN) 영수(EN) -0.942706 -0.539754 -0.983853 1.032223
    5 옥순(IN) 규빈(ES) -0.569901 -1.434720 -0.983853 0.853764
    6 옥순(IN) 다호(ES) -0.556357 -1.467226 -0.983853 0.944640
    7 영자(IN) 영식(IN) 0.323436 1.356050 -0.930421 0.651731
    8 영자(IN) 영철(IN) 0.324665 1.285471 -0.930421 0.705386
    9 영자(IN) 영호(IS) 0.797912 0.400107 -0.930421 0.618197
    10 영자(IN) 광수(IS) 0.832689 0.348249 -0.930421 0.545838
    11 영자(IN) 상철(EN) -1.064682 -0.392583 -0.930421 0.886856
    12 영자(IN) 영수(EN) -1.011034 -0.491713 -0.930421 1.032223
    13 영자(IN) 규빈(ES) -0.611208 -1.307021 -0.930421 0.853764
    14 정숙(IS) 영식(IN) 0.659914 0.304652 -0.846774 0.651731
    15 정숙(IS) 영철(IN) 0.662421 0.288796 -0.846774 0.705386
    16 정숙(IS) 영호(IS) 1.627999 0.089888 -0.846774 0.618197
    17 정숙(IS) 광수(IS) 1.698955 0.078238 -0.846774 0.545838
    18 정숙(IS) 상철(EN) -2.172296 -0.088198 -0.846774 0.886856
    19 정숙(IS) 규빈(ES) -1.247061 -0.293637 -0.846774 0.853764
    20 정숙(IS) 다호(ES) -1.217423 -0.300290 -0.846774 0.944640
    21 영숙(IS) 영식(IN) 0.628118 0.407719 -0.884238 0.651731
    22 영숙(IS) 영철(IN) 0.630504 0.386498 -0.884238 0.705386
    23 영숙(IS) 광수(IS) 1.617095 0.104707 -0.884238 0.545838
    24 영숙(IS) 상철(EN) -2.067629 -0.118037 -0.884238 0.886856
    25 영숙(IS) 영수(EN) -1.963442 -0.147842 -0.884238 1.032223
    26 영숙(IS) 규빈(ES) -1.186974 -0.392978 -0.884238 0.853764
    27 영숙(IS) 다호(ES) -1.158764 -0.401881 -0.884238 0.944640
    28 순자(EN) 영식(IN) -0.553524 -0.133968 -1.260765 0.651731
    29 순자(EN) 영호(IS) -1.365535 -0.039528 -1.260765 0.618197
    30 순자(EN) 광수(IS) -1.425051 -0.034405 -1.260765 0.545838
    31 순자(EN) 상철(EN) 1.822081 0.038784 -1.260765 0.886856
    32 순자(EN) 영수(EN) 1.730267 0.048578 -1.260765 1.032223
    33 순자(EN) 규빈(ES) 1.046011 0.129124 -1.260765 0.853764
    34 순자(EN) 다호(ES) 1.021151 0.132050 -1.260765 0.944640
    35 현숙(EN) 영식(IN) -0.521171 -0.265791 -1.371327 0.651731
    36 현숙(EN) 영철(IN) -0.523151 -0.251958 -1.371327 0.705386
    37 현숙(EN) 영호(IS) -1.285721 -0.078422 -1.371327 0.618197
    38 현숙(EN) 광수(IS) -1.341758 -0.068258 -1.371327 0.545838
    39 현숙(EN) 상철(EN) 1.715582 0.076948 -1.371327 0.886856
    40 현숙(EN) 영수(EN) 1.629135 0.096378 -1.371327 1.032223
    41 현숙(EN) 다호(ES) 0.961466 0.261986 -1.371327 0.944640
    42 서연(ES) 영식(IN) -0.218469 -1.394457 -1.191920 0.651731
    43 서연(ES) 영철(IN) -0.219299 -1.321879 -1.191920 0.705386
    44 서연(ES) 영호(IS) -0.538959 -0.411439 -1.191920 0.618197
    45 서연(ES) 광수(IS) -0.562449 -0.358112 -1.191920 0.545838
    46 서연(ES) 상철(EN) 0.719152 0.403702 -1.191920 0.886856
    47 서연(ES) 영수(EN) 0.682914 0.505639 -1.191920 1.032223
    48 서연(ES) 규빈(ES) 0.412847 1.344040 -1.191920 0.853764
    49 서연(ES) 다호(ES) 0.403035 1.374491 -1.191920 0.944640
    50 보람(ES) 영식(IN) -0.269182 -1.233859 -1.178461 0.651731
    51 보람(ES) 영철(IN) -0.270204 -1.169639 -1.178461 0.705386
    52 보람(ES) 영호(IS) -0.664067 -0.364054 -1.178461 0.618197
    53 보람(ES) 상철(EN) 0.886088 0.357208 -1.178461 0.886856
    54 보람(ES) 영수(EN) 0.841439 0.447405 -1.178461 1.032223
    55 보람(ES) 규빈(ES) 0.508681 1.189248 -1.178461 0.853764
    df_features[56:]
    W M 잠재특징궁합1 잠재특징궁합2 여성bias 남성bias
    56 하니(I) 영식(IN) 0.209820 0.540235 2.115016 0.651731
    57 하니(I) 영철(IN) 0.210617 0.512117 2.115016 0.705386
    58 하니(I) 광수(IS) 0.540183 0.138738 2.115016 0.545838
    59 하니(I) 상철(EN) -0.690681 -0.156401 2.115016 0.886856
    60 하니(I) 영수(EN) -0.655878 -0.195893 2.115016 1.032223
    61 하니(I) 규빈(ES) -0.396503 -0.520703 2.115016 0.853764
    62 하니(I) 다호(ES) -0.387079 -0.532500 2.115016 0.944640
    • I보다 E성향 남성이 좀 더 인기 있음.
    • E보다 I성향 여성이 좀 더 인기 있음.
    • 궁합1: (IN, IN) = 0.3 // (IN, IS) = 0.8 // (IN, EN) = -1.0 // (IN, ES) = -0.5
    • 궁합2: (IN, IN) = 1.4 // (IN, IS) = 0.4 // (IN, EN) = -0.4 // (IN, ES) = -1.4
    df_features[df_features.W.str.contains('IN') & df_features.M.str.contains('ES')]
    W M 잠재특징궁합1 잠재특징궁합2 여성bias 남성bias
    5 옥순(IN) 규빈(ES) -0.569901 -1.434720 -0.983853 0.853764
    6 옥순(IN) 다호(ES) -0.556357 -1.467226 -0.983853 0.944640
    13 영자(IN) 규빈(ES) -0.611208 -1.307021 -0.930421 0.853764

    H. NaN 에 대한 예측 – 숙제

    5. HW

    아래의 코드를 활용하여 net를 적합시켜라.

    df_view = pd.read_csv('https://raw.githubusercontent.com/guebin/DL2024/main/posts/solo.csv',index_col=0)
    df_train = df_view.stack().reset_index().set_axis(['W','M','y'],axis=1)
    w = {'옥순(IN)':0, '영자(IN)':1, '정숙(IS)':2, '영숙(IS)':3, '순자(EN)':4, '현숙(EN)':5, '서연(ES)':6, '보람(ES)':7, '하니(I)':8}
    m = {'영식(IN)':0, '영철(IN)':1, '영호(IS)':2, '광수(IS)':3, '상철(EN)':4, '영수(EN)':5, '규빈(ES)':6, '다호(ES)':7}
    X1 = torch.tensor(df_train['W'].map(w)) # length-n vector 
    X2 = torch.tensor(df_train['M'].map(m)) # length-n vector 
    E1 = torch.nn.functional.one_hot(X1).float()
    E2 = torch.nn.functional.one_hot(X2).float()
    y = torch.tensor(df_train['y']).float().reshape(-1,1)
    #--#
    torch.manual_seed(43052)
    l1 = torch.nn.Linear(in_features=9, out_features=2, bias=False)
    b1 = torch.nn.Linear(in_features=9, out_features=1, bias=False)
    l2 = torch.nn.Linear(in_features=8, out_features=2, bias=False)
    b2 = torch.nn.Linear(in_features=8, out_features=1, bias=False)
    sig = torch.nn.Sigmoid()
    loss_fn = torch.nn.MSELoss()
    params = list(l1.parameters())+list(b1.parameters())+list(l2.parameters())+list(b2.parameters())
    optimizr = torch.optim.Adam(params, lr=0.1) 
    #--#
    for epoc in range(100):
        ## step1 
        W_features = l1(E1)
        W_bias = b1(E1)
        M_features = l2(E2) 
        M_bias = b2(E2)
        score = (W_features * M_features).sum(axis=1).reshape(-1,1) + W_bias + M_bias
        yhat = sig(score) * 5 
        ## step2 
        loss = loss_fn(yhat,y)
        ## step3 
        loss.backward()
        ## step4 
        optimizr.step()
        optimizr.zero_grad()

    적합된 네트워크를 바탕으로 아래의 값에 대한 예측을 수행하라.

    df_test = pd.DataFrame({'W':['옥순(IN)','보람(ES)','하니(I)'],'M':['영식(IN)','다호(ES)','영호(IS)']})
    df_test
    W M
    0 옥순(IN) 영식(IN)
    1 보람(ES) 다호(ES)
    2 하니(I) 영호(IS)

    hint 답은 아래와 같다.

    yyhat.data
    tensor([[4.0671],
            [4.1238],
            [4.9121]])