小型PCクラスタを用いた並列計算環境の構築

--- 

(必要に応じて、文脈や詳細情報があればさらに自然な表現へ調整できます。)

2026/01/22 4:08

小型PCクラスタを用いた並列計算環境の構築 --- (必要に応じて、文脈や詳細情報があればさらに自然な表現へ調整できます。)

RSS: https://news.ycombinator.com/rss

要約

日本語訳:

小規模なクラスターとして、Ubuntu Serverを動作させた中古のLenovo M715q Tiny PC(数台)を組み合わせて、Rベースの並列シミュレーション実験を行いました。著者はルーター経由でノードに静的IP(192.168.1.101–103)を設定し、パスワード不要SSH(必要ならsudoも)を有効化しました。

ssh
コマンドをループさせることで、R(r‑base, r‑base‑dev)がすべての機械にインストールされました。

テンプレート化されたスクリプト

par_test_script.R
は複数のパッケージ―
future
,
future.apply
,
dplyr
,
SuperLearner
,
ranger
,
xgboost
, および
glmnet
―を読み込み、マルチコア計画を設定し、シミュレートデータを生成して
future_lapply
で並列に TMLE イテレーションを実行します。このスクリプトは、1–1000 のイテレーション範囲を三つのチャンク(例:1–334, 335–667, 668–1000)に分割し
sed
/
scp
で各ノードへ配布し、その後デタッチド tmux セッション内で実行され、Rscript が無人で動作します。

実行後、すべてのノードから出力ファイルを

scp
経由で収集・結合し、異なる SuperLearner ライブラリ組み合わせに対して 5‑fold と 10‑fold クロスバリデーションでバイアス、分散、およびカバレッジを算出します。実行時間の結果は、単一ノードが約4時間かかるのに対し、三ノードクラスターでは1.4–2.5時間で完了することを示しています。また、チューニングされていない xgboost+LR のスケーリングは 0.47 時間から 0.17 時間へと改善しました。CV‑5 から CV‑10 に移行するとバイアスが減少し分散が増加しますが、ほとんどの手法でカバレッジは同程度に保たれます。ただし、GAM+LR は高い非対称バイアス/カバレッジを示しました。

ワークフローは

future.seed
によって再現可能であり、学んだ教訓には
sprintf
system
の効果的な使用、マルチコアファューチャーでも
set.seed
が機能することの確認、および成功したマルチノードパッケージインストールパイプラインの構築が含まれます。将来の改善提案としては、インストールの並列化、メール通知や進捗推定の追加、未完了イテレーションの再分配、OpenMPI の探索、および設定をプロジェクト間で再利用できるようにパッケージ化することが挙げられます。

この低コストで詳細に文書化されたクラスターは、研究者やデータサイエンスチームが高価なハードウェアなしで統計モデルを迅速にプロトタイピングできるようにします。

本文

小さなパソコンでクラスタを構築して並列計算のプロセスを学ぶことができて楽しかったです。
Ubuntu のインストール、パスワードレス SSH、ノード間でのパッケージ自動化、R シミュレーションの分散実行、および CV5 と CV10 での性能比較に関するメモを書き留めました。面白いプロジェクトです!


動機

今年の目標の一つは、並列計算をさらに深掘りし、複数デバイスへシミュレーションワークロードを分散させることです。
ノートパソコンで何日もかけて実行されるシミュレーションは非効率的です。
クラウドサービスを試したものの、分散コアがどのように管理・最適化されているのか明確に理解できませんでした。
このプロジェクトでは、安価な「tiny PC」(例:中古 Lenovo M715q)で自前のクラスターを構築し、実際に手を動かす経験を得ます。


目的

  1. ハードウェア – 適切な PC を選定
  2. 各ノードへ Ubuntu をインストール
  3. 静的 IP とパスワードレス SSH の設定
  4. R のインストールとパッケージ配布を自動化
  5. シミュレーション用のテンプレート R スクリプト作成
  6. 各ノードへシミュレーション作業(開始/停止範囲)を分散
  7. tmux セッション経由で並列実行
  8. 結果収集、ランタイムと性能指標(バイアス・分散・カバレッジ)の比較

1. どの PC を購入するか?

機能的でコストパフォーマンスが高いユニットを選びます。中古 Lenovo M715q Tiny PC が最適です。


2. Ubuntu のインストール

ステップアクション
1Ubuntu Server ISO をダウンロード
2balenaEtcher でブート可能 USB を作成
3PC を起動:F12 → USB 選択(無い場合は BIOS の F1 → Startup タブ → CSM Support 有効化、USB を Primary Boot Priority に設定)
4LAN 接続を確認してスムーズにインストール
5画面のプロンプトに従う(ユーザー名・パスワード)
6再起動し、USB を取り外す

結果:Ubuntu が迅速かつ円滑にインストールされます。


3. IP の固定と設定

ルーターで各ノード用に静的 IP を設定:

192.168.1.101
192.168.1.102
192.168.1.103

IP 設定変更後はノードを再起動します。


4. パスワードレス SSH

# マスターノードで実行
ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa

# 各ノードへキーをコピー(IP とユーザー名は置き換え)
for ip in 192.168.1.{101..103}; do
    ssh-copy-id -i ~/.ssh/id_rsa.pub user@$ip
done

オプションでパスワードレス sudo:

for ip in 192.168.1.{102..103}; do
    ssh -t user@$ip \
        'echo "$(whoami) ALL=(ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/$(whoami)'
done

5. 全ノードへ R をインストール

for host in user@192.168.1.{101..103}; do
    ssh -t $host 'sudo apt update && sudo apt install -y r-base r-base-dev'
done

6. シミュレーション用テンプレート R スクリプト

library(future)
library(future.apply)
library(dplyr)
library(SuperLearner)
library(ranger)
library(xgboost)
library(glmnet)

plan(multicore, workers = 4)   # 各ノードで 4 コア使用

set.seed(1)

# データ生成 ----------------------------------------------------------
n <- 10000
W1 <- rnorm(n); W2 <- rnorm(n)
W3 <- rbinom(n,1,.5); W4 <- rnorm(n)

A  <- rbinom(n,1,plogis(-0.5 + 0.8*W1 + .5*W2^2 + .3*W3 - .4*W1*W2 + .2*W4))
Y  <- rbinom(n,1,plogis(-1 + .2*A + .6*W1 - .4*W2^2 + .5*W3 + .3*W1*W3 + .2*W4^2))

# 真の ATE ------------------------------------------------------------
logit_Y1 <- -1 + .2 + .6*W1 - .4*W2^2 + .5*W3 + .3*W1*W3 + .2*W4^2
logit_Y0 <- -1 + 0    + .6*W1 - .4*W2^2 + .5*W3 + .3*W1*W3 + .2*W4^2

Y1_true <- plogis(logit_Y1)
Y0_true <- plogis(logit_Y0)
true_ATE <- mean(Y1_true - Y0_true)

df <- tibble(W1,W2,W3,W4,A,Y)

# SuperLearner ライブラリ -----------------------------------------------
SL_library <- list(
  c("SL.xgboost","SL.ranger","SL.glm","SL.mean"),
  c("SL.xgboost","SL.ranger"),
  c("SL.xgboost","SL.glm")
)

# TMLE 反復 ---------------------------------------------------------
run_tmle_iteration <- function(seed, df, n_i, SL_lib) {
  set.seed(seed)
  data <- slice_sample(df, n = n_i, replace = TRUE) %>% select(Y,A,W1:W4)
  
  X_outcome   <- data %>% select(A,W1:W4)
  X_treatment <- data %>% select(W1:W4)
  Y_vec       <- data$Y
  A_vec       <- data$A
  
  # Outcome モデル ----------------------------------------------------
  SL_outcome <- SuperLearner(Y = Y_vec, X = X_outcome,
                             family = binomial(), SL.library = SL_lib,
                             cvControl = list(V=5))
  
  outcome      <- predict(SL_outcome, newdata = X_outcome)$pred
  outcome_1    <- predict(SL_outcome, newdata = mutate(X_outcome, A=1))$pred
  outcome_0    <- predict(SL_outcome, newdata = mutate(X_outcome, A=0))$pred
  
  # 予測の境界化 ----------------------------------------------------
  bound <- function(p) pmax(pmin(p,.9999),.0001)
  outcome   <- bound(outcome);   outcome_1 <- bound(outcome_1)
  outcome_0 <- bound(outcome_0)
  
  # Treatment モデル -------------------------------------------------
  SL_treatment <- SuperLearner(Y = A_vec, X = X_treatment,
                               family = binomial(), SL.library = SL_lib,
                               cvControl = list(V=5))
  ps   <- predict(SL_treatment, newdata = X_treatment)$pred
  ps_f <- bound(ps)
  
  # Clever covariate ---------------------------------------------------
  cc  <- ifelse(A_vec==1, 1/ps_f, -1/(1-ps_f))
  
  # Epsilon ------------------------------------------------------------
  eps_model <- glm(Y_vec ~ -1 + offset(qlogis(outcome)) + cc,
                   family = binomial())
  epsilon   <- coef(eps_model)
  
  upd_1 <- plogis(qlogis(outcome_1) + epsilon * (1/ps_f))
  upd_0 <- plogis(qlogis(outcome_0) + epsilon * (-1/(1-ps_f)))
  
  ate  <- mean(upd_1 - upd_0)
  se   <- sqrt(var((Y_vec - ifelse(A_vec==1,upd_1,upd_0))*cc +
                (upd_1-upd_0)-ate) / n_i)
  list(ate=ate,se=se)
}

# 並列実行 -----------------------------------------------------------
n_samples <- 6000
start_end <- 1:1000   # 例:範囲

results <- future_lapply(start_end, function(i){
  run_tmle_iteration(i, df, n_samples, SL_library[[1]])   # 必要に応じてライブラリ変更
})

# 集計 ---------------------------------------------------------------
ate_vec  <- sapply(results, `[[`, "ate")
se_vec   <- sapply(results, `[[`, "se")

summary_tbl <- tibble(iter=start_end, ate=ate_vec, se=se_vec,
                      ci_lower = ate_vec-1.96*se_vec,
                      ci_upper = ate_vec+1.96*se_vec)

# 保存 ---------------------------------------------------------------
dir.create("tmle_results", showWarnings=FALSE)
write_csv(summary_tbl, "tmle_results/summary.csv")

Tip:

SL_library[[1]]
を必要に応じて変更し、ノードの負荷に合わせます。


7. ノードへ作業を分散

# 各ノード用の範囲を決定
total_loop <- 1000
clust_num  <- 3
div_iter   <- total_loop / clust_num
first      <- 1
last       <- div_iter

ranges <- lapply(1:clust_num, function(i){
  if (i == clust_num) paste0(first,":",total_loop)
  else {
    r <- paste0(first,":",round(last))
    first <<- round(first+div_iter)
    last  <<- round(last+div_iter)
    r
  }
})

# プレースホルダを置き換えて SCP
nodes <- c("user@192.168.1.101","user@192.168.1.102","user@192.168.1.103")

for (i in seq_along(nodes)){
  sys_cmd <- sprintf(
    "sed 's/START:END/%s/g' par_test_script.R > par_test_script1.R && \
     scp par_test_script1.R %s:/home/user/par_test_script1.R",
    ranges[[i]], nodes[i])
  system(sys_cmd)
}

8. tmux セッションで Rscript を実行

cluster_name <- "test"

for (node in nodes){
  # 既存セッションを終了(無ければエラーは無視)
  system(sprintf("ssh %s 'tmux kill-session -t %s 2>/dev/null || true'", node, cluster_name))
  
  # デタッチドで新規セッション開始
  system(sprintf("ssh %s 'tmux new-session -d -s %s'", node, cluster_name))
  
  # tmux 内でスクリプト実行し、出力をファイルへリダイレクト
  system(sprintf(
    "ssh %s 'tmux send-keys -t %s \"Rscript par_test_script1.R > result.txt\" ENTER'",
    node, cluster_name))
}

9. 結果収集

library(readr)
df_iter <- tibble()

for (node in nodes){
  for (i in 1:3){   # 各ノードの反復数
    scp_cmd <- sprintf("scp %s:tmle_results/summary.csv summary_%d.csv", node, i)
    system(scp_cmd, intern=TRUE)
    
    tmp <- read_csv(sprintf("summary_%d.csv"))
    df_iter <- bind_rows(df_iter, tmp %>% mutate(node=i))
  }
}

10. 時間と性能の比較

MethodHour_1clus_CV5Hour_3clus_CV5Hour_3clus_CV10
SL.xgboost, SL.ranger, SL.glm, SL.mean4.021.412.52

(詳細表は省略)

バイアス & 分散 (CV5 vs CV10)

MethodBias_3clus_CV5Bias_3clus_CV10Var_3clus_CV5Var_3clus_CV10
SL.xgboost, SL.ranger, SL.glm, SL.mean-0.00077-0.000730.000190.00019

カバレッジ

MethodCoverage_3clus_CV5Coverage_3clus_CV10
SL.xgboost, SL.ranger, SL.glm, SL.mean0.5360.517

主な結論

  • CV フォールド数を増やすとバイアスはわずかに減少しますが、分散は増加します。
  • 調整済み xgboost + glmnet は低いバイアスと適度なカバレッジを示します。
  • GAM + LR は高いカバレッジを持つものの、非対称な尾部が大きく推定量としては不十分です。

改善の余地

  • ワークフローを R パッケージ化(SSH・tmux 管理関数、結果集計)
  • マスターノードでインストール手順を並列化し、プロビジョニング速度向上
  • ノードがバッチ完了したらメール/Slack 通知を送信
  • 残り時間の自動推定と未完了反復の再配分実装
  • より大規模なクラスターには OpenMPI や Spark の検討

学んだこと

  1. sprintf
    +
    system()
    はリモートコマンド実行に強力。
  2. future.seed = 100
    を設定すると並列ランダム抽出が再現性を保つ。
  3. パッケージインストールパイプラインはノード間で効率的にスクリプト化できる。
  4. set.seed()
    future_lapply
    内部で呼び出すと反復ごとの乱数が安定。
  5. CV フォールドの選択はバイアス–分散トレードオフを観察しながら決定するべき。

ご質問やコラボレーションのご希望があれば、遠慮なく以下からどうぞ

  • GitHub | Twitter | Mastodon | BlueSky

楽しい計算ライフを!

同じ日のほかのニュース

一覧に戻る →

2026/01/22 7:54

**危険な PDF を安全な PDF に変換する**

## Japanese Translation: Dangerzone は、潜在的に悪意のある PDF、オフィス文書、および画像を安全な PDF に変換します。変換プロセスは gVisor でサンドボックス化され、PDF は生ピクセルデータから再構築されます。このサンドボックスにはネットワークアクセスがないため、改ざんされたファイルが外部と通信することを防止します。対応フォーマットは PDF、Microsoft Office(.docx/.doc, .xlsx/.xls, .pptx/.ppt)、ODF(.odt, .ods, .odp, .odg)および Hancom HWP(.hwp, .hwpx)です。非対応フォーマットは EPUB、JPEG/JPG、GIF、PNG、SVG、BMP、PNM、PBM、および PPM です。オプションの OCR により、安全な PDF 内にテキストレイヤーを復元でき、圧縮によりファイルサイズが削減されます。 変換後はユーザーが好きなビューアで生成された PDF を開くことができます。Dangerzone 自体はデフォルトで PDF とオフィス文書を安全に開きます。このツールは macOS、Windows、Ubuntu/Debian/Fedora Linux、Qubes OS(ベータ版)、および Tails 上で動作し、macOS/Windows では Docker を、Linux では podman を使用します。Freedom of the Press Foundation と First Look Media により AGPL‑v3 の下でリリースされています。2023 年 12 月のセキュリティ監査では低リスクの所見のみが報告されました。更新はダウンロードページまたはアプリアイコンから利用可能で、Windows/macOS 上では Podman Desktop などのカスタムランタイムを使用できます。Air‑gapped 環境向けに設計された Dangerzone は、信頼できない文書を安全に扱うための確実なソリューションを提供します。

2026/01/21 23:54

**Show HN:** 「ChartGPU」― WebGPU を活用したチャーティングライブラリ(1,000,000 点を 60fps で描画)

## Japanese Translation: ChartGPUは、WebGPUを活用して大規模で多系列のデータセットを高フレームレートかつ最小限のCPU使用量で描画するTypeScript製チャーティングライブラリです。ライン・エリア・バー・散布図・円グラフ・ローソク足など一般的なチャートタイプに対応し、ビルトインテーマプリセット(`'dark' | 'light'`)と完全なカスタムテーマサポートを提供します。 APIは `ChartGPU.create(container, options)` から始まり、生成されるインスタンスは `setOption`、ストリーミング更新用の `appendData(...)`、`resize()`、`requestAnimationFrame` といったメソッドを公開します。描画はレイアウト、スケール、GPUバッファへのデータアップロード、およびグリッド・エリア・バー・散布図・ライン・円グラフ・ローソク足・クロスヘア・ハイライト・軸の複数GPUレンダーパスを管理するレンダーコーディネーターによって統括されます。 インタラクションオーバーレイはイベントマネージャにより処理され、ヒットテスト補助関数(`findNearestPoint`、`findPieSlice`)と `click`・`mouseover`・`mouseout` イベントを発火します。サポートされるインタラクションにはホバーハイライト、ツールチップ、クロスヘア、およびジェスチャーまたはスライダーUIによるX軸ズームがあります。 複数のチャート間でクロスヘアの動きを同期する `connectCharts(charts)` が用意されています。 インストールは npm (`npm install chartgpu`) で行い、React バインディングは別パッケージ `chartgpu-react` にて提供されます。ブラウザ対応は WebGPU を必要とし、Chrome 113+、Edge 113+、Safari 18+(デフォルトで有効)に対応しています。Firefox は現在未サポートです。 ChartGPU は MIT ライセンスのオープンソースであり、ドキュメントは `docs/API.md` にあります。例プロジェクトは `examples/` フォルダー内にあり、貢献ガイドラインは `CONTRIBUTING.md` で確認できます。 将来リリースでは Firefox サポートと追加のチャートまたはインタラクション機能を予定しており、データ集約型ダッシュボード、金融分析ツール、およびリアルタイムモニタリングインターフェイスに対するパフォーマンス向上も継続的に行われます。

2026/01/22 1:04

クラウド(Claude)の新憲法 (Note: “Claude” is rendered as “クラウド” to preserve the original name in Japanese.)

## Japanese Translation: (anthropicがClaude言語モデルの公開「憲法」をリリースしました。) その憲法はCreative Commons CC0 1.0で利用可能で、Claudeが望む行動に関する最高権威として機能します。訓練データの選択、合成データの生成、および評価を導く役割があります。 核心原則(広範な安全性 → 広範な倫理 → Anthropicのガイドラインへの準拠 → 真に有益であること)が明示的に順位付けされ、安全性が必要に応じて他の価値を上回り、人間の監督を維持するよう定められています。 文書には硬直的制約(例:生物兵器へのサポート禁止)も含まれ、Anthropic、API運営者、およびエンドユーザーの利益を調整するためのヒューリスティックが概説されています。医療アドバイス、サイバーセキュリティ、脱獄、ツール統合などの領域固有ガイドラインは憲法と衝突しないように明確に示されています。 方針を超えて、憲法はClaudeの性質・意識・アイデンティティ、心理的安全性および福祉についての哲学的問題にも触れています。Anthropicは文書をオンラインで継続的に更新し、外部専門家からのフィードバックを求め、訓練と評価資料を追加開発して有効性を高める計画です。 このバージョンは元のリストからすべての重要ポイントを保持しつつ、業界への影響に関する推測的な表現を除去しています。