반응형
wb_sunny 오늘 일상 공유
오늘은 제헌절이라서 부트캠프가 없는 날이었다. 어김없이 오전에 헬스를 갔다가 휴식을 취하고, 가족과 외식을 했다. 오후에는 그동안 부족했던 잠을 채웠고, OTT도 조금 시청했다. 이후 알바를 와서 프로그래머스 문제를 풀고, 부족한 부분을 채우기 위해 코드잇에서 제공하는 인강을 들었다. 오늘 블로그는 프로그래머스 문제 풀이 하나와, 선형 회귀·다중 선형 회귀 개념 정리를 담는다.
edit_note 오늘 코테 문제
| 문제 | 신고 결과 받기 |
| 플랫폼 | 프로그래머스 |
| 핵심 요구사항 | 유저가 서로를 신고한 기록(report)이 주어졌을 때, k번 이상 신고당한 유저는 정지 처리되고, 정지된 유저를 신고했던 모든 유저에게 정지 메일이 발송된다. 각 유저가 받게 되는 메일의 개수를 구하기. 단, 같은 유저를 여러 번 신고해도 신고 횟수는 1회로만 처리한다. |
python
def solution(id_list, report, k):
answer = []
id_dict = {}
report_dict = {}
report_list = []
for index, id in enumerate(id_list):
id_dict[id] = index
report_list.append([])
answer.append(0)
for info in report:
s_info = info.split(" ")
a_id = s_info[0]
b_id = s_info[1]
if b_id in report_list[id_dict[a_id]]:
continue
if b_id in report_dict.keys():
report_dict[b_id] += 1
else:
report_dict[b_id] = 1
report_list[id_dict[a_id]].append(b_id)
for i in range(len(report_list)):
for j in report_list[i]:
if report_dict[j] >= k:
answer[i] += 1
return answer
풀이 접근법
딕셔너리 2개와 리스트 1개를 선언했다.
id_dict는 유저 아이디를 인덱스로 바꿔서 접근을 쉽게 하기 위함이고, report_dict는 유저별로 신고받은 횟수를 세기 위함, report_list는 유저별로 누구를 신고했는지 아이디를 저장해두기 위함이다.1
유저 아이디를 인덱스로 매핑
id_list를 순회하면서 각 아이디를 숫자 인덱스로 바꿔주고, 그 개수만큼 신고 리스트와 answer를 미리 초기화했다.
2
신고 기록을 순회하며 중복 제거 후 집계
신고한 유저가 이미 그 유저를 신고한 기록이
report_list에 있으면 continue로 건너뛰어서 중복 신고를 1회로 처리하고, 그렇지 않으면 신고 횟수를 늘리면서 신고 리스트에 추가했다.3
유저별로 자신이 신고한 사람들의 신고 횟수 확인
유저마다 자신이 신고했던 사람들의 리스트를 다시 돌면서, 그 사람의 신고 횟수가 k 이상이면 answer를 1 증가시켰다.
시간복잡도
유저 수를 n, 신고 개수를 m이라고 하면 이중 for문이 있어서
O(m^2)이라고 판단했다. 특히 if b_id in report_list[id_dict[a_id]] 부분에서 리스트 안에 값이 있는지 확인하는 in 연산 자체가 리스트 길이만큼 걸리기 때문에, 신고 기록을 순회하며 이 체크를 반복하면 최악의 경우 m번 × m번이 되는 구조였다. for문을 좀 더 줄여볼 만하다는 생각이 들었다.고도화
아래 코드는 직접 짠 게 아니라 다른 풀이를 참고한 버전이다.
python
def solution(id_list, report, k):
answer = [0] * len(id_list)
reports = {x: 0 for x in id_list}
for r in set(report):
reports[r.split()[1]] += 1
for r in set(report):
if reports[r.split()[1]] >= k:
answer[id_list.index(r.split()[0])] += 1
return answer
이 코드는 answer와 reports 초기화를 컴프리헨션 한 줄로 줄이고,
id_list.index()로 for index, id in enumerate(id_list)로 하던 인덱스 매핑 작업을 대체했다. 가장 핵심은 for r in set(report)인데, 애초에 중복 신고를 set으로 걸러내서 내가 짠 코드처럼 리스트 안에 값이 있는지 매번 확인하던 in 체크 자체를 없앤 방식이다. 이러면 신고 기록을 딱 두 번만 순회하면 되기 때문에 O(m)으로 줄어든다.school 개념 정리 · 선형 회귀
선형 회귀란
선형 회귀(Linear Regression)는 입력 변수(x)와 목표 변수(y) 사이의 관계를 하나의 직선(또는 평면)으로 표현해서, 새로운 입력이 들어왔을 때 목표 변수를 예측하는 알고리즘이다. 데이터에 가장 잘 맞는 선을 찾기 위해 여러 가설 함수를 시도해보고, 예측값과 실제값의 오차(MSE)가 최소가 되는 θ 값들을 경사 하강법이나 정규 방정식으로 찾아낸다.
어디에 쓰이나
분류(classification)가 아니라 연속적인 숫자 값을 예측해야 하는 문제에 쓰인다. 집 크기로 집 가격을 예측하거나, 광고비 지출로 매출을 예측하거나, 공부 시간으로 시험 점수를 예측하는 것처럼 입력과 출력이 어느 정도 선형적인 관계를 가질 때 기본으로 시도해보는 모델이다. 성능이 아주 뛰어난 모델은 아니지만, 구현이 단순하고 해석이 쉬워서 다른 복잡한 모델(로지스틱 회귀, 신경망 등)로 넘어가기 전에 baseline으로 자주 쓰인다.
관련 개념
| 가설 함수 | 예측을 계산하는 식 hθ(x) = θ0 + θ1x |
| 손실 함수 | 가설 함수가 얼마나 틀렸는지 평가 (선형 회귀는 MSE 사용) |
| 경사 하강법 | 손실을 줄이는 방향으로 θ를 반복해서 업데이트 |
| 정규 방정식 | 반복 없이 방정식으로 θ를 한 번에 계산 |
| RMSE | 학습이 끝난 모델을 평가할 때 쓰는 지표 (MSE에 루트) |
일반화된 수식
위 개념들을 데이터 m개 전체에 대해 일반화하면 아래와 같은 식으로 정리된다.
가설 함수
hθ(x) = θ0 + θ1x
손실 함수 (MSE)
J(θ) =
1
2m
m
Σ
i=1
(hθ(x(i)) − y(i))2
경사 하강법 업데이트 (θ0, θ1 각각)
θ0 := θ0 − α ·
1
m
m
Σ
i=1
(hθ(x(i)) − y(i))
θ1 := θ1 − α ·
1
m
m
Σ
i=1
(hθ(x(i)) − y(i)) · x(i)
sklearn으로 구현하기
직접 경사 하강법이나 정규 방정식을 구현하지 않아도,
scikit-learn의 LinearRegression을 쓰면 몇 줄로 학습부터 평가까지 끝낼 수 있다. 내부적으로는 정규 방정식(또는 그와 동치인 최소자승법)으로 최적의 θ를 한 번에 계산해준다.python
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
# train/test 분리 (80% 학습, 20% 평가)
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=5)
model = LinearRegression()
model.fit(x_train, y_train) # 학습 (여기서 최적의 theta를 찾는다)
model.intercept_ # theta_0
model.coef_ # theta_1 (입력 변수가 여러 개면 배열로 나옴)
y_test_prediction = model.predict(x_test) # test셋으로 예측
# 예측값과 실제값을 RMSE로 비교해서 모델 성능 평가
mean_squared_error(y_test, y_test_prediction) ** 0.5
직접 구현할 때 신경 써야 했던 학습률(α), 반복 횟수(iterations), 편미분 공식 같은 건 전부
LinearRegression 내부에서 알아서 처리해준다. 개발자가 신경 쓰는 부분은 데이터를 어떻게 train/test로 나눌지, 어떤 입력 변수를 쓸지 정도로 줄어드는 셈이다.functions 개념 정리 · 다중 선형 회귀
다중 선형 회귀란
입력 변수가 하나가 아니라 여러 개(집 크기, 방 수, 지하철까지의 거리 등)인 선형 회귀다. 실무에서 목표 변수에 영향을 주는 요인은 보통 하나가 아니기 때문에, 실제로 많이 쓰이는 건 단순 선형 회귀보다 다중 선형 회귀 쪽이다. 여러 입력 변수를 하나의 설계 행렬(Design Matrix) X로 묶어서, 가설 함수를 θ 벡터와 X의 행렬곱으로 표현한다는 점만 단순 선형 회귀와 다르다.
어디에 쓰이나
목표 변수에 영향을 주는 요인이 하나로는 설명이 안 될 때 쓴다. 집 가격을 예측할 때 집 크기 하나만 보는 게 아니라 방 수, 지하철까지의 거리, 건축 연도까지 같이 고려하거나, 매출을 예측할 때 광고비 하나가 아니라 광고비·시즌·경쟁사 프로모션 여부를 같이 넣는 식이다. 속성(feature)을 늘릴수록 더 현실적인 예측이 가능해지지만, 그만큼 각 속성이 실제로 의미 있는 정보인지 따져보는 게 중요해진다.
관련 개념
| 속성(feature) | 입력 변수 하나하나 (x1, x2, ... xn) |
| 설계 행렬 | 모든 데이터·모든 속성을 하나로 묶은 행렬 X |
| 가중치 벡터 | 속성별 영향력을 나타내는 θ (θ0는 절편) |
| Convex 함수 | MSE는 항상 convex라 극소점이 곧 최소점이 된다 |
일반화된 수식 (행렬 표현)
입력 변수가 여러 개로 늘어나면 시그마로 하나씩 표현하기 번거로워지는데, 모든 데이터를 설계 행렬 X, 모든 파라미터를 벡터 θ로 묶으면 아래처럼 훨씬 간결하게 표현할 수 있다.
가설 함수 (모든 데이터에 대한 예측값)
hθ(X) = Xθ
예측 오차
error = Xθ − y
경사 하강법 업데이트 (θ 전체를 한 번에)
θ := θ − α ·
1
m
XT(Xθ − y)
정규 방정식
θ = (XTX)−1XTy
sklearn으로 구현하기
코드 구조는 단순 선형 회귀와 완전히 동일하다. 입력 변수 x가 컬럼 하나짜리 벡터였다면, 다중 선형 회귀에서는 컬럼이 여러 개인 X(2차원 배열/DataFrame)로 바뀔 뿐,
LinearRegression을 쓰는 방식 자체는 똑같다. sklearn이 입력 변수 개수와 상관없이 알아서 다중 회귀를 처리해준다.python
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
# X: 집 크기, 지하철 거리, 방 수 등 여러 컬럼을 가진 설계 행렬
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=5)
model = LinearRegression()
model.fit(X_train, y_train)
y_predict = model.predict(X_test)
mean_squared_error(y_test, y_predict) ** 0.5
한 줄 정리
✓선형 회귀는 연속적인 값을 예측하는 가장 기본적인 회귀 모델이고, 원리(가설 함수 → 손실 함수 → 최적화)를 이해했으면 실제 구현은 sklearn의 LinearRegression 한 줄로 끝난다. 다중 선형 회귀도 입력 변수가 여러 개인 X를 넣어주는 것 말고는 코드가 똑같다.
nights_stay 오늘 하루 회고
저번 주 부트캠프에서 머신러닝 기본기부터 로지스틱 회귀까지 정말 많은 이론을 진행했다. 이론으로는 이해되는 부분이 있었지만 실습과 연결이 잘 안 되는 느낌이라 솔직히 걱정이 많았다. 오늘 코드잇 인강을 통해 다시 공부해보니, 생각보다 선형 회귀와 다중 선형 회귀는 데이터를 어떻게 분류하고 표현하는지만 잘 잡으면 구현 자체는 크게 어렵지 않다는 걸 알게 됐다. 이론과 실습 사이의 간격을 이렇게라도 하나씩 채워나가야겠다는 생각이 든 하루였다.
반응형
'AI' 카테고리의 다른 글
| [부트캠프 10일차] 예측이 틀렸을 때 - 손실 함수부터 K-폴드까지 (0) | 2026.07.15 |
|---|---|
| [부트캠프 8일차] 선형대수학 (1) | 2026.07.13 |
| [부트캠프 5일차] 첫 멘토링 후기 (0) | 2026.07.08 |
| [부트캠프 3일차] 시간복잡도와 DataFrame 전처리 정리 (0) | 2026.07.06 |
| [부트캠프 2일차] 파이썬 기초부터 OOP까지 (0) | 2026.07.03 |
