Computer >> 컴퓨터 >  >> 프로그래밍 >> Ruby

루비(Ruby) 선형 회귀로 미래 예측하기 – Spotify 곡 인기도 분석 실습

우리가 내리는 많은 결정은 사실 수치적 관계를 중심으로 이루어집니다.

  • 과학 연구 결과 콜레스테롤을 낮춘다고 밝혀졌기 때문에 특정 음식을 먹습니다.
  • 더 높은 급여를 받을 가능성이 있기 때문에 교육을 계속 이어갑니다.
  • 가장 큰 폭으로 가치가 상승할 것이라 믿는 동네에 집을 삽니다.

그렇다면 우리는 어떻게 이런 결론에 도달하게 될까요? 아마도 누군가 방대한 데이터를 수집하고 이를 분석해 결론을 도출했을 가능성이 높습니다. 여기서 가장 널리 쓰이는 기법 중 하나가 바로 선형 회귀(linear regression)이며, 이는 지도 학습(supervised learning)의 한 형태입니다.

선형 관계(Linear Relationship)

x와 y라는 두 값이 선형 관계를 가진다는 것은, x가 1만큼 변할 때 y가 항상 일정한 고정된 양만큼 변한다는 의미입니다. 예시를 들어보면 더 쉽게 이해할 수 있습니다.

  • 피자 10판의 가격은 피자 한 판 가격의 정확히 10배입니다.
  • 높이 10피트짜리 벽은 5피트 벽보다 페인트가 두 배 필요합니다.

수학적으로 이러한 관계는 직선의 방정식으로 표현됩니다.

y = mx + b

수학은 때때로 어렵고 혼란스럽게 느껴지지만, 저에게는 종종 마법처럼 다가옵니다. 직선의 방정식을 처음 배웠을 때, 단 하나의 공식만으로 거리, 기울기, 그리고 직선 위의 점들을 계산할 수 있다는 사실이 얼마나 아름다운지 감탄했던 기억이 납니다.

하지만 데이터 포인트만 있을 때 이 공식은 어떻게 얻을 수 있을까요? 그 답이 바로 선형 회귀입니다. 선형 회귀는 머신러닝에서 가장 인기 있는 도구 중 하나입니다.

선형 회귀 예제: Spotify 곡의 인기도 예측

이번 글에서는 곡의 BPM(분당 비트 수)이 Spotify에서의 곡 인기도를 예측할 수 있는지 살펴보겠습니다.

선형 회귀는 두 변수 간의 관계를 모델링합니다. 하나는 '설명 변수(explanatory variable)', 다른 하나는 '종속 변수(dependent variable)'라고 부릅니다.

이 예제에서는 BPM이 인기도를 '설명'할 수 있는지 확인하려 합니다. 따라서 BPM이 설명 변수가 되고, 인기도가 종속 변수가 됩니다.

모델은 최소제곱법(least-squares regression)을 활용해 가장 잘 맞는 직선, 즉 y = mx + b 형태의 직선을 찾습니다.

설명 변수는 여러 개 사용할 수도 있지만, 이번 예제에서는 설명 변수가 하나뿐인 단순 선형 회귀(simple linear regression)를 진행합니다.

최소제곱법(Least-Squares)이란?

선형 회귀를 수행하는 방법은 여러 가지가 있으며, 그중 하나가 '최소제곱법'입니다. 이 방법은 각 데이터 포인트에서 직선까지의 수직 거리(편차)를 제곱한 값들의 합을 최소화함으로써 가장 잘 맞는 직선을 계산합니다.

다소 복잡하게 들릴 수 있지만, 요약하면 "직선과 데이터 포인트 사이의 간격을 최소화하는 직선을 만들어 달라"는 의미입니다.

편차를 제곱하고 합산하는 이유는 양수와 음수 값이 서로 상쇄되는 것을 방지하기 위해서입니다.

루비(Ruby) 선형 회귀로 미래 예측하기 – Spotify 곡 인기도 분석 실습

위 이미지는 Quora에서 가져온 것으로, 최소제곱법의 개념을 아주 잘 설명해 줍니다.

데이터셋 준비하기

이번 실습에서는 Kaggle의 Top 50 Spotify 2019 데이터셋을 사용합니다. CSV 파일로 다운로드할 수 있습니다.

데이터셋에는 16개의 열(column)이 있지만, 우리에게 필요한 것은 단 세 가지입니다. 'Track Name(곡 제목)', 'Beats Per Minute(BPM)', 'Popularity(인기도)'입니다. 머신러닝에서 가장 중요한 단계 중 하나는 데이터를 적절한 형식으로 정리하는 것이며, 이를 흔히 '데이터 정제(munging)'라고 부릅니다. 앞서 언급한 세 열을 제외한 나머지 데이터는 모두 삭제하면 됩니다.

정리된 CSV 파일은 다음과 같아야 합니다.

루비(Ruby) 선형 회귀로 미래 예측하기 – Spotify 곡 인기도 분석 실습

Ruby로 회귀 분석 수행하기

이번 예제에서는 ruby_linear_regression 젬(gem)을 사용합니다. 설치 명령어는 다음과 같습니다.

gem install ruby_linear_regression

이제 코딩을 시작할 준비가 되었습니다! 새 Ruby 파일을 생성하고 다음 require 문을 추가하세요.

require "ruby_linear_regression"
require "csv"

다음으로 CSV 데이터를 읽어들이고 #shift 메서드를 호출해 헤더 행을 제거합니다. 물론 CSV 파일에서 첫 번째 행을 직접 삭제하는 방법도 있습니다.

csv = CSV.read("top50.csv")
csv.shift

x 데이터 포인트와 y 데이터 포인트를 담을 빈 배열 두 개를 생성합니다.

x_data = []
y_data = []

그리고 .each 메서드로 반복 처리하면서 BPM 데이터는 x 배열에, Popularity 데이터는 y 배열에 추가합니다.

실제로 무슨 일이 일어나는지 궁금하다면 puts row 또는 p row로 행(row) 값을 출력해 보며 실험해 보세요.

csv.each do |row|
  x_data.push( [row[1].to_i] )
  y_data.push( row[2].to_i )
end

이제 ruby_linear_regression 젬을 사용할 차례입니다. 회귀 모델의 새 인스턴스를 생성하고, 데이터를 로드한 뒤, 모델을 학습시킵니다.

linear_regression = RubyLinearRegression.new
linear_regression.load_training_data(x_data, y_data)
linear_regression.train_normal_equation

다음으로 평균 제곱 오차(MSE, Mean Square Error)를 출력합니다. MSE는 실측값과 예측값 사이의 차이를 측정하는 지표로, 양수와 음수 값이 서로 상쇄되지 않도록 차이를 제곱합니다. 예측값과 실제값 사이의 거리가 커지지 않기를 원하기 때문에, 우리는 MSE를 최소화해야 합니다.

puts "Trained model with the following cost fit #{linear_regression.compute_cost}"

마지막으로, 학습된 모델을 활용해 예측을 수행해 보겠습니다. 과연 BPM이 250인 곡은 얼마나 인기가 있을까요? prediction_data 배열의 값을 자유롭게 바꿔가며 실험해 보세요.

prediction_data = [250]
predicted_popularity = linear_regression.predict(prediction_data)
puts "Predicted popularity: #{predicted_popularity.round}"

실행 결과

프로그램을 콘솔에서 실행하고 결과를 확인해 보겠습니다!

➜  ~ ruby spotify_regression.rb
Trained model with the following cost fit 9.504882197447587
Predicted popularity: 91

멋지네요! 이번에는 "250"을 "50"으로 바꾸고 모델이 무엇을 예측하는지 살펴보겠습니다.

➜  ~ ruby spotify_regression.rb
Trained model with the following cost fit 9.504882197447587
Predicted popularity: 86

결과를 보면 BPM이 높은 곡일수록 더 인기가 있는 것으로 나타납니다.

전체 프로그램 코드

제가 작성한 전체 파일은 다음과 같습니다.

require 'csv'
require 'ruby_linear_regression'

x_data = []
y_data = []
csv = CSV.read("top50.csv")
csv.shift

# Load data from CSV file into two arrays -- one for independent variables X (x_data) and one for the dependent variable y (y_data)
# Row[0] = title
# Row[1] = BPM
# Row[2] = Popularity
csv.each do |row|
  x_data.push( [row[1].to_i] )
  y_data.push( row[2].to_i )
end

# Create regression model
linear_regression = RubyLinearRegression.new

# Load training data
linear_regression.load_training_data(x_data, y_data)

# Train the model using the normal equation
linear_regression.train_normal_equation

# Output the cost
puts "Trained model with the following cost fit #{linear_regression.compute_cost}"

# Predict the popularity of a song with 250 BPM
prediction_data = [250]
predicted_popularity = linear_regression.predict(prediction_data)
puts "Predicted popularity: #{predicted_popularity.round}"

다음 단계

매우 간단한 예제였지만, 여러분은 방금 머신러닝의 핵심 기법인 첫 번째 선형 회귀를 성공적으로 실행했습니다. 더 깊이 파고들고 싶다면 다음과 같은 작업을 시도해 보세요.

  • 사용한 Ruby 젬의 소스 코드를 살펴보며 내부에서 어떤 수학적 계산이 일어나는지 확인해 보세요.
  • 원본 데이터셋으로 돌아가 변수를 추가로 포함시키고 다중 선형 회귀(multi-variable linear regression)를 실행해 MSE를 줄일 수 있는지 확인해 보세요. 예를 들어 '발렌스(valence, 곡의 긍정적인 정도)'도 인기도에 영향을 줄 수 있습니다.
  • ruby_linear_regression 젬으로 실행할 수 있는 경사 하강법(gradient descent) 모델도 직접 시도해 보세요.