241212 에이콘 아카데미 수업에 기반하여 작성되었음을 알립니다.
단순 선형 회기분석 실습
w : 슬로프, 기울기 / b : 바이어스, 편향
tf2.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- TensorFlow.js 로드 -->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs/dist/tf.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-vis/dist/tfjs-vis.umd.min.js"></script>
<!-- 통계관련 기타 (상관계수 등) 라이브러리 로드 -->
<script src="https://cdn.jsdelivr.net/npm/simple-statistics@7.7.0/dist/simple-statistics.min.js"></script>
<title>Document</title>
<!-- Main script -->
<script type="module">
import {func} from "./tf2script.mjs";
window.func = func;
</script>
</head>
<body>
<h3>회귀분석 모델 기본 이해</h3>
새로운 값 입력: <input type="number" value="1" id="inputValue" />
<button onclick="func()">결과보기</button>
<hr>
<div id="showResult"></div>
<div id="r2"></div> <!-- 모델 성능 확인용 결정계수 표시-->
<div id="chart-container" style="margin-top:20px"></div>
</body>
</html>
tf2script.mjs
function calcCorrelation(){
// 상관 관계 확인
const x = [1,2,3,4,5,6,7,8,9,10];
const y = [1,0,5,4,6,8,4,8,9,12];
const correlation = ss.sampleCorrelation(x, y);
console.log(`피어슨 상관계수 : ${correlation.toFixed(3)}`); // 0.892 매우 강한 양의 상관관계
// 인과 관계? 있다라는 판단이 들면 회귀분석을 시작
}
let model; // 회기분석 모델
let xs, ys; // 학습 데이터
export function func(){
calcCorrelation();
if(!model){
initModel().then(() => { // 모델 생성
predictDisplay();
calculateR2(); // 회기분석 모델 성능 확인
});
} else {
predictDisplay();
}
}
async function initModel(){
// 모델 초기화
model = tf.sequential();
model.add(tf.layers.dense({units:1, inputShape:[1]})); // 입력값이 1차원으로 1개의 유닛에 들어온다.
model.compile({loss:'meanSquaredError', optimizer:'sgd'});
// loss는 코스트를 말한다. 코스트가 작을수록 0에 가까울수록 좋다.
// optimizer에는 sgd, adam 등등 이 있다. 보통 adam이 좋다! 코스트를 minimize하는 방법
// 학습 데이터 : 표본으로 학습
xs = tf.tensor2d([1,2,3,4,5,6,7,8,9,10], [10, 1]); // 첫번째 변수 : 독립변수, x, 영향을 주는 변수
ys = tf.tensor1d([1,0,5,4,6,8,4,8,9,12]); // 두번째 변수 : 종속변수, y, 영향을 받는 변수
await model.fit(xs, ys); // 학습 실행 후 최적의 모델 생성 y = wx + b를 만들어줌, tensorFlow가 w, b를 만들어준다.
// 학습된 모델의 가중치(w)와 편향(b) 확인
const weights = model.getWeights();
const w = weights[0].dataSync();
const b = weights[1].dataSync();
console.log(`학습된 파라미터 : w=${w}, b=${b}`); // w=0.7995800971984863, b=0.11707036942243576
// y = 0.7995800971984863 * newx + 0.11707036942243576
const newx = 13.5435
console.log('미지의 새로운 x에 대한 예측 결과 : ', 0.7995800971984863 * newx + 0.11707036942243576);
// 미지의 새로운 x에 대한 예측 결과 : 10.946183415830136
}
학습된 파라미터 w, b 확인
미지의 x (13.5435)를 가정하여 예측 결과 출력
미지의 값을 입력받고 예측값 출력
function predictDisplay() {
const inputValue = parseFloat(document.getElementById('inputValue').value);
if(isNaN(inputValue)){
document.getElementById('showResult').innerText = '숫자 입력!';
return;
}
// 모델을 사용해서 예측
const pred = model.predict(tf.tensor2d([inputValue], [1,1])); // 학습할 때의 모양과 똑같게 넣어줘야한다.
const predValue = pred.dataSync()[0]; // dataSync : 텐서를 js 배열로 변환
// 예측 결과 출력
document.getElementById('showResult').innerText = `입력값 : ${inputValue}, 예측값 : ${predValue.toFixed(2)}`;
}
결정계수 계산 (회기분석모델의 성능 파악)
// 결정계수 계산
function calculateR2() {
// 예측값 계산
const predictedYs = model.predict(xs).dataSync();
// 실제 값과 예측값을 바탕으로 R² 계산
// 결정계수는 회귀 모델이 종속변수를 얼마나 잘 설명하는지를 나타내는 통계적 척도!
const actualYs = ys.dataSync();
// reduce 함수는 배열의 각 요소를 순회하면서 누적 결과를 계산하는 데 사용된다.
// reduce 함수는 배열을 한 개의 값으로 줄이는 데 활용
// actualYs(실제값) 배열의 평균값을 계산
const meanY = actualYs.reduce((sum, val) => sum + val, 0) / actualYs.length;
// 실제값과 평균값 간의 차이를 제곱하여 모두 더한 값으로, 데이터 전체 변동성을 나타낸다.
const ssTotal = actualYs.reduce((sum, val) => sum + Math.pow(val - meanY, 2), 0);
// 잔차 제곱합을 계산
const ssResidual = actualYs.reduce((sum, val, index) =>
sum + Math.pow(val - predictedYs[index], 2), 0);
// 회귀 모델의 설명력 R²를 계산하는 식.
// 모델이 종속변수의 총 변동성을 얼마나 설명하는지를 나타내는 통계적 척도.
const rSquared = 1 - ssResidual / ssTotal;
// R² 결과 표시
document.getElementById("r2").innerText = `rSquared:${rSquared.toFixed(3)}, 모델 설명력 (R²): ${(rSquared * 100).toFixed(1)}%`;
}
값이 매번 달라진다. 독립변수가 종속변수를 설명하는 비율값, 회기분석에서는 설명력이라고 부른다.
25%이상이면 잘나온 것, 높은 설명력은 보통 잘 안나온다.
https://cafe.daum.net/flowlife/RM66/26
https://datalabbit.tistory.com/54
https://aliencoder.tistory.com/40
차트
// 차트 관련 ---------------------
function getData() {
// xs와 ys라는 두 개의 텐서에서 데이터를 가져와서 이를 가공한 후,
// 차트에 사용하기 위한 데이터 형식으로 변환하는 역할
// dataSync()는 텐서의 모든 값을 동기적으로 가져와 자바스크립트 배열로 변환
const dataX = xs.dataSync();
const dataY = ys.dataSync();
// dataX 배열을 기반으로 새로운 배열을 만든다.
// 이 배열은 map() 메서드를 통해 각 요소가 변환
// map() 메서드는 dataX 배열의 각 요소에 대해 함수를 호출하고, 그 결과를 새 배열로 반환한다.
// 여기서 각 요소는 객체 { index: value, value: dataY[index] }로 변환된다.
return Array.from(dataX).map((value, index) => {
return { index: value, value: dataY[index] };
});
}
function chart() {
const data = getData(); // 학습 데이터를 가져옴
// HTML에 고정된 차트를 렌더링할 컨테이너 선택
const container = document.getElementById('chart-container');
// 산점도 데이터 준비
const scatterData = data.map(point => ({
x: point.index, // x축 (독립변수)
y: point.value // y축 (종속변수)
}));
// 추세선 데이터 계산
const trendlineData = calcTrendline(data);
// 산점도와 추세선 함께 그리기
tfvis.render.scatterplot(
container, // 기존 visor가 아닌 고정된 div에 렌더링
{
values: [scatterData, trendlineData],
series: ['Data', 'Trendline'] // 산점도와 추세선 이름 지정
},
{
xLabel: '독립변수(X)',
yLabel: '종속변수(Y)',
height: 300,
width: 500,
seriesColors: ['blue', 'red'], // 데이터 점: 파란색, 추세선: 빨간색
lineSeries: ['Trendline'], // 추세선을 직선으로 표시
style: { lineWidth: 1 }
}
);
}
function calcTrendline(data) {
// 데이터의 X와 Y 값을 분리
const xValues = data.map(point => point.index);
const yValues = data.map(point => point.value);
// 평균 계산
const meanX = xValues.reduce((sum, x) => sum + x, 0) / xValues.length;
const meanY = yValues.reduce((sum, y) => sum + y, 0) / yValues.length;
// 선형 회귀 계수 계산 (y = mx + b에서 m)
const bunja = xValues.reduce((sum, x, i) => sum + (x - meanX) * (yValues[i] - meanY), 0);
const bunmo = xValues.reduce((sum, x) => sum + Math.pow(x - meanX, 2), 0);
const slope = bunja / bunmo; // 기울기 m
const intercept = meanY - slope * meanX; // 절편 계산 (b)
// 추세선 데이터를 X 범위에서 여러 점으로 생성
// Math.min(...xValues) : 배열 xValues에서 가장 작은 값을 찾아 minX에 저장.
// Math.min은 숫자 중 최소값 반환하는 함수며,
// ... 연산자로 배열을 개별 요소로 펼쳐 Math.min에 전달한다.
const minX = Math.min(...xValues);
const maxX = Math.max(...xValues);
const step = (maxX - minX) / 100; // 100개의 점 생성
const linePoints = [];
for (let x = minX; x <= maxX; x += step) {
linePoints.push({ x, y: slope * x + intercept });
}
return linePoints;
}
단순 선형 회기분석 실습 (보스턴 데이터)
통계 연습 데이터
BostonHousing.csv 파일 : https://raw.githubusercontent.com/selva86/datasets/master/BostonHousing.csv
https://cafe.daum.net/flowlife/RM66/23
tf3boston.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#result-container{
display: none;
}
#scatter-plot {
position: absolute;
right: 10px;
top: 10px;
width: 40%;
height: 400px;
padding: 10px;
}
</style>
</head>
<body>
<h2>보스턴 지역 평균 집값 : 단순선형회수(x:방 갯수, rm, y:medv)</h2>
<button id="showButton">분석 결과 차트 보기</button> 예측결과가 나오는데 약간의 시간 필요
<div id="result-container">
<div id="input-container">
방 갯수 입력 :
<input type="number" id="roomsInput" min="1" />
<button id="predictButton">예측 가격 확인</button>
</div>
<div id="single-predict-container">
<h3>궁금한 방 갯수에 대한 집 예측 가격 : </h3>
<p id="singlePrediction"></p>
</div>
<div id="predictions-container">
<h3>실제값에 방 갯수에 따른 집 예측 가격 : </h3>
<p id="predictions-list"></p>
</div>
<div id="chart-container">
<div id="scatter-plot"></div>
</div>
</div>
<script type="module">
import {runAnalysis, predictPrice} from './tf3.mjs';
// 전역 객체
window.runAnalysis = runAnalysis;
window.predictPrice = predictPrice;
</script>
</body>
</html>
tf3.mjs
데이터 읽기
async function fetchHousingData(){
console.log("데이터 로드 중");
try{
// csv 파일 읽기
const response = await fetch('https://raw.githubusercontent.com/selva86/datasets/master/BostonHousing.csv');
if(!response.ok){
throw new Error(`데이터 읽기 실패 : ${response.statusText}`);
}
const data = await response.text();
//console.log(data);
const rows = data.split('\n').slice(1).filter(row => row.length > 0);
//console.log(rows);
const parseData = rows.map(row => {
const cols = row.split(",");
return {
rm:parseFloat(cols[5]), // rm : 방갯수(독립변수 x)
medv:parseFloat(cols[13]) // medv : 집값(종속변수 y)
};
});
//console.log(parseData);
return parseData;
}catch(error){
console.log(error);
return [];
}
}
데이터 읽기 확인
필요한 데이터만 추출
상관계수
// 상관계수
function calcCorrelation(x, y){
const correlation = ss.sampleCorrelation(x, y);
console.log(`피어슨 상관계수 : ${correlation.toFixed(3)}`);
}
export async function runAnalysis(){
console.log("분석 시작");
const data = await fetchHousingData();
if(data.length === 0){
console.error('데이터가 없어!');
return;
}
const dataX = data.map(d => d.rm); // rm 열 추출
const dataY = data.map(d => d.medv); // medv 열 추출
//console.log(dataX);
calcCorrelation(dataX, dataY); // 상관관계 확인
}
상관관계 확인, 0.695 매우높다!
예측값
차트
function createScatterPlot(dataX, dataY, predictions){
// 실제 데이터 (X, Y) 값을 객체 배열로 변환
const actualValues = dataX.map(( x, i ) => ( { x: x, y: dataY[i] } ) );
// 예측 데이터 (X, 예측 값) 값을 객체 배열로 변환
const predictValues = dataX.map(( x, i ) => ( { x: x, y: predictions[i] } ) );
// 'scatter-plot' div 선택
const container = document.getElementById('scatter-plot');
// TensorFlow.js의 시각화 라이브러리를 사용하여 산포도 그리기
tfvis.render.scatterplot(container, {values: [actualValues, predictValues]},
{
xLabel: '숙소당 방 수 (RM)',
yLabel: '주택의 중간 값 (MEDV)',
height: 300, // 그래프 높이 설정
series: ['Actual', 'Predicted'] // 데이터 시리즈 레이블 설정 (실제값, 예측값)
});
}
학습 결과가 좋지않으면 횟수를 늘려줌 epochs:1000으로 늘려줌, 그다지 변하지는 않았다.
실제값, 예측값 출력
function displayPredictions(actualYvalues, predictions){
const predictList = document.getElementById('predictions-list');
predictList.innerHTML = '';
predictions.forEach((pred, index) => {
const listitem = document.createElement('li');
listitem.textContent = `실제값 : ${actualYvalues[index].toFixed(2)}, 예측값 : ${pred.toFixed(2)}`;
predictList.appendChild(listitem);
})
}
궁금한 방 갯수에 대한 집 예측 가격
export async function predictPrice(){
// 입력된 미지의 궁금한 방 갯수의 집값 예측
const roomsInput = document.getElementById('roomsInput').value;
if(roomsInput && model) {
const inputTensor = tf.tensor2d([parseFloat(roomsInput)], [1, 1]);
// 학습 모델을 이용해서 가격 예측 수행
let prediction = model.predict(inputTensor).dataSync()[0];
if(prediction < 0) {
prediction = 0; // 예측된 가격이 음수일 경우 실제값의 최소값을 입력. 0을 준다.
}
// 예측 결과를 화면에 출력
document.getElementById('singlePrediction').textContent = `예측 집 가격 : ${prediction.toFixed(2)}`;
} else {
document.getElementById('roomsInput').textContent = '방 갯수 입력해라!';
}
}
느낀점
✨ 수업 느낀점
단순 선형 회기분석 모델을 통한 미지의 값에 대한 예측을 해보았다!
이러한 데이터 분석을 우리의 프로젝트에 적용할 수 있을까? 추가로 공부를 더 해보아야겠다!
'Study > Acorn' 카테고리의 다른 글
241216 리눅스 (기본 명령어, Java, MariaDB 설치) (0) | 2024.12.16 |
---|---|
241213 데이터 분석 (다중 선형 회기, 분류 - 아이리스, 가상화) (1) | 2024.12.13 |
241211 노드 (RESTful DB연동, 데이터 분석 : TensorFlow.js) (0) | 2024.12.11 |
241210 노드 (RESTful DB 연동) (0) | 2024.12.10 |
241209 노드 (RESTful) (0) | 2024.12.09 |