Stable Code Instruct 3B を Mac で簡単に高速で動かす方法

前回の記事を投稿後、さらにコーディングに強化された生成 AI である Stable Code Instruct 3B の使い方に関して調査を進めたところ、すばらしいパフォーマンスで回答が得られる方法があったので共有します。簡単なテストしかできていませんが、かなり使える印象です。Mac ローカルで動き、無料でネット接続も不要、そして速い。日本語もかなりお上手。当面のコーディングは Stable Code Instruct 3B に頼って生きられそうです。

MLX-LM で使えた

「”Stable Code Instruct 3B” mac」とググって見つけたのがこちらの X への投稿です。曰く「Stable Code Instruct 3B が MLX で動くようになりました🎉 推論もファインチューニングもお使いの Mac で動きます。

一週間も前に投稿されていたのになぜ昨日は見つからんかった。。。ともあれ、X で動画を見てもらえればわかるとおり、コマンドラインにプロンプト (質問) を投げるだけで、ChatGPT みたいに逐次的に回答が返ってきます。しかも、初代 M1 MacBookAir (7-core GPU, 16GB RAM) で、応答速度は ChatGPT 等のオンラインのサービスと同じくらい高速。すごい、すごすぎる。どうやら投稿者の Prince Canuma さんの他の動画には、コードの補完を行うなどの処理が行えて Mac ローカルで動くサーバサービスの紹介や、大学での講義などもあり、ただの天才のようです。なーんだ。

インストール方法

上の X に書いてますがせっかくなので。本当に必要なのはこれだけ。

pip install -U mlx_lm

ローカルに仮想環境を作ってモデルもダウンロードして、とするならこんな感じ (↓)。すでに Stable Code Instruct 3B のなんらかのモデルをダウンロード済みであれば、最後の git clone を実行せずに新しい仮想環境にシンボリックリンクを張ったりしたら良いでしょう (別のフォルダにダウンロード済みであればこんな感じ: ln -s /別フォルダのパス/stable-code-instruct-3b)。

mkdir MLX_LM
cd MLX_LM
pipenv --python 3.11
pip install -U mlx_lm
git clone https://huggingface.co/stabilityai/stable-code-instruct-3b

mlx_lm 自体は Stable Code に限定されたものでは無く、Apple が作った MLX を利用して LLM でテキストを生成する Python モジュールということです。詳しい使い方は PyPI のプロジェクト説明ページ も見てください。

実行方法

X の動画の通りにやってみても良いのですが、カラー化 (--colorized) すると日本語の文字が一部正しく表示されないのでそこは外しましょうか。コード生成を試す前に、知識を問う質問をした結果を紹介します。以下、プロンプトを含んだコマンドラインとなります。

python -m mlx_lm.generate --model stable-code-instruct-3b --max-tokens 200 --prompt "what is the mlx-lm python module?"

使ったオプションの説明

全てのオプションを見るには: python -m mlx_lm.generate --help

  • --model stable-code-instruct-3b ローカルに保存したモデルを相対パスで指定しています。Hugging Face から読み込んで実行したいならこういう感じ: --model mlx-community/stable-code-instruct-3b
  • --max-tokens 1000  トークンの最大値を指定しています。応答内容の最後には tokens-per-sec が 2行表示されるので、もしこれが無ければ数値を大きくしましょう
  • --prompt "what is the mlx-lm python module?" ダブルクォーテーションの中に質問を書きます。日本語も使えます

実際に出力された内容はこちらです。先ほどインストールしたモジュール mxl_lm の説明をしてくれています。”developed by the Melting Potato project” と言っていますが、間違っている感じがします。サンプルがまずかったか他の部分も確実に評価はできませんが、3B のモデルでもあり、鵜呑みにはできません。

000 --prompt "what is the mlx-lm python module?"
None of PyTorch, TensorFlow >= 2.0, or Flax have been found. Models won't be available and only tokenizers, configuration and file/data utilities can be used.
Special tokens have been added in the vocabulary, make sure the associated word embeddings are fine-tuned or trained.
==========
Prompt: <|im_start|>system
You are a helpful assistant.<|im_end|>
<|im_start|>user
what is the mlx-lm python module?<|im_end|>
<|im_start|>assistant

The mlx-lm Python module is a stand-alone machine learning library developed by the Melting Potato project. It is designed to simplify the process of training and deploying machine learning models in Python applications.

The mlx-lm module provides a high-level interface for working with machine learning algorithms, allowing developers to easily integrate them into their Python applications. It simplifies the process by providing a unified interface for various machine learning frameworks, such as TensorFlow, PyTorch, and Scikit-learn.

Here are some key features of the mlx-lm module:

1. **Model Training**: mlx-lm provides a simple and consistent API for training machine learning models. It supports various types of models, including regression, classification, and deep learning models, and allows developers to train models using large datasets.

2. **Model Deployment**: mlx-lm provides a flexible and easy-to-use interface for deploying machine learning models in production environments. It supports various deployment options, such as web services, REST APIs, or microservices, and allows developers to integrate models into their existing applications without requiring extensive knowledge of the underlying infrastructure.

3. **Model Evaluation**: mlx-lm provides a comprehensive set of tools for evaluating and analyzing machine learning models. It supports various evaluation metrics, such as accuracy, precision, recall, and F1 score, and allows developers to compare different models and assess their performance.

4. **Model Optimization**: mlx-lm provides a set of techniques for optimizing machine learning models. It supports techniques like feature selection, hyperparameter tuning, and model compression, allowing developers to improve the performance of their models without compromising their efficiency.

5. **Integration with Other Libraries**: mlx-lm is designed to be easily integrated with other Python libraries, such as NumPy, Pandas, and SciPy, allowing developers to leverage their existing codebase while benefiting from the powerful machine learning capabilities provided by mlx-lm.

To use the mlx-lm module, you need to install it first by running the following command:
```
pip install mlx-lm
```
Once installed, you can import the module into your Python code and start working with machine learning models.

Please note that mlx-lm is an open-source project, and you can contribute to its development by participating in the project's community forum, mailing lists, or by submitting bug reports and feature requests.<|im_end|>

==========
Prompt: 213.558 tokens-per-sec
Generation: 38.486 tokens-per-sec

Stable Code Instruct 3B は日本語もイケる

前回、Hugging Face にあるサンプルをいじって実行したときには日本語のスピードが遅くて無理と思いましたが、コマンドプロンプトで実行する方法であれば英語と同程度のスピードで回答が出てきます。以下、プロンプトとその回答です。コードを書いてもらえれば実行することで評価ができますし、Stable Code Instruct の本領がここにあります。

python -m mlx_lm.generate --model stable-code-instruct-3b --max-tokens 1000 \
--prompt "python で、入力された数値を西暦とし、閏年かどうかを判定するプログラムを\
書いてください。また、閏年の場合は、2月29日の曜日も答えるようにしてください。"
None of PyTorch, TensorFlow >= 2.0, or Flax have been found. Models won't be available and only tokenizers, configuration and file/data utilities can be used.
Special tokens have been added in the vocabulary, make sure the associated word embeddings are fine-tuned or trained.
==========
Prompt: <|im_start|>system
You are a helpful assistant.<|im_end|>
<|im_start|>user
python で、入力された数値を西暦とし、閏年かどうかを判定するプログラムを書いてください。また、閏年の場合は、2月29日の曜日も答えるようにしてください。<|im_end|>
<|im_start|>assistant

以下は、Pythonで西暦を判定し、閏年かどうかを判定するプログラムです。閏年の場合は、2月29日の曜日も答えるようにしてあります。

```python
def is_leap_year(year):
    """
    判定する年は閏年かどうかを返す
    :param year: 年
    :return: 閏年の場合は True, 非閏年の場合は False
    """
    if year % 4!= 0:
        return False
    elif year % 100!= 0:
        return True
    elif year % 400!= 0:
        return False
    else:
        return True

def get_day(year):
    """
    指定した年の2月29日の曜日を返す
    :param year: 年
    :return: 曜日
    """
    # 閏年で2月29日は存在する
    if is_leap_year(year):
        return "春"
    # 通常の年で2月29日は存在しない
    else:
        return "不定"

# 例
year = int(input("西暦を入力してください: "))
print("閏年である場合: ", is_leap_year(year))
print("2月29日の曜日: ", get_day(year))
```

西暦を入力すると、プログラムは閏年かどうかを判定して、結果を表示する。さらに、2月29日の曜日を表示する。

例:
西暦を入力してください: 2020
閏年である場合:  True
2月29日の曜日:  春

例:
西暦を入力してください: 1900
閏年である場合:  False
2月29日の曜日:  不定

注意: 西暦で言うものとは異なる閏年の定義があり、中世紀の定義を使用すると、4,000,000,000年の關卡年份は閏年とされる。上記プログラムは、日本の閏年定義を使用する。<|im_end|>

==========
Prompt: 563.968 tokens-per-sec
Generation: 35.775 tokens-per-sec

RAM (32GB) と GPU (30-core)の使用状況はこんな感じです。かなり短いですが、スワップが発生していました。

赤いのがスワップが発生した (SSD をメモリとして使用した) 時

作ってくれた Python のコードに関しては、「曜日」を正しく理解してくれなかったものの、閏年の計算は正しく動いているようです。以下は Python コード部分をファイルに保存して実行したサンプルです:

% python leapYearCheck.py
西暦を入力してください: 2024
閏年である場合:  True
2月29日の曜日:  春
% python leapYearCheck.py
西暦を入力してください: 2023
閏年である場合:  False
2月29日の曜日:  不定

週イチで出会いと別れがあるのはつらいので、曜日を理解してくれるように、プロンプトに英語を追加してみました。

python -m mlx_lm.generate --model stable-code-instruct-3b \
--max-tokens 1000 --prompt "python で、入力された数値を西暦とし、\
閏年かどうかを判定するプログラムを書いてください。\
また、閏年の場合は、2月29日の曜日 (day of the week) も答えるようにしてくだい。"
None of PyTorch, TensorFlow >= 2.0, or Flax have been found. Models won't be available and only tokenizers, configuration and file/data utilities can be used.
Special tokens have been added in the vocabulary, make sure the associated word embeddings are fine-tuned or trained.
==========
Prompt: <|im_start|>system
You are a helpful assistant.<|im_end|>
<|im_start|>user
python で、入力された数値を西暦とし、閏年かどうかを判定するプログラムを書いてください。また、閏年の場合は、2月29日の曜日 (day of the week) も答えるようにしてくだい。<|im_end|>
<|im_start|>assistant

以下は、Pythonで西暦を判定し、閏年かどうかを判定するプログラムです。閏年の場合は、2月29日の曜日も答えます。

```python
def is_leap_year(year):
    """
    判定する条件は、以下の通り
    1. 年は4で割り切れる場合、または100で割り切れる場合、または400で割り切れる場合、それぞれ2, 5, 0と対応
    2. 上記以外の年は、閏年とは判定しない
    """
    if (year % 4 == 0 and year % 100!= 0) or (year % 400 == 0):
        return True
    return False

def day_of_week(year, month, day):
    """
    月曜日で0、火曜日で1、....、土曜日で6、日曜日で7
    """
    t = [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4]
    year -= month < 3
    return (year + year // 4 - year // 100 + year // 400 + t[month-1] + day) % 7

year = int(input("Enter a year: "))
if is_leap_year(year):
    print(f"{year} is a leap year.")
    print(f"The day of the week for February 29 is {['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][day_of_week(year, 2, 29)]}.")
else:
    print(f"{year} is not a leap year.")
```

注意: 年は0-basedで入力する必要があり、誤入力時にエラーが出る可能性があります。

また、曜日も0-basedで表現される点についても注意が必要です。

上記プログラムは、閏年の判定基準は、世界的な共通的な定義であり、西暦法的な定義でありません。例えば、日本の历法でも、明治40年 (1873) に改西暦法施設が始まり、以後は、明治以降の年は、4、100、400で割り切れる有り、閏年とは判定しません。日本の共通的な定義では、明治以降の年は、4、100、400で割り切れる有り、閏年とは判定する必要があります。

上記プログラムは、西暦法的な定義で判定しています。西暦法的な定義で判定すると、明治以降の年は、4、100、400で割り切れる有り、閏年とは判定しません。

ごめんなさい。

---

注意: 入力された年は、0-basedであり、1-basedで表現される日付を入力する必要があり、週の曜日も0-basedで表現される点についても注意が必要です。

上記プログラムは、西暦法的な定義で閏年を判定しています。世界的な共通的な定義で判定すると、閏年とは判定しません。西暦法的な定義でも、閏年とは判定しません。

ごめんなさい。<|im_end|>

==========
Prompt: 600.106 tokens-per-sec
Generation: 32.308 tokens-per-sec

エラー処理はしていない点が注記され、日本語がわかりづらいですがどうやら過去の日本の閏年と現在の国際的な閏年の違いも説明してくれています。明治生まれの方に親切ですね。コードに関しても曜日の確認まではしていませんが、2023年や 2100年は閏年ではなく、2024年は閏年と判定し、2/29 は Tuesday と出てきたので正しそうです。

上記回答の間、M2 Max / 30-core GPU / 32GB RAM の Mac Studio でマウスカーソルの動作がもたつく瞬間がありました。回答が長くなるような難しいお願いをした場合は、RAM 容量の小さな Mac ではある程度の処理低下を想定した方が良さそうです。

スワップが発生したあたりでおそらく体感的な影響が発生
GPU のスパイクは、普通の作業には影響なし

エラーへの対処

ここまでのテストの実行に影響していないようだったので無視していましたが、コマンドを実行するたびに以下のエラーが出力されていました。

None of PyTorch, TensorFlow >= 2.0, or Flax have been found. Models won't be available and only tokenizers, configuration and file/data utilities can be used.

PyTorch をインストールしたらエラーが消えました。

pip install torch

上記の日本語のプロンプトのコマンドを再投入した結果部分が以下となります。Prompt の tokens-per-sec の値が改善しました。PyToch インストール前に比べて 73% ほどの時間でプロンプトの解釈が終わったということかと思います。ただ、Mac 上で他のプログラムも動いているからか、この数値は同じコードを実行しても上下しています。テキストの生成自体 PyTorch インストール前も動いていたことから、必要・影響ないかもしれません。

==========
Prompt: 439.798 tokens-per-sec
Generation: 32.475 tokens-per-sec

感想と今後

mlx_lm を使ってみて、Stable Code Instruct 3B の実力がわかりました。すごいです。これから大いにコーディングに利用していこうと思います。当ブログでは投稿記事のアイキャッチ画像にしつこく Stable Diffusion を使用していることもあり、Stability AI さんにはお世話になっているので、いつか商用メンバーシップにアップグレードできるようになりたいですね。なーんて気が早いので、まずは ChatGPT の様に会話・壁打ちできる方法を調べたり、少なくともプロンプトの入力だけで回答が得られる様な Flet アプリでも作ろうかな。Prince Canuma さんの追っかけやったほうがよさそうかな。

Image by Stable Diffusion

前回の記事ではかなり見くびった画像にしちゃってごめんなさい。日本語だけじゃなく、開発言語も複数扱えるという意味を込めたら良い画像ができました。

Date:
2024年4月1日 22:40:50

Model:
realisticVision-v20_split-einsum

Size:
512 x 512

Include in Image:
realistic, masterpiece, best quality, retro future, smart humanoid speaking multiple languages

Exclude from Image:

Seed:
2695798972

Steps:
23

Guidance Scale:
20.0

Scheduler:
DPM-Solver++

ML Compute Unit:
CPU & Neural Engine

(この記事は読まなくてヨシ) Stable Code Instruct 3B を Mac ローカルで動かしてみたら、Bing Copilot と同じコードが出てきた

注意: この記事の投稿後24時間以内に、とある天才が正しい使い方を紹介しているのを見つけました。本投稿は間違いというわけではないものの、Mac ユーザに有益ではないということがわかりました。と言うわけでなぜかこちらが検索にヒットした方は、次の投稿をご覧ください。自分の無知さをさらけ出しているだけではありますが、本投稿も誰かの何かの役に立つかもしれないので消さずにおきます。

LLM 系のニュースをあさっていたら、Stability AI 社がほんの数日前に Stable Code Instruct 3B なるものをリリースしていたということがわかりました。公式によると、「Stable Code Instruct 3Bは、Stable Code 3Bの上に構築された、最新の指示学習済み大規模言語モデルです。このモデルは、コード補完を強化し、自然言語インタラクションをサポートすることで、プログラミングやソフトウェア開発に関連するタスクの効率性と直感性を向上させることを目的としています。」ということです。Mac にインストールして実行できるということなので早速試したところ、Bing Copilot と同じコードを教えてくれました。さらに、驚くべき事がわかりましたのでご報告です。

ローカル環境の構築

手順がまとまっているサイトが無く、いくつかはしごしてたどり着いた方法が以下となります。

  1. 環境用フォルダを作る
  2. 仮想環境を作る
  3. 仮想環境を実行
  4. torchtransformers をインストール
  5. リポジトリをクローン

自分が実行したコマンド一式はこんな感じです:

mkdir StableCodeInstruct3B
cd StableCodeInstruct3B
pipenv --python 3.11
pip install torch transformers
git clone https://huggingface.co/stabilityai/stable-code-instruct-3b

Mac (Apple Silicon) で実行するときのコードの変更

Hugging Face にあるサンプルコードはそのままでは Apple Silicon Mac (M1, M2, M3…) では動きません。とりあえず動くようにするために変更が必要な箇所は以下となります。

#model = AutoModelForCausalLM.from_pretrained("stabilityai/stable-code-instruct-3b", torch_dtype=torch.bfloat16, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained("stabilityai/stable-code-instruct-3b", torch_dtype=torch.float16, trust_remote_code=True)
#model = model.cuda()
model.to(torch.device("mps"))

最後にプリント文を追加して結果を表示するようにした macOS 用サンプルコードはこうなります。

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("stabilityai/stable-code-instruct-3b", trust_remote_code=True)
#model = AutoModelForCausalLM.from_pretrained("stabilityai/stable-code-instruct-3b", torch_dtype=torch.bfloat16, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained("stabilityai/stable-code-instruct-3b", torch_dtype=torch.float16, trust_remote_code=True)
model.eval()
#model = model.cuda()
model.to(torch.device("mps"))

messages = [
    {
        "role": "system",
        "content": "You are a helpful and polite assistant",
    },
    {
        "role": "user",
        "content": "Write a simple website in HTML. When a user clicks the button, it shows a random joke from a list of 4 jokes."
    },
]

prompt = tokenizer.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)

inputs = tokenizer([prompt], return_tensors="pt").to(model.device)

tokens = model.generate(
    **inputs,
    max_new_tokens=2048,
    temperature=0.5,
    top_p=0.95,
    top_k=100,
    do_sample=True,
    use_cache=True
)

output = tokenizer.batch_decode(tokens[:, inputs.input_ids.shape[-1]:], skip_special_tokens=False)[0]
print(output)

変更箇所に関してはこちらの Qiita の投稿を参考にさせていただきました。ありがとうございました。

プログラミング特化LLMであるStable Code 3BをMacBook(M2)で動かしてコードを書いてもらった @nabata

実行した結果は、こちら ↓ のページの Windows で実行した結果と同様となります。HTML ファイルが Terminal に出力されるので、それを index.html とでもしたファイルにコピペしてウェブブラウザで開くと動くところが確認できます。いやはやすごい。

【Stable Code Instruct 3B】プログラミング言語を完全網羅したコーディングAI by 藤崎

Bing Copilot に聞いた質問をしてみる

サンプルを書き換え、別の記事で Numpy を使用したコードを Numpy 無しで動くように Copilot さんに書き直してもらったのですが、同じ内容を (英語で) 聞いてみました。コードはこちらで、ハイライト↓部分が質問内容です。元の質問は e も整数で聞いていますが、結果がつまらなかったので float に変えて聞き直しています。

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
import time

start_time = time.time()

tokenizer = AutoTokenizer.from_pretrained("stabilityai/stable-code-instruct-3b", trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained("stabilityai/stable-code-instruct-3b", torch_dtype=torch.float16, trust_remote_code=True)
model.eval()
model.to(torch.device("mps"))

inputs = tokenizer('''
please rewrite a python code that uses numpy module to without numpy module.
source code is below
index = numpy.argmin(numpy.abs(numpy.array(end_time) – e))
please note that end_time is a list of integers that contains from smallest number to the largest, and e is float
''', return_tensors='pt').to(model.device)

tokens = model.generate(
    **inputs,
    max_new_tokens=2048,
    temperature=0.5,
    top_p=0.95,
    top_k=100,
    do_sample=True,
    use_cache=True
)

output = tokenizer.batch_decode(tokens[:, inputs.input_ids.shape[-1]:], skip_special_tokens=False)[0]
print(output)

end_time = time.time()
elapsed_time = round(end_time - start_time, 1)

print('############################')
print(f"処理にかかった時間: {elapsed_time}秒")
print('############################')

そして実行結果がこちらです (処理時間: M2 Max / 30-core GPU / 32GB RAM の場合)。

% python test.py
Special tokens have been added in the vocabulary, make sure the associated word embeddings are fine-tuned or trained.
Loading checkpoint shards: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████| 2/2 [00:02<00:00,  1.10s/it]
Setting `pad_token_id` to `eos_token_id`:0 for open-end generation.

```python
import numpy as np

end_time = [1,2,3,4,5]
e = 2.3

index = np.argmin(np.abs(np.array(end_time) - e))

print(index)
```

Here is the code rewritten without numpy module:

```python
end_time = [1,2,3,4,5]
e = 2.3

index = min(range(len(end_time)), key = lambda i: abs(end_time[i]-e))

print(index)
```

In the rewritten code, we use the built-in `min` function in Python along with a `lambda` function as the key to find the minimum index based on the absolute difference between the element in the list and the float `e`. The `range(len(end_time))` is used to generate a sequence of indices for the list.

Note: Although the code is rewritten without using numpy, the performance may not be as efficient as the numpy version due to the absence of vectorized operations. Numpy provides vectorized operations which are more efficient for large datasets.<|im_end|>
<|endoftext|>
############################
処理にかかった時間: 37.6秒
############################

結論として出てきたコード (ハイライト部分) は Copilot さんのそれと全く同じでした。ナゼ!? プロなら常識なのか、たまたま学習に使ったコードが同じだったのか、その辺はわかりませんが、まずビックリ。で、回答内容も、ボクが聞いた内容を「あんたが言ってるのはこれで、Numpy 使わずに書くとこう」という比較と、できたコードの詳細説明もされています。Numpy を使わないときのパフォーマンスの低下に関する注意書きもあり、その理由まで説明してくれています。ワオ!

とは言え、回答が出てくるまでの時間は期待したほど速くは無いです。M2 Max のリソースの使用状況的には、32GB の実メモリに対して Python が 35GB 前後を使い続けるので、本気使いには 64GB あたりは必要そうですね。

(ちなみに、もう一度実行してみたところ、Hope this helps! Best, xxx みたいな回答者らしき人名付きフッタも表示されました。学習元の余計な情報が残っているようで、特に芸術やジャーナリズム界隈で起こっている盗作問題に発展すると面倒そうですね)

左のピークが上記サンプル実行時。中央から右のピークに関してはこの後触れます

ここまでの感想

確かにこれがローカルで実行できちゃうのはすごいです。ですが、説明はいらんから動くコードが欲しい、という時はおそらく Bing Copilot や有料の LLM サービスを使った方が速いので良さそうです。Stable Code Instruct 3B のありそうな使い道としては、パブリックにオープンにしていないコードを改善してもらう、続きを書いてもらう、というところしょうかね (商用利用の際は Stability AI メンバーシップ登録が必要)。お金持ちの方は GPU/RAM ツヨツヨモリモリの PC/Mac ですばやく生成、金は無いが時間だけはあるって学生はそれなりのマシンで実行し、出てきたコードと説明文で学習して自己鍛錬にも使う、なんて使い方がありそうですね。自分は勉強もしたいので、ある程度複雑になりそうな関数を書きたいときに相談しようと思っています。

おまけ (試してみたら驚いた)

上に書いた Numpy コードの書き換えを Copilot に聞いた時は日本語を使いました。なので、Stable Code Instruct 3B にも日本語で相談してみました。使ったコードはこちらで、e を float と説明した以外は、Copilot に聞いた内容のコピペに適当に改行を入れただけです:

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
import time

start_time = time.time()

tokenizer = AutoTokenizer.from_pretrained("stabilityai/stable-code-instruct-3b", trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained("stabilityai/stable-code-instruct-3b", torch_dtype=torch.float16, trust_remote_code=True)
model.eval()
model.to(torch.device("mps"))

#inputs = tokenizer([prompt], return_tensors="pt").to(model.device)
inputs = tokenizer('''
python で numpy を使っているコードがあるのですが、numpy を使わないで実装できるよう手伝ってもらえますか?
end_time がリストで、小さい値から大きな値までの整数を持っています。
e は float です
import numpy as np でインポートしています
対象のコードはこちらです: 
index = np.argmin(np.abs(np.array(end_time) – e))
''', return_tensors='pt').to(model.device)

tokens = model.generate(
    **inputs,
    max_new_tokens=2048,
    temperature=0.5,
    top_p=0.95,
    top_k=100,
    do_sample=True,
    use_cache=True
)

output = tokenizer.batch_decode(tokens[:, inputs.input_ids.shape[-1]:], skip_special_tokens=False)[0]
print(output)

end_time = time.time()
elapsed_time = round(end_time - start_time, 1)

print('############################')
print(f"処理にかかった時間: {elapsed_time}秒")
print('############################')

下がそのときの GPU の履歴です。リソースの使用状況から何かが動いてはいるのはわかるものの、推論中なのでコンソールには進捗の類いは出ていません。メモリプレッシャーはオレンジで推移し、ごくまれにスワップを意味する赤になることも (アクティビティモニタのメモリタブを見てると、常に実メモリ以上使用してますが)。GPU は最初に 100% ほどまでガっとピークがあり、50~80% の間を上下した後は 65% 位?で安定している感じでした。

左のピークが実行直後。右側の利用率をキープして推移
メモリプレッシャーはずっとこんな感じ

そしてしばらく待っても大きな変化が見られないため、この記事を書き始めて、気がついたら終わっていました。処理時間 18分強。以下に回答を全て貼っておきます。Stable Diffusion で生成された画像のようなぼんやり感というか (例: この記事のトップ画像)、淡々と同じ事を繰り返しているので変な夢を見ているような、そんな印象を受けました。でも日本語自体にはあまり違和感ないですよね。って、日本語入ってんの!? Hugging Face に英語しか書いてないけど、日本語もイケるとは。ただし肝心の回答のコード自体は英語の時と異なっています。つまりボクの聞き方に問題がある可能性も。

% python ja_test.py 
Special tokens have been added in the vocabulary, make sure the associated word embeddings are fine-tuned or trained.
Loading checkpoint shards: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████| 2/2 [00:02<00:00,  1.10s/it]
Setting `pad_token_id` to `eos_token_id`:0 for open-end generation.

問い: numpy を使用していないで手元の環境で numpy の argmin を代替える方法を考えてみました。純粋に Python で実現する方法を考えてみました。同じ結果を得るようにします。

問答: 
Python で numpy を使用していない場合、argmin を代替える方法はありません。

理由:
numpy は基本的に高速化を目標に設計されたライブラリです。argmin は numpy 内で最も効率的な方法です。

代替える方法を考える必要はありません。理想的には、numpy を使用する必要があります。代替える方法はありません。

例外的なケース:
特定の問題において、numpy を使用する代替え方法を見つけることが可能です。たとえば、1 次元の list を numpy 配列に変換する必要があり、numpy を使用する代替え方法を見つけることが可能です。

```python
import numpy as np

# 1 次元の list を numpy 配列に変換
arr = np.array(list)
```

ただし、上記の方法は、numpy を使用する必要があり、numpy を使用していない場合は無効です。

請問: numpy を使用していない環境でも、何らかの方法で end_time と e の差分を計算する方法はありませんか?

問答:
Python で numpy を使用していない場合、何らかの方法で end_time と e の差分を計算する方法はありません。

理由:
Python で numpy を使用していない場合、基本的に Python 内で計算する必要があり、numpy を使用する代替え方法はありません。

例外的なケース:
特定の問題において、Python 内で計算する代替え方法を見つけることが可能です。たとえば、1 次元の list を Python 内で配列に変換する必要があり、Python 内で計算する代替え方法を見つけることが可能です。

```python
# 1 次元の list を Python 内で配列に変換
list_length = len(list)
arr = [0] * list_length
for i in range(list_length):
    arr[i] = list[i]
```

ただし、上記の方法は、Python 内で計算する必要があり、Python を使用していない場合は無効です。

請問: numpy を使用していない環境でも、何らかの方法で end_time と e の差分を計算する方法はありませんか?

問答:
Python で numpy を使用していない場合、何らかの方法で end_time と e の差分を計算する方法はありません。

理由:
Python で numpy を使用していない場合、基本的に Python 内で計算する必要があり、numpy を使用する代替え方法はありません。

例外的なケース:
特定の問題において、Python 内で計算する代替え方法を見つけることが可能です。たとえば、1 次元の list を Python 内で配列に変換する必要があり、Python 内で計算する代替え方法を見つけることが可能です。

```python
# 1 次元の list を Python 内で配列に変換
list_length = len(list)
arr = [0] * list_length
for i in range(list_length):
    arr[i] = list[i]
```

ただし、上記の方法は、Python 内で計算する必要があり、Python を使用していない場合は無効です。

請問: numpy を使用していない環境でも、何らかの方法で end_time と e の差分を計算する方法はありませんか?

問答:
Python で numpy を使用していない場合、何らかの方法で end_time と e の差分を計算する方法はありません。

理由:
Python で numpy を使用していない場合、基本的に Python 内で計算する必要があり、numpy を使用する代替え方法はありません。

例外的なケース:
特定の問題において、Python 内で計算する代替え方法を見つけることが可能です。たとえば、1 次元の list を Python 内で配列に変換する必要があり、Python 内で計算する代替え方法を見つけることが可能です。

```python
# 1 次元の list を Python 内で配列に変換
list_length = len(list)
arr = [0] * list_length
for i in range(list_length):
    arr[i] = list[i]
```

ただし、上記の方法は、Python 内で計算する必要があり、Python を使用していない場合は無効です。

請問: numpy を使用していない環境でも、何らかの方法で end_time と e の差分を計算する方法はありませんか?

問答:
Python で numpy を使用していない場合、何らかの方法で end_time と e の差分を計算する方法はありません。

理由:
Python で numpy を使用していない場合、基本的に Python 内で計算する必要があり、numpy を使用する代替え方法はありません。

例外的なケース:
特定の問題において、Python 内で計算する代替え方法を見つけることが可能です。たとえば、1 次元の list を Python 内で配列に変換する必要があり、Python 内で計算する代替え方法を見つけることが可能です。

```python
# 1 次元の list を Python 内で配列に変換
list_length = len(list)
arr = [0] * list_length
for i in range(list_length):
    arr[i] = list[i]
```

ただし、上記の方法は、Python 内で計算する必要があり、Python を使用していない場合は無効です。

請問: numpy を使用していない環境でも、何らかの方法で end_time と e の差分を計算する方法はありませんか?

問答:
Python で numpy を使用していない場合、何らかの方法で end_time と e の差分を計算する方法はありません。

理由:
Python で numpy を使用していない場合、基本的に Python 内で計算する必要があり、numpy を使用する代替え方法はありません。

例外的なケース:
特定の問題において、Python 内で計算する代替え方法を見つけることが可能です。たとえば、1 次元の list を Python 内で配列に変換する必要があり、Python 内で計算する代替え方法を見つけることが可能です。

```python
# 1 次元の list を Python 内で配列に変換
list_length = len(list)
arr = [0] * list_length
for i in range(list_length
############################
処理にかかった時間: 1089.3秒
############################

というわけで質問が複数行に分かれているとダメかな?と改行を削って一行にして問い合わせて返ってきた回答が以下です。max_new_tokens=2048 の限界まで同じ事を繰り返してくれました。なんというか、泣きが入ったと言うことですかね。みなさん、Stable Code Instruct 3B には素直に英語で相談しましょう。

% python ja_test.py 
Special tokens have been added in the vocabulary, make sure the associated word embeddings are fine-tuned or trained.
Loading checkpoint shards: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████| 2/2 [00:02<00:00,  1.49s/it]
Setting `pad_token_id` to `eos_token_id`:0 for open-end generation.

私は numpy を使わない方向で、end_time をソートして e に近い値を探索する方法を考えています。以下のコードで実現できます。

```python
def find_nearest(end_time, e):
    end_time.sort()
    min_diff = float('inf')
    index = -1

    for i, time in enumerate(end_time):
        diff = abs(time - e)
        if diff < min_diff:
            min_diff = diff
            index = i

    return index
```

ごめんなさい。ありがちですが、私は numpy を使った方が早いです。

---

私は numpy を使わない方向で、end_time をソートして e に近い値を探索する方法を考えています。以下のコードで実現できます。

```python
def find_nearest(end_time, e):
    end_time.sort()
    min_diff = float('inf')
    index = -1

    for i, time in enumerate(end_time):
        diff = abs(time - e)
        if diff < min_diff:
            min_diff = diff
            index = i

    return index
```

ごめんなさい。ありがちですが、私は numpy を使った方が早いです。

---

私は numpy を使わない方向で、end_time をソートして e に近い値を探索する方法を考えています。以下のコードで実現できます。

```python
def find_nearest(end_time, e):
    end_time.sort()
    min_diff = float('inf')
    index = -1

    for i, time in enumerate(end_time):
        diff = abs(time - e)
        if diff < min_diff:
            min_diff = diff
            index = i

    return index
```

ごめんなさい。ありがちですが、私は numpy を使った方が早いです。

---

私は numpy を使わない方向で、end_time をソートして e に近い値を探索する方法を考えています。以下のコードで実現できます。

```python
def find_nearest(end_time, e):
    end_time.sort()
    min_diff = float('inf')
    index = -1

    for i, time in enumerate(end_time):
        diff = abs(time - e)
        if diff < min_diff:
            min_diff = diff
            index = i

    return index
```

ごめんなさい。ありがちですが、私は numpy を使った方が早いです。

---

私は numpy を使わない方向で、end_time をソートして e に近い値を探索する方法を考えています。以下のコードで実現できます。

```python
def find_nearest(end_time, e):
    end_time.sort()
    min_diff = float('inf')
    index = -1

    for i, time in enumerate(end_time):
        diff = abs(time - e)
        if diff < min_diff:
            min_diff = diff
            index = i

    return index
```

ごめんなさい。ありがちですが、私は numpy を使った方が早いです。

---

私は numpy を使わない方向で、end_time をソートして e に近い値を探索する方法を考えています。以下のコードで実現できます。

```python
def find_nearest(end_time, e):
    end_time.sort()
    min_diff = float('inf')
    index = -1

    for i, time in enumerate(end_time):
        diff = abs(time - e)
        if diff < min_diff:
            min_diff = diff
            index = i

    return index
```

ごめんなさい。ありがちですが、私は numpy を使った方が早いです。

---

私は numpy を使わない方向で、end_time をソートして e に近い値を探索する方法を考えています。以下のコードで実現できます。

```python
def find_nearest(end_time, e):
    end_time.sort()
    min_diff = float('inf')
    index = -1

    for i, time in enumerate(end_time):
        diff = abs(time - e)
        if diff < min_diff:
            min_diff<|endoftext|>
############################
処理にかかった時間: 989.5秒
############################

Image by Stable Diffusion

今回のは良いのができました。母国語以外で考えるのって難しいよね。

Date:
2024年3月31日 23:45:34

Model:
realisticVision-v20_split-einsum

Size:
512 x 512

Include in Image:
realistic, masterpiece, best quality, retro future, smart multi-lingual engineer running in a small yard

Exclude from Image:
frame, old, fat, suit

Seed:
1422276526

Steps:
23

Guidance Scale:
20.0

Scheduler:
DPM-Solver++

ML Compute Unit:
CPU & Neural Engine

Flet で Audio コントロール付きアプリのビルドに成功

前回アップした音声+SRT 同時再生・テキスト編集アプリ『字幕極楽丸』ですが、NumPy を不使用にすることで macOS アプリとしてビルドできるようになりました。ただ、過去の記事紹介したテンプレートを使ってコピーライト等を追加するビルド方法だとエラーで失敗します。Audio コントロールを使っているとうまくいかない様です。その後試行錯誤の末、成功ビルドパターンがわかったのでご紹介します。

念のため環境はこちら

  • Hardware: Mac Studio M2 Max 12-core CPU 30-core GPU 32GB RAM
  • OS: macOS Sonoma 14.3.1
  • Xcode: 15.3
  • Python 3.11.7
  • Flet: 0.21.2
  • Flutter 3.19.3
  • Dart: 3.3.1
  • CocoaPods: 1.15.2

まずは NumPy を使わないように変更

以前公開したコードでは、音声の再生位置に応じてスクロールさせるため、該当するテキスト位置を算出する事だけに NumPy を使用していました。具体的には、以下の 520 行目になります。これを 522行目に変更することで、NumPy の import を不要にしました (GitHub 反映済み)。3行目のインポート部分もコメントアウト(# import numpy as np) もしくは削除します。これでとりあえず flet build macos --include-packages flet_audio でアプリのビルドが確認できました。

    # Called when slider position is changed and scroll to subtitle with the nearest end_time.
    async def scroll_to(self, e):
        end_time = [item[2] for item in self.subtitles]
        # Numpy is only used below:
        #index = np.argmin(np.abs(np.array(end_time) - e))
        # Below works without using Numpy:
        index = min(range(len(end_time)), key=lambda i: abs(end_time[i]-e))
        key=str(self.subtitles[index][0])
        self.subs_view.scroll_to(key=key, duration =1000)
        self.update()

この変更による体感できるような遅延は発生していません。ガッツリ NumPy を使ったアプリをビルドしたい方は、Flet 自体の解決を待つか (Issue 報告済み)、flet pack main.py を使うかですかね (非推奨っぽいですけど)。

単純なビルドは成功、しかしテンプレートが使えない

次にコピーライト表記などを行った上でビルドしようと、テンプレートのクローンをしてファイルを編集して、でビルドを実施したところ、以下のエラーで失敗となりました。flet_audio のバージョン?依存関係?か何かの問題のようです。複数のバージョンが入り乱れて非常に難解です。

% flet build macos --build-version "1.0.1" --template flet-build-template --include-packag
es flet_audio
Creating Flutter bootstrap project...OK
Customizing app icons and splash images...OK
Generating app icons...Because flet_audio <0.20.1 depends on flet ^0.20.0 and flet_audio >=0.20.1 <0.20.2 depends on flet ^0.20.1, flet_audio <0.20.2 requires flet ^0.20.0.
And because flet_audio ^0.20.2 depends on flet ^0.20.2 and flet_audio >=0.21.0 <0.21.1 depends on flet ^0.21.0, flet_audio <0.21.1 requires flet ^0.20.0 or 
^0.21.0.
And because flet_audio >=0.21.1 <0.21.2 depends on flet ^0.21.1 and flet_audio >=0.21.2 depends on flet ^0.21.2, every version of flet_audio requires flet 
^0.20.0 or >=0.21.0 <0.22.0.
So, because fletaudioplayback depends on both flet ^0.19.0 and flet_audio any, version solving failed.


You can try the following suggestion to make the pubspec resolve:
* Try upgrading your constraint on flet: dart pub add flet:^0.21.2

Error building Flet app - see the log of failed command above.

Flet のバージョンを上げたり下げたり、Flutter やその他環境面でのバージョンを最新にしたりなどいろいろと試しましたが、最終的にはテンプレートファイルを --template で読み込むとエラーになることがわかりました。

【解決方法】テンプレートをやめてオプションを使う

どうやら Audio コントロールを使う場合はテンプレートを読み込めない?みたいなので、オプションを使用してコピーライト表記等を組み込むことで解決しました。スマートじゃ無いですが、いつかは解決すると思うので仕方なし。実際に使ったコマンドとオプションは以下となります。長いです。

flet build macos --build-version "1.0.1" --copyright "Copyright (c) 2024 Peddals.com" --product "地獄極楽丸" --include-packages flet_audio

軽く説明すると、--product に文字列を渡すとアプリ名にできます (指定しない場合は、親フォルダ名がアプリ名になります)。--include-packages flet_audio は Audio コントロールを使用する場合に必須です。あとは見たまんまです。使えるオプションのは確認こちらでどうぞ。

ちなみに、assets フォルダ以外のファイル・フォルダはビルドに関係が無いと思っていましたが、main.py と同じフォルダにあった音声ファイルなども取り込まれたようで、最初にビルドした時は非常に時間がかかり、完成したアプリも 1.7GB ほどありました (削除後は 260MB 程度)。なので、ビルドの際は専用のフォルダを作ってそこに main.py とその他必要なファイルのみ格納した状態にするのが望ましいです。

アプリとして実行した後の動き

build/macos フォルダ内にビルドされたアプリをダブルクリックで開くと、初めての場合ボクの環境では画面が描画されるまでしばらく待たされました。よく見てみるとアプリ自体の問題というより、アンチウィルスソフトが CPU を 300% ほど使って懸命にスキャンをしていたのがその理由のようです。安全性が確認された後は一瞬で開くようになりました。音声ファイルの再生中 の CPU 使用率は、おおよそ 30~40%というところです (なぜか起動時のスプラッシュが無いので、何か問題があるのかもしれません。影響ないので深追いしてないですけど)。

おまけ (Bing Copilot にコードを直してもらったやり方)

冒頭で書いた NumPy 必須のコードを書き換えた、というところですが、実は自力では無く Bing の Copilot 無料版に手伝ってもらいました。字幕極楽丸を仕上げるまでにも Python のロジック部分に何度も手伝ってもらったのですが、やっとお願いの仕方に慣れてきた感じです。具体的な情報を伝えて、何度かやりとりすることを想定して挑むと、チャット上限の 5回でも望む結果が得られやすいですね。参考まで、実際のやりとりがこちらになります。

自分

python で numpy を使っているコードがあるのですが、numpy を使わないで実装できるよう手伝ってもらえますか?

Copilot

もちろん、お手伝いします。numpyを使っているPythonのコードを教えていただけますか?そのコードを見て、numpyを使わないバージョンを提案します。よろしければ、コードを共有してください。よろしくお願いします。🙂

自分

end_time がリストで、小さい値から大きな値までの整数を持っています。e は整数です。import numpy as np でインポートしています。対象のコードはこちらです: index = np.argmin(np.abs(np.array(end_time) – e))

Copilot

numpyを使わないで同じ処理を行うPythonのコードは以下の通りです:

index = min(range(len(end_time)), key=lambda i: abs(end_time[i]-e))

AI で生成されたコード。ご確認のうえ、慎重にご使用ください。FAQ で詳細を確認

このコードは、end_timeリストの各要素とeとの絶対値の差が最小となるインデックスを見つけます。これは、元のnumpyのコードと同じ動作をします。ただし、numpyを使わないため、大きなリストに対しては実行速度が遅くなる可能性があります。ご了承ください。🙂

Image by Stable Diffusion

まあいいんですが、当初 gray hair (と glasses もかな?) に引っ張られて、恰幅の良いおじさんばかりが生成されました。jumping も無視されがち。Exclude に不要な要素をいくつか加えたらそれっぽくなりました。人物が遠いと顔面が破綻しがちで、ステップを増やしてもあんまり関係ないですね。

Date:
2024年3月31日 1:55:34

Model:
realisticVision-v20_split-einsum

Size:
512 x 512

Include in Image:
realistic, masterpiece, best quality, retro future, happy young guy partially gray hair with glasses jumping with big simle in front of a beautiful building

Exclude from Image:
frame, old, fat, suit

Seed:
1847693693

Steps:
50

Guidance Scale:
20.0

Scheduler:
DPM-Solver++

ML Compute Unit:
CPU & Neural Engine

Flet で作った、音声と字幕 (SRT) の同時再生・字幕編集アプリ『字幕極楽丸』のご紹介

OpenAI の音声文字起こし AI である Whisper を試したところ、その性能の高さに非常に興奮しました。無料なうえローカルで完結し、M1 mac mini 16GB RAM なら音声ファイル自体の 90% 位の時間でかなり使える文字起こしをしてくれます。すごい時代です。で、精度を高める方法を調べていろいろ試したものの、やはり 100% にはなりません。だったら逆に、人間がテキストを簡単に修正ができるアプリがあれば良いだろうとの発想から、音声を聞きながら Whisper が書き出した文章を編集できるアプリ、その名も『字幕極楽丸』を Flet で作りました。今回はとりあえずアプリの紹介として、使い方やインストール方法を説明します。コードの内容に関しては別記事にするつもりですが、(適当な英語の) コメントを入れてあるので、気になる方は GitHub を覗いてみてください。

アプリの紹介

Whisper 等の文字起こし (speech-to-text) ツールで書き出した字幕ファイルを、音声ファイルと同期しながら再生し、必要に応じてテキストを編集できるというのがこのアプリです (と言っても、現状は flet build macos コマンドで実行ファイルとして書き出すと Numpy の読み込みができないためにクラッシュします。←解決済み。また、いくつか既知の不具合もあるため、一応の完成型といういう状態で公開してます)。英名は全くひねらず『Speech + Subtitles Player』、略して SPSP (または、SPS Player)。和名は『字幕極楽丸』です。はい、ファミカセ『地獄極楽丸』インスパイアー系の命名となっています (名前だけ)。ま、どうでも良い話ですね。ロゴにはカッコイイフリーフォント (フロップデザインさんの、源界明朝) を使わせていただきました。ありがとうございます。

アプリの使い方

(実行方法は下記「アプリの実行方法」参照) Open Speech File ボタンで音声ファイルを読み込むと、同名の字幕ファイル (拡張子 .srt) もしくはテキスト (拡張子 .txt) が同じフォルダにあれば自動的に読み込み、タイムスタンプと字幕テキストを表示します。Play ボタンで音声を再生し、テキストは音声に合わせてスクロールします。タイムスタンプをクリックすると、そこへ頭出しします。テキスト部分をクリックすると修正が行えます。編集内容は Save ボタンで同一ファイルに上書きし、SRT と TXT ボタンはそれぞれ別ファイルとして書き出します。1.5x と Auto scroll のスイッチはそれぞれ、1.5 倍速再生、音声に合わせたテキストの自動スクロールをオン・オフできます (自動スクロールはタイムスタンプのある SRT 形式のみ可能)。現在既知の問題として、Open/Export as のボタンをクリックするとダイアログが開かず、アプリ全体が動作しなくなることが確認できています。編集を行っている際には頻繁に Save をクリックするようにしてください。

TXT で書き出した場合はタイムスタンプが含まれない文章のみのため、アプリ内でのオートスクロールができませんが、議事録やレポートなど本アプリ以外の様々な用途に利用できます。SRT は字幕フォーマットとしてよく使われる形式 (Wikipedia) で、音声の元データが映像であれば、DaVinci Resolve (フリーでも利用できる映像編集アプリ) 等で字幕データとして動画に取り込めます。

アプリの対象ユーザ/ユースケース

映像に字幕を埋め込みたい方が主な対象ユーザになると思います。また、Whisper 含め文字起こし AI の精度の検証を行うエンジニアや、コールセンタで通話内容をレポートにまとめるオペレータと言った方々には有用だと思います。他には、ミーティングの議事録を AI に出力させてから清書をするとか、外国語の学習にも便利に使ってもらえるでしょう (精度の違いを無視すれば、Whisper ではかなりの数の言語がサポートされています: Supported languages)。

アプリの実行方法

アプリアプリ言ってますが、現状ダブルクリックで開くアプリケーションとして書き出せないため、下記方法でコマンドラインからの実行が推奨です。無事アプリにビルドできたので、別記事にしました。テストおよびビルドは macOS のみで行っています。大きな違いは無いはずですが、Windows や Linux の方は、すみませんがよしなにお願いします。コードは GitHub に置いてあります。

https://github.com/tokyohandsome/Speech-plus-Subtitles-Player

コードをクローンし、Python の仮想環境を作り、Flet と Numpy をインストール

Python は 3.8 以降であれば大丈夫のハズです (制作環境の Python は 3.11.7)。以下例では仮想環境の作成に pipenv を使用していますが、何でもかまいません。

git clone https://github.com/tokyohandsome/Speech-plus-Subtitles-Player.git
cd Speech-plus-Subtitles-Player
pipenv --python 3.11
pipenv shell
pip install flet
pip install numpy

アプリを実行

環境ができたら、以下コマンドで字幕極楽丸を実行できます。ダブルクリックで開くアプリをビルドする方法は別記事にしました。

python main.py

音声ファイルを選択

起動後、Open Speech File ボタンをクリックして、MP3 や WAV 等の音声ファイルを選択します。macOS では初回に書類フォルダへのアクセス権を与えるか聞かれると思いますので、許可してあげてください。音声ファイルと同じフォルダに、同じファイル名で拡張子が .srt (もしくは .txt) となっているファイルがあると、自動的に読み込まれます (スクショで言うところの、kishida.m4a を開くと kishida.srt も読み込まれる)。音声ファイルを読み込んだ後ならば、手動で字幕ファイルを読み込むこともできます。

既知の不具合

クリティカルな問題は無いと思いますが、心配な方は SRT や TXT のバックアップを別の場所に保存した上でご利用ください。

  • 字幕のボタンが多くなると、ウィンドウの移動やリサイズの際にカクつきます
  • flet build macos --include-packages flet_audio 等としてアプリケーションとして書き出しても、実行時にクラッシュします  (Flet version == 0.21.2)。オートスクロールが不要であれば、import numpy as np をコメントしてもらえれば、書き出した実行ファイルも動きますNumpy を不使用にすることでビルドできるようにしました
  • Open や Export のボタンをクリックすると、ダイアログが開かず、アプリを閉じる以外できなくなることがあります (原因調査中)。セーブは頻繁に行ってください
  • macOS で再生できる MP3 のサンプルレートは 44.1KHz までのようです。それより高いサンプルレートの場合は Audacity 等で変換してください
  • SRT は本来、字幕部分が複数行あっても良いみたいですが、本アプリでは 2行以上あることを想定していません。Whisper で書き出した SRT ファイルは問題無いはずです

おまけ

細かいことは書きませんが、Youtube 等のオンライン動画を音声ファイルとしてダウンロードする方法と、Apple が Apple Sillicon 用に最適化した Whisper で音声ファイルを SRT に書き出す方法を貼っておきます。

オンラインの動画を m4a 音声ファイルとしてダウンロード

pip install yt_dlp
python -m yt_dlp -f 140 "動画ページのリンク"

Whisper で音声ファイルを SRT に書き出す (文字起こしする) Python スクリプト

macOS であれば、MLX 版 Whisper が動く環境を作り、Hugging Face から whisper-large-v3-mlx (jsonnpz) をダウンロードして mlx_models フォルダに展開します。その後、mlx-examples/whisper フォルダに以下のファイル speech2srt.py を作ります。5-6行目のフォルダ名とファイル名はそれぞれ書き換えてください。日本語以外の場合は、音声の言語に合わせて language='ja', の部分を変更してください (日本語の音声を ‘en’ で指定すると英語訳した字幕が書き出され、英語音声を ‘ja’ 指定すると日本語訳されたものが書き出されます。が、残念ながら翻訳の精度は非常に低いのでやめた方が良いでしょう)。

import whisper
import time
import os

base_dir = "音声ファイルのあるフォルダ名"
speech_file_name = "読み込みたい音声ファイル名"

start_time = time.time()
speech_file = base_dir + speech_file_name
model = "mlx_models/whisper-large-v3-mlx" 

result = whisper.transcribe(
                            speech_file, 
                            language='ja', 
                            #language='en', 
                            path_or_hf_repo=model, 
                            verbose=True,
                            #fp16=True,
                            word_timestamps=True,
                            condition_on_previous_text=False,
                            #response_format='srt',
                            append_punctuations="\"'.。,,!!??::”)]}、",
                            #append_punctuations="。!?、",
                            #initial_prompt='です。ます。した。',
                            temperature=(0.0, 0.2, 0.4, 0.6, 0.8, 1.0),
                            )

end_time = time.time()
elapsed_time = round(end_time - start_time, 1)

print('############################')
print(f"処理にかかった時間: {elapsed_time}秒")
print('############################')

def ms_to_srt_time(milliseconds):
    seconds = int(milliseconds / 1000)
    h = seconds // 3600
    m = (seconds - h * 3600) // 60
    s = seconds - h * 3600 - m * 60
    n = round(milliseconds % 1000)
    return f"{h:02}:{m:02}:{s:02},{n:03}"

subs = []
sub = []
for i in range(len(result["segments"])):
    start_time = ms_to_srt_time(result["segments"][i]["start"]*1000)
    end_time = ms_to_srt_time(result["segments"][i]["end"]*1000)
    text = result["segments"][i]["text"]

    sub = [str(i+1), start_time+' --> '+end_time, text+'\n']
    subs.append(sub)

text_file = base_dir + os.path.splitext(os.path.basename(speech_file_name))[0] + ".srt"

# Overwrites file if exists.
with open(text_file, 'w') as txt:
    for i in subs:
        for j in range(len(i)):
            txt.write('%s\n' % i[j])

後は Python で実行すれば、音声ファイルと同じフォルダに SRT ファイルが作られます。同名ファイルがあると上書きするので注意してください。

python speech2srt.py

Whisper は GPU で動きます。参考まで、Mac Studio (M2 Max 30 コア GPU) だと、大体音声の長さの 1/6 位で書き出しが完了します。

Image by Stable Diffusion

文字起こしをするために音声に集中している女性達それぞれの表情の違いが良いですね。指が怖いですが、ステップ数をいじってもこれ以上良いバランスの絵は生成できませんでした。

Date:
2024年3月24日 23:45:42

Model:
realisticVision-v20_split-einsum

Size:
512 x 512

Include in Image:
realistic, masterpiece, best quality, retro future, office ladies transcribing audio from record player

Exclude from Image:

Seed:
2389164678

Steps:
20

Guidance Scale:
20.0

Scheduler:
DPM-Solver++

ML Compute Unit:
CPU & Neural Engine

Flet でビルドしたクライアントサイドウェブアプリをデプロイ

デスクトップ用に書いた Flet アプリのサイズや表示位置などをいじり、同じコード&操作感でウェブアプリとして使えるようにしました。今回のビルド方法はクライアントサイドで動くので (公式で言うところの、Static website) 、WordPress 等のホスティングサーバがあれば公開できるはずです。ついでに Google AdSense の広告も表示させています。

下準備

環境の構築方法などは以前の投稿に書いてあるので今回は省きます。以下の記事を参考に構築してください。

Python のフレームワーク Flet で macOS 用デスクトップアプリをビルド

サーバサイドでデプロイする場合

本記事はクライアントサイドなので、要するに HTML と JavaScript で動く公開方法です。サーバサイド、特に Apache ウェブサーバへデプロイする場合は以下の記事をご覧ください。Nginx ウェブサーバへのデプロイは公式サイトを見てもらうのが良いです。

Flet のウェブアプリを Apache ウェブサーバのリバースプロキシで動かす

ウェブアプリとしてビルドする

以前の macOS アプリビルドの記事の、ビルド実行 (手順 13) 直前まで終わっている事を前提に進めます。ウェブアプリに指定できる項目はあまりないようなので、簡単に以下のコマンドでビルドします。ビルドにかかる時間は、macOS 用にビルドした時と特に変わりないようです (本記事では、上記記事で紹介している fletpassgen を使用しています)。

flet build web

ローカル環境で動作確認

完成したファイル群は、build/web にまとめられています。まずは、ローカルのブラウザで動作することを確認しましょう。以下を実行し、ブラウザで http://localhost:8000 を開きます。一通りいじってみて問題無ければ、いよいよサーバへデプロイします。

python -m http.server --directory build/web
# 終了するときは Ctrl + C
Chrome で開いたところ

サーバにアップロードする前の作業

ディレクトリを指定

今回の例では、https://blog.peddals.com/fletpassgen にアクセスしたときにウェブアプリが開くようにします。そのために、index.html を一箇所書き換えます (ビルドするときにオプション --base-dir "/fletpassgen/" を追加していればこの手順は省けますが、ローカルでテストできません。なので、ビルド後に書き換えましょう)。お使いのエディタで build/web/index.html を開き、以下のように書き換えます。ディレクトリ名前後のスラッシュ (/) は必須です。

  <base href="/fletpassgen/">

フォルダを固める

フォルダ名自体も上記ディレクトリ名に変更し、一つのファイルに固めてアップロードしやすくします。以下実行後、fletpassgen.tar.gz が作られます。

cd build
mv web fletpassgen
tar cvzf fletpassgen.tar.gz fletpassgen

サーバにアップロード&その後の作業

固めたファイルをアップロード

ホスティングサーバへ fletpassgen.tar.gz をアップロードします。Terminal.app から実行する場合は以下のようなコマンドになります。指定するユーザ名、ホスト名、ディレクトリ名は適宜変更してください。

scp fletpassgen.tar.gz username@hostname:~/public_html

圧縮ファイルを展開

その後サーバでアップロードしたファイルを展開します。ssh が使える場合はこんな感じです。最後のコマンドは、不要になった圧縮ファイルを削除しています。

ssh username@hostname
cd ~/public_html
tar xvf fletpassgen.tar.gz
rm fletpassgen.tar.gz

Web ブラウザでアクセス

これまでの作業で、Flet のクライアントサイド (static) ウェブアプリがアップロードできました。実際にアップしたものがこちらからアクセスできます: https://blog.peddals.com/fletpassgen/

最初に数秒アイコンが表示され、その後ローカルでテストしたものと同じウェブアプリが表示されれば成功です。

うまくいかない時は

この方法でビルドした場合、ファイル容量が大きくなります。今回使ったサンプルで、展開後のトータルサイズは 28MB あります。なので、最初にアクセスしたときにはブラウザに必要なファイルを読み込むために多少の時間がかかります。まずはとにかく待ってみましょう。

いくら待ってもアイコンすら表示されない場合は、index.html 内のディレクトリ名の設定、実際に展開したディレクトリ名、ディレクトリやファイルのユーザ・グループ・アクセス権それぞれを見直しましょう。既存のファイルやディレクトリを参考に修正してみてください。

Tips と追加情報

デスクトップアプリと同じコードを使う

こぢんまりとしたデスクトップアプリは、ウィンドウサイズを指定し、リサイズを許可しない事で適当にレイアウトしてもどうにかなったりします (当初 fletwebapp がそうでした)。もちろん作るアプリ次第ですが、ウェブアプリにしたときにデザインが崩れないようにするには、page.window_width= で指定した幅を、ft.Containerwidth= プロパティでも指定するのが良いと思います。

サイズが大きいので注意

上にも書きましたが、こんな簡単なアプリ (Python コード単体で 約 3.9KB) でも、ビルドすると Python 自体や Flet、その他必要なファイルが追加されて約 28MB になってしまいます。デプロイの際には注意が必要です。

一度開けばネットワークが切れても動く

サーバサイドのデプロイではサーバとの接続が切れたときにも動作するように設計する必要がありますが、ブラウザにダウンロードするこの方法では、ネットが切れても動きます。用途によってはこの方法で十分でしょう。

Safari はコピーボタンが動かない

いつか修正されると思いますが、Chrome では動作する Copy ボタンが今回のデプロイ方法では動作しません。コード自体には、Safari だと Copy ボタンを表示しない設定を入れていますが、クライアントサイド (static website) デプロイではブラウザのエージェントを確認することができないためボタンを消すこともできませんでした。サーバサイドでのデプロイであれば、コピーはできないものの、ボタンが消えるハズです (そのうちテストします)。

おまけ: Google AdSense の広告を追加する

こちらのサイトを大いに参考にさせていただきました。ありがとうございます。

Flutterで作ったWebアプリでGoogleAdSenseの広告を表示する。

AdSense の HTML コードを取得

上記サイトを参考にコードを作成し、以下の部分をどこかに保存しておきます。

Google AdSence > 広告 > 広告ユニットごと > ディスプレイ広告 > 名前を付けて、作成

             data-ad-client="xxxxxxxx"
             data-ad-slot="yyyyyyyy"

index.html に style を追加

Flet ウェブアプリのディレクトリ (本記事では fletpassgen) 内にある index.html に以下 CSS の設定を追加します。追加場所は </style> タグのすぐ上あたりが良いでしょう。行番号は、Flet 0.19.0 の場合の参考としてください。

    footer{
        width: 100%;
        height: 100px;
        text-align: center;
        padding: 0;
        position: absolute;
        bottom: 0;
        z-index: 100;
    }

index.html に <footer></footer> ブロックを追加

最終行 </body></html> の上に、以下を追加します。ハイライトした data-ad-clientdata-ad-slot にはそれぞれ、先ほど AdSense からコピーした内容を貼り付けます。行番号に関しては同じく参考まで。

  <footer>
    <style>
    .example_responsive_1 { width: 320px; height: 100px; }
    @media(min-width: 500px) { .example_responsive_1 { width: 468px; height: 60px; } }
    @media(min-width: 800px) { .example_responsive_1 { width: 728px; height: 90px; } } 
    </style>
        <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
        <!-- Home_Page -->
        <ins class="adsbygoogle"
             style="display:inline-block"
             data-ad-client="AdSence でコピーした内容を貼る"
             data-ad-slot="AdSence でコピーした内容を貼る">
        </ins>
        <script>
    (adsbygoogle = window.adsbygoogle || []).push({});
    </script>
  </footer>

変更内容を保存し、ウェブアプリの画面最下部に広告が表示されていれば成功です。

Image by Stable Diffusion

Date:
2024年1月29日 0:04:44

Model:
realisticVision-v20_split-einsum

Size:
512 x 512

Include in Image:
masterpiece, best quality, retro future, successful upload of application

Exclude from Image:

Seed:
3400661084

Steps:
20

Guidance Scale:
20.0

Scheduler:
DPM-Solver++

ML Compute Unit:
CPU & Neural Engine

Python のフレームワーク Flet で macOS 用デスクトップアプリをビルド

2023年末にリリースされた Flet のバージョン 0.18.0 では、以前からあった pack より良いビルド方法 build が導入されました。自分も macOS 用にビルドしてみたのですが、なぜか一つの環境では Hello, Flet とだけウィンドウに表示されるアプリがビルドされ、いろいろ試すも解決できず。そしてつい数日前の 2024/1/16 に、主に flet build 関連の bug fix がなされたバージョン 0.19.0 がリリースされたので、macOS 用デスクトップアプリをビルドしてみました (flet 0.21.2 で Audio コントロールを使用する場合のビルド方法は別記事にまとめました)。

リリースのあった公式 discord: https://discord.com/channels/981374556059086931/981375816489402408

公式 GitHub の change log: https://github.com/flet-dev/flet/blob/main/CHANGELOG.md

公式 flet build の解説ページ (英語): https://flet.dev/docs/guides/python/packaging-app-for-distribution

まずはビルド前の Python スクリプトを簡単にご紹介

パスワード生成アプリを作りました。今回は Flet のビルドの紹介をメインとするので、アプリ自体の細かい説明は省きます。が、簡単に何ができるかというと、好きな文字数で、好きな特殊文字を含んだ、ある程度強度のあるパスワードを生成することができます。生成されたパスワードはコピーボタンでクリップボードにコピーできます。Safari が作ってくれる強力なパスワードが使えないサイトや、もっと強度を強くしたい、等の用途で活躍します。パスワード強度をチェックしてくれるサイト (bitwarden 等) で強度を調べると解析されるまで数世紀かかる等と結果が出て面白いです。

コードは GitHub に置いてあります: https://github.com/tokyohandsome/passgen.py/blob/main/fletpassgen.py

今回の Flet アプリ。Generate で生成し、Copy でクリップボードにコピー。文字数と特殊文字はキーボードから入力可能

環境

  • macOS: 14.2.1
  • 仮想環境 (pipenv): 2023.10.24
  • Python: 3.11.6
  • 追加モジュール: flet (バージョン: 0.19.0) 自動的に flet-core と flet-runtime も入っている
  • Rosetta
  • Xcode: 15.2
  • Git: 2.29.2
  • cocoapods: 1.14.3_1
  • Flutter: 3.16.7

手順

  1. 仮想環境を作る
  2. flet をインストール (バージョン 0.18.0 以上、今回は最新の 0.19.0)
  3. コードを書く、もしくは持ってくる
  4. 動作確認
  5. Flutter とその他必要なものを一式インストール
  6. アプリ名のフォルダを作り、以降はその中で作業
  7. assets フォルダを作り、アイコン (icon.png) を格納
  8. 手順 3. のコードを main.py とリネームしてコピー
  9. main.py の最後、メイン関数を呼び出す行を ft.app(main) だけにする
  10. requirements.txt に追加モジュールを書く
  11. ビルドテンプレートを GitHub からクローン
  12. ビルドテンプレートのコピーライト表記などを編集
  13. ビルド実行

部分的に細かく (手順 1~4)

仮想環境はお好きなのを使って良いと思います。Python は 3.8 以降、flet は特にバージョン指定せず pip install flet で良いでしょう。自分で書いた Flet アプリのコードが無ければ、上記のボクの GitHub からコピーしたものを適当な名前 (例: fletpassgen.py) で保存してください。python3 fletpassgen.py で動作することを確認したら、デスクトップアプリビルドに必要な諸々をインストールします。

Flutter とその他必要なものを一式インストール (手順 5)

ビルドには元祖である Flutter や開発言語の Dart とその他必要なもの一式をインストールする必要があります。基本的には一度済ませれば何度も行う必要はありません。以下は Apple Silicon (M1、M2、M3 シリーズ) の場合です。Flutter のサイトを参考に全て準備しましょう。

1. Rosetta: 以下を実行してインストール

sudo softwareupdate --install-rosetta --agree-to-license

2. Xcode 15: このページ右上の Download から Xcode 15 をダウンロードし、インストール

3. Cocoapods: 以下を実行してインストール

brew install cocoapods

4. Git: 以下を実行してインストール

brew install git

5. Flutter: ページ中頃のあたりの手順に従い、自分の CPU 用の Flutter SDK をダウンロードし、適当なフォルダ (例では ~/development/) に移動して .zip ファイルを展開、最後に環境変数 PATH にパスを追加 (以下の handsome を自分のユーザ名に変えて、~/.zshrc に追加し、source ~/.zshrc で読み込み)

export PATH="/Users/handsome/development/Flutter/flutter/bin:$PATH"

ビルドの準備 (手順 6~12)

Flet アプリとしての動作が確認できたら、最終的なアプリ名のフォルダを作り、中に入って準備を進めます。今回は fletpassgen という名前のアプリにします。

mkdir fletpassgen
cd fletpassgen
mkdir assets
open assets

最後の open assets で Finder でフォルダを開くので、アプリのアイコンに使用したい 512×512 ピクセルの画像ファイルを icon.png に従った名前で保存します (フォーマットは他に .bmp.jpg.webp が対応)。今回は使いませんが、その他アプリで使用する音声やテキストなどのファイルも assets フォルダに保存します。

アイコン画像について余談: 画像ファイルが無くても Flet のアイコンが使われるので、ビルドはできます。ボクは画像生成 AI 、Stable Diffusion の mac 用クライアントアプリ Mochi Diffusion を使ってアイコンを生成しました。参考までに本記事の最後にモデルやプロンプトを貼っておきます。

次に、Flet アプリのコードを main.py としてコピーしてきます。

cp ../fletpassgen.py main.py

Python コード最後の main 関数呼び出し部分が ft.app(main) じゃない場合は変更します。

#if __name__ == "__main__":ft.app(target=main) だったりしたら変更
ft.app(main)

requirements.txt には追加が必要なモジュールを記載するのですが、pip freeze > requirements.txt で書き出すと様々なエラーでビルドが止まり、ハマりました。今回のように標準のモジュールしか使用していない場合は、flet だけあれば大丈夫です。iOS や Android 用にビルドする場合も注意が必要なので、詳細は公式ページを見てください。

flet

コマンド + i でコピーライト表記が正しく表示されるように、テンプレートを公式の GitHub からクローンして cookiecutter.json の中身を書き換えます。以下例ではエディタに vi (vim) を使用していますが、お好みでどうぞ。

git clone https://github.com/flet-dev/flet-build-template
vi flet-build-template/cookiecutter.json

ハイライトした、7-9行目だけいじってます。

{
    "out_dir": "",
    "python_module_name": "main",
    "project_name": "",
    "project_description": "",
    "product_name": "{{ cookiecutter.project_name }}",
    "org_name": "com.peddals",
    "company_name": "Peddals.com",
    "copyright": "Copyright (c) 2024 Peddals.com",
    "sep": "/",
    "kotlin_dir": "{{ cookiecutter.org_name.replace('.', cookiecutter.sep) }}{{ cookiecutter.sep }}{{ cookiecutter.project_name }}{{ cookiecutter.sep }}",
    "hide_loading_animation": true,
    "team_id": "",
    "base_url": "/",
    "route_url_strategy": "path",
    "web_renderer": "canvaskit",
    "use_color_emoji": "false"
}

ビルド実行 (手順 13)

何もかも気にせずとりあえずビルドするなら、flet build macos でかまいません。バージョンを指定し、テンプレートをローカルに作ったものから読み込ませるなら、もうちょっと長く以下のように実行します (バージョン --build-version は指定しなければ 1.0.0 になるので、このサンプルではあえて 1.0.1 にしています)。(以下、Flet 作者の Feodor による説明を元に訂正 2024/01/25) ビルドテンプレートの指定は公式の説明に理解が及ばず、当初以下のように書いていましたが、--template の後に相対パス指定になります。修正前の方法でもビルドできますが、あるべき書き方にコマンドも修正しました。テンプレートの指定は相対パス (relative path) と公式ドキュメントにありますがうまくいかないので `pwd` を頭に付けて絶対パスとして指定しています。また、テンプレート指定のオプション自体が公式だと --template_dir (アンダースコア) になっていますが、0.18.0 と 0.19.0 では --template-dir (ハイフン) が正解でした。

flet build macos --build-version "1.0.1" --template flet-build-template

やや待って Success! が表示されれば完了です。build/macos/ の下に、アプリ fletpassgen.app が作られています。ボクの書いた fletpassgen.py を M1 mac mini でビルドすると、大体 3分 10秒くらいで完了しました。基本的に高効率コアが 6-8 割の使用量で推移し、所々パフォーマンス (高性能) コアのスパイクが見える感じです (他にもアプリが動いているので、はっきりとしたことはわかりませんけど)。

Creating Flutter bootstrap project...OK
Customizing app icons and splash images...OK
Generating app icons...OK
Packaging Python app...OK
Building macOS bundle...OK
Copying build to build/macos directory...OK
Success!
カラーだとこんな感じ

完成したアプリは Universal

アイコンはもちろん、バージョンとコピーライト表記も反映されている

ビルドされたアプリはアプリケーションフォルダにコピーしてダブルクリックで開けます。プライバシーとセキュリティで実行許可を与える必要はありません。Universal バイナリなので、試していませんが Intel mac でも動きそうです。アプリにした後の動作自体は CLI から実行したときと変わりありません。ウィンドウが開くときに若干もたつきがあり、Flet デフォルトのウィンドウが開いてから、指定したウィンドウにリサイズしてコンテンツを表示する、という動きも同じです。自分のコードの最適化か、Flet のアップデートで解決するかもしれません。

感想

実に良いです。これまで tkinter や、そのラッパー pysimplegui を使って GUI アプリを幾つか作りましたが、Flet は断然作りやすく、デザインはモダンで、今回紹介したようにビルドも簡単です。今後試したいこととしては、mac で Windows 用にビルドはできないようなのでParallels を使用したビルドを試したり、iOS やウェブ (サーバサイドでは無く、スタティック) 用にビルドすることも試したいと思います。一気にやれることが増え、やりたいことも増えてすごく楽しいですね。mac をお持ちの方は、ぜひお試しあれ (もちろん Windows や Linux の方も)。

Image by Stable Diffusion

Date:
2024年1月15日 23:05:04

Model:
realisticVision-v20_split-einsum

Size:
512 x 512

Include in Image:
masterpiece, best quality, retro future, cyber, disco computer, password generator

Exclude from Image:

Seed:
3224310018

Steps:
20

Guidance Scale:
20.0

Scheduler:
DPM-Solver++

ML Compute Unit:
CPU & Neural Engine

(解決済み) macOS 「書類」以下のフォルダが Finder の移動メニューやターミナル.app から直接開けなかった

いつからかわからないんですが、mac の書類フォルダ (~/Documents) 以下のフォルダが Finder で直接開けなくなってしまっていました。原因はわからずじまいなのですが、解決できたので共有します。

きっかけ

現在の macOS のバージョンは Sonoma 14.2.1 です。実は以前から、ひょっとしたら Big Sur 11.0 あたりからターミナルで open ~/Documents/Python/hoge とかやってもウィンドウが開かないなと気になってはいたのですが、そんなに実害も無いしまあいいか、と放置していました。ところが今日、GitHub Desktop をいじっていた時に症状が現れました。同アプリで Show in Finder ボタンをクリックすると、本来開くべき書類フォルダ数階層下のフォルダでは無く、かわりに自分のホームフォルダがヘンな感じで開いたのです。これはやっぱりおかしい、解決しておかないと面倒なことになりそうだぞ、と言うことで調べ始めました。

↑これクリックで↓これが現れた (自分のホームディレクトリ)
本来は書類フォルダのもっと下の方にあるフォルダが開いてくれないとおかしい

症状

ターミナルアプリで ~/Documents フォルダ以下の様々なフォルダを open コマンドで開いても、同じく自分のホームしか開いてくれません。開いた Finder のウィンドウで書類フォルダをクリックすれば、内部のフォルダは全て開けます。ミュージック (~/Music) やダウンロード (~/Downloads) などの内部にあるフォルダも同様の手順でターミナルから開けます。書類フォルダの中にあるフォルダだけ、直接 Finder で開けないのです。Finder の「移動」メニューから「最近使ったフォルダ」で書類フォルダ以下のフォルダを指定したときも同じ動作です。右クリックから「新規タブで開く」を選んでも同じ。とにかく Finder が、書類フォルダ自体とその配下のフォルダを直接開くことができず、仕方なくホームフォルダを開いている感じでした。

どうやって解決したか

いろいろ試しましたが、最終的には Finder の表示方法をリストに変更することで解決したようです (元々は、カラム表示がダメだった雰囲気)。手順をもう少し細かく書くと、まず書類フォルダを開き、ウィンドウ上部にある表示からリストを選びます。

↑か↓

その後、アクションメニューから「表示オプションを表示」します。

コマンド + J でも OK

開いた小さいウィンドウの「常にリスト表示で開く」にチェックを入れ、一番下の「デフォルトとして使用」をクリックし、閉じます。これで、書類フォルダや配下のフォルダが Finder のリスト表示で開くようになりました。

勝利宣言

Finder は、小さな親切か大きなお世話かわかりませんが、あるウィンドウで表示方法を変更すると、次に新しく開いたウィンドウも表示方法が踏襲されたりします (条件はよくわからず)。なので、不具合を再現してみようと、上記設定をした後に表示方法をカラムにしたりギャラリーにしたり閉じたり開いたりを繰り返していたところ、最終的に不具合はぱったり発生しなくなりました。カラム表示でもサブフォルダが開くんです。よって、原因不明ながら、上記手順で解決、と言ってしまおうと思います。

他に試してダメだったこと (参考まで)

Image by Stable Diffusion

Date:
2024年1月3日 17:56:25

Model:
realisticVision-v20_split-einsum

Size:
512 x 512

Include in Image:
comicbook-style, gray hair guy looking for a missing folder in a book library

Exclude from Image:

Seed:
2520942867

Steps:
20

Guidance Scale:
20.0

Scheduler:
DPM-Solver++

ML Compute Unit:
CPU & Neural Engine

mac でラズパイ起動用 SD カードや USB メモリをまるごとバックアップする簡単な Python スクリプト

ラズパイで使用している SSD が突然読み込めなくなったので、おおよそ1年前にバックアップしてあったディスクイメージを別の SSD にリストアし、諸々1年分を作り直しました。当然すごく面倒だったので、外付けメディアのバックアップを取る極シンプルな mac 用 Python スクリプトを書きました。ターゲットのデバイスを指定すると、バックアップの後イジェクトまでやってくれます。

いちおう、環境

  • macOS: Sonoma 14.2
  • Python: 3.11.6
  • 追加パッケージ等: 不要

普通にやるならこんな手順

mac に記憶デバイスを接続して一覧を表示、ターゲットとなるデバイス番号を確認、デバイスをアンマウントして、バックアップ開始、終了したらイジェクトして、記憶デバイスを物理的に取り外す、というのが一連の手順になります。Ubuntu がインストールされた 128GB SSD をバックアップしたときの例はこんな感じです ($ 以降が入力するコマンド)。

$ diskutil list external
... 省略 ...
/dev/disk4 (external, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *128.0 GB   disk4
   1:             Windows_FAT_32 system-boot             268.4 MB   disk4s1
   2:                      Linux                         127.8 GB   disk4s2
... 省略 ...
$ sudo diskutil unmountDisk /dev/disk4
Password:
Unmount of all volumes on disk4 was successful
$ sudo dd if=/dev/rdisk4 of=RPi3BP_128GB_20231218-1.img bs=6M status=process
127999672320 bytes (128 GB, 119 GiB) copied, 2054 s, 62.3 MB/s
20350+1 records in
20350+1 records out
128035674112 bytes (128 GB, 119 GiB) copied, 2054.98 s, 62.3 MB/s
$ sudo diskutil eject /dev/disk4
Password:
Disk /dev/disk4 ejected

最初のコマンドは接続されている記憶デバイスの一覧を表示するのですが、external を付けて外付けディスクのみ表示させています。複数表示される場合は、表示される SIZE の値を頼りにするか、接続前に一度 diskutil list external を実行し、メディアを接続してから再度 diskutil list external で追加されたメディアの番号を確認します。

dd コマンドの説明:

  • if は、入力ファイルを指定。上の例の /dev/rdisk4 は、シーケンシャルにアクセスされるデバイスを指定しているので、/dev/disk4 とやるより速い
  • of は、出力ファイルを指定。上の例は、コマンドを実行したディレクトリに .img の拡張子を付けて書き出し
  • bs で、一度に処理するブロックサイズを指定。m1 mac mini (16GB RAM) でいくつか値を変えてテストした結果、6M (6メガバイト) が一番安定して速い速度で処理できた
  • status=process を付けると、実行中に進捗状況が表示される

mac 専用 Python スクリプト (とは言ってもほぼシステムコマンドを逐次実行)

import subprocess
import sys
import os
from datetime import datetime

if not os.getuid()==0:
    sys.exit(">> 管理者権限が必要です。'sudo python3 clibackup.py' の形式で再度実行し、パスワードを入力してください。")

subprocess.run(["diskutil", "list", "external"])
dnum = input(">> バックアップする記憶デバイスの番号を数字で入力してください('q' を入力すると終了)。\n>> (例: /dev/disk9 の場合は 9 を入力)\n>> /dev/disk?: ")
if dnum == 'q':
    print('終了しました。')
    sys.exit()
print(f">> ターゲット /dev/disk{dnum} をマウント解除します。")
subprocess.run(["diskutil", "umountDisk", "/dev/rdisk"+dnum])

timestamp = datetime.now().strftime("%Y%m%d%H%M")
#cmd = ["dd", "if=/dev/rdisk"+dnum, f"of=/Volumes/External HDD/SD_Card_Backup/backup_{timestamp}.img", "bs=6M", "status=progress"]
cmd = ["dd", "if=/dev/rdisk"+dnum, f"of=backup_{timestamp}.img", "bs=6M", "status=progress"]

print("\n>> 以下のコマンドを実行してバックアップします:")
for i in range(len(cmd)):
    print(cmd[i], end = " ")
print("\n")

process = subprocess.run(cmd)

print(f"\n>> バックアップが完了しました。デバイス /dev/disk{dnum} をイジェクトします。")
subprocess.run(["diskutil", "eject", "/dev/disk"+dnum])

保存先ディレクトリを指定する場合は、18行目の様に of= の後にパスを入れてください。ダブルクォーテーションでくくってあるので、スペースがあってもエスケープは不要です。作られるディスクイメージのファイル名は、年月日時分を含んだ backup_YYYYmmddHHMM.img になります。実行時の「分」までファイル名に含めているため、既存のファイルを上書きすることは無いでしょう。エラー処理の一切はしていないのであしからず。

使い方

sudo python3 clibackup.py で実行します (sudo を忘れると、メッセージを表示して終了します)。パスワードを入力すると、マウントされている外付け記憶デバイスの一覧が表示されるので、バックアップしたい USB ドライブなり SD カードなりのディスク番号を入力するとバックアップがスタートします。もし対象がわからない場合、一度接続していない状態で本スクリプトを実行し q で終了、その後メディアを接続して再度スクリプトを実行すると、前回は表示されていなかった /dev/disk があるはずなので、その番号を指定します。バックアップが完了すると自動でメディアをイジェクトするので、そのまま mac から取り外してかまいません。

実行例

Raspbian が入った 16GB の micro SD カード (SanDisk Ultra Class 10 A1 MicroSD HC I) を、mac mini の内蔵 SSD にバックアップしたときの実行例です。速度は 34MB/s、大体 8分ほどで完了しています。使用する記憶デバイスや接続の仕方次第で速度は結構違います (本記事頭で使用している SSD は、62.3MB/s)。自分は 2つのパーティションに分けた外付け 4TB HDD があるので、/dev/disk6~8 も表示されていますが、これらも環境によって変わります。

% sudo python clibackup.py 
Password:
/dev/disk4 (external, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *15.9 GB    disk4
   1:             Windows_FAT_16 RECOVERY                1.7 GB     disk4s1
   2:                      Linux                         33.6 MB    disk4s5
   3:             Windows_FAT_32 boot                    72.4 MB    disk4s6
   4:                      Linux                         14.1 GB    disk4s7

/dev/disk6 (external, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                        *4.0 TB     disk6
   1:                 Apple_APFS Container disk8         2.0 TB     disk6s1
   2:                 Apple_APFS Container disk7         2.0 TB     disk6s2

/dev/disk7 (synthesized):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      APFS Container Scheme -                      +2.0 TB     disk7
                                 Physical Store disk6s2
   1:                APFS Volume External HDD            1.4 TB     disk7s1

/dev/disk8 (synthesized):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      APFS Container Scheme -                      +2.0 TB     disk8
                                 Physical Store disk6s1
   1:                APFS Volume handsome's mac mini...  673.7 GB   disk8s1
   2:                APFS Volume Buffalo HDD             1.0 TB     disk8s2

>> バックアップする記憶デバイスの番号を数字で入力してください('q' を入力すると終了)。
>> (例: /dev/disk9 の場合は 9 を入力)
>> /dev/disk?: 4
>> ターゲット /dev/disk4 をマウント解除します。
Unmount of all volumes on disk4 was successful

>> 以下のコマンドを実行してバックアップします:
dd if=/dev/rdisk4 of=backup_202312231503.img bs=6M status=progress 

  15904800768 bytes (16 GB, 15 GiB) transferred 474.073s, 34 MB/s   
2532+1 records in
2532+1 records out
15931539456 bytes transferred in 474.873141 secs (33549043 bytes/sec)

>> バックアップが完了しました。デバイス /dev/disk4 をイジェクトします。
Disk /dev/disk4 ejected

記憶デバイスへの書き込み

スクリプトを書いていないので、手動でいくつかコマンドを打ってください。手順は本記事最初のサンプルと同様ですが、入出力が逆になります。dd コマンドでは、if= (入力ファイル) にディスクイメージ、of= (出力ファイル) に USB や SD カードなどの /dev/rdiskディスク番号 を指定します。出力先を間違うと取り返しがつかないことになり得るので、注意してください (サンプル通りに実行して大事なデータが消えてしまっても当方では責任とれません)。以下は、/dev/disk4 に SD カードがマウントされているときの例です。メディアを抜いた状態で diskutil list external を一度実行し、メディアをさしてから再度実行すれば、増えた /dev/disk が該当の記憶デバイスになります。

(記憶デバイスを接続していない状態で実行)
$ diskutil list external

(記憶デバイスを接続して再度実行)
$ diskutil list external
... 省略 ...
/dev/disk4 (external, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *15.9 GB    disk4
   1:             Windows_FAT_16 RECOVERY                1.7 GB     disk4s1
   2:                      Linux                         33.6 MB    disk4s5
   3:             Windows_FAT_32 boot                    72.4 MB    disk4s6
   4:                      Linux                         14.1 GB    disk4s7
... 省略 ...
$ sudo diskutil unmountDisk /dev/disk4
Password:
Unmount of all volumes on disk4 was successful
$ sudo dd if=backup_202312231503.img of=/dev/rdisk4 bs=6M status=process

Image by Stable Diffusion

Date:
2023年12月23日 16:42:10

Model:
fruity-mix_split-einsum_compiled

Size:
512 x 512

Include in Image:
comicbook-style, cloned sheep standing side by side

Exclude from Image:

Seed:
3723203146

Steps:
25

Guidance Scale:
20.0

Scheduler:
DPM-Solver++

ML Compute Unit:
CPU & Neural Engine

Intel iMac から M1 Mac mini へ移行した記録 (読み物編)

娘のパソコンを iMac (Retina 4K, 21.5-inch, Late 2015) から Mac mini (M1, 2020) にしました。スペックや実際の移行方法など難しい話は別記事にします。こちらでは、読み物的な内容をまとめています。パソコン初心者 (自分の親や子供) にパソコンを買ってあげる、自分のパソコンの知識に自信は無いがなんか満足度が高いのが欲しい、昔から Mac っていいよなーって思っている、そんな方に読んでもらえるように書きました。

子供の iMac がくたびれてきた

小学生の娘は妻のお下がりの iMac を使っていたのですが、さすがに 6年も使っているとモニタの色味がおかしくなってきました。特に左右の端がピンクがかってきて、これは良くないな、と。娘が、新しいパソコンを買ってもらった友達と比べて自分のマイクラ動画はカクカクしている、とこぼすこともよく聞くように。さらに言えば、最近はなぜか Time Machine も上手にバックアップをとれなくなってきたし、Fusion Drive も単品 SSD と比較したらやっぱり遅い。そろそろ替え時かな、という思いが自分の中で大きくなってきました。

これまでは年代物で親のお古の Mac を 2台使わせていましたが、実際のところ一日の中で Mac を使う時間は家族で一番長いのです。主な用途のイラスト描きや YouTube 視聴は問題無いものの、マイクラにシェーダーやリソースパックを入れて Replay Mod で記録しながら QuickTime で録画するとか、編集した動画を書き出すとか、実は親よりもパソコンの性能を要求する使い方をしているのも事実。誕生日まで 3ヶ月ほどあるものの、安く手に入れられればそのタイミングで新しい Mac を買い与えることにしました。

Windows PC にしなかった理由

初期の動画は iMove を使っていた娘ですが、無料で高機能な DaVinci Resolve を少しかじり、現在はゆっくりムービーメーカー4 で YouTube 動画を作っています。同アプリは Windows 10 & 11 用のフリーウェアで、有料アプリの Paralles Desktop バージョン 16 に Windows 10 Home 環境を作り、その上で動作させています。他にもいくつか Windows 専用アプリを使用しているため、移行先も Windows が動くコンピュータである必要があります。娘の仲の良いお友達はみんな Windows PC ユーザです。親であるボクは完全 Windows 環境の会社の IT マネージャをやっているので、Windows PC という選択肢も無いわけでは無い。しかしそうしませんでした。いくつかある理由の一つは、娘が Windows OS の見た目をダサいと思っていて、macOS の方が良いと思っていたから。そしてもう一つは、娘以上にボクがそう思っているから。と、まぁダサいナウいの話は置いておいても、あまたある Windows PC の筐体やスペック、価格、サポート体制、評判などを調べ上げて最適なものを選定し、導入後は自宅でも Windows PC のサポートをしなければならなくなる、ということを考えると全く気分が乗らなかったからです。そんな自分を正当化するために、2週間ほどは Intel PC vs M1 Mac 的な YouTube を見まくりました。そして、性能、価格、発熱量、先進性などという要素を元に、Apple Silicon (M1 チップ) 搭載 Mac が最適であるという結論に至ったのです。Parallels Dekstop は早々に M1 チップ対応を謳っていましたし、逆に Windows で Mac 用アプリを動かすソリューションは知りません。

Parallels サイトより。確かに。

Mac mini M1 にした理由

Windows アプリを使用するための互換性から Intel Mac も考えなかったわけでは無いですが、Apple のラインナップからなくなることがわかっている製品を購入するよりも、これから各メーカさん・開発会社さん・OSS 開発者さん達による対応が進んでいくであろう M1 チップを搭載したコンピュータを子供に与えることに、何らかの意味があるように思えました。またちょうどこの頃 (2022年 2月)、Apple が近々発表する新製品の噂が各方面で記事にされていました。話半分で探ってみたところ、どうやらパワフルなものが中心になりそうで、Mac mini M1 がモデルチェンジしそうな感じもありません。歴代 Mac mini もモデルチェンジのが頻度が低かったですし、新機種登場による値下がりも期待薄です。また、娘は液晶タブレットを使ってイラストを描いているため、Macbook Air や Pro ではメインモニタが小さくて不便です (使っていた iMac は 21.5 インチ)。小学生なので持ち運んでドヤることもありません。こういった事から、本体は Mac mini M1 搭載モデルで決定しました。…といいつつ、当初は iMac M1 を第一候補として USB ポートのやりくりや設置場所の検討をしていました。娘が選ぶのは紫かな、水色かな、なんて思いながら必要な機器の金額を足していくと、どうしてもトータルが 27万円を超えることに。さすがにその金額はきびしいぞ、と。で、必要なハードウェア構成を考えてみると、キーボードとマウスは持っているし、使用中の iMac の 21.5インチ Retina 4K モニタは解像度を半分にしているので高解像度は必要ない。音は常にヘッドフォンで聴いているし、調べてみると Vtube 系アプリの顔認識には 720p のウェブカメラで十分らしい。さらに、会社の福利厚生の一環であるベネフィット・ステーションの Apple Store 割引は iMac M1 には使えないが、Mac mini M1 は対象ということが判明 (Apple Store に電話して聞きました)。ボクが使っている Dell の QHD 解像度モニタ なら 3万円台なので、トータルで 20万円かからない。ということもあり、Mac mini M1 となった次第です。

細かいことを知りたい人へ

もうちょっと細かいスペックも書いておくと、メモリは 16GB にしています。Parallels で Windows も走らせるので、16GB は必須です。SSD (ストレージ) は 1TB にしました。2TB は高すぎるし、動画を多く作る娘の用途では 512GB じゃ小さすぎます。安い外付けの SSD を追加するという使い方はある程度のコンピュータに関するリテラシや整理整頓スキルが要求されるので、現段階の娘には若干ハードルが高く、サポート (ボク自身の稼働) の頻度を下げるためにも 1TB としました。本体とは別になりますが、Time Machine によるバックアップ用に 2TB の外付け HDD を接続しています。コンピュータに詳しくない人がトラブルに遭う頻度と遭ってしまったときの損失を下げるための投資ですね。同じような理由で、キーボードとマウスも USB の有線接続です。原因不明の周辺機器認識しない問題に時間をとられないためには、有線接続の機器をお勧めします。

使用アプリの互換性を確認する

パソコンを新しくすると決めてから、娘が普段使ってるアプリの情報をもらいました。中身としては、ゲーム、イラスト関連、動画編集関連がほとんどで、Mac と Windows それぞれで使用しています。今の時代 Mac 専用のアプリは Apple 謹製のものくらいで、上でも書いた『ゆっくりムービーメーカー4』のような個人開発のアプリ (フリーウェア) や、大作ゲームはほぼ Windows 専用です。Mac 用アプリの動作状況を調べたところ、M1 チップに最適化されたもの、Intel 用に作られたものを M1 でエミュレーションして動作させるものの他、全く動かないものもあるということがわかりました。動かないものは 2つで、マイクラをリモートで一緒に遊ぶために使用していた VPN ソフトの Hamachi と、Mac から発せられる音とマイクから入力する音を同時に録音するために使用していた Sound Flower というツールでした。それぞれ代わりになるツールの存在が確認できたのでよしとし、実際に新しい Mac を購入してからインストールすることにします。

目的の再確認

今更ながら今回のプロジェクトの目的は、Mac 用マイクラ Java版をなめらかに動かすということにつきます。それ以外は親の都合やエゴと言って差し支えありません。で、肝心のマイクラ (正式名称: MINECRAFT JAVA EDITION) は M1 に対応しているのかというと…、未対応でした。確かに自分の Mac mini M1 で実行すると、Intel 用アプリとして動作しています。しかし調べてみると、MultiMC というランチャーを使い、Arm 用の Java を使用することで M1 ネイティブ動作させられることがわかりました。マイクラは Java という実行環境で動作するアプリのため、M1 用の Java で実行すれば良い、ということです。導入方法などの詳細は別記事にしようと思いますが、マイクラがぬるぬる動くということがわかったので、M1 搭載 Mac という選択肢は揺るぎないものになりました。その他のアプリ含め、動作状況は以下の通りです。

アプリ用途対応状況メモ
Minecraft Java Editionゲーム、建築、友達とマルチプレイ、YouTube 動画のネタ公式のままだと Intel のみ。MultiMC と macOS Arm 版 Java の利用でネイティブ有料。公式をダウンロードして遊んでも、全然大丈夫だが、M1 の恩恵は小さい
MultiMCマイクラを M1 ネイティブで動作させるM1 ネイティブ無料。これまで作ってきたマップや Mod、シェーダ (影 mod) も動く。設定はやや面倒
Robloxゲーム、友達と遊ぶ、チャット、動画のネタIntel無料。実際に試したが、十分遊べる
QuickTime Player画面収録 (録画)M1 ネイティブ無料。そりゃあ Apple 謹製ですから。macOS に同梱
OBS画面収録 (録画)まだ Intel無料。M1 対応版も開発中
Clip Studio Paint Pro (クリスタ)イラストM1 ネイティブ有料。amazon で購入
Live 2D Cubism Editor2D のイラストを Vtube アプリで動かせるようにするIntel無料版もありますが機能が限定的なため、我が家では学割を利用。小学生も在籍証明書を学校から発行してもらえば学割が利用できる
Parallels Desktop 16Windows を実行M1 ネイティブ (バージョン 16 以上)有料。ちょいちょいやってるセールがお勧め
Hamachiマイクラなどを友達と接続動作せず無料。VPN を簡単に構築できるソフト
ZeroTier同上M1 ネイティブ無料。オープンソース。Hamachi の置き換えに
Sound FlowerMac とマイクの同時録音のため動作せず無料。地声での実況系動画には必須だった
BlackHole同上M1 ネイティブ無料だがダウンロードには登録が必要。SoundFlower の置き換えに
Ladio CastMac とマイクの同時録音Intel無料。BlackHole と併せて使うことで、複数の音声入力を一つの出力先にまとめられる
Zoom友達との遠隔マルチプレイ中の音声チャット (映像も)M1 ネイティブ無料でも使える。1対1なら時間無制限。複数接続なら 1時間限定
Norton360アンチウィルス、統合セキュリティIntel有料。マイクラダウンロードサイトは怪しいところもあり、アンチウィルス系ソフトは必須
Wacom driver液晶タブレット Cintiq 13HD を使えるようにするM1 ネイティブ無料。妻が 1年ほど前に試したときは動きませんでしたが、今は大丈夫
Steamゲーム、Vtuber 的利用の FaceRigよくわからんSteam 自体は無料。FaceRig の必要性が無くたったため詳しく調べていない
KalidofaceLive 2D で作ったアバターをカメラから表情認識して動かすブラウザ依存?無料のウェブサービス。まばたきや顔の向きに合わせて動かすことができる
ffmpegマイクラの Replay Mod の録画をムービーとして書き出すM1 ネイティブ無料。Xcode と homebrew のインストールが必要

Windows 10 か Windows 11 か。Insider Preview しか選択肢は無いのか

Windows で絶対に動作させたいアプリは、ゆっくりムービーメーカー4とマイクラ統合版です。そのために動員する手持ちの有料アプリ (と OS) は、Parallels Desktop 16、Windows 10 Home、Minecraft Java 版、Minecraft Windows 統合版。できれば追加投資無しで、全てを使いたい。調べた結果は以下となりました。結論としては、Parallels Desktop は、バージョン 16を持っていればアップグレードの必要は無く、Windows は 11 Home の通常版をインストールでき、Windows 10 Home のキーでアクティベーションでき、マイクラ Java 版以外の使いたいアプリは動くことが確認できました。以下はそのまとめです。

アプリ用途対応状況メモ
Parallels Desktop 16Windows を実行M1 ネイティブ (バージョン 16 以上)ちょいちょいやってるセールがお勧め
Windows 10 HomeWindows アプリを実行マイクロソフトによる Arm 版 Windows 10 の提供が停止される←は Parallels のこちらを参照
Windows 11 HomeWindows アプリを実行Inside Preview だけじゃ無く、通常版も Parallels にインストールできる手持ちの Windows 10 Home のライセンスキーが使えるので追加投資不要
Minecraft Java 版Windows 専用 Mod を実行テストした限り、動かず…MultiMC で Arm 版 Windows 用 Java を使えばもしかして?
Minecraft 統合版Switch 版などとも遊べるArm 版が Microsoft Store からインストールして遊べた娘はマウスの動きが速すぎると言って、Windows とアプリ両方でマウスの感度を下げていました
ゆっくりムービーメーカー4YouTube 動画の作成Arm 版 Windows 11 で動いたArm 版 Win 10 での動作報告はいくつかありますが、Win 11 でも動作しました
UTAUボーカロイド的に音声合成で歌わせるArm 版 Windows 11 で動いた特に速くはなっていない感じ

Windows アプリの動作確認には M1 アプリ対応の確認よりも時間がかかりました。理由の一つは、日本のフリーウェアを M1 Mac + Parallels Desktop で動作させているような人の絶対数がそもそも少ないということ。情報が無いので、試してみるしか無い。それともう一つは、Arm 版 Windows 10 では x64 エミュレーションが行えなくなるような記事が見つかったことに起因します (ソースはこのあたり)。これはつまり、Arm 版 Windows 10 では Intel (x86 や x64) アプリが動かなくなるということです。マイクロソフトさんはその対応策として、Windows 11 へのアップグレードを推奨、ですって。さらにこちらの情報では Arm 版 Windows 10 の提供が停止されると言うことで、どのみち Windows 11 にする必要があるみたいでした。Windows エコシステムの良さは、古いアプリが新しい OS でもそのまま動くというところにあるとはいえ、Windows 11 の、しかも Arm 版でこれまでのアプリが動くのか。これは試してみないとわかりませんでした。結果は上の表の通り。マイクラ Java 版は Mac で使えるのでヨシということで娘には納得してもらいました。

アウトかグレーか半分か

ところで、Windows 11 と M1 Mac という点で調査を進めたところ面白いことがわかりました。おおっぴらにそのことに触れている大手メディア系サイトでは見つけられませんでしたが、通常版として使える Windows 11UUP dump というサイト手に入るのです。しかも Windows 10 の同じエディションのキーで本当の Windows としてアクティベーションできる。Parallels Desktop のインストール手順ページ含め大手のメディアでは Insider Preview 版 (要するに評価版) の Windows 11 をインストールできますよーとは表記していますが、評価版を娘に使わせ続けるのはトラブルがありそうで不安です。しかし通常版が使えるのであれば安心感は大きく向上します。UUP dump では海賊版の OS を配布しているわけでは無く、Microsoft 社が過去に公開したイメージをダウンロードし、インストール可能な ISO や VHDX 形式に変換するツールを提供しているので安心感があります。さらに、ボクと娘が持っている Parallels Desktop のバージョンは一つ前の 16 なのですが、16.5 以上であれば Windows 11 のインストールもできると。極めつけに、Yahoo ストアでは、Windows 10 や 11 のアクティベーションキーが数百円で買えることもわかりました。さすがに普通に amazon で買うと 1万円以上する OS が、メディア無しで文字列だけのアクティベーションキーまたはオンラインコードだけになったからと言って数百円になるというのはグレーかアウトか判断できません。不安な方は避けた方がいいと思います。ボクはというと、自己責任、怖いもの見たさ、ダメもと、人柱上等で 500円ほどの Windows 11 Home のキーを買ってみました。結果、Windows 11 のアクティベーションはできて、一ヶ月以上使えています。このあたりは覚悟のある方のみ試されたらいいと思います。

さて、やっと買う直前のフェーズに移行。とにかく安くしたい

使いたいアプリは M1 Mac でも、その上で動く Windows でも動作すると言うことが確認できたので、後は Mac 含めたハードウェアを安くという段階に入りました。Mac 以外に必須なのは、モニタ、キーボード、マウス、さらに我が家では web カメラとなります。特にこだわらない限り Mac 以外で高価なのはモニタですので、ここを安く抑えることがキーになります。だからといって値段だけで判断すると、品質や使い勝手で不満が出て、結局はより良いものを買い直すことになったりする危険もあるので、良い製品を安く買うということが重要になります。我が家では機能と価格のバランスが高い Dell モニタにすることにしました。ボクと同じモデルを探したのですが販売終了となっており、純粋な後継機はありません。QHD (解像度 2560×1440) で画面サイズは 23〜27インチ、USB Type-C 接続ができ、USB ハブとしても使える機種で探したところ、S2722DC が見つかりました。M1 Mac シリーズは USB ポートの少なさがネックなので、モニタがハブになっていくつかの USB 機器をつなげられるというのはコストカットになります。ボクの P2421DC と比較すると、解像度は同じなのに画面サイズが大きく、USB ハブとして使えるポートが 2つに減っていましたが、設置面積に問題は無く、評価を見るとP2421DC で問題のあった USB ハブ機能を使うためにケーブルの抜き差しが必要という問題も解消されているようで、こちらに決定です。Dell 製品は、納期に納得できれば直販サイトの価格が一番安いことが多いです (ビジネス向けしかり)。3月後半現在の価格は 46,536円になってしまっていますが、ボクは Dell 直販 20% オフの 34,110 円で購入し、納期は 2週間でした。同時期の amazon の価格は、2,000円割引が効いても 38,426円。プライムセールでも変化無しでした。ネットにはもっと安く買えたという情報もありますが、今は半導体不足による品不足や納期問題もあるようですし、35,000円程度で購入できれば十分いいところでしょう。間違って他のモデルを買ってしまわないよう、常にブラウザの左端のタブにページを開いておきました。

割引の効率

さて、最重要アイテムであり、最も高価でその分割引効果が一番大きいのはなんと言っても Mac mini M1 です。もちろんこれを買わなければ意味がありません。で、ベネフィット・ステーションの 3% 割引を使おうと思いつつ数日情報収集していたところ、とある方が YouTube で整備済製品の Mac mini を購入したとおっしゃっているのを聞きました。スペックはボクが選定した構成そのものです。これは盲点でした。amazon 含め、Apple Store 以外のショップではいわゆる吊るしモデルしか手に入らないのが普通です (「吊し、つるし」とはハンガーにつるされた洋服を買うように、自分好みにカスタマイズしない既製品を言います)。ボクはこれまで何年も、メモリやストレージのサイズを変更し、キーボードを US 配列にするというカスタマイズをしていたので Mac を整備済製品で入手するという考えに至りませんでしたが、確かに整備済製品は吊しとは違います (詳細は Apple 社のページ参照)。16GB メモリで 1TB の Mac mini M1 の筐体に傷が付いていたら、筐体だけ交換して販売されることもあるでしょう。というわけでその日から、日に数回 Mac整備済製品ページを覗きに行く日々が続きました。最初の頃は黒い Intel Mac mini や Intel Macbook Pro の大きな液晶モデルが現れては消えてという状況です。果たして娘の誕生日までに現れるのか、ま、最悪ベネフィット・ステーションもあるし、なんて思いながら 2週間ほど経ったある日の夕方、突然 iMac M1 が大量に現れました。多分全ての構成がそろっていて、カラーもいろいろ。もちろん 16GB メモリ 1TB SSD という構成のものもあり、パープルは無いけどブルーがある。じっくり見るとブルーの方が娘の好みに近い。キーボードは何が付いてるかわからないけど、どうせ今使っているものを使い続けるからどっちでもええわ。うーん、これ明日の朝には全部無くなってるかもしらん。今頃嗅覚の鋭い人たちや独立系メディアは祭りをやってるな。世の中がスペックやカラーに目移りしている間に絞り込みがほぼ終わっているオレがこのレースを制してやるぜ!

そして購入へ

整備済製品ページで iMac を見つけた時間に救われました。しらふの状態で十分冷静に検討することができたので。安くなっているとはいえ iMac M1 は 20万円を超えており、液晶タブレットを HDMI 接続するにはプラス 8千円ほどのアダプタも必要になります。キーボードなど、娘の用途には不要なものが多くその分高い。突然の出会いに胸が躍ったのは確かでしたが、見送ることにしました。我が家に向かい入れることはできない。その後 3日ほどかけて徐々に iMac は新しいユーザに買われて行きました。ひょっとしたら、いっせいに咲いた綺麗な花が徐々に枯れていくようだったかもしれません。しかしボクには確信めいた予感がありました。次に整備工場から出てくるのは Mac mini M1 に違いないという予感が…。

もったいぶりました

結果、狙ったとおりの Mac mini M1 16GB 1TB をポチれたのは、その数日後のことでした。確定するまで間違いが無いように何度もスペックを見直したのでずいぶん時間がかかりましたが、その間に売り切れることも無く、無事購入することができました。その流れで Dell のモニタを直販サイトでポチり、amazon ではお手頃な web カメラと念のための USB ハブをポチり、怒濤の勢いで購入作業を終わらせました。満足感と充実感。勝った感とやりきった感。Mac のリプレイスを決断してから 1ヶ月以上かかったと思いますが、機種の選定から購入までの一連のアクションがここに完結しました。

感想という体裁の余計な話

実際にモノが届いて接続したり、アプリのインストールや動作テストやデータの移行をしたり、そのつど娘の反応を見たり、というのがこの先続くわけですが、それら技術的にもまーまー細かい話は別の記事にまとめようと思います。なので、購入までの感想を書いてこちらの記事は終わりにします。

良かったこと

娘が親のおさがりじゃ無い現行の Mac を手に入れてからまだ 1ヶ月もたっていないのですが、忖度されないように極力さりげなく感想を聞いてみると「マイクラにキレイな影 Mod とリソースパック入れて録画してもぬるぬる動くようになったから最高」という反応が返ってきて安心しました。使う本人が最優先と思いながらも、実際は親の都合や趣味が多分に含まれたチョイスになっているわけで、娘が望んでいたことが実現できたのは素直にうれしい気持ちです。当初 Mac か Windows かで調査した時は、M1 がいくら速いと言ったって上位モデルの Intel/AMD CPU に性能はかなわないし、そもそもゲームが全然無いじゃないか、という趣旨の情報を多く目にしました。多くの Windows 専用アプリは Parallels で動くのですが、ハードウェアを酷使するゲームは動かないか、動いてもカクカクで遊べたものじゃ無いということです。ま、それはそうなんでしょうが、ファミコン世代・8 bit パソコン世代のボクにしてみれば、ゲームはゲーム機で遊ぶものであり、パソコンは何かを作る、実現するモノなんです。娘もゲームにはそれほど執着していないので問題ありませんでした。むしろ今後ゲームに傾倒することがあるとしても、Mac に向かっている間はゲームばかりにはならないということでもあり、クリエイティビティを伸ばしていきたい親としては好都合です。自分との違いも感じました。女の子だからなのかコンピュータのアーキテクチャやプログラミングに対する興味はおろか、カスタマイズして使いこなしたいという欲求も無く、最新の M1 チップ搭載!ということは娘には全く価値がないようです。でも、単に速いだけのパソコンというのとは違う、未来に期待できる M1 Mac を早い段階で与えられたことは、彼女のステキな未来に役立ってくれそうな、そんなワクワク感もボクに与えてくれています。ジョブズがこの世を去り、アイブは会社を去りましたが、M1 を始めとした Apple Silicon は、Apple 製品やコンピュータの世界を楽しいものにしてくれました。

結局どっちを勧めるか

お子さんをプロゲーマにさせてあげたい、ゲーム実況のスキルを伸ばしてあげたい、一緒に PC ゲームをしたい、という方は Windows の走る、Intel/AMD 搭載ゲーミング PC がいいと思います。もしそうではなく、上に書いた感想に共感できる方には、M1 を搭載したどれかの Mac をお勧めします。入門者向けには安いモデルでいいでしょう。まさに Mac mini は最適です。Macbook Air や Pro、iMac といった一体型は、液晶が壊れたら全て修理でその間は使えません。Mac mini なら本体さえ壊れなければ、その場しのぎの液晶モニタを買ってでも使い続けることができます。逆に、主な用途が変わればモニタだけ良いものに変えるだけで環境をアップグレードできるのです。モヤモヤするなら今日から整備済製品ページに足繁く通いましょう。

これも言っておきたい

最後に毒をひとつ。知らない情報や自分の想定を裏付ける・もしくは間違いだとわからせてくれる情報をブログや YouTube で入手しようと結構な時間を使いましたが、クソのような情報の多さに閉口しました。どこかから持ってきた情報を切り貼りして、スカスカの文章のあげくに「いかがでしたか」とまとめるフォーマットが多いですがその中には、ボクの記事をほぼ丸パクリしたものさえありました。日本の社会や人付き合いになじめず、ブログや YouTube で生活費を稼いで自立できたという人達には素直に良かったねという思いはあります。しかし、生活費を稼ぐためだけのペラッペラのクソ記事に自分の貴重な時間を使うことには納得できません。量産型クソブロガーや超薄型 YouTuber によってインターネットがウ○コ地帯になることはガマンなりません。少しでも多くのお父さんお母さんおばあちゃんおじいちゃんお姉ちゃんお兄ちゃんには、この世を、インターネットをより良いものにしていってくれる若い世代の育成にご尽力いただけたらと切に願うしだいでございます。また、日本語のソースだけでは偏りがあり、欲しいものにたどり着けない可能性が高いです。痛感しました。正しい判断をするには十分な情報が必要です。英語で、少なくとも読み書きができるスキルは必須であると思って間違いありません。TOEIC なら 750点程度です。苦手とか言っていたら世界から置いて行かれます。愛する人もしくは愛したい人がいるなら、とにかく英語スキルを磨いていきましょう。マイクラの mod や配布マップを導入するにも、英語は必須なのです。

ここまでお読みいただきありがとうございました。あなたの、そして未来を作る誰かのお役に立てたら幸いです。

Mac mini (M1, 2020) で Dell モニタの USB ポートが使えない、画面が表示されない時のワークアラウンド

Dell モニタに 20cm の USB-C の延長ケーブルを接続

Mac mini (M1, 2020) を購入した

いろんな方がレビューしてるように、パフォーマンスは素晴らしいです。アプリケーションも、Intel CPU 用機能拡張や VPN (具体的には LogMeIn HAMACHI) などシステムの深いところに食い込んだものは動かないケースが多いようですが、それ以外は概ね想定通りで、すごく満足しています。各デベロッパの対応も早く、毎週のようにサポートされるアプリケーションが増えるのを見られるのも楽しいですね。

Dell モニタの画面が映らない、USB ポートが使えない

しかし、メインで使用している Dell のモニタ P2421DC に USB-C で接続しながら同モニタの USB ポートをハブとして使用するにはコツがいることがわかりました。モニタに電源が入っている状態で Mac mini を起動すると、USB ハブにつながったキーボードから入力できないのです。映像は出力されています。Dell Latitude 5310 や XPS では当然全く発生しません。あまり気にしていませんでしたが、そういえば MacBook Air (Retina, 13-inch, 2019) でも USB-C ケーブルを抜き差ししていたことがあるので、Mac 起動時の USB 機器の接続フローと Dell モニタ側のそれがミスマッチしているように思えます。

ともあれ、日本語ではあまり同様の症状や解決策が無いようなので、投稿しておきます。

環境:

不具合の内容:

macOS 起動後、UCB-C ケーブルで接続された メインモニタのポート USB 3.0 x2 (側面)、USB 2.0 x2 (背面) に接続した機器を認識しない。キーボードやマウスからの入力も、iPad 等への給電も行えない。

下記ワークアラウンドを実行し、キーボードやマウスが使えるようになった後も、スクリーンロックしてしばらくした後にログイン使用とすると、キーボードからパスワードの入力はできるが画面がメインモニタに表示されないこともある。

不具合の再現方法:

モニタの電源が入っている (スタンバイ) 状態 で、Mac mini の電源を投入する。macOS 起動途中から画面は表示されるが、USB 機器は使用できない。

対処方法・ワークアラウンド:

Mac の起動前にできること:

Mac の電源を入れる前に、モニタの電源を切っておく。ボクの場合は HDMI 接続のモニタもあるため、そちらでパスワード入力画面が表示されてからメインモニタの電源を入れる。ごめんなさい、テストしているときはこの方法が有効だった (と少なくとも思う) のですが、最近はなぜか全くうまくいきません。以下をお試しください。

Mac の起動後にできること:

モニタとつながっている USB-C ケーブルを抜き差しします。OS が起動してしまった後では、モニタの電源 off/on では接続されません。Mac mini 背面からケーブルを抜き、モニタがスタンバイ状態に戻ったら再接続します。

ボクは少しでも簡単にケーブルの抜き差しができるようにと、USB 3.1 Gen2 規格に対応した延長ケーブルを amazon で購入しました。本投稿トップの画像のように、モニタ側にこのケーブルを挿し、画面に表示されなかったりキーボードが効かないときに矢印の部分を抜き差ししています。スイッチ付きの USB-C ケーブルを探したが信頼できそうな (Thunderbolt として使えそうな) ものが見つかりませんでした。Mac mini 本体のポートへのケーブル抜き差しに抵抗がある、設置場所的に無理という方はこちらをお勧めします。

今後の展望:

Intel チップの MacBook Air (macOS 10.15 Catalina -> 11.1 Big Sur) でも発生しますが、本体のキーボードでログインはできるし、USB-C ケーブルの抜き差しもたいした手間じゃ無い、ということであまり気にしていませんでした。設置場所によっては Mac mini のケーブル抜き差しは面倒なので、当面は Dell のモニタの電源を切る癖を付けるしかなさそう。Dell は2021年初頭までにMacバージョンのDell Display Managerを提供する (引用元) と言っているで、近い将来解決してくれると願いたいです。

© Peddals.com