2017년 10월 27일 금요일

[Build System] Windows 플랫폼에서 ARM Linux 크로스 컴파일 환경 Standalone으로 구성

IDE 등으로 배포하기 위해 Windows 플랫폼에서 ARM 크로스 컴파일 환경을 Standalone으로 구성하려고 한다.

준비물 : mingw32 arm linux toolchain, cmake, mingw32-make

우선 작업을 위한 폴더를 만든다. 나는 D: 바로 아래에 arm이라는 폴더를 만들었다.
그 폴더 안에 다음 링크를 통해 받은 파일을 저장한다.

mingw32 arm liunux 툴체인 : https://releases.linaro.org/components/toolchain/binaries/latest-7/arm-linux-gnueabihf/
이 사이트에서 "gcc-linaro-[버전]-[날짜]-i686-mingw32_arm-linux-gnueabihf.tar.xz" 파일을 다운받는다.
(다른 버전은 여기 참조)

cmake : https://cmake.org/files/v3.10/cmake-3.10.0-rc3-win64-x64.zip
(역시 다른 버전을 원하면 여기 참조)

Standalone으로 구성하기 위해 cmake는 zip 파일로 내려받는다.

위 파일들을 받고 압축을 해제한다.

mingw32-make는 정식 경로에서 Standalone으로 받는 방법은 찾지 못했다.
대신에 다음 사이트에서 mingw-get을 받아 설치한 후 mingw32-make.exe를 가져오는 방법을 사용하였다.
https://sourceforge.net/projects/mingw/

설치 후 mingw-get GUI에서 다음과 같이 mingw32-make를 선택해서 설치하거나


명령 프롬프트에서 설치할 수 있다.

> C:\MinGW\bin\mingw-get.exe install mingw32-make

어떤 방법으로든 설치하고 나면 MinGW 설치 폴더 아래 bin 폴더에 다음과 같은 파일이 생길 것이다.


이 파일들 중 mingw-get.exe를 제외한 5개 파일을 작업 폴더에 mingw32-make라는 폴더를 만들고 그 아래에 복사한다.

준비물을 모두 챙기고 나면 작업 폴더는 다음과 같이 될 것이다.


이제 지난 글에서처럼 툴체인을 명시한 toolchain.arm.cmake 파일을 작성한다.

toolchain.arm.cmake
SET(CMAKE_SYSTEM_NAME Linux)
SET(CMAKE_SYSTEM_PROCESSOR arm)

SET(COMPILER_ROOT "D:/arm/gcc-linaro-7.1.1-2017.08-i686-mingw32_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-")

SET(CMAKE_C_COMPILER ${COMPILER_ROOT}gcc.exe)
SET(CMAKE_CXX_COMPILER ${COMPILER_ROOT}g++.exe)
SET(CMAKE_LINKER ${COMPILER_ROOT}ld.exe)
SET(CMAKE_NM ${COMPILER_ROOT}nm.exe)
SET(CMAKE_OBJCOPY ${COMPILER_ROOT}objcopy.exe)
SET(CMAKE_OBJDUMP ${COMPILER_ROOT}objdump.exe)
SET(CMAKE_RANLIB ${COMPILER_ROOT}ranlib.exe)

경로는 각자 환경에 맞게 수정하면 된다. 단, 절대경로를 사용해야 한다.
파일 구분자를 백슬레시(\)로 입력하면 바보같은 cmake가 자꾸 escape character로 처리하고 못알아먹겠다고 징징거리니 슬레시(/)로 입력하도록 하자.

이제 역시 또 간단한 예제 소스코드를 작성한다.

main.c
#include <stdio.h>

int main()
{
    printf("hello arm!!\n");
    return 0;
}

CMakeLists.txt
ADD_EXECUTABLE(App main.c)

이제 준비는 모두 끝났으니 빌드하면 된다.

매번 명령어를 입력하기는 귀찮으니 배치 파일을 만들자

build.bat
mkdir build
cd build

"../cmake-3.10.0-rc3-win64-x64/bin/cmake.exe" -DCMAKE_MAKE_PROGRAM="D:/arm/mingw32-make/mingw32-make.exe" -DCMAKE_TOOLCHAIN_FILE=../toolchain.arm.cmake -G "MinGW Makefiles" ..

"../cmake-3.10.0-rc3-win64-x64/bin/cmake.exe"  --build .

@echo off
set /p str=completed

준비물들이랑 빌드 결과물이 섞이면 곤란하니 build 폴더를 생성하고 그 아래에 빌드 결과물이 생성되도록 하였다.

세 번째 명령어가 매우 긴데, 다음과 같은 일을 한다.

-DCMAKE_MAKE_PROGRAM : mingw32-make.exe의 경로를 지정한다.(절대경로) 각자 환경에 맞게 수정하자.
mingw-get을 설치하고 나서는 이 옵션이 없어도 cmake가 알아서 mingw32-make의 경로를 찾아내는데, mingw-get 없이 Standalone으로 실행하려면 이 옵션이 반드시 필요하다.

-DCMAKE_TOOLCHAIN_FILE : 툴체인을 명시한 toolchain.arm.cmake 파일의 경로를 지정한다. 역시 각자 환경에 맞게 수정하자.

-G "MinGW Makefiles" : mingw32-make.exe가 이해할 수 있는 Makefile을 생성한다.

build.bat 파일을 실행하고 나면 build 폴더 안에 App이라는 파일이 생성되었을 것이다.

이것을 ARM Linux 플랫폼에 옮기고 실행하면!

2017년 10월 26일 목요일

[Build System] cmake에서 크로스 컴파일러(툴체인) 사용하기 (x86_64 Linux에서 ARM Linux 실행 파일 빌드)

지난 번에는 cmake를 사용하여 서로 다른 플랫폼에서 빌드하는 방법을 알아보았는데, 이번엔 한 플랫폼에서 다른 플랫폼 실행 파일을 크로스 컴파일하는 방법을 알아볼 것이다.

예제로, 데스크탑 PC(x86_64 Ubuntu 16.04)에서 ARM Linux 실행 파일을 크로스 컴파일해 볼 것이다.

먼저 툴체인을 받아와서 압축을 해제한다.

# wget https://releases.linaro.org/components/toolchain/binaries/latest/arm-linux-gnueabihf/gcc-linaro-7.1.1-2017.08-x86_64_arm-linux-gnueabihf.tar.xz
# tar xf gcc-linaro-7.1.1-2017.08-x86_64_arm-linux-gnueabihf.tar.xz

원하는 버전의 다른 ARM 툴체인은 여기서 찾아볼 수 있다.

이제 toolchain.arm.cmake 라는 파일을 만들어서 내용을 다음과 같이 채워 넣는다.

toolchain.arm.cmake
SET(CMAKE_SYSTEM_NAME Linux)
SET(CMAKE_SYSTEM_PROCESSOR arm)

SET(COMPILER_ROOT /root/cmake/gcc-linaro-7.1.1-2017.08-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-)

SET(CMAKE_C_COMPILER ${COMPILER_ROOT}gcc)
SET(CMAKE_CXX_COMPILER ${COMPILER_ROOT}g++)
SET(CMAKE_LINKER ${COMPILER_ROOT}ld)
SET(CMAKE_NM ${COMPILER_ROOT}nm)
SET(CMAKE_OBJCOPY ${COMPILER_ROOT}objcopy)
SET(CMAKE_OBJDUMP ${COMPILER_ROOT}objdump)
SET(CMAKE_RANLIB ${COMPILER_ROOT}ranlib)

이 파일은 툴체인의 경로(절대경로)를 명시하는 역할을 한다. 나중에 cmake를 실행할 때 이 파일을 입력해서 빌드시 사용할 툴체인을 지정해줄 수 있다.
첫 번째 줄의 COMPILER_ROOT 변수는 각자 환경에 맞게 수정하면 된다. 여기서는 앞에서 받아온 ARM 툴체인의 실행파일(gcc, ld 등)들의 경로를 지정하였다.

예제로 빌드할 소스코드(main.c)와 cmake 파일(CMakeLists.txt)를 작성하자.

main.c
#include <stdio.h>

int main()
{
    printf("hello arm!\n");
    return 0;
}

CMakeLists.txt
ADD_EXECUTABLE(App main.c)

이제 다음 명령어를 입력하면 빌드된다!

# cmake -DCMAKE_TOOLCHAIN_FILE=toolchain.arm.cmake .
# make

App 이라는 실행파일이 생성되었을 것이다. file 명령어를 통해 확인해 보면 잘 컴파일 된 것을 확인할 수 있다.


이 파일을 ARM 플랫폼으로 (여기서는 raspberry pi에) 전송한 후에 실행한 화면이다.

2017년 10월 25일 수요일

[VR] Unity : Bluetooth Controller를 사용하여 캐릭터 컨트롤하기

이번엔 지난 글저번 글의 내용을 합쳐서 Bluetooth Controller를 통해 1인칭 캐릭터를 컨트롤할 수 있는 VR앱을 만들 것이다.

배경까지 다 만들기는 귀찮으니까 Assets Store에서 괜찮은 꽁짜 프로젝트를 받아온다.
- 2018년 11월 현재 위 프로젝트는 서비스되지 않아서 받아올 수 없다. 다른 프로젝트를 사용해도 무방하니 적당한 것을 찾아보자


필요없는 부분은 다 지우고 먼저 1인칭 캐릭터 역할을 할 GameObject를 적당한 위치에 적당한 크기로 하나 만든다.



벽을(심지어 바닥도!) 뚫고 다니면 안 되니까 Capsule Collider를 추가하고 물리엔진과 상호작용(중력이라던가 다른 물체와 충돌이라던가)을 할 수 있도록 Rigidbody를 추가한다.
중요한게 Rigidbody에서 Constraints안에 Freeze Rotation의 모든 축을 체크해야 한다. 물리엔진에 의해 캐릭터가 회전하지 않도록 제약을 걸어 두는 것으로, 이거 안 해주면 Capsule Collider의 곡면 때문에 캐릭터가 자기 맘대로 막 굴러다닌다.

그 다음엔 캐릭터 GameObject의 하위 Object로 포함되도록 Camera를 추가한다.



위치는 적당히 잡아주면 된다.
단, 비대칭을 좋아하는 사람이 아니면 X와 Z 좌표는 0으로 해 두자.

이제 스크립트를 하나 추가한다. 이거 참고해서 작성했다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent (typeof (Rigidbody))]
[RequireComponent (typeof (CapsuleCollider))]

public class BlueControlManager : MonoBehaviour
{
  private GameObject cameraObject;
  private Rigidbody CharacterRigidbody;

  private bool jumpPushed = false;

  // contants
  private float speed = 2.2f;
  private float gravity = 10.0f;
  private bool grounded = false;

  private float maxVelocityChange = 1.5f;

  private float jumpHeight = 0.5f;
  private Vector3 jumpVelocity;

  void Start()
  {
  cameraObject = GameObject.Find("Camera");
  CharacterRigidbody = GetComponent<Rigidbody>();

  // From the jump height and gravity we deduce the upwards speed 
  // for the character to reach at the apex.
  jumpVelocity = new Vector3(0, Mathf.Sqrt(2 * jumpHeight * gravity), 0);
  }

  void Update()
  {
  if(Input.GetKeyDown(KeyCode.JoystickButton0))
  jumpPushed = true;
  }

  void FixedUpdate()
  {
  if(grounded)
  {
  // get joystick data
  Vector2 joystickPosition = new Vector2();
  joystickPosition.x = Input.GetAxis("Horizontal");
  joystickPosition.y = Input.GetAxis("Vertical");
  joystickPosition = joystickPosition.normalized;

  // Calculate how fast we should be moving
  Vector3 targetVelocity = new Vector3(0, 0, 0);
  Vector3 forward = new Vector3(0, 0, 0);
  Vector3 right = new Vector3(0, 0, 0);

  forward.x = cameraObject.transform.forward.x;
  forward.z = cameraObject.transform.forward.z;
  right.x = cameraObject.transform.right.x;
  right.z = cameraObject.transform.right.z;
  forward = forward.normalized;
  right = right.normalized;
  targetVelocity = forward * joystickPosition.y + right * joystickPosition.x;
  targetVelocity *= speed;

  // Apply a force that attempts to reach our target velocity
  Vector3 velocity = CharacterRigidbody.velocity;
  Vector3 velocityChange = (targetVelocity - velocity);
  velocityChange.x = Mathf.Clamp(velocityChange.x,
                -maxVelocityChange, maxVelocityChange);
  velocityChange.z = Mathf.Clamp(velocityChange.z,
                -maxVelocityChange, maxVelocityChange);
  velocityChange.y = 0;
  CharacterRigidbody.AddForce(velocityChange, ForceMode.VelocityChange);

  // Jump
  if(jumpPushed)
  CharacterRigidbody.AddForce(jumpVelocity, ForceMode.VelocityChange);
  }

  grounded = false;
  jumpPushed = false;
  }

  void OnCollisionStay(Collision collisionInfo)
  {
  foreach(ContactPoint contact in collisionInfo.contacts)
  {
  if(contact.normal.y > 0.7f)
  {
  grounded = true;
  break;
  }
  }
  }
}

주요 특징

1. UI 처리는 Update() 함수에서, 물리적 처리는 FixedUpdate() 함수에서 한다.
Update() 함수는 프레임마다 한 번 호출되며, 초당 프레임의 변화에 따라 호출 주기가 달라지는 반면 FixedUpdate() 함수는 호출 주기가 일정하다. Input.GetKeyDown() 함수는 버튼이 눌린 시점의 프레임에서만 true를 반환한다. 따라서 FixedUpdate() 함수에서 Input.GetKeyDown() 함수로 값을 읽어오는 경우 프레임 타이밍에 따라 버튼을 눌러도 true값을 읽지 못할 수도 있다. 반면에 물리적 처리는 호출 주기가 일정한 FixedUpdate() 함수에서 처리해야 자연스럽게 동작하며 그렇지 않고 Update() 함수에서 처리할 경우 초당 프레임 변화에 따라 다르게 동작할 것이다.

2. 캐릭터를 움직일 때는 rigidbody의 AddForce() 함수 사용
캐릭터를 움직이는 방법, 즉 캐릭터의 위치를 바꾸는 방법은 위치 자체를 바꾸는 방법, 속도를 바꾸는 방법, 힘(가속도)를 주는 방법의 세 가지가 있다. 위치 자체를 바꾸는 것은 캐릭터가 순간이동하는 것으로 구현될 것이며, 속도를 바꾸거나 힘을 주어야 위치가 연속적으로 변할 것이다. 힘을 주는 방법이 위치와 속도가 연속적으로 변하는 것이므로 가장 자연스럽게 동작할 것이다.

3.  grounded (캐릭터가 땅을 밟고 서 있는지) 판단 조건
캐릭터의 Collider가 다른 Collider와 접촉할 때 호출되는 OnCollisionStay() 함수에서 항상 grounded를 true로 바꾸도록 하면 벽과 접촉할 때에도 땅을 밟고 있다고 판단하여 벽타기가 가능하다.. 따라서 OnCollisionStay() 함수의 인자로 주어지는 Collision 정보 내에서 다른 Collider와 접촉한 방향이 옆쪽이 아닌 아래쪽 방향이 있는 경우에만 땅을 밟고 있다고 판단하게 한다.



플레이 영상!

2017년 10월 24일 화요일

[VR] Unity VR Support

VR 앱을 개발하기 위한 기본적인 것들..
화면을 반으로 나누고, 각 화면의 시점(카메라)을 다르게 해서 3D로 보이도록 만들며, 기기의 센서 정보를 이용해 그에 맞게 시점을 회전하는 head tracking은 VR앱이라면 기본적으로 갖춰야 하는 기능이다.

예전에 처음 Unity를 사용해 VR 앱을 개발할 때에는 센서 값을 읽어오는 플러그인을 넣고, 카메라를 두 개 만든 다음....... 센서 값에 따라 카메라를 회전하는걸 직접 구현했었는데,, 무려 지금은 Unity에서 자동으로 해 준다.
심지어 카드보드, 데이드림, 오큘러스 등등의 VR 플랫폼 지원까지 해준다.
관련 내용은 문서에 잘 나와 있다.

언리얼 엔진도 무료로 풀렸으니 나중엔 이거도 사용해 봐야겠다.
엔진 개발자님들 만쉐이




이번 글은 Unity에서 다 해주므로.. 분량이 없다. Edit -> Project Settings -> Player에 들어가서 원하는 플랫폼에 대해 Virtual Reality Supported에 체크한방 날리고 카드보드인지 뭔지 사용할 VR 플랫폼을 선택하면 끝이다.



알아서 화면도 나눠주고 렌즈를 통해 보기 편하도록 화면 왜곡도 해 주며 head tracking도 된다!


카드보드를 선택하면 안드로이드 4.4 Kit Kat (API level 19) 부터, 데이드림을 선택하면 안드로이드 7.0 Nougat (API level 24) 부터 앱을 실행할 수 있다.

음.. 사실 데이드림은 Daydream Ready로 인증받은 기기에서만 실행이 가능하다. 자세한 내용은 여기 참조

지금은 그냥 조용히 카드보드를 선택하자.

2017년 10월 18일 수요일

[VR] Unity에서 Bluetooth Controller 제어

VR 앱을 개발하기 위해 아이페가 PG-9068 TOMAHAWK 모델의 블루투스 컨트롤러를 구입하였다.


집에 도착한 모습!



Xbox 컨트롤러와 유사한 구조를 갖고 있다.

Unity에서 컨트롤러의 입력값을 읽어오기 위해서는 각 버튼이 어떻게 매핑되어 있는지 알아야 한다.
직접 테스트해본 결과 다음 그림과 같이 매핑되어 있었다.


OS와 컨트롤러에 따라 매핑 정보는 달라질 수 있다. 따라서 출시되는 VR 앱에는 이를 잘 파악하여 동적으로 매핑 정보를 파악할 수 있도록 해야 한다.

그림에서 "JoystickButton*"로 매핑되는 버튼은 Digital 값이고, "*th axis"로 매핑되는 조이스틱이나 버튼은 Analog 값이다. Digital인가 Analog인가에 따라 Unity에서 값을 읽어오는 방식이 다르다.
(LT, RT 버튼은 Digital과 Analog 방식 모두 동작한다.)

먼저 Digital 값은 스크립트에서 다음과 같이 쉽게 읽어올 수 있다.

bool key_value = Input.GetKey(KeyCode.JoystickButton0);

Unity reference를 확인해 보면 KeyCode의 다양한 값을 볼 수 있다. 최대 8개까지의 조이스틱(컨트롤러)를 구분할 수 있으며 각 컨트롤러마다 최대 20개의 버튼을 구분할 수 있다.
이 값을 Input.GetKey() 함수의 인자로 주면 버튼 값을 True나 False로 돌려 준다.
물론 Input.GetKeyDown() 이나 Input.GetKeyUp() 등의 다른 함수도 사용할 수 있다.

Analog 값은 조금 복잡한데, 우선 스크립트에서 읽는 방법은 다음과 같다.
-1.0 ~ 1.0 범위의 값을 얻을 수 있다.

float key_value = Input.GetAxis("Horizontal");

Input.GetAxis() 함수를 사용하는데, 인자로 주는 string은 읽으려는 axis의 이름이다. Unity는 기본적으로 컨트롤러의 첫 번째 조이스틱에 "Horizontal", "Vertical" axis가 매핑되어 있다. 이러한 정보는 Edit -> Project Settings -> Input에서 설정 가능하다.


설정 창을 열게 되면 Inspector에 다음과 같이 InputManager가 보일 것이다.


다양한 Axis 이름들이 정의되어 있는데, 그 중에서 "Horizontal", "Vertical" axis가 미리 정의되어 있는 것을 볼 수 있다. 또 다른 Analog 입력을 추가하고 싶다면 InputManager의 가장 위에 있는 속성인 Size 값을 늘린 후에 추가된 항목에 값을 채워넣으면 된다.

여기서는 다음과 같이 오른쪽 조이스틱을 입력으로 추가했다.


각 속성에 대한 상세한 내용은 여기 참조

가장 위에 있는 Name 속성에 입력하는 string을 스크립트에서 Input.GetAxis() 등의 함수에 인자로 사용하여 해당 Analog 입력 값을 받아올 수 있다.
Axis 속성에는 앞서 언급했던, 버튼과 조이스틱이 매핑된 axis를 선택하면 된다. 오른쪽 조이스틱은 X 축이 3rd axis에, Y 축이 4th axis에 매핑되어 있다고 했으므로 그에 맞게 설정하였다.

2017년 10월 17일 화요일

[Machine Learning] mldivide ('\')를 활용한 linear regression

1. 정의


행렬 왼쪽 나눗셈 (mldivide, '\')는 행렬 A, X, Y로 이루어진 다음 시스템에 대해
A * X = Y
다음과 같이 정의된다.
A \ Y = X 
A * (A \ Y) = Y

A가 역행렬이 존재하는 정사각 행렬일 경우 다음과 같이 계산할 수 있다.
A \ Y = inv(A) * Y = X

그러나 mldivide는 A의 역행렬이 존재하지 않거나 심지어 정사각 행렬이 아닐 경우에도 정의되는데, 이를 계산하기 위해 다음 링크에 있는 MATLAB 문서는 다음과 같이 그 알고리즘을 설명하고 있다.
https://kr.mathworks.com/help/matlab/ref/mldivide.html#bt4jslc-6
복잡한 알고리즘은 모두 A가 정사각 행렬인 경우에 대한 내용이고, 정사각 행렬이 아닌 경우 QR solver로 해결하도록 되어 있다. MATLAB에는 mldivide 연산에 위와 같은 알고리즘이 구현되어 있어서, 이를 통해 정사각 행렬이 아닌 경우에도 연산이 가능하며, 나아가 linear regression도 mldivide로 계산할 수 있다.


2. 의미



위 그림과 같이 행렬 A의 크기를 n * m, Y의 크기를 n * l로 정의할 경우 n, m, l은 각각 다음을 의미한다.

  • n : 시스템을 이루는 식의 수
  • m : 시스템을 이루는 미지수의 수 (차원)
  • l : 시스템의 개수 (각 시스템은 동일한 계수를 갖지만, 미지수와 상수는 다름)

예를 들어 다음과 같은 시스템에 대해


행렬로 다음과 같이 표현할 수 있고, mldivide로 해를 구할 수 있다.


n과 m이 동일하면서 A의 rank가 m과 같은 경우, 즉 역행렬이 존재하는 경우는 위와 같이 풀이가 가능하다.

그러나 정사각 행렬이 아닌 경우 n과 m의 관계에 따라 mldivide는 다음과 같은 의미를 갖는다.

  • n(또는 A의 rank)이 m보다 작은 경우 : 무수히 많은 해
  • n이 m보다 큰 경우 : Least square를 만족하는 Linear regression

n이 m보다 작은 경우 mldivide는 다음과 같이 m - n(또는 A의 rank)개에 해당하는 미지수를 0으로 설정한 후 나머지 미지수에 대한 값을 계산한다.



3. Linear Regression


n이 m보다 큰 경우 선형 시스템 상에서는 해가 존재하지 않게 된다. 대신에 mldivide는 Least square를 만족하는 Linear regression으로 동작한다. 따라서 다음 식과 같이 행렬곱이 mldivide의 역연산이 될 수 없다.
A * (A \ Y) ≠ Y
Linear regression의 경우 n, m의 의미는 다음과 같이 재 정의될 수 있다. l은 동일한 데이터, 속성에 대한 단순 반복이므로 중요하지 않다.

  • n : 데이터의 수
  • m : 속성의 수

이에 따라 행렬 A는 기존의 계수 행렬(Coefficient Matrix)에서 각 속성들의 관계를 나타내는 데이터들의 집합, 즉 Training data의 집합으로 재해석할 수 있고, Y 행렬도 동일하게 Training data 집합의 속성을 갖는다. Linear regression의 결과인 X 행렬은 각 속성들의 가중치를 나타낸다.

가장 간단하게, n개의 두 속성 x, y를 갖는 데이터를 이용하여 다음과 같은 선형 모델로 Regression할 때


행렬 A, Y와 X는 다음과 같이 정의된다.


행렬 A는 상수항을 의미하는 1로 이루어진 열과 각각의 속성에 해당하는 데이터로 이루어진 열들로 구성되며, Y는 남은 한 속성에 해당하는 데이터로 구성된다.
행렬 X는 상수항과 속성의 계수로 구성되며, 선형 모델을 나타낸다.

예를 들어서 키와 몸무게 사이의 관계에 관한 Linear regression 문제를 MATLAB을 사용하여 푸는 방법은 다음과 같다.


최종적으로 다음과 같은 선형 모델을 얻게 된다.



4. 계산


n > m인 행렬 A에 대해 일반적인 경우에는 다음과 같이 계산이 가능하다.
X = A \ Y = inv(A' * A) * A' * Y            (A'는 A의 Transpose)
그러나 다음 행렬과 같이 데이터의 스케일 차이가 큰 경우 부동소수점 연산 정밀도의 한계로, 정확한 해를 구할 수 없게 된다.


MATLAB에는 해를 구하기 위해 다음의 여러 가지 방법들을 제공한다.


  • QR Solver :
        [Q, R] = qr(A, 0)
        X = R \ (Q' * Y)
  • X = pinv(A) * Y
  • X = linsolve(A, Y)


앞의 예제를 각각의 방법으로 구한 해와 RMSE는 다음과 같다.

exact
solution
A \ YQR Solverpinv(A) * Ylinsolve(A, Y)inv(A' * A) * A' * Y
b0.0-0.0127-0.0555-0.0313-0.01270.0041
a1.01.00001.00001.00001.00001.0000
RMSE0.00.00780.03490.05900.00780.1243

A \ Y와 linsolve의 성능이 가장 좋은 것을 확인할 수 있다.
반면 상수항에 비해 데이터가 작은 경우에는 QR Solver가 가장 정확했다.
exact
solution
A \ YQR Solverpinv(A) * Ylinsolve(A, Y)inv(A' * A) * A' * Y
b0.0-1.05998e-28-5.5511e-29-8.2740e-29-1.05998e-28-1.4724e-28
a1.01.00001.00001.00001.00001.0000
RMSE0.05.6798e-293.1554e-294.0409e-295.6798e-296.1834e-29

경우에 따라 다른 방법의 성능이 더 좋을 수 있으며, 데이터의 특성에 따라 적절한 방법을 선택해야 한다.