파이썬의 matplotlib 노트

파이썬의 matplotlib는 수치 데이터를 그래프로 효과적으로 표시해주는 API입니다. 이에 대해 간단한 활용 예시에 대한 코드를 기록해 둡니다.

import matplotlib.pyplot as plt

x = [1, 2, 3, 4, 5]
y = [1, 2, 3, 4, 5]

plt.scatter(x, y)

plt.show()

X축과 Y축에 대한 포인트 데이터를 표시하는 코드입니다. 결과는 다음과 같습니다.

그래프에서 포인트의 크기와 색상, 투명도를 지정하는 예제는 다음과 같습니다.

import matplotlib.pyplot as plt

x = [1, 2, 3, 4, 5]
y = [1, 2, 3, 4, 5]
s = [10, 20, 30, 40, 50]

plt.scatter(x = x, y = y, s = s, c = 'red', alpha=0.5)

plt.show()

결과는 다음과 같습니다.

다음은 꺽은선 그래프입니다.

import numpy as np
import matplotlib.pyplot as plt

x = np.arange(0, 10, 0.5)
y = np.sin(x)

plt.plot(x, y)
plt.show()

x축과 y축의 데이터는 4번과 5번 코드에서 정의합니다. 결과는 다음과 같습니다.

하나의 차트에 여러개의 그래프를 동시에 표시하고, 추가적으로 제목, 축이름 등을 표시하는 코드입니다.

import numpy as np
import matplotlib.pyplot as plt

x = np.arange(0, 10, 0.5)
y1 = np.sin(x)
y2 = np.cos(x)

plt.plot(x, y1, label="sin(x)")
plt.plot(x, y2, label="cos(x)", linestyle="--")

plt.xlabel("x")
plt.xlabel("y")

plt.title("sin & cos")
plt.legend()

plt.show()

3차원 차트의 경우 먼저 X, Y축에 대한 데이터와 이 X, Y를 변수로 하여 계산된 Z 값의 함수가 정의해야 합니다. 이렇게 정의된 X, Y, Z에 대한 3차원 그래프는 아래의 예제 코드를 통해 3차원 차트로 시각화할 수 있습니다.

import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt

X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)
Z = X**2 + Y**2

fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_wireframe(X, Y, Z, color='black')

plt.show()

여러개의 차트를 동시에 표시하는 경우입니다.

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, np.pi * 3, 100)
fig, axes = plt.subplots(2,2)

axes[0][0].plot(x, np.sin(x))
axes[0][1].plot(x, np.arccos(x))
axes[1][0].plot(x, np.cos(x))
axes[1][1].plot(x, np.arcsin(x))

plt.show()

아래는 차트를 그리는 스타일을 지정하고 범례를 표현하는 코드입니다.

import matplotlib.pyplot as plt
import numpy as np

x = np.arange(-10,10)
y = x**2

plt.plot(x, y, 
    linewidth=2, color='green', linestyle=':', 
    marker='*', markersize=10, markerfacecolor='yellow', markeredgecolor='red', 
    label='y=x^2')
   
plt.legend()

plt.show()

결과는 다음과 같습니다.

[PyQt5] 타이머(Timer) 사용하기

PyQt에서 제공하는 타이머를 사용해 간단한 디지털 시계에 대한 UI를 구성해 보겠습니다. 디지털 시계에 대한 표현은 QLCDNumber라는 위젯을 사용합니다. 아래는 최종 실행 결과입니다.

시작 버튼을 클릭하면, 일정한 주기로 시간 표시가 변경되며 멈춤 버튼을 클릭하면 시간 표시에 대한 갱신을 중단합니다. 멈춤 상태에서 다시 시작 버튼을 클릭하면 시간 표시가 변경되기 시작합니다. 코드는 다음과 같습니다.

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QTimer, QTime

class MyWindow(QWidget):
    def __init__(self):
        super().__init__()

        self.timer = QTimer(self)
        self.timer.setInterval(1000)
        self.timer.timeout.connect(self.timeout)

        self.setWindowTitle('QTimer')
        self.setGeometry(100, 100, 600, 280)
 
        layout = QVBoxLayout()
 
        self.lcd = QLCDNumber()
        self.lcd.display('')
        self.lcd.setDigitCount(8)

        subLayout = QHBoxLayout()
        
        self.btnStart = QPushButton("시작")
        self.btnStart.clicked.connect(self.onStartButtonClicked)
 
        self.btnStop = QPushButton("멈춤")
        self.btnStop.clicked.connect(self.onStopButtonClicked)
 
        layout.addWidget(self.lcd)
        
        subLayout.addWidget(self.btnStart)
        subLayout.addWidget(self.btnStop)
        layout.addLayout(subLayout)
 
        self.btnStop.setEnabled(False)

        self.setLayout(layout)        

    def onStartButtonClicked(self):
        self.timer.start()
        self.btnStop.setEnabled(True)
        self.btnStart.setEnabled(False)

    def onStopButtonClicked(self):
        self.timer.stop()
        self.btnStop.setEnabled(False)
        self.btnStart.setEnabled(True)

    def timeout(self):
        sender = self.sender()
        currentTime = QTime.currentTime().toString("hh:mm:ss")

        if id(sender) == id(self.timer):
            self.lcd.display(currentTime)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    myWindow = MyWindow()
    myWindow.show()
    sys.exit(app.exec_())

앞서 언급한 타이머의 갱신 시간 주기는 QTimer의 setInterval 함수로 지정하며 단위는 ms로써, 1000이 1초에 해당합니다. 타이머에 대한 갱신 시작과 중지는 각각 start와 stop입니다.

[PyQt5] MatplotLib의 차트를 Widget으로 사용하기

PyQt에서 차트를 위젯으로 사용하기 위해 코드입니다. 먼저 실행 결과는 다음과 같습니다.

하단에 컴보박스를 통해 2가지 항목을 선택할 수 있는데요. 항목을 선택할때마다 해당되는 항목의 그래프가 상단에 표시됩니다.

먼저 이를 위한 UI 구성을 위해 아래와 같은 코드를 작성합니다.

import sys
import numpy as np
from PyQt5.QtWidgets import *
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas

class MyWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

        self.setLayout(self.layout)
        self.setGeometry(200, 200, 800, 600)

    def initUI(self):
        self.fig = plt.Figure()
        self.canvas = FigureCanvas(self.fig)
        
        layout = QVBoxLayout()
        layout.addWidget(self.canvas)

        cb = QComboBox()
        cb.addItem('Graph1')
        cb.addItem('Graph2')
        cb.activated[str].connect(self.onComboBoxChanged)
        layout.addWidget(cb)

        self.layout = layout

        self.onComboBoxChanged(cb.currentText())

    def onComboBoxChanged(self, text):
        if text == 'Graph1':
            self.doGraph1()
        elif text == 'Graph2':
            self.doGraph2()

    def doGraph1(self):
        ....

    def doGraph2(self):
        ....
            
if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MyWindow()
    window.show()
    app.exec_()

Qt5에서 Matplot을 사용하기 위해서는 matplotlib.backends.backend_qt5agg 패이지의 FigureCanvasQTAgg라는 이름의 위젯 클래스가 필요하다는 것을 알 수 있습니다. MyWindow 클래스의 doGraph1과 doGraph2 함수의 코드가 핵심인데, 그 내용은 아래와 같습니다.

   def doGraph1(self):
        x = np.arange(0, 10, 0.5)
        y1 = np.sin(x)
        y2 = np.cos(x)
        
        self.fig.clear()

        ax = self.fig.add_subplot(111)
        ax.plot(x, y1, label="sin(x)")
        ax.plot(x, y2, label="cos(x)", linestyle="--")
        
        ax.set_xlabel("x")
        ax.set_xlabel("y")
        
        ax.set_title("sin & cos")
        ax.legend()
        
        self.canvas.draw()

    def doGraph2(self):
        X = np.arange(-5, 5, 0.25)
        Y = np.arange(-5, 5, 0.25)
        X, Y = np.meshgrid(X, Y)
        Z = X**2 + Y**2
        
        self.fig.clear()
        
        ax = self.fig.gca(projection='3d')
        ax.plot_wireframe(X, Y, Z, color='black')

        self.canvas.draw() 

doGraph2 함수에 대한 실행 결과는 다음과 같습니다. 물론 이 함수의 실행은 컴보박스의 항목 중 Graph2를 선택했을때 실행됩니다.

참고로 이 글의 차트는 아래의 글을 참고로 하여 작성하였습니다.

파이썬의 matplotlib 노트

[PyQt5] 메인 윈도우와 다이얼로그 연동

메인 윈도우에서 대화상자를 열고, 대화상자에서 입력한 값을 메인 윈도우에 표시하고하는 경우에 대한 설명입니다. UI 라이브러리는 PyQt5를 사용했습니다. 먼저 메인 모듈에 대한 코드입니다. 참고로 이글은 PyQt5에 대한 최소한의 기초 내용을 파악하고 있는 개발자를 대상으로 합니다.

import sys
from MainWindow import MainWindow
from PyQt5.QtWidgets import *

if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = MainWindow()
    win.show()
    sys.exit(app.exec_())

위의 코드에서 메인 윈도우는 MainWindow.py 파일에 정의되어 있으며, 코드는 다음과 같습니다.

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from SubWindow import SubWindow

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setWindowTitle('Main Window')
        self.setGeometry(100, 100, 300, 200)

        layout = QVBoxLayout()
        layout.addStretch(1)

        label = QLabel("미지정")
        label.setAlignment(Qt.AlignCenter)
        font = label.font()
        font.setPointSize(30)
        label.setFont(font)
        self.label = label

        btn = QPushButton("값 얻어오기")
        btn.clicked.connect(self.onButtonClicked)

        layout.addWidget(label)
        layout.addWidget(btn)

        layout.addStretch(1)

        centralWidget = QWidget()
        centralWidget.setLayout(layout)
        self.setCentralWidget(centralWidget)

    def onButtonClicked(self):
        win = SubWindow()
        r = win.showModal()

        if r:
            text = win.edit.text()
            self.label.setText(text)

    def show(self):
        super().show()

위의 메인 윈도우는 아래와 같은 UI를 표시합니다.

“값 얻어오기” 버튼을 클릭하면 대화창을 표시되며, 표시된 대화창에서 텍스트를 입력하고 대화창의 “확인” 버튼을 클릭하면 대화창에서 입력한 텍스트값을 메인 윈도우의 라벨 위젯에 표시하게 됩니다. 대화창에 대한 코드 파일은 SubWindow.py이며 다음과 같습니다.

import sys
from PyQt5.QtWidgets import *

class SubWindow(QDialog):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setWindowTitle('Sub Window')
        self.setGeometry(100, 100, 200, 100)

        layout = QVBoxLayout()
        layout.addStretch(1)

        edit = QLineEdit()
        font = edit.font()
        font.setPointSize(20)
        edit.setFont(font)
        self.edit = edit

        subLayout = QHBoxLayout()
        
        btnOK = QPushButton("확인")
        btnOK.clicked.connect(self.onOKButtonClicked)

        btnCancel = QPushButton("취소")
        btnCancel.clicked.connect(self.onCancelButtonClicked)

        layout.addWidget(edit)
        
        subLayout.addWidget(btnOK)
        subLayout.addWidget(btnCancel)
        layout.addLayout(subLayout)

        layout.addStretch(1)

        self.setLayout(layout)

    def onOKButtonClicked(self):
        self.accept()

    def onCancelButtonClicked(self):
        self.reject()

    def showModal(self):
        return super().exec_()

아래는 메인 윈도우에서 위의 코드에 대한 대화창을 표시한 뒤 사용자가 “하이! PyQt5″텍스트를 입력한 화면입니다.

위의 화면에서 닫기 버튼을 클릭하면 창이 닫히고 메인 윈도우에 대화창에서 입력한 텍스트가 표시되는데, 아래와 같습니다.

선형회귀모델에 대한 PyTorch를 이용한 두가지 접근

아래와 같은 식을 회귀하는 모델을 구하는 두가지 접근을 PyTorch로 살펴본다.

    $$y=1+5a+7b$$

즉, 입력값(a, b)에 대한 출력값 y가 100개 주어지고, 이 데이터를 통해 상수항인 1과 계수 5, 7을 구하는 것이 문제다. 물론 y에는 오차가 반영되어 있다. 첫번째 접근은 다음과 같다. 손실함수는 평균최소제곱을, 역전파를 통한 최적값 수렴을 위한 기울기를 구해 반영한 학습률은 0.01을 사용했다. 아래의 코드의 경우 기울기를 구하기 위한 방법을 PyTorch의 역전파를 이용한 것이다.

import torch
from matplotlib import pyplot as plt

weight_true = torch.Tensor([1,5,7]) # y = 1 + 5a + 7b
X = torch.cat([torch.ones(100,1),torch.randn(100,2)], 1)
y = torch.mv(X, weight_true) + torch.randn(100)
weight = torch.randn(3, requires_grad=True)

lr = 0.01

losses = []

for epoch in range(1000):
    weight.grad = None

    y_pred = torch.mv(X, weight)
    loss = torch.mean((y - y_pred)**2)
    loss.backward()

    weight.data = weight.data - lr*weight.grad.data

    losses.append(loss.item())

print(weight)

plt.plot(losses)
plt.show()

두번째 접근은 다음과 같다. 앞서 직접 하나 하나 개발자가 지정했던 것들에 대한 모듈을 사용한 경우이다.

import torch
from torch import nn, optim
from matplotlib import pyplot as plt

weight_true = torch.Tensor([1,5,7]) # y = 1 + 5a + 7b
X = torch.cat([torch.ones(100,1),torch.randn(100,2)], 1)
y = torch.mv(X, weight_true) + torch.randn(100)

net = nn.Linear(in_features=3, out_features=1, bias=False)
optimizer = optim.SGD(net.parameters(), lr=0.01)
loss_fn = nn.MSELoss()

losses = []

for epoch in range(1000):
    optimizer.zero_grad()

    y_pred = net(X)
    loss = loss_fn(y_pred.view_as(y), y)
    loss.backward()

    optimizer.step()

    losses.append(loss.item())

print(net.weight)

plt.plot(losses)
plt.show()

두 경우 모두 실행하면 아래와 같은 손실값에 대한 그래프와 추론된 상수와 두계수 값이 콘솔에 출력된다.

tensor([0.9295, 4.9402, 7.0627], requires_grad=True)