linear_numpy.py

Download
python 256 lines 6.7 KB
  1"""
  2Linear Regression - NumPy From-Scratch ๊ตฌํ˜„
  3
  4์ด ํŒŒ์ผ์€ ์„ ํ˜• ํšŒ๊ท€๋ฅผ ์ˆœ์ˆ˜ NumPy๋กœ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
  5ํ”„๋ ˆ์ž„์›Œํฌ ์—†์ด gradient descent๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•˜์—ฌ
  6๋”ฅ๋Ÿฌ๋‹์˜ ๊ธฐ๋ณธ ์›๋ฆฌ๋ฅผ ์ดํ•ดํ•ฉ๋‹ˆ๋‹ค.
  7
  8ํ•™์Šต ๋ชฉํ‘œ:
  91. Forward pass: y_hat = Xw + b
 102. Loss ๊ณ„์‚ฐ: MSE = (1/2n) * ||y - y_hat||^2
 113. Backward pass: gradient ๊ณ„์‚ฐ
 124. Weight update: w = w - lr * dw
 13"""
 14
 15import numpy as np
 16import matplotlib.pyplot as plt
 17
 18
 19class LinearRegressionNumpy:
 20    """
 21    NumPy๋กœ ๊ตฌํ˜„ํ•œ Linear Regression
 22
 23    ์ˆ˜ํ•™์  ๋ฐฐ๊ฒฝ:
 24    - ๋ชจ๋ธ: ลท = Xw + b
 25    - ์†์‹ค: L = (1/2n) ฮฃ(y - ลท)ยฒ
 26    - ๊ทธ๋ž˜๋””์–ธํŠธ:
 27        โˆ‚L/โˆ‚w = (1/n) X^T (ลท - y)
 28        โˆ‚L/โˆ‚b = (1/n) ฮฃ(ลท - y)
 29    """
 30
 31    def __init__(self, input_dim: int, output_dim: int = 1):
 32        """
 33        Args:
 34            input_dim: ์ž…๋ ฅ ํŠน์„ฑ ์ˆ˜
 35            output_dim: ์ถœ๋ ฅ ์ฐจ์› (๊ธฐ๋ณธ 1)
 36        """
 37        # Xavier/He ์ดˆ๊ธฐํ™”: ๋ถ„์‚ฐ์„ 2/n์œผ๋กœ ์œ ์ง€
 38        self.W = np.random.randn(input_dim, output_dim) * np.sqrt(2.0 / input_dim)
 39        self.b = np.zeros((1, output_dim))
 40
 41        # ๊ทธ๋ž˜๋””์–ธํŠธ ์ €์žฅ์šฉ
 42        self.dW = None
 43        self.db = None
 44
 45        # Forward์—์„œ ์บ์‹œ (backward์—์„œ ์‚ฌ์šฉ)
 46        self._cache = {}
 47
 48    def forward(self, X: np.ndarray) -> np.ndarray:
 49        """
 50        Forward pass: ลท = Xw + b
 51
 52        Args:
 53            X: ์ž…๋ ฅ ๋ฐ์ดํ„ฐ (batch_size, input_dim)
 54
 55        Returns:
 56            y_hat: ์˜ˆ์ธก๊ฐ’ (batch_size, output_dim)
 57        """
 58        # ์ž…๋ ฅ ์บ์‹œ (backward์—์„œ ํ•„์š”)
 59        self._cache['X'] = X
 60
 61        # ์„ ํ˜• ๋ณ€ํ™˜: y = Xw + b
 62        y_hat = np.dot(X, self.W) + self.b
 63
 64        return y_hat
 65
 66    def compute_loss(self, y: np.ndarray, y_hat: np.ndarray) -> float:
 67        """
 68        Mean Squared Error ์†์‹ค ๊ณ„์‚ฐ
 69
 70        L = (1/2n) ฮฃ(y - ลท)ยฒ
 71
 72        Args:
 73            y: ์‹ค์ œ๊ฐ’ (batch_size, output_dim)
 74            y_hat: ์˜ˆ์ธก๊ฐ’ (batch_size, output_dim)
 75
 76        Returns:
 77            loss: ์Šค์นผ๋ผ ์†์‹ค๊ฐ’
 78        """
 79        n = y.shape[0]
 80        loss = (1 / (2 * n)) * np.sum((y - y_hat) ** 2)
 81        return loss
 82
 83    def backward(self, y: np.ndarray, y_hat: np.ndarray) -> None:
 84        """
 85        Backward pass: ๊ทธ๋ž˜๋””์–ธํŠธ ๊ณ„์‚ฐ
 86
 87        Chain Rule ์ ์šฉ:
 88        โˆ‚L/โˆ‚w = โˆ‚L/โˆ‚ลท ร— โˆ‚ลท/โˆ‚w
 89               = (1/n)(ลท - y) ร— X^T
 90               = (1/n) X^T (ลท - y)
 91
 92        โˆ‚L/โˆ‚b = โˆ‚L/โˆ‚ลท ร— โˆ‚ลท/โˆ‚b
 93               = (1/n) ฮฃ(ลท - y)
 94
 95        Args:
 96            y: ์‹ค์ œ๊ฐ’
 97            y_hat: ์˜ˆ์ธก๊ฐ’
 98        """
 99        X = self._cache['X']
100        n = y.shape[0]
101
102        # ์˜ค์ฐจ
103        error = y_hat - y  # (batch_size, output_dim)
104
105        # ๊ทธ๋ž˜๋””์–ธํŠธ ๊ณ„์‚ฐ
106        # โˆ‚L/โˆ‚W = (1/n) X^T @ error
107        self.dW = (1 / n) * np.dot(X.T, error)
108
109        # โˆ‚L/โˆ‚b = (1/n) ฮฃerror (๊ฐ ์ถœ๋ ฅ ์ฐจ์›๋ณ„)
110        self.db = (1 / n) * np.sum(error, axis=0, keepdims=True)
111
112    def update(self, lr: float) -> None:
113        """
114        ๊ฐ€์ค‘์น˜ ์—…๋ฐ์ดํŠธ (Gradient Descent)
115
116        w = w - ฮท ร— โˆ‚L/โˆ‚w
117        b = b - ฮท ร— โˆ‚L/โˆ‚b
118
119        Args:
120            lr: learning rate
121        """
122        self.W -= lr * self.dW
123        self.b -= lr * self.db
124
125    def fit(
126        self,
127        X: np.ndarray,
128        y: np.ndarray,
129        lr: float = 0.01,
130        epochs: int = 1000,
131        verbose: bool = True
132    ) -> list:
133        """
134        ๋ชจ๋ธ ํ•™์Šต
135
136        Args:
137            X: ํ•™์Šต ๋ฐ์ดํ„ฐ (n_samples, n_features)
138            y: ํƒ€๊ฒŸ๊ฐ’ (n_samples, 1) ๋˜๋Š” (n_samples,)
139            lr: learning rate
140            epochs: ํ•™์Šต ๋ฐ˜๋ณต ํšŸ์ˆ˜
141            verbose: ์ง„ํ–‰ ์ƒํ™ฉ ์ถœ๋ ฅ ์—ฌ๋ถ€
142
143        Returns:
144            losses: ์—ํญ๋ณ„ ์†์‹ค ๋ฆฌ์ŠคํŠธ
145        """
146        # y shape ๋ณด์ •
147        if y.ndim == 1:
148            y = y.reshape(-1, 1)
149
150        losses = []
151
152        for epoch in range(epochs):
153            # 1. Forward pass
154            y_hat = self.forward(X)
155
156            # 2. Loss ๊ณ„์‚ฐ
157            loss = self.compute_loss(y, y_hat)
158            losses.append(loss)
159
160            # 3. Backward pass (gradient ๊ณ„์‚ฐ)
161            self.backward(y, y_hat)
162
163            # 4. Weight update
164            self.update(lr)
165
166            # ์ง„ํ–‰ ์ƒํ™ฉ ์ถœ๋ ฅ
167            if verbose and (epoch + 1) % (epochs // 10) == 0:
168                print(f"Epoch {epoch + 1}/{epochs}, Loss: {loss:.6f}")
169
170        return losses
171
172    def predict(self, X: np.ndarray) -> np.ndarray:
173        """์˜ˆ์ธก"""
174        return self.forward(X)
175
176
177def generate_sample_data(n_samples: int = 100, n_features: int = 1, noise: float = 0.1):
178    """
179    ํ…Œ์ŠคํŠธ์šฉ ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ
180
181    y = 2x + 3 + noise
182    """
183    np.random.seed(42)
184    X = np.random.randn(n_samples, n_features)
185
186    # ์‹ค์ œ ๊ฐ€์ค‘์น˜ (ํ•™์Šต์œผ๋กœ ์ฐพ์•„์•ผ ํ•  ๊ฐ’)
187    true_w = np.array([[2.0]])
188    true_b = 3.0
189
190    y = np.dot(X, true_w) + true_b + noise * np.random.randn(n_samples, 1)
191
192    return X, y, true_w, true_b
193
194
195def main():
196    """๋ฉ”์ธ ์‹คํ–‰ ํ•จ์ˆ˜"""
197    print("=" * 60)
198    print("Linear Regression - NumPy From-Scratch ๊ตฌํ˜„")
199    print("=" * 60)
200
201    # 1. ๋ฐ์ดํ„ฐ ์ƒ์„ฑ
202    print("\n1. ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ")
203    X, y, true_w, true_b = generate_sample_data(n_samples=100, noise=0.1)
204    print(f"   X shape: {X.shape}")
205    print(f"   y shape: {y.shape}")
206    print(f"   True w: {true_w.flatten()}, True b: {true_b}")
207
208    # 2. ๋ชจ๋ธ ์ƒ์„ฑ
209    print("\n2. ๋ชจ๋ธ ์ดˆ๊ธฐํ™”")
210    model = LinearRegressionNumpy(input_dim=1, output_dim=1)
211    print(f"   Initial W: {model.W.flatten()}")
212    print(f"   Initial b: {model.b.flatten()}")
213
214    # 3. ํ•™์Šต
215    print("\n3. ํ•™์Šต ์‹œ์ž‘")
216    losses = model.fit(X, y, lr=0.1, epochs=100, verbose=True)
217
218    # 4. ๊ฒฐ๊ณผ ํ™•์ธ
219    print("\n4. ํ•™์Šต ๊ฒฐ๊ณผ")
220    print(f"   Learned W: {model.W.flatten()}")
221    print(f"   Learned b: {model.b.flatten()}")
222    print(f"   True W: {true_w.flatten()}")
223    print(f"   True b: {true_b}")
224    print(f"   Final Loss: {losses[-1]:.6f}")
225
226    # 5. ์‹œ๊ฐํ™”
227    print("\n5. ์‹œ๊ฐํ™”")
228    fig, axes = plt.subplots(1, 2, figsize=(12, 4))
229
230    # Loss ๊ณก์„ 
231    axes[0].plot(losses)
232    axes[0].set_xlabel('Epoch')
233    axes[0].set_ylabel('Loss (MSE)')
234    axes[0].set_title('Training Loss')
235    axes[0].grid(True)
236
237    # ๋ฐ์ดํ„ฐ์™€ ์˜ˆ์ธก ์ง์„ 
238    y_pred = model.predict(X)
239    sorted_idx = np.argsort(X.flatten())
240    axes[1].scatter(X, y, alpha=0.5, label='Data')
241    axes[1].plot(X[sorted_idx], y_pred[sorted_idx], 'r-', linewidth=2, label='Prediction')
242    axes[1].set_xlabel('X')
243    axes[1].set_ylabel('y')
244    axes[1].set_title('Linear Regression Fit')
245    axes[1].legend()
246    axes[1].grid(True)
247
248    plt.tight_layout()
249    plt.savefig('linear_regression_result.png', dpi=150)
250    plt.show()
251    print("   ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ์ €์žฅ: linear_regression_result.png")
252
253
254if __name__ == "__main__":
255    main()