서론
선형 회귀분석은 두 변수 간의 관계를 모델링 하는데에 사용되는 기법 중 하나이다.
본 보고서에서는 현재 졸업 프로젝트로 진행하고 있는 심박수 관리 앱을 진행하면서 나이와 관련된 심박수를
추정하기위하여 필요한 부분에서 회귀분석을 이용하여 테스트를 해보고자 한다.
매개변수 추정을 위해서는 모의 담금질 알고리즘을 사용할 예정이다.
처음에는 나이에 따른 심박수 변화에 대한 시뮬레이션을 해보려고 하였지만, 공공데이터에 관련된 자료가 없었고
그래도 관련이 있는 나이에 따른 혈압 공공데이터를 찾을 수 있었다.
따라서 국민건강보험공단_한국인 혈압 참조 표준 데이터를 사용하여 알고리즘 수행하고, 구해진 값을 기준으로
랜덤으로 생성된 혈압 값을 알고리즘을 적용해 얼마나 일치시킬 수 있는지 진행할 예정이다.
선형 회귀 모델링
데이터 준비:

사진에서 보이는 것과 같이 나이와 혈압의 관측 데이터가 준비 되었다.
혈압 평균 데이터와 해당 연령대의 나이 데이터를 사용할 예정이다.
나이를 독립 변수 X로, 혈압을 종속 변수 Y로 설정하여 구축한다.
엑셀로 표현된 데이터에서 20세의 남성 혈압 평균을 시작으로 하여 75세 남성 혈압 평균까지의 데이터를 이용해보자.
데이터가 많기 때문에 1년씩이 아닌 5년의 간격으로 데이터를 수집해보자.
1
2
3
double[] age = { 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75 };
double[] bloodPressure = { 120, 121, 122, 124, 124, 124, 124, 125, 126, 127, 128, 129 };
//데이터 준비
나이를 X로 혈압을 Y로 설정하고, 이를 위해 X와 Y의 평균, 분산, 공분산을 계산한다.
1
2
3
4
double xMean = calculateMean(age);
double yMean = calculateMean(bloodPressure);
double xVariance = calculateVariance(age);
double xyCovariance = calculateCovariance(age, bloodPressure);
이후 모의 담금질 알고리즘을 사용하여 최적의 매개 변수 A,B를 추정한다. 이는 선형 회귀 모델에서의 기울기와 절편을 나타낸다.
모의 담금질 알고리즘
우선 모의 담금질 알고리즘은 최적화 문제를 해결하기 위해 사용된다.
이 알고리즘은 금속 소재의 물리적 성질을 모방하여 최적해를 탐색하는 방법이다.
이 알고리즘을 적용하기 위해서는 초기 솔루션, 이웃해, 온도 감소 로직을 작성해야 하는데,
각 회귀분석 문제에 맞게 구현하여 적용하여야 한다.
현재 진행하고 있는 심박수 데이터를 사용한 회귀 분석에서는나이에 따른 혈압의 변화를 예측하고 분석하기 때문에
예를 들어, 주어진 범위 내에서 나이에 따른 예상 혈압 값을 계산한다.
따라서 초기온도와 냉각비율을 여러번 조정 해보면서 최적의 조합을 찾는것이 목표이다.
실제로 알고리즘을 구현하여, 성능을 평가해보자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import java.util.Random;
public class SimulatedAnnealing {
private static final double INITIAL_TEMPERATURE = 1000; // 초기 온도
private static final double COOLING_RATE = 0.95; // 냉각율
private static final int MAX_ITERATIONS = 1000; // 최대 반복 횟수
public static void main(String[] args) {
// 최적화 문제에 대한 초기 솔루션 생성
double currentSolution = generateInitialSolution();
double bestSolution = currentSolution;
// 온도 초기화
double temperature = INITIAL_TEMPERATURE;
// 모의 담금질 알고리즘 반복
for (int i = 0; i < MAX_ITERATIONS; i++) {
// 이웃해 생성
double newSolution = generateNeighborSolution(currentSolution);
// 현재 솔루션과 이웃해를 비교하여 새로운 솔루션 선택
if (acceptSolution(currentSolution, newSolution, temperature)) {
currentSolution = newSolution;
}
// 현재 솔루션과 최적 솔루션 비교하여 갱신
if (currentSolution < bestSolution) {
bestSolution = currentSolution;
}
// 온도 감소
temperature *= COOLING_RATE;
}
System.out.println("Best Solution: " + bestSolution);
}
// 최적화 문제에 대한 초기 솔루션 생성
private static double generateInitialSolution() {
Random random = new Random();
// 초기 솔루션 생성 로직 구현
return random.nextDouble();
}
// 이웃해 생성
private static double generateNeighborSolution(double currentSolution) {
Random random = new Random();
// 이웃해 생성 로직 구현
return currentSolution + random.nextDouble() * 0.1 - 0.05;
}
// 새로운 솔루션을 선택할지 여부 결정
private static boolean acceptSolution(double currentSolution, double newSolution, double temperature) {
double delta = newSolution - currentSolution;
// Metropolis 확률 분포에 따른 선택 로직 구현
return delta < 0 || Math.exp(-delta / temperature) > Math.random();
}
}
현재 이 코드를 실행 하였을떄, 결과값이 약 -10정도의 값에서 머무른다.
여기서 냉각율 조정에 따라 얼마나 차이가 나는지 확인해보자.
- 냉각율 0.95: Best Solution: -10.429206910793436
- 냉각율 0.97: Best Solution: -7.236561185200891
- 냉각율 0.99: Best Solution: -0.054459183795332884
결과에서 보이는것과 같이 냉각율이 1에 가까워 질수록 BEST Solution은 0에 가까워지게 된다.
모의담금 알고리즘은 현재의 솔루션과 이웃해를 비교하여 새로운 솔루션을 선택하기 때문에 반복횟수가 많을수록
더 정확한 솔루션을 구할수 있다. 또한 냉각율이 1에 가까울때에도 마찬가지이다.
당연하겠지만 손실 함수 값이 낮을수록 더 좋은 솔루션이다.
그리고 온도는 알고리즘의 탐색 범위를 조절하는 요소이며, 초기에는 높은 온도로 시작하지만, 시간이 지남에 따라 온도를 감소
시킨다. 온도 감소는 확률적으로 나쁜 솔루션을 선택할 가능성을 줄이기도 한다.
현재 이 코드에서는 종료 조건을 일정한 반복 횟수로 설정해두었다. 위에서 말했다시피 반복횟수가 많을수록 정확한 솔루션을 얻을 수 있지만, 너무 큰 횟수로 설정했을때는 오류가 날수도 있다.
나이에 따른 혈압 데이터를 활용하여 알고리즘 적용
그럼 위에서 알아보고 구현한 알고리즘을 적용시켜 실제로 데이터값을 비교해보자.
우선 JAVA 코드로 진행하려고 하였지만, 그래프를 출력시키기 위하여 라이브러리를 추가하여야 하는데
라이브러리 추가를 하는 단계에서 계속 적용이 되지않는 일이 생겨, PYTHON 코드로 우선적으로 진행하게 되었다.
위에서 구한 나이에 따른 혈압 공공 데이터의 값을 기준으로 최적화된 “Optimized Intercept”: 절편값과 “Optimized Slope”:기울기 값을 출력하고, 그래프를 출력시켜 reqression을 확인하는 것이 목표이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import random
import matplotlib.pyplot as plt
import numpy as np
# 나이와 혈압 데이터
age = [20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75]
bloodPressure = [120, 121, 122, 124, 124, 124, 124, 125, 126, 127, 128, 129]
# 모의 담금질 알고리즘을 사용한 회귀분석
def perform_regression_analysis(x, y, initial_solution):
initial_temperature = 1000 # 초기 온도
cooling_rate = 0.95 # 냉각율
max_iterations = 1000 # 최대 반복 횟수
current_solution = initial_solution[:]
best_solution = current_solution[:]
temperature = initial_temperature
for i in range(max_iterations):
new_solution = generate_neighbor_solution(current_solution)
if accept_solution(current_solution, new_solution, temperature):
current_solution = new_solution[:]
if calculate_loss(current_solution) < calculate_loss(best_solution):
best_solution = current_solution[:]
temperature *= cooling_rate
return best_solution
# 이웃해 생성
def generate_neighbor_solution(solution):
neighbor_solution = solution[:]
neighbor_solution[0] += random.uniform(-0.05, 0.05) # Intercept
neighbor_solution[1] += random.uniform(-0.05, 0.05) # Slope
return neighbor_solution
# 새로운 솔루션을 선택할지 여부 결정
def accept_solution(current_solution, new_solution, temperature):
current_loss = calculate_loss(current_solution)
new_loss = calculate_loss(new_solution)
delta = new_loss - current_loss
return delta < 0 or random.random() < np.exp(-delta / temperature)
# 손실 함수 계산
def calculate_loss(solution):
intercept = solution[0]
slope = solution[1]
predicted_blood_pressure = [intercept + slope * x_val for x_val in age]
loss = sum((predicted_blood_pressure[i] - bloodPressure[i])**2 for i in range(len(age)))
return loss / len(age)
# 초기 솔루션 생성
initial_solution = [random.random(), random.random()]
# 회귀분석 수행
optimized_solution = perform_regression_analysis(age, bloodPressure, initial_solution)
optimized_intercept, optimized_slope = optimized_solution
# 결과 출력
print("Optimized Intercept:", optimized_intercept)
print("Optimized Slope:", optimized_slope)
# 그래프 출력
x_vals = np.linspace(min(age), max(age), 100)
y_vals = optimized_intercept + optimized_slope * x_vals
plt.scatter(age, bloodPressure, label='Data')
plt.plot(x_vals, y_vals, color='red', label='Regression Line')
plt.xlabel('Age')
plt.ylabel('Blood Pressure')
plt.title('Linear Regression Analysis')
plt.legend()
plt.show()
결과:
Optimized Intercept: 4.636075892580279
Optimized Slope: 2.2414906858506733

나이와 혈압 데이터:
age = [20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75]
bloodPressure = [120, 121, 122, 124, 124, 124, 124, 125, 126, 127, 128, 129]
이 코드에서는 처음부터 위의 데이터값을 활용하여 적용시켰기 떄문에 정상적인 그래프의 형태를 볼 수 있다.
랜덤으로 생성한 혈압 데이터를 기반으로 알고리즘 적용
그럼 이번에는 이 공공데이터의 나이에 따른 혈압데이터를 기준으로 랜덤 데이터를 생성하여 얼마나 가깝게 이 그래프에
형상화 될 수 있는지 확인해보고자 한다.
정상적인 사람의 혈압은 60~150정도 이기때문에 랜덤값은 60~150사이의 값으로 설정하고자 한다.
위에 주어진 공공데이터는 기준으로 두고, 새로 만들어지는 솔루션은 60~150 사이의 값인 것이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import random
import matplotlib.pyplot as plt
import numpy as np
age = [20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75]
bloodPressure = [120, 121, 122, 124, 124, 124, 124, 125, 126, 127, 128, 129]
def calculate_loss(age, bloodPressure, coefficients):
total_loss = 0
for i in range(len(age)):
predicted_bloodPressure = coefficients[0] * age[i] + coefficients[1]
loss = (predicted_bloodPressure - bloodPressure[i])**2
total_loss += loss
return total_loss / len(age)
def generate_initial_solution():
age_mean = np.mean(age)
bloodPressure_mean = np.mean(bloodPressure)
slope = 0
if age_mean != 0:
slope = (bloodPressure_mean - np.mean(bloodPressure)) / age_mean
intercept = bloodPressure_mean - slope * age_mean
return [slope, intercept]
def generate_neighbor_solution(current_solution):
return [
current_solution[0] + random.uniform(-0.01, 0.01),
current_solution[1] + random.uniform(-0.01, 0.01)
]
def accept_solution(current_solution, new_solution, temperature):
delta = calculate_loss(age, bloodPressure, new_solution) - calculate_loss(
age, bloodPressure, current_solution)
return delta < 0 or random.uniform(0, 1) < pow(2.71828, -delta / temperature)
def simulated_annealing():
initial_temperature = 1000
cooling_rate = 0.95
max_iterations = 1000
current_solution = generate_initial_solution()
best_solution = current_solution.copy()
temperature = initial_temperature
for iteration in range(max_iterations):
new_solution = generate_neighbor_solution(current_solution)
if accept_solution(current_solution, new_solution, temperature):
current_solution = new_solution.copy()
if calculate_loss(age, bloodPressure, current_solution) < calculate_loss(
age, bloodPressure, best_solution):
best_solution = current_solution.copy()
temperature *= cooling_rate
return best_solution
# 모의담금 알고리즘 실행
optimized_coefficients = simulated_annealing()
optimized_slope, optimized_intercept = optimized_coefficients
print("Optimized Slope:", optimized_slope)
print("Optimized Intercept:", optimized_intercept)
# 그래프 출력
plt.scatter(age, bloodPressure, color='blue', label='Data')
plt.plot(age, [optimized_slope * x + optimized_intercept for x in age],
color='red',
label='Regression Line')
plt.xlabel('Age')
plt.ylabel('Blood Pressure')
plt.legend()
plt.show()

그래프에서 보이는 것과 같이 기존의 그래프에 비해서는 많이 완만한 형태 인것을 볼 수 있다.
그렇다면 기존의 그래프와 가깝게 일치하도록 수행하려면 어떤 방법을 써야할지 고민해봐야한다.
기준 그래프와 y절편과 기울기 값을 비슷하게 만들기 위해서는 초기 솔루션의 설정을 조정하는 방법을 써보려고한다.
초기 솔루션의 기울기와 y절편을 평균값에 더한 다음, 랜덤한 작은 범위의 값을 더해주는 방식을 사용한다면, 초기 솔루션의 값들을 조금 조정하기 때문에, 랜덤으로 생성되는 그래프와 기준 그래프의 형태가 비슷할 것이다.
1
2
3
4
5
6
7
8
def generate_initial_solution():
age_mean = np.mean(age)
bloodPressure_mean = np.mean(bloodPressure)
slope = 0
if age_mean != 0:
slope = (bloodPressure_mean - np.mean(bloodPressure)) / age_mean
intercept = bloodPressure_mean - slope * age_mean
return [slope + random.uniform(-0.01, 0.01), intercept + random.uniform(-0.01, 0.01)]
위의 내용대로 수정하여 다시 출력해보았지만, 크게 바뀌는 점은 없었다.
하지만, 그래프를 자세히 보니 두 그래프의 y축의 범위가 다른것을 볼 수 있었다.
따라서 2번째 그래프의 y축 범위를 1번째 그래프와 동일하게 설정하였더니 그래프의 형태가 비슷한것을 확인할 수 있었다.

또한 y절편의 값과 기울기 값 역시
Optimized Intercept: 3.7324675017631814
Optimized Slope: 2.252491426907542으로 비슷하게 출력되는것을 확인하였다.
결론
이 프로젝트에서 진행한것을 순서대로 정리해보자.
공공데이터를 활용하여 모의 담금질 알고리즘을 이용한 회귀분석을 적용시켜 정규회된 그래프와 값을 구하였다.
구해진 값과 그래프를 기준으로 두고, 랜덤된 값을 생성시켜 새로 회귀분석을 적용시켜 기준이 되는 값과 비슷하도록
범위 설정과 여러번의 조정을 통해 최적의 값을 출력할수 있었다.
모의 담금질이 사용된 첫번째 코드에서는 솔루션 공간을 무작위로 탐색하고, 약간 변경된 새로운 솔루션을 계속 생성하여 테스트
하는 방법으로 진행하였으며, 두번쨰 코드에서는 첫번째 코드에서 구해진 값을 기준으로 이웃 해결책은 현재 해결책에서 약간의 변화를 주어 생성되도록 진행하였다.
두 코드의 손실함수는 모두 예측 혈압과 실제 혈압 간의 평균 제곱 오차를 사용하여 계산되었으며, 예측값이 실제 값과 얼마나 가까운지를 측정한다.
initial_temperature = 1000을 설정한 이유는 최적해를 찾기 위해 탐색 공간을 얼마나 할지에 대한 설정값이며,
cooling_rate = 0.95는 각 반복후에 온도를 얼마나 줄일지 결정하는 것이다. 이러한 파라미터들을 조금씩 변동을 주며
테스트 해보면 더욱더 정확한 값을 출력할수 있었다.