シェルスクリプトマガジン

test

シェルスクリプトマガジンvol.84 Web掲載記事まとめ

投稿日:2023.05.25 | カテゴリー: コード

004 レポート オープンソースLLM「MPT-7B」登場
005 レポート メタバースファッションフェア初開催
006 製品レビュー 調理家電「コーヒー豆焙煎機 MR-F60A」
007 NEWS FLASH
008 特集1 pandasを使いこなそう/鶴長鎮一
020 特集2 AutoML徹底解説 応用編/田村孝、山口武彦、新田陸、細野友基
034 特別企画 Raspberry Piを100%活用しよう 拡大版/米田聡 コード掲載
046 注目技術「XR BASE」/井上香
048 Pythonあれこれ/飯尾淳 コード掲載
054 法林浩之のFIGHTING TALKS/法林浩之
056 中小企業手作りIT化奮戦記/菅雄一 コード掲載
060 ツールキット/桑原滝弥、イケヤシロウ
062 タイ語から分かる現地生活/つじみき
068 香川大学SLPからお届け!/三井颯剛 コード掲載
075 Hello Nogyo!
076 行動経済学と心理学で円滑に業務を遂行/請園正敏
080 Linux定番エディタ入門/大津真 コード掲載
086 AWKでデジタル信号処理/斉藤博文 コード掲載
094 Techパズル/gori.sh
095 コラム「ユニケージにおける独特なドキュメント」/シェル魔人

特別企画 Raspberry Piを100%活用しよう 拡大版(Vol.84記載)

投稿日:2023.05.25 | カテゴリー: コード

著者:米田 聡

小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。本企画では、前回に引き続き、ラズパイと連携して使用するマイコンボードを扱います。

シェルスクリプトマガジン Vol.84は以下のリンク先でご購入できます。

図8 I★2★CアドレスとGPIOピン番号の設定(ADRS2040U_i2c.h)

 // ADRS2040U I2Cアドレス
 #define I2C0_SLAVE_ADDR 0x41
 
 // I2Cで使うGPIO
 #define GPIO_SDA0 0
 #define GPIO_SCK0 1

図9 I2CとI2C Slaveライブラリの初期化

#include <stdio.h>
#include <pico/stdlib.h>
#include "hardware/i2c.h" ← hardware_i2cライブラリのヘッダーファイル
#include "pico/i2c_slave.h" ← I2C Slaveライブラリのヘッダーファイル
(略)
#include "ADRS2040U_i2c.h"
(略)
static void i2c_slave_handler(i2c_inst_t *i2c, i2c_slave_event_t event) ← I★★2C割り込みコールバック関数
{
(略)
}

void i2c_setup(void)
{
    i2c_init(i2c0, 100 * 1000); ← I★2★Cコントローラの初期化(通信速度100Kビット/秒)
    gpio_set_function(GPIO_SDA0, GPIO_FUNC_I2C); ← I★2★Cで利用するGPIOピンの設定
    gpio_set_function(GPIO_SCK0, GPIO_FUNC_I2C);
    // ADRS2040Uでは基板上でプルアップされているので
    // プルアップを無効化する
    gpio_disable_pulls(GPIO_SDA0);
    gpio_disable_pulls(GPIO_SCK0);
(略)
    // I2Cスレーブ初期化
    i2c_slave_init(i2c0, I2C0_SLAVE_ADDR, &i2c_slave_handler);
}

図10 コールバック関数「i2c_slave_handler()」

static void i2c_slave_handler(i2c_inst_t *i2c, i2c_slave_event_t event)
{
(略) 
    switch(event) {
        // I2Cデータ受信
        case I2C_SLAVE_RECEIVE:
(略)
            break;
        
        // I2Cデータ要求
        case I2C_SLAVE_REQUEST:
(略)
            break;
        // STOP or RESTARTコンデション
        case I2C_SLAVE_FINISH:
            break;
        
        default:
            break;
    }
}

図11 ADCの初期化で呼び出す関数

adc_init();
adc_gpio_init(26);
adc_select_input(0);

図12 adc_fifo_setup()関数の引数パラメータ

adc_fifo_setup(
    en,
    dreq_en,
    dreq_threash,
    error_in_fifo,
    byte_shift
);

図13 ADCの受信FIFOバッファ割り込み処理のサンプルコード

uint16_t adc_buffer[1024];
static int p = 0;
(略)
// ADC FIFO割り込み関数
void adc_interrupt(void)
{
    // FIFOが空になるまでデータをadc_bufferに読み取る
    while(! adc_fifo_is_empty()) {
        adc_buffer[p++] = adc_fifo_get() & 0xFFF;
    }
}

void main(void)
{
(略)
    adc_fifo_setup(true, false, 1, true, false);
(略)
    // 排他的割り込みの設定
    irq_set_exclusive_handler(ADC_IRQ_FIFO, adc_interrupt);
    // 割り込みの有効化
    irq_set_enabled(ADC_IRQ_FIFO, true);
    // フリーランスタート
    adc_run(true);
(略)
}

図14 サンプリングレート設定のサンプルコード

int sample_rate = 1000;  // 1000 サンプリング/秒

adc_clk = clock_get_hz(clk_adc);
adc_set_clkdiv(adc_clk/sample_rate);

図15 リングバッファのサンプルコード

uint16_t buffer[0x100];
int write_pointer, read_pointer;

write_pointer = read_pointer = 0;

// バッファの読み出し
data = buffer[(read_pointer++) & 0xFF];
// バッファへの書き込み
buffer[(write_pointer++) & 0xFF] = data;

図16 クリティカルセクションの保護

mutex_enter_blocking(&my_mutex);

ここがクリティカルセクション

mutex_exit(&my_mutex);

図17 サンプルファームウエアのレジスタ番号定義(ADRS2040U_i2c.h)

// I2Cレジスタ
enum ADRS2040_CMD {
    ADRS2040_CMD_INVALID,       // 無効
    ADRS2040_CMD_ADC_START,     // ADCフリーラン開始
    ADRS2040_CMD_ADC_STOP,      // ADCフリーラン停止
    ADRS2040_CMD_SET_RATE,      // サンプリングレート設定
    ADRS2040_CMD_GET_COUNT,     // バッファデータ数
    ADRS2040_CMD_GET_VALUE,      // ADCデータ読み取り
};

図18 I2Cスレーブ動作時に使えるFIFOバッファ読み書き関数

// 1バイト読みだし
i2c_read_byte_raw(i2c_inst_t *);
// 指定バイト数の読み出し
i2c_read_raw_blocking(i2c_inst_t *, uint8_t *, size_t);
// 1バイト書き込み
i2c_write_byte_raw(i2c_inst_t *, uint8_t);
// 指定バイト数の書き込み
i2c_write_raw_blocking(i2c_inst_t *, uint8_t *, size_t);

図19 サンプルファームウエアのi2c_slave_handler()関数とその関連個所(main.cpp)

// ADCドライバ
ADC_Driver adcd;


typedef union {
    uint16_t d;
    uint8_t  b[2];
} I2C_WORD_t;


static void i2c_slave_handler(i2c_inst_t *i2c, i2c_slave_event_t event)
{
    static uint8_t ADRS2040U_cmd = ADRS2040_CMD_INVALID;

    switch(event) {
        // I2Cデータ受信
        case I2C_SLAVE_RECEIVE:
            if(ADRS2040U_cmd == ADRS2040_CMD_INVALID) {
                ADRS2040U_cmd = i2c_read_byte_raw(i2c);
            }
            if(ADRS2040U_cmd == ADRS2040_CMD_ADC_START) {
                DEBUG_PRINT("ADC START\n");
                adcd.run(true);
                ADRS2040U_cmd = ADRS2040_CMD_INVALID;
            }
            else if(ADRS2040U_cmd == ADRS2040_CMD_ADC_STOP) {
                DEBUG_PRINT("ADC STOP\n");
                adcd.run(false);
                ADRS2040U_cmd = ADRS2040_CMD_INVALID;

            }
            else if(ADRS2040U_cmd == ADRS2040_CMD_SET_RATE) {
                I2C_WORD_t rate;
                i2c_read_raw_blocking(i2c, rate.b, sizeof(I2C_WORD_t));
                DEBUG_PRINT("Rate = %d\n", rate.d * 10);
                adcd.set_sample_rate(rate.d * 10);
                ADRS2040U_cmd = ADRS2040_CMD_INVALID;
            }
            break;
        
        // I2Cデータ要求
        case I2C_SLAVE_REQUEST:
            I2C_WORD_t sdata;
            sdata.d = 0;

            if(ADRS2040U_cmd == ADRS2040_CMD_GET_COUNT) {
                sdata.d = adcd.count();
            }
            else if(ADRS2040U_cmd == ADRS2040_CMD_GET_VALUE) {
                int value = adcd.get_value();

                if( value >= 0) {
                    sdata.d = value & 0xFFF;
                }
                else {
                    sdata.d = 0xFFFF;
                }
            }
            i2c_write_raw_blocking(i2c, sdata.b, sizeof(I2C_WORD_t));
            ADRS2040U_cmd = ADRS2040_CMD_INVALID;

            break;
        // STOP or RESTARTコンデション
        case I2C_SLAVE_FINISH:
            break;
        
        default:
            break;
    }
}
(略)
int main(void)
{
    stdio_init_all();
    i2c_setup();
    while (true)
    {
        ;
    }
}

図20 受信FIFOバッファからデータを取り出すための前処理

i2c_hw_t *i2chw = i2c_get_hw(i2c0);
uint32_t datacmd = i2chw->data_cmd; // FIFOからデータを取り出す
uintu_t value = datacmd & I2C_IC_DATA_CMD_DAT_BITS; // 下位8ビットがデータ本体
if(datacmd & I2C_IC_DATA_CMD_FIRST_DATA_BYTE_BITS) {
    // I2Cアドレスに続く最初の書き込みバイト
    // つまりレジスタ番号
}

図22 ADCへアクセスするサンプルプログラム(adcsample.py)

from smbus2 import SMBus

ADRS2040_ADDR=0x41
ADRS2040_CMD_INVALID      = 0
ADRS2040_CMD_ADC_START    = 1
ADRS2040_CMD_ADC_STOP     = 2
ADRS2040_CMD_SET_RATE     = 3
ADRS2040_CMD_GET_COUNT    = 4
ADRS2040_CMD_GET_VALUE    = 5

with SMBus(1) as i2c:
    # 500 spsで初期化・スタート
    self.i2c.write_word_data(ADRS2040_ADDR, ADRS2040_CMD_SET_RATE, 50)
    self.i2c.write_byte(ADRS2040_ADDR, ADRS2040_CMD_ADC_START)

    while True:
        nod = 0
        nod = self.i2c.read_word_data(ADRS2040_ADDR,ADRS2040_CMD_GET_COUNT)
        for i in range(nod):
            value = self.i2c.read_word_data(ADRS2040_ADDR,ADRS2040_CMD_GET_VALUE)
            print(value)

図23 ADCから取得したデータをグラフ化するサンプルプログラム(adctest.py)

from smbus2 import SMBus
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
import sys
import time

# 定数
ADRS2040_ADDR=0x41
ADRS2040_CMD_INVALID      = 0
ADRS2040_CMD_ADC_START    = 1
ADRS2040_CMD_ADC_STOP     = 2
ADRS2040_CMD_SET_RATE     = 3
ADRS2040_CMD_GET_COUNT    = 4
ADRS2040_CMD_GET_VALUE    = 5

class ADCGraph:
    def __init__(self):
        # グラフウィンドウ
        self.win = pg.GraphicsWindow()
        self.win.setWindowTitle('ADC Input')
        self.plt = self.win.addPlot()
        self.plt.setYRange(0,1024)
        self.curve = self.plt.plot(pen=(0, 0, 255))

        # グラフデータを用意
        self.data = np.zeros(100)

        self.i2c = SMBus(1)
        # 500 spsで初期化・スタート
        self.i2c.write_word_data(ADRS2040_ADDR, ADRS2040_CMD_SET_RATE, 50)
        self.i2c.write_byte(ADRS2040_ADDR, ADRS2040_CMD_ADC_START)

        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.update)
        # 10msごとに更新
        self.timer.start(10)

    # グラフ更新
    def update(self):
        nod = 0
        nod = self.i2c.read_word_data(ADRS2040_ADDR,ADRS2040_CMD_GET_COUNT )
        for i in range(nod):
            value = self.i2c.read_word_data(ADRS2040_ADDR,ADRS2040_CMD_GET_VALUE)
            self.data = np.delete(self.data, 0)
            self.data = np.append(self.data, value)
        self.curve.setData(self.data)

if __name__ == "__main__":
    graphWin = ADCGraph()

    if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
        QtGui.QApplication.instance().exec_()

Pythonあれこれ(Vol.84掲載)

投稿日:2023.05.25 | カテゴリー: コード

著者:飯尾 淳

本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温
かく見守ってください。皆さんと共に勉強していきましょう。第14回では、話題のAIチャットボット「ChatGPT」を使ったPythonプログ「ラムの作成にチャレンジします。ChatGPTは、どの程度のプログラムを生成してくれるのでしょうか。

シェルスクリプトマガジン Vol.84は以下のリンク先でご購入できます。

図2 1回目にChatGPTが生成したPythonプログラム

import sys

if len(sys.argv) < 3:
    print("Usage: python program_name.py num1 num2")
else:
    num1 = float(sys.argv[1])
    num2 = float(sys.argv[2])
    result = num1 + num2
    print("Result:", result)

図3 2回目にChatGPTが生成したPythonプログラム

import sys
from fractions import Fraction

if len(sys.argv) < 3:
    print("Usage: python program_name.py num1 num2")
else:
    try:
        num1 = float(sys.argv[1])
        num2 = float(sys.argv[2])
        if num1.is_integer() and num2.is_integer():
            num1, num2 = int(num1), int(num2)
        elif '/' in sys.argv[1] and '/' in sys.argv[2]:
            num1 = Fraction(sys.argv[1])
            num2 = Fraction(sys.argv[2])
        result = num1 + num2
        print("Result:", result)
    except ValueError:
        print("Please enter numbers only.")

図4 3回目にChatGPTが生成したPythonプログラム

import sys
from fractions import Fraction

if len(sys.argv) < 3:
    print("Usage: python program_name.py num1 num2")
else:
    try:
        num1 = 0
        if '/' in sys.argv[1]:
            num1 = Fraction(sys.argv[1])
        else:
            num1 = float(sys.argv[1])
        num2 = 0
        if '/' in sys.argv[2]:
            num2 = Fraction(sys.argv[2])
        else:
            num2 = float(sys.argv[2])
        result = num1 + num2
        print("Result:", result)
    except ValueError:
        print("Please enter numbers only.")

図5 4回目にChatGPTが生成したPythonプログラム

import sys
from fractions import Fraction

def check_arg_validity(arg):
    try:
        if '/' in arg:
            num = Fraction(arg)
        else:
            num = float(arg)
        return num
    except ValueError:
        return None

if len(sys.argv) < 3:
    print("Usage: python program_name.py num1 num2")
else:
    try:
        num1 = check_arg_validity(sys.argv[1])
        num2 = check_arg_validity(sys.argv[2])
        if num1 is None or num2 is None:
            print("Please enter numbers only.")
        else:
            result = num1 + num2
            print("Result:", result)

図6 5回目にChatGPTが生成したPythonプログラム

import sys
from fractions import Fraction

def check_number(num_str):
    try:
        float(num_str)
    except ValueError:
        try:
            Fraction(num_str)
        except ValueError:
            print(f"{num_str} is not a valid number.")
            return None
    return num_str

def add2(num1, num2):
    result = Fraction(num1) + Fraction(num2)
    print(f"{num1} + {num2} = {result}")

if __name__ == '__main__':
    num1 = check_number(sys.argv[1])
    num2 = check_number(sys.argv[2])

    if num1 and num2:
        add2(num1, num2)

中小企業手作りIT化奮戦記(Vol.84掲載)

投稿日:2023.05.25 | カテゴリー: コード

筆者:菅 雄一

自社サーバーからレンタルサーバーへの移行が世の流れだ。本連載の第58回「PHP+MySQL環境にシステムを移行」に書いたが、筆者の勤務先でも保守作業の軽減や故障回避のためにレンタルサーバーに移行する方針になった。2023年1月に、四苦八苦しながら顧客向けWeb検索システムを移行し、ようやくレンタルサーバーへの移行が一段落した。今回は、この難航した移行作業について書くことにする。

シェルスクリプトマガジン Vol.84は以下のリンク先でご購入できます。

図2 バックアップファイル内の文字コード設定を変更

--
-- PostgreSQL database dump
--

SET client_encoding = 'EUC';
SET standard_conforming_strings = off;
SET check_function_bodies = false;
SET client_min_messages = warning;
SET escape_string_warning = off;
(略)

香川大学SLPからお届け!(Vol.84掲載)

投稿日:2023.05.25 | カテゴリー: コード

著者:三井 颯剛

SLPでは、Webページの公開などに使用しているサーバーを立て直す計画が進行中です。再建後のサーバーでは、「Traefik(トラフィック) 」というコンテナ環境向けのリバースプロキシソフトウエアを採用する予定です。今回は、Dockerでサーバーコンテナを稼働させて、それに対するアクセスをTraefikで制御する場合を例に、Traefikの利用方法について紹介します。

シェルスクリプトマガジン Vol.84は以下のリンク先でご購入できます。

図3 「docker-compose.yml」ファイルに記述する設定

services:
  reverse-proxy:
    image: traefik:latest
    restart: always
    ports:
      - "80:80"
    environment:
      - TZ=Asia/Tokyo
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    command:
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"

  whoami:
    image: traefik/whoami
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=PathPrefix(/whoami)"
      - "traefik.http.routers.whoami.entrypoints=web"

図6 ダッシュボードを有効にする場合の「docker-compose.yml」ファイルの記述

services:
  reverse-proxy:
    image: traefik:latest
    restart: always
    ports:
      - "80:80"
      - "8080:8080"
    environment:
      - TZ=Asia/Tokyo
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    command:
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.dashboard.address=:8080"
      - "--api.dashboard=true"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.api.entrypoints=dashboard"
      - "traefik.http.routers.api.rule=Host(localhost)"
      - "traefik.http.routers.api.service=api@internal"

  whoami:
    image: traefik/whoami
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=PathPrefix(/whoami)"
      - "traefik.http.routers.whoami.entrypoints=web"

図9 「traefik.yml」ファイルに記述する内容

providers:
  docker:
    exposedByDefault: false
entryPoints:
  web:
    address: ":80"
  dashboard:
    address: ":8080"
api:
  dashboard: true

図10 設定を分離した場合の「docker-compose.yml」ファイルの記述例

services:
  reverse-proxy:
    image: traefik:latest
    restart: always
    ports:
      - "80:80"
      - "8080:8080"
    environment:
      - TZ=Asia/Tokyo
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./traefik.yml:/etc/traefik/traefik.yml
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.api.entrypoints=dashboard"
      - "traefik.http.routers.api.rule=Host(localhost)"
      - "traefik.http.routers.api.service=api@internal"

  whoami:
    image: traefik/whoami
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=PathPrefix(/whoami)"
      - "traefik.http.routers.whoami.entrypoints=web"
   - "traefik.http.services.myservice.loadbalancer.server.port=80"

AWKでデジタル信号処理(Vol.84掲載)

投稿日:2023.05.25 | カテゴリー: コード

著者:斉藤 博文

プログラミング言語「AWK」は、データストリーム(データの流れ)を逐次処理するのに適しています。本連載では、電子回路の分野でその特徴を生かし、シェルスクリプトを組み合わせてデジタル信号を処理します。最終回は前回の続きとして、心電図データの解析について解説します。

シェルスクリプトマガジン Vol.84は以下のリンク先でご購入できます。

図5 ecg.awk内のlpf()関数

# low pass filter
function lpf(arr_x, arr_y, idx_x, idx_y, ord, len_x, len_y,     _ret, _gain) {
    _ret = 0.0;
    _gain = 1.0 / (ord * ord);

    _ret += 2.0 * get_buffer(arr_y, idx_y - 1, len_y);
    _ret -= 1.0 * get_buffer(arr_y, idx_y - 2, len_y);
    _ret += 1.0 * _gain * arr_x[idx_x];
    _ret -= 2.0 * _gain * get_buffer(arr_x, idx_x - ord, len_x);
    _ret += 1.0 * _gain * get_buffer(arr_x, idx_x - 2 * ord, len_x);

    return _ret;
}

図6 ecg.awk内の最終結果を出力するprintf()文で群遅延を補正

    # print results
    printf("%f,%f,%f,%f,%f,%f,%f,%f\n",
            get_buffer( \
                    arr_data_raw,
                    idx_data - (val_gd_lpf + val_gd_hpf + val_gd_dif + val_gd_sma) - 2 * val_fs,
                    len_data),
            get_buffer( \
                    arr_data_lpf,
                    idx_data - (val_gd_hpf + val_gd_dif + val_gd_sma) - 2 * val_fs,
                    len_data),
            get_buffer( \
                    arr_data_hpf,
                    idx_data - (val_gd_dif + val_gd_sma) - 2 * val_fs,
                    len_data),
            get_buffer( \
                    arr_data_dif,
                    idx_data - val_gd_sma - 2 * val_fs,
                    len_data),
            get_buffer( \
                    arr_data_squ,
                    idx_data - val_gd_sma - 2 * val_fs,
                    len_data),
            get_buffer( \
                    arr_data_sma,
                    idx_data - 2 * val_fs,
                    len_data),
            cnt_rri_curr * 0.005 * 1000,
            get_buffer( \
                    arr_data_flg,
                    idx_data - (val_gd_dif + val_gd_sma) - 2 * val_fs,
                    len_data));
}

図12 ecg.awk内のしきい値(val_data_sma_threshold)を設定している箇所

# update threshold every interval
if (num_data % tap_interval == 0) {
    val_data_sma_threshold = val_data_sma_max * val_peak_rate;
    val_data_sma_max = 0;
} else {
    if (val_data_sma_max < arr_data_sma[idx_data]) {
        val_data_sma_max = arr_data_sma[idx_data];
    }
}

図13 ecg.awk内のval_data_flgを「1」に設定している箇所

if (val_data_sma_curr > val_data_sma_threshold && val_data_sma_threshold >= val_data_sma_last) {
    val_data_hpf_max = 0;
    idx_data_hpf_max = 0;
    val_data_flg = 1;
}

図14 ecg.awk内のidx_data_hpf_maxを求めている箇所

# search maximum index
if (val_data_flg == 1) {
    val_data_hpf_curr = get_buffer(arr_data_hpf, idx_data - (val_gd_dif + val_gd_sma), len_data);
    idx_data_hpf_curr = idx_data - (val_gd_dif + val_gd_sma);
    idx_data_hpf_curr = idx_data_hpf_curr >= 0 ? idx_data_hpf_curr : idx_data_hpf_curr + len_data;

    if (val_data_hpf_max < val_data_hpf_curr) {
        val_data_hpf_max = val_data_hpf_curr;
        idx_data_hpf_max = idx_data_hpf_curr;
    }
}

図15 ecg.awk内のarr_data_flg[idx_data_hpf_max]を「1」に設定している箇所

# end point to detect main peak
if (val_data_sma_curr < val_data_sma_threshold && val_data_sma_threshold <= val_data_sma_last) {
    arr_data_flg[idx_data_hpf_max] = 1;

図18 ecg.awk内のRRIを求めている箇所

# calculate peak to peak interval
if (idx_data - idx_data_hpf_max >= 0) {
    cnt_peak_curr = num_data - (idx_data - idx_data_hpf_max);
} else {
    cnt_peak_curr = num_data - (idx_data - idx_data_hpf_max + len_data);
}

cnt_rri_curr = cnt_peak_curr - cnt_peak_last;
cnt_peak_last = cnt_peak_curr;

Linux定番エディタ入門(Vol.84掲載)

投稿日:2023.05.25 | カテゴリー: コード

著者:大津 真

文章の作成やプログラミングに欠かせないのがテキストエディタ(以下、エディタ)です。この連載では、Linuxで利用できる定番エディタの特徴と使い方を解説していきます。第1回に取り上げるのは、CU(I Character User Interface)環境で利用できるシンプルで扱いやすい「GNU nano」です。

シェルスクリプトマガジン Vol.84は以下のリンク先でご購入できます。

図12 nanoの設定ファイル「~/.nanorc」の記述例

# バックアップファイルを作成
set backup 
# 各行の左に行番号を表示
set linenumbers
# 文字列検索時に大文字小文字を区別
set casesensitive
# 長い行を画面の右端で折り返す 
set softwrap
# オートインデント
set autoindent
# タブサイズを「4」に(デフォルトは「8」)
set tabsize 4
# マウスのサポートを有効に
set mouse

Pythonあれこれ(Vol.83掲載)

投稿日:2023.03.25 | カテゴリー: コード

著者:飯尾 淳

本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温
かく見守ってください。皆さんと共に勉強していきましょう。第13回では、PyTorchという機械学習ライブラリを使って、手書きの数字を画像認識によって分類してみます。実際に作業することで、機械学習の効果を感じてください。

シェルスクリプトマガジン Vol.83は以下のリンク先でご購入できます。

図1 MNISTデータセットをダウンロードするためのコード

data_folder = '~/data'
BATCH_SIZE = 8
mnist_data = MNIST(data_folder, 
                   train=True, 
                   download=True, 
                   transform=transforms.ToTensor())
data_loader = DataLoader(mnist_data, 
                         batch_size=BATCH_SIZE, 
                         shuffle=False)

図4 MNISTデータセットのデータを1セット表示するコード

data_iterator = iter(data_loader)
images, labels = next(data_iterator)
# 最初の画像を表示
location = 0
# 画像データを28×28画素のデータに変換して表示
data = images[location].numpy()
reshaped_data = data.reshape(28, 28)
plt.imshow(reshaped_data, cmap='inferno', interpolation='bicubic')
plt.show()
print('ラベル:', labels[location])

図6 学習データと検証データを用意するコード

# 学習データ
train_data_loader = DataLoader(
    MNIST(data_folder, train=True, download=True, 
          transform=transforms.ToTensor()),
    batch_size=BATCH_SIZE, shuffle=True)
# 検証データ
test_data_loader = DataLoader(
    MNIST(data_folder, train=False, download=True, 
          transform=transforms.ToTensor()),
    batch_size=BATCH_SIZE, shuffle=True)

図9 ニューラルネットワークモデルを定義するコード

from torch.autograd import Variable
import torch.nn as nn
# 親クラスのnn.Moduleを継承してモデルを作成
class MLP(nn.Module):
  def __init__(self):
    super().__init__()
    self.layer1 = nn.Linear(28 * 28, 100)
    self.layer2 = nn.Linear(100, 50)
    self.layer3 = nn.Linear(50, 10)
  def forward(self, input_data):
    input_data = input_data.view(-1, 28 * 28)
    input_data = self.layer1(input_data)
    input_data = self.layer2(input_data)
    input_data = self.layer3(input_data)
    return input_data

図11 学習前の準備をするコード

import torch.optim as optimizer

# モデルの作成
model = MLP()
# 評価器(誤差項)と最適化器の作成
lossResult = nn.CrossEntropyLoss()
optimizer = optimizer.SGD(model.parameters(), lr=0.01)

図12 画像認識の精度を検証するコード

import torch

# 検証した数と正解の数
total = 0
count_when_correct = 0
for data in test_data_loader:
  test_data, teacher_labels = data
  results = model(Variable(test_data))
  _, predicted = torch.max(results.data, 1)
  total += teacher_labels.size(0)
  count_when_correct += (predicted == teacher_labels).sum()
rate = int(count_when_correct) / int(total)
print(f'count_when_correct:{count_when_correct}')
print(f'total:{total}')
print(f'正解率:{count_when_correct} / {total} = {rate}')

図13 モデルを学習させるコード

# 最大学習回数
MAX_EPOCH = 4
for epoch in range(MAX_EPOCH):
  total_loss = 0.0
  for i, data in enumerate(train_data_loader):
    train_data, teacher_labels = data
    train_data, teacher_labels = \
      Variable(train_data), Variable(teacher_labels)
    # 勾配情報をリセット
    optimizer.zero_grad()
    outputs = model(train_data)
    loss = lossResult(outputs, teacher_labels)
    loss.backward()
    # 勾配を更新
    optimizer.step()
    # 誤差を積み上げる
    total_loss += loss.data
    if i % 2000 == 1999:
      print(f'学習進捗:[{epoch+1}, {i+1}]', end='')
      print(f'学習誤差(loss): {total_loss / 2000:.3f}')
      total_loss = 0.0
print('学習終了')

図15 個別の判定結果を確認するコード

# データの取得と検証
test_iterator = iter(test_data_loader)
test_data, teacher_labels = next(test_iterator)
results = model(Variable(test_data))
_, predicted_label = torch.max(results.data, 1)
# 最初のデータを検証して画像を表示
location = 0
plt.imshow(test_data[location].numpy().reshape(28, 28), 
           cmap='inferno', interpolation='bicubic')
print('ラベル:', predicted_label[location])

香川大学SLPからお届け!(Vol.83掲載)

投稿日:2023.03.25 | カテゴリー: コード

著者:安田 大朗

最近、さまざまな場面でチャットbotなどの対話システムを見かけます。その中には、感情表現の機能を持ち、より人間らしい振る舞いをするものもあります。私はそうした対話システムに興味があり、ユーザーが入力したテキストの感情を分析し、それに応じた反応をする対話システムを作成しました。今回は、私が開発したその対話システムについて紹介します。

シェルスクリプトマガジン Vol.83は以下のリンク先でご購入できます。

図4 「telegram-bot.py」ファイルに記述するコード

from telegram.ext import (Updater, CommandHandler,
                          MessageHandler, Filters)

TOKEN = "トークン"
class TelegramBot:
  def __init__(self, system):
    self.system = system
  def start(self, update, bot):
    input = {'utt':None, 'sessionId':str(update.message.from_user.id)}
    update.message.reply_text(self.system.initial_message(input)["utt"])
  def message(self, update, bot):
    input = {'utt':update.message.text,
             'sessionId':str(update.message.from_user.id)}
    system_output = self.system.reply(input)
    update.message.reply_text(system_output["utt"])
  def run(self):
    updater = Updater(TOKEN, use_context=True)
    dp = updater.dispatcher
    dp.add_handler(CommandHandler("start", self.start))
    dp.add_handler(MessageHandler(Filters.text, self.message))
    updater.start_polling()
    updater.idle()

図5 「aim_system1.py」ファイルに記述するコード

import aiml
import MeCab
from telegram_bot import TelegramBot

class AimlSystem:
  def __init__(self):
    self.sessiondic = {}
    self.tagger = MeCab.Tagger('-Owakati')
  def initial_message(self, input):
    sessionId = input['sessionId']
    kernel = aiml.Kernel()
    kernel.learn("aiml.xml")
    self.sessiondic[sessionId] = kernel
    return {'utt':'はじめまして,雑談を始めましょう',
            'end':False}
  def reply(self, input):
    sessionId = input['sessionId']
    utt = input['utt']
    utt = self.tagger.parse(utt)
    response = self.sessiondic[sessionId].respond(utt)
    return {'utt':response, 'end':False}

if __name__ == '__main__':
    system = AimlSystem()
    bot = TelegramBot(system)
    bot.run()

図6 「aim.xml」ファイルに記述するルールの例

<?xml version="1.0" encoding="UTF-8"?>
<aiml version="1.0.1" encoding="UTF-8">
 <category>
    <pattern>* へ 旅行 に 行き まし た</pattern>
    <template><star/>に旅行ですか,いいなー.</template>
  </category>
  <category>
    <pattern>私 の 名前 は * です</pattern>
    <template><set name="username"><star/></set>さん、よろしくね!.</template>
   </category>
  <category>
    <pattern>じゃあね</pattern>
    <template><get name="username"/>さん、バイバイー.</template>
  </category>
</aiml>

図8 「aim_system2.py」ファイルに記述するコード

    response = self.sessiondic[sessionId].respond(utt)
    emotion_dic = {'suki':'🥰', 'ikari':'😡', 'kowa':'😱',
                   'yasu':'😊', 'iya':'😫', 'aware':'😭',
                   'takaburi':'🤩', 'odoroki':'🙄', 'haji':'🤭',
                   'yorokobi':'😄'}
    emotion_analyzer = MLAsk()
    json_emot = emotion_analyzer.analyze(utt)
    if json_emot['emotion'] == None:
      return {'utt':response, 'end':False}
    else:
      emotion = json_emot['representative'][0]
      return {'utt':response + emotion_dic[emotion], 'end':False}

if __name__ == '__main__':

Bash入門(Vol.83記載)

投稿日:2023.03.25 | カテゴリー: コード

著者:大津 真

LinuxやmacOSなど、UNIX系OSのコマンドラインで使用されるコマンドインタプリタをシェルと呼びます。本連載では高機能シェルとして人気の高い「Bash」の基本操作について説明していきます。最終回となる今回は、シェルで実行可能なプログラミング言語である「シェルスクリプト」について解説します。

シェルスクリプトマガジン Vol.83は以下のリンク先でご購入できます。

図2 図1のコマンド行と同様の処理をするシェルスクリプトの例

# サイズの大きなファイルを見つける
echo "--「~/ピクチャ」以下にある1Mバイト以上のファイル--"
find ~/ピクチャ -type f -size +1M -print0 | \ 
  xargs -0 du | \
  sort -nr | \
  head -n 5

図5 図2のシェルスクリプトを変数を使うように書き換えた例

#!/usr/bin/bash
dir=~/ピクチャ
size="1M"
echo "--「${dir}」以下にある${size}バイト以上のファイル--"
find "$dir" -type f -size "+${size}" -print0 | \
  xargs -0 du | \
  sort -nr | \
  head -n 5

図6 図5のシェルスクリプトをコマンドライン引数を参照するように書き換えた例

#!/usr/bin/bash
dir=$1
size="1M"
echo "--「${dir}」以下にある${size}バイト以上のファイル--"
find "$dir" -type f -size "+${size}" -print0 | \
  xargs -0 du | \
  sort -nr | \
  head -n 5

図7 if文を使用したシェルスクリプトの例

#!/usr/bin/bash
if cd $1 2>/dev/null
then
  echo "${1}に移動しました"
else
  echo "${1}に移動できませんでした"
fi

図8 図6のシェルスクリプトを条件式とif文を使って書き換えた例

#!/usr/bin/bash
if [ $# -eq 0 ]
then
  echo "エラー: 引数でディレクトリを指定してください"
  exit 1 ←③
fi
if [ ! -d "$dir" ]
then
  echo "エラー: ${dir}が見つかりません"
  exit 1
fi
size="1M"
echo "--「${dir}」以下にある${size}バイト以上のファイル--"
find "$dir" -type f -size "+${size}" -print0 | \
  xargs -0 du | \
  sort -nr | \
  head -n 5

図9 for文を使ったシェルスクリプトの例

#!/usr/bin/bash
four_seasons="春 夏 秋 冬"
for season in $four_seasons
do
    echo "$season"
done

図10 図8のシェルスクリプトをfor文を使って書き換えた例

#!/usr/bin/bash
if [ $# -eq 0 ]
then
  echo “エラー: 引数でディレクトリを指定してください”
  exit 1
fi
size="1M"
for dir in "$@"
do
  if [ ! -d “$dir” ]
  then
    echo "エラー: ${dir}が見つかりません"
  else
    echo "--「${dir}」以下にある${size}バイト以上のファイル--"
    find $dir -type f -size "+${size}" -print0 | \
      xargs -0 du | \
      sort -nr | \
      head -n 5
  fi
done

図12 配列を使ったシェルスクリプトの例

#!/usr/bin/bash
if [ $# -eq 0 ] || [ ! -d $1 ]
then
  echo "エラー: 引数でディレクトリを指定してください"
  exit 1
fi
target=~/公開/pictures
extensions=(jpg jpeg png gif pdf)
for ext in "${extensions[@]}"
do
  for file in "$1"/*.${ext}
  do
    if [ -f "$file" ]
    then
      cp -v "$file" $target
    fi
  done
done

Vol.83 補足情報

投稿日:2023.03.25 | カテゴリー: コード

特別企画 Raspberry Piを100%活用しよう 拡大版

 同企画で扱いました、PlatformIO向けのRP2040用ソフトウエア基盤(フレームワーク)が「https://github.com/Wiz-IO/wizio-pico」から入手できなくなってしまいました。原作者に問い合わせましたが、返答はなく理由は不明です。ご迷惑をおかけして申し訳ございません。
 「https://github.com/Wiz-IO/wizio-pico」にアクセスして図1のように表示されたときは、以下の方法で入手・導入してください。
(1)特別企画記事の通りに開発ツール「Visual Studio Code」(VSCode)とWindows版Gitクライアント「Git for Windows」をインストールします。
(2)PlatformIO向けのRP2040用ソフトウエア基盤(フレームワーク)を「https://future.quake4.jp/wizio-pico.zip」からダウンロードします。
(3)VSCodeを起動しているならいったん終了します。
(4)「C:¥Users¥ユーザー名¥.platformio¥」(ユーザー名はWindowsのアカウント名)の下にダウンロードした「wizio-pico.zip」ファイルを展開します。

図1 ページが見つからない場合の表示

情報は随時更新致します。

シェルスクリプトマガジンvol.83 Web掲載記事まとめ

投稿日:2023.03.25 | カテゴリー: コード

004 レポート モーションキャプチャ「mocopi」 サンプル動画掲載
005 レポート ChatGPT API公開 コード掲載
006 製品レビュー ウエアラブルデバイス「Redmi Smart Band2」
007 NEWS FLASH
008 特集1 Ruby on Rails入門/安川要平 コード掲載
020 特集2 AutoML徹底解説 入門編/田村孝、山口武彦、新田陸、細野友基
034 特別企画 Raspberry Piを100%活用しよう 拡大版/米田聡 コード掲載
044 Pythonあれこれ/飯尾淳 コード掲載
049 Hello Nogyo!
050 香川大学SLPからお届け!/安田大朗 コード掲載
056 法林浩之のFIGHTING TALKS/法林浩之
058 中小企業手作りIT化奮戦記/菅雄一
062 SCM/桑原滝弥、イケヤシロウ
064 タイ語から分かる現地生活/つじみき
070 行動経済学と心理学で円滑に業務を遂行/請園正敏
074 AWKでデジタル信号処理/斉藤博文
082 ユニケージ通信/田渕智也、高橋未来哉
086 Bash入門/大津真 コード掲載
096 Techパズル/gori.sh
097 コラム「ユニケージ式プロジェクト管理法」/シェル魔人

レポート2(Vol.83掲載)

投稿日:2023.03.25 | カテゴリー: コード

著者:末安 泰三

話題のAIチャットボット「ChatGPT」のAP(I Application Programming Interface)を、開発元の米OpenAIが有償公開した。APIの公開により、自作プロダクトにChatGPTの機能を手軽に組み込めるようになった。2023年3月時点の利用料金は、1000トークン当たり0.002ドルだ。

シェルスクリプトマガジン Vol.83は以下のリンク先でご購入できます。

図1 ChatGPT APIを利用するPythonコードの例

import openai
openai.api_key = "ここにAPIキーを記述する"
response = openai.ChatCompletion.create(
  model = "gpt-3.5-turbo",
  messages = [
    {"role":"system", "content":"①ChatGPTの動作をここで指定する"},
    {"role":"user", "content":"②ChatGPTに送るメッセージを記述する"}
  ]
)
print(response['choices'][0]['message']['content'])

特集1 Ruby on Rails入門(Vol.83記載)

投稿日:2023.03.25 | カテゴリー: コード

著者:安川要平

本特集では、地図アプリの作成を題材に、Webアプリケーションフレームワーク「Ruby on Rails」によるWebアプリの開発手順を紹介します。説明は、米GitHub社が提供するクラウド開発環境「GitHub Codespaces」を使って進めます。そのため、Webブラウザさえあれば紹介する手順を確認できます。Webブラウザだけで体験できるようになった最新のRubyとRailsを一緒に触ってみましょう。

シェルスクリプトマガジン Vol.83は以下のリンク先でご購入できます。

図15 「app/models/spot.rb」ファイルに追加する1行

class Spot < ApplicationRecord
  has_one_attached :photo
end

図16 「app/views/apots/_form.html.erb」ファイルに追加する記述

(略)
  <div>
    <%= form.label :name, style: "display: block" %>
    <%= form.text_field :name, readonly: false %>
  </div>
  <div>
    <%= form.label :photo, style: "display: block" %>
    <%= form.file_field :photo, readonly: false %>
  </div>
  <div>
    <%= form.submit %>
  </div>
<% end %>
(略)

図18 「app/controllers/spots_controller.rb」ファイルに追加する記述

(略)
    # Only allow a list of trusted parameters through.
    def spot_params
      params.require(:spot).permit(:lat, :lng, :name, :photo)
    end

end

図19 「app/helpers/spots_helper.rb」ファイルに追加する記述

(略)
    html << "<strong>Name:</strong> #{spot.name}<br />"
    if spot.photo.attached?
      html << "<strong>Photo:</strong> #{image_tag(spot.photo, width: '100%')}<br />"
    end
    return html.html_safe
  end
end

図25 提供されるサンプルデータの内容

[
  {
    id:    "1037",
    lat:   "35.7087568",
    lng:   "139.7196777",
    name:  "早大で入試「一般選抜」がスタート"
    photo: "https://localmap.jp/images/takadanobaba/1037.jpg",
    url:   "https://takadanobaba.keizai.biz/headline/1037/",
  },
(略)
]

図26 サンプルデータを取得して画面に表示するコード

require 'net/http' # 'net-http'ではないことに注意
uri = URI('https://localmap.jp/scaffold.json')
response = Net::HTTP.get(uri)   # ファイルを取得 
map_data = JSON.parse(response)	# 結果を変数に格納
p map_data # 画面に表示	

図28 サンプルデータを自動入力するコード

require 'net/http'
uri = URI('https://localmap.jp/scaffold.json')
response = Net::HTTP.get(uri)
map_data = JSON.parse(response)

# 各スポットのデータ(map)に対して以下のコードを実行
map_data.each do |map|
  # 新規スポットにサンプルデータを入力
  spot = Spot.new(
    lat:  map['lat'],
    lng:  map['lng'],
    name: map['name'],
  )
  # 新規スポットに画像を添付
  spot.photo.attach(
    io: URI.open(map['photo']),
    filename: map['id'] + '.jpg',
  )
  # 新規スポットをDBに保存
  spot.save
end

特別企画 Raspberry Piを100%活用しよう 拡大版(Vol.83記載)

投稿日:2023.03.25 | カテゴリー: コード

著者:米田 聡

小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。本企画では、ラズパイと連携して使用するマイコンボードを扱います。

シェルスクリプトマガジン Vol.83は以下のリンク先でご購入できます。

図A uf2conv.pyの修正箇所

(略)
    return resfile

def to_str(b):
#    return b.decode("utf-8")
    return b.decode("cp932")

def get_drives():
    drives = []
(略)

レポート1(Vol.83掲載)

投稿日:2023.02.15 | カテゴリー: コード

シェルスクリプトマガジン Vol.83(2023年4月号)で紹介したモーションキャプチャデバイス「mocopi」で作成したモーションキャプチャ画像(サイズ:12Mバイト)です。

この画像をクリック

シェルスクリプトマガジンvol.82 Web掲載記事まとめ

投稿日:2023.01.25 | カテゴリー: コード

004 レポート ポッキーでプログラミング
005 レポート WebAssembly対応Ruby 3.2.0
006 製品レビュー 制御機器「NFCタグ」
007 NEWS FLASH
008 特集1 LibreOfficeでPythonマクロ/荒川雄介 コード掲載
016 特集2 Dropboxを活用しよう/岡崎隆之、進藤麻礼子、井川恵理
028 緊急企画 Mastodonサーバーを構築しよう/麻生二郎
046 Raspberry Piを100%活用しよう/米田聡
050 行動経済学と心理学で円滑に業務を遂行/請園正敏
052 中小企業手作りIT化奮戦記/菅雄一
057 Hello Nogyo!
058 Pythonあれこれ/飯尾淳
066 法林浩之のFIGHTING TALKS/法林浩之
068 タイ語から分かる現地生活/つじみき
072 Emotet/桑原滝弥、イケヤシロウ
074 香川大学SLPからお届け!/谷﨑勇太 コード掲載
080 AWKでデジタル信号処理/斉藤博文 コード掲載
086 ユニケージ通信/田渕智也、高橋未来哉
090 Bash入門/大津真
098 Techパズル/gori.sh
099 コラム「ユニケージ流思考法」/シェル魔人

特集1 LibreOfficeでPythonマクロ(Vol.82記載)

投稿日:2023.01.25 | カテゴリー: コード

著者:荒川 雄介

オープンソースのオフィスソフト「LibreOffice」は、Pythonで記述されたマクロ(簡易スクリプト)を実行できます。しかしデフォルト状態では、Pythonマクロを作成しやすい環境が整備されていません。本特集では、開発環境を整備する手順を中心に、Pythonマクロ作成についての基礎知識を紹介します。使い慣れた言語を使って、LibreOfficeのさまざまな処理を効率化しましょう。

シェルスクリプトマガジン Vol.82は以下のリンク先でご購入できます。

図3 「Hello!」と書かれたダイアログを表示するPythonマクロのコード

from scriptforge import CreateScriptService
def hello():
  bas = CreateScriptService("Basic")
  bas.MsgBox("Hello!")
g_exportedScripts = (hello,)

図9 CSVファイルからCalcのシートにデータを読み込むPythonマクロのコード

from scriptforge import CreateScriptService
def load_csv():
  cal = CreateScriptService("Calc")
  cal.ImportFromCSVFile(r"C:\test.csv", "A1")
g_exportedScripts = (load_csv, )

図11 図9のコードを新規のCalcシートを開くように変更したもの

from scriptforge import CreateScriptService
def load_csv():
  ui = CreateScriptService("UI")
  cal = ui.CreateDocument("Calc")
  cal.ImportFromCSVFile(r"C:\test.csv", "A1")
g_exportedScripts = (load_csv, )

図12 文書ファイルがある場所からCSVファイルを読み込めるように図9のコードを変更したもの

from scriptforge import CreateScriptService
import uno
import os.path
def load_csv():
  doc  = XSCRIPTCONTEXT.getDocument()
  path = uno.fileUrlToSystemPath(doc.URL)
  cdir = os.path.dirname(path)
  cal = CreateScriptService("Calc")
  cal.ImportFromCSVFile(cdir + r"\test.csv", "A1")
g_exportedScripts = (load_csv, )

図13 データベースからデータを読み込んで集計/グラフ表示をするPythonマクロのコード

from scriptforge import CreateScriptService
def importdata():
  # データベースからデータを取得
  db=CreateScriptService('database',registrationname='Bibliography')
  sql='SELECT [Custom1] AS [Language],[Identifier] FROM [biblio] ORDER BY [Language] ASC'
  data=db.GetRows(sql,header=True)
  db.CloseDatabase()
  # 取得したデータをシートにコピー
  calc=CreateScriptService("Calc")
  datarange=calc.setArray('Sheet1.A1',data)
  # ピボットテーブルを作成して集計
  pivot=calc.CreatePivotTable('Pivot1',datarange,targetcell='D1',
  datafields='Identifier;Count',rowfields='Language',
  rowtotals=False,columntotals=False,filterbutton=False)
  # グラフを作成
  chart=calc.CreateChart('NumberByLanguage','Sheet1',pivot,rowheader=True,
                         columnheader=True)
  chart.ChartType, chart.Dim3D,chart.Legend='Pie',True,True
  chart.Resize(4000, 3000, 7000, 2500)
g_exportedScripts = (importdata, )

図18 図9のコードをイベント駆動できるように修正

from scriptforge import CreateScriptService
def load_csv(args=None):
  cal = CreateScriptService("Calc")
  cal.ImportFromCSVFile(r"C:\test.csv", "A1")
g_exportedScripts = (load_csv, )

図19 Calcのシート(Sheet1)の内容をすべて消去するPythonマクロのコード

from scriptforge import CreateScriptService
def clear(args=None):
  calc=CreateScriptService("Calc")
  calc.ClearAll('Sheet1.*')
g_exportedScripts = (clear, )

図20 外部ライブラリ(NumPy)を利用するythonマクロのサンプルコード

from scriptforge import CreateScriptService
import numpy as np
def test_numpy():
  A = np.array([1, 2, 3, 4, 5, 6]).reshape(2,3)
  B = A + 10
  cal = CreateScriptService("Calc")
  cal.setArray("A1", B.tolist())
g_exportedScripts = (test_numpy, )

香川大学SLPからお届け!(Vol.82掲載)

投稿日:2023.01.25 | カテゴリー: コード

著者:谷﨑 勇太

 最近、さまざまな場面で「VTuber」が活躍しています。VTuberとは、2D/3Dアバターの表情や身体をリアルタイムに動かしながら動画配信をする人、あるいはそのアバターのことです。多くの場合は、カメラで人の動きを検知し、それをトレースするようにアバターを動かしています。今回は、私がPythonを使って作成した簡易VTuberシステムについて紹介します。

シェルスクリプトマガジン Vol.82は以下のリンク先でご購入できます。

図1 Webカメラの映像を読み取るためのベースコード

import cv2
camera = cv2.VideoCapture(0)
while True:
  ret,image = camera.read()
  image = cv2.cvtColor(image,BGR2RGB)
  cv2.imshow("frame", image)
  if cv2.waitKey(1) & 0xFF == ord('q'):
    break
camera.release()
cv2.destroyAllWindows()

図2 感情を検出するためのコード

from paz.pipelines import DetectMiniXceptionFER
pipeline = DetectMiniXceptionFER([0.1, 0.1])
output = pipeline(image)
if len(output["boxes2D"]) == 1:
  emotion = output["boxes2D"][0].class_name

図3 左右の目の縦横比の平均値を算出する関数の定義コード

import cv2
from imutils import face_utils
import dlib
face_detector = dlib.get_frontal_face_detector()
face_parts_detector = \
  dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")
def face_landmark_find(img):
  eye = 10
  img_gry = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  faces = face_detector(img_gry, 1)
  for face in faces:
    landmark = face_parts_detector(img_gry, face)
    landmark = face_utils.shape_to_np(landmark)
    left_eye_ear = calc_ear(landmark[42:48])
    right_eye_ear = calc_ear(landmark[36:42])
    eye = (left_eye_ear + right_eye_ear) / 2.0
  return eye

図5 目の縦横比を計算する関数の定義コード

from scipy.spatial import distance
def calc_ear(eye):
  A = distance.euclidean(eye[1], eye[5])
  B = distance.euclidean(eye[2], eye[4])
  C = distance.euclidean(eye[0], eye[3])
  eye_ear = (A + B) / (2.0 * C)
  return eye_ear

図6 目の開閉を判定して処理を分岐させるコード

if w != 0:
  if eye < EYE_AR_THRESH:
    image = overlayImage(image,close,(x,y),(w,h))
  elif eye >= EYE_AR_THRESH:
    image = overlayImage(image,overlay,(x,y),(w,h))

図7 顔の座標と幅の情報を取得するコード

if len(output["boxes2D"]) ==1:
  x_min, y_min, x_max, y_max = output["boxes2D"][0].coordinates
  w,h = (x_max-x_min,y_max-y_min)
  x,y =x_min,y_min

図8 overlayImage ()関数の定義コード

import cv2
from PIL import Image
import numpy as np
def overlayImage(image, overlay, location, size):
  image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
  pil_image = Image.fromarray(image)
  pil_image = pil_image.convert('RGBA')
  overlay = cv2.cvtColor(overlay, cv2.COLOR_BGRA2RGBA)
  pil_overlay = Image.fromarray(overlay)
  pil_overlay = pil_overlay.convert('RGBA')
  pil_overlay = pil_overlay.resize(size)
  pil_tmp = Image.new('RGBA', pil_image.size, (255, 255, 255, 0))
  pil_tmp.paste(pil_overlay, location, pil_overlay)
  result_image = Image.alpha_composite(pil_image, pil_tmp)
  return cv2.cvtColor(np.asarray(result_image), cv2.COLOR_RGBA2BGRA)

図9 insert()関数の定義コード

import os
def insert():
  r = os.path.exists('user.txt')
  if r == False:
    print("初期設定を行います")
    print("目を閉じてください")
    setting()
    eye_int()
  elif r == True:
    count = len(open('user.txt').readlines())
    if count == 0:
      print("初期設定を行います")
      print("目を閉じてください")
      setting()
      eye_int()
    elif count == 1:
      eye_int()

図10 setting ()関数の定義コード

import cv2
import math
def setting():
  f = open('user.txt', 'w')
  count = 0
  eye_sum=0
  while True:
    ret,rgb = cap.read()
    eye = face_landmark_find(rgb)
    if ret == True:
      if eye != 10:
        count +=1
        eye_sum += eye
    if(count > 50):
      x=eye_sum/50+0.01
      break
  cap.release()
  cv2.destroyAllWindows()
  x = math.floor(x*100)/100
  f.write(str(x)+'\n')
  f.close()

図11 eye_int()関数の定義コード

def eye_int():
  f = open('user.txt','r+')
  lins = f.readlines()
  print('推奨する設定値は'+lins[0]+'です')
  tmp = input('設定値を入力してください:')
  f.write(tmp)

AWKでデジタル信号処理(Vol.82掲載)

投稿日:2023.01.25 | カテゴリー: コード

著者:斉藤博文

プログラミング言語「AWK」は、データストリーム(データの流れ)を逐次処理するのに適しています。本連載では、電子回路の分野でその特徴を生かし、シェルスクリプトを組み合わせてデジタル信号を処理します。第4回は「微分フィルタ」を使って目的の波形を取り出す方法を紹介します。

シェルスクリプトマガジン Vol.82は以下のリンク先でご購入できます。

図10 微分フィルタの差分方程式のAWKプログラム(dif.awk)

#! /usr/bin/gawk -f

BEGIN {
    # define number of order
    num_ord = num_ord ? num_ord : 31;

    # define length of ring buffers
    len_data_raw = num_ord + 1;
    len_data_dif = 1;

    # initialize ring buffers
    for (i = 0; i < len_data_raw; i++) {
        arr_data_raw[i] = 0.0;
    }
    for (i = 0; i < len_data_dif; i++) {
        arr_data_dif[i] = 0.0;
    }

    # initialize index of ring buffers
    idx_data_raw = 0;
    idx_data_dif = 0;

    # initialize number of data
    num_data_raw = 0;
}

{
    # add number of data
    num_data_raw++;

    # update index of ring buffers (write pointers)
    idx_data_raw = num_data_raw % len_data_raw;
    idx_data_dif = num_data_raw % len_data_dif;

    # clear number of data
    if (idx_data_raw == 0 && idx_data_dif == 0) {
        num_data_raw = 0;
    }

    # store input raw data
    val_data_raw = $0;
    arr_data_raw[idx_data_raw] = val_data_raw;

    # apply differential filter
    arr_data_dif[idx_data_dif] = dif(arr_data_raw, idx_data_raw, num_ord, len_data_raw);

    # print results
    print arr_data_dif[idx_data_dif];
}

# get value of ring buffer
function get_buffer(arr, idx, len) {
    if (idx < 0) {
        return arr[idx + len];
    }

    return arr[idx];
}

# differential filter
function dif(arr_x, idx_x, ord, len_x,      _ret, _half, i) {
    _ret = 0.0;
    _half = int((ord - 1) / 2);

    for (i = 0; i < ord; i++) {
        _ret += (_half - i) * get_buffer(arr_x, idx_x - i, len_x);
    }

    return _ret;
}

図12 群遅延を考慮したAWKプログラム(dif_gd.awk)

#! /usr/bin/gawk -f

BEGIN {
    # define number of order
    num_ord = num_ord ? num_ord : 31;

    # define group delay
    val_gd = int((num_ord - 1) / 2);

    # define length of ring buffers
    len_data_raw = num_ord + 1;
    len_data_dif = 1;

    # initialize ring buffers
    for (i = 0; i < len_data_raw; i++) {
        arr_data_raw[i] = 0.0;
    }
    for (i = 0; i < len_data_dif; i++) {
        arr_data_dif[i] = 0.0;
    }

    # initialize index of ring buffers
    idx_data_raw = 0;
    idx_data_dif = 0;

    # initialize number of data
    num_data_raw = 0;
}

{
    # add number of data
    num_data_raw++;

    # update index of ring buffers (write pointers)
    idx_data_raw = num_data_raw % len_data_raw;
    idx_data_dif = num_data_raw % len_data_dif;

    # clear number of data
    if (idx_data_raw == 0 && idx_data_dif == 0) {
        num_data_raw = 0;
    }

    # store input raw data
    val_data_raw = $0;
    arr_data_raw[idx_data_raw] = val_data_raw;

    # apply differential filter
    arr_data_dif[idx_data_dif] = dif(arr_data_raw, idx_data_raw, num_ord, len_data_raw);

    # print results
    print get_buffer(arr_data_raw, idx_data_raw - val_gd, len_data_raw), arr_data_dif[idx_data_dif];
}

# get value of ring buffer
function get_buffer(arr, idx, len) {
    if (idx < 0) {
        return arr[idx + len];
    }

    return arr[idx];
}

# differential filter
function dif(arr_x, idx_x, ord, len_x,      _ret, _half, _gain i) {
    _ret = 0.0;
    _half = int((ord - 1) / 2);

    _gain = 0.0;
    for (i = 0; i < ord; i++) {
        _gain += (_half - i)^2;
    }

    for (i = 0; i < ord; i++) {
        _ret += (_half - i) * get_buffer(arr_x, idx_x - i, len_x);
    }

    return _ret / _gain;
}

図14 係数の補正を加えた関数dif()

# differential filter
function dif(arr_x, idx_x, ord, len_x,      _ret, _half, _gain i) {
    _ret = 0.0;
    _half = int((ord - 1) / 2);

    _gain = 0.0;
    for (i = 0; i < ord; i++) {
        _gain += (_half - i)^2;
    }

    for (i = 0; i < ord; i++) {
        _ret += (_half - i) * get_buffer(arr_x, idx_x - i, len_x);
    }

    return _ret / _gain;
}

Vol.82 補足情報

投稿日:2023.01.19 | カテゴリー: コード

Pythonあれこれ

 記事中で使用するデータファイルは以下のWebページから入手できます。

https://github.com/shellscript-magazine/python_this_and_that/releases/tag/ver2.0

情報は随時更新致します。

特集1 本格的なホームサーバーを構築(Vol.81記載)

投稿日:2022.11.25 | カテゴリー: コード

著者:麻生 二郎

小型コンピュータボードの最上位モデルである「Raspberry Pi 4 Model B」の4G/8Gバイト版と、人気のLinuxディストリビューション「Ubuntu」のサーバー版を組み合わせて、本格的なサーバーを構築しましょう。本特集では、サーバーの可用性や信頼性を向上する方法を紹介します。

シェルスクリプトマガジン Vol.81は以下のリンク先でご購入できます。

図A3 ラズパイサーバーの初期設定(ubuntu_init1.sh)

#!/bin/sh

##日本のタイムゾーン設定
sudo timedatectl set-timezone Asia/Tokyo

##全ソフトウエア更新
sudo apt update
sudo apt -y upgrade
sudo apt -y autoremove
sudo apt clean

##ファームウエアアップデート
sudo apt -y install rpi-eeprom
sudo rpi-eeprom-update

##完了後の再起動
read -p "再起動しますか [y/N]:" YN
if [ " $YN" = " y" ] || [ " $YN" = " Y" ]; then
  sudo reboot
fi

図A4 ラズパイサーバーの初期設定(ubuntu_init2.sh)

#!/bin/sh

##固定IPアドレスとルーターのIPアドレス
IP_ADDRESS="192.168.10.100"
ROUTER_IP="192.168.10.1"

##旧設定バックアップ
mkdir -p ~/old_settings
sudo mv /etc/netplan/50-cloud-init.yaml ~/old_settings/.

##新ネットワーク設定作成
cat << EOF | sudo tee /etc/netplan/50-cloud-init.yaml > /dev/null
network:
  ethernets:
    eth0:
      dhcp4: false
      addresses: [ip_address/24]
      gateway4: router_ip
      nameservers:
        addresses: [8.8.8.8]
  version: 2
EOF
sudo sed -i -e "s%ip_address%$IP_ADDRESS%" /etc/netplan/50-cloud-init.yaml
sudo sed -i -e "s%router_ip%$ROUTER_ip%" /etc/netplan/50-cloud-init.yaml

##ネットワーク設定反映
sudo netplan apply

Raspberry Piを100%活用しよう(Vol.81掲載)

投稿日:2022.11.25 | カテゴリー: コード

著者:米田 聡

小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。第14回は、高品質なアナログ音声を出力する拡張基板を扱います。

シェルスクリプトマガジン Vol.81は以下のリンク先でご購入できます。

図7 MP3形式の音楽ファイルを再生するPythonプログラム(player.py)

import time
from pydub import AudioSegment
from pydub.playback import _play_with_simpleaudio

audio = AudioSegment.from_file("sample.mp3", "mp3")
play = _play_with_simpleaudio(audio)

try:
    while True:
        time.sleep(1)

except KeyboardInterrupt:
    pass

play.stop()

Pythonあれこれ(Vol.81掲載)

投稿日:2022.11.25 | カテゴリー: コード

筆者:飯尾 淳

本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温
かく見守ってください。皆さんと共に勉強していきましょう。第11回では、永続的な非同期通信を簡単に実現できる「WebSocket」というプロトコルを利用するシンプルなチャットアプリの作成に挑戦しま
す。

シェルスクリプトマガジン Vol.81は以下のリンク先でご購入できます。

図1 エコーサーバーを実現するコード

import asyncio
import websockets

async def echo(soc):
  async for msg in soc:
    print(f'RECV: {msg}')
    await soc.send(msg)

async def main():
  async with websockets.serve(echo, port=8765, ping_timeout=None):
    await asyncio.Future()

asyncio.run(main())

図2 エコーサーバーに接続するクライアントのコード

import asyncio
import websockets

async def client():
  uri = "ws://localhost:8765/"
  async with websockets.connect(uri, ping_timeout=None) as soc:
    while True:
      msg = input('> ')
      await soc.send(msg)
      msg = await soc.recv()
      print(f'< {msg}')

asyncio.run(client())

図4 チャットサーバーのコード

import asyncio
import websockets

clients = {}

async def echo(soc):
  async for msg in soc:
    if not (soc in clients):
      clients[soc] = msg
      print(f'{msg} is registered')
      for s in clients:
        await s.send(f'{clients[soc]} が参加しました')
    else:
      print(f'RECV: {msg}')
      for s in clients:
        await s.send(f'{clients[soc]}: {msg}')

async def main():
  async with websockets.serve(echo, port=8765, ping_timeout=None):
    await asyncio.Future()

asyncio.run(main())

図5 チャットクライアントのHTMLコード

<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>A Simple BBS</title>
  <meta name="description" content="WebSockets TEST">
  <meta name="author" content="Jun IIO">
  <script src="jquery-3.6.1.min.js"></script>
  <script src="scripts.js"></script>
</head>
<body>
  <h1>シンプル・チャット・システム</h1>
  <div>
    名前:
    <input id="regname" type="text">
    <button id="register">登録</button>
  </div>
  <div>
    <textarea id="msgarea" cols=80 rows=10
              readonly disabled="disabled"></textarea>
  </div>
  <div>
    <input id="chatmsg" type="text" size=80 disabled="disabled">
    <button id="send" disabled="disabled">送信</button>
  </div>
</body>
</html>

図6 チャットクライアントのJavaScriptコード

$(function(){
  let socket = new WebSocket("ws://localhost:8765/");

  $("#register").on("click", function(event) {
    name = $("#regname").val();
    if (name != "") {
      socket.send(name);
      $("#register").prop("disabled", true);
      $("#regname").prop("disabled", true);
      $("#msgarea").prop("disabled", false);
      $("#chatmsg").prop("disabled", false);
      $("#send").prop("disabled", false);
    };
  });

  socket.onmessage = function(event) {
    text = $("#msgarea").val();
    $("#msgarea").val(text + event.data + "\n");
  };

  $("#send").on("click", function(event) {
    msg = $("#chatmsg").val();
    if (msg != "") {
      socket.send(msg);
      $("#chatmsg").val("");
    };
  });
});

香川大学SLPからお届け!(Vol.81掲載)

投稿日:2022.11.25 | カテゴリー: コード

著者:永田 歩

今回は、米Google社が提供するメディアデータ向けの機械学習ライブラリ「MediaPiPe」を用いて、動画内にある人の顔を検出し、そのデータを基に3D CGアニメーションを生成するプログラムを紹介します。AIを3D CGに利用する試みの一つとして参考にしてください。

シェルスクリプトマガジン Vol.81は以下のリンク先でご購入できます。

図1 動画をフレームに分割するプログラム「video2img.py」のコード

import cv2 as cv
import os

video_path = './video/test_video.mp4'
dir_path = './image'
basename = 'img_frame'
ext = 'jpg'
# 動画を読み込んで存在しなければ終了
cap = cv.VideoCapture(video_path)
if not cap.isOpened():
  exit()
os.makedirs(dir_path, exist_ok=True)
base_path = os.path.join(dir_path, basename)
# 画像保存名のためにフレームの桁数をカウント
digit = len(str(int(cap.get(cv.CAP_PROP_FRAME_COUNT))))
n = 0
while True:
  ret, frame = cap.read()
  if ret:
    # 画像の保存
    cv.imwrite('{}_{}.{}'.format(base_path, str(n).zfill(digit), ext), frame)
    n += 1
  else:
    exit()

図2 顔のランドマーク座標を出力するコード「images2npy.py」のメイン部分

import cv2 as cv
import mediapipe as mp
import os
import glob
import numpy as np

# 使用する画像のパス名を取得してリスト化
resource_dir = r'./image'
file_list = glob.glob(os.path.join(resource_dir, "*.jpg"))
xyzval = []
for file_path in file_list:
  # 画像を読み込む
  image = cv.imread(file_path)
  xyzval.append(get_landmark(image, file_path))
# 3次元numpy配列のバイナリファイルとして保存
np.save('result/np_save', xyzval)

図3 get_landmark()関数の定義コード

def get_landmark(image, file_path):
  # 顔のランドマークを取得する準備
  mp_face_mesh = mp.solutions.face_mesh
  face_mesh = mp_face_mesh.FaceMesh(
    static_image_mode=True,
    max_num_faces=1,
    min_detection_confidence=0.5)
  rgb_image = cv.cvtColor(image, cv.COLOR_BGR2RGB)
  # ランドマーク検出
  results = face_mesh.process(rgb_image)
  # 顔が検出されなければ終了
  if not results.multi_face_landmarks:
    exit()
  face_landmarks = results.multi_face_landmarks[0]
  file_path = os.path.splitext(file_path)[0]
  file_name = file_path.split('/')[-1]
  # 保存先のフォルダ作成
  result_txt_dir = 'result/txt'
  os.makedirs(result_txt_dir, exist_ok=True)
  txtfile_path = result_txt_dir+'/'+file_name+'.txt'
  # テキストファイルにRawデータを書き込む
  f = open(txtfile_path, 'w')
  f.write(str(face_landmarks))
  f.close()
  make_landmarkimg(image, face_landmarks,
                   file_name, mp_face_mesh)
  face_mesh.close()
  return get_original_scalexyz(image, txtfile_path)

図5 get_original_scalexyz()関数の定義コード

def get_original_scalexyz(image, txtfile_path):
  height, width = image.shape[:2]
  vers = []
  i = 0
  with open(txtfile_path) as ft:
    lines = ft.read()
    for l in lines.split("\n"):
      if i%5 == 0 or i%5 == 4:
        i += 1
        continue
      elif i%5 == 1:
        tmp = l.split()  
        x = f'{float(tmp[1]) *width:.5f}'
      elif i%5 == 2:
        tmp = l.split()
        y = f'{float(tmp[1]) *height:.5f}'
      else:
        tmp = l.split()
        z = f'{float(tmp[1]) *width:.5f}'
        vers.append([float(x), float(y), float(z)])
      i += 1
  return vers

図6 make_landmarkimg()関数の定義コード

def make_landmarkimg(image, face_landmarks, file_name, mp_face_mesh):
  result_image_dir = 'result/image'
  os.makedirs(result_image_dir, exist_ok=True)
  # 顔のランドマークを画像上に出力する準備
  mp_drawing = mp.solutions.drawing_utils
  drawing_spec = mp_drawing.DrawingSpec(thickness=1, circle_radius=1)
  # 元画像のコピーを作成
  annotated_image = image.copy()
  # 顔のランドマークを描画した画像を生成
  mp_drawing.draw_landmarks(
    image=annotated_image,
    landmark_list=face_landmarks,
    connections=mp_face_mesh.FACEMESH_TESSELATION,
    landmark_drawing_spec=drawing_spec,
    connection_drawing_spec=drawing_spec)
  cv.imwrite(result_image_dir+'/'+file_name+'.png', annotated_image)

図8 3D CGアニメーションを作成するコード「npy2blender.py」のメイン部分

import numpy as np
import bpy
filepath = r'3次元numpy配列のバイナリファイルのパス名'
# 例 → r'C:/Users/user1/python/shellmag/result/np_save.npy'
xyzval = np.load(filepath)
ver_count = len(xyzval[0])
frame_count = len(xyzval)
generate_ver(ver_count)
move_ver_anyframe(xyzval, frame_count)

図9 generate_ver()関数の定義コード

def generate_ver(ver_count):
  # 点群オブジェクトの生成
  bpy.data.meshes.new(name='shellmag_Mesh')
  bpy.data.objects.new(name='shellmag_Obj',
                       object_data=bpy.data.meshes['shellmag_Mesh'])
  bpy.context.scene.collection.objects.link(bpy.data.objects['shellmag_Obj'])
  Obj = bpy.data.objects['shellmag_Obj'].data
  Obj.vertices.add(ver_count)    
  return

図10 move_ver_anyframe()関数の定義コード

def move_ver_anyframe(xyzval, frame_count):
  ob = bpy.data.objects['shellmag_Obj'].data
  for frame_num in range(frame_count):
    i = 0
    # XYZ座標を各点ごとに定義してフレームとして保存
    for p in xyzval[frame_num]:
      X = p[0]
      Y = p[1]
      Z = p[2]
      ver = ob.vertices[i]
      ver.co.x = X
      ver.co.y = Y
      ver.co.z = Z
      i += 1
      ver.keyframe_insert('co',index = -1,frame = frame_num) 
  return

AWKでデジタル信号処理(Vol.81掲載)

投稿日:2022.11.25 | カテゴリー: コード

著者:斉藤 博文

プログラミング言語「AWK」は、データストリーム(データの流れ)を逐次処理するのに適しています。本連載では、電子回路の分野でその特徴を生かし、シェルスクリプトを組み合わせてデジタル信号を処理します。第3回は低周波成分を減らす「ハイパスフィルタ」について解説します。

シェルスクリプトマガジン Vol.81は以下のリンク先でご購入できます。

図7 ハイパスフィルタの移動平均プログラム(hpf.awk)

#! /usr/bin/gawk -f

BEGIN {
    # define number of order
    num_ord = num_ord ? num_ord : 255;

    # define length of ring buffers
    len_data_raw = num_ord + 1;
    len_data_hpf = 2;

    # initialize ring buffers
    for (i = 0; i < len_data_raw; i++) {
        arr_data_raw[i] = 0.0;
    }
    for (i = 0; i < len_data_hpf; i++) {
        arr_data_hpf[i] = 0.0;
    }

    # initialize index of ring buffers
    idx_data_raw = 0;
    idx_data_hpf = 0;

    # initialize number of data
    num_data_raw = 0;
}

{
    # add number of data
    num_data_raw++;

    # update index of ring buffers (write pointers)
    idx_data_raw = num_data_raw % len_data_raw;
    idx_data_hpf = num_data_raw % len_data_hpf;

    # clear number of data
    if (idx_data_raw == 0 && idx_data_hpf == 0) {
        num_data_raw = 0;
    }

    # store input raw data
    val_data_raw = $0;
    arr_data_raw[idx_data_raw] = val_data_raw;

    # apply high pass filter
    arr_data_hpf[idx_data_hpf] = hpf( \
            arr_data_raw, arr_data_hpf,
            idx_data_raw, idx_data_hpf,
            num_ord,
            len_data_raw, len_data_hpf);

    # print results
    print arr_data_hpf[idx_data_hpf];
}

# get value of ring buffer
function get_buffer(arr, idx, len) {
    if (idx < 0) {
        return arr[idx + len];
    }

    return arr[idx];
}

# high pass filter
function hpf(arr_x, arr_y, idx_x, idx_y, ord, len_x, len_y,       _ret, _gain) {
    _ret = 0.0;
    _gain = 1.0 / ord;

    _ret += get_buffer(arr_y, idx_y - 1, len_y);
    _ret -= _gain * get_buffer(arr_x, idx_x, len_x);
    _ret += get_buffer(arr_x, idx_x - int((ord - 1) / 2), len_x);
    _ret -= get_buffer(arr_x, idx_x - int((ord + 1) / 2), len_x);
    _ret += _gain * get_buffer(arr_x, idx_x - ord, len_x);

    return _ret;
}

図12 群遅延を補正したプログラム(hpf_gd.awk)

#! /usr/bin/gawk -f

BEGIN {
    # define number of order
    num_ord = num_ord ? num_ord : 255;

    # define group delay
    val_gd = int((num_ord - 1) / 2);

    # define length of ring buffers
    len_data_raw = num_ord + 1;
    len_data_hpf = val_gd + 1;

    # initialize ring buffers
    for (i = 0; i < len_data_raw; i++) {
        arr_data_raw[i] = 0.0;
    }
    for (i = 0; i < len_data_hpf; i++) {
        arr_data_hpf[i] = 0.0;
    }

    # initialize index of ring buffers
    idx_data_raw = 0;
    idx_data_hpf = 0;

    # initialize number of data
    num_data_raw = 0;
}

{
    # add number of data
    num_data_raw++;

    # update index of ring buffers (write pointers)
    idx_data_raw = num_data_raw % len_data_raw;
    idx_data_hpf = num_data_raw % len_data_hpf;

    # clear number of data
    if (idx_data_raw == 0 && idx_data_hpf == 0) {
        num_data_raw = 0;
    }

    # store input raw data
    val_data_raw = $0;
    arr_data_raw[idx_data_raw] = val_data_raw;

    # apply high pass filter
    arr_data_hpf[idx_data_hpf] = hpf( \
            arr_data_raw, arr_data_hpf,
            idx_data_raw, idx_data_hpf,
            num_ord,
            len_data_raw, len_data_hpf);

    # print results
    print get_buffer(arr_data_raw, idx_data_raw - val_gd, len_data_raw), arr_data_hpf[idx_data_hpf];
}

# get value of ring buffer
function get_buffer(arr, idx, len) {
    if (idx < 0) {
        return arr[idx + len];
    }

    return arr[idx];
}

# high pass filter
function hpf(arr_x, arr_y, idx_x, idx_y, ord, len_x, len_y,       _ret, _gain) {
    _ret = 0.0;
    _gain = 1.0 / ord;

    _ret += get_buffer(arr_y, idx_y - 1, len_y);
    _ret -= _gain * get_buffer(arr_x, idx_x, len_x);
    _ret += get_buffer(arr_x, idx_x - int((ord - 1) / 2), len_x);
    _ret -= get_buffer(arr_x, idx_x - int((ord + 1) / 2), len_x);
    _ret += _gain * get_buffer(arr_x, idx_x - ord, len_x);

    return _ret;
}

Vol.81 補足情報

投稿日:2022.11.25 | カテゴリー: コード

執筆・編集後記

p.103の執筆・編集後記のページが広告になっていました。以下が掲載する予定だった執筆・編集後記です。Kindle版やPDF版は差し替えています。お詫びして訂正いたします。

情報は随時更新致します。

シェルスクリプトマガジンvol.81 Web掲載記事まとめ

投稿日:2022.11.25 | カテゴリー: コード

004 レポート Windows 11が動くVirtualBox
005 レポート Webブラウザ動作のPostgreSQL
006 製品レビュー 玩具「coemo(コエモ)」
007 NEWS FLASH
008 特集1 本格的なホームサーバーを構築/麻生二郎 コード掲載
018 特集2 Google AppSheet入門/伊藤勇斗、石野耀久、江口隆司
038 特集3 さくらのクラウド/前佛雅人
050 特別企画 MySQL HeatWave Database Serviceの新機能/生駒眞知子
057 Hello Nogyo!
058 Raspberry Piを100%活用しよう/米田聡 コード掲載
062 Pythonあれこれ/飯尾淳 コード掲載
066 法林浩之のFIGHTING TALKS/法林浩之
068 中小企業手作りIT化奮戦記/菅雄一
074 メタバース/桑原滝弥、イケヤシロウ
076 タイ語から分かる現地生活/つじみき
080 香川大学SLPからお届け!/永田歩 コード掲載
086 AWKでデジタル信号処理/斉藤博文 コード掲載
094 ユニケージ通信/田渕智也、高橋未来哉
098 Bash入門/大津真
104 Techパズル/gori.sh
105 コラム「ユニケージの学術論文が出ました」/シェル魔人

シェルスクリプトマガジンvol.80 Web掲載記事まとめ

投稿日:2022.09.25 | カテゴリー: コード

004 レポート 6GHz帯の無線LAN国内解禁
005 レポート Zen 4採用のRyzenプロセッサ出荷
006 製品レビュー パソコン「LAVIE GX(GX750/EAB)」
007 NEWS FLASH
008 特集1 Red Hat Enterprise Linux 9/森若和雄
022 特集2 Linux初心者テストにチャレンジ!/長原宏治 コード掲載
042 緊急特集 チャットサービスを立ち上げよう/麻生二郎
058 Raspberry Piを100%活用しよう/米田聡 コード掲載
062 Pythonあれこれ/飯尾淳 コード掲載
068 NFT/桑原滝弥、イケヤシロウ
070 中小企業手作りIT化奮戦記/菅雄一
075 Hello Nogyo!
076 法林浩之のFIGHTING TALKS/法林浩之
078 香川大学SLPからお届け!/佐々木龍之介 コード掲載
082 タイ語から分かる現地生活/つじみき
088 AWKでデジタル信号処理/斉藤博文 コード掲載
096 Bash入門/大津真
104 Techパズル/gori.sh
105 コラム「ユニケージ黎明(れいめい)期」/シェル魔人

特集1 Linux初心者テストにチャレンジ!(Vol.80記載)

投稿日:2022.09.25 | カテゴリー: コード

著者:長原 宏治

Linuxエンジニアの認定試験としては「LPIC」が有名ですが、より幅広くLinux初学者を対象とする「Linux Essentials」という認定試験が2020年から開始されています。本特集では出題範囲に対する理解を確認できる例題を挙げながら、Linux Essentials試験の内容を紹介します。つまずきがちな部分について問う例題を盛り込みましたので、腕に覚えがある人もぜひチャレンジしてみてください。

シェルスクリプトマガジン Vol.80は以下のリンク先でご購入できます。

Part3 演習問題集の解答と解説

図1 問3-3-1の解答例

#!/bin/bash
codefile=data.csv
prefecture=$1
grep $prefecture $codefile | sort -t , -k 5 | cut -d , -f 3 | tail -n +2

図2 問3-3-2の解答例

#!/bin/bash
codefile=data.csv
if [ $# -le 0 ]
then
  echo Usage: $0 PREFACTURE... >&2
  exit 1
fi
for prefecture in $*
do
  echo ${prefecture}:
  grep $prefecture $codefile | sort -t , -k 5 | cut -d, -f 3 | tail -n +2
done
exit 0

Raspberry Piを100%活用しよう(Vol.80掲載)

投稿日:2022.09.25 | カテゴリー: コード

著者:米田 聡

小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。第13回は、照明用などの高輝度LEDの明るさを制御できる拡張基板を扱います。

シェルスクリプトマガジン Vol.80は以下のリンク先でご購入できます。

図5 LEDの明るさを制御するプログラム(ADRSZSW.py)

import RPi.GPIO as GPIO
import smbus
import time

ADDR=0x56   # I2Cアドレス
SW=5        # スイッチのGPIO
bus = None  

value: int = 0x0    # 明るさの値

# スイッチのコールバック関数
def switch(ch):
        global value
        value += 0x05
        # デューティ比を書き込む
        bus.write_byte_data(ADDR, 0x01, (value & 0xFF))
        # デューティー比を読み出す
        current = bus.read_byte_data(ADDR,0)
        print(current)

if __name__ == "__main__":
        bus = smbus.SMBus(1)
        bus.write_byte_data(ADDR, 0x01, 0)

        # GPIOの設定
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(SW, GPIO.IN)
        GPIO.add_event_detect(SW, GPIO.FALLING, callback=switch, bouncetime=200)

        try:
                while True:
                        time.sleep(30)
        except KeyboardInterrupt:
                pass
        GPIO.remove_event_detect(SW)
        GPIO.cleanup(SW)

Pythonあれこれ(Vol.80掲載)

投稿日:2022.09.25 | カテゴリー: コード

著者:飯尾 淳

本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温
かく見守ってください。皆さんと共に勉強していきましょう。第10回は、データを可視化する例として、プログラムの対話型実行環境「Jupyter Notebook」をローカル環境で動かして簡単な地理情報を表示させてみます。

シェルスクリプトマガジン Vol.80は以下のリンク先でご購入できます。

図7 町丁目データを地図上に重ねて表示するためのコード

for _, r in df.iterrows():
    sim_geo = gpd.GeoSeries(r['geometry'])
    geo_j = sim_geo.to_json()
    geo_j = folium.GeoJson(data=geo_j,
      style_function=lambda x: {'fillColor': 'grey', 'color': 'grey', 
        'weight': 0.5, 'fill_opacity': 0.3, 'line_opacity': 0.1 })
    folium.Popup(r['Name']).add_to(geo_j)
    geo_j.add_to(m)
m

図10 駅の位置と乗降客数のデータをまとめるためのコード

df2 = pd.merge(df_stations.drop('Description', axis=1), \
                df_passengers, on='Name')
df2.head()

図12 駅の位置を地図に表示するためのコード(その1)

for _, row in df2.iterrows():
    folium.Marker(
        location=[row['geometry'].y, row['geometry'].x],
        popup=row['Name']
    ).add_to(m)
m

図14 駅の位置を地図に表示するためのコード(その2)

m = folium.Map(location=[35.65, 139.34], \
       zoom_start=12, tiles=‘openstreetmap’)

for _, r in df.iterrows():
    sim_geo = gpd.GeoSeries(r['geometry'])
    geo_j = sim_geo.to_json()
    geo_j = folium.GeoJson(data=geo_j,
      style_function=lambda x: {'fillColor': 'grey', 'color': 'grey', 
        'weight': 0.5, 'fill_opacity': 0.3, 'line_opacity': 0.1 })
    folium.Popup(r['Name']).add_to(geo_j)
    geo_j.add_to(m)

for _, row in df2.iterrows():
    folium.CircleMarker(
        location=[row['geometry'].y, row['geometry'].x],
        radius=3,
        color='red',
        fill_color='red',
        weight=2
    ).add_to(m)
m

図16 「緯度」「経度」「乗降客数」のカラムを持つデータフレームを作成するためのコード

df3 = pd.DataFrame(data={'Lat': df2[‘geometry'].y,
                         'Lng': df2['geometry'].x, 
                         'Passengers': df2['Passengers']})
 
df3.head()

香川大学SLPからお届け!(Vol.80掲載)

投稿日:2022.09.25 | カテゴリー: コード

著者:佐々木 龍之介

今回は、数値解析アルゴリズムの一つである「ニュートン法」を紹介します。ニュートン法を利用すると、反復計算によって方程式の解を近似的に求められます。C言語を使ったサンプルプログラムを挙げながら、同手法について簡単に解説します。

シェルスクリプトマガジン Vol.80は以下のリンク先でご購入できます。

図3 「x2 – 1 = 0」という方程式の解を求めるCプログラムの例

#include <stdio.h>
#include <math.h>
#define e 0.000001

double f(double x) {
    return x*x - 1;
}

double df(double x) {
    return 2*x;
}

int main() {
    double x1 = 2.5, x2;
    while(1) {
        x2 = x1 - f(x1)/df(x1);
        if(fabs(f(x2)) < e) {
            break;
        }
        x1 = x2;
    }
    printf("%f\n", x2);
}

図4 「x2 – 1 = 0」という方程式の解を求めるCプログラムの改良版

#include <stdio.h>
#include <math.h>
#define e 0.000001

double f(double x) {
    return x*x - 1;
}

double df(double x) {
    return 2*x;
}

int main() {
    double x1 = 2.5, x2;
    while(1) {
        x2 = x1 - f(x1)/df(x1);
        if(fabs(x2 - x1) < e) {
            break;
        }
        x1 = x2;
    }
    printf("%f\n", x2);
}

図5 √2の近似解を求めるCプログラムの例

#include <stdio.h>
#include <math.h>
#define e 0.000001

double f(double x) {
    return x*x - 2;
}

double df(double x) {
    return 2*x;
}

double g(double x) {
    return x - f(x) / df(x);
}

int main() {
    double x1 = 2.0, x2;
    while(1){
        x2 = g(x1);
        if(fabs(x2 - x1) < e) {
            break;
        }
        x1 = x2;
        printf("%f\n", x2);
    }

    printf("√2= %f\n", x2);
}

AWKでデジタル信号処理(Vol.80掲載)

投稿日:2022.09.25 | カテゴリー: コード

著者:斉藤 博文

プログラミング言語「AWK」は、データストリーム(データの流れ)を逐次処理するのに適しています。本連載では、電子回路の分野でその特徴を生かし、シェルスクリプトを組み合わせてデジタル信号を処理します。第2回は、高周波ノイズを除去する「ローパスフィルタ」について解説します。

シェルスクリプトマガジン Vol.80は以下のリンク先でご購入できます。

図6 移動平均のプログラム(lpf.awk)

#! /usr/bin/gawk -f

BEGIN {
    # define number of order
    num_ord = num_ord ? num_ord : 7;

    # define length of ring buffers
    len_data_raw = num_ord + 1;
    len_data_lpf = 2;

    # initialize ring buffers
    for (i = 0; i < len_data_raw; i++) {
        arr_data_raw[i] = 0.0;
    }
    for (i = 0; i < len_data_lpf; i++) {
        arr_data_lpf[i] = 0.0;
    }

    # initialize index of ring buffers
    idx_data_raw = 0;
    idx_data_lpf = 0;

    # initialize number of data
    num_data_raw = 0;
}

{
    # add number of data
    num_data_raw++;

    # update index of ring buffers (write pointers)
    idx_data_raw = num_data_raw % len_data_raw;
    idx_data_lpf = num_data_raw % len_data_lpf;

    # clear number of data
    if (idx_data_raw == 0 && idx_data_lpf == 0) {
        num_data_raw = 0;
    }

    # store input raw data
    val_data_raw = $0;
    arr_data_raw[idx_data_raw] = val_data_raw;

    # apply low pass filter
    arr_data_lpf[idx_data_lpf] = lpf( \
            arr_data_raw, arr_data_lpf,
            idx_data_raw, idx_data_lpf,
            num_ord,
            len_data_raw, len_data_lpf);

    # print results
    print arr_data_lpf[idx_data_lpf];
}

図10 ずれ補正を加えた移動平均のプログラム(lpf_gd.awk)

#! /usr/bin/gawk -f

BEGIN {
    # define number of order
    num_ord = num_ord ? num_ord : 7;

    # define group delay
    val_gd = int((num_ord - 1) / 2);

    # define length of ring buffers
    len_data_raw = num_ord + 1;
    len_data_lpf = val_gd + 1;

    # initialize ring buffers
    for (i = 0; i < len_data_raw; i++) {
        arr_data_raw[i] = 0.0;
    }
    for (i = 0; i < len_data_lpf; i++) {
        arr_data_lpf[i] = 0.0;
    }

    # initialize index of ring buffers
    idx_data_raw = 0;
    idx_data_lpf = 0;

    # initialize number of data
    num_data_raw = 0;
}

{
    # add number of data
    num_data_raw++;

    # update index of ring buffers (write pointers)
    idx_data_raw = num_data_raw % len_data_raw;
    idx_data_lpf = num_data_raw % len_data_lpf;

    # clear number of data
    if (idx_data_raw == 0 && idx_data_lpf == 0) {
        num_data_raw = 0;
    }

    # store input raw data
    val_data_raw = $0;
    arr_data_raw[idx_data_raw] = val_data_raw;

    # apply low pass filter
    arr_data_lpf[idx_data_lpf] = lpf( \
            arr_data_raw, arr_data_lpf,
            idx_data_raw, idx_data_lpf,
            num_ord,
            len_data_raw, len_data_lpf);

    # print results
    print get_buffer(arr_data_raw, idx_data_raw - val_gd, len_data_raw), arr_data_lpf[idx_data_lpf];
}

# get value of ring buffer
function get_buffer(arr, idx, len) {
    if (idx < 0) {
        return arr[idx + len];
    }

    return arr[idx];
}

# low pass filter
function lpf(arr_x, arr_y, idx_x, idx_y, ord, len_x, len_y,   _ret, _gain) {
    _ret = 0.0;
    _gain = 1.0 / ord;

    _ret += get_buffer(arr_y, idx_y - 1, len_y);
    _ret += _gain * get_buffer(arr_x, idx_x, len_x);
    _ret -= _gain * get_buffer(arr_x, idx_x - ord, len_x);

    return _ret;
}

Vol.80 補足情報

投稿日:2022.09.14 | カテゴリー: コード

Pythonあれこれ

 記事中で使用するデータファイルは以下のWebページから入手できます。

https://github.com/shellscript-magazine/python_this_and_that/releases/tag/ver1.0

情報は随時更新致します。

レポート 「Raspberry Pi Zero 2 W」国内版発売(Vol.79掲載)

投稿日:2022.07.25 | カテゴリー: コード

著者:末安 泰三

超小型コンピュータボード「Raspberry Pi Zero」シリーズの最新版、「Raspberry Pi Zero 2 W」の国内版が2022年6月に発売された。Raspberry Pi財団が2021年10月に発売した製品だが、国内版には技適マークが付く。同シリーズの従来製品と比べ、マルチスレッド性能が4~5倍程度向上した。

シェルスクリプトマガジン Vol.79は以下のリンク先でご購入できます。

図2 LinuxカーネルのRaspberry Pi Zero 2 W用のDTSファイルの内容

(略)
       compatible = "raspberrypi,model-zero-2-w", "brcm,bcm2837";
        model = "Raspberry Pi Zero 2 W";

        memory@0 {
                device_type = "memory";
                reg = <0 0x20000000>;
        };

        chosen {
                /* 8250 auxiliary UART instead of pl011 */
                stdout-path = "serial1:115200n8";
        };
(略)

特集2 FlutterでGUIアプリを開発しよう(Vol.79記載)

投稿日:2022.07.25 | カテゴリー: コード

著者:三好 文二郎

「Flutter」は、米Google社がOSS(オープンソースソフトウエア)として提供する、GUI アプリ開発用のSDK(Software Development Kit)です。単一のコードで複数のプラットフォーム向けのGUIアプリを作成できるほか、標準で用意されるUIコンポーネントが豊富、Hot Reloadによる開発者体験が素晴らしいことなどから開発者の間で人気が高まっています。Flutterの概要と、Flutterを利用したアプリ開発方法について紹介します。

シェルスクリプトマガジン Vol.79は以下のリンク先でご購入できます。

図18 ウィジェットに関する記述の例

children: <Widget>[
  const Text(
    'You have pushed the button this many times:',
  ),
  Text(
    '$_counter',
    style: Theme.of(context).textTheme.headline4,
  ),
],

図19 ボタンウィジェットを追加するコードの例

children: <Widget>[
  const Text(
    'You have pushed the button this many times:',
  ),
  Text(
    '$_counter',
    style: Theme.of(context).textTheme.headline4,
  ),
  MaterialButton (
    color: Colors.cyan.withOpacity(0.7),
    shape: RoundedRectangleBorder(borderRadius:BorderRadius.circular(30.0)),
    onPressed: () {},
    child: const Text('this is a button widget'),
  ),
],

図21 my_first_appプロジェクトの「pubspec.yaml」ファイルの内容

name: my_first_app
description: A new Flutter project.
publish_to: "none"
version: 1.0.0+1
environment:
  sdk: ">=2.16.2 <3.0.0"
dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^1.0.0
flutter:
  uses-material-design: true

図22 go_routerパッケージを利用する場合の記述例

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  go_router: ^3.1.1

図23 画面を追加するためのコード

class NavigatedPage extends StatelessWidget {
  const NavigatedPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('navigated page'),
      ),
      body: const Center(
        child: Text('navigated with go_router'),
      ),
    );
  }
}

図24 画面遷移のためのコード

class MyApp extends StatelessWidget {
  MyApp({Key? key}) : super(key: key);
  final _router = GoRouter(
    routes: [
      GoRoute(
        path: '/',
        builder: (context, state) =>
          const MyHomePage(title: 'Flutter Demo Home Page'),
      ),
      GoRoute(
        path: '/navigated',
        builder: (context, state) => const NavigatedPage(),
      ),
    ],
  );

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
    ),
    routeInformationParser: _router.routeInformationParser,
    routerDelegate: _router.routerDelegate);
  }
}

図25 ボタンウィジェットに画面遷移処理を割り当てるコード

MaterialButton (
  color: Colors.cyan.withOpacity(0.7),
  shape: RoundedRectangleBorder(borderRadius:BorderRadius.circular(30.0)),
  onPressed: () {
    GoRouter.of(context).go('/navigated');
  },
  child: const Text('Navigate'),
),

図26 main()関数のコードを変更

void main() {
  runApp(MyApp());
}

特集3 ユニケージ開発のシステム(Vol.79記載)

投稿日:2022.07.25 | カテゴリー: コード

著者:松浦 智之

システム開発手法「ユニケージ」では、データ保存用に「ファイル」を使い、ユニケージ専用コマンド群「usp Tukubai」と「シェルスクリプト」で業務システムを開発します。usp Tukubaiは有償ソフトですが、無償のオープンソース版「Open usp Tukubai」もあります。このOpen usp Tukubaiを使って、ユニケージ開発を無料で始めてみましょう。

シェルスクリプトマガジン Vol.79は以下のリンク先でご購入できます。

図6 Apache HTTP Serverの郵便番号・住所検索システム用設定ファイル(/etc/apache2/sites-available/zip2addr.conf)

<IfModule alias_module>
    Alias /zip2addr /home/ユーザー名/ZIP2ADDR/public_html
    <Directory /home/ユーザー名/ZIP2ADDR/public_html>
         AddHandler cgi-script .cgi
         Options all
         Require all granted
    </Directory>
</IfModule>

図10 MK_ZIPTBL.SHのソースコード

#!/bin/sh -u
(略)
homd=$(d=${0%/*}/; [ "_$d" = "_$0/" ] && d='./'; cd "$d.."; pwd)
datd=$homd/DATA
shld=$homd/SHELL
webd=$homd/public_html

url_ken=https://www.post.japanpost.jp/zipcode/dl/oogaki/zip/ken_all.zip
url_jig=https://www.post.japanpost.jp/zipcode/dl/jigyosyo/zip/jigyosyo.zip

export LC_ALL=C
(略)
nocmds=''
type wget   >/dev/null 2>&1 || { nocmds="$nocmds,wget"  ; }
type gunzip >/dev/null 2>&1 || { nocmds="$nocmds,gunzip"; }
type iconv  >/dev/null 2>&1 || { nocmds="$nocmds,iconv" ; }
if [ -n "$nocmds" ]; then
  echo "${0##*/}: ${nocmds#,} not found. Install them in advance." 1>&2
  exit 1
fi
(略)
wget -q -O - "$url_ken"           |
gunzip                            |
tr -d '\r'                        |
iconv -c -f Shift_JIS -t UTF-8    |
tr -d '"'                         |
awk -F , '{print $3,$7,$8,$9}'    > $datd/ziptbl_ken.txt

if [ ! -s $datd/ziptbl_ken.txt ]; then
  echo "${0##*/}: Failed to make zip_ken.txt" 1>&2; exit 1
fi
(略)
wget -q -O - "$url_jig"           |
gunzip                            |
tr -d '\r'                        |
iconv -c -f Shift_JIS -t UTF-8    |
tr -d '"'                         |
awk -F , '{print $8,$4,$5,$6 $7}' > $datd/ziptbl_jig.txt

if [ ! -s $datd/ziptbl_jig.txt ]; then
  echo "${0##*/}: Failed to make zip_ken.txt" 1>&2; exit 1
fi
(略)
exit 0

図12 ZIP2ADDR.AJAX.cgiのコード

#!/bin/sh -u
(略)
homd=$(d=${0%/*}/; [ "_$d" = "_$0/" ] && d='./'; cd "$d.."; pwd)
datd=$homd/DATA
shld=$homd/SHELL
webd=$homd/public_html

export LC_ALL=C
(略)
exit_trap() {
  set -- ${1:-} $?  # $? is set as $1 if no argument given
  trap '' EXIT HUP INT QUIT PIPE ALRM TERM
  [ -d "${tmpd:-}" ] && rm -rf "$tmpd"
  trap -  EXIT HUP INT QUIT PIPE ALRM TERM
  exit $1
}

error500_exit() {
  echo 'Status: 500 Internal Server Error'
  echo 'Content-Type: text/plain'
  echo
  echo '500 Internal Server Error'
  echo "($@)"
  exit 1
}

error400_exit() {
  echo 'Status: 400 Bad Request'
  echo 'Content-Type: text/plain'
  echo
  echo '400 Bad Request'
  echo "($@)"
  exit 1
}
(略)
trap 'exit_trap' EXIT HUP INT QUIT PIPE ALRM TERM
tmpd=$(mktemp -d -t "_${0##*/}.$$.XXXXXXXXXXX")
[ -d "$tmpd" ] || error500_exit 'Failed to mktemp'
(略)
printf '%s\n' "${QUERY_STRING:-}" |
cgi-name                          > $tmpd/cgivars

zip=$(nameread zipcode $tmpd/cgivars)
printf '%s\n' "$zip" | grep -qE '^[0-9]{7}$' || error400_exit 'Invalid zipcode'
(略)
echo $homd/DATA/ziptbl* | grep -qF '*' && error500_exit 'No table files exist'

cat $homd/DATA/ziptbl*                 |
awk '$1=="'"$zip"'"{print "pref",$2;
                    print "city",$3;
                    print "town",$4;}' > $tmpd/address123
[ -s $tmpd/address123 ] || error400_exit 'No address found'
(略)
echo 'Content-Type: text/html; charset=utf-8'
echo 'Cache-Control: private, no-store, no-cache, must-revalidate'
echo 'Pragma: no-cache'
echo

formhame $webd/ZIP2ADDR.html $tmpd/address123 |
sed -n '/BEGIN ADDRESS123/,/END ADDRESS123/p'
(略)
[ -d "${tmpd:-}" ] && rm -rf "$tmpd"
exit 0

図13 ZIP2ADDR.htmlのコード

<!DOCTYPE html>
(略)
<script type="text/javascript" src="ZIP2ADDR.js"></script>
(略)
</head>

<body>
<h1>郵便番号→住所検索 デモ</h1>
<form action="#dummy">
<table border="0" id="addressform">
  <tr>
    <td>
      <dl>
        <dt>郵便番号</dt>
        <dd><input id="zipcode1" type="text" name="zipcode1" value="" size="3" maxlength="3" />-<input id="zipcode2" type="text" name="zipcode2" value="" size="4" maxlength="4" /></dd>
        <dd><input id="run" type="button" name="run" value="検索実行!" onclick="zip2addr();"></dd>
      </dl>
    </td>
  </tr>
  <tr>
    <td>
      <dl id="adress123">
        <!-- BEGIN ADDRESS123-->
        <dt>住所(都道府県名)</dt>
        <dd>
          <select id="pref" name="pref">
            <option value="(未選択)">(未選択)</option>
(略)
            <option value="沖縄県"  >沖縄県</option>
          </select>
        </dd>
        <dt>住所(市区町村名)</dt><dd><input id="city" type="text" size="60" name="city" value="" /></dd>
        <dt>住所(町名以降)</dt><dd><input id="town" type="text" size="60" name="town" value="" /></dd>
        <!-- END ADDRESS123-->
      </dl>
    </td>
  </tr>
</table>
</form>
</body>
</html>

図14 ZIP2ADDR.jsのコード

(略)
function zip2addr() {
  var url;
  var zipcode;
  var xhr;
(略) 
  if (! document.getElementById('zipcode1').value.match(/^([0-9]{3})$/)) {
    alert('郵便番号(前の3桁)が正しくありません');
    return;
  }
  zipcode  = RegExp.$1;
  if (! document.getElementById('zipcode2').value.match(/^([0-9]{4})$/)) {
    alert('郵便番号(後の4桁)が正しくありません');
    return;
  }
  zipcode += RegExp.$1;
(略)
  xhr = createXMLHttpRequest();
  if (xhr) {
    url  = 'ZIP2ADDR.AJAX.cgi?zipcode='+zipcode;
    url += '&dummy='+parseInt((new Date)/1);

    xhr.open('GET', url, true);
    xhr.onreadystatechange = function(){zip2addr_callback(xhr)};
    xhr.send(null);
  }
(略)
  return;
}
(略)
function zip2addr_callback(xhr) {

  var e;
(略)
  if (xhr.readyState != 4) {return;}
  if (xhr.status == 0    ) {return;}
  if      (xhr.status == 400) {
    alert('郵便番号が正しくありません');
    return;
  }
  else if (xhr.status != 200) {
    alert('アクセスエラー(' + xhr.status + ')');
    return;
  }
(略)
  e = document.getElementById('adress123');
  if (!e) {alert('エラー: 住所欄が存在しない!'); return;}
  e.innerHTML = xhr.responseText;
(略)
  return;
}

特別企画 本格的なホームサーバーを構築(Vol.79記載)

投稿日:2022.07.25 | カテゴリー: コード

著者:麻生 二郎

小型コンピュータボードの最上位モデルである「Raspberry Pi 4 Model B」の2G/4G/8Gバイト版と、人気のLinuxディストリビューション「Ubuntu」のサーバー版を組み合わせて、本格的なサーバーを構築しましょう。本特集では、サーバーをインターネットに公開する方法を紹介します。

シェルスクリプトマガジン Vol.79は以下のリンク先でご購入できます。

図A3 ラズパイサーバーの初期設定(ubuntu_init1.sh)

#!/bin/sh

##日本のタイムゾーン設定
sudo timedatectl set-timezone Asia/Tokyo

##全ソフトウエア更新
sudo apt update
sudo apt -y upgrade
sudo apt -y autoremove
sudo apt clean

##ファームウエアアップデート
sudo apt -y install rpi-eeprom
sudo rpi-eeprom-update

##完了後の再起動
read -p "再起動しますか [y/N]:" YN
if [ " $YN" = " y" ] || [ " $YN" = " Y" ]; then
  sudo reboot
fi

図A4 ラズパイサーバーの初期設定(ubuntu_init2.sh)

#!/bin/sh

##固定IPアドレスとルーターのIPアドレス
IP_ADDRESS="192.168.10.100"
ROUTER_IP="192.168.10.1"

##旧設定バックアップ
mkdir -p ~/old_settings
sudo mv /etc/netplan/50-cloud-init.yaml ~/old_settings/.

##新ネットワーク設定作成
cat << EOF | sudo tee /etc/netplan/50-cloud-init.yaml > /dev/null
network:
  ethernets:
    eth0:
      dhcp4: false
      addresses: [ip_address/24]
      gateway4: router_ip
      nameservers:
        addresses: [8.8.8.8]
  version: 2
EOF
sudo sed -i -e "s%ip_address%$IP_ADDRESS%" /etc/netplan/50-cloud-init.yaml
sudo sed -i -e "s%router_ip%$ROUTER_ip%" /etc/netplan/50-cloud-init.yaml

##ネットワーク設定反映
sudo netplan apply

Raspberry Piを100%活用しよう(Vol.79掲載)

投稿日:2022.07.25 | カテゴリー: コード

著者:米田 聡

小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。第12回は、電流を測定するクランプメーターを搭載する拡張基板を扱います。

シェルスクリプトマガジン Vol.79は以下のリンク先でご購入できます。

図6 本連載で作成したサンプルプログラム(sample.py)

import time
import board
import math
import busio
import adafruit_ads1x15.ads1115 as ADS
from adafruit_ads1x15.ads1x15 import Mode
from adafruit_ads1x15.analog_in import AnalogIn

GAIN = 1
RATE = 860

i2c = busio.I2C(board.SCL, board.SDA, frequency=1000000)
ads = ADS.ADS1115(i2c, GAIN)
# 差動入力で初期化
in0 = AnalogIn(ads, ADS.P0, ADS.P1)

# 連続モードの設定
ads.mode = Mode.CONTINUOUS
ads.data_rate = RATE
SAMPLE_INTERVAL = 1.0 / ads.data_rate

# ADS1115のCONTINUOUSモードは初回読み取り実行後に設定が行われ
# 設定に2サンプリングクロックが必要になる
_ = in0.voltage # 設定用の空読み込み

value = 0.0
current_value = 0.0
sqrtI = 0.0

time_next_sample = time.monotonic() + SAMPLE_INTERVAL
for i in range(ads.data_rate):
    while time.monotonic() < (time_next_sample):
        pass
    # 前回と同じ値が読み取られた場合は読み飛ばす
    current_value = in0.voltage
    while current_value == value:
        current_value = in0.voltage
    value = current_value
    sqrtI += value * value

    time_next_sample = time.monotonic() + SAMPLE_INTERVAL

# 電流値(RMS)の計算
ampere = math.sqrt(sqrtI/ads.data_rate) * 20.0
print(ampere)

香川大学SLPからお届け!(Vol.79掲載)

投稿日:2022.07.25 | カテゴリー: コード

著者:岩本 和真

人間の言語をコンピュータで処理する自然言語処理の分野は、近年、急速に進歩しています。それによって例えば、自然な翻訳や文章生成をする技術などが開発されています。同分野の学習成果として筆者は、「秘書チャット」と名付けたチャットボットアプリを作成しました。今回は、このアプリに盛り込んだ機能について、技術的に解説します。

シェルスクリプトマガジン Vol.79は以下のリンク先でご購入できます。

図5 形態素解析処理をするmorpheme()関数の定義コード

def morpheme(self, input, speech=False):
  input=unicodedata.normalize('NFKC', input)
    if speech:
      speech_list = []
      sentence = self.wakati.parse(input).split()
      node = self.wakati.parseToNode(input)
      while node:
        if node.feature.split(",")[0] != "BOS/EOS":
          speech_list.append(node.feature.split(",")[0])    
        node = node.next
      return sentence, speech_list
    else:
      sentence = self.wakati.parse(input).split()
      return sentence

図8 予定の内容を抽出するcontent_extract()関数の定義コード

def content_extract(self, input_list, speech_list):
  out_list = []
  ban_word = ["覚え", "記憶"]
  pass_word = ["予定", "こと"]
  for i, input in enumerate(input_list):
    if input in ban_word:
      break
    elif input in pass_word:
      continue
    elif input.isdecimal() and \
         input_list[i+1] in self.date_key:
      continue
    elif (input_list[i-1]).isdecimal() and \
         input in self.date_key:
      continue
    elif out_list != [] and speech_list[i-1] == "名詞" and \
         speech_list[i+1] == "名詞" and \
         input_list[i+1] not in pass_word:
      out_list.append(input_list[i])
    elif speech_list[i] == "名詞":
      out_list.append(input)
    else:
      continue
  return ''.join(out_list) 

図9 Webページのテキスト情報を抽出するscraping()関数の定義コード

from bs4 import BeautifulSoup
import requests

def scraping(url, file_path):
  responses = requests.get(url)
  soup = BeautifulSoup(responses.content, 'html.parser')
  text_list = soup.get_text().splitlines()
  text_list = list(set(text_list))
  text_list = [text.replace('\u3000', '') for text in text_list]
  text = '\n'.join(text_list)
  with open(file_path, 'w', encoding='utf_8') as f:
    f.write(text)

図12 曜日や豆知識を答える機能のコード

def week_teach(self, input):
  year = None
  month = None
  day = None
  input = self.date_update.convert(input)
  input_list = self.morpheme(input)
  if "年" in input_list:
    year = self.date_specify("年", input_list)
  else:
    year = self.year
  if "月" in input_list:
    month = self.date_specify("月", input_list)
  else:
    month = self.month
  day = self.date_specify("日", input_list)
  d_key = dt.date(year, month, day)
  week_key = d_key.weekday()
  return year, month, day, self.week_list[week_key]
    
def knowledge_teach(self):
  file_path = "text_data/min_kl.txt"
  with open(file_path, 'r', encoding='UTF-8') as f:
    knowledge_data = f.readlines()
  knowledge = random.choice(knowledge_data)
  return knowledge

AWKでデジタル信号処理(Vol.79掲載)

投稿日:2022.07.25 | カテゴリー: コード

著者:斉藤 博文

プログラミング言語「AWK」は、データストリーム(データの流れ)を逐次処理するのに適しています。本連載では、電子回路の分野でその特徴を生かし、シェルスクリプトを組み合わせてデジタル信号を処理します。第1回は「デジタルフィルタ」と「リングバッファ」について解説します。

シェルスクリプトマガジン Vol.79は以下のリンク先でご購入できます。

図14 1~10000の出力データから末尾の10行を取り出すAWKプログラム(tail.awk)

#! /usr/bin/gawk -f

{
    lines[NR] = $0;
}

END {
    for (i = NR - 9; i <= NR; i++) {
        print lines[i];
    }
}

図15 リングバッファを用いたAWKプログラム(tail_ring.awk)

#! /usr/bin/gawk -f

BEGIN {
    len = 10;
}

{
    idx = NR % 10;
    buf[idx] = $0;
}

END {
    for (i = 9; i >= 0; i--) {
        print get_buf(buf, idx - i, len);
    }
}

function get_buf(buf, idx, len) {
    if (idx < 0) {
        return buf[idx + len];
    } else {
        return buf[idx];
    }
}

図17 「get_buf」関数をPythonのプログラムで記述

def get_buf(buf, idx, len):
    if (idx < 0):
        return buf[idx + len]
    else:
        return buf[idx]

Vol.79 補足情報

投稿日:2022.07.25 | カテゴリー: コード

特集3 ユニケージ開発のシステム

 p.30の中央右段に「163mm」の不要な文字が入り込み、本文と重なってしまいました。お詫びして訂正いたします。

特別企画 本格的なホームサーバーを構築 インターネット公開編

 p.46の左段にある「$ wget -O /home/pi/DDNSNow_update.log “https://f5.si/update.php?domain=ユーザー名&password=パスワード”」の「pi」は「Ubuntu Serverのユーザー名」の誤り、右段にある「0-59 * * * * wget -O DDNSNow_update.log “https://f5.si/update.php?domain=ユーザー名&password=パスワード”」の「 -O DDNSNow_update.log」は「 -O /home/Ubuntu Serverのユーザー名/DDNSNow_update.log」の誤りです。お詫びして訂正いたします。

IPアドレスの調べ方

 ルーターのDHCP(Dynamic Host Configuration Protocol)のリース情報から、Raspberry Piに割り当てられたIPアドレスを、次のように調べます。
 パソコンのWebブラウザを開いて、ルーターの管理画面にログインします。ログイン方法は、ルーターのマニュアルや、インターネットの情報で調べてください。
 管理画面が開いたら、DHCPのリース情報を表示します。こちらもルーターの機種によって操作方法や名称が異なりますが、一般的な家庭用ルーターには必ずあります。例にしたルーターでは、「詳細設定」から「LAN」「DHCPリース」でネットワーク機器に割り当てられているIPアドレスを表示できます(図1)。

図1 リース情報

 リース情報を表示した状態で、Raspberry Piを起動するとRaspberry Piに割り当てられたIPアドレスの情報が追加されます(図2の赤枠)。IPアドレスを使ってリモートからアクセスできます。

図2 Raspberry Piの情報が追加された

 ちなみに、このリース情報画面でRaspberry PiのMACアドレスと、割り当てたいIPアドレスを設定すれば、Raspberry Pi側のネットワーク設定を変更せずとも常に同じIPアドレス、固定IPアドレスを設定できます。

The Poetry and Art Collection

 p.64の右上に前号で掲載した詩の一部(複雑のひと 単純のこころ 唯一のひかり 無限のやみ)が入ってしまいした。お詫びして訂正いたします。

情報は随時更新致します。

シェルスクリプトマガジンvol.79 Web掲載記事まとめ

投稿日:2022.07.25 | カテゴリー: コード

004 レポート テキストエディタ「Vim 9.0」リリース コード掲載
005 レポート 「Raspberry Pi Zero 2 W」国内版発売 コード掲載
006 製品レビュー イヤホン「LinkBuds」
007 NEWS FLASH
008 特集1 仮想化ソフト「VirtualBox」 徹底活用/麻生二郎
020 特集2 FlutterでGUIアプリを開発しよう/三好文二郎 コード掲載
030 特集3 ユニケージ開発のシステム/松浦智之 コード掲載
042 特別企画 本格的なホームサーバーを構築 インターネット公開編/麻生二郎 コード掲載
051 Hello Nogyo!
052 Raspberry Piを100%活用しよう/米田聡 コード掲載
056 Pythonあれこれ/飯尾淳
064 マルウエア/桑原滝弥、イケヤシロウ
066 中小企業手作りIT化奮戦記/菅雄一
070 タイ語から分かる現地生活/つじみき
076 法林浩之のFIGHTING TALKS/法林浩之
078 香川大学SLPからお届け!/岩本和真 コード掲載 
084 AWKでデジタル信号処理/斉藤博文 コード掲載
090 Bash入門/大津真
098 Techパズル/gori.sh
099 コラム「こうしてユニケージは生まれた」/シェル魔人

レポート テキストエディタ「Vim 9.0」リリース(Vol.79掲載)

投稿日:2022.07.25 | カテゴリー: コード

著者:末安 泰三

テキストエディタ「Vim」の最新版「Vim 9.0」が2022年6月28日に公開された。前版「Vim 8.2」の公開は2019年12月のため、約2年半ぶりのリリースとなる。Vim 9.0の目玉となる新機能は、文法を見直して大幅な高速化を実現したスクリプト言語「Vim9 Script」のサポートである。

シェルスクリプトマガジン Vol.79は以下のリンク先でご購入できます。

図2 新旧のコードで定義した関数の実行結果と実行時間を表示するコードの例

command! -bar TimerStart let start_time = reltime()
command! -bar TimerEnd echo reltimestr(reltime(start_time))
                       \ | unlet start_time

function Add_old()
  let a = 0
  for i in range(1, 1000000)
    let a += i
  endfor
  return a
endfunction

def Add_new(): number
  var a = 0
  for i in range(1, 1000000)
    a += i
  endfor
  return a
enddef

TimerStart
echo Add_old()
TimerEnd
TimerStart
echo Add_new()
TimerEnd

Vol.78 補足情報

投稿日:2022.05.25 | カテゴリー: コード

Raspberry Piを100%活用しよう

 「明るさセンサ拡張基板」(ADRSZLX)に搭載されている光学式センサーは、「VCNL4010」ではなく、「VCNL4020」でした。お詫びして訂正します。なお、記事内で利用している「Adafruit_VCNL40xx」ライブラリおよび、サンプルプログラムは、VCNL4020でも問題なく動作します。

情報は随時更新致します。

シェルスクリプトマガジンvol.78 Web掲載記事まとめ

投稿日:2022.05.25 | カテゴリー: コード

004 レポート OpenSSH 9.0/9.0p1リリース
005 レポート ゲームエンジンのUnreal Engine 5
006 製品レビュー マグカップ型電気鍋「Cook Mug」
007 NEWS FLASH
008 特集1 アプリケーション開発基盤 .NET/松井恵一、木下舜、上原将司 コード掲載
023 Hello Nogyo!
024 特集2 UMLを使い始めてみよう/森三貴
040 特別企画 ESP32とラズパイで作る本格IoT 簡易地震計/魔法少女 コード掲載
058 Raspberry Piを100%活用しよう/米田聡 コード掲載
060 Pythonあれこれ/飯尾淳 コード掲載
066 法林浩之のFIGHTING TALKS/法林浩之
068 中小企業手作りIT化奮戦記/菅雄一 コード掲載
072 ダークウェブ/桑原滝弥、イケヤシロウ
074 香川大学SLPからお届け!/樋口史弥 コード掲載
084 タイ語から分かる現地生活/つじみき
092 Bash入門/大津真
098 Techパズル/gori.sh
099 コラム「計画は全体を、問題は点を追求する」/シェル魔人

特集1 アプリケーション開発基盤 .NET(Vol.78記載)

投稿日:2022.05.25 | カテゴリー: コード

著者:松井 恵一、木下 舜、上原 将司

デスクトップパソコンやモバイル、Web、クラウドなど、さまざまな環境で動作するアプリケーションを開発できる基盤が「.NET」です。昨年11月に初の長期サポート版となる.NET 6がリリースされました。本特集では、最新の.NET 6を含め、.NETについて分かりやすく解説します。

シェルスクリプトマガジン Vol.78は以下のリンク先でご購入できます。

図13 Program.csファイルの中身

// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");

図14 C#で記述した、「Hello World」を表示するプログラムの例

using System;

namespace ConsoleApplication
{
    public class Program
    {
        public static void Main()
        {
            Console.WriteLine("Hello World!");
        }
    }
}

図15 MyFirstDotnetApp.csprojファイルの中身

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

特集1 ESP32とラズパイで作る本格IoT(Vol.78記載)

投稿日:2022.05.25 | カテゴリー: コード

筆者:魔法少女

マイコン「ESP32」と小型コンピュータボード「Raspberry Pi」を使って本格的なIoT(Internet of Things、モノのインターネット)環境を構築してみましょう。本企画では、3軸の加速度センサーを用いて簡易地震計を作成し、リアルタイムに揺れをグラフで表示します。

シェルスクリプトマガジン Vol.78は以下のリンク先でご購入できます。

図4 Eclipse Mosquittoの設定スクリプト(mosquitto_setting.sh)

#!/bin/sh

sudo tee /etc/mosquitto/conf.d/confer.conf << EOF >dev/null
listener 1883
allow_anonymous true
listener 8081
protocol websockets
sudo systemctl reload mosquitto

図18 ESP32搭載ボートの起動時に無線LANに接続するプログラム(boot.py)

import network

SSID = 'SSID'
PASSWORD = 'パスワード'

wlan_if = network.WLAN(network.STA_IF)
wlan_if.active(True)
wlan_if.connect(SSID, PASSWORD)

図19 パブリッシャのプログラム(main.py)

import time
from machine import Pin, SoftI2C
import mpu6050
from umqtt.simple import MQTTClient
import json

mqtt_topic = "home/seismometer1"
publisher_id = "place_esp32"
broker_address = "192.168.1.100"
interval_time = "1"

i2c = SoftI2C(scl=Pin(22), sda=Pin(21))
accelerometer = mpu6050.accel(i2c)

time.sleep(5)

while True:
  iot_value = accelerometer.get_values()
  iot_value_json = json.dumps(iot_value)
  print(iot_value_json)
  publisher = MQTTClient(publisher_id,broker_address)
  publisher.connect()
  publisher.publish(mqtt_topic, msg=str(iot_value_json))
  publisher.disconnect()
  time.sleep(int(interval_time))

図24 ブスクライバとなるJavaScriptプログラムを含んだHTMLファイル(seismometer.html)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>簡易地震計</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-streaming@1.9.0"></script>
  <script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
</head>
<body>
  <canvas id="myChart"></canvas>
  <script>
    const client = mqtt.connect('ws://raspibroker.local:8081');
    const ctx = document.getElementById('myChart').getContext('2d');
    var chart = new Chart(ctx, {
      type: 'line',
      data: {
        datasets: [{
          data: [],
          label: 'X軸',
          borderColor: 'rgb(255, 0, 255)',
          backgroundColor: 'rgba(255, 255, 255, 0)',
          lineTension: 0,
        }, {
          data: [],
          label: 'Y軸',
          borderColor: 'rgb(0, 255, 0)',
          backgroundColor: 'rgba(255, 255, 255, 0)',
          lineTension: 0,
        }, {
          data: [],
          label: 'Z軸',
          borderColor: 'rgb(0, 0, 255)',
          backgroundColor: 'rgba(255, 255, 255, 0)',
          lineTension: 0,
        }]
      },
      options: {
        scales: {
          xAxes: [{
            type: 'realtime',
            realtime: {
              delay: 2000,
            },
          }],
          yAxes: [{
            ticks: {
              min: -32767,
              max: 32767
            }
          }]
        }
      }
    });
    client.on('connect', () => {
      console.log('connected');
      client.subscribe('home/seismometer1');
    });
    client.on('message', (topic, message) => {
      console.log(message.toString());
      acc_json = JSON.parse(message.toString());
      chart.data.datasets[0].data.push({
        x: Date.now(),
        y: acc_json.AcX
      });
      chart.data.datasets[1].data.push({
        x: Date.now(),
        y: acc_json.AcY
      });
      chart.data.datasets[2].data.push({
        x: Date.now(),
        y: acc_json.AcZ - 16384
      });
      chart.update();
    });
  </script>
</body>
</html>

Raspberry Piを100%活用しよう(Vol.78掲載)

投稿日:2022.05.25 | カテゴリー: コード

著者:米田 聡

小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。第11回は、光の反射で物体との距離を調べるセンサー搭載の拡張基板を扱います。

シェルスクリプトマガジン Vol.78は以下のリンク先でご購入できます。

図4 VCNL4010から値を読み出すPythonプログラム(sample.py)

import time
import Adafruit_VCNL40xx

vcnl = Adafruit_VCNL40xx.VCNL4010()

try:
  while True:
    prox = vcnl.read_proximity()
    ambient = vcnl.read_ambient()
    print('Proximity = ' + str(prox))
    print('Ambient = ' + str(ambient) + ' Lux')
    time.sleep(0.5)
except KeyboardInterrupt:
    pass

Pythonあれこれ(Vol.78掲載)

投稿日:2022.05.25 | カテゴリー: コード

著者:飯尾 淳

本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温
かく見守ってください。皆さんと共に勉強していきましょう。第8回は、Webアプリケーションフレームワーク「Flask」を使って基本的なWebアプリを作成する方法を紹介します。データベースにアクセスする方法やテンプレートの利用方法も解説します。

シェルスクリプトマガジン Vol.78は以下のリンク先でご購入できます。

図1 「Hello World」に相当するシンプルなWebアプリのコード(app.py)

from flask import Flask

app = Flask(__name__)
@app.route('/')
def hello():
  return '<h1>Hello World!</h1>'

図4 ルーティングの記述を追加したWebアプリのコード

from flask import Flask
app = Flask(__name__)

@app.route('/hello')
def hello():
  return '<h1>Hello World!</h1>'

@app.route('/goodbye')
def goodbye():
  return '<h1>Good-bye World!</h1>'

図5 パスパラメータを使うWebアプリのコードの例

from flask import Flask
app = Flask(__name__)

@app.route('/hello/<name>')
def hello(name):
  return f'<h1>Hello {name}!</h1>'

図7 クエリーパラメータを使うWebアプリのコードの例

from flask import Flask, request
app = Flask(__name__)

@app.route('/hello')
def hello():
  name = request.args['name']
  return f'<h1>Hello {name}!</h1>'

図9 メモ帳Webアプリのひな型コード

from flask import Flask, request
app = Flask(__name__)

@app.route('/addMemo')
def addMemo():
  name = request.args['name']
  description = request.args['description']
  return f'ADD: name = {name}, desc = {description}'

@app.route('/getMemo')
def getMemo():
  return f'[TBD]'

図10 データベースを使うメモ帳Webアプリのコード(その1)

from flask import Flask, request
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Integer, String, Text

app = Flask(__name__)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///memo.sqlite'

db = SQLAlchemy(app)

class Memo(db.Model):
  __tablename__ = 'memo'
  ID          = db.Column(Integer, primary_key=True)
  NAME        = db.Column(String(500))
  DESCRIPTION = db.Column(Text)

db.create_all()

@app.route('/addMemo')
def addMemo():
  name = request.args['name']
  description = request.args['description']
  return f'ADD: name = {name}, desc = {description}'

@app.route('/getMemo')
def getMemo():
  return f'[TBD]'

図12 データベースを使うメモ帳Webアプリのコード(その2)

from flask import Flask, request
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Integer, String, Text

app = Flask(__name__)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///memo.sqlite'

db = SQLAlchemy(app)

class Memo(db.Model):
  __tablename__ = 'memo'
  ID          = db.Column(Integer, primary_key=True)
  NAME        = db.Column(String(500))
  DESCRIPTION = db.Column(Text)

db.create_all()

@app.route('/addMemo')
def addMemo():
  name = request.args['name']
  description = request.args['description']
  db.session.add(Memo(NAME=name, DESCRIPTION=description))
  db.session.commit()
  db.session.close()
  return f'ADD: name = {name}, desc = {description}'

@app.route('/getMemo')
def getMemo():
  memos = db.session.query(
    Memo.ID, Memo.NAME, Memo.DESCRIPTION).all()
  return str(memos)

図14 テンプレートファイル「index.html」の内容

<!DOCTYPE html>
<html>
  <head>
    <title>Flask Test</title>
  </head>
  <body>
    <h1>The Memo List</h1>
    <table>
      <tr><th>ID</th><th>Name</th><th>Description</th></tr>
      {% for memo in memos: %}
      <tr><td>{{ memo.0 }}</td>
          <td>{{ memo.1 }}</td>
          <td>{{ memo.2 }}</td></tr>
      {% endfor %}
    </table>
  </body>
</html>

図15 テンプレートを使うメモ帳Webアプリのコード(その1)

from flask import Flask, request, render_template
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Integer, String, Text

app = Flask(__name__)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///memo.sqlite'

db = SQLAlchemy(app)

class Memo(db.Model):
  __tablename__ = 'memo'
  ID          = db.Column(Integer, primary_key=True)
  NAME        = db.Column(String(500))
  DESCRIPTION = db.Column(Text)

db.create_all()

@app.route('/addMemo')
def addMemo():
  name = request.args['name']
  description = request.args['description']
  db.session.add(Memo(NAME=name, DESCRIPTION=description))
  db.session.commit()
  db.session.close()
  return f'ADD: name = {name}, desc = {description}'

@app.route('/getMemo')
def getMemo():
  memos = db.session.query(
    Memo.ID, Memo.NAME, Memo.DESCRIPTION).all()
  return render_template('index.html', memos=memos)

図17 修正したテンプレートファイル「index.html」の内容

<!DOCTYPE html>
<html>
  <head>
    <title>Flask Test</title>
  </head>
  <body>
    <h1>The Memo List</h1>
    <table border="1" width="500" cellspacing="0"
           cellpadding="5" bordercolor="#333333">
      <tr><th>ID</th><th>Name</th><th>Description</th></tr>
      {% for memo in memos: %}
      <tr><td>{{ memo.0 }}</td>
          <td>{{ memo.1 }}</td>
          <td>{{ memo.2 }}</td></tr>
      {% endfor %}
    </table>
    <h2>New Memo</h2>
    <form action="/addMemo" method="post">
      <table style="text-align: left;">
        <tr style="vertical-align: top;"><th>Name:</th>
          <td><input type="text" name="name"></input></td></tr>
        <tr style="vertical-align: top;"><th>Description:</th>
          <td><textarea name="description" rows="4" cols="40">
              </textarea></td></td>
      </table>
      <input type="submit" value="Submit">
      <input type="reset" value="Reset">
    </form>
  </body>
</html>

図18 テンプレートを使うメモ帳Webアプリのコード(その2)

from flask import Flask, request, render_template, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Integer, String, Text

app = Flask(__name__)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///memo.sqlite'

db = SQLAlchemy(app)

class Memo(db.Model):
  __tablename__ = 'memo'
  ID          = db.Column(Integer, primary_key=True)
  NAME        = db.Column(String(500))
  DESCRIPTION = db.Column(Text)

db.create_all()

@app.route('/addMemo', methods=['POST'])
def addMemo():
  name = request.form['name']
  description = request.form['description']
  db.session.add(Memo(NAME=name, DESCRIPTION=description))
  db.session.commit()
  db.session.close()
  return redirect(url_for('getMemo'))

@app.route('/getMemo')
def getMemo():
  memos = db.session.query(
    Memo.ID, Memo.NAME, Memo.DESCRIPTION).all()
  return render_template('index.html', memos=memos)

中小企業手作りIT化奮戦記(Vol.78掲載)

投稿日:2022.05.25 | カテゴリー: コード

著者:菅 雄一

筆者は2001年、PHP言語とOSS(オープンソースソフトウエア)データベース管理システムのPostgreSQLに出会った。それ以降、データベースが絡んだWebシステムを構築する際は、PHPとPostgreSQLを組み合わせて使っていた。しかし2021年に社内Web業務システムなどをレンタルサーバーに移行させた際には、PHPとOSSデータベース管理システムのMySQLを初めて組み合わせて使った。今回は、その移行作業について紹介する。

シェルスクリプトマガジン Vol.78は以下のリンク先でご購入できます。

図1 自作のメール送信用関数の定義コード

function Send_Mail($addr,$content)
{
  $fp = fsockopen(" メールサーバーのアドレス ",25, &$errno , &$errstr);
  if(!$fp)
  {
    print "$errstr ($errno)<br>n" ;
  }
  else
  {
    $subject = "Subject: 発送依頼書" ;
    $subject = mb_convert_encoding($subject,"UTF-8");
    $content = mb_convert_encoding($content,"JIS");
    fwrite($fp,"HELO server.example.co.jp\n");
    fwrite($fp,"MAIL FROM:chohyo@example.co.jp\n");
    fwrite($fp,"RCPT TO:$addr\n");
    fwrite($fp,"DATA\n");
    fwrite($fp,"From: chohyo@example.co.jp\n");
    fwrite($fp,"$subject\n");
    fwrite($fp,"Content-Type: text/plain; charset=ISO-2022-JP\n");
    fwrite($fp,"$content\n");
    fwrite($fp,".\n");
    fwrite($fp,"QUIT\n");
  }
  fclose($fp);
}

香川大学SLPからお届け!(Vol.78掲載)

投稿日:2022.05.25 | カテゴリー: コード

著者:樋口 史弥

アプリケーションやサービスは、ユーザーの意見や技術状況に合わせた変更をしやすいように開発するのが理想的です。そうした開発を実現するための設計手法の一つが「クリーンアーキテクチャ」です。今回は、クリーンアーキテクチャを用いて簡単な匿名掲示板を作る方法を解説します。

シェルスクリプトマガジン Vol.78は以下のリンク先でご購入できます。

図4 投稿削除機能を追加した場合のコード例

// メッセージを表す構造体
type struct Message {
(略)
}
// 全メッセージを取得する関数
func (m *Message)GetAllMessages() error {
(略)
}
// メッセージを投稿する関数
func (m *Message)Post() error {
(略)
}
// メッセージを削除する関数
func (m *Message)Delete() error {
(略)
}

図6 単一責任原則に沿ったコードの例

// メッセージを表す構造体
type struct Message {
(略)
}
// 一般ユーザーを表す構造体
type struct User {
    Message
(略)
}
// メッセージを投稿する関数
func (u *User)Post() error {
(略)
}
// 全メッセージを取得する関数
func (u *User)GetAllMessages() error {
(略)
}
// 管理者を表す構造体
func type struct Administrator {
    Message
(略)
}
// メッセージを削除する関数
func (a *Administrator)Delete() error {
(略)
}

図8 モジュールに依存したコードの例

func (m *Message)Post() error {
(略)
    // MySQLHandlerモジュールのExecute関数を直接実行
    err := MySQLHandler.Execute(sql)
(略)
}

図10 モジュールに依存したコードの例

type struct Message {
 (略)
    i DBInterface
}
func (m *Message)Post() error {
(略)
    // MySQLHandlerモジュールのExecute関数を
    // 直接実行せず、DBInterface経由で実行
    err := m.i.Execute(sql)
(略)
}

図19 messageモジュールのコード

type Message interface {
    // メッセージの文字列を返却する関数
    Message() string
}
type message struct {
    // メッセージの文字列を格納するメンバー
    detail string
}

図20 投稿時刻やハンドルを付加する場合のmessageモジュールのコード

type Message interface {
    // メッセージの文字列を返却する関数
    Message() string
    // 投稿したユーザーのハンドルを返却する関数
    HandleName() string
}
type message struct {
    // メッセージの文字列を格納するメンバー
    detail string
    // 投稿ユーザーのハンドルを格納るメンバー
    handleName string
}

図21 ログイン機能を付加するためのuserモジュールのコード

type User interface {
    // ユーザーIDを返却する関数
    ID() string
    // パスワードが適切かどうかを検査する関数
    MatchPassword(string) bool
}
type user struct {
    id string
    password string
}

図22 メッセージを投稿するというユースケースに対応するコード

type controller struct {
    // データベースに保存するモジュールに
    // アクセスするためのインタフェース
    database db_gateway.DB
}
func (c *controller)Post(m message.Message) error {
    // Messageの中身の検証
    if len(m.Message()) == 0 {
        return MessageNotFound
    }
    // インタフェースを用いてメッセージを保存
    err := c.database.RecordMessage(m)
(略)
}

図23 メッセージを保存するRecordMessageメソッドのコード

type mySQLHandler struct {
    // Frameworks&Drivers層のデータベースクライアントライブラリ
    // のモジュールを呼び出すインタフェース
    database mysql_gateway.MySQLHandler
}
// メッセージを保存するためのハンドラメソッド
func (m *mySQLHandler)RecordMessage(
    message message.Message,
) error {
    // SQL文を生成
    sql := 
    INSERT INTO messages
    (message)
    VALUES (?)
    
    // インタフェースを介してSQL文を実行
    _, err := m.database.Execute(sql, message.Message())
(略)
}

図24 mySQLHandler 構造体のdatabaseメンバーに所属するインタフェースの定義コード

type MySQLHandler interface {
    Query(string, ...interface{}) (Row, error)
    Execute(statement string, args ...interface{}) (Result, error)
}

図25 MySQLHandler インタフェースで定義されるExecute メソッドのコード

import (
(略)
    // データベースクライアントライブラリをインポート
    "database/sql"
)
type mysql struct {
    // データベースに問い合わせるためのコネクション
    Conn *sql.DB
}
func (handler *mysql) Execute(
    statement string,
    args ...interface{},
) (worker.Result, error) {
    res := new(SQLResult)
    // コネクションを用いてSQL文を実行
    result, err := handler.Conn.Exec(statement, args...)
(略)
    res.Result = result
    return res, nil
}

図26 匿名掲示板のMainコンポーネントのコード

func main() {
    // External Interfaceのdatabaseモジュールを初期化
    mysqlDB, f, err := database.NewMySQL(db_user, db_password, 
                                         db_ip, db_port, db_name)
(略)
    // Interface Adaptersのdb_handlerモジュールを初期化
    DBHandler := db_handler.NewMySQL(mysqlDB)
    // Application Business Rulesのcontrollerモジュールを初期化
    controller := interactor.NewController(DBHandler)
    // External Interfaceのserverモジュールを初期化
    server := server.New(port, controller)
    // Application Business Rulesのinteractorモジュールを初期化
    interactor := interactor.NewInteractor(server)
    // Application Business Rulesに制御を渡す
    e := interactor.Run()
(略)
}

図27 データの保存先を変更する際のMainコンポーネント書き換え箇所

func main() {
    // External Interfaceのdatabaseモジュールを初期化
    textDB, err := database.NewText(TextPath)
(略)
    // Interface Adaptersのdb_handlerモジュールを初期化
    DBHandler := db_handler.New(textDB)
    // Application Business Rulesのcontrollerモジュールを初期化
    controller := interactor.NewController(DBHandler)
    // External Interfaceのserverモジュールを初期化
    server := server.New(port, controller)
    // Application Business Rulesのinteractorモジュールを初期化
    interactor := interactor.NewInteractor(server)
    // Application Business Rulesに制御を渡す
    e := interactor.Run()
(略)
}

特別企画 ESP32とラズパイで作る本格IoT(Vol.77記載)

投稿日:2022.03.25 | カテゴリー: コード

著者:魔法少女

マイコン「ESP32」と小型コンピュータ「Raspberry Pi」を使って本格的なIoT(モノのインターネット)環境を構築してみましょう。本企画では、五つの会議室の温度、湿度、利用状況を監視・分析し、会議室内の電源を制御するシステムを構築します。

シェルスクリプトマガジン Vol.77は以下のリンク先でご購入できます。

図4 ユーザー名とパスワードの生成スクリプト(user_create.sh)

#!/bin/sh

tee ~/confer_pw.txt << EOF > /dev/null
pub1:passwd1
pub2:passwd2
pub3:passwd3
pub4:passwd4
pub5:passwd5
sub1:passwd6
EOF
for i in $(seq 6)
do
sed -i -e "s|passwd${i}|$(pwgen 8 1)|" ~/confer_pw.txt
done

図5 Eclipse Mosquittoの認証設定スクリプト(mosquitto_setting.sh)

#!/bin/sh

sudo cp ~/confer_pw.txt /etc/mosquitto/confer_pwfile
sudo mosquitto_passwd -U /etc/mosquitto/confer_pwfile
sudo tee /etc/mosquitto/conf.d/confer.conf << EOF > /dev/null
listener 1883
allow_anonymous false
password_file /etc/mosquitto/confer_pwfile
EOF
sudo systemctl reload mosquitto

図7 サブスクライバのプログラム(subscriber.py)

#!/bin/env python3

import paho.mqtt.client as mqtt
import time

##
mqtt_topic = "confer/#"
csv_file = "./room1.csv"
subscriber_username = "sub1"
subscriber_password = "IBehie1h"
broker_hostname = "localhost"

##
def on_connect(client, userdata, flags, rc):
    print("Connected with result code "+str(rc))
    client.subscribe(mqtt_topic)

def on_message(client, userdata, msg):
    now_unixtime = time.time()
    iot_device = msg.topic + "," + str(msg.payload, encoding='utf-8', errors='replace') + "," + str(now_unixtime)
    print(iot_device)
    with open(csv_file, mode='a') as f:
        f.write(iot_device + "\n")

##
client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.username_pw_set(username=subscriber_username, password=subscriber_password)
client.connect(broker_hostname)
client.loop_forever()

図24 ESP32搭載ボートの起動時に無線LANに接続するプログラム(boot.py)

import network

SSID = 'USP_OFFICE_B'
PASSWORD = 'uspuspusp1234'

wlan_if = network.WLAN(network.STA_IF)
wlan_if.active(True)
wlan_if.connect(SSID, PASSWORD)

図25 パブリッシャのプログラム(main.py)

import time
import dht
from machine import Pin
from umqtt.simple import MQTTClient

mqtt_topic = "confer/room1"
publisher_username = "pub1"
publisher_password = "パスワード"
publisher_id = "room1_esp32"
broker_address = "192.168.1.100"
interval_time = "1"

hf_sensor = Pin(5, Pin.IN, Pin.PULL_UP)
th_sensor = dht.DHT11(Pin(22))

time.sleep(5)

while True:
  value1 = hf_sensor.value()
  th_sensor.measure()
  value2 = th_sensor.temperature()
  value3 = th_sensor.humidity()
  iot_value = str(value1)+","+str(value2)+","+str(value3)
  print(iot_value)
  publisher = MQTTClient(publisher_id,broker_address,user=publisher_username,password=publisher_password)
  publisher.connect()
  publisher.publish(mqtt_topic, msg=iot_value)
  publisher.disconnect()
  time.sleep(int(interval_time))

Raspberry Piを100%活用しよう(Vol.77掲載)

投稿日:2022.03.25 | カテゴリー: コード

筆者:米田 聡

小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。第10回は、サーボモーターを制御できる拡張基板を扱います。

シェルスクリプトマガジン Vol.77は以下のリンク先でご購入できます。

図6 チャンネル1に接続したSG90を動かすサンプルプログラム(sv.py)

import smbus, SVO
import time

servo = SVO.SVO()

#init

servo.Servo_Switch('b', '0')         # 両チャンネルオフ
servo.Set_servo_cycle('20000')       # PWMピリオド20ミリ秒
servo.Set_servo_duty_min('b','500')  # 最小デューティサイクル0.5ミリ秒
servo.Set_servo_duty_max('b','2400') # 最大デューティサイクル2.4ミリ秒
servo.Set_servo_duty('b', '500')     # -90度に初期化
servo.Set_servo_Write()              # 初期値を書き込み

# 作動
servo.Servo_Switch('b', '1')         # 両チャンネルオン
time.sleep(3)

for r in range(10, 190, 10):         # 10度から180度まで
    print("r=" + str(r))
    sval = str(int( r*1900/180 + 500 ))
    servo.Set_servo_duty('1', sval)
    servo.Set_servo_Write()
    time.sleep(3)

機械学習ことはじめ(Vol.77掲載)

投稿日:2022.03.25 | カテゴリー: コード

筆者:川嶋 宏彰

本連載では、機械学習の基礎となるさまざまな手法の仕組みや、それらの手法のPythonでの利用方法を解説していきます。最終回となる今回は、ニューラルネットの仕組みと、基本的なモデルについて解説します。

シェルスクリプトマガジン Vol.77は以下のリンク先でご購入できます。

図3 単純パーセプトロンを学習するための関数を定義するPythonコード

import numpy as np

def h_step(x):
  """ ステップ関数: 0より大きければ1、それ以外は0 """
  return int(x > 0)
def train_perceptron(X, y, lr=0.5, max_it=20, random_state=None):
  """ パーセプトロンの学習アルゴリズム
      lr: 学習率 (learning rate)
      max_it: 最大反復回数 (maximum number of iterations)
  """
  N, d = X.shape  # データ数x次元
  X1 = np.c_[np.ones(N), X]  # 1だけの列を1列目に追加
  d1 = d + 1  # バイアス項込みの次元数
  np.random.seed(random_state)
  w = np.random.rand(d1)  # (w0, w1, w2)
  print('initial w', w)
  w_log = [w.copy()]  # 初期の重み係数
  info_log = [[-1] * 5]  # [it, i, y, o, y-o]
  for it in range(max_it):  # ① 反復 (iteration)
    print('--- iteration:', it)
    err = 0
    for i in range(N):  # ② 各データを入力
      x = X1[i, :]
      y_pred = h_step(np.dot(w, x))
      err += (y[i] - y_pred) ** 2
      print('yhat:', y_pred, 'y:', y[i])
      if y_pred != y[i]:
        w += lr * (y[i] - y_pred) * x  # ③ wを更新
      w_log.append(w.copy())
      info_log.append([it, i, y[i], y_pred, y[i] - y_pred])
    print('err:', err)
    if err == 0:
      print('## converged @ it=', it)
      break
  return w_log, info_log
def get_fourpoints(xor_flag=False):
  """4点のデータを準備
     xor_flag: 各データのラベルをどう設定するか
         True: 入力が異なる符号なら1, False: 入力が共に正なら1
  """
  X = np.array([[-1, -1], [-1, 1], [1, -1], [1, 1]])
  y = np.array([0, 1, 1, 0]) if xor_flag else np.array([0, 0, 0, 1])
  return X, y

図4 単純パーセプトロンの学習過程を可視化する関数を定義するPythonコード

import matplotlib.pyplot as plt
import seaborn as sns

plt.rcParams['font.size'] = 14
def plot_2dline_with_data(X, y, w, title='', i=-1):
  """ データと w0 + w1 x1 + w2 x2 = 0 をプロット """
  fig = plt.figure(figsize=(5, 5))
  sns.scatterplot(x=X[:, 0], y=X[:, 1], hue=y, s=150)
  xlim = [-1.5, 1.5]
  ylim = [-1.5, 1.5]
  plt.xlim(xlim)
  plt.ylim(ylim)
  plt.xticks([-1, 1])
  plt.yticks([-1, 1])
  # w0 + w1*x1 + w2*x2 = 0 のプロット
  if w[2] == 0:  # w2 == 0 のとき: x1 = -w0/w1
    x2 = np.linspace(*ylim, 2)
    plt.plot([-w[0]/w[1]] * x2.size, x2, '-', linewidth=3, color='r')
  else:  # w2 != 0 のとき: x2 = -(w0 + w1*x1)/w2
    x1 = np.linspace(*xlim, 2)
    plt.plot(x1, -(w[0] + w[1]*x1)/w[2], '-', linewidth=3, color='r')
  if i >= 0: plt.scatter(X[i, 0], X[i, 1], s=300, facecolor='none', 
                         edgecolor='r', linewidth=2)
  plt.title(title)
  return fig

図5 単純パーセプトロンの学習と結果表示をするPythonコード

# データの読み込みと学習
xor_flag = False  # (★) True: XOR,  False: AND
X, y = get_fourpoints(xor_flag)  # 学習データを取得
print(f' X:\n{X},\n y: {y}')
w_log, info_log = train_perceptron(X, y, random_state=0)
# 学習過程の表示
for step in range(len(w_log)):
  title = 'it: {}, i: {}, y: {}, y_pred: {}, y - y_pred:{}'\
          .format(*info_log[step])
  print(title)
  w = w_log[step]
  it = info_log[step][0]
  i = info_log[step][1]
  print('w:', w)
  plot_flag = False if (it >= 3) and (it % 5 != 0) else True
  if plot_flag:
    plot_2dline_with_data(X, y, w, title, i)
    plt.show() 

図12 決定境界を可視化するための関数を定義するPythonコード

import seaborn as sns
import matplotlib as mpl

def plot_decision_boundary(X, y, clf, xylabels=None, palette=None, fig=None, ngrid=50):
  """ 分類器 clf の決定境界を描画 """
  if fig is None: fig = plt.figure(figsize=(5, 5))
  else: plt.figure(fig.number)
  # 2次元空間にグリッド点を準備
  xmin = X.min(axis=0)  # 各列の最小値
  xmax = X.max(axis=0)  # 各列の最大値
  xstep = [(xmax[j]-xmin[j]) / ngrid for j in range(2)]  # グリッドのステップ幅
  xmin = [xmin[j] - 8*xstep[j] for j in range(2)]  # 少し広めに
  xmax = [xmax[j] + 8*xstep[j] for j in range(2)]
  aranges = [np.arange(xmin[j], xmax[j] + xstep[j], xstep[j]) for j in range(2)]
  x0grid, x1grid = np.meshgrid(*aranges)
  # 各グリッド点でクラスを判定
  y_pred = clf.predict(np.c_[x0grid.ravel(), x1grid.ravel()])
  y_pred = y_pred.reshape(x0grid.shape)  # 2次元へ
  y_pred = np.searchsorted(np.unique(y_pred), y_pred)  # 値をindexへ
  clist = palette.values() if type(palette) is dict else palette
  cmap = mpl.colors.ListedColormap(clist) if palette is not None else None
  plt.contourf(x0grid, x1grid, y_pred, alpha=0.3, cmap=cmap)
  sns.scatterplot(x=X[:, 0], y=X[:, 1], hue=y, palette=palette)
  plt.legend()
  plt.xlim([xmin[0], xmax[0]])
  plt.ylim([xmin[1], xmax[1]])
  if xylabels is not None:
    plt.xlabel(xylabels[0])
    plt.ylabel(xylabels[1])
  return fig

図13 多層パーセプトロンによる学習をするPythonコード

from sklearn.neural_network import MLPClassifier

clf = MLPClassifier(hidden_layer_sizes=3, learning_rate_init=0.05, random_state=0)
# XORデータの準備
xor_flag = True  # True: XOR,  False: AND
X, y = get_fourpoints(xor_flag)  # 学習データを取得
print(f' X:\n{X},\n y: {y}')
clf.fit(X, y)  # 学習(誤差逆伝播法)の実行
plot_decision_boundary(X, y, clf)  # 決定境界
plt.show()

図15 学習曲線や結合荷重などを表示するPythonコード

# 学習曲線の表示
plt.plot(range(1, clf.n_iter_ + 1), clf.loss_curve_)
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.ylim(0,)
plt.show()
# 構造を確認
print('n_outputs:', clf.n_outputs_)  # 出力層の素子数
print('n_layers (with input layer):', clf.n_layers_)  # 入力層を含めた層の数
# 結合荷重
print('coefs:\n', clf.coefs_)  # バイアス項以外の結合荷重(行列のリスト)
print('intercepts:\n', clf.intercepts_)  # バイアス項(ベクトルのリスト)

図17 Breast Cancerデータセットの準備をするPythonコード

from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_curve, roc_auc_score
from sklearn.preprocessing import StandardScaler

data = load_breast_cancer()  # データセット読み込み
X_orig = data['data']
y = 1 - data['target']  # 悪性を1、良性を0とする正解ラベル
X = X_orig[:, :10]  # 一部の特徴量を利用する
# 訓練データとテストデータに2分割する(検証データは今回は切り分けない)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,
                                                    random_state=1)
# ROCカーブの表示用に関数を準備
def plot_roc(fpr, tpr, marker=None):
    plt.figure(figsize=(5, 5))
    plt.plot(fpr, tpr, marker=marker)
    plt.xlabel('False Positive Rate')
    plt.ylabel('True Positive Rate')
    plt.grid()

図18 Breast Cancerデータセットによる学習と評価をするPythonコード

clf = MLPClassifier(hidden_layer_sizes=(5, 3), learning_rate_init=0.005, random_state=1)
with_scaling = False  # (★)標準化するか否か
if with_scaling:  # 標準化によるスケーリングあり
  scaler = StandardScaler().fit(X_train)  # 訓練データで平均と標準偏差を求める
  Xscaled_train = scaler.transform(X_train)  # X_train -> Xscaled_train
  Xscaled_test = scaler.transform(X_test)  # X_test -> Xscaled_test
  clf.fit(Xscaled_train, y_train)  # 学習
  y_proba_train = clf.predict_proba(Xscaled_train)  # 訓練データで事後確率予測
  y_proba_test = clf.predict_proba(Xscaled_test)  # テストデータ事後確率予測
else:  # 標準化なし(そのまま)
  clf.fit(X_train, y_train)  # 学習
  y_proba_train = clf.predict_proba(X_train)  # 訓練データで事後確率予測
  y_proba_test = clf.predict_proba(X_test)  # テストデータ事後確率予測
# テストデータに対するROCカーブ
fpr, tpr, threshold = roc_curve(y_test, y_proba_test[:, 1])
plot_roc(fpr, tpr)
plt.show()
# 訓練・テストデータのAUC(Area Under the Curve)スコア
print('AUC (train):', roc_auc_score(y_train, y_proba_train[:, 1]))
print('AUC (test):', roc_auc_score(y_test, y_proba_test[:, 1]))

図21 PyTorchによる画像認識の準備をするPythonコード

import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision
from torchvision import datasets
from torchvision import transforms as transforms
import matplotlib.pyplot as plt
import numpy as np

# GPUが使えるか否かでデータの転送先を指定
device = 'cuda:0' if torch.cuda.is_available() else 'cpu'
img_mean, img_std = (0.1307, 0.3081)  # 画素値の標準化用
# 標準化などの前処理を設定
trans = transforms.Compose([
  transforms.ToTensor(),
  transforms.Normalize((img_mean,), (img_std,))
])
# データセットのダウンロード
rootdir = './data'  # ダウンロード先
train_dataset = datasets.MNIST(root=rootdir, train=True,
  transform=trans, download=True)
test_dataset = datasets.MNIST(root=rootdir, train=False,
  transform=trans, download=True)
# データを読み込む専用のクラスDataLoaderを準備
batch_size = 32
train_loader = torch.utils.data.DataLoader(
  dataset=train_dataset, batch_size=batch_size, shuffle=True)
test_loader = torch.utils.data.DataLoader(
  dataset=test_dataset, batch_size=batch_size, shuffle=False)
# クラス情報
num_classes = len(train_dataset.classes)
cls_names = [f'{i}' for i in range (num_classes)]
# 画像表示用の関数を定義しておく
def tensor_imshow(img, title=None):
  """ Tensorを画像として表示 """
  img = img.numpy().transpose((1, 2, 0))  # (C,H,W)->(H,W,C)
  img = img_std * img + img_mean
  plt.imshow(np.clip(img, 0, 1))
  if title is not None:
    plt.title(title)
  plt.show()
def test_with_examples(model, loader):
  """ loaderのミニバッチの画像を予測結果と共にグリッド表示
    model: 予測に用いるニューラルネット
    loader: DataLoader
  """
  model.eval()  # 推論モード
  with torch.no_grad():  # 推論のみ(勾配計算なし)
    imgs, labels = next(iter(loader))  # ミニバッチ取得
    imgs_in = imgs.view(-1, imgs.shape[2]*imgs.shape[3])
    outputs = model(imgs_in.to(device))  # 順伝播による推論
  _, pred = torch.max(outputs, 1)  # 予測ラベル
  grid = torchvision.utils.make_grid(imgs)  # グリッド画像生成
  title_str1 = '\n'.join(
    [', '.join([cls_names[y] for y in x]) 
      for x in pred.view(-1, 8).tolist()])
  title_str2 = '\n'.join(
    [', '.join([cls_names[y] for y in x]) 
      for x in labels.view(-1, 8).tolist()])
  tensor_imshow(grid, title='Predicted classes:\n'
    + f'{title_str1}\n\nTrue classes:\n{title_str2}\n')
# 訓練データ例の表示
print('\n--- Training data (example) ---\n')
imgs, labels = next(iter(train_loader))
grid = torchvision.utils.make_grid(imgs)
title_str = '\n'.join([', '.join([cls_names[y] for y in x])
  for x in labels.view(-1, 8).tolist()])
tensor_imshow(grid, title=f'True classes:\n{title_str}\n')
# ニューラルネットの構造を定義
class Net(nn.Module):
  def __init__(self):
    super(Net, self).__init__()
    self.fc1 = nn.Linear(28*28, 512)
    self.fc2 = nn.Linear(512, 256)
    self.fc3 = nn.Linear(256, num_classes)
  def forward(self, x):
    x = F.relu(self.fc1(x))
    x = F.relu(self.fc2(x))
    x = self.fc3(x)  # 活性化関数なし(損失関数側でsoftmax)
    return x
net = Net()  # インスタンス生成
net = net.to(device)  # モデルをGPUへ転送(もしGPUがあれば)
# 学習前にテストデータを入力してみる
print('\n--- Result BEFORE training ---\n')
test_with_examples(net, test_loader)

図22 PyTorchによるニューラルネットの学習と結果表示をするPythonコード

# 損失関数(softmax + cross entropy)
criterion = nn.CrossEntropyLoss()
# 最適化手法の選択
optimizer = torch.optim.SGD(net.parameters(), lr=0.01)
# 学習のループ
num_epochs = 5
train_loss_log = []
train_acc_log = []
for epoch in range(num_epochs):
  print(f'Epoch {epoch + 1}/{num_epochs}\n', '-' * 10)
  # 訓練フェーズ
  sum_loss, sum_ok = [0.0, 0]
  for inputs, labels in train_loader:  # ミニバッチ入力
    inputs = inputs.view(-1, 28*28).to(device)
    labels = labels.to(device)  # deviceに転送
    outputs = net(inputs)  # 順伝播
    loss = criterion(outputs, labels)  # 損失計算
    optimizer.zero_grad()  # 勾配をクリア
    loss.backward()  # 誤差逆伝播
    optimizer.step()  # パラメータ更新
    # ミニバッチの評価値(1バッチ分)を計算
    _, preds = torch.max(outputs, 1)  # 予測ラベル
    sum_loss += loss.item() * inputs.size(0)  # 損失
    sum_ok += torch.sum(preds == labels.data)  # 正解数
  # 1エポック分の評価値を計算
  e_loss = sum_loss / len(train_dataset)  # 平均損失
  e_acc = sum_ok.cpu().double() / len(train_dataset)  # 正解率
  print(f'Loss: {e_loss:.4f} Acc: {e_acc:.4f}')
  train_loss_log.append(e_loss)
  train_acc_log.append(e_acc)
# 学習曲線をプロット
fig = plt.figure(figsize=(12, 6))
fig.add_subplot(121)  # 損失
plt.plot(range(1, num_epochs + 1), train_loss_log, '.-')
plt.title('Training data')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.ylim(0)
fig.add_subplot(122)  # 正解率
plt.plot(range(1, num_epochs + 1), train_acc_log, '.-')
plt.title('Training data')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.show()
# テストデータを入力してみる
print('\n--- Result AFTER training ---\n')
test_with_examples(net, test_loader)

Pythonあれこれ(Vol.77掲載)

投稿日:2022.03.25 | カテゴリー: コード

著者:飯尾 淳

本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温
かく見守ってください。皆さんと共に勉強していきましょう。第7回は、Pythonで実装されたWebアプリケーションフレームワーク「Flask」の使い方と、Pythonのユニークな文法である「デコレータ」について紹介します。

シェルスクリプトマガジン Vol.77は以下のリンク先でご購入できます。

図5 ルーティングのサンプルWebアプリケーションのコード

from flask import Flask
app = Flask(__name__)
@app.route('/hello')
def hello_flask():
  return '<h1>Hello, Flask!</h1>'
@app.route('/goodbye')
def goodbye_flask():
  return '<h1>Good bye, Flask!</h1>'

図7 簡単なメッセージを表示するプログラム

#!/usr/bin/env python

def hello():
  print('Hello, World')
def goodbye():
  print('Goodbye, World')
def main():
  hello()
  goodbye()
if __name__ == '__main__':
  main()

図9 デコレータを追加したプログラム

#!/usr/bin/env python

def deco(func):
  def decorator():
    print('-- start --')
    func()
    print('-- end --')
  return decorator
@deco
def hello():
  print('Hello, World')
@deco
def goodbye():
  print('Goodbye, World')
def main():
  hello()
  goodbye()
if __name__ == '__main__':
  main()

図11 Flaskのルーティング設定処理を模したプログラム

#!/usr/bin/env python
 
class Flask():
  def __init__(self):
    self.url_map = {}
    print(f'url_map on init: {self.url_map}')
  def add_url_rule(self, rule, func):
    self.url_map[rule] = func
  def route(self, rule):
    def decorator(f):
      self.add_url_rule(rule, f)
      return f
    return decorator
  def dispatch_request(self, rule):
    return self.url_map[rule]()

app = Flask()
print(f'url_map after create: {app.url_map}')
@app.route('/index')
def index():
  print("hello world")
print(f'url_map after function def: {app.url_map}')
app.dispatch_request('/index')

図13 図9のプログラムを修正した「greeting_deco2.py」

#!/usr/bin/env python
 
def deco(rule):
  def decorator(func):
    print(f'rule = \'{rule}\', func = {func}')
    return func
  return decorator
@deco('/hello')
def hello():
  print('Hello, World')
@deco('/goodbye')
def goodbye():
  print('Goodbye, World')

図15 デコレータを使わないように修正した「greeting_deco3.py」

#!/usr/bin/env python

def deco(rule):
  def decorator(func):
    print(f'rule = \'{rule}\', func = {func}')
    return func
  return decorator
def hello():
  print('Hello, World')
hello = deco('/hello')(hello)
def goodbye():
  print('Goodbye, World')
goodbye = deco('/goodbye')(goodbye)

図16 関数の挙動を変えるデコレータの使用例

#!/usr/bin/env python

def plus_n(n):
  def decorator(func):
    def func_modified(*args, **kwargs):
      return (func(*args, **kwargs) + n)
    return func_modified
  return decorator
@plus_n(3)
def square(x):
  return x*x
def main():
  print(square(2))
if __name__ == '__main__':
  main()

図17 関数に複数のデコレータを適用したプログラム「greeting_deco4.py」

#!/usr/bin/env python

def DECO(func):
  def decorator():
    print('-- START --')
    func()
    print('-- END --')
  return decorator
def deco(func):
  def decorator():
    print('-- start --')
    func()
    print('-- end --')
  return decorator
@DECO
@deco
def hello():
  print('Hello, World')
def main():
  hello()
if __name__ == '__main__':
  main()

香川大学SLPからお届け!(Vol.77掲載)

投稿日:2022.03.25 | カテゴリー: コード

著者:石上椋一

毎年年末に開催されている競馬の「有馬記念」というレースをご存知でしょうか。今回は、その有馬記念の順位を予測するAIを開発した話を紹介します。2021年12月には、開発したAIを使って第66回有馬記念の順位を予測してみました。結果がどうだったのかについては、記事の最後に書いています。

シェルスクリプトマガジン Vol.77は以下のリンク先でご購入できます。

図2 RankNetを実装するPythonコード

from tensorflow.keras import layers, Model, Input
from tensorflow.nn import leaky_relu

class RankNet(Model):
  def __init__(self):
    super().__init__()
    self.dense = [layers.Dense(16, activation=leaky_relu),
                  layers.Dense(8, activation=leaky_relu)]
    self.o = layers.Dense(1, activation='linear')
    self.oi_minus_oj = layers.Subtract()
  def call(self, inputs):
    xi, xj = inputs
    densei = self.dense[0](xi)
    densej = self.dense[0](xj)
    for dense in self.dense[1:]:
      densei = dense(densei)
      densej = dense(densej)
    oi = self.o(densei)
    oj = self.o(densej)
    oij = self.oi_minus_oj([oi, oj])
    output = layers.Activation('sigmoid')(oij)
    return output

図3 データの整理とラベル付けをするPythonコード

import pandas as pd
import numpy as np
from itertools import combinations

years = [2020,2019,2018,2017,2016,2015,2014,2013,2012,2011]
# ここで任意のCSVファイルを指定する
# 今回はCSVにデータを残しているため、CSVから読み込んでいる
df = pd.read_csv(CSVFile)
df["タイム指数2-3"] = df["タイム指数2"] - df["タイム指数3"]
index_num = 0
xi = xj = pij = pair_ids = pair_query_id = []
for year in years:
  one_year_Data = df[df['年数'] == year]
  index_list = [i for i in range(len(one_year_Data))]
  random.shuffle(index_list)
  for pair_id in combinations(index_list, 2):
    pair_query_id.append(year)
    pair_ids.append(pair_id)
    i = pair_id[0]
    j = pair_id[1]
    xi.append([one_year_Data.at[i+index_num,"タイム指数2"],
               one_year_Data.at[i+index_num,"タイム指数2-3"],
               one_year_Data.at[i+index_num,"上り"]])
    xj.append([one_year_Data.at[j+index_num,"タイム指数2"],
               one_year_Data.at[j+index_num,"タイム指数2-3"],
               one_year_Data.at[j+index_num,"上り"]])
    if one_year_Data.at[i+index_num,"順位"] == one_year_Data.at[j+index_num,"順位"] :
      pij_com = 0.5
    elif one_year_Data.at[i+index_num,"順位"] > one_year_Data.at[j+index_num,"順位"] :
      pij_com = 0
    else:
      pij_com = 1
    pij.append(pij_com)
  index_num += len(one_year_Data)
  index_list.clear()
xi = np.array(xi)
xj = np.array(xj)
pij = np.array(pij)
pair_query_id = np.array(pair_query_id)

図4 学習用データと評価用データを仕分けるPythonコード

from sklearn.model_selection import train_test_split

xi_train, xi_test, xj_train, xj_test, pij_train, pij_test, \
pair_id_train, pair_id_test = train_test_split(
  xi, xj, pij, pair_ids, test_size=0.2, stratify=pair_query_id)

図5 コンパイルと学習を実施するPythonコード

ranknet = RankNet()
# コンパイル
ranknet.compile(optimizer='sgd', loss='binary_crossentropy',metrics=['accuracy'])
# 学習
ranknet.fit([xi_train, xj_train], pij_train,
            epochs=85, batch_size=4,
            validation_data=([xi_test, xj_test], pij_test))

図8 ResNet50を使った学習モデルを実装するPythonコード

from tensorflow.keras.applications.resnet50 import ResNet50
from keras.layers import Dense, Dropout, Input, Flatten

# ResNetの準備
input_tensor = Input(shape=(64, 64, 3))
resnet50 = ResNet50(include_top=False, weights='imagenet',
                    input_tensor=input_tensor)
# FC層の準備
fc_model = Sequential()
fc_model.add(Flatten(input_shape=resnet50.output_shape[1:]))
fc_model.add(Dense(512, activation='relu'))
fc_model.add(Dropout(0.3))
fc_model.add(Dense(2, activation='sigmoid'))
# モデルの準備
resnet50_model = Model(resnet50.input, fc_model(resnet50.output))
# ResNet50の一部の重みを固定
for layer in resnet50_model.layers[:100]:
  layer.trainable = False

図9 コンパイルと学習を実施するPythonコード

# コンパイル
resnet50_model.compile(optimizer='sgd',
                       loss='categorical_crossentropy',
                       metrics=['accuracy'])
# 学習
history = resnet50_model.fit(train_generator, 
                             batch_size=4, 
                             epochs=50, verbose=1,
                             validation_data=val_generator)

Vol.77 補足情報

投稿日:2022.03.25 | カテゴリー: コード

特別企画 ESP32とラズパイで作る本格IoT

 p.41の左側中央にある「>>> upip,install(‘umqtt.simple’)」の「,」(カンマ)は「.」(ピリオド)の誤りです。お詫びして訂正します。

>>> upip.install('umqtt.simple')

 p.47の右側中央やや上にある「最後の「COM5」がCOM番号です。「5」の数字は、使っているパソコンの環境によって変わりますので、」の「COM5」は「COM9」、「5」は「9」の誤りです。お詫びして訂正します。

情報は随時更新致します。

シェルスクリプトマガジンvol.77 Web掲載記事まとめ

投稿日:2022.03.25 | カテゴリー: コード

004 レポート 古いPC向けOS「Chrome OS Flex」
005 レポート WebブラウザでDebianが動作
006 NEWS FLASH
008 特集1 AWKプログラミング入門/斉藤博文 コード掲載
017 Hello Nogyo!
018 特集2 MIRACLE LINUXという選択肢/弦本春樹
022 特集3 デスクトップ版Power Automateを活用しよう/三沢友治
032 特別企画 ESP32とラズパイで作る本格IoT/魔法少女 コード掲載
050 先端技術 量子コンピュータ
052 Raspberry Piを100%活用しよう/米田聡 コード掲載
056 機械学習ことはじめ/川嶋宏彰 コード掲載
068 Pythonあれこれ/飯尾淳 コード掲載
074 JSON/桑原滝弥、イケヤシロウ
076 中小企業手作りIT化奮戦記/菅雄一
080 法林浩之のFIGHTING TALKS/法林浩之
082 タイ語から分かる現地生活/つじみき
088 香川大学SLPからお届け!/石上椋一 コード掲載
094 Bash入門/大津真
104 Techパズル/gori.sh
105 コラム「ユニケージ流の業務システム開発」/シェル魔人

特集1 AWKプログラミング入門(Vol.77記載)

投稿日:2022.03.25 | カテゴリー: コード

著者:斉藤 博文

シェルスクリプトで複雑なテキストデータ処理を実行する場合、「AWK」(オーク)というプログラミング言語が利用されます。AWKの処理系(実行環境)はとても小さく、シェルスクリプトと組み合わせて使うのに適しています。本特集では、このAWKのプログラミングを分かりやすく解説します。

シェルスクリプトマガジン Vol.77は以下のリンク先でご購入できます。

図8 ユーザー定義関数「factorial」の実装例

{
    print factorial($0);
}

function factorial(n,    i, ret) {
    ret = 1;

    for (i = 1; i <= n; i++) {
        ret *= i
    }

    return ret;
}

図9 改変した階乗処理のAWKプログラム(factorial.awk)

BEGIN {
    n = n ? n : 5;

    print factorial(n);
}

function factorial(n) {
    ret = 1;

    for (i = 1; i <= n; i++) {
        ret *= i
    }

    return ret;
}

Vol.76 補足情報

投稿日:2022.01.25 | カテゴリー: コード

訂正・補足情報はありません。
情報は随時更新致します。

シェルスクリプトマガジンvol.76 Web掲載記事まとめ

投稿日:2022.01.25 | カテゴリー: コード

004 レポート Visual Studio 2022と.NET 6
005 レポート 超高速リンカー「mold」
008 特集1 Linuxカーネルの仕組み/平田豊 コード掲載
024 特集2 micro:bit v2を使おう/斉暁
034 特集3 国産のGroupSession/大場雄一郎
048 Raspberry Piを100%活用しよう/米田聡  コード掲載
050 機械学習ことはじめ/川嶋宏彰  コード掲載
061 Hello Nogyo!
062 香川大学SLPからお届け!/三枝泰士
066 法林浩之のFIGHTING TALKS/法林浩之
068 タイ語から分かる現地生活/つじみき
072 中小企業手作りIT化奮戦記/菅雄一
076 デジタル庁/桑原滝弥、イケヤシロウ
078 Pythonあれこれ/飯尾淳  コード掲載
084 特別レポート/松浦智之  コード掲載
090 Bash入門/大津真
098 Techパズル/gori.sh
099 コラム「ユニケージ流プロジェクトの初期段階」/シェル魔人

特集1 Linuxカーネルの仕組み(Vol.76記載)

投稿日:2022.01.25 | カテゴリー: コード

著者:平田 豊

LinuxというOSの中核となるのが「Linuxカーネル」というソフトウエアです。Linuxカーネルの動作を理解するには、ソースコードを追いかける必要があります。しかし、いきなりソースコードを読んでもよく分からないものです。本特集記事では、学習の「入り口」となるように、学習用の環境を構築する方法を紹介します。また、Linuxカーネルの一部の機能をピックアップして、その実装について解説します。本特集をきっかけにLinuxカーネルのソースコード読解を始めましょう。

シェルスクリプトマガジン Vol.76は以下のリンク先でご購入できます。

図1 OS レスプログラムのイメージ

for (;;) {
    // スイッチが押された
    if (isSwitchOn()) {
        // 要因クリア
        ClearSwitch();
        // LED点灯
        TurnOnLED();
    }
    // スイッチが離された
    if (isSwitchOff()) {
        // 要因クリア
        ClearSwitch();
        // LED点灯
        TurnOffLED();
    }
    // ディレー
    delay();
}

図7 QEMU の起動用シェルスクリプト

#!/bin/sh

BUILD_PATH="${HOME}/buildroot-2021.08.1"
IMAGE_DIR="${BUILD_PATH}/output/images"
exec qemu-system-aarch64 \
    -M virt \
    -cpu cortex-a53 \
    -nographic \
    -smp 2 \
    -m 512 \
    -kernel ${IMAGE_DIR}/Image \
    -append "rootwait root=/dev/vda console=ttyAMA0" \
    -drive file=${IMAGE_DIR}/rootfs.ext4,if=none,format=raw,id=hd0 \
    -device virtio-blk-device,drive=hd0 \
    -netdev user,id=eth0 \
    -device virtio-net-device,netdev=eth0 \
    -object rng-random,filename=/dev/urandom,id=rng0 \
    -device virtio-rng-pci,rng=rng0

図8 Linux カーネルに追加するコード

(略)
static int run_init_process(const char *init_filename)
{
        const char *const *p;
    printk("hoge 0x%016lx (%zu)\n", jiffies, sizeof(jiffies));
        argv_init[0] = init_filename;
(略)

図11 init デーモンを起動する箇所のソースコード

if (!try_to_run_init_process("/sbin/init") ||
    !try_to_run_init_process("/etc/init") ||
    !try_to_run_init_process("/bin/init") ||
    !try_to_run_init_process("/bin/sh"))
    return 0;

panic("No working init found.  Try passing init= option to kernel. "
      "See Linux Documentation/admin-guide/init.rst for guidance.");

図12 buildroot 環境のC ライブラリにおけるsyscall 関数の実装

ENTRY (syscall)
    uxtw    x8, w0
    mov x0, x1
    mov x1, x2
    mov x2, x3
    mov x3, x4
    mov x4, x5
    mov x5, x6
    mov x6, x7
    svc 0x0
    cmn x0, #4095
    b.cs    1f
    RET

図13 システムコールに対応する関数を呼び出すためのマクロ定義の例

#define __SYSCALL_DEFINEx(x, name, ...)                     \
(略)
    asmlinkage long __arm64_sys##name(const struct pt_regs *regs)       \
    {                                   \
        return __se_sys##name(SC_ARM64_REGS_TO_ARGS(x,__VA_ARGS__));    \
    }                                   \
(略)
    static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))

図14 machine_halt() 関数のコード

void machine_halt(void)
{
    local_irq_disable();
    smp_send_stop();
    while (1);
}

図15 machine_power_off() 関数のコード

void machine_power_off(void)
{
    local_irq_disable();
    smp_send_stop();
    if (pm_power_off)
        pm_power_off();
}

図17 schedule() 関数のコード

asmlinkage __visible void __sched schedule(void)
{
    struct task_struct *tsk = current;

    sched_submit_work(tsk);
    do {
        preempt_disable();
        __schedule(false);
        sched_preempt_enable_no_resched();
    } while (need_resched());
    sched_update_worker(tsk);
}
EXPORT_SYMBOL(schedule);

図18 FIFO からのデータを出力するプログラムのコード

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char **argv){
  int i, fd = -1;
  ssize_t sz;
  char *filename, buf[64];
  if (argc == 1) {
    printf("Usage: %s fifo\n", argv[0]);
    exit(1);
  }
  filename = argv[1];
  fd = open(filename, O_RDWR);
  if (fd == -1) {
    perror("open");
    goto error;
  }
  sz = read(fd, buf, sizeof(buf));
  if (sz == -1) {
    perror("read");
    goto error;
  }
  for (i = 0 ; i < sz ; i++) {
    printf("%02x ", buf[i]);
  }
  printf("\n");

error:
  if (fd != -1) close(fd);
}

図19 wait_event_interruptible_exclusive() 関数のマクロ定義

#define __wait_event_interruptible_exclusive(wq, condition)         \
    ___wait_event(wq, condition, TASK_INTERRUPTIBLE, 1, 0,          \
              schedule())

#define wait_event_interruptible_exclusive(wq, condition)           \
({                                      \
    int __ret = 0;                              \
    might_sleep();                              \
    if (!(condition))                           \
        __ret = __wait_event_interruptible_exclusive(wq, condition);    \
    __ret;                                  \
})

図20 msleep() 関数のコード

void msleep(unsigned int msecs)
{
    unsigned long timeout = msecs_to_jiffies(msecs) + 1;

    while (timeout)
        timeout = schedule_timeout_uninterruptible(timeout);
}

図21 process_timeout() 関数とschedule_timeout() 関数のコード

static void process_timeout(struct timer_list *t)
{
    struct process_timer *timeout = from_timer(timeout, t, timer);

    wake_up_process(timeout->task);
}
(略)
signed long __sched schedule_timeout(signed long timeout)
{
(略)
    expire = timeout + jiffies;

    timer.task = current;
    timer_setup_on_stack(&timer.timer, process_timeout, 0);
    __mod_timer(&timer.timer, expire, MOD_TIMER_NOTPENDING);
    schedule();
(略)
}

図22 mutex_lock() 関数のコード

void __sched mutex_lock(struct mutex *lock)
{
    might_sleep();

    if (!__mutex_trylock_fast(lock))
        __mutex_lock_slowpath(lock);
}

図23 preempt_enable() 関数のマクロ定義

#ifdef CONFIG_PREEMPTION
#define preempt_enable() \
do { \
    barrier(); \
    if (unlikely(preempt_count_dec_and_test())) \
        __preempt_schedule(); \
} while (0)
#else /* !CONFIG_PREEMPTION */
#define preempt_enable() \
do { \
    barrier(); \
    preempt_count_dec(); \
} while (0) 

図24 ヘッダーファイルでのjiffies 変数の宣言

extern u64 jiffies_64;
extern unsigned long volatile jiffies;

図25 jiffies_64 変数の実体の定義

u64 jiffies_64 = INITIAL_JIFFIES;

図26 INITIAL_JIFFIES 定数の定義

#define INITIAL_JIFFIES ((unsigned long)(unsigned int) (-300*HZ))

Raspberry Piを100%活用しよう(Vol.76掲載)

投稿日:2022.01.25 | カテゴリー: コード

著者:米田 聡

小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。第9回は、人感センサーを搭載する拡張基板を扱います。

シェルスクリプトマガジン Vol.76は以下のリンク先でご購入できます。

図3 焦電赤外線センサ拡張基板のサンプルプログラム(ADRSZPY.py)

import RPi.GPIO as GPIO
import time

IR_PORT=5

GPIO.setmode(GPIO.BCM)
GPIO.setup(IR_PORT, GPIO.IN)

try:
    while True:
        if GPIO.input(IR_PORT) == GPIO.HIGH:
            print("There is human")
        else:
            print("Nothing")
        
        time.sleep(1)


except KeyboardInterrupt:
        pass

GPIO.cleanup()

機械学習ことはじめ(Vol.76掲載)

投稿日:2022.01.25 | カテゴリー: コード

著者:川嶋 宏彰

本連載では、機械学習の基礎となるさまざまな手法の仕組みや、それらの手法のPythonでの利用方法を解説していきます。今回は、複数のモデルを束ねて用いるアンサンブル学習について解説します。

シェルスクリプトマガジン Vol.76は以下のリンク先でご購入できます。

図2 データセットの読み込みから、散布図のプロットまでを実施するPythonコード

import seaborn as sns
import matplotlib.pyplot as plt

plt.rcParams['font.size'] = 14
penguins = sns.load_dataset('penguins')
# 取り出す特徴量
features = ['bill_depth_mm', 'body_mass_g']  # (★)
# features = ['bill_depth_mm', 'bill_length_mm']  # 取り出す特徴量を変える
# 対象とするペンギン種
target_species = ['Adelie', 'Gentoo']  # (★)
# target_species = ['Adelie', 'Chinstrap', 'Gentoo']  # 3種のペンギンにする
# 今回用いる特徴量をクラスラベルと共に取り出す
df = penguins[['species'] + features].copy()
df.dropna(inplace=True)  # NaN が含まれる行は削除
# 今回用いるペンギン種のみとする
df2 = df[df['species'].isin(target_species)].copy()
print(df2.shape)  # (274, 3) と表示される
# 現在のパレットから用いる種 (target_species) に合わせたパレットを作成
palette = {c: sns.color_palette()[k] for k, c
           in enumerate(df['species'].unique()) if c in target_species}
plt.figure(figsize=(5, 5))
sns.scatterplot(data=df2, x=features[0], y=features[1],
                hue='species', palette=palette)
plt.show()
X = df2[features].values   # 各個体の2次元特徴量(行数=個体数)
y = df2['species'].values  # 各個体のクラスラベル

図3 決定木による2クラス分類をするPythonコード

import matplotlib as mpl
import numpy as np
from sklearn.tree import DecisionTreeClassifier, export_text, plot_tree

def plot_decision_boundary(X, y, clf, xylabels=None, palette=None, fig=None, ngrid=50):
  """ 分類器 clf の決定境界を描画 """
  if fig is None: fig = plt.figure(figsize=(5, 5))
  else: plt.figure(fig.number)
  # 2次元空間にグリッド点を準備
  xmin = X.min(axis=0)  # 各列の最小値
  xmax = X.max(axis=0)  # 各列の最大値
  xstep = [(xmax[j]-xmin[j]) / ngrid for j in range(2)]  # グリッドのステップ幅
  xmin = [xmin[j] - 8*xstep[j] for j in range(2)]  # 少し広めに
  xmax = [xmax[j] + 8*xstep[j] for j in range(2)]
  aranges = [np.arange(xmin[j], xmax[j] + xstep[j], xstep[j]) for j in range(2)]
  x0grid, x1grid = np.meshgrid(*aranges)
  # 各グリッド点でクラスを判定
  y_pred = clf.predict(np.c_[x0grid.ravel(), x1grid.ravel()])
  y_pred = y_pred.reshape(x0grid.shape)  # 2次元に
  y_pred = np.searchsorted(np.unique(y_pred), y_pred)  # 値をインデックスに
  clist = palette.values() if type(palette) is dict else palette
  cmap = mpl.colors.ListedColormap(clist) if palette is not None else None
  plt.contourf(x0grid, x1grid, y_pred, alpha=0.3, cmap=cmap)
  sns.scatterplot(x=X[:, 0], y=X[:, 1], hue=y, palette=palette)
  plt.legend()
  plt.xlim([xmin[0], xmax[0]])
  plt.ylim([xmin[1], xmax[1]])
  if xylabels is not None:
    plt.xlabel(xylabels[0])
    plt.ylabel(xylabels[1])
  return fig
# 決定木
max_depth = 2  #(★)木の深さ
clf_dt = DecisionTreeClassifier(max_depth=max_depth)
clf_dt.fit(X, y)  # 学習
# 決定境界を可視化
plot_decision_boundary(X, y, clf_dt, features, palette)
plt.show()
# 決定木をテキストで表示
tree_text = export_text(clf_dt)
print(tree_text)
# 木の可視化
plt.figure(figsize=(10, 8))
plot_tree(clf_dt, class_names=target_species, feature_names=features)
plt.show()

図13 アンサンブル学習された決定木の表示用関数を定義するPythonコード

from sklearn.base import is_classifier

def plot_trees_and_boundaries(clf, X, y):
  # 決定境界を可視化
  plot_decision_boundary(X, y, clf, features, palette)
  plt.show()
  # 各ベースモデル(決定木)を表示
  for i, clf_dt in enumerate(clf.estimators_[:3]):
    print('-' * 10, i, '-' * 10)
    plt.figure(figsize=(10, 8))
    plot_tree(clf_dt, class_names=target_species, feature_names=features)
    plt.show()
    if is_classifier(clf_dt):  # 分類木ならば決定境界を可視化            
      plot_decision_boundary(X, y, clf_dt, features, palette)
      plt.show()

図14 ランダムフォレストによる分類器の学習をするPythonコード

from sklearn.ensemble import RandomForestClassifier

clf_rf = RandomForestClassifier(n_estimators=100, random_state=1)
clf_rf.fit(X, y)
plot_trees_and_boundaries(clf_rf, X, y)

図16 構成モデルに決定木を用いたバギングをするPythonコード

from sklearn.ensemble import BaggingClassifier

clf_bag = BaggingClassifier(DecisionTreeClassifier(),
                            n_estimators=100, random_state=1)
clf_bag.fit(X, y)

図18 構成モデルに決定木を用いたアダブーストをするPythonコード

from sklearn.ensemble import AdaBoostClassifier

clf_ada = AdaBoostClassifier(DecisionTreeClassifier(max_depth=2),
                             n_estimators=100)
clf_ada.fit(X, y)
plot_trees_and_boundaries(clf_ada, X, y)

図20 構成モデル(回帰木)の数を変えながら勾配ブースティングをするPythonコード

from sklearn.ensemble import GradientBoostingClassifier

for m in [2, 4, 10, 20, 50]:
  clf_gbdt = GradientBoostingClassifier(n_estimators=m)
  clf_gbdt.fit(X, y)  # 学習
  plot_decision_boundary(X, y, clf_gbdt, features, palette)
  plt.title(f'GradientBoosting m = {m}')
  plt.show()

図22 XGBoost による学習をするPythonコード

from xgboost import XGBClassifier

# クラスラベルを整数0、1、2に変換
y_int = df2['species'].map({'Adelie': 0, 'Chinstrap': 1, 'Gentoo': 2}).values
clf_xgb = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss')
clf_xgb.fit(X, y_int)
plot_decision_boundary(X, y, clf_xgb, features, palette)
plt.show()

Pythonあれこれ(Vol.76掲載)

投稿日:2022.01.25 | カテゴリー: コード

著者:飯尾 淳

本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第6回は、やや趣向を変えてアルゴリズムを解説します。前半では関数の再帰的定義とその効率化、後半ではソーティングアルゴリズムについて紹介します。

シェルスクリプトマガジン Vol.76は以下のリンク先でご購入できます。

図1 再帰を使わずに階乗を求める関数を定義した例

def factorial(n):
  retvar = 1
  for i in range(n):
    retvar = retvar * i
  return retvar

図4 メモ化アルゴリズムを使って効率化したfib() 関数の定義例

memo = [0] * 100
memo[0] = memo[1] = 1
def fib(n):
  if memo[n-1] == 0: memo[n-1] = fib(n-1)
  if memo[n-2] == 0: memo[n-2] = fib(n-2)
  if memo[n] == 0: memo[n] = memo[n-1] + memo[n-2]
  return memo[n]

図7 バブルソートを実施する関数の実装例

def bubble_sort(x):
  y = x[:]
  for i in range(len(y)-1):
    for j in range(len(y)-1,i,-1):
      if y[j-1] > y[j]:
        tmp = y[j-1]; y[j-1] = y[j]; y[j] = tmp
    print(y)
  return y

図9 マージソートを実施する関数の実装例

def merge_sort(x):
  retary = []
  if len(x) <= 1:
    retary.extend(x)
  else:
    m = len(x) // 2
    first = merge_sort(x[:m])
    second = merge_sort(x[m:])
    while len(first) > 0 and len(second) > 0:
      retary.append(first.pop(0) \
        if first[0] < second[0] else second.pop(0))
    retary.extend(first if len(first) > 0 else second)
  return retary

図11 ヒープソートを実施する関数の実装例

from heapq import heappush, heappop
 
def heap_sort(x):
  retary = []
  heap = []
  for i in x: heappush(heap, i)
  while len(heap) > 0: retary.append(heappop(heap))
  return retary

特別レポート(Vol.76掲載)

投稿日:2022.01.25 | カテゴリー: コード

著者:松浦智之

2021年6月2~4日に技術カンファレンス「ソフトウェア・シンポジウム 2021 in 大分」がオンラインで開催されました。筆者が所属するユニバーサル・シェル・プログラミング研究所は、そのカンファレンスに二つの論文を投稿しました。その中で最優秀発表賞に選ばれた「UNIX機におけるIoT機器制御のためのタイミング管理」を紹介します。

シェルスクリプトマガジン Vol.76は以下のリンク先でご購入できます。

図2 シェルスクリプト(Accurate_interval.sh)

#!/bin/bash

i=1
yes | valve -l 5s | while read dummy; do
  wget WebサイトのURL/${i}.txt
  [ $i -ge 100 ] && break
  i=$((i+1))
done

特集2 Javaの最新動向(Vol.75記載)

投稿日:2021.11.25 | カテゴリー: コード

著者:伊藤 智博

エンタープライズシステムの分野で活用されている、プログラミング言語および開発・実行環境の「Java」。開発元である米旧Sun Microsystems社の買収、OpenJDKとしてのオープンソース化など、さまざまな変化がありました。本特集では、Javaの最新動向について分かりやすく解説します。

シェルスクリプトマガジン Vol.75は以下のリンク先でご購入できます。

図13 HelloWorld.javaのソースコード

public class HelloWorld {
  public static void main(String... args) {
    String message = "Hello World";
    System.out.println(message);
  }
}

図17 GreetingResource.javaのソースコード

package org.acme;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/hello")
public class GreetingResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "Hello RESTEasy";
    }
}

図20 変更後のソースコード

@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
    return "Hello World";
}

図23 NullPointerExceptionが発生するコード

public class NullPo {
    public static void main(String[] args) {
        Object o = createObject();
        o.toString();
    }
    
    private static Object createObject(){
        return null;
    }
}

図26 HTMLによるコード

<html>
    <body>
        <p>Hello, world</p>
    </body>
</html>

図27 HTMLによるコードをJavaでエスケープと結合で実装

String html = "<html>\n" +
              "    <body>\n" +
              "        <p>Hello, world</p>\n" +
              "    </body>\n" +
              "</html>\n";

図28 HTMLによるコードをテキストブロックで実装

String html = """
              <html>
                  <body>
                      <p>Hello, world</p>
                  </body>
              </html>
              """;

特別企画 ラズパイで作る簡単VPN環境(Vol.75記載)

投稿日:2021.11.25 | カテゴリー: コード

著者:魔法少女

職場と自宅のネットワークをインターネット経由で安全につなぐ方法として「VPN」(Virtual Private Network)があります。本企画では、小型コンピュータボード「Raspberry Pi」を利用して誰でも簡単にできるVPN環境構築方法を紹介します。

シェルスクリプトマガジン Vol.75は以下のリンク先でご購入できます。

図43 NAPTの追記

[Interface]
PrivateKey = XXXXXXXXXXXXXXXXXXXXX=
Address = 10.6.0.1/24
MTU = 1420
ListenPort = 51820
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

Raspberry Piを100%活用しよう(Vol.75掲載)

投稿日:2021.11.25 | カテゴリー: コード

著者:米田 聡

小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。第8回は、色を認識できるカラーセンサーを搭載する拡張基盤を扱います。

シェルスクリプトマガジン Vol.75は以下のリンク先でご購入できます。

図4adrszCS_CSmode_sample.pyの変更箇所

(略)
import json
import RPi.GPIO as GPIO
LED = 18

i2c = smbus.SMBus(1)
(略)
        #while True:
                GPIO.setmode(GPIO.BCM)
                GPIO.setup(LED, GPIO.OUT)
                GPIO.output(LED, GPIO.HIGH)
                time.sleep(1)   # LED点灯
(略)
                print(out_msg)
                GPIO.cleanup(LED)
(略)

機械学習ことはじめ(Vol.75掲載)

投稿日:2021.11.25 | カテゴリー: コード

著者:川嶋 宏彰

本連載では、機械学習の基礎となるさまざまな手法の仕組みや、それらの手法のPythonでの利用方法を解説していきます。今回は、入力データがどのようなクラス(カテゴリ)であるかを予測する分類の問題を扱います。

シェルスクリプトマガジン Vol.75は以下のリンク先でご購入できます。

図2 データセットの読み込みから、散布図のプロットまでを実施するPythonコード

import seaborn as sns
import matplotlib.pyplot as plt

plt.rcParams['font.size'] = 14
penguins = sns.load_dataset('penguins')
# 取り出す特徴量
features = ['bill_depth_mm', 'body_mass_g']  # (★)
# features = ['bill_depth_mm', 'bill_length_mm']  # 取り出す特徴量を変える
# 対象とするペンギン種
target_species = ['Adelie', 'Gentoo']  # (★)
# target_species = ['Adelie', 'Chinstrap', 'Gentoo']  # 3種のペンギンにする
# 今回用いる特徴量をクラスラベルと共に取り出す
df = penguins[['species'] + features].copy()
df.dropna(inplace=True)  # NaN が含まれる行は削除
# 今回用いるペンギン種のみとする
df2 = df[df['species'].isin(target_species)].copy()
print(df2.shape)  # (274, 3) と表示される
# 現在のパレットから用いる種 (target_species) に合わせたパレットを作成
palette = {c: sns.color_palette()[k] for k, c
           in enumerate(df['species'].unique()) if c in target_species}
fig = plt.figure(figsize=(5, 5))
sns.scatterplot(data=df2, x=features[0], y=features[1],
                hue='species', palette=palette)
plt.show()

図3 ロジスティック回帰の準備をするPythonコード

from sklearn.linear_model import LogisticRegression
import numpy as np

X = df2[features].values   # 各個体の2次元特徴量(行数 = 個体数)
y = df2['species'].values  # 各個体のクラスラベル
clf_lr = LogisticRegression(penalty='none')  # 分類モデルの準備

図4 特徴量一つ(体重)のみでロジスティック回帰をするPythonコード

f_idx = 1  # 用いる特徴量のインデックス
# 指定した列(特徴量)だけ取り出して学習
clf_lr.fit(X[:, [f_idx]], y)
# 学習されたパラメータを表示
w0 = clf_lr.intercept_[0]
w1 = clf_lr.coef_[0][0]
print(f'w_0 = {w0:.2f}, w_1 = {w1:.4f}, b = -w0/w1 = {-w0/w1:.2f}')
# 予測値および分類結果の可視化用グリッド
xmin = np.min(X[:, f_idx])
xmax = np.max(X[:, f_idx])
xs = np.linspace(xmin - (xmax - xmin) / 10,
                 xmax + (xmax - xmin) / 10, 100)  # 等間隔に100点用意
fig = plt.figure(figsize=(10, 5))
# 元データのプロット
sns.scatterplot(x=X[:, f_idx], y=(y=='Gentoo'), hue=y, palette=palette)
# 予測値(事後確率)の曲線をプロット
y_pred = clf_lr.predict_proba(xs.reshape(-1, 1))[:, 1]
plt.plot(xs, y_pred, c=palette['Gentoo'])
plt.ylim(-0.1, 1.1)
plt.xlabel(f'x ({features[f_idx]})')
plt.ylabel('y')
plt.grid()
plt.show()

図6 特徴量二つ(くちばしの高さと体重)でロジスティック回帰をするPythonコード

import matplotlib as mpl
from sklearn.preprocessing import StandardScaler

def plot_decision_boundary(X, y, clf, xylabels=None, palette=None, fig=None, ngrid=50):
  """ 分類器 clf の決定境界を描画 """
  if fig is None: fig = plt.figure(figsize=(5, 5))
  else: plt.figure(fig.number)
  # 2次元空間にグリッド点を準備
  xmin = X.min(axis=0)  # 各列の最小値
  xmax = X.max(axis=0)  # 各列の最大値
  xstep = [(xmax[j]-xmin[j]) / ngrid for j in range(2)]  # グリッドのステップ幅
  xmin = [xmin[j] - 8*xstep[j] for j in range(2)]  # 少し広めに
  xmax = [xmax[j] + 8*xstep[j] for j in range(2)]
  aranges = [np.arange(xmin[j], xmax[j] + xstep[j], xstep[j]) for j in range(2)]
  x0grid, x1grid = np.meshgrid(*aranges)
  # 各グリッド点でクラスを判定
  y_pred = clf.predict(np.c_[x0grid.ravel(), x1grid.ravel()])
  y_pred = y_pred.reshape(x0grid.shape)  # 2次元に
  y_pred = np.searchsorted(np.unique(y_pred), y_pred)  # 値をインデックスに
  clist = palette.values() if type(palette) is dict else palette
  cmap = mpl.colors.ListedColormap(clist) if palette is not None else None
  plt.contourf(x0grid, x1grid, y_pred, alpha=0.3, cmap=cmap)
  sns.scatterplot(x=X[:, 0], y=X[:, 1], hue=y, palette=palette)
  plt.legend()
  plt.xlim([xmin[0], xmax[0]])
  plt.ylim([xmin[1], xmax[1]])
  if xylabels is not None:
    plt.xlabel(xylabels[0])
    plt.ylabel(xylabels[1])
  return fig

Xs = StandardScaler().fit_transform(X)  # 標準化によるスケーリング
clf_lr.fit(Xs, y)  # 二つの特徴量を両方用いて学習
print('w0:', clf_lr.intercept_)
print('w1, w2:', clf_lr.coef_)
xylabels = [s + ' (standardized)' for s in features]  # 軸ラベル変更
plot_decision_boundary(Xs, y, clf_lr, xylabels, palette, ngrid=100)  # 決定境界
plt.show()

図9 ロジスティック回帰による各点での予測値をプロットするPythonコード

from matplotlib import cm
from mpl_toolkits.mplot3d import axes3d

# メッシュで
def gen_2dgrid(X):
  """ 2次元メッシュグリッドの生成 """
  d = X.shape[1]
  xmin = X.min(axis=0)  # 各列の最小値
  xmax = X.max(axis=0)  # 各列の最大値
  xstep = [(xmax[j]-xmin[j]) / 100 for j in range(d)]  # グリッドのステップ幅
  xmin = [xmin[j] - 10*xstep[j] for j in range(d)]  # 少し広めに
  xmax = [xmax[j] + 10*xstep[j] for j in range(d)]
  aranges = [np.arange(xmin[j], xmax[j] + xstep[j], xstep[j]) for j in range(2)]
  return np.meshgrid(*aranges)  # d=2のときは (x0grid, x1grid) が返る
# 2次元の場合の予測値 (proba)
def proba_2dgrid(clf, x0grid, x1grid):
  """ 2次元メッシュの各点での予測値を計算 """    
  return clf.predict_proba(np.c_[x0grid.ravel(), x1grid.ravel()])[:, 1]
                          .reshape(x0grid.shape)
xgrid_2d = gen_2dgrid(Xs)  # xgrid_2d: (x0grid, x1grid) のような二つ組
proba_2d = proba_2dgrid(clf_lr, *xgrid_2d)  # 予測値
# 3次元プロット
fig = plt.figure(figsize=(14, 8))
ax = fig.add_subplot(111, projection='3d')
cmap = cm.winter  # カラーマップを設定
ax.plot_surface(*xgrid_2d, proba_2d, cmap=cmap)
# ax.set_box_aspect((3, 3, 1))  # matplotlib のバージョンが3.3以上で利用可能
ax.set_zlim(-0.1, 1.1)
ax.set_xlabel(xylabels[0])
ax.set_ylabel(xylabels[1])
ax.set_zlabel('y')
ax.set_zticks([0, 0.5, 1.0])
ax.view_init(elev=60, azim=-120)  # 3次元プロットの表示角度の設定
plt.show()

図12 線形SVM による学習と決定境界の表示をするPythonコード

from sklearn.svm import SVC

C = 10  # 大きいほどハードマージンに近い
clf_lin = SVC(kernel='linear', C=C)
clf_lin.fit(Xs, y)
# 決定境界とサポートベクターを可視化
plot_decision_boundary(Xs, y, clf_lin, xylabels, palette, ngrid=100)
plt.scatter(clf_lin.support_vectors_[:, 0], clf_lin.support_vectors_[:, 1],
            s=50, facecolors='none', edgecolors='r')
plt.title(f'SVM(linear) C = {C}')
plt.show()

図17 非線形SVMとパラメータCの関係を示すPythonコード

from sklearn.datasets import make_moons

X_moon, y_moon = make_moons(n_samples=100, noise=0.2, random_state=6)
palette_o = {k: sns.color_palette()[k] for k in range(2)}
# moonデータの散布図をプロット
plt.figure(figsize=(5, 5))
sns.scatterplot(x=X_moon[:, 0], y=X_moon[:, 1],
                hue=y_moon, palette=palette_o)
plt.show()
# 異なるCでSVM(RBFカーネル)の学習と決定境界の表示
for C in [0.1, 10, 10000]:
  clf_rbf = SVC(C=C, kernel='rbf')  # kernel='rbf'は省略可能
  clf_rbf.fit(X_moon, y_moon)
  # 決定境界をプロット
  plot_decision_boundary(X_moon, y_moon,
                         clf_rbf, None, palette_o, ngrid=100)
  plt.title(f'SVM(rbf) C = {C}')
  plt.show()

図20 グリッドサーチをするPythonコード

from sklearn.model_selection import cross_val_score

best_score = -1
for gamma in [0.1, 0.5, 1, 2, 10]:  # 5通り
  for C in [0.1, 1, 10, 100, 1000, 10000]:  # 6通り
    print(f'gamma: {gamma},\tC: {C}', end='')
    svm = SVC(gamma=gamma, C=C)
    cv_scores = cross_val_score(svm, X_moon, y_moon, cv=5)
    score = np.mean(cv_scores)
    print(f'\t | average: {score:.2} <- {cv_scores}')
    if score > best_score:
      best_score = score
      best_params = {'C': C, 'gamma': gamma}
print('best_score:', best_score)
print('best_params:', best_params)
clf_rbf = SVC(**best_params)
clf_rbf.fit(X_moon, y_moon)
plot_decision_boundary(X_moon, y_moon,
                       clf_rbf, None, palette_o, ngrid=100)
title = f"C = {best_params['C']}, gamma = {best_params['gamma']}"
plt.title(title)
plt.show()

香川大学SLPからお届け!(Vol.75掲載)

投稿日:2021.11.25 | カテゴリー: コード

著者:平西 宏彰

 プログラミング言語で記述されたプログラムを解釈して、実行可能な形式に変換するのがコンパイラの役割です。コンパイラは、簡単なものであれば200行程度のコードで作成できます。今回は、整数の四則演算用の数式を処理できるコンパイラを作成します。

シェルスクリプトマガジン Vol.75は以下のリンク先でご購入できます。

図4 「main.go.y」ファイルに最初に記述するコード

%{
package main
import (
  "fmt"
  "log"
  "strconv"
  "github.com/llir/llvm/ir"
  "github.com/llir/llvm/ir/constant"
  "github.com/llir/llvm/ir/types"
  "github.com/llir/llvm/ir/value"
)
%}
%%
%%
type Node struct { // 構文木
  ID string        // 構文の種類
  Value string     // 構文が持つ値
  Left *Node
  Right *Node
}
type Lexer struct { // 字句解析器
  src string  // ソースコード
  index int   // 調査中のインデックス
  node *Node  // 構文木
}
func (l *Lexer) Error(err string) { // エラー処理
  log.Fatal(err)
}
func main() {
  var code string
  fmt.Scan(&code) // コード入力
  lexer := &Lexer{src: code, index: 0} // 字句解析
  yyParse(lexer) // 構文解析
  generator(lexer.node) // コード生成
}

図5 「main.go.y」ファイルの宣言部に追加するコード

%union {
  num int
  ope string
  node *Node
}
%type<node> program expr
%token<num> NUMBER
%token<ope> ADD SUB MUL DIV
%left ADD, SUB
%left MUL, DIV

図6 「main.go.y」ファイルのプログラム部に追加するコード

func (l *Lexer) Lex(lval *yySymType) int {
  if len(l.src) <= l.index { return -1 }
  c := l.src[l.index]
  l.index++
  if c == '+' { return ADD }
  if c == '-' { return SUB }
  if c == '*' { return MUL }
  if c == '/' { return DIV }
  if '0' <= c && c <= '9' {
    la := l.index
    for la < len(l.src) && '0' <= l.src[la] && l.src[la] <= '9' {
      la++
    }
    num, _ := strconv.Atoi(l.src[l.index-1:la])
    lval.num = num
    l.index = la
    return NUMBER
  }
  return -1
}

図7 「main.go.y」ファイルの規則部に記述するコード

program // 構文解析を開始するための構文
  : expr {
    $$ = $1
    yylex.(*Lexer).node = &Node{} // nodeの初期化
    // 式の構文解析で生成された構文木をprogram構文に
    yylex.(*Lexer).node = $$ }
expr // 式の構文
  : NUMBER { // 生成規則が数値のときの動作を記述
    $$ = &Node{
      ID: "NUMBER",
      Value: strconv.Itoa($1),
    }
  }
  | expr ADD expr {
    $$ = &Node{
      ID: "expr",
      Value: "+",
      Left: $1, // 左の式
      Right: $3, // 右の式
    }
  }
  | expr SUB expr { 
    $$ = &Node{
      ID: "expr",
      Value: "-",
      Left: $1,
      Right: $3,
    }
  }
  | expr MUL expr { 
    $$ = &Node{
      ID: "expr",
      Value: "*",
      Left: $1,
      Right: $3,
    }
  }
  | expr DIV expr { 
    $$ = &Node{
      ID: "expr",
      Value: "/",
      Left: $1,
      Right: $3,
    }
  }

図8 「main.go.y」ファイルのプログラム部に追加するコード

func generator(node *Node) {
  m := ir.NewModule()
  main := m.NewFunc("main", types.I32)
  block := main.NewBlock("")
  block.NewRet(calc(block, node))
  fmt.Println(m.String())
}
func calc(block *ir.Block, node *Node) value.Value {
  if node.ID == "expr" { return expr(block, node) }
  if node.ID == "NUMBER" { return number(node) }
  log.Fatal("error: generator")
  return nil
}
func number(node *Node) value.Value {
  val, ok := strconv.Atoi(node.Value)
  if ok != nil {
    log.Fatal("error: generator")
    return nil
  }
  return constant.NewInt(types.I32, int64(val))
}
func expr(block *ir.Block, node *Node) value.Value {
  if node.Left == nil || node.Right == nil {
    log.Fatal("error: generator")
    return nil
  }
  left := calc(block, node.Left)
  right := calc(block, node.Right)
  if node.Value == "+" { return block.NewAdd(left, right) }
  if node.Value == "-" { return block.NewSub(left, right) }
  if node.Value == "*" { return block.NewMul(left, right) }
  if node.Value == "/" { return block.NewUDiv(left, right) }
  log.Fatal("error: generator")
  return nil
}

Pythonあれこれ(Vol.75掲載)

投稿日:2021.11.25 | カテゴリー: コード

著者:飯尾 淳

本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第5回は、「Google Colaboratory」というクラウドサービスで、対話的にPythonコードを実行する方法を紹介します。

シェルスクリプトマガジン Vol.75は以下のリンク先でご購入できます。

図13 タートルグラフィックスで幾何学模様を描くPythonコード

from ColabTurtle.Turtle import *
initializeTurtle(initial_speed=10)
color('red')
bgcolor('white')
width(1)
x0 = pos()[0]
y0 = pos()[1]
while True:
  forward(200)
  left(170)
  if abs((pos()[0]-x0)**2 + (pos()[1]-y0)**2) < 1: break

図19 指定ファイルの各行をリストとして読み込み、その内容を表示するPythonコード

with open('/content/drive/MyDrive/TSdata.csv') as f:
  while True:
  line = f.readline().rstrip()
  if line == '': break
  print(line.split(','))

Vol.75 補足情報

投稿日:2021.11.25 | カテゴリー: コード

特別企画 ラズパイで作る簡単VPN環境

p.48の『ユーザー名の「pi」を入力して[Enter]キーを押します。』の後に『「Password:」が表示されたら、パスワードの「raspberry」を入力して[Enter]キーを押します。』が抜けていました。お詫びして訂正いたします。

情報は随時更新致します。

シェルスクリプトマガジンvol.75 Web掲載記事まとめ

投稿日:2021.11.25 | カテゴリー: コード

004 レポート Windows 11の導入メディア作成
005 レポート YouTube配信に便利なOBS Studio
006 NEWS FLASH
008 特集1 Windows 11 徹底解説/三沢友治
022 特集2 Javaの最新動向/伊藤智博 コード掲載
036 特集3 Debian 11の新機能&改善点/やまねひでき
044 特別企画 ラズパイで作る簡単VPN環境/魔法少女 コード掲載
058 Raspberry Piを100%活用しよう/米田聡 コード掲載
061 Hello Nogyo!
062 機械学習ことはじめ/川嶋宏彰 コード掲載
072 テレワーク/桑原滝弥、イケヤシロウ
074 Pythonあれこれ/飯尾淳  コード掲載
080 タイ語から分かる現地生活/つじみき
084 法林浩之のFIGHTING TALKS/法林浩之
086 中小企業手作りIT化奮戦記/菅雄一
090 香川大学SLPからお届け!/平西宏彰   コード掲載
096 Bash入門/大津真
104 Techパズル/gori.sh
105 コラム「システムエンジニアを育てる」/シェル魔人

Vol.74 補足情報

投稿日:2021.10.15 | カテゴリー: コード

訂正・補足情報はありません。
情報は随時更新致します。

シェルスクリプトマガジンvol.74 Web掲載記事まとめ

投稿日:2021.10.15 | カテゴリー: コード

004 レポート UNIX基本コマンド集の新版
005 レポート IBMのフリー日本語ソフト
006 NEWS FLASH
008 特集1 本格的なホームサーバーを構築 アプリ編/麻生二郎 コード掲載
023 コラム「ユニケージは従来のやり方と何が違うのか(前編)」/シェル魔人
024 特集2 オープンソースのJitsiで構築しよう/橋本知里
034 特集3 キーボードを自作しよう/東峰沙希
044 特別企画 MDSを始めよう!(応用編)/生駒眞知子
054 Raspberry Piを100%活用しよう/米田聡 コード掲載
057 Hello Nogyo!
058 機械学習ことはじめ/川嶋宏彰 コード掲載
068 タイ語から分かる現地生活/つぎみき
072 MySQLのチューニング/稲垣大助
080 PPAP/桑原滝弥、イケヤシロウ
082 中小企業手作りIT化奮戦記/菅雄一
086 法林浩之のFIGHTING TALKS/法林浩之
088 Pythonあれこれ/飯尾淳  コード掲載
094 香川大学SLPからお届け!/増田嶺  コード掲載
102 Bash入門/大津真
110 Techパズル/gori.sh
111 コラム「ユニケージは従来のやり方と何が違うのか(後編)」/シェル魔人

特集1 本格的なホームサーバーを構築(Vol.74記載)

投稿日:2021.10.15 | カテゴリー: コード

著者:麻生 二郎

小型コンピュータボードの最上位モデルである「Raspberry Pi 4 Model B」の4G/8Gバイト版と、人気のLinuxディストリビューション「Ubuntu」のサーバー版を組み合わせて、本格的なサーバーを構築しましょう。本特集では、テレワークに役立つサーバーアプリの導入方法を紹介します。

シェルスクリプトマガジン Vol.74は以下のリンク先でご購入できます。

図11 Sambaをインストールして実行するシェルスクリプト(samba_install.sh)

#!/bin/sh

##初期設定
WORKGROUP_NAME="SHMAG"
SHARE_NAME="share"
SHARE_FOLDER="/var/share"

##Sambaのインストール
sudo apt update
sudo apt -y install samba

##旧設定バックアップ
mkdir -p ~/old_settings
sudo mv /etc/samba/smb.conf ~/old_settings/.

##Sambaの共有設定
cat << EOF | sudo tee /etc/samba/smb.conf > /dev/null
[global]
workgroup = workgroup_name
dos charset = CP932
unix charset = UTF8

[share_name]
comment = Raspberry Pi share
path = share_folder
browsable = yes
writable = yes
create mask = 0777
directory mask = 0777
EOF
sudo sed -i -e "s%workgroup_name%'$WORKGROUP_NAME'%" /etc/samba/smb.conf
sudo sed -i -e "s%share_name%'$SHARE_NAME'%" /etc/samba/smb.conf
sudo sed -i -e "s%share_folder%'$SHARE_FOLDER'%" /etc/samba/smb.conf

##共有フォルダ作成
sudo mkdir -p $SHARE_FOLDER
sudo chmod 777 $SHARE_FOLDER

##Sambaの設定反映
sudo systemctl restart smbd
sudo systemctl restart nmbd

図14 MediaWikiを導入するシェルスクリプト(mediawiki_install.sh)

#!/bin/sh

##初期設定
DB_PASSWORD="shmag"

##必要なパッケージのインストール
sudo apt update
sudo apt -y install mediawiki imagemagick

##データベースの作成
sudo mysql -e "create database my_wiki;"
sudo mysql -e "create user 'mediawiki'@'%' identified by '$DB_PASSWORD';"
sudo mysql -e "grant all privileges on my_wiki.* to 'mediawiki'@'%';"

図25 ownCloudをインストールするシェルスクリプト(owncloud_install.sh)

#!/bin/sh

##初期設定
DB_PASSWORD="shmag"
ADMIN_NAME="admin"
ADMIN_PASSWORD="admin"
OWNCLOUD_FILE="owncloud-10.8.0.tar.bz2"

##ヘルパースクリプト「occ」の作成
cat << EOM | sudo tee /usr/local/bin/occ
#! /bin/bash
cd /var/www/owncloud
sudo -E -u www-data /usr/bin/php /var/www/owncloud/occ "\$@"
EOM
sudo chmod +x /usr/local/bin/occ

##必要・推奨パッケージのインストール
sudo apt update
sudo apt -y install apache2 libapache2-mod-php mysql-server php-imagick php-common php-curl php-gd php-imap php-intl php-json php-mbstring php-mysql php-ssh2 php-xml php-zip php-apcu php-redis redis-server
sudo apt -y install jq inetutils-ping

##ownCloudの設定ファイル作成
sudo sed -i "s%html%owncloud%" /etc/apache2/sites-available/000-default.conf
cat << EOM | sudo tee /etc/apache2/sites-available/owncloud.conf
Alias /owncloud "/var/www/owncloud/"

<Directory /var/www/owncloud/>
  Options +FollowSymlinks
  AllowOverride All

 <IfModule mod_dav.c>
  Dav off
 </IfModule>

 SetEnv HOME /var/www/owncloud
 SetEnv HTTP_HOME /var/www/owncloud
</Directory>
EOM

##Apache HTTP Serverの設定
sudo a2ensite owncloud.conf
sudo a2enmod dir env headers mime rewrite setenvif
sudo systemctl restart apache2

##ownCloudの入手と展開
wget https://download.owncloud.org/community/$OWNCLOUD_FILE
tar -jxf $OWNCLOUD_FILE
sudo mv owncloud /var/www/.
sudo chown -R www-data /var/www/owncloud

##データベースの作成
sudo mysql -e "create database owncloud;"
sudo mysql -e "create user 'owncloud'@'%' identified by '$DB_PASSWORD';"
sudo mysql -e "grant all privileges on owncloud.* to 'owncloud'@'%';"

##ownCloudのインストール
echo "しばらくおまちください。"
occ maintenance:install --database "mysql" --database-name "owncloud" --database-user "owncloud" --database-pass $DB_PASSWORD --admin-user "$ADMIN_NAME" --admin-pass "$ADMIN_PASSWORD"
myip=$(hostname -I|cut -f1 -d ' ')
occ config:system:set trusted_domains 1 --value="$myip"

##バックグラウンド処理の設定
occ background:cron
sudo sh -c 'echo "*/15  *  *  *  * /var/www/owncloud/occ system:cron" > /var/spool/cron/crontabs/www-data'
sudo chown www-data.crontab /var/spool/cron/crontabs/www-data
sudo chmod 0600 /var/spool/cron/crontabs/www-data

##キャッシュとロックファイルの作成
occ config:system:set memcache.local --value '\OC\Memcache\APCu'
occ config:system:set memcache.locking --value '\OC\Memcache\Redis'
occ config:system:set redis --value '{"host": "127.0.0.1", "port": "6379"}' --type json

##ログローテーションの設定
cat << EOM | sudo tee /etc/logrotate.d/owncloud
/var/www/owncloud/data/owncloud.log {
  size 10M
  rotate 12
  copytruncate
  missingok
  compress
  compresscmd /bin/gzip
}
EOM

図29 RainLoopをインストールするシェルスクリプト(rainloop_install.sh)

#!/bin/sh

##初期設定
SIZE="100M"
DB_PASSWORD="admin"

##必要なパッケージの導入
sudo apt update
sudo apt -y install apache2 php php-curl php-xml php-mysql mysql-server unzip

##RainLoop Webmailの導入
wget https://www.rainloop.net/repository/webmail/rainloop-community-latest.zip
sudo mkdir -p /var/www/html/rainloop
sudo unzip rainloop-community-latest.zip -d /var/www/html/rainloop/.
sudo chown -R www-data /var/www/html/rainloop
cat << EOF | sudo tee /etc/apache2/sites-available/rainloop.conf >> /dev/null
<Directory /var/www/html/rainloop/data>
    Require all denied
</Directory>
EOF
sudo a2ensite rainloop

##連絡先データベースの作成とApacheに反映
sudo mysql -e "create database rainloop;"
sudo mysql -e "create user 'rainloop'@'%' identified by '$DB_PASSWORD';"
sudo mysql -e "grant all privileges on rainloop.* to 'rainloop'@'%';"

##添付ファイルサイズの拡大
sudo sed -i -e "s%upload_max_filesize = 2M%upload_max_filesize = '$SIZE'%" /etc/php/7.4/apache2/php.ini
sudo sed -i -e "s%post_max_size = 8M%post_max_size = '$SIZE'%" /etc/php/7.4/apache2/php.ini
sudo systemctl restart apache2

図36 Mattermostをインストールするシェルスクリプト(mattermost_install.sh)

#!/bin/sh

##初期設定
DB_PASSWORD="shmag"
MATTERMOST="v5.39.0/mattermost-v5.39.0-linux-arm64.tar.gz"
SITE_URL="http://192.168.11.100/"

##データベースの作成
sudo apt update
sudo apt -y install mysql-server
sudo mysql -uroot -e "create user 'mmuser'@'%' identified by '$DB_PASSWORD';"
sudo mysql -uroot -e "create database mattermost;"
sudo mysql -uroot -e "grant all privileges on mattermost.* to 'mmuser'@'%';"

##mattermostの入手と展開
wget https://github.com/SmartHoneybee/ubiquitous-memory/releases/download/$MATTERMOST
tar -xvzf mattermost*.gz
sudo mv mattermost /opt
sudo mkdir /opt/mattermost/data
sudo useradd --system --user-group mattermost
sudo chown -R mattermost:mattermost /opt/mattermost
sudo chmod -R g+w /opt/mattermost

##設定ファイルの書き換え
sudo sed -i -e 's%"postgres"%"mysql"%' /opt/mattermost/config/config.json
sudo sed -i -e 's%postgres://mmuser:mostest@localhost/mattermost_test?sslmode=disable\\u0026connect_timeout=10%mmuser:'$DB_PASSWORD'@tcp(localhost:3306)/mattermost?charset=utf8mb4,utf8\&writeTimeout=30s%' /opt/mattermost/config/config.json
sudo sed -i -e 's%"SiteURL": "",%"SiteURL": "'$SITE_URL'",%' /opt/mattermost/config/config.json

##起動・停止ファイルの作成
cat << EOF | sudo tee /lib/systemd/system/mattermost.service > /dev/null
[Unit]
Description=Mattermost
After=network.target
After=mysql.service
BindsTo=mysql.service

[Service]
Type=notify
ExecStart=/opt/mattermost/bin/mattermost
TimeoutStartSec=3600
KillMode=mixed
Restart=always
RestartSec=10
WorkingDirectory=/opt/mattermost
User=mattermost
Group=mattermost
LimitNOFILE=49152

[Install]
WantedBy=mysql.service
EOF

##mattermostの起動と自動起動設定
sudo systemctl daemon-reload
sudo systemctl start mattermost
sudo systemctl enable mattermost

図43 MosP勤怠管理をインストールするシェルスクリプト(mosp_install.sh)

#!/bin/sh

##初期設定
MOSP="time4.war"

##必要なパッケージのインストール
sudo apt update
sudo apt -y install tomcat9 tomcat9-admin postgresql
##Mosp勤怠管理の導入
sudo chown tomcat:tomcat $MOSP
sudo chmod 775 $MOSP
sudo mv $MOSP /var/lib/tomcat9/webapps/.

##データベース管理者に切り替え
sudo -i -u postgres

##初期設定
DBADMIN_PASSWORD="shmag"

##管理者パスワード設定
psql -c "alter role postgres with password '$DBADMIN_PASSWORD';"

図A3 ラズパイサーバーの初期設定(ubuntu_init1.sh)

#!/bin/sh

##日本のタイムゾーン設定
sudo timedatectl set-timezone Asia/Tokyo

##全ソフトウエア更新
sudo apt update
sudo apt -y upgrade
sudo apt -y autoremove
sudo apt clean

##ファームウエアアップデート
sudo apt -y install rpi-eeprom
sudo rpi-eeprom-update

##完了後の再起動
read -p "再起動しますか [y/N]:" YN
if [ " $YN" = " y" ] || [ " $YN" = " Y" ]; then
  sudo reboot
fi

図A4 ラズパイサーバーの初期設定(ubuntu_init2.sh)

#!/bin/sh

##固定IPアドレス
IP_ADDRESS="192.168.10.100"

##旧設定バックアップ
mkdir -p ~/old_settings
sudo mv /etc/netplan/50-cloud-init.yaml ~/old_settings/.

##新ネットワーク設定作成
cat << EOF | sudo tee /etc/netplan/50-cloud-init.yaml > /dev/null
network:
  ethernets:
    eth0:
      dhcp4: false
      addresses: [ip_address/24]
      gateway4: 192.168.10.1
      nameservers:
        addresses: [8.8.8.8]
  version: 2
EOF
sudo sed -i -e "s%ip_address%$IP_ADDRESS%" /etc/netplan/50-cloud-init.yaml

##ネットワーク設定反映
sudo netplan apply

図A3 データ領域を拡張するシェルスクリプト(storage_expand.sh)

#!/bin/sh

##パーティション作成とフォーマット
sudo parted -s /dev/sda rm 1
sudo parted -s /dev/sda mklabel msdos
sudo parted -s /dev/sda mkpart primary 0% 100%
sudo mke2fs -t ext4 -F /dev/sda1

##/varディレクトリに自動マウント
sudo e2label /dev/sda1 usbssd
sudo sh -c "echo 'LABEL=usbssd /var ext4 defaults 0 0' >> /etc/fstab"

##読み書き許可と/varディレクトリコピー
sudo mount /dev/sda1 /mnt
sudo chmod 777 /mnt
sudo cp -a /var/* /mnt

##完了後の再起動
read -p "再起動しますか [y/N]:" YN
if [ " $YN" = " y" ] || [ " $YN" = " Y" ]; then
  sudo reboot
fi

Raspberry Piを100%活用しよう(Vol.74掲載)

投稿日:2021.10.15 | カテゴリー: コード

著者:米田 聡

小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。第7回は、ボリュームスイッチのような操作を実現する拡張基板を扱います。

シェルスクリプトマガジン Vol.74は以下のリンク先でご購入できます。

図4 サンプルプログラム(sample.py)

import smbus
import struct
import time

ADRSZRE_ADDR = 0x54
VALUE_HI     = 0x00
RESET        = 0x02

STEPS = 80  # 1回転=80カウント

bus = smbus.SMBus(1)
degree = 0.0

try:
    while True:
        temp = bus.read_word_data(ADRSZRE_ADDR, VALUE_HI)
        value = struct.unpack(">H", struct.pack("<H", temp))[0]
        if value == 0:
            bus.write_byte_data(ADRSZRE_ADDR, RESET, True) # Zero Reset
            
        degree =  (value % STEPS) *  360.0 / STEPS
        print("%.1f" % degree, end='\r', flush=True)
        time.sleep(0.1)

except KeyboardInterrupt:
    pass

機械学習ことはじめ

投稿日:2021.10.15 | カテゴリー: コード

著者:川嶋 宏彰

本連載では、機械学習の基礎となるさまざまな手法の仕組みや、それらの手法のPythonでの利用方法を解説していきます。今回は、入力から予測値を出力する「回帰モデル」と、その教師あり学習について紹介します。

シェルスクリプトマガジン Vol.74は以下のリンク先でご購入できます。

図5 気温に対するチョコレート菓子支出金額の散布図をプロットするPython コード

import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib  # pip install japanize-matplotlibが必要
import re
plt.rcParams['font.size'] = 14
# 家計調査データの読み込み
beg_col = 5  # 品目の始まる列
end_col = 6  # 品目の終わる列
usecols = [3] + list(range(beg_col, end_col + 1))  # 用いる列リスト
# 年月の列をインデックスとするデータフレームに
df_estat = pd.read_csv('estat.csv', header=0, encoding='cp932',
    usecols=usecols, index_col=0, parse_dates=True, 
    date_parser=lambda x: pd.to_datetime(x, format='%Y年%m月'))
# 各品目の支出金額を各月の日数で割る(★)
for col in df_estat:
    new_col = re.sub(r'.* (.*)【円】', r'\1 (円/日)', col)
    df_estat[new_col] = df_estat[col] / df_estat.index.days_in_month
# 気象庁の気温データの読み込み
df_jma = pd.read_csv('jma.csv', skiprows=5, header=None, 
    encoding='cp932',
    usecols=[0, 1], index_col=0, parse_dates=True)
hitemp_col = '平均最高気温 (℃)'  # 気温の列名を変える
df_jma.columns = [hitemp_col]
# データフレームの結合
df = pd.merge(df_estat, df_jma, left_index=True, right_index=True)
df.index.name = 'y/m'  # インデックスの名前を変更
# 年と月の列を追加(後で利用)
df['year'] = df_estat.index.year
df['month'] = df_estat.index.month
print('df (merged):\n', df)
# 散布図をプロット
target_col = 'チョコレート菓子 (円/日)'
df.plot(kind='scatter', x=hitemp_col, y=target_col, figsize=(5, 5))
plt.show()

図7 チョコレート菓子支出金額の単回帰の結果を示すPythonコード

from sklearn.linear_model import LinearRegression

reg = LinearRegression()  # モデルを準備
# データを準備
X = df[[hitemp_col]].values  # 平均最高気温(2次元配列)。「.values」は省略可
y = df[target_col].values  # 支出金額(1次元配列)。「.values」は省略可
reg.fit(X, y)  # 学習(データに直線を当てはめる)
print('intercept:', reg.intercept_)  # 切片
print('coef:', reg.coef_[0])  # 直線の傾き
print('R2:', reg.score(X, y))  # 決定係数
df.plot(kind='scatter', x=hitemp_col, y=target_col, figsize=(5, 5))
plt.plot(X, reg.predict(X), 'r')
plt.show()

図10 チョコレート菓子支出金額の単回帰における残差をプロットする
Pythonコード

fig = plt.figure(figsize=(5, 5))
plt.scatter(X, y - reg.predict(X))  # 残差をプロット
plt.axhline(y=0, c='r', linestyle='-')  # 水平線をプロット
plt.ylim([-3, 3])
plt.xlabel(hitemp_col)
plt.ylabel('残差')
plt.show()

図13 年を説明変数とした単回帰のためのPythonコード

from matplotlib import cm
from matplotlib.colors import ListedColormap

def ListedGradation(cmapname, num=10):
    """ LinearSegmentedColormap から ListedColormap を作成 """
    color_list = []
    cmap = cm.get_cmap(cmapname)
    for i in range(num):
        color = list(cmap(i/num))
        color_list.append(color)
    return ListedColormap(color_list)
# 年ごとに散布図の色を変える
df.plot(kind='scatter', x=hitemp_col, y=target_col, c=df['year'],
    cmap=ListedGradation('cividis', 11), sharex=False, figsize=(6, 5))
plt.show()
# 「年」を説明変数とする単回帰
reg_year = LinearRegression()
X = df[['year']].values  # 年
y = df[target_col].values  # 支出金額
reg_year.fit(X, y)  # 学習(データに直線を当てはめる)
print('intercept:', reg_year.intercept_)  # 切片
print('coef:', reg_year.coef_[0])  # 直線の傾き
print('R2:', reg_year.score(X, y))  # 決定係数
df.plot(kind='scatter', x='year', y=target_col, figsize=(5, 5))
plt.plot(X[:, 0], reg_year.predict(X), 'r')
plt.show()

図15 平均最高気温と年の二つを説明変数とした重回帰のためのPythonコード

from mpl_toolkits.mplot3d import Axes3D
import numpy as np

reg_multi = LinearRegression()
X = df[[hitemp_col, 'year']].values  # 二つの説明変数(2次元配列)
y = df[target_col].values  # 支出金額(1次元配列)
reg_multi.fit(X, y)  # 学習(データに平面を当てはめる)
print('intercept:', reg_multi.intercept_)  # 切片
print('coef:', reg_multi.coef_)  # 平面の傾き
print('R2:', reg_multi.score(X, y))  # 決定係数
# 3次元散布図のプロット
def scatter3d(X, y, xlabels, ylabel):
    fig = plt.figure(figsize=(9, 6))
    ax = fig.add_subplot(111, projection='3d')
    ax.scatter(X[:, 0], X[:, 1], y)
    ax.set_xlabel(xlabels[0])
    ax.set_ylabel(xlabels[1])
    ax.set_zlabel(ylabel)
    ax.view_init(elev=30, azim=-60)
    return ax
scatter3d(X, y, [hitemp_col, '年'], target_col)
plt.show()
# 平面(wireframe)を合わせてプロット
ax = scatter3d(X, y, [hitemp_col, '年'], target_col)
xlim = ax.get_xlim()
ylim = ax.get_ylim()
mesh_x = np.meshgrid(np.arange(*xlim, (xlim[1] - xlim[0]) / 20),
    np.arange(*ylim, (ylim[1] - ylim[0]) / 20))
mesh_y = reg_multi.intercept_ + reg_multi.coef_[0] * mesh_x[0] \ 
    + reg_multi.coef_[1] * mesh_x[1]  # 回帰式より予測
ax.plot_wireframe(*mesh_x, mesh_y, color='r', linewidth=0.5)
plt.show()

図18 平均最高気温と年の二つを説明変数とした重回帰の残差をプロットするPythonコード

fig = plt.figure(figsize=(5, 5))
plt.scatter(X[:, 0], y - reg_multi.predict(X)) # 残差をプロット
plt.axhline(y=0, c='r', linestyle='-') # 水平線をプロット
plt.ylim([-3, 3])plt.xlabel(hitemp_col)
plt.ylabel('残差')
plt.show()

図20 平均最高気温とアイスクリーム支出金額の散布図をプロットするPythonコード

target_col = 'アイスクリーム・シャーベット (円/日)'  # 目的変数を変える
df.plot(kind='scatter', x=hitemp_col, y=target_col, figsize=(5, 5))
plt.show()

図22 多項式回帰モデルを使ったPythonコード

from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import Ridge

def poly_fit(df, xlabel, ylabel, degree=2, ylim=None):
    """ 多項式回帰 """
    y = df[ylabel]
    pf = PolynomialFeatures(degree=degree, include_bias=False)
    Xpf = pf.fit_transform(df[[xlabel]])
    reg_pf = LinearRegression(normalize=True)
    # reg_pf = Ridge(normalize=True, alpha=0.01)  # (★)
    reg_pf.fit(Xpf, y)
    print('intercept:', reg_pf.intercept_)  # 切片
    print('coef:', reg_pf.coef_)  # 直線の傾き
    print('R2:', reg_pf.score(Xpf, y))  # 決定係数
    fig = plt.figure(figsize=(5, 5))
    plt.plot(X, y, '.', ms=8)
    plt.ylim(ylim)
    plt.xlabel(xlabel)
    plt.ylabel(ylabel)
    # x軸に等間隔に点を配置して回帰曲線を描く
    xlim = plt.xlim()
    X_lin = np.arange(*xlim, (xlim[1] - xlim[0])/50).reshape(-1, 1)
    Xpf_lin = pf.fit_transform(X_lin)
    ypf_pred = reg_pf.predict(Xpf_lin)
    plt.plot(X_lin, ypf_pred, color='r')
X = df[[hitemp_col]].values  # 平均最高気温
y = df[target_col].values  # 支出金額
for p in [1, 2, 3, 20]:  # 多項式の次数
    poly_fit(df, hitemp_col, target_col, degree=p)
    plt.title(f'p = {p}')
    plt.show()

Pythonあれこれ(Vol.74掲載)

投稿日:2021.10.15 | カテゴリー: コード

著者:飯尾 淳

本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第4回は、フラクタル図形の一種である「ヒルベルト曲線」を描く方法を解説します。

シェルスクリプトマガジン Vol.74は以下のリンク先でご購入できます。

図1 1次のヒルベルト曲線を描くPythonコード

#!/usr/bin/env python

from turtle import *
SIZE = 300
p = [[-0.5, 0.5], [-0.5, -0.5], [0.5, -0.5], [0.5, 0.5]]
def screen_pos(x):
  return (x[0]*SIZE, x[1]*SIZE)
setup(width = 2*SIZE, height = 2*SIZE)
color('red')
width(5)
hideturtle()
for x in p:
  goto(screen_pos(x))
mainloop()

図4 1 次のヒルベルト曲線を描くPythonコードの改良版

#!/usr/bin/env python

from turtle import *
SIZE = 300
penup_flag = True
p = [[-0.5, 0.5], [-0.5, -0.5], [0.5, -0.5], [0.5, 0.5]]
def screen_pos(x):
  return (x[0]*SIZE, x[1]*SIZE)
setup(width = 2*SIZE, height = 2*SIZE)
color('red')
width(5)
hideturtle()
penup()
for x in p:
  goto(screen_pos(x))
  if penup_flag:
    pendown()
    penup_flag = False
onkey(exit, 'q')
listen()
mainloop()

図7 2 次のヒルベルト曲線を描くPythonコード

#!/usr/bin/env python

from turtle import *
SIZE = 300
penup_flag = True
tm = [
  [ [0.0, -0.5, -0.5], [-0.5, 0.0,  0.5], [0.0, 0.0, 1.0] ],
  [ [0.5,  0.0, -0.5], [ 0.0, 0.5, -0.5], [0.0, 0.0, 1.0] ],
  [ [0.5,  0.0,  0.5], [ 0.0, 0.5, -0.5], [0.0, 0.0, 1.0] ],
  [ [0.0,  0.5,  0.5], [ 0.5, 0.0,  0.5], [0.0, 0.0, 1.0] ]
]
e = [ [ 1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0] ]
p = [ [-0.5, 0.5], [-0.5, -0.5], [0.5, -0.5], [0.5, 0.5] ]
def affine_transform(m, p):
  return [ m[0][0] * p[0] + m[0][1] * p[1] + m[0][2],
           m[1][0] * p[0] + m[1][1] * p[1] + m[1][2] ]
def mat_mul(m0, m1):
  return [ [m0[0][0]*m1[0][0]+m0[0][1]*m1[1][0]+m0[0][2]*m1[2][0],
            m0[0][0]*m1[0][1]+m0[0][1]*m1[1][1]+m0[0][2]*m1[2][1],
            m0[0][0]*m1[0][2]+m0[0][1]*m1[1][2]+m0[0][2]*m1[2][2]],
           [m0[1][0]*m1[0][0]+m0[1][1]*m1[1][0]+m0[1][2]*m1[2][0],
            m0[1][0]*m1[0][1]+m0[1][1]*m1[1][1]+m0[1][2]*m1[2][1],
            m0[1][0]*m1[0][2]+m0[1][1]*m1[1][2]+m0[1][2]*m1[2][2]],
           [m0[2][0]*m1[0][0]+m0[2][1]*m1[1][0]+m0[2][2]*m1[2][0],
            m0[2][0]*m1[0][1]+m0[2][1]*m1[1][1]+m0[2][2]*m1[2][1],
            m0[2][0]*m1[0][2]+m0[2][1]*m1[1][2]+m0[2][2]*m1[2][2]] ]
def screen_pos(x):
  return (x[0]*SIZE, x[1]*SIZE)
setup(width = 2*SIZE, height = 2*SIZE)
color('red')
width(5)
hideturtle()
penup()
for m in tm:
  p2 = [ affine_transform(mat_mul(e, m), x) for x in p ]
  for x in p2: 
    goto(screen_pos(x))
    if penup_flag:
      pendown()
      penup_flag = False
onkey(exit, 'q')
listen()
mainloop()

図9 書き換えた2 次のヒルベルト曲線を描くPythonコード

#!/usr/bin/env python

from turtle import *
import numpy as np
SIZE = 300
penup_flag = True
tm = [ np.matrix(x) for x in [
  [ [0.0, -0.5, -0.5], [-0.5, 0.0,  0.5], [0.0, 0.0, 1.0] ],
  [ [0.5,  0.0, -0.5], [ 0.0, 0.5, -0.5], [0.0, 0.0, 1.0] ],
  [ [0.5,  0.0,  0.5], [ 0.0, 0.5, -0.5], [0.0, 0.0, 1.0] ],
  [ [0.0,  0.5,  0.5], [ 0.5, 0.0,  0.5], [0.0, 0.0, 1.0] ] ]
]
e = np.eye(3) # 3次の単位行列
p = [ np.matrix(x).T for x in [
      [-0.5,  0.5, 1.0], [-0.5, -0.5, 1.0],
      [ 0.5, -0.5, 1.0], [ 0.5,  0.5, 1.0] ] ]
def screen_pos(x):
  return (x[0,0]*SIZE, x[1,0]*SIZE)
setup(width = 2*SIZE, height = 2*SIZE)
color('red', 'yellow')
width(5)
hideturtle()
penup()
for m in tm:
  for x in [ e * m * x for x in p ]:
    goto(screen_pos(x))
    if penup_flag:
      pendown()
      penup_flag = False
onkey(exit, 'q')
listen()
mainloop()

図10 図9 の赤枠部分を置き換えるPythonコード

def hilbert(n, m):
  if n > 1:
    for m_tm in tm: hilbert(n-1, m * m_tm)
  else:
    for x in [ m * x.T for x in p ]:
      goto(screen_pos(x))
      global penup_flag
      if penup_flag:
        pendown()
        penup_flag = False
hilbert(3, e)

図13 1 ~ 7 次のヒルベルト曲線を重ねて描画するPythonコード

#!/usr/bin/env python

from turtle import *
import numpy as np
SIZE = 300
MARGIN = 50
penup_flag = True
settings = [
  { 'color': 'forestgreen', 'width': 5 },
  { 'color': 'navy',        'width': 4 },
  { 'color': 'purple',      'width': 3 },
  { 'color': 'brown',       'width': 2 },
  { 'color': 'red',         'width': 2 },
  { 'color': 'orange',      'width': 1 },
  { 'color': 'yellowgreen', 'width': 1 }
]
tm = [ np.matrix(x) for x in [
  [ [0.0, -0.5, -0.5], [-0.5, 0.0,  0.5], [0.0, 0.0, 1.0] ],
  [ [0.5,  0.0, -0.5], [ 0.0, 0.5, -0.5], [0.0, 0.0, 1.0] ],
  [ [0.5,  0.0,  0.5], [ 0.0, 0.5, -0.5], [0.0, 0.0, 1.0] ],
  [ [0.0,  0.5,  0.5], [ 0.5, 0.0,  0.5], [0.0, 0.0, 1.0] ] ] ]
e = np.eye(3)
p = [ np.matrix(x).T for x in [
      [-0.5,  0.5, 1.0], [-0.5, -0.5, 1.0],
      [ 0.5, -0.5, 1.0], [ 0.5,  0.5, 1.0] ] ]
def draw_line_to(x):
  goto(x[0,0]*SIZE, x[1,0]*SIZE)
  global penup_flag
  if penup_flag:
    pendown()
    penup_flag = False
def reset():
  penup()
  global penup_flag
  penup_flag = True
def hilbert(n, m):
  if n > 1:
    for m_ in tm: hilbert(n-1, m * m_)
  else:
    for q in [ m * p_ for p_ in p ]: draw_line_to(q)
setup(width = 2*SIZE+MARGIN, height = 2*SIZE+MARGIN)
hideturtle()
for i in range(len(settings)):
  reset()
  color(settings[i]['color'], 'white')
  width(settings[i]['width'])
  hilbert(i+1, e)
onkey(exit, 'q')
listen()
mainloop()

香川大学SLPからお届け!(Vol.74掲載)

投稿日:2021.10.15 | カテゴリー: コード

著者:増田 嶺

 前回に引き続き、SLPで開発したリズムゲームを紹介します。前回はゲームで使用する譜面作成ツールについて解説しました。今回は、ゲーム本体について解説します。ゲーム本体は「Visual Studio Code」(VSCode)で開発します。

図5 「js/game/singleNote.js」ファイルに記述するコード

class SingleNote {
  constructor(speed, reachTime) {
    this.height = note.height;
    this.width = note.width;
    this.frameColor = 'rgb(' + note.frameColor + ')'; // 枠の色
    this.bodyColor = 'rgba(' + note.bodyColor + ')';  // 中の色
    this.this.centerY = -this.height;                 // ノーツ中心のY座標
    this.speed = speed * note.speedRatio;             // px/ms
    // ゲーム開始から判定ラインに到達するまでの時間
    this.reachTime = reachTime + note.delay; 
    // canvasに入る時間
    this.appearTime = this.reachTime -
                      (JUDGE_LINE.centerY + this.height / 2) / this.speed;           
    // canvasから出る時間
    this.hideTime = this.reachTime + 
                    (CANVAS_H - JUDGE_LINE.centerY + this.height / 2) / 
                    this.speed;
    this.act = true;   // 自身がヒット処理対象かどうか
    this.show = false; // 自身がcanvasに描画されるかどうか
  }
}

図6 「js/game/backLane.js」ファイルに追加するコード

(略)
  generateNote(data) {
    this.note = data.map((val) => new SingleNote(val[2], val[3]));
  }
(略)

図7 「js/game/singleNote.js」ファイルに追加するコード

class SingleNote {
  constructor(speed, reachTime) {
(略)
  }
  draw(x) {
    if (this.show) {
      this.centerY = JUDGE_LINE.centerY - 
                     (this.reachTime - time.elapsed) * this.speed;
      CTX.fillStyle = this.bodyColor;
      CTX.fillRect(x, this.centerY - this.height / 2, 
                   this.width, this.height);
      CTX.strokeStyle = this.frameColor;
      CTX.strokeRect(
        x + LINE_WIDTH / 2,
        this.centerY - this.height / 2 + LINE_WIDTH / 2,
        this.width - LINE_WIDTH,
        this.height - LINE_WIDTH
      );
    }
  }
}

図8 「js/game/backLane.js」ファイルに追加するコード

(略)
  drawNote() {
    this.note.forEach(val => val.draw(this.x));
  }
(略)

図9 「js/game/singleNote.js」ファイルに追加するコード

class SingleNote {
  constructor(speed, reachTime) {
(略)
  }
  draw(x) {
(略)
  }
  update() {
    this.updateShow();
  }
  updateShow() {
    if (this.act || this.show) {
      if (this.appearTime <= time.elapsed && 
          time.elapsed <= this.hideTime) {
        this.show = true;
      } else {
        this.show = false;
      }
    }
  }
}

図11 「js/game/backLane.js」ファイルに追加するコード

(略)
  update() {
    this.note.forEach(val => val.update());
  }
(略)

図13 「js/game/backLane.js」ファイルに追加するコード

(略)
  judge() {
    calcElapsedTime(); // 経過時間を更新
    // ヒットしているノーツを抽出
    const TARGET = this.note.filter(val => val.checkHit(note.hitRange[3])); 
    // TARGETの先頭から処理、先頭ノーツのグレードがBadだった場合のみ
    // 二つ目以降のノーツを処理し、それらのノーツがBadだった場合は中断
    const GRADE = [3]
    for (let i = 0; TARGET[i] && GRADE[0] === 3; i++) {
      GRADE[i] = TARGET[i].getGrade();
      // 二つ目以降のノーツがBadの場合はそこで中断
      if (i > 0 && GRADE[i] === 3) {  
        break;
      }
      JUDGE_LINE.setGrade(GRADE[i]); // ノーツヒットのグレードを描画
      TARGET[i].close();             // 判定済みのノーツ処理を停止
    }
  }
(略)

図15 「js/game/singleNote.js」ファイルに追加するコード

class SingleNote {
(略)
  updateShow() {
(略)
  }
  checkHit(range) {
    if (this.act && 
        Math.abs(time.elapsed - this.reachTime) <= range) {
      return true;
    } else {
      return false;
    }
  }
  getGrade() {
    let grade = 3;
    for (let i = 2; i >= 0; i--) {
      if (this.checkHit(note.hitRange[i])) {
        grade = i;
      }
    }
    return grade;
  }
}

図16 「js/game/singleNote.js」ファイルに追加するコード

class SingleNote {
(略)
  update() {
    this.updateShow();
    if (!this.act) {
      return;
    }
    // 判定ラインから自身の判定ゾーンが
    // 過ぎた時点で処理
    if (this.reachTime < time.elapsed &&
        !this.checkHit(note.hitRange[3])) {
      JUDGE_LINE.setGrade(3);
      this.act = false;
    }
  }
(略)
  close() {
    this.act = false;
    this.show = false;
  }
}

図18 「js/game/gameScoreManager.js」ファイルに追加するコード

(略)
  calcScore(grade) {
    switch (grade) {
      case 0:
        this.point += 100 + this.combo;
        this.perfect++;
        this.combo++;
        break;
      case 1:
        this.point += 80 + this.combo * 0.8;
        this.great++;
        this.combo++;
        break;
      case 2:
        this.point += 50 + this.combo * 0.5;
        this.good++;
        this.combo++;
        break;
      case 3:
        this.bad++;
        this.combo = 0;
        break;
    }
    if (this.maxCombo < this.combo) {
      this.maxCombo = this.combo;
    }
  }
(略)

図19 「js/game/backLane.js」ファイルに追加するコード

(略)
  judge() {
(略)
      if (GRADE[i] < 3) {
        // Bad以外の判定ならヒットSEを鳴らす
        sound.playSE(sound.seList[0]);   
      } else {
        sound.playSE(sound.seList[1]); // Bad判定ならバッドSEを鳴らす
      }
      gameScore.calcScore(GRADE[i]); // スコアを更新
      JUDGE_LINE.setGrade(GRADE[i]); // ノーツヒットのグレードを描画
      TARGET[i].close();             // 判定済みのノーツ処理を停止
    }
  }
(略)

図20 「js/game/singleNote.js」ファイルに追加するコード

class SingleNote {
(略)
  update() {
(略)
    if (this.reachTime < time.elapsed &&
        !this.checkHit(note.hitRange[3])) {
      gameScore.calcScore(3);
      JUDGE_LINE.setGrade(3);
      this.act = false;
    }
  }
(略)
}

図22 「js/game/backLane.js」ファイルに追加するコード

(略)
  draw() {
(略)
    if (inputKey.status[this.key]) {
      const GRAD = CTX.createLinearGradient(this.x, 
                     JUDGE_LINE.centerY, this.x, CANVAS_H / 3);
      GRAD.addColorStop(0.0, 'rgba(' + this.actColor + ', 0.6)');
      GRAD.addColorStop(1.0, 'rgba(' + this.actColor + ', 0)');
      CTX.fillStyle = GRAD;
      CTX.fillRect(this.x, 0, this.width, JUDGE_LINE.centerY);
    }
  }
(略)

シェルスクリプトマガジンvol.73 Web掲載記事まとめ

投稿日:2021.07.25 | カテゴリー: コード

004 レポート Windows 11発表
005 レポート HE C++ Transpiler
006 NEWS FLASH
008 特集1 PowerShellを学ぼう/三沢友治 コード掲載
024 特集2 JenkinsによるCI/CD/長久保篤、酒井利治 コード掲載
042 特集3 ラズパイでコロナ感染症対策/麻生二郎 コード掲載
066 特別企画 新・地球シミュレータ(ES4)への招待/今任嘉幸、上原均
074 Raspberry Piを100%活用しよう/米田聡
076 機械学習ことはじめ/川嶋宏彰 コード掲載
085 Hello Nogyo!
086 CRM/桑原滝弥、イケヤシロウ
088 レッドハットのプロダクト/宇都宮卓也
096 MySQLのチューニング/稲垣大助
104 法林浩之のFIGHTING TALKS/法林浩之
106 中小企業手作りIT化奮戦記/菅雄一
110 Pythonあれこれ/飯尾淳 コード掲載
114 香川大学SLPからお届け!/岩本和真 コード掲載
120 Bash入門/大津真
130 Techパズル/gori.sh
131 コラム「ユニケージは従来のやり方と何が違うのか(前編)」/シェル魔人

特集1 PowerShellを学ぼう(Vol.73記載)

投稿日:2021.07.25 | カテゴリー: コード

著者:三沢 友治

PowerShellとは、米Microsoft社が開発したシェルおよびスクリプト言語です。Windows OSや、Microsoft Office製品に関連するサービス「Microsoft 365」を管理するのに活用されています。本特集では、これからPowerShellに取り組み始めたい人を対象に、PowerShellについて基本から分かりやすく解説します。

シェルスクリプトマガジン Vol.73は以下のリンク先でご購入できます。

図47 時刻を取得するスクリプト(writeTime.ps1)

# 現在の時刻で時間オブジェクトを取得
# 時間オブジェクトは文字列ではなく時間の情報を保持している
# そのため一部の情報を容易に取得できる
$date = Get-Date
# 取得した時間から指定のフォーマットでファイルを上書き
# 時間を取得するhh:mmを指定し、その内容をfile.txtに上書きしている。追記したい場合は >> file.txtとする
$date.ToString("hh:mm") > file.txt

図48 ランダムな名前、ランダムな作成日でファイルを作成するスクリプト(CreateRandomFiles.ps1)

# 指定したディレクトリにランダムな内容のファイルを指定サイズで作成する
# 成功時はTrue、失敗時はFalseを返す
Function CreateRandomBiteArrayFile([string]$Path, [int]$sizeMB)
{
    try
    {
        # ランダムな10文字のファイル名を作成 
        $fileName = -join ((1..10) | %{(65..90) + (97..122) | Get-Random} | foreach-object {[char]$_})
        # フルパスのファイル名を作る
        $fullFileName = $Path + "\" + $fileName + ".tmp"
        # ファイルの存在を確認
        if(Test-Path $fullFileName)
        {
            # 同一ファイル名が失敗として終了する
            return $false
        } 
        # 指定サイズでバイト配列を作成する
        $file = new-object byte[] ($sizeMB * 1024 * 1024)
        # バイト配列にランダムデータを入れる
        (new-object Random).NextBytes($file)
        # IO APIを利用してファイルに書き込む
        [IO.File]::WriteAllBytes($fullFileName, $file)
        # 100日の間でランダムな日を決める
        $day = Get-Random -maximum 100
        # ファイルの作成日付を変更する(100日の間でランダム)
        Set-ItemProperty $fullFileName -Name LastWriteTime -Value (Get-Date).addDays($day * -1) 
        return $true
    }
    catch
    {
        # サンプルスクリプトのため例外を大きく割愛している
        # 実務的なコードでは例外発生個所が分かる範囲でCatchするのが理想となる(同じ例外が発生しない粒度で)
        return $false
    }
}


# ファイルを格納するディレクトリ(今回の指定はカレントディレクトリ)
$fileDirectoryPath = pwd
# ファイルサイズ(Mバイト指定)
$size = 1
# 作成ファイル数
$num = 10
# num個のファイルが作成される
# ただし、ランダムなファイル名が被る場合、作成されるファイルが減る
@(1..$num) | Foreach{
    CreateRandomBiteArrayFile $fileDirectoryPath $size
}

特集2 JenkinsによるCI/CD(Vol.73記載)

投稿日:2021.07.25 | カテゴリー: コード

著者:長久保 篤、酒井 利治

ソフトウエアの開発・生産性を高めるには「CI(Continuous Integration)/CD(Continuous Delivery)」が不可欠となっています。本特集では、CI/CDとは何か、オープンソースソフトウエアの「Jenkins」を用いてCI/CD環境を構築する方法を分かりやすく解説します。

シェルスクリプトマガジン Vol.73は以下のリンク先でご購入できます。

図9 Dockerfileの内容

FROM jenkins/jenkins:2.277.4-lts-jdk11
USER root
RUN apt update && apt install -y apt-transport-https \
      ca-certificates curl gnupg2 \
      software-properties-common
RUN curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add -
RUN apt-key fingerprint 0EBFCD88
RUN add-apt-repository \
      "deb [arch=amd64] https://download.docker.com/linux/debian \
      $(lsb_release -cs) stable"
RUN apt update && apt install -y docker-ce-cli
USER jenkins
RUN jenkins-plugin-cli --plugins "blueocean:1.24.6 docker-workflow:1.26"

図21 Jenkinsfileの内容

pipeline {
  agent any
  stages {
    stage('ビルド') {
      steps {
        sh 'mvn -B -DskipTest clean package'
      }
    }
    stage('テスト') {
      steps {
        sh 'mvn -B test'
      }
    }
    stage('デプロイ') {
      steps {
        sh './jenkins/scripts/deliver.sh'
      }
    }
  }
}

図41 説明した内容を反映したJenkinsfile

pipeline {
  agent {
    label 'mvn3'
  }
  stages {
    stage('ビルド') {
      steps {
        sh 'mvn -B -DskipTest clean package'
        archiveArtifacts artifacts: 'target/*.jar'
      }
    }
    stage('テスト') {
      steps {
        sh 'mvn -B test'
      }
      post {
        always {
          junit 'target/surefire-reports/*.xml'
        }
      }
    }
    stage('デプロイ') {
      when {
        branch 'master'
        beforeInput true
      }
      input {
        message "デプロイしますか?"
      }
      steps {
        sh './jenkins/scripts/deliver.sh'
      }
    }
  }
  post {
    failure {
      emailext (
        subject: "失敗: プロジェクト '${env.JOB_NAME} [${env.BUILD_NUMBER}]'",
        body: """次のページでコンソールの出力を確認してください:
        ${env.BUILD_URL}""",
        recipientProviders: [developers()]
      )
    }
  }
}

特集3  ラズパイでコロナ感染症対策(Vol.73記載)

投稿日:2021.07.25 | カテゴリー: コード

著者:麻生 二郎

新型コロナウイルス感染症対策として、日々の手洗いと検温は重要です。ただ、1人暮らしの人など、それらを習慣化するのは難しいかもしれません。本特集では、小型コンピュータボード「Raspberry Pi」(ラズパイ)と電子回路を用いて、新型コロナウイルス感染症対策を習慣化できるような支援システムを構築します。

シェルスクリプトマガジン Vol.73は以下のリンク先でご購入できます。

Part2 感染症対策システムを完成する

図2 pirm_sensor.pyのソースコード

#!/usr/bin/env python3

import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)
GPIO.setup(21,GPIO.IN)

pir = GPIO.input(21)
print(pir)

図3 temp_sensor.pyのソースコード

#!/usr/bin/env python3

from smbus2 import SMBus
from mlx90614 import MLX90614

bus = SMBus(1)
sensor = MLX90614(bus, address=0x5A)
print(sensor.get_ambient(),sensor.get_object_1())
bus.close()

図4 go_out.shのソースコード

#!/bin/bash

##外出確認の初期値と玄関にいる時間(秒)
GOOUT_CHECK=0
STAY_TIME=3

##表面体温の初期値と基準値
OUTSIDE_TEMP=0
OUTSIDE_TEMP_BASE=32

##周辺温度の初期値と基準値
AMBIENT_TEMP=35
AMBIENT_TEMP_BASE=28

##体内温度の基準値
INTSIDE_TEMP_BASE=36.7

##体内温度との差分
INSIDE_BODY_DIFF=4.2

##メッセージ集
MESSAGE1="おはようございます。出かける前に検温をしてください"
MESSAGE2="周辺温度が${AMBIENT_TEMP}であり、検温に適していません。エアコンをかけてください"
MESSAGE3="周辺温度が${AMBIENT_TEMP}です。検温を開始します。おでこをセンサーに向けてください"
MESSAGE4="今朝の体温は${INSIDE_TEMP}です。正常値なので外出可能です。マスクを着用し、予備のマスクも持って出かけてください"
MESSAGE5="今朝の体温は${INSIDE_TEMP}です。熱がありますので、正確に測れる体温計で再度測定してください"

##音声メッセージ関数
function voice_message(){
  echo $1 | open_jtalk -m /usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice -x /var/lib/mecab/dic/open-jtalk/naist-jdic -ow /dev/stdout | aplay --quiet
}

##外出の確認
while [ ${GOOUT_CHECK} -lt ${STAY_TIME} ]
do
  if [ $(pirm_sensor.py) -eq 1 ];then
    GOOUT_CHECK=$(echo "${GOOUT_CHECK} + 1" | bc)
  else
    GOOUT_CHECK=0
  fi
  sleep 1
done
voice_message ${MESSAGE1}

##周辺温度の確認
while [ ${AMBIENT_TEMP} -ge ${AMBIENT_TEMP_BASE} ]
do
  AMBIENT_TEMP=$(/usr/bin/python3 temp_sensor.py | cut -d " " -f 1)
  voice_message ${MESSAGE2}
  sleep 5
done
voice_message ${MESSAGE3}

##検温
while [ ${OUTSIDE_TEMP} -gt ${OUTSIDE_TEMP_BASE} ]
do
  OUTSIDE_TEMP=$(/usr/bin/python3 temp_sensor.py | cut -d " " -f 2)
  sleep 1
done
echo "$OUTSIDE_TEMP,$(date +%Y%m%d-%H:%M)" >> ${HOME}/body_temp.csv

##測定結果
INSIDE_TEMP=$(echo "scale=1; ${OUTSIDE_TEMP} + ${INSIDE_BODY_DIFF}" | bc)

if [ ${INSIDE_TEMP} -lt ${OUTSIDE_TEMP_BASE} ]; then
  voice_message ${MESSAGE4}
else
  voice_message ${MESSAGE5}
fi

図5 handwash_mouthwash.pyのソースコード

#!/usr/bin/env python3

import board
import busio
i2c = busio.I2C(board.SCL, board.SDA)
ads = ADS.ADS1115(i2c)

chan1 = AnalogIn(ads, ADS.P0)
chan2 = AnalogIn(ads, ADS.P1)
print(chan1.voltage,chan2.voltage)

図6 go_home.shのソースコード

#!/bin/sh

##帰宅確認の初期値
GOHOME_CHECK=0

##手洗いとうがいの初期値
HAND_WASH=1
MOUTH_WASH=1

##ハンドソープのポンプ圧力基準値
POMP_PUSH_BASE=3

##うがい用コップの重量基準値
CUP_WEIGHT_BASE=3

##メッセージ集
MESSAGE1="おかえりなさい。帰ってきたら,手を洗って、うがいをしましょう"
MESSAGE2="手洗いを確認しました"
MESSAGE3="うがいを確認しました"		

##音声メッセージ関数
function voice_message(){
  echo $1 | open_jtalk -m /usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice -x /var/lib/mecab/dic/open-jtalk/naist-jdic -ow /dev/stdout | aplay --quiet
}

##帰宅の確認
while [ ${GOHOME_CHECK} == 1 ]
do
  GOHOME_CHECK=$(pirm_sensor.py)
  sleep 1
done
voice_message ${MESSAGE1}

##手洗いとうがいの検出
while [ ${HANDWASH} == 1 -o ${MOUTHWASH} == 1 ]
do
  ##手洗い検出の処理
  HANDWASH_MOUTHWASH=$(handwash_mouthwash.py)
  if [ $(cut -d " " -f 1 ${HANDWASH_MOUTHWASH}) -gt ${POMP_PUSH_BASE} ]; then
    if [ $(HANDWASH) == 1 ]; then
      voice_message ${MESSAGE2}
      echo $(date +%Y%m%d-%H%M-%S) >> ${HOME}/handwash.csv
    fi
    HANDWASH=0
  fi
  ##うがい検出の処理
  if [ $(cut -d " " -f 2 ${HANDWASH_MOUTHWASH}) -lt ${CUP_WEIGHT_BASE} ]; then
    if [ $(MOUTHWASH) == 1 ]; then
      voice_message ${MESSAGE3}
      echo $(date +%Y%m%d-%H%M-%S) >> ${HOME}/mouthwash.csv
    fi
    MOUTHWASH=0
  fi
  sleep 0.5
done

図7 open_close.pyのソースコード

#!/usr/bin/env python3

import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)
GPIO.setup(20,GPIO.IN)

window = GPIO.input(20)
print(window)

図8 ventilation.shのソースコード

#!/bin/bash

##メッセージ
MESSAGE="窓を開けて空気を入れ換えましょう"

##音声メッセージ関数
function voice_message(){
  echo $1 | open_jtalk -m /usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice -x /var/lib/mecab/dic/open-jtalk/naist-jdic -ow /dev/stdout | aplay --quiet
}

##在宅の確認
STAY_HOME=$(pirm_sensor.py)

##開閉の確認
OPEN_CHECK=$(open_close.py)

##換気の通知
if [ ${STAY_HOME} == 1 -a ${OPEN_CHECK} == 0 ]; then
  voice_message ${MESSAGE}
  sleep 180
  if [ ${OPEN_CHECK} == 1 ]; then
    echo $(date +%Y%m%d-%H%M-%S) >> ${HOME}/ventilation.csv
  fi
fi

図9 spo2_sensor.pyのソースコード

#!/usr/bin/env python3

import max30102
import hrcalc

m = max30102.MAX30102()

red, ir = m.read_sequential()

hr_spo2 =hrcalc.calc_hr_and_spo2(ir, red)
print(hr_spo2[2], hr_spo2[3])

図10 spo2.shのソースコード

#!/bin/bash

##在宅状態の初期値
STAY_HOME=0

##メッセージ集
MESSAGE1="寝るときは血中酸素飽和度を測定しましょう"
MESSAGE2="酸素が不足しています。医療機器で正確な値を測定してください。同じ値を示せば、至急病院に連絡しましょう"

##音声メッセージ関数
function voice_message(){
  echo $1 | open_jtalk -m /usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice -x /var/lib/mecab/dic/open-jtalk/naist-jdic -ow /dev/stdout | aplay --quiet
}

##在宅の確認
While [ ${STAY_HOME} == 0 ]
do
  STAY_HOME=$(pirm_sensor.py)
  sleep 180
done

##血中酸素飽和度測定の通知
voice_message ${MESSAGE1}
sleep 300

##血中酸素飽和度の測定開始
while [ 1 ]
do
  SPO2=$(/usr/bin/python3 spo2_sensor.py)
  SPO2_VALUE=$(echo ${SPO2} | cut -d " " -f 1)
  SPO2_JUDGE=$(echo ${SPO2} | cut -d " " -f 2)
  if [ ${SPO2_JUDGE} == "True" ]; then
    echo "${SPO2_VALUE} $(date +%Y%m%d-%H%M)" >> ${HOME}/spo2.csv
    if [ ${SPO2_VALUE} -le 93 ]; then
      voice_message ${MESSAGE2}
      exit 0
    fi
  fi
  sleep 300
done

Pythonあれこれ(Vol.73掲載)

投稿日:2021.07.25 | カテゴリー: コード

著者:飯尾 淳

本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第3回は、Pythonの言語機能である「ジェネレータ」に親しむための活用例を紹介します。

シェルスクリプトマガジン Vol.73は以下のリンク先でご購入できます。

図2 「jugem.txt」の内容を行単位で反転して表示するPythonコード

#!/usr/bin/env python
  
def readJugemu():
  with open('jugemu.txt', 'r') as f:
    return f.readlines()

lines = readJugemu()
for l in lines:
  print(l.rstrip()[::-1])

図5 lines変数を使わないコード例

#!/usr/bin/env python
  
def readJugemu():
  with open('jugemu.txt', 'r') as f:
    return f.readlines()

for l in readJugemu():
  print(l.rstrip()[::-1])

図6 ジェネレータを使用したPythonコード「reverse2.py」

#!/usr/bin/env python
  
def readJugemu():
  with open('jugemu.txt', 'r') as f:
    for line in f:
      yield line

for l in readJugemu():
  print(l.rstrip()[::-1])

図7 関数readJugemu()が返すデータの種類を調べるPythonコード「test.py」

#!/usr/bin/env python
  
def readJugemu():
  with open('jugemu.txt', 'r') as f:
    return f.readlines()

lines = readJugemu()
print(type(lines))

図8 ジェネレータ関数readJugemu()が返すデータの種類を調べるPythonコード「test2.py」

#!/usr/bin/env python
  
def readJugemu():
  with open('jugemu.txt', 'r') as f:
    for line in f:
      yield line

lines = readJugemu()
print(type(lines))

図9 ジェネレータ関数readJugemu()をfor文で使用するPythonコード「reverse3.py」

#!/usr/bin/env python
  
def readJugemu():
  with open('jugemu.txt', 'r') as f:
    for line in f:
      print('readJugemu')
      yield line

for l in readJugemu():
  print('main: ', end='')
  print(l.rstrip()[::-1])

図11 ハノイの塔の解を求めるPythonコード「hanoi.py」

#!/usr/bin/env python
  
def hanoi(n, src, via, dst):
  if n <= 1:
    yield src, dst
  else:
    yield from hanoi(n-1, src, dst, via)
    yield src, dst
    yield from hanoi(n-1, via, src, dst)

for src, dst in hanoi(3, 'A', 'B', 'C'):
  print(f'{src}->{dst}')

図12 yield from構文を使わない場合のコード

#!/usr/bin/env python
  
def hanoi(n, src, via, dst):
  if n <= 1:
    yield src, dst
  else:
    for s, d in hanoi(n-1, src, dst, via): yield s, d
    yield src, dst
    for s, d in hanoi(n-1, via, src, dst): yield s, d

for src, dst in hanoi(3, 'A', 'B', 'C'):
  print(f'{src}->{dst}')

機械学習ことはじめ(Vol.73掲載)

投稿日:2021.07.25 | カテゴリー: コード

筆者:川嶋 宏彰

本連載では、機械学習の基礎となるさまざまな手法の仕組みや、それらの手法のPythonでの利用方法を解説していきます。今回は「確率モデル」による機械学習である、ガウス分布を用いた教師あり学習と教師なし学習の手法を紹介します。

シェルスクリプトマガジン Vol.73は以下のリンク先でご購入できます。

図2 二つの特徴量を抽出して散布図をプロットするPythonコード

import seaborn as sns
import matplotlib.pyplot as plt

plt.rcParams['font.size'] = 14
penguins = sns.load_dataset('penguins')
# 取り出す特徴量
features = ['bill_depth_mm', 'body_mass_g'] # ★
# 対象とするペンギン種
target_species = ['Adelie', 'Gentoo'] # ★
# 今回用いる特徴量をクラスラベルと共に取り出す
df = penguins[['species'] + features].copy()
df.dropna(inplace=True)  # NaN が含まれる行は削除
# 今回用いるペンギン種のみとする
df2 = df[df['species'].isin(target_species)].copy()
print(df2.shape)  # (274, 3) と表示される
# 種(target_species)に合わせたパレットを作成
palette = {c: sns.color_palette()[k] 
           for k, c in enumerate(df['species'].unique())
           if c in target_species}
fig = plt.figure(figsize=(5, 5))
sns.scatterplot(data=df2, x=features[0], y=features[1],
                hue='species', palette=palette)
plt.show()

図4 各クラス(ペンギン種)の体重分布をプロットするPythonコード

import numpy as np
import scipy

X = df2[features].values  # 各個体の2次元特徴量(行数=個体数)
y = df2['species'].values  # 各個体のクラスラベル
prob_y = {c: np.sum(y==c)/y.size for c in target_species}  # 事前確率
print('Prior:', prob_y)
# 平均と分散は2次元にまとめてベクトルや行列として計算しておく
mu = {c: X[y==c, :].mean(axis=0) for c in target_species}  # 平均
sigma2 = {c: X[y==c, :].var(axis=0, ddof=1) for c in target_species}  # 不偏分散
f_idx = 1  # 用いる特徴量のindex(これは1次元の場合)
fig = plt.figure(figsize=(10, 5))
ax1 = fig.add_subplot(111)
ax2 = ax1.twinx()  # 右の縦軸
# ヒストグラムをプロット (体重のみ)
sns.histplot(ax=ax1, x=X[:, f_idx], hue=y, palette=palette, alpha=0.2)
xmin = np.min(X[:, f_idx])
xmax = np.max(X[:, f_idx])
xs = np.linspace(xmin-(xmax-xmin)/10, xmax+(xmax-xmin)/10, 100)  # 等間隔に100点用意
# 確率密度関数を準備 (体重のみ)
norm_dist1d = {c: scipy.stats.multivariate_normal(mu[c][f_idx], sigma2[c][f_idx])
               for c in target_species}
for c in target_species:
    # 各クラスの確率密度関数をプロット
    sns.lineplot(ax=ax2, x=xs, y=norm_dist1d[c].pdf(xs)*prob_y[c],
                 hue=[c]*len(xs), palette=palette)
# 各データを小さな縦線でプロット
sns.rugplot(x=X[:, f_idx], hue=y, palette=palette, height=0.05)
ax2.set_ylabel('Probability density')
ax1.set_xlabel(features[f_idx])
plt.show()

図6 1次元での決定境界を求めるPythonコード

# 曲線の上下が変化するおおよその点を求める
diff = norm_dist1d['Adelie'].pdf(xs)*prob_y['Adelie'] -
       norm_dist1d['Gentoo'].pdf(xs)*prob_y['Gentoo']
# 符号の変化点を見つけるために隣り合う要素の積を計算
ddiff = diff[1:] * diff[:-1]
print('boundary:', xs[np.where(ddiff < 0)[0][0]])

図8 2次元ガウス分布とその等高線を表示するPythonコード

from mpl_toolkits.mplot3d import axes3d
from matplotlib import cm
# 共分散行列を求めておく
Sigma = {c: np.cov(X[y==c, :].T, ddof=1) for c in target_species}
def gen_2dgrid(X):
    """ 2次元メッシュグリッドの生成 """
    d = X.shape[1]
    xmin = X.min(axis=0)  # 各列の最小値
    xmax = X.max(axis=0)  # 各列の最大値
    xstep = [(xmax[j]-xmin[j]) / 100 for j in range(d)]  # グリッドのステップ幅
    xmin = [xmin[j] - 10*xstep[j] for j in range(d)]  # 少し広めに
    xmax = [xmax[j] + 10*xstep[j] for j in range(d)]
    aranges = [np.arange(xmin[j], xmax[j] + xstep[j], xstep[j]) for j in range(2)]
    return np.meshgrid(*aranges)  # d=2のときは (x0grid, x1grid) が返る
def gaussian_2dgrid(m, S, x0grid, x1grid):
    """ 2次元ガウス分布の密度関数の値を2次元メッシュで計算 """
    gaussian = scipy.stats.multivariate_normal(m, S)
    return gaussian.pdf(np.c_[x0grid.ravel(), x1grid.ravel()]).reshape(x0grid.shape)
c = 'Adelie'  # プロットするクラスを設定
xgrid_2d = gen_2dgrid(X)  # xgrid_2d: (x0grid, x1grid) のような二つ組
px_adelie = gaussian_2dgrid(mu[c], Sigma[c], *xgrid_2d)  # *xgrid_2d で2引数に展開
fig = plt.figure(figsize=(8, 5))  # 3次元プロット
ax = fig.add_subplot(111, projection='3d')
# 2次元ガウシアンの3次元的プロット
cmap = cm.coolwarm  # カラーマップを設定
ax.plot_surface(*xgrid_2d, px_adelie, cmap=cmap)
# 等高線のプロット
z_offset = -np.max(px_adelie)
ax.contour(*xgrid_2d, px_adelie, zdir='z', offset=z_offset, cmap=cmap)
ax.set_zlim(z_offset, ax.get_zlim()[1])
ax.view_init(elev=40, azim=-60)  # 3次元プロットの表示角度の設定
plt.show()

図10 2次元ガウス分布の等高線と元データの散布図を表示するPythonコード

def plot_ellipses(ms, Ss, pys, xgrids, xylabels=None, fig=None):
    """ 各クラスの確率だ円をプロット """
    if fig is None: fig = plt.figure(figsize=(5, 5))  # 新たに作成
    else: plt.figure(fig.number)  # 既存のfigureを利用
    levels = None
    # クラス名を辞書キーから取得
    for c in ms.keys() if type(ms) is dict else range(len(ms)):
        cs = plt.contour(*xgrids, gaussian_2dgrid(ms[c],
                         Ss[c], *xgrids)*pys[c], cmap=cmap,
                         levels=levels)
        levels = cs.levels  # 二つ目以降は一つ目のlevelsを利用
        # 密度(山の標高)をだ円に表示
        # plt.clabel(cs, inline=True, fontsize=8)
        if xylabels is not None:
        plt.xlabel(xylabels[0])
        plt.ylabel(xylabels[1])
    return fig
plot_ellipses(mu, Sigma, prob_y, xgrid_2d, xylabels=features)
sns.scatterplot(x=X[:, 0], y=X[:, 1], hue=y, palette=palette)
plt.show()

図12 2次の識別による決定境界を表示するPythonコード

from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis
# plot_decision_boundary()は
# 注釈のリンク先を参照して別途定義
clf_qda = QuadraticDiscriminantAnalysis()
clf_qda.fit(X, y)  # 学習
# 決定境界の表示
fig = plot_decision_boundary(X, y, clf_qda,
                             features, palette)
# 確率だ円の表示
plot_ellipses(mu, Sigma, prob_y, xgrid_2d, fig=fig)
plt.show()

図15 ガウス分布によるデータの生成をするPythonコード

fig = plt.figure(figsize=(5, 5))
Ngen_each = 120  # 各クラスで120個体生成する場合
# 各クラスの割合を変化させてもよい
X_gen = np.vstack(
            [np.random.multivariate_normal(mu[c], Sigma[c], Ngen_each)
            for c in target_species])
y_gen = np.hstack([[c] * Ngen_each for c in target_species])
sns.scatterplot(x=X_gen[:, 0], y=X_gen[:, 1], hue=y_gen, palette=palette)
plt.xlabel(features[0])
plt.ylabel(features[1])
plt.show()

図18 混合ガウス分布によるソフトクラスタリングをするPythonコード

from sklearn.mixture import GaussianMixture
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
k = 2  # クラスタ数(非階層的クラスタリングなので決める必要がある)
use_gmm = True  # Falseにするとk-meansになる
if use_gmm:
    gmm = GaussianMixture(n_components=k, covariance_type='full')
    # full、diagはスケーリング無しも可(sphericalでは必要)
    Xu = X
    # GMM推定による各データのクラスタID
    y_pred = gmm.fit_predict(Xu)
else:
    kmeans = KMeans(n_clusters=k)
    # 標準化によるスケーリング
    Xu = StandardScaler().fit_transform(X)
    # k-meansによる各データのクラスタID
    y_pred = kmeans.fit_predict(Xu)
fig = plt.figure(figsize=(11, 5))
fig.add_subplot(121)
sns.scatterplot(x=X[:, 0], y=X[:, 1], color='k')
plt.xlabel(features[0])
plt.ylabel(features[1])
plt.title('Unlabeled data')
fig.add_subplot(122)
sns.scatterplot(x=Xu[:, 0], y=Xu[:, 1],
                hue=y_pred, palette='bright')
method_str = 'GMM' if use_gmm else 'k-means'
plt.title(f'{method_str} clustering')
if use_gmm:
    plot_ellipses(gmm.means_, gmm.covariances_,
                  gmm.weights_, xgrid_2d, fig=fig)
plt.show()

香川大学SLPからお届け!(Vol.73掲載)

投稿日:2021.07.25 | カテゴリー: コード

著者:岩本 和真

SLPでは最近、Webブラウザで動作するリズムゲームをチームで開発しました。さまざまな曲でプレーできるように、リズムゲーム本体と並行して、ゲームで使用する譜面を作成するツールも開発しました。このツールもWebブラ
ウザで動作します。今回は、この譜面作成ツールの実装について紹介します。

シェルスクリプトマガジン Vol.73は以下のリンク先でご購入できます。

図3 最初に実行されるコード

window.onload = async function() {
  const {bpm, musicL} = getMusicInfoViaElement()
  await getWidth();
  await numberQLine(bpm, musicL);
  await setCanvas();
  await setQLine();
  await setXLine();
  await update();
  await draw();
  await scrollBottom();
}

図4 getMusicInfoViaElement()関数のコード

function getMusicInfoViaElement() {
  const bpm = document.getElementById('bpm').value;
  const musicL = document.getElementById('musicL').value;
  const musicBody = document.getElementById('musicBody').value;
  return {bpm, musicL, musicBody}
}

図5 getWidth()関数のコード

const can = document.getElementById("can2");
const ctx = can.getContext("2d");
can.setAttribute('style', 'background-color: #f6f7d7');
function getWidth() {
  return new Promise(function(resolve) {
    cst = window.getComputedStyle(can);
    canvasW = parseInt(cst.width);
    can.width = canvasW;
    resolve();
  })
}

図6 numberQLine()関数のコード

function numberQLine(bpm, musicL) {
  return new Promise(function(resolve) {
    qLineQty = Math.floor(musicL / (60 / bpm) + 1);
    resolve();
  })
}

図7 setCanvas()関数のコード

let qLineMargin = 80;
function setCanvas() {
  return new Promise(function(resolve) {
    canvasH = qLineQty * qLineMargin;
    can.height = canvasH;
    ctx.translate(0, can.height);
    ctx.scale(1, -1);
    can.style.height = canvasH * 2.5 +'px';
    resolve();
  })
}

図8 setQLine()関数のコード

function setQLine() {
  return new Promise(function(resolve) {
    for (let i = quarterLine.length; i < qLineQty; i++) {
      quarterLine[i] = new QuarterLine(i);
    }
    resolve();
  })
}

図9 QuarterLineクラスのコード

class QuarterLine {
  constructor(no) {
    this.no = no;
    this.y = qLineMargin * this.no + qLineMargin / 4;
  }
  update() {
    this.y = qLineMargin * this.no + qLineMargin / 4;
  }
  draw() {
    ctx.beginPath();
    ctx.strokeStyle = "#3ec1d3";
    ctx.lineWidth = Q_LINE_T;
    ctx.moveTo(0, Math.round(this.y));
    ctx.lineTo(canvasW, Math.round(this.y));
    ctx.stroke();
  }
}

図10 setXLine()関数のコード

function setXLine() {
  return new Promise(function(resolve) {
    for (let i = 0; i < 4; i++) {
      for (let j = xLine[i].length; j < qLineQty; j++) {
        xLine[i][j] = [];
        for (let k = 0; k < 14; k++) {
          xLine[i][j][k] = new XthLine(i, j, k);
        }
      }
    }
    resolve();
  })
}

図11 update()関数のコード

function update() {
  return new Promise(function (resolve) {
    // ノーツの縦の長さを更新
    noteH = (qLineMargin / 9 >= Q_LINE_T || divValue == 8)
             ? qLineMargin / 9 : Q_LINE_T
      // ノーツの位置と各拍、各連符の横線の位置を更新
    const laneMargin = canvasW / 5;
    laneSet = [laneMargin / 2, laneMargin * 1.5,
               laneMargin * 2.5, laneMargin * 3.5,
               laneMargin * 4.5];
    noteW = laneMargin / 3;
    quarterLine.forEach((val) => val.update() )
    editLane.forEach((val) => val.update() )
    for (let i = 0; i < 4; i++) {
      for (let j = 0; j < qLineQty; j++) {
        for (let k = 0; k < 14; k++) {
          xLine[i][j][k].update();
        }
      }
    }
    resolve();
  })
}

図12 draw()関数のコード

function draw() {
  return new Promise(function(resolve) {
    ctx.clearRect(0, 0, can.width, can.height);
    for (let i = 0; i < 4; i++) {
      for (let j = 0; j < qLineQty; j++) {
        for (let k = 0; k < 14; k++) {
          xLine[i][j][k].draw();
        }
      }
    }
    for (let i = 0; i < qLineQty; i++) {
      quarterLine[i].draw();
    }
    editLane.forEach((val) => val.draw())
    for (let i = 0; i < 4; i++) {
      for (let j = 0; j < qLineQty; j++) {
        for (let k = 0; k < 14; k++) {
          xLine[i][j][k].drawNote();
        }
      }
    }
    resolve();
  })
}

図13 draw()関数のコード

function scrollBottom() {
  return new Promise(function(resolve) {
    let target = document.getElementById('scroll');
    target.scrollTop = target.scrollHeight;
    resolve();
  }
)

図14 レーン上のノーツをマウスクリックで制御するためのコード

let mouseDown;
can.addEventListener('mousedown', async function(e) {
  mouseDown = true;
  await pos(e);
  await update();
  await draw();
});
can.addEventListener('mouseup', function(e) {
  mouseDown = false;
});
function pos(e) {
  return new Promise(function(resolve) {
    mouseDownX = (e.clientX -
                  can.getBoundingClientRect().left);
    mouseDownY = -(e.clientY -
                   can.getBoundingClientRect().bottom) / 2.5;
    resolve();
  })
}

図15 スライダ機能のコード

let canScale = document.getElementById('canScale');
canScale.onchange = async function() {
  qLineMargin = this.value;
  await setCanvas();
  await update();
  await draw();
  await scrollBottom();
}

図16 apply()関数のコード

async function apply() {
  const {bpm, musicL} = getMusicInfoViaElement()
  await numberQLine(bpm, musicL);
  await setCanvas();
  await setQLine();
  await setXLine();
  await update();
  await draw();
  return Promise.resolve();
}

図17 convert()関数のコード

function calcNote(jnoteValue, calced, musicBody) {
  return Math.round((jnoteValue+calced+musicBody*1000)*100)/100;
}
async function convert() {
  await apply();
  const fileName = document.getElementById('fileName').value;
  const speed = document.getElementById("speed").value;
  const {bpm, musicL, musicBody} = getMusicInfoViaElement()
  const {noteValue, note32Value, note6Value} = calcNoteValue(bpm)
  const outInfo = Array()
  xLine.forEach((val1, i) => {
    val1.forEach((val2, j) => {
      val2.forEach((val3, k) => {
        if (val3.note) {
          const tmp = k < 8 ? calcNote(j*noteValue, k*note32Value, musicBody)
                      : calcNote(j*noteValue, k%8*note6Value, musicBody)
          outInfo.push(Array(1, i+1, speed, tmp))
        }
      })
   })
 })
 createAndDownloadCsv(fileName, outInfo, bpm, musicL, musicBody);
}

Vol.73 補足情報

投稿日:2021.07.25 | カテゴリー: コード

特集1 PowerShellを学ぼう

pp.18-21の図33~図34のキャプション内容に誤りがありました。キャプションの内容が一つずつずれています。正しくは、以下の通りです。お詫びして訂正いたします。

図33 テーマ「powerlevel10k_rainbow」を適用した画面
図34 フォントのインストール
図35 インストールしたフォントをPowerShellに適用する
図36 プロファイルの配置を確認する
図37 Windows PowerShell ISEの起動
図38 ISEの初期画面
図39 インテリセンスにより候補を表示する
図40 コマンド アドオンからコマンドレットを調べる
図41 コマンド アドオンからコマンドレットを選択したら、パラメータを付けてそのまま実行できる
図42 ISEのデバックメニュー
図43 ブレークポイントは茶色で表示される
図44 スニペットを表示して挿入できる
図45 ISEが意図しない状態で停止した場合に表示されるダイアログ
図46 復元されたスクリプトはタブに「回復済み」と表示される
図47 時刻を取得するスクリプト(writeTime.ps1)

p.22の「図48 ランダムな名前、ランダムな作成日でファイルを作成するスクリプト(CreateRandomFiles.ps1)」の最終行として「}」が抜けていました。 お詫びして訂正いたします。

特集3  ラズパイでコロナ感染症対策

p.59の「図2 pirm_sensor.pyのソースコード」の先頭にある「1 !/usr/bin/env python3」は「2 #!/usr/bin/env python3」の誤りです。お詫びして訂正します。

コラム「ユニケージは従来のやり方と何が違うのか(前編)」

2021年6月号(Vol.72)と同じ内容でした。2021年8月号(Vol.73)のコラムはこちらから閲覧できます(Kindle版やPDF版は反映済みです)。次号となる2021年10月号(Vol.74)にも掲載する予定です。お詫びして訂正します。

情報は随時更新致します。

Vol.72 補足情報

投稿日:2021.05.25 | カテゴリー: コード

訂正・補足情報はありません。
情報は随時更新致します。

シェルスクリプトマガジンvol.72 Web掲載記事まとめ

投稿日:2021.05.25 | カテゴリー: コード

004 レポート eBPF for Windows
005 レポート TechLIONが10周年
006 NEWS FLASH
008 特集1 本格的なホームサーバーを構築/麻生二郎 コード掲載
020 特集2 MDSを始めよう!(基礎編)/生駒眞知子 
029 Hello Nogyo!
030 特別企画 ローコード開発基盤 OutSystems/阿島哲夫
040 Raspberry Piを100%活用しよう/米田聡 コード掲載
042 機械学習ことはじめ/川嶋宏彰 コード掲載
052 インタプリタ/桑原滝弥、イケヤシロウ
054 レッドハットのプロダクト/暮林達也
066 Pythonあれこれ 飯尾淳
070 香川大学SLPからお届け!/重松亜夢 コード掲載
076 法林浩之のFIGHTING TALKS/法林浩之
078 中小企業手作りIT化奮戦記/菅雄一
082 MySQLのチューニング/稲垣大助
090 Bash入門/大津真
098 Techパズル/gori.sh
099 コラム「ロールオーバーラップのススメ」/シェル魔人

特集1 本格的なホームサーバーを構築(Vol.72記載)

投稿日:2021.05.25 | カテゴリー: コード

著者:麻生 二郎

小型コンピュータボードの最上位モデルである「Raspberry Pi 4 Mobel B」の4G/8Gバイト版と、人気のLinuxディストリビューションのサーバー版「Ubuntu Server」を組み合わせて、本格的なサーバーを構築しましょう。本特集では、サーバー向けにハードウエアを強化する方法を紹介します。

シェルスクリプトマガジン Vol.72は以下のリンク先でご購入できます。

図20 無線LANの設定

    wifis:
        wlan0:
            access-points:
                SSID:
                    password: パスワード
            dhcp4: true

図27 固定IPアドレスを割り当てる

wifis:
  wlan0:
    dhcp4: false
    addresses: [192.168.10.100/24]
    gateway4: 192.168.10.1
    nameservers:
      addresses: [192.168.10.1]
    access-points:
            SSID:
              password: パスワード

Raspberry Piを100%活用しよう(Vol.72掲載)

投稿日:2021.05.25 | カテゴリー: コード

著者:米田 聡

小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。第3回は、温度、湿度、気圧を測定する拡張基板を扱います。

シェルスクリプトマガジン Vol.72は以下のリンク先でご購入できます。

図4 温度、湿度、気圧を測定するサンプルプログラム(sample.py)

import smbus2
import bme280

BME_ADDRESS = 0x76      # BME280のI2Cアドレス
bus = smbus2.SMBus(1)
data = bme280.sample(bus, BME_ADDRESS) # 測定データを得る

print("温度: %.2f ℃" % data.temperature)
print("気圧: %.2f hPa" % data.pressure)
print("湿度: %.2f %%" % data.humidity)

機械学習ことはじめ(Vol.72掲載)

投稿日:2021.05.25 | カテゴリー: コード

著者:川嶋 宏彰

本連載では、機械学習の基礎となるさまざまな手法の仕組みや、それらの手法のPythonでの利用方法を解説していきます。第1回となる今回は、機械学習の概要についても解説します。また、「k近傍法」という手法を使いながら機械学習の重要な概念をいくつか紹介します。

シェルスクリプトマガジン Vol.72は以下のリンク先でご購入できます。

図7 データセットを読み込んで散布図行列をプロットするPythonコード

import seaborn as sns
import matplotlib.pyplot as plt

plt.rcParams['font.size'] = 14

# seabornより読み込む場合
penguins = sns.load_dataset('penguins')

# seabornではなくpalmerpenguinsから読み込む場合
# from palmerpenguins import load_penguins
# penguins = load_penguins()

# 散布図行列をプロット
sns.pairplot(penguins, hue='species')
plt.show()

# 相関係数を計算
print(penguins.corr())

図9 二つの特徴量を抽出して散布図をプロットするPythonコード

features = ['bill_depth_mm', 'body_mass_g']
target_species = ['Adelie', 'Gentoo']

# 特徴量をクラスラベルと共に取り出す
df = penguins[['species'] + features].copy()
df.dropna(inplace=True)  # NaN が含まれる行は削除

# 読み込むデータセットを対象種のものだけにする
df2 = df[df['species'].isin(target_species)].copy()
print(df2.shape)  # (274, 3) と表示される

# カラーパレットの取得と設定
palette = sns.color_palette()
palette2 = {'Adelie':palette[0], 'Gentoo':palette[2]}

fig = plt.figure(figsize=(5, 5))
sns.scatterplot(data=df2, x=features[0], y=features[1],
                hue='species', palette=palette2)
# plt.axis('equal')  # ★この行は後で利用
plt.show()

図11 k-NNによる判定をするシンプルなPythonコード

import numpy as np
import scipy

X = df2[features].values  # 2次元特徴量
y = df2['species'].values  # クラスラベル

x_test = np.array([16, 4000])  # 判定したいデータ
k = 5  # 近い順に何個のデータまで見るか

# x_testとXの各点(各行)との距離の二乗
dist2 = ((X - x_test) ** 2).sum(axis=1)
# 距離の小さいk個の点のラベル
k_labels = y[np.argsort(dist2)][:k]
result = scipy.stats.mode(k_labels)[0][0]  # 最頻ラベル

図12 k-NNによる判定をする改良版のPythonコード

from sklearn.neighbors import KNeighborsClassifier

clf = KNeighborsClassifier(n_neighbors=k)
clf.fit(X, y)  # 学習
y_pred = clf.predict([[16, 4000], [16, 5000]])  # 一度に複数判定
print(y_pred)

図13 分類器の決定境界を描画するPythonコード

import matplotlib as mpl

def plot_decision_boundary(X, y, clf, xylabels=features, palette=None):
    # 分類器clfの決定境界を描画
    fig = plt.figure(figsize=(5, 5))
    # 2次元空間にグリッド点を準備
    xmin = X.min(axis=0)  # 各列の最小値
    xmax = X.max(axis=0)  # 各列の最大値
    xstep = [(xmax[i]-xmin[i]) / 50 for i in range(2)]  # グリッドのステップ幅
    xmin = [xmin[i] - 8*xstep[i] for i in range(2)]  # 少し広めに
    xmax = [xmax[i] + 8*xstep[i] for i in range(2)]
    aranges = [np.arange(xmin[i], xmax[i] + xstep[i], xstep[i]) for i in range(2)]
    x0grid, x1grid = np.meshgrid(*aranges)
    # 各グリッド点でクラスを判定
    y_pred = clf.predict(np.c_[x0grid.ravel(), x1grid.ravel()])
    y_pred = y_pred.reshape(x0grid.shape)  # 2次元に
    y_pred = np.searchsorted(np.unique(y_pred), y_pred)  # 値をindexへ

    clist = palette.values() if type(palette) is dict else palette
    cmap = mpl.colors.ListedColormap(clist)
    plt.contourf(x0grid, x1grid, y_pred, alpha=0.3, cmap=cmap)
    sns.scatterplot(x=X[:, 0], y=X[:, 1], hue=y, palette=palette)
    plt.legend()
    plt.xlim([xmin[0], xmax[0]])
    plt.ylim([xmin[1], xmax[1]])
    plt.xlabel(xylabels[0])
    plt.ylabel(xylabels[1])

clf_orig = KNeighborsClassifier(n_neighbors=k)
clf_orig.fit(X, y)
plot_decision_boundary(X, y, clf_orig, palette=palette2)
plt.show()

図16 スケーリング後に決定境界を描画するPythonコード

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
Xs = scaler.fit_transform(X)
# StandardScaler を用いず以下のようにしてもよい
# Xs = (X - X.mean(axis=0))/X.std(axis=0)

clf_scaled = KNeighborsClassifier(n_neighbors=k)
clf_scaled.fit(Xs, y)

# 軸ラベル変更
xylabels = [s.replace('_mm', '_s').replace('_g', '_s')
           for s in features]
# スケーリング後の決定境界を描く
plot_decision_boundary(Xs, y, clf_scaled, 
                       xylabels=xylabels, palette=palette2)
plt.show()

図21 kの値を変えた場合の3種のペンギンの決定境界を描画するPythonコード

# 取り出す特徴量を変える
features2 = ['bill_depth_mm', 'bill_length_mm']
df3 = penguins[['species'] + features2].copy()
df3.dropna(inplace=True)  # NaN が含まれる行は削除
Xs = scaler.fit_transform(df3[features2])
y = df3['species']

palette3 = dict(zip(y.unique(), palette))  # 3種用カラーパレット
xylabels = [s.replace('_mm', '_s') for s in features2]

for k in [1, 5, 11]:
    clf_scaled = KNeighborsClassifier(n_neighbors=k)
    clf_scaled.fit(Xs, y)
    plot_decision_boundary(Xs, y, clf_scaled, 
                           xylabels=xylabels, palette=palette3)
    plt.title(f'k = {k}')
    plt.legend(loc='upper left', borderaxespad=0.2, fontsize=12)
    plt.show()

香川大学SLPからお届け!(Vol.72掲載)

投稿日:2021.05.25 | カテゴリー: コード

著者:重松 亜夢

今回は、「gRPC」という通信プロトコルを使った、Webアプリケーションの作成方法を紹介します。gRPCを使うことで、通信量が減らせます。最近注目のマイクロサービスの連携にも活用できます。

シェルスクリプトマガジン Vol.72は以下のリンク先でご購入できます。

図3 「pb/picture.proto」ファイルに記述する内容

syntax = "proto3";
option go_package = "example.com/user_name/sample/pb/go/picture";
package picture;
service Picture {
  rpc GetPictures (GetPicturesRequest) returns (GetPicturesReply) {}
}
message GetPicturesRequest {
  uint32 num = 1;
}
message GetPicturesReply {
  repeated bytes pictures = 1;
}

図4 「pb/protoc-web/Dockerfile」ファイルに記述する内容

FROM node:15-buster
WORKDIR /pb
RUN npm i rimraf -g
RUN curl -L -O https://github.com/protocolbuffers/protobuf/releases/download/v3.15.8/protoc-3.15.8-linux-x86_64.zip
RUN curl -L -O https://github.com/grpc/grpc-web/releases/download/1.2.1/protoc-gen-grpc-web-1.2.1-linux-x86_64
RUN unzip protoc-3.15.8-linux-x86_64.zip && cp ./bin/protoc /usr/local/bin/. && chmod +x /usr/local/bin/protoc
RUN cp protoc-gen-grpc-web-1.2.1-linux-x86_64 /usr/local/bin/protoc-gen-grpc-web && chmod +x /usr/local/bin/protoc-gen-grpc-web

図5 「pb/scripts/picture-compile.sh」ファイルに追記する内容

docker build protoc-web -t streaming-protoc-web
mkdir -p js/picture
docker run -v "$(pwd):/pb" -w /pb --rm streaming-protoc-web \
  protoc --proto_path=. picture.proto \
    --js_out=import_style=commonjs:js/picture \
    --grpc-web_out=import_style=typescript,mode=grpcwebtext:js/picture
mkdir -p ../services/client/src/pb
cp -r ./js/* ../services/client/src/pb/

図6 「Makefil e」ファイルの変更内容

proto: pb/js/picture/picture_pb.js
	# make .proto
pb/js/picture/picture_pb.js: pb/picture.proto
	bash ./pb/scripts/picture-compile.sh

図8 「docker-compose.yaml」ファイルに追加する内容

  proxy:
    container_name: sample-proxy-container
    image: envoyproxy/envoy-dev:1f642ab20b8975654482411537a6bdc5e2f6c4f6
    ports:
      - "8080:8080"
    volumes:
      - ./services/proxy/envoy.yaml:/etc/envoy/envoy.yaml

図9 「services/client/src/components/Picture.tsx」ファイルの内容

import { useState } from 'react';
import { GetPicturesRequest, GetPicturesReply } from "../pb/picture/picture_pb";
import { PictureClient } from "../pb/picture/PictureServiceClientPb";
import { Error } from 'grpc-web';
export const Picture = () => {
  const [num, setNumber] = useState(1); // 枚数の指定
  const [pictures, setPictures] = useState<JSX.Element[]>([]);
  const jspb = require('google-protobuf');
  const client = new PictureClient(http://${window.location.hostname}:8080/server, {}, {});
  const getPictures = () => {
    if (num <= 0) return;
    const request = new GetPicturesRequest();
    request.setNum(num);
    client.getPictures(request, {}, (err: Error, response: GetPicturesReply) => {
      if (err || response === null) { throw err; }
      setPictures(jspb.Message.bytesListAsB64(response.getPicturesList())
                  .map((images: string, index: number) => (
        <img key={${index}} width="200px"
             src={data:image/jpg;base64,${window.atob(images)}}
             alt="pictures" />
      )));
    });
  }
  const onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const n = event.target.valueAsNumber;
    if (!isNaN(n)) { setNumber(n); }
  };
  return (
    <div>
      <input type="number" min="1" defaultValue="1" onChange={onChange} />
      <button onClick={getPictures}>GetPictures</button>
      <div className="getPictures">{pictures}</div>
    </div>
  );
}

図10 「services/client/src/App.tsx」ファイルに記述する内容

import {Picture} from './components/Picture'
import './App.css';
function App() {
  return (
    <div className="App">
      <header className="App-header">
        <Picture/>
      </header>
    </div>
  );
}
export default App;

特別企画 ゼロから始めるEthereum(Vol.71記載)

投稿日:2021.03.25 | カテゴリー: コード

著者 志茂博、高畑祐輔

「インターネット誕生以来の革命」と称されることもあるブロックチェーン。そのブロックチェーン技術を利用したものの中で、幅広い用途での活用が注目されている「Ethereum(イーサリアム)」という分散アプリケーション基盤について解説します。後半では、実際に自分専用のブロックチェーンを立ち上げて操作する方法を紹介します。Ethereumとブロックチェーンの世界を体験してみてください。

シェルスクリプトマガジン Vol.71は以下のリンク先でご購入できます。

図4 「genesis.json」ファイルに記述する内容

{
  "config": {
    "chainId": 15
  },
  "nonce": "0x0000000000000042",
  "timestamp": "0x0",
  "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "extraData": "",
  "gasLimit": "0x8000000",
  "difficulty": "0x4000",
  "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "coinbase": "0x3333333333333333333333333333333333333333",
  "alloc": {}
}

Pythonあれこれ(Vol.71掲載)

投稿日:2021.03.25 | カテゴリー: コード

著者:飯尾淳

本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第1回は、アンケート結果から非定型なデータを取り出して集計します。

シェルスクリプトマガジン Vol.71は以下のリンク先でご購入できます。

図5 形態素解析の処理をするPythonコード

#!/usr/bin/env python
import spacy
import sys

nlp = spacy.load('ja_ginza')

for line in sys.stdin:                 # 一行ずつ処理
  for sent in nlp(line.strip()).sents: # 一文ずつに分解
    for token in sent:                 # さらに文中のトークンごとに処理
      # i:トークン番号, orth_:表層形, lemma_:基本形,
      # pos_:品詞(英語), tag_:品詞細分類(日本語)
      sys.stdout.write(f'{token.i}\t{token.orth_}\t{token.lemma_}\t'
                       f'{token.pos_}\t{token.tag_}\n')
    sys.stdout.write('EOS\n')

図7 名詞の連接処理と抽出をするPythonコード

#!/usr/bin/env python

import sys
import re

def sequence_gen():
  sequence = []
  for line in sys.stdin:
    if line == 'EOS\n':
      yield sequence
      sequence = []
      continue
    word_info = line.strip().split('\t')
    pos = word_info[4].split('-')
    sequence.append({'surface': word_info[1],
                     'base': word_info[2],
                     'pos': pos[0]})

pattern = re.compile('N+')

for seq in sequence_gen():
  encode_str = ''.join('N' if w['pos'] in ('名詞')
                       else '?' for w in seq)
  for m in pattern.finditer(encode_str):
    print(''.join(w['surface'] for w in seq[m.start():m.end()]))

図9 データを分析してプロットするPythonコード

#!/usr/bin/env python

import sys
import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
import spacy

from matplotlib import rcParams
rcParams['font.family'] = 'sans-serif'
rcParams['font.sans-serif'] = ['Hiragino Maru Gothic Pro',
    'Yu Gothic', 'Meiryo', 'Takao', 'IPAexGothic', 'IPAPGothic',
    'VL PGothic', 'Noto Sans CJK JP']
nlp = spacy.load('ja_ginza')
with open(sys.argv[1]) as f:
  texts = [s.strip() for s in f.readlines()]
vectors = [nlp(t).vector for t in texts]
pca = PCA(n_components=2).fit(vectors)
trans = pca.fit_transform(vectors)
pc_ratio = pca.explained_variance_ratio_
plt.figure()
plt.scatter(trans[:,0], trans[:,1])
i = 0
for txt in texts:
  plt.text(trans[i,0]+0.02, trans[i,1]-0.02, txt)
  i += 1
plt.hlines(0, min(trans[:,0]), max(trans[:,0]),
            linestyle='dashed', linewidth=1)
plt.vlines(0, min(trans[:,1]), max(trans[:,1]),
            linestyle='dashed', linewidth=1)
plt.xlabel('PC1 ('+str(round(pc_ratio[0]*100,2))+'%)')
plt.ylabel('PC2 ('+str(round(pc_ratio[1]*100,2))+'%)')
plt.tight_layout()
plt.show()

もっと見る

新しい記事一覧へ戻る

  • カテゴリー コード のアーカイブを表示しています。

  • -->