ラズパイ5のデスクをUSB SSD(SATA)にしてみた

以前の記事でNVMeのSSDにした記事掲載しましたが、どうも安定して動作しないので結局SDカードに戻してましたが、今回USBのSATA SSDのケース買ってそこにイメージ写してシステムデスク(128GB SATA SSD)を変更してみた

<SDカードをUSB SSD化する>

SD Card Copierで丸ごとコピー(New Partition UUIDsにチェック忘れずに)

再起動で、

・boot orderの確認
pi@rasp5:~ $ sudo rpi-eeprom-config
[all]
BOOT_UART=1
POWER_OFF_ON_HALT=0
BOOT_ORDER=0xf146	#NVMe / USB / SDカードのオーダー
PCIE_PROBE=1

Sdカードよりも優先順位は高くなっているけど最優先にする

$ sudo rpi-eeprom-config --edit

で変更してrebootで、

$ sudo rpi-eeprom-config
[all]
BOOT_UART=1
POWER_OFF_ON_HALT=0
BOOT_ORDER=0xf164
PCIE_PROBE=1

SDカードは抜いてないから見えてるけど、USB SSDに切り替わった、loopxはsnapで仮想的にインストールされたモジュールが割り当てされるドライブネームなのでmountpointsは/snapから始まっている

ついでに性能測定、

     Category                  Test                      Result     
HDParm                    Disk Read                 361.17 MB/sec            
HDParm                    Cached Disk Read          327.60 MB/sec            
DD                        Disk Write                292 MB/s                 
FIO                       4k random read            29510 IOPS (118040 KB/s) 
FIO                       4k random write           24323 IOPS (97292 KB/s)  
IOZone                    4k read                   44614 KB/s               
IOZone                    4k write                  42302 KB/s               
IOZone                    4k random read            22563 KB/s               
IOZone                    4k random write           44361 KB/s               

                          Score: 12810         

sdカードに比較して書き込みは一桁上、MVNEe SSDのPCIe接続に比較すると1/3~1/4ぐらい、そんなものかな(以下のリンクは過去記事)

ラズパイ5のドライブをSSDにする

実装はこんな感じ、chatbotのエンビパイプの中に収めてる、

ケースは、

https://www.amazon.co.jp/dp/B0C2D1ZZGW?ref=ppx_yo2ov_dt_b_fed_asin_title

 

admin

Luantiサーバーのインストールとsnap

Wi-Fi内でローカルで使うためのLuantiサーバー、色々やってみたけど結局行き着いたのはラズパイ5

① wsl2:一見筋が良さそうだけど、Windowsのセキュリティを乗り越えられない、つまりportが通らない

② USB SSDでLinux:これだとWindows関係なくなるから筋は良さそうなのだけど、HP NoteBookのUEFIのガードが固くてUSB SSDのbootができない、bootableと認識していないということ

③ MacのVMware FusionのUbuntuで立ち上げる:これはMacBookのローカルからだと仲間と認識してもらえるけど、外部からは同様の問題がある、Fusion Proだと同じネットワーク内のアドレス取得されるから良さそうだけど、結局***.localは使えないよね

④ ラズパイ5:これがおそらく一番素直、つまり仮想マシン配下に構築すると必ずネットワークが通らないための工夫が必要だけど、専用機なら何ら問題ないしmDNSも使えるから固定I Pである必要もない

<ラズパイ5でのインスト手順>

ここでsnapが出てくる、aptではLunatiが結構古いバージョンだったりするらしくて、最新版を取得するならばsnapを使う、ソースコンパイルという手段もあるけど結構手間ではある

$ sudo apt update
$ sudo apt install snapd

snapとはLinuxにおける新たなパッケージ管理ツール(アプリ単位の隔離で依存関係を全部まとめることでosから切り離されるからaptでインストールできないアプリもインストールできる)、サイズと起動時間がネックかなFlatpakがデスクトップアプリでsnapはサーバーサイドアプリという棲み分けかな、起動時間とか問題なること少ないし
https://www.choge-blog.com/programming/ubuntu-snap/

$ sudo reboot

snapdの最新版をsnapでインストールする
$ sudo snap install snapd

$ sudo snap install luanti

snapのラズパイ用のLuantiはクライアントとサーバーが兼用らしい

$ luantiで起動して作成ずみのworldをDLしてデフォルトに設定する

$ cat ~/snap/luanti/common/.minetest/minetest.conf 
server_announce = false
maintab_LAST = online
enable_damage = true
creative_mode = false
mainmenu_last_selected_world = 1
default_game = minetest 


$ mkdir -p ~/snap/luanti/common/.minetest/games/
$ cd ~/snap/luanti/common/.minetest/games/
$ git clone https://github.com/luanti-org/minetest_game.git minetest

起動
$ luanti --server --worldname test --gameid minetest --port 30000

2025-12-24 19:33:28: WARNING[Main]: system-wide share not found at "/usr/share/luanti"
2025-12-24 19:33:28: WARNING[Main]: system-wide share found at "/snap/luanti/11/usr/bin/../share/luanti"
2025-12-24 19:33:28: [Main]: Using world specified by --worldname on the command line
2025-12-24 19:33:28: [Main]: Using game specified by --gameid on the command line
 _                   _   _ 
| |_   _  __ _ _ __ | |_(_)
| | | | |/ _` | '_ \| __| |
| | |_| | (_| | | | | |_| |
|_|\__,_|\__,_|_| |_|\__|_|  ___ 5.14.0
2025-12-24 19:33:28: ACTION[Main]: World at [/home/pi/snap/luanti/common/.minetest/worlds/test]
2025-12-24 19:33:28: ACTION[Main]: Server for gameid="minetest" listening on [::]:30000.

クライアントの画面

snapは起動時間が長くなるというけど、サーバーなら起動しちまえば終わりだから問題にはならない、sdカード使ってるけど特にレスポンスが遅いとも感じない

 

admin

ラズパイのsdカードイメージのバックアップと復元

すぐにやり方忘れるので、過去記事に見当たらないので作成

<環境>

MacBook Pro M4/Tahoe

SDカード:ラズパイ5で使っている64GBのSDカード

 

<手順>

・Macに差し込んでdisk番号の確認

% diskutil list
/dev/disk0 (internal, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                        *1.0 TB     disk0
   1:             Apple_APFS_ISC Container disk1         524.3 MB   disk0s1
   2:                 Apple_APFS Container disk3         994.7 GB   disk0s2
   3:        Apple_APFS_Recovery Container disk2         5.4 GB     disk0s3

/dev/disk3 (synthesized):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      APFS Container Scheme -                      +994.7 GB   disk3
                                 Physical Store disk0s2
   1:                APFS Volume Macintosh HD            12.2 GB    disk3s1
   2:              APFS Snapshot com.apple.os.update-... 12.2 GB    disk3s1s1
   3:                APFS Volume Preboot                 8.1 GB     disk3s2
   4:                APFS Volume Recovery                1.2 GB     disk3s3
   5:                APFS Volume Data                    455.7 GB   disk3s5
   6:                APFS Volume VM                      5.4 GB     disk3s6

/dev/disk4 (disk image):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                        +17.3 GB    disk4
   1:                 Apple_APFS Container disk5         17.3 GB    disk4s1

/dev/disk5 (synthesized):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      APFS Container Scheme -                      +17.3 GB    disk5
                                 Physical Store disk4s1
   1:                APFS Volume iOS 17.4 21E213 Simu... 16.8 GB    disk5s1

/dev/disk6 (external, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                        *1.0 TB     disk6
   1:                 Apple_APFS Container disk7         1.0 TB     disk6s1

/dev/disk7 (synthesized):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      APFS Container Scheme -                      +1.0 TB     disk7
                                 Physical Store disk6s1
   1:                APFS Volume Extreme SSD             370.5 GB   disk7s2

/dev/disk8 (disk image):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                        +17.6 GB    disk8
   1:                 Apple_APFS Container disk9         17.6 GB    disk8s1

/dev/disk9 (synthesized):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      APFS Container Scheme -                      +17.6 GB    disk9
                                 Physical Store disk8s1
   1:                APFS Volume iOS 17.5 21F79 Simul... 17.0 GB    disk9s1

/dev/disk11 (internal, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *61.9 GB    disk11
   1:             Windows_FAT_32 bootfs                  536.9 MB   disk11s1
   2:                      Linux                         61.4 GB    disk11s2

最後の/dev/disk11が該当

・該当diskをunmountする

mountしたままではMacOSの配下にあって、キャッシュとかでまともにバックアップが取れないと

% diskutil unmountDisk /dev/disk11
でunmountする

・ddコマンドでバックアップイメージの作成

% sudo dd if=/dev/disk11 of=rasp5_chatbot.img bs=1m
でbackupが時間はかかる(おそらく30分近くかな)けど作成される

59024+0 records in
59024+0 records out
61891149824 bytes transferred in 1211.657719 secs (51079731 bytes/sec)

<復元>

復元時も同様にdisk番号取得してsdカードに書き込み(x)には取得したsdカードのdisk番号を入れる

% sudo dd if=rasp5_cahtbot.img of=/dev/disk(x) bs=1m

 

admin

ラズパイでAvahiは外部サービスの状態をモニターしている

WSGIやFlaskサーバーでサービスを再起動すると、なぜmDNSが再度実行されるかというとAvahi(mDNSのラズパイ実装)がサービスをモニターしていて、再起動されるとCache flushを実行して、結果としてクライアントは再びmDNSを実行するようです

<tcpdumpの実行ログ>

MacBookからtfliteサービスのリクエストを出した時のログ、一度Cache flushが発行されると、タイムアウト(TTL)するまでは発行されない

% sudo tcpdump -i en0 udp port 5353 and src host 192.168.1.19

Password:
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on en0, link-type EN10MB (Ethernet), snapshot length 524288 bytes

(サービス立ち上げ後にクライアントから最初にリクエスト)
09:36:44.585023 IP 192.168.1.19.mdns > 224.0.0.251.mdns: 0*- [0q] 1/0/0 (Cache flush) A 192.168.1.19 (39)
09:36:44.585025 IP 192.168.1.19.mdns > 224.0.0.251.mdns: 0*- [0q] 1/0/0 (Cache flush) AAAA 240d:1a:896:8300:99fc:1d59:78ab:a156 (51)


(サービスの再起動後のリクエスト)
09:39:08.361077 IP 192.168.1.19.mdns > 224.0.0.251.mdns: 0*- [0q] 1/0/0 (Cache flush) AAAA 240d:1a:896:8300:99fc:1d59:78ab:a156 (51)
09:39:08.361545 IP 192.168.1.19.mdns > 224.0.0.251.mdns: 0*- [0q] 1/0/0 (Cache flush) A 192.168.1.19 (39)

Avahiはdaemonで動作しています

$ ps -aux|grep avahi
avahi        655  0.0  0.0   7696  2672 ?        Ss   09:25   0:00 avahi-daemon: running [rasp5.local]

Avahiがどのようなロジックで外部サービスの状態をモニターしているのかはソース読まないとわからないけど

https://github.com/avahi/avahi

READMEには

AVAHI SERVICE DISCOVERY SUITE

とあるから、mDNSの実装よりもこちらがメインのように思える

 

admin

tfliteをマルチコア(ラズパイ5)で動かした時の性能改善

従来のコードからの変更箇所、

    1. サーバーにFlaskではなく正式運用推奨のWSGIサーバーに変更
    2. スレッド数をsingleからラズパイ5のコア数である4に変更(num_threads)
# work on Flask server
# server will be activated only when client request is occured
#
#
from flask import Flask, jsonify
import cv2
import numpy as np
import tflite_runtime.interpreter as tflite
from picamera2 import Picamera2
from waitress import serve

app = Flask(__name__)

# モデル・ラベル初期化
interpreter = tflite.Interpreter(model_path="efficientdet_lite0.tflite", num_threads=4)
interpreter.allocate_tensors()
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
with open("coco_labels.txt", "r") as f:
    labels = [line.strip() for line in f.readlines()]

# カメラ初期化
picam2 = Picamera2()
picam2.preview_configuration.main.size = (640, 480)
picam2.preview_configuration.main.format = "RGB888"
picam2.preview_configuration.align()
picam2.configure("preview")
picam2.start()

def preprocess_image(image):
    resized = cv2.resize(image, (320, 320))
    resized = resized[:, :, [2, 1, 0]]  # BGR→RGB
    return np.expand_dims(resized, axis=0).astype(np.uint8)

def postprocess_results(boxes, scores, classes, count, image_shape, labels):
    detections = []
    for i in range(count):
        if scores[i] > 0.4:
            ymin, xmin, ymax, xmax = boxes[i]
            left, right, top, bottom = (
                int(xmin * image_shape[1]),
                int(xmax * image_shape[1]),
                int(ymin * image_shape[0]),
                int(ymax * image_shape[0])
            )
            detections.append({
                'box': [left, top, right, bottom],
                'class_id': int(classes[i]),
                'score': float(scores[i]),
                'label': labels[int(classes[i])] if int(classes[i]) < len(labels) else f"id:{int(classes[i])}"
            })
    return detections

def detect_once():
    frame = picam2.capture_array()
    input_data = preprocess_image(frame)
    interpreter.set_tensor(input_details[0]['index'], input_data)
    interpreter.invoke()
    boxes = interpreter.get_tensor(output_details[0]['index'])[0]
    classes = interpreter.get_tensor(output_details[1]['index'])[0]
    scores = interpreter.get_tensor(output_details[2]['index'])[0]
    count = int(interpreter.get_tensor(output_details[3]['index'])[0])
    detections = postprocess_results(boxes, scores, classes, count, frame.shape, labels)
    return detections

@app.route('/detect', methods=['GET'])
def detect_route():
    detections = detect_once()
    return jsonify(detections)

if __name__ == '__main__':
	# when you limit access only from the local machine, use a loopback address instead of 0.0.0.0
    #app.run(host='0.0.0.0', port=5000)
    serve(app, host='0.0.0.0', port=5000)

時間測定用のスクリプトは、

#
# test script for image detect function(tflite server)
#

import requests
import time

if __name__ == '__main__':
    time1 = time.time()
    result = requests.get('http://rasp5.local:5000/detect').json()
    time2 = time.time()
    print('func1: {:.3f} sec'.format(time2 - time1))
    # 'label'が'person'を含んでいるかを判定
    person_detected = any(item['label'] == 'person' for item in result)
    
    if person_detected:
        print("Person detected!")
    else:
        print("No person detected.")

測定結果、

    1. 最初のアクセスはmDNSが動作して遅くなる、以降はキャッシュが有効になるのでアドレス引きの時間は見えなくなる
    2. マルチコアでの改善率は2.5倍ぐらい高速化だから、こんなもんかのレベルで良くてもせいぜい3倍ぐらいかと思っていたので
    3. サーバーにWSGI使っても見かけレスポンス時間が早くなったとは感じない

70m secぐらいでレスが返るということは、efficientdet_lite0.tfliteモデル使えばラズパイ5で十数フレームぐらいの動作は可能ということが言える

 

admin

ラズパイ5のchatbot機能を統合

一月末からほぼ100日プロジェクトでしたが、ようやく全ての機能を統合できました

具体的には、画像認識で人の認識を行う、音声認識と音声出力でLLMと会話する、カメラは眼球に仕込んで瞼の動きは状態(起動表示、レスポンス内容の喜怒哀楽)を返す手段とする、というところですが、LLMをラズパイ5のローカル動作は割と早めに断念してそこはクラウドを使用(API KEYはローカルのDifyへのアクセス用でGeminiのKEYは明には見えないようになっています)

現状のコード等は以下のリンクに、

https://github.com/chateight/rasp5_bot

まだいくつかbuggyなところは残ってますが、追加機能の現状のアイディアとしては顔画像から表情を読み取ることですね

現状の機能としては、

① アプリが立ち上がったら音声メッセージを出す

② カメラが人(person)検出したらこれも音声メッセージを出して③に移行する

③ Dify経由でLLMとチャットモードで問い合わせのやり取りを行う

 

admin

tfliteをFlaskで動かしてクライアントからのリクエストで起動させる

tfliteは軽量とはいえ常時稼働させるのは、ラズパイ5のリソース的にも熱的には厳しいだろうから、ワンショットで動かすようにする

手段としてはFlaskでサーバー建てて、クライアントから認識リクエストが来たらカメラから画像取り込み、画像認識してその結果を返すという流れになります

<現状のサーバーで動くコード>

今は全てのリクエスト受けるアドレスになっている(MacBookからリクエスト出すから)けど、最終的にはローカルアドレスからだけ受けるようにします

# work on Flask server
# server will be activated only when client request is occured
#
#
from flask import Flask, jsonify
import cv2
import numpy as np
import tflite_runtime.interpreter as tflite
from picamera2 import Picamera2
from waitress import serve

app = Flask(__name__)

# モデル・ラベル初期化
interpreter = tflite.Interpreter(model_path="efficientdet_lite0.tflite")
interpreter.allocate_tensors()
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
with open("coco_labels.txt", "r") as f:
    labels = [line.strip() for line in f.readlines()]

# カメラ初期化
picam2 = Picamera2()
picam2.preview_configuration.main.size = (640, 480)
picam2.preview_configuration.main.format = "RGB888"
picam2.preview_configuration.align()
picam2.configure("preview")
picam2.start()

def preprocess_image(image):
    resized = cv2.resize(image, (320, 320))
    resized = resized[:, :, [2, 1, 0]]  # BGR→RGB
    return np.expand_dims(resized, axis=0).astype(np.uint8)

def postprocess_results(boxes, scores, classes, count, image_shape, labels):
    detections = []
    for i in range(count):
        if scores[i] > 0.4:
            ymin, xmin, ymax, xmax = boxes[i]
            left, right, top, bottom = (
                int(xmin * image_shape[1]),
                int(xmax * image_shape[1]),
                int(ymin * image_shape[0]),
                int(ymax * image_shape[0])
            )
            detections.append({
                'box': [left, top, right, bottom],
                'class_id': int(classes[i]),
                'score': float(scores[i]),
                'label': labels[int(classes[i])] if int(classes[i]) < len(labels) else f"id:{int(classes[i])}"
            })
    return detections

def detect_once():
    frame = picam2.capture_array()
    input_data = preprocess_image(frame)
    interpreter.set_tensor(input_details[0]['index'], input_data)
    interpreter.invoke()
    boxes = interpreter.get_tensor(output_details[0]['index'])[0]
    classes = interpreter.get_tensor(output_details[1]['index'])[0]
    scores = interpreter.get_tensor(output_details[2]['index'])[0]
    count = int(interpreter.get_tensor(output_details[3]['index'])[0])
    detections = postprocess_results(boxes, scores, classes, count, frame.shape, labels)
    return detections

@app.route('/detect', methods=['GET'])
def detect_route():
    detections = detect_once()
    return jsonify(detections)

if __name__ == '__main__':
	# when you limit access only from the local machine, use a loopback address instead of 0.0.0.0
    app.run(host='0.0.0.0', port=5000)

立ち上げのメッセージ

$ python tflite_flask.py
INFO: Created TensorFlow Lite XNNPACK delegate for CPU.
[0:43:36.818382413] [87808]  INFO Camera camera_manager.cpp:327 libcamera v0.4.0+53-29156679
[0:43:36.825774869] [87830]  INFO RPI pisp.cpp:720 libpisp version v1.1.0 e7974a156008 27-01-2025 (21:50:51)
[0:43:36.914007444] [87830]  INFO RPI pisp.cpp:1179 Registered camera /base/axi/pcie@1000120000/rp1/i2c@88000/imx708@1a to CFE device /dev/media0 and ISP device /dev/media1 using PiSP variant BCM2712_C0
[0:43:36.916831533] [87808]  INFO Camera camera.cpp:1202 configuring streams: (0) 640x480-RGB888 (1) 1536x864-BGGR_PISP_COMP1
[0:43:36.916945996] [87830]  INFO RPI pisp.cpp:1484 Sensor: /base/axi/pcie@1000120000/rp1/i2c@88000/imx708@1a - Selected sensor format: 1536x864-SBGGR10_1X10 - Selected CFE format: 1536x864-PC1B
 * Serving Flask app 'tflite_flask'
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:5000
 * Running on http://192.168.1.19:5000
Press CTRL+C to quit
192.168.1.18 - - [26/Apr/2025 19:16:35] "GET /detect HTTP/1.1" 200 -

本番運用はWSGI server使ってと言われてますが、ずっとそのままの可能性もあり、最後の行はリクエストに対する応答ですね

<クライアントからのリクエストコード>

import requests

if __name__ == '__main__':
    result = requests.get('http://rasp5.local:5000/detect').json()
    print(result)

クライアントはMacBook Proからリクエストすると、

<人がカメラから見える位置>
% python tfreq.py
[{'box': [3, -1, 636, 481], 'class_id': 0, 'label': 'person', 'score': 0.9375}]


<体をカメラからずらすと>
% python tfreq.py
[{'box': [143, 23, 607, 472], 'class_id': 81, 'label': 'id:81', 'score': 0.4765625}]

こんな感じで帰ってきます、レスポンス時間は体感1秒だから、まあこんなものかというレベルでアプリから考えたらこれで十分だし、ほとんどtfliteに負荷かけないから熱問題も起こらないでしょう

topでCPU負荷見てると、認識処理が走ってる時に瞬間的に%CPUが10%に近づくけどその程度で収まってるということ

 

admin

Pythonで感情分析

chatbotの瞼を感情(悲しい、嬉しい)で動きを作るための仕掛け、

Pythonで形態素解析と、事象を参照してスコア化できるライブラリがあるのでそれを使う

辞書(wago.121808.pn)はLLMが教えてくれた東北大のサイトからダウンロードしてます

<Python script : LLM会話での現在形態>

必要なライブラリは、spacy/jaconv/ja-ginzaをpip install、spacyは多言語の形態素解析のできるライブラリでラズパイ5でもそこそこの速度で動作可能、辞書のサイズはおよそ五千語です

import spacy
import jaconv

# --- 辞書ロード関数 ---
def normalize_to_hiragana(text):
    """表記をひらがなに正規化"""
    return jaconv.kata2hira(jaconv.z2h(text, kana=True, digit=True, ascii=True))

def load_combined_sentiment_dict(paths):
    sentiment = {}
    for path in paths:
        with open(path, encoding='UTF-8') as f:
            for line in f:
                cols = line.strip().split('\t')
                if len(cols) < 2: continue label = cols[0] # "ポジ"や"ネガ"を抽出 word = cols[1] # 単語を抽出 # スコアをラベルに応じて決定 if "ネガ" in label: score = -1.0 elif "ポジ" in label: score = 1.0 else: score = 0.0 # 中立の場合 sentiment[word] = score # 単語とスコアを登録 return sentiment # --- 感情分析処理 --- def analyze_sentiment(text, sentiment_dict, nlp): doc = nlp(text) positive_count = 0 negative_count = 0 print("=== マッチ単語とスコア ===") for token in doc: lemma = token.lemma_ # 語幹 print(f"処理中: {token.text}({lemma})") # 辞書に単語がマッチした場合 if lemma in sentiment_dict: score = sentiment_dict[lemma] print(f"マッチ: {lemma} → {score:+.1f}") if score > 0:
                positive_count += 1
            elif score < 0: negative_count += 1 total_words = len([token for token in doc if not token.is_punct]) # 句読点を除く単語数 sentiment_score = positive_count - negative_count # ポジティブ単語数 - ネガティブ単語数 normalized_score = sentiment_score / total_words * 10 if total_words > 0 else 0  # スコアの正規化

    result = "neutral"
    if normalized_score > 0:
        result = "positive"
    elif normalized_score < 0:
        result = "negative"

    print(f"\n感情スコア: {normalized_score:+.1f}")
    print(f"判定結果: {result}")
    print(f"ポジティブ単語数: {positive_count}")
    print(f"ネガティブ単語数: {negative_count}")

    return {
        "score": normalized_score,
        "positive_count": positive_count,
        "negative_count": negative_count,
        "result": result
    }

# --- メイン関数 ---
def main():
    text = """シンドラーとはね、第二次世界大戦中に、ユダヤ人をナチスから守った人だよ。彼は、ドイツのオースヴィッツ強制収容所から、たくさんのユダヤ人を助けたんだ。すごい人だよね!"""

    print("GiNZAロード中...")
    nlp = spacy.load("ja_ginza")

    print("辞書読み込み中...")
    sentiment_dict = load_combined_sentiment_dict([
        "wago.121808.pn"
    ])

    print(f"\n分析対象: {text}\n")
    analyze_sentiment(text, sentiment_dict, nlp)

if __name__ == "__main__":
    main()

<実行結果>

クリスマスのことを語っている文章を入力にすると、

% python emotion.py
GiNZAロード中...
辞書読み込み中...

分析対象: うん、そうだよ!プレゼントとか、クリスマスツリーとか、すごく楽しいよね!

=== マッチ単語とスコア ===
処理中: うん(うん)
処理中: 、(、)
処理中: そう(そう)
処理中: だ(だ)
処理中: よ(よ)
処理中: !(!)
処理中: プレゼント(プレゼント)
処理中: と(と)
処理中: か(か)
処理中: 、(、)
処理中: クリスマスツリー(クリスマスツリー)
処理中: と(と)
処理中: か(か)
処理中: 、(、)
処理中: すごく(すごい)
処理中: 楽しい(楽しい)
マッチ: 楽しい → +1.0
処理中: よ(よ)
処理中: ね(ね)
処理中: !(!)

感情スコア: +0.7
判定結果: positive
ポジティブ単語数: 1
ネガティブ単語数: 0

感情スコアは+1 ~ -1の間に治るようにしたいから乗算してます、上限と下限もこの値で区切るようにした方がいいだろうね、

 

admin

ラズパイ5のWi-Fi不具合はSSD原因らしい

以前からラズパイ5のWi-Fiが不調、他のSSIDに切り替えられない、でしたがついにデフォルトのSSIDでも動作不安定になってリブートしても程なく通信が切れてVNCが使えない、あるいはターミナルが開くのにやたら時間かかるとかで使えないので数週間前にバックアップしてたSDカードでなおかつ有線LANにつないで調べてみると、

経緯は、

https://isehara-3lv.sakura.ne.jp/blog/2025/04/16/ラズパイ5のwi-fi不具合/

① 実はバックアップのSDカードから立ち上げるとSSIDの切り替えはできた

② ラズパイ5でSSDにSDカードからImagerでデータ転送してコピーあるいはラズパイ5でサラのイメージ書き込みしようとしても途中で失敗、MacでOSイメージ書き込んで(これは正常に終了)ラズパイ5に持ってきても立ち上がらない

ということで、極めて寿命が短かった(一月ちょっとか)けれどもSSDの不良というのが現段階の結論です(Macでは書き込めるからHATに原因という可能性もあるけれども)

P.S. とは言ってもMacBookではまともに扱えているから、ラズパイ5での使い方に問題と考えるべきかな、俗にいう相性と言われるやつだけど、Silicon Powerはイマイチらしいから

まあ筒の中に収納されて、温度もかなり上がるからSSDは今回は諦めかな

ラズパイ5のブラウザでSpeedTest実行するとラズパイ5のacモードの転送でも200Mbps以下で、有線LAN はn規格のアダプタ経由でも200Mbps以上は出てるから(有線LANはgiga Lanだから)、ラズパイ5のWi-Fiの性能は低すぎ故にWi-Fiは非常用と考えた方がよさそうだ

P.S. 2025/4/20

この部分の記述は誤り、つまりWi-Fi中継機が100Mbpsの有線LANなのに、なぜそれ以上の速度が出るかというと実はWi-Fiを経由してしまってるから、つまりLinuxの場合にはそういうルーティングも起こるらしい

 

admin

 

ラズパイ5のWi-Fi不具合

元々の問題(今現在解決できていないので途中経過)

ラズパイ5でSSIDが追加できない、より正確に言えば以前はpreconfiguredのWi-Fi SSIDがなければ他のSSIDに接続できていたのが接続できなくなったのでその調査として、ラズパイ0でやってみたことと、ラズパイ5の振る舞い

<wpa_supplicant.confを作成必要な問題:これはラズパイ0での実行時>

昨年終わり頃にOSがbookworm版にアップされているので、ラズパイ0で最新のOSでWi-Fiの初期設定を追加(rpi imagerでラズパイ0用のヘッドレスのイメージ書き込んでやってみた)、しかし最初の壁は起動したと思える時間待ってもWi-Fi内にラズパイ0が見つからない

作成したSDカードのルートに以下のwpa_supplicant.confファイルを作成してやると見えるようになった

country=JP
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1

network={
    ssid="your SSID"
    psk="your PW"
}

<nmcli(bookwormからはこのコマンドで制御になってる)での必要コマンド>

rescanやらないとキャッシュがクリアされないから、今接続中のSSIDしか見えない
$ sudo nmcli device wifi rescan

次にlist出力させる
$ sudo nmcli device wifi list

一回の入力で済ますなら、
$ sudo nmcli device wifi rescan && sleep 2 && nmcli device wifi list

SSIDの追加設定
$ sudo nmcli device wifi connect aterm-eb69c6-g password “your PW” hidden yes
Device ‘wlan0’ successfully activated with ‘unique UUIDの値’

設定済み接続リスト(-gに切り替えている)
$ nmcli connection show

接続先を切り替える時は例えば、
$ sudo nmcli connection up preconfigured

ここまでは正常動作、自宅以外のSSIDもちゃんと見えている普通の姿

————————–

<ラズパイ5のWi-Fi問題>

1. 自宅内と周辺のセンスできるはずのSSIDがほとんど見えない、感度が低下している状態と言えなくもないが、その割には受信できてるSSIDの電波強度はかなり強い

2. かつpreconfigured以外は見え方が不安定で、見えたり見えなかったり

3. 見えてる状態でSSID切り替えしようとすると、ハング状態になるからVNC接続では電源切るしかない

4. 当初は複数のSSID登録できて、自動切り替えもできてたから、その後のシステムの変更で発生しているようにも見える、ケース(筒)の影響の可能性は?ケースに収めた後に問題発生しているように思うから

そもそもアンテナが基板(CPU側)に構成されていて、なおかつクーリングファンの影響(遮蔽)も受けるだろうから、感度がそれほど期待できないということがある

ただしwavemonで見た限りは問題ないね

原因がどうしても判明しないときにはUSB Wi-Fiドングル(多分その方が無線的な特性はまともだろうと思う)、あるいはWi-Fiアダプタ使って有線LANでつなげるという選択肢もあるかな

 

admin