■ 선형 최소 제곱법(Linear Least Squares Method) 사용하기

------------------------------------------------------------------------------------------------------------------------


TestProject.zip


MathHelper.cs

 

 

using System;

using System.Collections.Generic;

using System.Drawing;

 

namespace TestProject

{

    /// <summary>

    /// 수학 헬퍼

    /// </summary>

    public class MathHelper

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Static

        //////////////////////////////////////////////////////////////////////////////// Public

 

        #region 제곱 오차 구하기 - GetErrorSquared(pointList, a, b)

 

        /// <summary>

        /// 제곱 오차 구하기

        /// </summary>

        /// <param name="pointList">포인트 리스트</param>

        /// <param name="a">A</param>

        /// <param name="b">B</param>

        /// <returns>제곱 오차</returns>

        public static double GetErrorSquared(List<PointF> pointList, double a, double b)

        {

            double result = 0;

 

            foreach(PointF point in pointList)

            {

                double error = point.Y - (a * point.X + b);

 

                result += error * error;

            }

 

            return result;

        }

 

        #endregion

        #region 선형 최소 제곱법 해 구하기 - GetLinearLeastSquaresFit(pointList, a, b)

 

        /// <summary>

        /// 선형 최소 제곱법 해 구하기

        /// </summary>

        /// <param name="pointList">포인트 리스트</param>

        /// <param name="a">A</param>

        /// <param name="b">B</param>

        /// <returns>오차</returns>

        public static double GetLinearLeastSquaresFit(List<PointF> pointList, out double a, out double b)

        {

            double count     = pointList.Count;

            double xSummary  = 0;

            double ySummary  = 0;

            double xxSummary = 0;

            double xySummary = 0;

 

            foreach(PointF point in pointList)

            {

                xSummary  += point.X;

                ySummary  += point.Y;

                xxSummary += point.X * point.X;

                xySummary += point.X * point.Y;

            }

 

            a = (xySummary * count - xSummary * ySummary) / (xxSummary * count - xSummary * xSummary);

            b = (xySummary * xSummary - ySummary * xxSummary) / (xSummary * xSummary - count * xxSummary);

 

            return Math.Sqrt(GetErrorSquared(pointList, a, b));

        }

 

        #endregion

    }

}

 

 

MainForm.cs

 

 

using System;

using System.Collections.Generic;

using System.Drawing;

using System.Drawing.Drawing2D;

using System.Windows.Forms;

 

namespace TestProject

{

    /// <summary>

    /// 메인 폼

    /// </summary>

    public partial class MainForm : Form

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field

        ////////////////////////////////////////////////////////////////////////////////////////// Private

 

        #region Field

 

        /// <summary>

        /// X 최소값

        /// </summary>

        private const float X_MINIMUM = -10.0f;

 

        /// <summary>

        /// X 최대값

        /// </summary>

        private const float X_MAXIMUM =  10.0f;

 

        /// <summary>

        /// Y 최소값

        /// </summary>

        private const float Y_MINIMUM = -10.0f;

 

        /// <summary>

        /// Y 최대값

        /// </summary>

        private const float Y_MAXIMUM =  10.0f;

 

        /// <summary>

        /// 그리기 변환

        /// </summary>

        private Matrix drawingTransform;

 

        /// <summary>

        /// 반전 변환

        /// </summary>

        private Matrix inverseTransform;

 

        /// <summary>

        /// 포인트 리스트

        /// </summary>

        private List<PointF> pointList = new List<PointF>();

 

        /// <summary>

        /// 솔루션 여부

        /// </summary>

        private bool hasSolution = false;

 

        /// <summary>

        /// 최적 A

        /// </summary>

        private double bestA;

        

        /// <summary>

        /// 최적 B

        /// </summary>

        private double bestB;

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 생성자 - MainForm()

 

        /// <summary>

        /// 생성자

        /// </summary>

        public MainForm()

        {

            InitializeComponent();

 

            #region 이벤트를 설정한다.

 

            Load                            += Form_Load;

            this.calculateButton.Click      += calculateButton_Click;

            this.clearButton.Click          += clearButton_Click;

            this.graphButton.Click          += graphButton_Click;

            this.graphPictureBox.MouseClick += graphPictureBox_MouseClick;

            this.graphPictureBox.Paint      += graphPictureBox_Paint;

 

            #endregion

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Private

        //////////////////////////////////////////////////////////////////////////////// Event

 

        #region 폼 로드시 처리하기 - Form_Load(sender, e)

 

        /// <summary>

        /// 폼 로드시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void Form_Load(object sender, EventArgs e)

        {

            RectangleF worldRectangle = new RectangleF

            (

                X_MINIMUM,

                Y_MINIMUM,

                X_MAXIMUM - X_MINIMUM,

                Y_MAXIMUM - Y_MINIMUM

            );

 

            PointF[] pointArray =

            {

                new PointF(0, graphPictureBox.ClientSize.Height),

                new PointF(graphPictureBox.ClientSize.Width, graphPictureBox.ClientSize.Height),

                new PointF(0, 0),

            };

 

            this.drawingTransform = new Matrix(worldRectangle, pointArray);

 

            this.inverseTransform = this.drawingTransform.Clone();

 

            this.inverseTransform.Invert();

        }

 

        #endregion

        #region 계산하기 버튼 클릭시 처리하기 - calculateButton_Click(sender, e)

 

        /// <summary>

        /// 계산하기 버튼 클릭시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void calculateButton_Click(object sender, EventArgs e)

        {

            Cursor = Cursors.WaitCursor;

 

            this.aTextBox.Clear();

            this.bTextBox.Clear();

            this.errorTextBox.Clear();

 

            Application.DoEvents();

 

            MathHelper.GetLinearLeastSquaresFit(this.pointList, out this.bestA, out this.bestB);

 

            this.hasSolution = true;

 

            this.aTextBox.Text = this.bestA.ToString();

            this.bTextBox.Text = this.bestB.ToString();

 

            ShowError();

 

            this.hasSolution = true;

 

            this.graphPictureBox.Refresh();

 

            Cursor = Cursors.Default;

        }

 

        #endregion

        #region 지우기 버튼 클릭시 처리하기 - clearButton_Click(sender, e)

 

        /// <summary>

        /// 지우기 버튼 클릭시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void clearButton_Click(object sender, EventArgs e)

        {

            this.pointList = new List<PointF>();

 

            this.hasSolution = false;

 

            this.graphPictureBox.Refresh();

 

            this.aTextBox.Clear();

            this.bTextBox.Clear();

            this.errorTextBox.Clear();

        }

 

        #endregion

        #region 그래프 버튼 클릭시 처리하기 - graphButton_Click(sender, e)

 

        /// <summary>

        /// 그래프 버튼 클릭시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void graphButton_Click(object sender, EventArgs e)

        {

            this.bestA = double.Parse(this.aTextBox.Text);

            this.bestB = double.Parse(this.bTextBox.Text);

 

            ShowError();

 

            this.graphPictureBox.Refresh();

        }

 

        #endregion

        #region 그래프 픽처 박스 마우스 클릭시 처리하기 - graphPictureBox_MouseClick(sender, e)

 

        /// <summary>

        /// 그래프 픽처 박스 마우스 클릭시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void graphPictureBox_MouseClick(object sender, MouseEventArgs e)

        {

            PointF[] pointArray =

            {

                new PointF(e.X, e.Y)

            };

 

            this.inverseTransform.TransformPoints(pointArray);

 

            this.pointList.Add(pointArray[0]);

 

            this.graphPictureBox.Refresh();

        }

 

        #endregion

        #region 그래프 픽처 박스 페인트시 처리하기 - graphPictureBox_Paint(sender, e)

 

        /// <summary>

        /// 그래프 픽처 박스 페인트시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void graphPictureBox_Paint(object sender, PaintEventArgs e)

        {

            e.Graphics.Transform = this.drawingTransform;

 

            DrawAxe(e.Graphics);

 

            if(this.hasSolution)

            {

                using(Pen pen = new Pen(Color.Blue, 0))

                {

                    double y0 = this.bestA * X_MINIMUM + this.bestB;

                    double y1 = this.bestA * X_MAXIMUM + this.bestB;

 

                    e.Graphics.DrawLine

                    (

                        pen,

                        (float)X_MINIMUM,

                        (float)y0,

                        (float)X_MAXIMUM,

                        (float)y1

                    );

                }

            }

 

            float deltaX = (X_MAXIMUM - X_MINIMUM) / 200;

            float deltaY = (Y_MAXIMUM - Y_MINIMUM) / 200;

 

            using(Pen pen = new Pen(Color.Black, 0))

            {

                foreach(PointF point in this.pointList)

                {

                    e.Graphics.FillRectangle

                    (

                        Brushes.White,

                        point.X - deltaX,

                        point.Y - deltaY,

                        2 * deltaX,

                        2 * deltaY

                    );

 

                    e.Graphics.DrawRectangle

                    (

                        pen,

                        point.X - deltaX,

                        point.Y - deltaY,

                        2 * deltaX,

                        2 * deltaY

                    );

                }

            }

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////// Function

 

        #region 축 그리기 - DrawAxe(graphics)

 

        /// <summary>

        /// 축 그리기

        /// </summary>

        /// <param name="graphics">그래픽스</param>

        private void DrawAxe(Graphics graphics)

        {

            using(Pen pen = new Pen(Color.Black, 0))

            {

                const float xThick = 0.2f;

                const float yThick = 0.2f;

 

                graphics.DrawLine(pen, X_MINIMUM, 0, X_MAXIMUM, 0);

 

                for(float x = X_MINIMUM; x <= X_MAXIMUM; x += 1.0f)

                {

                    graphics.DrawLine(pen, x, -yThick, x, yThick);

                }

 

                graphics.DrawLine(pen, 0, Y_MINIMUM, 0, Y_MAXIMUM);

 

                for(float y = Y_MINIMUM; y <= Y_MAXIMUM; y += 1.0f)

                {

                    graphics.DrawLine(pen, -xThick, y, xThick, y);

                }

            }

        }

 

        #endregion

        #region 오차 보여주기 - ShowError()

 

        /// <summary>

        /// 오차 보여주기

        /// </summary>

        private void ShowError()

        {

            double error = Math.Sqrt(MathHelper.GetErrorSquared(this.pointList, this.bestA, this.bestB));

 

            this.errorTextBox.Text = error.ToString();

        }

 

        #endregion

    }

}

 

------------------------------------------------------------------------------------------------------------------------

Posted by 사용자 icodebroker
TAG

댓글을 달아 주세요