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

test

Pythonあれこれ

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

筆者:飯尾 淳

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

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

図4 プルダウンメニューでサブページに移動できるようにしたコード

import streamlit as st

selected = st.selectbox('なに食べる?',
                        ('-', 'ramen', 'curry', 'katsudon'))
pages = {'ramen': 'pages/1_🍜_ramen.py',
         'curry': 'pages/2_🍛_curry.py',
         'katsudon': 'pages/3_🍚_katsudon.py' }
if selected in pages.keys():
  st.switch_page(pages[selected])

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

import streamlit as st
import pandas as pd

df = pd.read_excel('TODO.xlsx')
df = st.data_editor(df)
clicked = st.button('保存', type='primary',
                    on_click=lambda:\
                    df.to_excel('TODO.xlsx', index=False))
if (clicked):
  st.switch_page('index.py')

図11 「2_new.py」ファイルに記述するコード

import streamlit as st
import pandas as pd

df = pd.read_excel('TODO.xlsx')
title = st.text_input(label='タイトル')
memo = st.text_area(label='内容')
date = st.date_input(label='締切')
done = False
def append():
  global df
  df_new = pd.DataFrame({'タイトル': [title],
                         '内容':     [memo], 
                         '締切':     [date],
                         '実施':     [done]})
  df = pd.concat([df, df_new])
  df.to_excel('TODO.xlsx', index=False)
clicked = st.button('登録', type='primary', on_click=append)
if (clicked): 
  st.switch_page('index.py')

図11 「2_new.py」ファイルに記述するコード

import streamlit as st
import pandas as pd

df = pd.read_excel('TODO.xlsx')
title = st.text_input(label='タイトル')
memo = st.text_area(label='内容')
date = st.date_input(label='締切')
done = False
def append():
  global df
  df_new = pd.DataFrame({'タイトル': [title],
                         '内容':     [memo], 
                         '締切':     [date],
                         '実施':     [done]})
  df = pd.concat([df, df_new])
  df.to_excel('TODO.xlsx', index=False)
clicked = st.button('登録', type='primary', on_click=append)
if (clicked): 
  st.switch_page('index.py')

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

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

著者:小河原 直道

Raspberry Pi 5(以下、ラズパイ5)が日本でも発売され、私も購入しました。センサーとの接続やサーバーの構築をしてみたいと思ったため、湿温度/気圧/CO₂濃度を測定できるセンサーを接続し、その値をWebブラウザから確認できるシステムを作成しました。このシステムの作成ポイントについて解説します。

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

図8 BME280のチップIDを読み出すPythonコード

import spidev
# 読み取りは8ビット目を「1」に, 書き込みは「0」にする
def get_reg_addr(rw: str, addr: int) -> int:
  rw_flag = 0b1000_0000
  if rw == "w":
    rw_flag = 0b0000_0000
  return rw_flag | (addr & 0b0111_1111)
spi = spidev.SpiDev()
spi.open(0, 0) # bus0, cs0
spi.mode = 0   # mode0, CPOL=CPHA=0
spi.max_speed_hz = 10 * 10**6 # 10MHz
bme_id = spi.xfer2([get_reg_addr("r", 0xD0), 0])
print(f"id: {hex(bme_id[1])}")
spi.close()

図9 BME280の気温データを読み出すPythonコード

import spidev
# 読み取りは8ビット目を「1」に, 書き込みは「0」にする
def get_reg_addr(rw: str, addr: int) -> int:
  rw_flag = 0b1000_0000
  if rw == "w":
    rw_flag = 0b0000_0000
  return rw_flag | (addr & 0b0111_1111)
spi = spidev.SpiDev()
spi.open(0, 0) # bus0, cs0
spi.mode = 0   # mode0, CPOL=CPHA=0
spi.max_speed_hz = 10 * 10**6 # 10MHz
# 湿度の測定を有効化。オーバーサンプリングx1
r = spi.xfer2([get_reg_addr("r", 0xF2), 0])[1]
ctrl_hum = r & 0b1111_1000 | 0b001
spi.xfer2([get_reg_addr("w", 0xF2), ctrl_hum])
# 気圧、気温の測定を有効化。オーバーサンプリングx1、ノーマルモードに
ctrl_meas = (0b001 << 5) + (0b001 << 3) + 0b11
spi.xfer2([get_reg_addr("w", 0xF4), ctrl_meas])
# 気温データの読み出し
temp_raw_bins = spi.xfer2([get_reg_addr("r", 0xFA), 0, 0, 0])
temp_raw = (temp_raw_bins[1] << 8+4) | (temp_raw_bins[2] << 4) |\
  ((temp_raw_bins[3]>>4) & 0b00001111)
print(f"気温: {temp_raw}")
spi.close()

図10 MH-Z19CのCO2濃度データを読み出すPythonコード

import serial
import sys
import time

serial = serial.Serial(port="/dev/ttyAMA0", timeout=5)
# 念のためバッファをリセットする
serial.reset_input_buffer()
serial.reset_output_buffer()
time.sleep(2) # 待ち時間は適宜増減して調整
s = bytearray.fromhex("FF 01 86 00 00 00 00 00 79")
serial.write(s)
r = serial.read(size=9)
serial.close()
if r[0] != 0xFF:
  print("invalid data\n")
  sys.exit(-1)
for_check=0
for i, b in enumerate(r):
  if i==0 or i==8:
    continue
  # チェックサムは1バイトのみ、あふれた分は捨てる
  for_check = for_check + b & 0b1111_1111
for_check = 0xFF - for_check + 1
if r[8] != for_check:
  print("checksum error\n")
  sys.exit(-1)
print(f"{r[2]*256 + r[3]} ppm")

図12 測定データをデータベースに保存するPythonコード

import mariadb
import sys

try:
  conn = mariadb.connect(
    user     = ユーザー名,  password = パスワード,
    host     = "127.0.0.1", port     = 3306,
    database = "sensor1",   autocommit = True
  )
except mariadb.Error as e:
  print(f"db connect error: {e}")
  sys.exit(-1)
cur = conn.cursor()
sql = 'INSERT INTO sensor (datetime, temp, humid, press, co2) VALUES(NOW(), ?, ?, ?, ?)'
データ(temp_data、humid_data、press_data、co2_data)取得用コードをここに記述
try:
  cur.execute(sql, (temp_data, humid_data, press_data, co2_data))
except Exception as e:
  print(f"insert error: {e}")
finally:
  cur.close()
conn.close()

図13 「db/db_pool.js」ファイルに記述するコード

const mariadb = require("mariadb");
const pool = mariadb.createPool({
  host:     "localhost",
  user:     ユーザー名,
  password: パスワード,
  database: "sensor1",
  connectionLimit: 5,
});
module.exports = pool;

図14 「route/api_v1.js」ファイルに記述するコード(抜粋)

const router = require("express").Router();
const pool = require("../db/db_pool");
function getColumnName(sensor){
  switch(sensor){
    case "all":return "*";
    case "temperature": return "datetime, temp"
    case "humidity": return "datetime, humid"
    case "pressure": return "datetime, press"
    case "co2": return "datetime, co2"
    default: throw new Error("sensor name error");
  }
}
router.get("/:sensor/latest", async(req, res) => {
  let sqlCol;
  try{
    sqlCol = getColumnName(req.params.sensor);
  } catch (e) { return res.status(404).send(bad sensor name); }
  try{
    let dbRes = await pool.query(
      select ${sqlCol} from sensor order by datetime desc limit 1
    );
    // クエリー結果の最初のレコードをJSON形式で送る
    return res.status(200).json(dbRes[0]);
  } catch(err) {
    return res.status(500).json({mes:err.toString()});
  }
});
module.exports = router;

図15 「route/index.js」ファイルに記述するコード

const express = require('express');
const router = express.Router();
const path = require('path');
router.get("/", (req, res) => {
  res.sendFile(path.join(__dirname, "../view/index.html"));
});
router.use("/api/v1", require("./api_v1"));
module.exports = router;

図16 「app.js」ファイルに記述するコード

const express = require("express");
const app = express();
const path = require('path');
app.use("/", require("./route/index"));
app.use(express.static(path.join(__dirname, "public")));
app.all("*", (req, res) => {
  return res.sendStatus(404);
});
app.listen(3000, () => {
  console.log("Listening at port 3000");
});

Markdownを活用する(Vol.90掲載)

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

著者:藤原 由来

本連載では文書の装飾や構造付けを手軽に行える記法である「Markdown」を用いて、さまざまな文書や成果物を作成する方法を紹介します。今回も前回に引き続き、Markdownベースの高機能なノートアプリ「Obsidian」を扱います。前回の内容を踏まえて、Obsidianの発展的な使い方を紹介します。

図11 デイリーノートのテンプレート(テンプレート_カレー日記)

テンプレート_カレー日記
作成日時:{{date}} {{time}}

## 今日のカレー

- メニュー:
- 感想:
- 評価:★★★☆☆

## お店の情報

- 店名:
- 住所:
- 最寄り駅:
- URL:

Raspberry Pi Pico W/WHで始める電子工作(Vol.90掲載)

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

著者:米田 聡

本連載では、人気のマイコンボード「Raspberry Pi Pico W/WH」を活用していきます。同ボードは、無
線LANやBluetoothの通信機能を搭載し、入手しやすく価格も手頃なので、IoT機器を自作するのに最適なハードウエアです。第6回は、DHT-11で測った湿温度をグラフ化します。

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

図2 10 分おきに湿温度を測定して配列に保存する「DhtDataLogger」クラスのコード(ddl.py)

import dht
import time
import gc
import micropython
import _thread
from machine import Pin
from machine import RTC
from machine import Timer

DHT_PIN = 15
TICK = 10
SIZE = 144

class DhtDataLogger():
    
    def __init__(self, dht_gpio = DHT_PIN, tick=TICK, size=SIZE):
        self.rtc = RTC()
        self.dht = dht.DHT11(Pin(dht_gpio))
        self.data = []
        self.tick = tick
        self.size = size
        # 1分おきにタイマー割り込みを呼ぶ
        self.timer = Timer(period=60*1000, mode=Timer.PERIODIC, callback=self._timer_handler)
        self.lock = _thread.allocate_lock()
    
    def _timer_handler(self, t):
        now = self.rtc.datetime()
        if now[5] % self.tick == 0:        
            micropython.schedule(self._logger, now)
    
    def _logger(self, now):
        # ロックをかける
        self.lock.acquire()
        try:
            self.dht.measure()
        except OSError as e:
            pass
        
        # 測定日時
        nowstr = "{0:02d}:{1:02d}:{2:02d}".format(now[4],now[5],now[6])
        self.data.append([nowstr, self.dht.temperature(), self.dht.humidity()])
        if len(self.data) > self.size:
            del self.data[0]
        # debug
        # print(self.data[-1])
        # ロック解除
        self.lock.release()
        # ガベージコレクト
        gc.collect()

    def get_data(self):
        if len(self.data) == 0:
            return None
        # 配列のコピーを返す        
        rval = []
        self.lock.acquire()
        for v in self.data:
            rval.append(v)
        self.lock.release()
        return rval

図3 蓄積した湿温度をグラフとして表示する簡易Webサーバーのプログラム(main.py)

import usocket as socket
import network
import time
import ujson as json
import gc
from machine import Timer
from ddl import DhtDataLogger

http_header = '''
HTTP/1.1 200 OK\r
Content-Type: text/html;charset=UTF-8\r
Cache-Control: no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate\r
Connection: close\r
\r
'''

html_base='''
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>Pico W</title>
</head>
%s
</html>
'''

graph_body = '''
<body>
  <canvas id="DHTChart"></canvas>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.js"></script>
    
  <script>
  var dhtc = document.getElementById("DHTChart");
  var DHTChart = new Chart(dhtc, {
    type: 'line',
    data: {
      labels: %s,
      datasets: [
        {
          label: '気温(度)',
          data: %s,
          borderColor: "rgba(255,0,0,1)",
          backgroundColor: "rgba(0,0,0,0)"
        },
        {
          label: '湿度(%)',
          data: %s,
          borderColor: "rgba(0,0,255,1)",
          backgroundColor: "rgba(0,0,0,0)"
        }
      ],
    },
    options: {
      title: {
        display: true,
        text: '気温と湿度'
      },
      scales: {
        yAxes: [{
          ticks: {
            suggestedMax: 100,
            suggestedMin: -10,
            stepSize: 10,
          }
        }]
      },
    }
  });
  </script>
</body>
'''

logger = DhtDataLogger()

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('', 80))
sock.listen(4)

try:
    while True:
        conn, addr = sock.accept()
        connect_from = str(addr[0])
        req = conn.recv(1024).decode()
        if req.startswith('GET / HTTP/1.1'):
            tlist = logger.get_data()
            if tlist is None:
                body = html_base % ("<body><h1>Data not ready</h1></body>")
            else:
                labels = []
                temps = []
                hums = []
                for v in tlist:
                    labels.append(v[0])
                    temps.append(v[1])
                    hums.append(v[2])
                body = graph_body % (json.dumps(labels), json.dumps(temps), json.dumps(hums))
            
            html = html_base % (body)
            # 送信            
            conn.send(http_header)
            conn.sendall(html)
            conn.close()
                        
        else:
            conn.sendall('HTTP/1.1 404 Not Found\r\n')
            conn.close()
    
        gc.collect()

except KeyboardInterrupt:
    pass

sock.close()

特集2 NVIDIA NeMoで独自の生成AIを構築する(Vol.90記載)

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

著者:澤井 理紀、藤田 充矩

大規模言語モデル(LLM)に基づく生成AIが注目を集めています。そうした生成AIを活用するには、LLMを特定のタスクやデータセットに最適化する「ファインチューニング」が欠かせません。本特集では、生成AIを支える技術を解説した上で、NVIDIAが提供する生成AI向けのフレームワーク「NVIDIA NeMo」を使って独自の生成AIをローカル環境に構築し、LLMをファインチューニングする方法を紹介します。

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

図2 LLMダウンロード用のPythonスクリプト

import os
from huggingface_hub import snapshot_download

MODEL_DIR = "./models"
os.makedirs(MODEL_DIR, exist_ok=True)
snapshot_download(
    repo_id="elyza/ELYZA-japanese-Llama-2-7b",
    local_dir=f"{MODEL_DIR}/ELYZA-japanese-Llama-2-7b",
    local_dir_use_symlinks=False
    )

図4 PEFTに使用するデータセットの形式を変換するPythonスクリプト

import json
import os
import random

INPUT_TRAIN = "./JGLUE/datasets/jcommonsenseqa-v1.1/train-v1.1.json"
INPUT_VALID = "./JGLUE/datasets/jcommonsenseqa-v1.1/valid-v1.1.json"
OUTPUT_DIR = "./data/jcommonsenseqa-v1.1"
random.seed(42)
os.makedirs(OUTPUT_DIR, exist_ok=True)
def write_jsonl(fname, json_objs):
    with open(fname, 'wt') as f:
        for o in json_objs:
            f.write(json.dumps(o, ensure_ascii=False)+"\n")
def form_question(obj):
    st = ""
    st += "### 指示:\n"
    st += "与えられた選択肢の中から、最適な答えを選んでください。"
    st += "出力は以下から選択してください:\n"
    st += f"- {obj['choice0']}\n"
    st += f"- {obj['choice1']}\n"
    st += f"- {obj['choice2']}\n"
    st += f"- {obj['choice3']}\n"
    st += f"- {obj['choice4']}\n"
    st += "### 入力:\n"
    st += f"{obj['question']}\n"
    st += "### 応答:"
    return st
def prosess(input_path, train=False):
    with open(input_path) as f:
        dataset = [json.loads(line) for line in f.readlines()]
    processed = []
    for data in dataset:
        prompt = form_question(data)
        answer = data[f"choice{data['label']}"]
        processed.append({"input": prompt, "output": f"{answer}"})
    if train:
        random.shuffle(processed)
        train_ds = processed[:-1000]
        valid_ds = processed[-1000:]
        write_jsonl(f"{OUTPUT_DIR}/train-v1.1.jsonl", train_ds)
        write_jsonl(f"{OUTPUT_DIR}/valid-v1.1.jsonl", valid_ds)
    else:
        write_jsonl(f"{OUTPUT_DIR}/test-v1.1.jsonl", processed)
    return
def main():
    prosess(INPUT_TRAIN, train=True)
    prosess(INPUT_VALID)
if __name__ == "__main__":
    main()

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

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

004 レポート 米OpenAI社の新LLM「GPT-4o」登場
005 レポート JPEGライブラリ「jpegli」公開
006 製品レビュー バイク用エアバッグ「TECH-AIR 5」
007 NEWS FLASH
008 特集1 2024年大注目のMicrosoft Copilot/三沢友治
018 特集2 NVIDIA NeMoで独自の生成AIを構築する/澤井理紀、藤田充矩 コード掲載
026 特集3 Raspberry Pi 5を使う/麻生二郎
036 特別企画 AlmaLinux 9のインストール/麻生二郎
042 Raspberry Pi Pico W/WHで始める電子工作/米田聡 コード掲載
046 Markdownを活用する/藤原由来 コード掲載
052 中小企業手作りIT化奮戦記/菅雄一
056 香川大学SLPからお届け!/小河原直道 コード掲載
062 Pythonあれこれ/飯尾淳 コード掲載
068 これだけは覚えておきたいLinuxコマンド/大津真
076 Techパズル/gori.sh
077 コラム「プロジェクトリーダーの役割」/シェル魔人

Vol.90

投稿日:2024.05.25 | カテゴリー: バックナンバー

 AI(人工知能)による文章、画像、映像、会話、音楽などの自動生成技術「生成AI」がとても注目されています。特に、レポートで紹介した、米OpenAI社の大規模言語モデル(LLM)「GPT-4o」は、人と会話しているように話せる驚きの技術です。シェルスクリプトマガジンでもこの生成AIに着目し、特集1と特集2でも生成AIに関するサービスや技術を取り上げました。
 特集1では、マイクロソフトが提供する生成AIサービス「Microsoft Copilot」を紹介しています。同サービスは、オフィスソフト「Microsoft Office」を含むクラウドサービス「Microsoft 365」から利用できます。そのため、ビジネス分野における生成AIの活用が期待できます。
 特集2では、米NVIDIA社の生成AIフレームワーク「NVIDIA NeMo」による独自生成AI環境を構築する方法を解説しています。同社は、生成AIや機械学習などに不可欠な半導体チップ(素子)「GPU」(Graphics Processing Unit)において世界トップシェアの企業であり、AIに関する多くの最先端技術を有しています。
 特集3では、2024年2月に国内販売が開始された小型コンピュータボードの最上位機種「Raspberry Pi 5」を紹介しています。Raspberry Pi 5では、CPUやGPUの性能が向上し、PCI Expressの外部インタフェースが追加されています。このPCI ExpressにSSDを接続することで、ストレージの高速化と大容量化が期待できます。AI分野でも活用が進む小型コンピュータとして注目の製品です。
 特別企画では、2024年6月末でサポートが切れるLinuxディストリビューション「CentOS Linux」の代替となる「AlmaLinux」のインストール方法を紹介しました。AlmaLinuxは無料で利用でき、サポートが必要なら国内でも有償で受けられます。ビジネスの分野で利用する場合なら、お薦めの代替候補です。
 このほか、新連載として「これだけは覚えておきたいLinuxコマンド」が始まりました。数回に分けて、初心者向けに知っておくべきLinuxコマンドをまとめています。
 今回も読み応え十分のシェルスクリプトマガジン Vol.90。お見逃しなく!

※記事掲載のコードはこちら。記事の補足情報はこちら

※読者アンケートはこちら

Vol.90 補足情報

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

特別企画 AlmaLinux 9のインストール

 p.36の「2024年6月31日」は「2024年6月30日」の誤りです。同じページの最初から4行目の「をして」は「として」の誤りです。p.37の右段下から2行目の「Ubuntu Server」は「AlmaLinux」の誤りです。お詫びして訂正します。
 p.37で紹介したISOイメージの書き込みツール「balenaEtcher」の最新版(バージョン1.19.×)では、「PORTABLE」版が提供されていません。記事で紹介したバージョン1.18.11のPORTABLE版(balenaEtcher-Portable-1.18.11.exe)はここからダウンロードできます。

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

  • shell-mag ブログの 2024年5月 のアーカイブを表示しています。

  • -->