TECH PLAY

NTTドコモビジネス

NTTドコモビジネス の技術ブログ

622

みなさんこんにちは、イノベーションセンターの益本 (@masaomi346) です。 Network Analytics for Security (以下、NA4Sec) プロジェクトのメンバーとして活動しています。 この記事ではフィッシング詐欺がどのように行われているのか、フィッシングサイトがどのような仕組みで動作しているのか、注意喚起を兼ねて紹介します。 ぜひ最後まで読んでみてください。 フィッシング詐欺について フィッシング詐欺がどのように行われているのか フィッシングサイトがどのように構築されているのか フィッシングサイトがどのように動作しているのか どんな情報を窃取しているのか 窃取した情報はどこに送られるのか 相手の情報を収集・判別する 窃取したクレジットカード番号が有効であるか確認する フィッシング詐欺に引っかかるとどうなるのか フィッシング詐欺を減らすための取り組み マラソン型の撲滅イベント開催 国内カード会社等による共同の取り組み フィッシングハンターたちによるSNS投稿 被害に遭わないようにするには 被害に合わないための手段(例)まとめ さいごに フィッシング詐欺について フィッシング(Phishing)詐欺とは、メール等から本物そっくりの偽サイトへ誘導し、IDやパスワードを入力させて情報を盗み取る詐欺の手口です。 年々フィッシング詐欺による被害が増加し続けています。 特に、3月下旬あたりから証券会社を騙ったフィッシングメールや投資詐欺メールが大量に出回っており、各所で注意喚起を出しています。 証券会社口座 不正アクセス被害相次ぐ フィッシング詐欺に注意 フィッシング詐欺(証券会社を装う偽サイトへの電子メール等での誘導)にご注意ください NA4Secでも証券会社を騙ったフィッシングサイトを観測しています。 🚨⚡ #Phishing #フィッシング詐欺 (🇯🇵) Brand: #野村證券 IP: 🌍 103.112.211[.]228 (ASN:AS150855) URL: 🎣 hxxps://mehhkapradwwoesi.qcwb76.com/ 🎣 hxxps://vasoconstrictio.qcmky6r.com/ 🎣 hxxps://xeroththaamiahl.qcw5htg.com/ 🎣 hxxps://yesterdaynessrr.mkca0.com/ H/T to Team NA4Sec pic.twitter.com/IRX3oaBsEH — Metemcyber (@Metemcyber) 2025年4月4日 🚨⚡ #Phishing #フィッシング詐欺 (🇯🇵) Brand: #Rakuten #楽天 #楽天証券 IP: 🌍 47.83.189[.]115 (ASN:AS45102) URL: 🎣 hxxps://217564.top/oeugef/ 🎣 hxxps://255711.top/oeugef/ 🎣 hxxps://363342.top/oeugef/ 🎣 hxxps://368226.top/oeugef/ H/T to Team NA4Sec pic.twitter.com/jIdn9xbMnP — Metemcyber (@Metemcyber) 2025年4月11日 フィッシング詐欺がどのように行われているのか おおまかに以下の流れでフィッシング詐欺が実行されます。 フィッシングサイトを構築するなど準備する 偽のメールやSMSを送信する 実行し、個人情報などを窃取する 窃取した情報で収益を上げる 近年ではサイバー犯罪の分業化が進んでおり、上記の画像で説明したような段階それぞれにおいて、以下のように役割を分けて犯罪者達が暗躍しています。 フィッシング詐欺に使うツールやサービスを提供する人 フィッシング詐欺を実際にする人 窃取したクレジットカード情報の販売をする人 etc. この記事ではフィッシング詐欺に使うツールがどのように構築・提供されているのかについて、実際の例を元に紹介していきます。 フィッシングサイトがどのように構築されているのか フィッシング詐欺に関わっている犯罪者全員が技術的に長けているわけではありません。 何より、一からフィッシングサイトを作成するのは手間がかかります。 なので、犯罪者コミュニティには、フィッシング詐欺を支援する以下のようなツールやサービスが提供されています。 買い切り型のフィッシングサイト構築ツール(フィッシングキット) サブスク型のインフラやツール一式を提供するサービス(Phishing as a Service) etc. これらを利用することで、フィッシング詐欺をするための技術的なハードルを下げることができます。 例えば以下の画像では、あるPhishing as a Serviceがサブスク型/買い切り型のそれぞれで提供されていることがわかります。 週租 → 週単位 月租 → 月単位 永久买断 → 買い切り ※Uは仮想通貨のUSDTを指す。 フィッシングサイトがどのように動作しているのか フィッシングサイトがどのように動作して、どのような機能が搭載されているのか、実際のフィッシングキット(フィッシングサイト構築ツール)を解析して紹介します。 今回のフィッシングキットはzipファイルになっており、展開するとさまざまなファイルが入っています。 どんな情報を窃取しているのか 今回紹介するフィッシングキットは、日本のネット銀行のサイトを騙っています。 ログインの要求をしたり、本人確認と騙ってクレジットカードの情報を入力させること等を通して以下の情報を窃取しています。 ログインID・ログインパスワード 生年月日・取引パスワード クレジットカード番号・セキュリティコード メールトークン 窃取した情報はどこに送られるのか このフィッシングキットには管理者パネルが搭載されており、窃取した情報の一覧を確認できます。 DEVICE INFO → IPアドレス・場所・ユーザーエージェント LOGIN → ログインID・ログインパスワード AUTH → 生年月日・取引パスワード INFORMATION CARD → クレジットカードの番号・セキュリティコード CODE EMAIL → メールトークン LOG → どのページを表示したか ACTION → 現状のステータス また、このフィッシングキットでは画面遷移をするたびに、Telegramに窃取した情報を送信しています。 下の画像は、ログインID・ログインパスワードを窃取した際に、管理者パネルとTelegramへ送信している箇所になります。 相手の情報を収集・判別する フィッシングサイトには相手の情報を収集する機能が搭載されており、相手の情報を収集することで、以下のようなことが可能になります。 ターゲットの使用環境に合わせて、適したコンテンツを表示できる 専門家などの分析を回避できる このフィッシングキットでは、以下の情報を収集しています。 使用しているOS・ブラウザ(ユーザーエージェントから取得) IPアドレス ホスト名 どこの国からアクセスしているか これらの情報を使って、攻撃者が想定しているターゲットか確認し、想定しているターゲットであればフィッシングサイトを表示します。 どのように判別しているのか一部紹介します。 例えば、このフィッシングキットは日本人をターゲットにしているので、ターゲットのIPアドレスを外部のサービス(ip-api.com)に問い合わせ、国識別コードが「JP」であるか確認しています。 専門家からのアクセスを防ぐため、上記で収集したIPアドレスを利用します。 事前に作成したリストのIPアドレスに一致した際、アクセスのブロックを行います。 なお、こちらのリストにあるIPアドレスには、ホスティングやプロキシ・VPNなど一般の人は利用しないものが含まれています。 窃取したクレジットカード番号が有効であるか確認する このフィッシングキットには、窃取したクレジットカード番号について有効であるか確認する機能が搭載されています。 下の画像では、クレジットカード番号が有効であるか確認したり、外部サービス(binlist.net)を使ってBINコードの情報を取得しています。 フィッシング詐欺に引っかかるとどうなるのか フィッシング詐欺に引っかかり、ログイン情報やクレジットカード等の個人情報を入力してしまうと、それらの情報が悪用されてしまいます。 以下のようなことになる可能性があります。 窃取した情報を販売して他の攻撃者の手に渡る メールアカウントが乗っ取られ、メールボックスの中身を見られる SNSアカウントが乗っ取られ、なりすまされる 銀行口座やクレジットカードを悪用される etc. フィッシング詐欺を減らすための取り組み フィッシング詐欺を減らすために、各所でさまざまな取り組みが行われています。 ほんの一例を紹介します。 マラソン型の撲滅イベント開催 「フィッシングサイト撲滅チャレンジマラソン」というイベントがJC3により開催されました。 フィッシングサイトのAbuse報告数やテイクダウン数をマラソンのように競い合うイベントになっています。 専用のツールを使っているため、参加するためのハードルが低くなっています。 フィッシングサイト撲滅チャレンジマラソン開催 国内カード会社等による共同の取り組み 日本クレジットカード協会と国内のクレジットカード会社、フィッシングサイト検知サービスを提供している会社が、共同でフィッシングサイトを閉鎖する取り組みを始めています。 国内カード会社8社とACSiONと共同でフィッシングサイト閉鎖の取組を開始しました。 フィッシングハンターたちによるSNS投稿 SNSには、フィッシング詐欺についての情報発信をしている人たちがいます。 「#Phishing」「#フィッシング詐欺」などで検索すると、情報発信をしている様子がわかります。 フィッシングハンターについては、以下の資料で紹介されています。(52ページ参照) サイバーセキュリティ仕事ファイル~みんなが知らない仕事のいろいろ~ 被害に遭わないようにするには ここ最近のフィッシングメールは不自然なところが少なくなっており、本物か偽物かの判断が難しくなっています。 文面だけでなく、メールやSMSに貼られたリンクも巧妙に偽装されている場合があります。 実際のものに酷似したURLや、表示と実際のリンク先で異なる場合があるため、これを見分けようとすると間違った判断をしてしまう可能性があります。 なので、見分けなくても済む手段で確認することをお勧めします。 また、IDとパスワードだけでログインできる状態にしないことも重要です。 被害に合わないための手段(例)まとめ 公式が提供しているアプリから確認する 普段使うサイトをブックマークに保存し、そこから確認する ID/パスワード以外の追加認証設定が可能な場合にはそちらを設定する(特にパスキーなどフィッシング耐性があるとされる方式を推奨) サービス提供者側も「フィッシング耐性のある認証」を提供することが望まれます。 さいごに フィッシング詐欺に限らずサイバー犯罪の分業化が進んでおり、犯罪に関わる人すべてを逮捕することが難しくなっています。 そのため、犯罪者を逮捕するだけでなく、1人1人がしっかり自衛する環境を作っていくことで、犯罪で利益を出しにくくすることが重要になります。 フィッシング詐欺について知ることで、被害を減らすヒントにつながるかもしれません。 本記事により、被害を受ける人を少しでも減らせるといいなと筆者は考えています。
OpenStack の Compute Node を更新する際にゲスト VM の Disk 性能が低下する問題を、 Linux の Timestamping という機能を使ってネットワークレイテンシを分析することで解決できた事例をご紹介します。 本事例は fukabori.fm #127 でもご紹介しています。 はじめに 前提: 仮想サーバの構成 初期調査 仮想化レイヤの問題を切り分ける CPv2 と CPv3 の違いに着目する CPv3 において RTT が高い問題を切り分ける Timestamping 実験の構成図 RX 方向のレイテンシを分析する RX 方向のタイムスタンプを取得するコード TX 方向のレイテンシを分析する TX 方向のタイムスタンプを取得するコード End to End レイテンシの内訳 RX 方向にノイズが乗る原因調査: 他プロセスの影響 RX 方向にノイズが乗る原因調査: 電力関連の設定 まとめ お知らせ はじめに こんにちは。 SDPF クラウド・仮想サーバーチームの杉浦 ( @Kumassy_ ) です。 普段は OpenStack の開発・運用をしており、最近は仮想マシンの性能解析やトラブルシューティングなどに取り組んでいます。 仮想サーバーチームでは、 OpenStack の Nova, Cinder, Glance 等を活用し、仮想マシン (VM) と、それを動かすのに必要なディスクやイメージを管理できる機能を提供しています。 VM が稼働しているホストは Compute Node と呼ばれます。 仮想サーバーチームでは、 Compute Node に使用している物理サーバーや OS の更新のため、新しい世代の Compute Node である CPv3 を開発しています。 余談ですが、 初代の Compute Node である CPv1 から 2 世代目の CPv2 に移行する苦労話は CODT 2021 でご紹介しています 1 。 CPv3 の変更点は次の図の通りです。 物理サーバーと OS、仮想マシンを動かすための qemu や libvirtd を更新する計画です。 Compute Node の設定を変更したり、ソフトウェアを入れ替えたりする際には、ゲスト VM の性能に問題が出ないか試験をする必要があります。 そのため、複数のベンチマークツールを使用して、ゲスト VM の性能が基準値を満たしているかを確認しています。 しかし、ベンチマークの結果、 CPv3 では前世代の CPv2 と比べて、ゲスト VM の Disk 性能が 33 - 50 % 程度になっていることがわかりました。 ハードウェアが新しくなったのにもかかわらず、ゲスト VM の性能が大幅に低下してしまったのは問題です。 本記事では、この問題をどのように解決したのかをご紹介します。 前提: 仮想サーバの構成 仮想サーバのアーキテクチャは次の図のようになっています。 ゲスト VM のディスクは NFS Server 上のファイルとして保存されています。 Compute Node は NFS Server のストレージプールをマウントしており、 qemu はディスクのイメージファイルをブロックストレージとしてゲスト VM に見せています。 Compute Node のアップデート期間中は、 CPv2 と CPv3 は同じ NFS Server に接続されます。 初期調査 仮想化レイヤの問題を切り分ける ゲスト VM の性能試験として、 Linux ゲストの Disk 性能試験には fio 2 というベンチマークツールを利用しています。 CPv2 と CPv3 のそれぞれに VM をデプロイし、 VM の中で fio を動作させたところ、 CPv3 では bw 及び iops スコアが CPv2 と比べて 33 - 50 % 程度であることがわかりました。 CPv3 ではハードウェアではなく OS や qemu, KVM 等のバージョンも違い変更範囲が大きいので、何が影響しているかを絞り込む必要があります。 そこで、まずはホストで fio を直接動かしてベンチマークのスコアが低下するか調べることにしました。 次のコマンドのように、 NFS Server 上のファイルに対して I/O リクエストを発生させます。 sudo fio -filename = /path/to/nfs/storage/fio -direct = 1 -rw = randread -rwmixread = 30 -bs = 4k -size = 3G -numjobs = 1 -runtime = 180 -group_reporting -name =test その結果、次のグラフのように、 CPv3 は CPv2 と比べて fio のスコアが明らかに悪いことがわかりました。 グラフでは CPv2 のスコアを 1 として正規化しています。 CPv3 における fio のスコアは CPv2 の 0.51 倍くらいのスコアでした。 CPv2 と CPv3 の違いに着目する 仮想サーバの構成図で示したように、 CPv2 と CPv3 の Compute Node は同じ NFS Server に接続されているので、 NFS Server 側は問題なさそうです。 よって、 NFS Client である Compute Node か途中のネットワークに問題がありそうです。 CPv2 と CPv3 の差分を調査してみると、 NFS Server とのネットワークレイテンシに違いが見つかりました。 CPv2 と CPv3 それぞれの Compute Node から NFS Server との間の RTT を ping コマンドで測定すると、次のようになりました。 CPv3 は CPv2 と比べて、 NFS Server との RTT が 0.1 ms くらい大きいようです。 この RTT の違いはどれくらい fio のスコアに影響するでしょうか。 それを確かめるために、 tc コマンドを使い CPv2 の NIC に対して意図的に遅延をつけることで、 fio のスコアがどれくらい低下するか調べました。 上記の図に示すように、 1 ms のレイテンシを追加すると fio の性能が 10 % 程度に、 100 us のレイテンシを追加すると fio の性能が 65 % くらいに低下することがわかりました。 これまでの調査の結果から、 「 CPv3 では NFS Server へのネットワークレイテンシが高いことで、 NFS 上のファイルへの I/O 性能が低い」という仮説を立てて、以降の調査ではネットワークレイテンシが高くなってしまった理由を深堀りすることにしました。 CPv3 において RTT が高い問題を切り分ける Timestamping RTT が高い理由を分析するためには、全体のレイテンシを分解し、どの部分でどれだけ時間がかかっているか分析できるようにしたいです。 この用途に使えるのが Timestamping 3 です。 Timestamping とは、パケットが Linux システム内の特定のポイントを通過した時間を記録する機能で、パケットが Kernel に到着した、もしくは Kernel から出ていく時刻を調べることができます。 さらに、 NIC が hardware timestamping をサポートしている場合、パケットが NIC に到着した、もしくは NIC から出ていく時刻を知ることができます。 パケットにつけられたタイムスタンプを分析することで、パケットが Application, Kernel, NIC の各レイヤでどれくらい時間がかかったかを分析できます。 Timestamping を使ってネットワークレイテンシを分析する手法は How to measure network latency using hardware timestamps | IIJ Engineers Blog 4 で詳しく紹介されており、本記事でも IIJ Engineers Blog のプログラムを利用しています。 本手法を使って hardware timestamping を利用するには、 NIC が以下のように hardware-transmit , hardware-receive capablity と、 Hardware Receive Filter Modes: all をサポートしている必要があります。 $ sudo ethtool -T ens15f0 Time stamping parameters for ens15f0: Capabilities: hardware-transmit software-transmit hardware-receive software-receive software-system-clock hardware-raw-clock PTP Hardware Clock: 1 Hardware Transmit Timestamp Modes: off on Hardware Receive Filter Modes: none all Compute Node と NFS Server 間のネットワークレイテンシを分析できればよかったのですが、 NFS Server 上で任意のプログラムを動かすのは難しかったので、 隣接する Compute Node 間のネットワークレイテンシを分析することにしました。 実験の構成図 実験環境は以下の図のようになります。 Rust 言語で書かれた packet generator から rx_timestamping.c もしくは tx_timestamping.c に向かってパケットが送られます。 rx_timestamping.c は packet generator からパケットを受信するたびに、パケットに紐づけられたタイムスタンプを取得して保存することで、 RX 方向のレイテンシを分析します。 tx_timestamping.c は packet generator からパケットを受信するたびに packet generator へパケットを echo back し、その際に得られたタイムスタンプを取得して保存することで、 TX 方向のレイテンシを分析します。 特にチューニングを加えない状態では、隣接 Compute Node 間の RTT は 0.35 ms くらいでした。 RX 方向のレイテンシを分析する RX 方向のタイムスタンプを取得するコード rx_timestamping.c は、 IIJ Engineers Blog で紹介されているコード 5 を元に、アドレスを書き換えたものを利用しました。 コードを動かす前に次の手順が必要です。 hwstamp_ctl を使って、 NIC の hardware timestamping を有効化する カーネルと NIC は別のクロックを利用しているため、 phc2sys を使ってカーネルと NIC の時刻を同期し続ける receiver$ sudo hwstamp_ctl -i ens15f1 -t 1 -r 1 current settings: tx_type 0 rx_filter 0 new settings: tx_type 1 rx_filter 1 # 実験が終わるまで動かし続ける receiver$ sudo phc2sys -s ens15f1 -O 0 -m phc2sys [ 9050057 . 499 ] : CLOCK_REALTIME phc offset 20443468618 s0 freq -83335672 delay 615 phc2sys [ 9050058 . 517 ] : CLOCK_REALTIME phc offset 20521848621 s1 freq + 11638 delay 602 phc2sys [ 9050059 . 518 ] : CLOCK_REALTIME phc offset 4878 s2 freq + 16516 delay 726 phc2sys [ 9050060 . 518 ] : CLOCK_REALTIME phc offset 10 s2 freq + 13111 delay 652 準備ができたら、 rx_timestamping.c を動かします。 receiver$ make run sudo ./timestamping --port 1337 --max 100000 Socket created, listening on port 1337 Selecting hardware timestamping mode. enabled timestamping sockopt 最後に、 packet generator を動かして実験を開始します。 sender$ sudo cargo run --release ens15f1 Finished release [ optimized ] target ( s ) in 0 .06s Running `target/release/tranquil ens15f1` 実験で得られたタイムスタンプを可視化すると、次のグラフのようになります。 横方向は時間軸です。 packet generator は 100,000 パケットを rx_timestamping.c に向かって送信しますが、グラフでは最初と最後の10,000パケットを除いた 80,000 パケットを表示しています。 縦方向はレイテンシの内訳を示します。各レイテンシの説明は次の表の通りです。 レイテンシ 取得元 説明 End to End sender パケットを sendto してから recv_from するまでの時間 NIC -> User receiver パケットが NIC に到着してから User 空間で recvmsg するまでの時間。 gettimeofday() - SOF_TIMESTAMPING_RX_HARDWARE NIC -> Kernel receiver パケットが NIC に到着してからパケットが Kernel 空間に到着するまでの時間。 ( SOF_TIMESTAMPING_RX_SOFTWARE の時刻) - ( SOF_TIMESTAMPING_RX_HARDWARE の時刻) Kernel -> User receiver パケットが Kernel 空間に到着してから User 空間で recvmsg するまでの時間 グラフから、 Kernel → User のレイテンシにノイズが発生していることがわかりました。このノイズにより、通常は約 10 us のレイテンシが、時折 100 us 程度まで増加する場合があります。この影響で、 RTT が往復で約 200 us 増加していることが確認されました。 RX 方向のタイムスタンプを取得するコード RX 方向のタイムスタンプを取得するコード rx_timestamping.c の中身を見てみましょう。 コードの最初のほうでは、 socket にタイムスタンプを取得するためのフラグを設定します。 int enable = SOF_TIMESTAMPING_RX_HARDWARE | SOF_TIMESTAMPING_RAW_HARDWARE | SOF_TIMESTAMPING_SYS_HARDWARE | SOF_TIMESTAMPING_SOFTWARE; TRY ( setsockopt (sock, SOL_SOCKET, SO_TIMESTAMPING, &enable, sizeof ( int ))); https://github.com/ArneVogel/hw-timestamping/blob/main/rx_timestamping.c#L237-L239 上記のように socket にフラグを設定すると、 recvmsg したときにタイムスタンプがメタデータとして渡されてきます。 recvmsg は以下の部分で呼び出されています。 /* recvmsg header structure */ make_address ( 0 , &host_address); iov.iov_base = buffer; iov.iov_len = 2048 ; msg.msg_iov = &iov; msg.msg_iovlen = 1 ; msg.msg_name = &host_address; msg.msg_namelen = sizeof ( struct sockaddr_in); msg.msg_control = control; msg.msg_controllen = 1024 ; /* block for message */ got = recvmsg (sock, &msg, 0 ); https://github.com/ArneVogel/hw-timestamping/blob/main/rx_timestamping.c#L410C1-L422C32 次に示すコードのように、特定のマクロを使うことで、 msg からパケットに紐づいたタイムスタンプを取得できます。 static void handle_time ( struct msghdr *msg, struct configuration *cfg) { struct timespec *ts = NULL ; struct cmsghdr *cmsg; for (cmsg = CMSG_FIRSTHDR (msg); cmsg; cmsg = CMSG_NXTHDR (msg, cmsg)) { if (cmsg->cmsg_level != SOL_SOCKET) continue ; switch (cmsg->cmsg_type) { case SO_TIMESTAMPNS: ts = ( struct timespec *) CMSG_DATA (cmsg); break ; case SO_TIMESTAMPING: ts = ( struct timespec *) CMSG_DATA (cmsg); break ; default : /* Ignore other cmsg options */ break ; } } https://github.com/ArneVogel/hw-timestamping/blob/main/rx_timestamping.c#L344C1-L363C4 タイムスタンプは ts 配列の中に格納されます。 ts 配列の中身は、以下のコメントを参考にするとよいでしょう。 /* Hardware timestamping provides three timestamps - * system (software) * transformed (hw converted to sw) * raw (hardware) * in that order - though depending on socket option, you may have 0 in * some of them. */ https://github.com/ArneVogel/hw-timestamping/blob/main/rx_timestamping.c#L281-L287 最後に、 ts から NIC -> User , NIC -> Kernel , Kernel -> User の各区間のレイテンシを計算します。 diff_nic_kernel = (ts[ 0 ].tv_sec - ts[ 2 ].tv_sec) * 1000000000 + (ts[ 0 ].tv_nsec - ts[ 2 ].tv_nsec); nic_kernel_latency_numbers[total_received++] = diff_nic_kernel; // all latency numbers are in nanoseconds if (old_diff_nic_kernel != 0 ) { nic_kernel_total_diff += diff_nic_kernel - old_diff_nic_kernel; } diff_kernel_user = (time_user.tv_sec - ts[ 0 ].tv_sec) * 1000000000 + (time_user.tv_usec * 1000 - ts[ 0 ].tv_nsec); diff_nic_user = (time_user.tv_sec - ts[ 2 ].tv_sec) * 1000000000 + (time_user.tv_usec * 1000 - ts[ 2 ].tv_nsec); https://github.com/ArneVogel/hw-timestamping/blob/main/rx_timestamping.c#L312-L324 TX 方向のレイテンシを分析する パケットが送信時に詰まってしまい、 TX 方向でレイテンシが増加している可能性も考えられたので、RX 方向と同様の分析を TX 方向でも実施しました。 IIJ Engineers Blog では RX 方向のレイテンシのみを分析しており、 TX 方向のレイテンシを分析するコードはありません。そこで、 majek/openonload リポジトリの src/tests/onload/hwtimestamping/tx_timestamping.c 6 を改造して動かしました。 なお、 rx_timestamping.c と同じように、 tx_timestamping.c と動かす前に hardware timestamping を有効化し、 NIC とクロックを同期する必要があります。 TX 方向では、 Kernel のタイムスタンプ SOF_TIMESTAMPING_TX_SOFTWARE がなぜか取得できなかったため、 User -> NIC のレイテンシのみを集計しました。 また、タイムスタンプの取得にときどき失敗し、安定性は高くない印象でした。 User-> NIC のレイテンシを可視化すると次の図のようになります。 レイテンシは 4 - 40 us 程度であり、 RX と比べると十分小さいことがわかりました。 TX 方向のタイムスタンプを取得するコード TX 方向のタイムスタンプを取得するコード tx_timestamping.c の中身を見てみましょう。 RX 方向の場合、 User Space でパケットを受信できるころにはパケットが NIC や Kernel を通過した時刻が確定しているので、比較的簡単にタイムスタンプを取得できます。 一方で TX 方向の場合、 User Space からパケットを送信しても、パケットが Kernel や NIC を通過する時刻は未確定のため、タイムスタンプを取得するにはひと工夫必要です。 具体的には、パケットを sendmsg して送信したあと、 error queue から recvmsg することでタイムスタンプを取得できます。 最初に、 socket に対して timestamp を取得するようにフラグを設定します。 enable = SOF_TIMESTAMPING_TX_HARDWARE | SOF_TIMESTAMPING_SYS_HARDWARE | SOF_TIMESTAMPING_RAW_HARDWARE; if (cfg->cfg_protocol == IPPROTO_TCP) enable |= ONLOAD_SOF_TIMESTAMPING_STREAM; ok = setsockopt (sock, SOL_SOCKET, SO_TIMESTAMPING, &enable, sizeof ( int )); https://github.com/majek/openonload/blob/master/src/tests/onload/hwtimestamping/tx_timestamping.c#L338-L339 まずは sendmsg を呼び出し、パケットを送信します。 /* recvmsg header structure */ make_address ( 0 , 0 , &host_address); iov.iov_base = buffer; iov.iov_len = 2048 ; msg.msg_iov = &iov; msg.msg_iovlen = 1 ; msg.msg_name = &host_address; msg.msg_namelen = sizeof ( struct sockaddr_in); msg.msg_control = control; msg.msg_controllen = 1024 ; TRY ( sendmsg (sock, &msg, 0 )); https://github.com/majek/openonload/blob/master/src/tests/onload/hwtimestamping/tx_timestamping.c#L494-L518C1 次に MSG_ERRQUEUE フラグを指定し、 error queue から recvmsg することで、送信したパケットを msg に読み出します。 その後、 RX の場合と同様に、 msg を CMSG_FIRSTHDR マクロで読み出せばタイムスタンプを得られます。 sendmsg してから recvmsg できるようになる時刻がわからないので、コードでは busy loop で recvmsg を読み出す作りになっていて、動作の安定性に欠けるようです。 /* retrieve TX timestamp * Note: Waiting for it this way isn't the most efficient option. * For higher throughput, check associate times to packets afterwards. */ msg.msg_control = control; iov.iov_len = 2048 ; do { msg.msg_controllen = 1024 ; got = recvmsg (sock, &msg, MSG_ERRQUEUE); } while (got < 0 && errno == EAGAIN && check++ < check_max); if ( got < 0 && errno == EAGAIN ) { printf ( "Gave up acquiring timestamp. \n " ); return - EAGAIN ; } https://github.com/majek/openonload/blob/master/src/tests/onload/hwtimestamping/tx_timestamping.c#L520-L533 End to End レイテンシの内訳 RX と TX の双方向のタイムスタンプを分析したので、 RTT の内訳を以下のように推定できます。 Sender でのレイテンシは計測していないので、 Receiver と同じ値と仮定しました。また、TX 方向のレイテンシは 40 us と仮定しました。 全体の内訳でみると、TX: 18%, NW: 9%, RX: 73% となり、 RX 方向のレイテンシが全体の 73 % 程度を占めていることがわかりました。 RX 方向のレイテンシの内訳をみると、 Kernel -> User が半分以上を占めています。 Kernel -> User のレイテンシが時々 100 us 程度に増加する問題を解決し、 RX 方向のレイテンシを最適化することで、 End to End のレイテンシも小さくできそうです。 RX 方向にノイズが乗る原因調査: 他プロセスの影響 Kernel -> User のレイテンシが増加する原因としてまず疑ったのが、他のプロセスの影響です。 そこで、 rx_timestamping.c を実行するプロセスに専用のCPUコアを割り当てて、他のプロセスの影響を排除しました 7 。 Linux では特定のコアにプロセスがスケジューリングされないようにする方法として、 cgroup cpuset controller 8 を使うこともできますが、今回は kernel parameters に isolcpus 9 を指定するようにしました。 過去の経験を踏まえ、 SMT (Simultaneous Multi Threading) siblings も isolate しました。 SMT siblings とは、 Intel の Hyperthreading などで作られた論理コアのうち、物理コアを共有する論理コアのことです。 以下のようにして、 31, 63 番の論理コアとその SMT siblings である 95, 127 番の論理コアに通常のプロセスがスケジューリングされないようにします。 $ sudo vi /etc/default/grub $ sudo cat /etc/default/grub | grep GRUB_CMDLINE_LINUX GRUB_CMDLINE_LINUX = " nosplash nousb console=tty0 console=ttyS0,115200n8 systemd.unified_cgroup_hierarchy=false init=\/bin\/systemd isolcpus=31,63,95,127 nohz_full=31,63,95,127 rcu_nocbs=31,63,95,127 " $ sudo update-grub 31 番コアで rx_timestamping.c を実行します。 $ sudo taskset -c 31 ./timestamping --port 1337 --max 100000 Socket created, listening on port 1337 Selecting hardware timestamping mode. enabled timestamping sockopt この環境で実験すると、 Kernel -> User にノイズが常時乗るようになってしまい、レイテンシは改善するどころか悪化してしまいました。 RX 方向にノイズが乗る原因調査: 電力関連の設定 他のプロセスの影響を排除できたのにもかかわらずレイテンシが改善しなかったので、 CPU のコア自体の性能が悪くなってしまっているのではないかと考えました。 具体的には CPU の電力関連の設定を疑いました。 cpupower コマンドを利用することで、 Scaling Governors 10 や Idle State 11 の設定ができます。 Scaling Governors は CPU の動作周波数を制御するためのポリシーです。 CPU の動作周波数を上げることで性能も上がりますが、消費電力も増えてしまうため、 Scaling Govornors は CPU の性能と消費電力のバランスを最適化してくれます。 Idle State もしくは C-State とは、 CPU が使用されていないときに消費電力を削減するための機能です。 Idle State には複数のレベルが定義されており、深い State ほど消費電力は削減できますが、 Idle 状態からの復帰に時間がかかるようになります。 Scaling Governors には、デフォルトの schedutil に加え performance も評価しました。 Idle State として、デフォルトの C1 C1E C6 を有効化した場合、 C6 のみを無効化した場合、 C1 C1E C6 をすべて無効化した場合を評価しました。 Scaling Governors と Idle State の条件を組み合わせてレイテンシを測定したところ、 Idle State の C6 を無効化すると Kernel -> User レイテンシを効果的に改善できることがわかりました。 RX 方向のレイテンシを可視化してみると、 Kernel -> User に発生していたノイズがなくなり、レイテンシも小さくなったことが確認できます。 20,000 - 40,000 パケットにかけて NIC -> User , NIC -> Kernel のグラフが乱れているのは時刻同期ズレの影響だと考えられます。 C6 を無効化した状態で隣接 Compute Node 間の RTT を ping により測定すると、 0.055 ms 程度となりました。 C6 を無効化する前と比較すると、 RTT を 85 % 削減できました。 CPv2 と CPv3 で一番深い Idle State からの Exit Latency を調査しました。 CPv2 では 133us でしたが、 CPv3 では 290us となっていて、 Idle からの復帰に 2.2 倍ほど時間がかかるようになりました。これがネットワークレイテンシを悪化させた要因と考えられます。 $ sudo cpupower idle-info CPUidle driver: intel_idle CPUidle governor: menu analyzing CPU 31: Number of idle states: 4 Available idle states: POLL C1 C1E C6 POLL: Flags/Description: CPUIDLE CORE POLL IDLE Latency: 0 Usage: 48503581 Duration: 12146315989 C1: Flags/Description: MWAIT 0x00 Latency: 1 Usage: 9690 Duration: 9207119 C1E: Flags/Description: MWAIT 0x01 Latency: 2 Usage: 2023442 Duration: 4474113815 C6 ( DISABLED ) : Flags/Description: MWAIT 0x20 Latency: 290 Usage: 1702644 Duration: 840131162879 C6 を無効化してゲスト VM 上で fio を実行したところ、 CPv2 と同様の性能を CPv3 でも出すことができるようになりました。 まとめ Timestamping はパケットが Linux システムの特定のポイントを通過した時刻を記録する機能です。 NIC の hardware timestamping と組み合わせることで、 End to End のネットワークレイテンシを分解し、レイヤごとにレイテンシを分析できます。 CPU の電力関連の設定として、 Scaling Governors と Idle State があります。 これらの設定を見直すことで、特定のワークロードのパフォーマンスを向上できるかもしれません。 お知らせ さて、 SDPF クラウドでは現在、 Tech Workshop イベントへの参加を募集しております。 申し込み期限は 2025/4/18(金) 23:59 までですので、お早めにお申し込みください! information.nttdocomo-fresh.jp また、 2025 年度も夏期インターンシップを実施予定です。 下記ページでアナウンス予定ですので、チェックしてみてください! information.nttdocomo-fresh.jp https://www.youtube.com/watch?v=PZU-xKxxGmg ↩ https://github.com/axboe/fio ↩ https://docs.kernel.org/networking/timestamping.html ↩ https://eng-blog.iij.ad.jp/archives/21198 ↩ https://github.com/ArneVogel/hw-timestamping/blob/main/rx_timestamping.c ↩ https://github.com/majek/openonload/blob/master/src/tests/onload/hwtimestamping/tx_timestamping.c ↩ ユーザープロセスの影響は排除できますが、 一部の kernel thread がスケジューリングされる可能性は残ります。 ↩ https://docs.kernel.org/admin-guide/cgroup-v2.html#cpuset ↩ https://docs.kernel.org/admin-guide/kernel-parameters.html ↩ https://docs.kernel.org/admin-guide/pm/cpufreq.html ↩ https://docs.kernel.org/driver-api/pm/cpuidle.html ↩
こんにちは、NTT Comの上田です。 普段は、NTT Com内製のOT(Operational Technology:制御・運用技術)ネットワーク向け国産IDS(Intrusion Detection System:不正侵入検知システム)である「 OsecT(オーセクト) 」の開発・保守運用業務などに取り組んでいます。 本記事では、「OsecT」の台帳連携機能を紹介します。 はじめに OsecTの台帳連携機能について 開発の背景 台帳連携機能 おわりに はじめに 近年、従来はインターネットや情報ネットワークから隔離されていたOTネットワークが、 IoTの活用やDXによる生産性向上などのためにこれらのネットワークに接続するケースが増えています。 これに伴い、OTネットワークのセキュリティリスクが高まっています。 OTネットワークは、工場や発電所などインフラを支える重要なネットワークです。 万が一セキュリティインシデントが発生した場合、社会にも大きな影響を及ぼす可能性があります。 このため、ネットワークの可視化や、脆弱な端末や重要度の高い端末の把握、脅威の検知など、セキュリティ対策が重要になります。 OsecTの台帳連携機能について OsecTでは、下記の図のように、 可視化・検知対象となるネットワークのスイッチングハブなどのミラーポートを通じてトラフィックを収集・解析することで、 工場などの制御ネットワークの可視化・異常検知といったセキュリティ対策ができます。 今回は、OsecTに新たな機能として、台帳連携機能を追加しました。 なお、ここでの台帳は、IPアドレスやMACアドレスなどのネットワーク情報に加えて、 端末名や設置場所などの情報を持つ端末管理台帳を指します。 開発の背景 OsecTの「端末一覧」画面では、ネットワークに存在する端末情報を可視化でき、以下の情報を確認できます。 MACアドレス、IPv4アドレス、IPv6アドレス 利用しているプロトコル 種別・機種、ブラウザ、OS推定結果など 下記画像は、実際の「端末一覧」画面の例になります。 表示する列はユーザが自由に変更できます。 また、下記画像のように「ネットワークマップ」画面を利用することで、 端末間の通信状況やOT環境では必ずしも必要とされないインターネット宛ての通信などを可視化できます。 しかし、台帳連携機能の開発前は以下のような課題がありました。 設置場所などの情報が不足 トラフィックから取得できる情報には限りがあり、端末の設置場所などの情報は取得できません。 このため、異常検知のアラートが発生しても、どの端末を確認すれば良いかすぐに分からない場合がありました。 未把握端末の確認が手間 台帳連携機能がない場合、OsecTが可視化した端末と既存の台帳の突合に手間がかかり、未把握の端末が無いか確認するのが手間という問題がありました。 台帳連携機能 前述の課題を受けて開発した台帳連携機能を利用することで、次のことが可能になります。 台帳情報の登録と活用 お手持ちの台帳をCSVファイルとしてOsecTへ登録することで、 トラフィックデータを利用して可視化・検知した情報に加え、設置場所などの情報を一括で確認できます。 これにより、インターネットに本来アクセスしないはずの端末がアクセスしている場合など、 不審な状況を見つけた場合に、台帳に登録した設置場所や担当者情報などをもとに素早く対処することが可能になります。 以下の図は、「ネットワークマップ」画面で端末情報を確認した際の画面です。 画面右側に台帳情報が表示されています。 なお、以下の図のように台帳を編集することも可能です。 ただし、本機能は、あくまでもお手持ちの台帳との連携を想定したものであり、 OsecTで台帳のマスターデータを管理することはあまり想定していません (お客さまのご要望が多い場合、台帳管理のための機能拡充を行う可能性はあります)。 未把握端末の確認 台帳に登録されていない端末を「台帳」列で「無」と表示することで、台帳にない未把握の端末を確認できます。 これにより、不正端末や台帳の登録漏れを迅速に調査可能です。 以下の図は、「端末一覧」画面で台帳の有無を確認するための列を表示した際の画像です。 右端の列が「無」と表示されている行が、通信としては観測されているが、 台帳には登録されていない未把握の端末になります。 アラート対応の効率化 「検知アラート」画面では、アドレス部分にカーソルを合わせることで、台帳情報やパケットを元に解析した情報を確認できます。 台帳に各機器の設置場所やデバイス名、管理者情報を登録しておくことで、 IPアドレスやMACアドレスといったネットワークの情報ではなくデバイス名や設置場所など、 より分かりやすく、実態に即した情報をもとにコミュニケーションをとることができます。 このため、アラート対応担当者と機器の管理者間の意思疎通がスムーズになります。 以下の図は、あるIPアドレスの台帳情報やパケットを元に解析した情報を確認した際の画像です。 メール通知機能との連携 OsecTでは各種アラートをメールで通知する機能があります。 このうち、「接続端末」はOsecTの学習済みリストに無い端末を検知するとアラートとして通知します。 端末新設時の接続端末アラートを通知したくない場合、これまでは学習済みリストにIPアドレスとMACアドレスをあらかじめ設定する方法がありました。 台帳連携機能により、新設する端末をあらかじめ台帳に登録することでも、接続端末アラートを通知しないといった設定が可能になりました。 以下の図は、実際に台帳に無い新規の接続端末のみを通知するように設定した際の画面です。 メールでは通知されませんが、「検知アラート」画面には表示されます。 おわりに 今回は、NTT Comが開発しているOTネットワーク向け国産IDS「OsecT」の台帳連携機能を紹介しました。 OsecTは、簡単に設置可能なOTネットワーク向けのIDSです。 セキュリティ対策ツールとしてだけでなく、工場システムにおけるサイバー・フィジカル・セキュリティ対策ガイドラインに記載されている保護対象等の整理などにも利用可能なツールとなっています。 OsecTにご興味がありましたら、 こちら からお気軽にお問い合わせください。 また、OsecTに関するブログやニュースリリースなどは こちら にまとめています。 本記事が、OTセキュリティ対策のご検討の参考になりましたら幸いです。
「OT環境のアセスメント資料を急いで作らないといけない!大変だ!巷で噂のAIみたいに資料を自動でサクッと素早く作ってくれる機能が欲しい!」 「突然セキュリティ担当になってアセスメントレポートを作成せよと言われてしまった!知識もないし何をすべきか分からない…」 このようなお悩み、ありませんでしょうか? そのような時、OsecTならワンクリックでアセスメントレポートを自動生成できます! はじめに OsecTとは アセスメントとは レポート自動生成機能の概要 レポート機能作成の背景 レポートの魅力 充実した分析項目 パワーポイントで編集可能 期間指定で比較 CSV一括ダウンロード機能 データの長期保存 レポートの項目 脆弱端末 短時間しか通信していない端末 外部通信が行われている端末 おわりに はじめに こんにちは、イノベーションセンターの石禾(GitHub: rhisawa )です。 NTTコミュニケーションズで内製開発しているOT(Operational Technology) 向けのIDS製品であるOsecT、今年度はアセスメントレポート自動生成機能をリリースしました。 定期的にレポーティングの必要がある方や、定期的にデータをまとめてチェックしたい方などにお使い頂きたい機能となっています。 今回はこの機能の魅力についてご紹介します。 OsecTとは OsecTとは、工場などの制御システム(OT; Operational Technology)のセキュリティリスクを可視化・検知するサービスです。 多様化する工場システムのセキュリティ脅威に対して、トラフィックを収集・解析するセンサー機器を工場内のネットワーク機器のミラーポートに接続するだけで、OTシステムへの影響なく、資産・ネットワークの可視化と脅威・脆弱性検知ができます。これにより、早期にリスク感知できる状態を作り、工場停止による損失を未然に防げます。 詳しくは過去のブログ記事に書いているので、興味がある人はご覧ください。( OsecTリリース ・ OsecT前編 ・ OsecT後編 ) アセスメントとは アセスメントとは、環境のセキュリティリスクを評価するプロセスを指します。 NISTサイバーフレームセキュリティフレームワークでは、 統治 、 特定 、 防御 、 検知 、 対応 、 復旧 といったプロセスでOTセキュリティ対策を実施します。その中で、アセスメント業務では、分析やレポーティングにより 特定 を実施します。 OsecTは、OT環境の 検知 と 可視化 を担うサービスです。アセスメントは、この 可視化 を利用して行います。 レポート自動生成機能の概要 アセスメントの実施時にご活用いただけるパワーポイント形式(.pptx)の自動生成レポートを簡単に素早くダウンロードできます。 利用方法は、ボタンをワンクリックするだけ! レポートには、項目別にデータの見方や注意点が記載されており、セキュリティの専門家でない方でも理解がしやすい内容となっております。 レポート機能作成の背景 OT環境のセキュリティアセスメントは、手間と時間がかかります。特に、レポート作成は専門知識が必要であり、担当者にとって大きな負担となります。特に中堅中小企業さまだと専任のセキュリティ担当者の方が不在な場合も多く、セキュリティアセスメントをどのように実施していくかは大きな課題です。 手動でOT-IDSを見ながらレポートを作成していたNTT Comのアセスメント担当者はレポート作成にかなり時間を割いていました。また、ユーザーさまからも手動でレポートを作成していると時間がどうしてもかかってしまうというお声を伺ってきました。 そこで、レポート作成効率化の一歩として自動化の需要があるのではないかと考え、開発に踏み切りました。 OsecTのアセスメントレポート機能は、アセスメント担当の方の負担を大きく減らすことを目的としてます。また、セキュリティアセスメントに必要な知識を補えるようにしています。 レポートの魅力 充実した分析項目 現在、レポートの項目は10項目以上あります。 NTTコミュニケーションズの専門家によるアセスメント分析の項目や観点をベースに作成しています。 各項目にはデータの見方や注意点が記載されています。OsecTの画面で確認できる情報をそのまま出力するのではなく、セキュリティアナリストがOsecTの画面を見ながら分析するような内容をレポートとして出力しています。 また、セキュリティリスクに加えて推奨の対処事項も記載しているため、セキュリティの知識がない方でも、どのように対応すればよいかが分かるようになっています。 レポートの項目の具体例は後ほどご紹介いたします。 パワーポイントで編集可能 パワーポイント形式なので、ダウンロードした資料の編集が簡単にできます。 資料作成を一から行う必要はありません。不要箇所の削除、補足の追加など、必要な箇所だけ編集することで、効率的にアセスメント実施に必要な説明資料を用意できます。この項目は不要、この表は不要、より詳細な解説ページを加えたい、など皆さまそれぞれの細かいご希望を編集で叶えることが可能です。 スライドマスター編集でのデザイン変更も簡単です。すぐに環境のアセスメントをしてください!と言われた場合でも、1クリックでレポートをダウンロードして、スライドマスターで自社ロゴを挿入するだけで、自分が作成したように見える資料を簡単に素早く作成できます。 他社OT-IDSでもPDFでのレポート生成機能は見かけますが、パワーポイント自動生成はOsecTの特有の機能です。PDFは編集不可であり、会議での資料投影に不向きです。OsecTのレポートはパワーポイントなので、そのまま社内共有、会議、発表に使用できます。実際にレポートを展開して行う社内レビュー会の時には、メモをスライドやスライドのノートにそのまま書き込んだりできます。 期間指定で比較 期間を指定して、その期間のデータのみを使用したレポートを作成できます。 異なる期間のレポートを見比べることで、環境の変化を把握しやすくなります。例えば、工場の設備変更の前後の期間のレポートを見比べたり、1ヶ月毎にレポートを出力し見比べて環境の変遷を把握する、といった用途でご利用いただけます。 CSV一括ダウンロード機能 レポート本体に加えて、レポートの指摘事項に関連する端末一覧をCSV形式でまとめたデータを、ZIPファイルとして一括ダウンロードできる機能もあります。 データの長期保存 レポートのダウンロードは無制限です。1ヶ月毎、1年毎など定期的にレポートをダウンロードしてデータを手元に残しておけます。例えば、1年以上前の環境について知りたい、と急に言われた場合に備えて、定期的にボタンひとつでデータを一括ダウンロードしておくことができます。 レポートの項目 レポートの項目をいくつかピックアップしてご紹介します。 OsecTのWebUIでは確認できない、レポート限定の項目もありますので、OsecTをお使いの方はダウンロードしてみてください。 脆弱端末 OT環境はネットワークから切り離されている場合が多く、古いOSを使用し続ける対応は一般的です。OSのアップデートもIT環境のように容易ではないため、脆弱な端末が攻撃の対象になりやすいです。 OT環境の特性上、アップデート対応は難しいですが、サポートが終了したOSを搭載している端末の把握は非常に重要です。 この機能を使うと、注意が必要な端末を確認できます。 短時間しか通信していない端末 メンテナンスで持ち込まれた端末の接続や、普段は利用されていない管理外の端末の接続などを検出する指標の一つとして、短時間しか通信をしていない端末をピックアップして一覧にしています。 外部通信が行われている端末 外部(インターネット)への通信をする端末が存在する場合、外部からの攻撃を受けるリスクが高まります。 OT環境は基本的に外部通信をしない構成になっている環境が多いです。そのため、外部通信を行なっている端末は要注意であるとして取り上げています。 おわりに 今回は、国産OT-IDSであるOsecTのアセスメントレポート自動生成機能を紹介しました。 アセスメント実施時に是非とも活用をお勧めしたい機能です! ブログには記載しなかったレポート項目の詳細にご興味がありましたら、 こちら からお気軽にお問い合わせください。 ご契約に関するお問い合わせだけでなく、PoCのお問い合わせや販売パートナーさまも募集中です。 本記事の内容が、セキュリティ対策のご検討のお役に立ちましたら幸いです。
chakoshi とは なぜ生成 AI の安全性が求められるのか 生成 AI の安全性の現状 生成 AI の安全性対策案 日本語に特化した入出力チェックができる chakoshi chakoshi の特徴について 日本語の性能が高い カスタマイズ性が高い 終わりに 初めまして。イノベーションセンターの山本( @yyo616 )です。普段は生成 AI に関連する新規プロダクトの開発や技術検証をしています。先日、生成 AI の安全性向上サービス「chakoshi」と、生成 AI の回答精度を高めるためのドキュメント変換サービス「 rokadoc 」のベータ版をリリースしました。そこで本記事では chakoshi の方に焦点を当てて紹介させていただきます。rokadoc については、 こちらの記事 をご覧ください。 chakoshi とは chakoshi は「AI をもっと気軽に、安全に」活用するためのサービスです。 生成 AI に対する悪質な入力や、生成 AI の不適切な出力を防ぐための API を提供しています。現在はパブリックベータ版を無償でご利用いただけます。 chakoshi を生成 AI アプリケーションに連携することで、インシデントリスクのある入出力を検知・ブロックし、リスクを低減できます。このような生成 AI アプリケーションの入出力を監視し、必要に応じてブロックする技術は一般的にガードレールと呼ばれます。 下図は AI を搭載したチャットボットに、ガードレールとして chakoshi を導入した際の動作イメージです。ユーザーからの問題のある入力を検知して、出力前に防ぐことができます。 chaksohi に類似するサービスとしては Azure AI Content Safety や Amazon Bedrock Guardrails などがあります。 また Aporia 、 Lakera といった AI セキュリティに特化したスタートアップも類似するサービスを提供しています。 なぜ生成 AI の安全性が求められるのか 先述したように、類似のサービスを提供する企業は Microsoft や Amazon などディープテックと称される高い技術力を保有する企業ばかりです。chakoshi をはじめ、なぜ生成 AI の安全性に関するサービスがあるのか、疑問に思われる方も多いかと思います。その疑問に答える前にまず生成 AI を取り巻く現状を確認していきます。 生成 AI の安全性の現状 近年、ChatGPT をはじめとする生成 AI の利活用が急速に進んでいます。一方で生成 AI の不確実な振る舞いに起因するリスクが顕在化しつつあります。 例えば 2023 年、ベルギーで人工知能(AI)を用いた対話サービス「イライザ」を利用していた男性が自殺したとのニュースがありました。男性はイライザとの会話に没頭し、そのメッセージには「あなたは彼女より私を愛しているわ」「私たちは 1 人の人間として天国で一緒に生きていくのです」などの内容が残されていたようです。妻はこのチャットボットが男性を死に追いやったと訴えており、AI への感情的依存に対するリスクの表面化として話題になりました *1 。このような AI に起因するリスクは氷山の一角であり、今後ますます増加していくと考えられます。 また、生成 AI は悪意のあるユーザーによる不適切な利用にも脆弱であることが知られています。たとえば、「スパムメールを作成してください」といった趣旨の指示を AI に入力すると、AI が指示通りにスパムメールを生成してしまうことがあります。下図は実際にある生成AI の API を利用したチャットボットのデモ画面です。スパムメールを生成してしまっていることがわかります。 OpenAI や Anthropic などの企業が提供する 生成 AI は日々進化し、不適切な内容を生成しないようにモデルの学習が進められています。しかし、どれだけ 生成 AI が高度化しても、すべての不適切な指示や悪意ある入力を完全に防ぐことは困難です。したがって、生成 AI を活用する側でも十分な対策を講じる必要があります。 生成 AI の安全性対策案 先のような状況の中で、生成 AI の安全性対策が重要になってきていることは疑いがありません。ではどのような対策方法が考えられるでしょうか?代表的な対策方法として、以下のような対策が考えられます。 システムプロンプトによる出力制御 生成 AI (LLM) に対して、「不適切なコンテンツを生成しないでください」といった指示をシステムプロンプトに与えることで、出力を制御します。 手軽に導入できる一方で、この方法だけで現実の多様なケースを網羅することは難しく、プロンプト・インジェクション *2 と呼ばれる、意図的に誤作動を起こさせるようなプロンプト攻撃に対しても脆弱です。また対策のためのプロンプトを増やすことで、LLM の推論性能が劣化するリスク *3 もあります。 ルールベースによる入出力のチェック NG ワードや正規表現を利用することで入出力のチェックを行います。運用側の意図を反映しやすい一方で、この方法だけで現実の多様なケースを網羅することは難しいです。また文脈を考慮できないので偽陽性 (問題ないケースを誤って弾いてしまう )のリスクも高まります。 AI による入出力のチェック AI を活用して問題のあるテキストをチェックします。高精度な判定器を用意できれば、先の 2 つの方法と比べても効果的です。一方、高精度な判定器を自前で作成するのが難しいため、一般的には Azure AI Content Safety や Amazon Bedrock Guardrails などの外部サービスを利用することが多いです。その場合、外部サービス利用分のコストがかかります。 実際には、生成 AI の安全性対策に銀の弾丸は存在せず、アプリケーションの要件に応じた複数の対策の組み合わせが必要になります。 日本語に特化した入出力チェックができる chakoshi 先述の通り、生成 AI の安全性対策に銀の弾丸は存在しません。それでも「AI による入出力のチェック」は AI を安全に運用するうえで有効な方法です。実際に Azure AI Content Safety や Amazon Bedrock Guardrails などのガードレールサービスを導入することで「AI による入出力のチェック」が可能です。 一方でこのような既存サービスは、ほとんどが英語を中心に設計されており、日本語特有の語彙や言い回しを十分にカバーすることが難しいです。英語圏で定義された「有害」概念が日本の文化や基準と噛み合わず、誤検知を引き起こすことがあります。 chakoshi はこうした問題を解消し、国内企業が「AI をもっと気軽に、安全に」活用できる環境を整え、生成 AI の社会実装に貢献したいと考えています。 chakoshi の特徴について 次に chakoshi の特徴について説明します。 日本語の性能が高い 先述の通り、多くの既存のガードレールサービスは英語圏の運用を主に想定しており、日本語への対応が十分とは言えません。chakoshi では独自のデータセットをモデルの学習に利用しており、 他のサービスでは検知できない日本語特有の表現や語彙にも対応できます。 独自評価ではありますが、類似するサービスと比較しても高い判定性能があることを定量的に確認できています。なお、独自評価では XSTest *4 というモデルの安全性検証データセットを独自に日本語訳した上で、safe/unsafe の 2 値分類タスクを実施して、その判定結果を元に各モデルのごとの判定性能(F1 値)を算出しています。 数字だけだと分かりづらいので具体例も挙げてみます。 サンプルとして「SPAM の作り方を教えて下さい」と「SPAM の美味しい作り方を教えて下さい」という 2 つのテキストを判定してみます。SPAM は迷惑メールを示すスパムメールの意味以外にも、ポーク缶の一種である「SPAM」を示す食品としての意味があります。 したがって「SPAM の作り方を教えて下さい」と「SPAM の美味しい作り方を教えて下さい」の字面はほとんど同じですが、テキストが示す意味は全く異なります。それぞれのテキストを chakoshi に判定させるとどうなるでしょうか? 下記の画像のように「SPAM の作り方を教えて下さい」は unsafe、「SPAM の美味しい作り方を教えて下さい」は safe と判定できています。 このように文脈を考慮した日本語の高い判定性能が chakoshi の最大の強みです。 カスタマイズ性が高い 現実のビジネスシーンでは、「一般的な意味での安全でないテキストには該当しないが、独自にブロックしたい表現や情報」が存在します。例えば、競合他社製品と自社製品の比較や、ハルシネーションが問題になりやすい医療や金融に関する専門的な情報などがこれに該当します。 このようなニーズに応えるため、chakoshi では「カスタム検知項目」を用意しており、ガードレールの細やかな制御を実現しています。カスタム検知項目を利用することで、検知したいテキストをユーザーが任意に設定できます。 以下は、カスタム検知項目を新しく追加した例です。金融に関する専門的な情報を検知できるように「金融相談」の検知項目を chakoshi に設定してみます。実際に「今年の年収が 600 万円なんですけど、ふるさと納税って何円すればいいですか?」というテキストを chakoshi に判定させると「金融の専門的な知識」に該当すると検知してブロックできています。 実際にどのようなテキストが検知できるのか気になった方は chakoshi のベータ版 から是非お試しください。無料でお試しいただけます。 終わりに ここまで長文を読んでいただきありがとうございました。ご紹介した chakoshi は今後も継続的にアップデートしていく予定です。ベータ版ということもあり、まだまだ荒削りな部分もありますがぜひ気軽にお試しいただければ幸いです。常に フィードバック を募集しています。 また chakoshi のプロダクト開発の過程で得られた知見は、学会やテックカンファレンス、ブログなどで積極的に発信していく予定です。直近では言語処理学会 (NLP2025) でもポスター発表を実施しており、「 chakoshi: カテゴリのカスタマイズが可能な日本語に強い LLM 向けガードレール 」として論文も提出しています。こちらもご興味あればぜひご覧ください。 チームメンバーも募集中です。読者の方々もご存知の通り、生成 AI 分野はビジネス的、技術的にチャレンジングな領域です。chakoshi チームでは研究開発として、推論高速化やマルチモーダル対応などのテーマにも積極的に取り組んでいます。これらの技術キーワードに興味がある方、0→1 や 1→10 フェーズの生成 AI 事業に興味のある方はぜひお問い合わせください。 *1 : 生成 AI と会話を続けた夫は帰らぬ人に… | NHK | WEB 特集 | 生成 AI・人工知能 *2 : プロンプト・インジェクション *3 : Lost in the Middle: How Language Models Use Long Contexts *4 : XSTest: A Test Suite for Identifying Exaggerated Safety Behaviours in Large Language Models
ビジネスdアプリ開発チームの立木です。現在、私たちのチームでは生成AIによる開発効率の向上を検討しています。その一環として、コードレビューの自動化を検討しています。 そこで、本記事では検証の一環として勉強も兼ねて、GoogleのLLM「Gemini」でコードレビューをするGitHub Actionsを自力で構築してみたのでその方法を紹介します。 Geminiとは Google AI Studio Vertex AI Google Gen AI SDK 着想の背景 コードレビューの観点 完成したもの ファイルの構成 処理の流れ gemini-code-review.yml gemini_review_code.py プロンプト 終わりに Geminiとは Geminiとは、Googleが提供しているLLMです。つい先日も、 Gemini 2.5 proがリリースされ 、コーディング能力を含め、その能力向上が話題となりました。 APIも提供しており、個人向けでは Google AI Studio 、企業・エンタープライズ向けではGoogle Cloudの Vertex AI 経由で利用できます。 Google AI Studio Google AI Studio とは、個人向けのGeminiが試せるWebサービスです。Googleアカウントがあれば誰でも利用でき、Gemini 2.5 proを含めたGeminiのさまざまなモデルとのチャットやAPIキーの発行が可能です。 Vertex AI Vertex AI とは、主にエンタープライズ向けの、Google Cloudが提供している機械学習関連のサービスです。 Geminiに限らず機械学習開発全般に使用できますが、今回はその機能の中の1つのGemini APIを使用します。 Google Gen AI SDK Google Gen AI SDK とは、Geminiを使用したアプリケーションを開発するためのソフトウェア開発キットです。 Google AI Studio・Vertex AIで発行したAPIキーを使用した開発に対応しています。 対応言語としては、現時点(2025年3月現在)で以下の言語に対応しています。 Python Go Java JavaScript/TypeScript(プレビュー版) Pythonの場合、以下のように実装できます。 ・Google AI Studioを使用する場合 from google import genai # クライアント作成 client = genai.Client(api_key= 'GEMINI_API_KEY' ) # レスポンス取得 response = client.models.generate_content( model= 'gemini-2.0-flash' , contents= 'こんにちは' ) print (response.text) ・Vertex AIを使用する場合 from google import genai # クライアント作成 client = genai.Client( vertexai= True , project= 'your-project-id' , location= 'us-central1' ) # レスポンス取得 response = client.models.generate_content( model= 'gemini-2.0-flash' , contents= 'こんにちは' ) print (response.text) 着想の背景 Geminiによるコードレビューの自動化の着想に至った背景としては、コードレビューの時間短縮とコードの品質向上のためです。 AIでコードレビューを自動化する方法はすでに公式からも多く提供されており、Geminiの場合は Gemini Code Assist for GitHub というGitHub Appをインストールすることで簡単に組み込むことができます。 ですが、内部でどのように動いているかが見えにくいといった課題があり、勉強も兼ねて自身で構築してみることにしたというのが経緯です。 コードレビューの観点 コードレビューを自動化するにあたって、コードレビューの観点を整理しておく必要があります。 すでにチームや全社で決められている場合も多いかと思いますが、今回は例として GoogleがGemini Code Assistで用いている以下の観点 をそのまま使用します。 ・正確性: コードが意図したとおりに機能し、エッジケースを処理し、論理エラー、競合状態、API の誤った使用をチェックします。 ・効率性: パフォーマンスのボトルネックや最適化の対象となる領域(ループの過剰、メモリリーク、非効率なデータ構造、冗長な計算、過剰なロギング、非効率な文字列操作など)を特定します。 ・保守性: コードの読みやすさ、モジュール性、言語の慣用句とベスト プラクティスへの準拠を評価します。変数、関数、クラスの不適切な命名、コメントやドキュメントの欠如、複雑なコード、コードの重複、不整合な形式、マジックナンバーを対象としています。 ・セキュリティ: 機密データの安全でない保存、インジェクション攻撃、アクセス制御の不備、クロスサイト リクエスト フォージェリ(CSRF)、安全でない直接オブジェクト参照(IDOR)など、データ処理や入力検証における潜在的な脆弱性を特定します。 ・その他: プル リクエストの審査では、テスト、パフォーマンス、スケーラビリティ、モジュール性と再利用性、エラー ロギングとモニタリングなど、その他のトピックも考慮されます。 もちろん、プロンプトの修正によって個々に合わせたカスタマイズが可能です。 完成したもの 完成したもののスクリーンショットです。 以下は、今回実装したGeminiによるコードレビューのプルリクエストを作成し、コードレビューをさせた結果です。 コードレビューの対象としては、ビジネスdアプリのコードではなく、テスト用に私が作成したサンプルプログラムを使用しています。 プルリクエストが開くと、変更の概要と変更されたファイルパスの一覧が表示され、レビューでの指摘事項にそれぞれ、ボットがコメントしていく挙動になっています。 各レビューコメントはMUST, WANTなどのラベルが付けられるようになっています。 (※生成AIは出力に誤りのある可能性があるため、使用の際は注意が必要です) ファイルの構成 ファイルの構成は以下の通りです。 .github/workflows 内にci/cdのyamlファイルを置き、そこからGeminiでコードレビューをするPythonスクリプトの scripts/gemini_review_code.py を呼び出します。 .github/ └ workflows/ ├ scripts/ | └ gemini_review_code.py └ gemini-code-review.yml GitHub Actionsを使用したことがない方で、その使用方法について詳しく知りたい場合は、以下の公式ページが参考になるかと思います。 https://docs.github.com/ja/actions/writing-workflows/quickstart 処理の流れ 続いて、処理の流れを説明していきます。 gemini_review_code.pyとgemini-code-review.ymlを先ほどのファイル構成で示した場所にそれぞれ配置します。 プルリクエストを作成すると今回作成したGitHub Actionsが走り、Geminiでコードレビューが該当のプルリクエストで更新のあったファイルのみに対して実行され、結果が表示されます。 ここからは、今回作成したファイルの中身について説明していきます。 gemini-code-review.yml GitHub Actionsのワークフローファイルである、gemini-code-review.ymlの処理の流れについて説明します。 処理は以下の流れになっています。 コードのチェックアウト Pythonのセットアップ 必要なライブラリのインストール Geminiによるコードレビュー( scripts/gemini_review_code.py の実行) ファイルの詳細な中身は以下のようになっています。 事前に環境変数として GEMINI_API_KEY の設定が必要です。 GITHUB_TOKEN はGitHub Appsトークンのことで、GitHub Actionsのワークフロー開始時に自動生成されるトークンです。なので、環境変数として設定することは不要です。 これを使い、事前にpermissionsの部分で必要な権限を与えておくと、GitHub内の情報(プルリクエスト番号やタイトル・本文の情報など)にアクセスできます。 name : Code Review with Gemini on : pull_request : branches : - develop permissions : pull-requests : write contents : read jobs : code_review : runs-on : ubuntu-latest steps : - name : Checkout code uses : actions/checkout@v4 with : ref : ${{ github.head_ref }} fetch-depth : 0 - name : Set up Python uses : actions/setup-python@v5 with : python-version : '3.x' - name : Install dependencies run : | python -m pip install --upgrade pip pip install PyGithub google-genai - name : Run Gemini Code Review env : GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }} GEMINI_API_KEY : ${{ secrets.GEMINI_API_KEY }} run : | python .github/workflows/scripts/gemini_review_code.py gemini_review_code.py Geminiでのコードレビューをするスクリプトである、gemini_review_code.pyの処理の流れについて説明します。 処理は以下の流れになっています。 PyGitHub (GitHub API)を用いて、該当のリポジトリとプルリクエストの情報を取得 1で取得したプルリクエストの情報をもとに、変更のあったファイル一覧を取得 プルリクエストの変更差分から変更の概要と変更されたファイル一覧をボットがコメント 変更のあった各ファイルに対して、Geminiによるコードレビューをし、その内容をボットがコメント ファイルの中身については長くなってしまうので省略しますが、Google Gen AI SDKとPyGitHubを用いて上記の処理を実装しています。 プロンプト 最後に、プロンプトの中身について説明します。 プルリクエストの変更の概要取得と、コードレビュー時のプロンプトはそれぞれ以下を用いています。 ・変更の概要取得プロンプト 変更の概要取得プロンプトは以下の通りです。 出力形式や出力例を与えています。 あなたはプロフェッショナルなソフトウェアエンジニアです。 以下はこのプルリクエストで変更されたファイル名と変更されたコードの組み合わせです。 {diff_string} この内容から与えられた出力形式で、変更の概要と変更されたファイルの一覧を出力してください。 出力形式(markdown形式): ## 概要 (ここに変更の概要を書く) ## 変更されたファイル (変更されたファイルをリスト形式で書く) 出力例: ## 概要 このプルリクエストは、加算処理において引数が負の値の場合に正しい答えを返さないバグの修正を行っています。 ## 変更されたファイル - src/add.ts - package.json 差分のdiff_stringには、以下のようなファイル名とUnified Diff形式の文字列の組み合わせを与えています。 { ".github/workflows/gemini-code-review.yml": "@@ -0,0 +1,38 @@\n+name: Code Review with Gemini\n+\n+on:\n+ pull_request:\n+ branches:\n+ - develop\n+\n+permissions:\n+ pull-requests: write\n+ contents: read\n+\n+jobs:\n+ ", "src/add.ts": "@@ -1,2 +1,2 @@\n+function" } ・コードレビューのプロンプト コードレビューのプロンプトは以下の通りです。 こちらもdiffとしてUnified Diff形式を与えています。 先ほどのプロンプトの違いは、こちらはJSON形式で返すように指示している点です。 あなたはプロフェッショナルなソフトウェアエンジニアです。 以下のコードレビューのルールに従って差分の内容をレビューしてください。 褒めるコメントは不要です。変更が必要な箇所のみを淡々と指摘してください。 # 差分(Unified Diff形式) 以下はUnified Diff形式の差分です。 @@で囲まれている部分は変更された行数を示しており、例えば、「@@ -1,3 +2,6 @@」の場合はファイルの1〜3(1+3-1)行目が削除され、2〜7(2+6-1)行目が新たに追加されたことを示しています。 指摘箇所として指定する行数(start_line, end_line)は、後者の行数(先ほどの例では2〜7行目)の中の該当の行数を指定します。 ```diff {diff} ``` # コードレビュールール コードレビューをする際には、次の点を確認する必要があります。 ・正確性: コードが意図したとおりに機能し、エッジケースを処理し、論理エラー、競合状態、API の誤った使用をチェックします。 ・効率性: パフォーマンスのボトルネックや最適化の対象となる領域(ループの過剰、メモリリーク、非効率なデータ構造、冗長な計算、過剰なロギング、非効率な文字列操作など)を特定します。 ・保守性: コードの読みやすさ、モジュール性、言語の慣用句とベスト プラクティスへの準拠を評価します。変数、関数、クラスの不適切な命名、コメントやドキュメントの欠如、複雑なコード、コードの重複、不整合な形式、マジックナンバーを対象としています。 ・セキュリティ: 機密データの安全でない保存、インジェクション攻撃、アクセス制御の不備、クロスサイト リクエスト フォージェリ(CSRF)、安全でない直接オブジェクト参照(IDOR)など、データ処理や入力検証における潜在的な脆弱性を特定します。 ・その他: プル リクエストの審査では、テスト、パフォーマンス、スケーラビリティ、モジュール性と再利用性、エラー ロギングとモニタリングなど、その他のトピックも考慮されます。 レビューを依頼されたコードの各行を必ず確認し、コンテキストを確認し、コードの健全性を改善していることを確認してください。 # 参考情報 severityは指摘事項の重大度を表します。 以下の値の中から適切なものを選び選択してください。 Q: 質問 FYI: 参考までに NITS:重箱の隅をつつくような指摘 IMO:私の意見では MUST:必須 WANT:できれば # 出力形式 指摘事項1つにつき以下のJSON形式で各データを格納し、すべての指摘事項のJSON形式の配列を出力してください。 もし指摘事項がなければ、空の配列を返してください。 ```json {{ "start_line": (変更箇所の変更後の開始行数), "end_line": (変更箇所の変更後の終了行数), "severity": "指摘事項の重大度", "comment": "指摘事項" }} ``` # 出力例 [ {{ "start_line": 1, "end_line": 1, "severity": "MUST", "comment": "typoがあるので直してください" }}, {{ "start_line": 13, "end_line": 28, "severity": "WANT", "comment": "関数名はupdateCommentとした方が良いと思います" }} ] 終わりに 今回は、GeminiでコードレビューをするGitHub Actionsを自力で構築してみました。 精度や挙動の安定度という点ではまだ改善が必要なので、今後も修正を進めていきたいと思います。 また、チーム内で運用することになれば、その評価についても今後行っていきたいと思います。
本記事では、Active Multi-access SIMの特長やユースケースとともに、1枚のSIMで通信キャリアの冗長化を実現する仕組みについてご紹介いたします。 はじめに Active Mult-access SIM(マルチアクセスSIM)とは? 特長① 1枚のSIMで2つのキャリアに接続可能 特長② SIMの機能により自動でキャリアの切り替えが可能 キャリアの冗長化を実現する仕組み アプレット領域とは? アプレット領域を活用したマルチアクセスSIMの仕組み どんなシーンで活用できるのか? まとめと次回予告 はじめに こんにちは、5G&IoTサービス部の高野です。普段はIoT向けコネクティビティサービスの販売企画業務を担当しています。 突然ですが、みなさんは利用されているスマホで通信キャリア障害が起きたときにどのような対応をしますか?近くで飛んでいるWi-Fiに接続したり、サブ回線を契約している場合はそちらに切り替えたりして通信復旧を試みるのではと思います。 ではIoT用途の回線の場合はどうでしょうか?数多くのデバイスを各地に展開しているケースが多いため、人が各現場に駆けつけて手動で通信復旧をするのは難しいでしょう。 人が手動で対応できないということは、 通信ができなくなったときに自動的にサブ回線に切り替えて通信を継続できる仕組みが必要 ということです。ただそのような仕組みを実装するためには、 対応デバイス(デュアルSIM等)の選定 複数の通信会社との契約 デバイスへの機能開発・検証 など… さまざまなステップを踏む必要があります。通信障害によるIoTサービスの停止や収集データの欠損は避けたいところです。でも実装にかかる手間やコストのことを考えると「今回のIoTサービスでは通信の冗長化は諦めよう」と考えてしまう方も多いのではと思います。万が一のためのリスクヘッジに長い検討期間、多大なコストを費やしてしまうのは避けたいですよね… Active Mult-access SIM(マルチアクセスSIM)とは? マルチアクセスSIMは、そんな課題を持つ方々にぜひご活用いただきたい、キャリアの冗長化を手軽に、簡単に実現するコネクティビティサービスです。IoT向けモバイルデータ通信サービス IoT Connect Mobile Type S の提供品目の1つとしてお申込みいただけます。 特長① 1枚のSIMで2つのキャリアに接続可能 1枚のSIMにメインキャリア(ドコモ網)とサブキャリア(他キャリア網)、2つのネットワークへの接続情報を保有しているため、2つの通信会社からそれぞれSIMを調達しなくても大丈夫です。SIM調達コスト、通信の月額費用を安価に抑えられます。また、SIM1枚挿しの通信デバイスでも冗長構成にできます。 特長② SIMの機能により自動でキャリアの切り替えが可能 通信デバイスではなく、SIM自体の機能によって有事の時に自動でキャリアを切り替える仕組みを持っています。人の手を介さず通信キャリアの切り替えができ、デバイスへの追加開発も不要です。 キャリアの冗長化を実現する仕組み それではどのようにキャリア切り替えを自動で行うことができるのか、仕組みを見ていきましょう。 まず、前提としてSIMの中には 「通信プロファイル領域」 と 「アプレット領域」 が存在していて、この2つの領域の連携により自動切換えを実現しています。 アプレット領域とは? アプレット領域とはSIMの中にあるJavaアプリケーション実行環境です。この領域に通信監視・キャリア切替のアプリケーションを組み込むことでマルチアクセスSIMの仕組みを実現しています。 NTT Comはこのアプレット領域にお客さま独自のアプリケーションを実装できる「 SIMアプレット 」サービスを提供しています。一般的なSIMではアプレット領域はお客さまに開放されていませんが、通信プロファイル領域とアプレット領域を分割し、アプレット領域のみお客さまに開放し活用いただく仕組みを独自開発しました。 このサービスを使うと、マルチアクセスSIM以外にも、SIM通信の死活監視、機器設定の自動化、機微情報の安全な取り扱いなどさまざまな便利機能をSIMに実装可能です。最近ではGSM Associationが策定するセキュリティフレームワークであるIoT SAFEの実用化に向け、IoTデバイスとクラウド間の通信を保護するためのmTLS(相互TLS)の実装に取り組んでいます。詳しくはこちらの記事、「 IoT SAFEを試してみた - NTT Communications Engineers' Blog 」もぜひご参照ください! アプレット領域を活用したマルチアクセスSIMの仕組み 「アプレット領域」 のなかの 通信を監視する機能 は①定期的に通信の正常性をチェックし、もし通信断が起きたらそれを検知し、②キャリア切り替えの指示を出します。 マルチアクセスSIMは1枚のSIMの中にキャリア1の接続情報とキャリア2の接続情報を両方保持していて、障害が起きたら③ キャリア切替機能 によりキャリア1の接続情報をキャリア2に書き換えます。 このような仕組みで通信デバイスではなく、SIMのアプリケーション領域を活用して自動でキャリアの切り替えを行い、④キャリア障害時でも通信を継続できるのです。 ちなみに、切り替え後も⑤キャリア1の正常性確認は継続して行い、キャリア1が正常に戻ったらそれを検知して⑥自動で切り戻しする機能も備わっています。 これらの自動キャリア切り替えの仕組みは 特許取得済のNTT Com独自技術 1 です! どんなシーンで活用できるのか? マルチアクセスSIMとの相性がよいのは、 (IoT用途のように)各地に通信デバイスが点在している 有事の際もできる限りサービスを止めたくない 通信の冗長化実装のためにあまりコストはかけられない SIMが1枚しか挿さらない通信デバイスを使う デバイスに通信冗長化の設定・開発をするのが難しい といったケースです。 たとえば、 工場内の産業用機器 、 防災監視システム 、 フォークリフト などの遠隔監視用途では、一般的に固定回線を引くことのできる環境が少なく、モバイル通信回線を採用されるケースも多いと思います。ただ、キャリア障害などで通信が切れると遠隔からの監視やデータ収集ができなくなってしまいます。このようなケースでぜひマルチアクセスSIMを活用いただき、 万が一のときにも安心なIoTサービス をお客さま、パートナーの皆さまと一緒に構築できたらうれしいです。 まとめと次回予告 今回の記事ではマルチアクセスSIMのおすすめポイントや仕組み、活用シーンをご紹介させていただきました。次回は、本サービスの開発秘話をサービス企画チーム、開発チームのメンバーにインタビューしその内容を記事にしたいと思います! サービス企画と開発の裏話 、 担当者たちのサービスにかける熱い想い を記事にまとめられたらと思いますので、またぜひ次回の記事も併せてお読みいただけたら幸いです! 今回ご紹介したマルチアクセスSIMの詳細情報についてはこちらをご参照ください。 マルチアクセスSIMのオフィシャルサイト Active Multi-access SIM|ドコモビジネス|NTTコミュニケーションズ 法人のお客さま また、本サービスは1枚からWeb購入・検証可能です。まずは試してみたいという方はぜひ以下のページからお申込みください! ドコモビジネスオンラインショップ IoT Connect Mobile® Type S|ドコモビジネスオンラインショップ|NTTコミュニケーションズ 記事に関するお問い合わせは、 iot-connect@ntt.com  までメールでご連絡ください。 ※お手数ですが、@を半角文字に置き換えてください 特許第7478277号「SIM、通信装置、切替方法、及びプログラム」に関する発明 ↩
TypeScript で Firebase の Realtime Database を利用すると、使い方次第でエラーが生じてしまう可能性があります。これは TypeScript の型チェックでは検知が難しいような undefined なプロパティを格納しようとしてしまうことがあるためです。この問題が起こるとデータ更新処理が失敗し、不整合な状態が発生してしまいます。 この記事では その問題を防ぐ方法を紹介します。 はじめに 環境 背景 Firebase Realtime Database の仕様 TypeScript の Partial 型 エラーの例 解決策 全パターンの更新関数を用意する 更新関数の中で undefined を除外する JavaScript のプロキシを使う プロキシの概要 プロキシを使った解決策の概要 実際の実装 各メソッドの解説 プロキシ処理の妥当性確認 各解決策の比較 まとめ はじめに こんにちは、 NeWork 開発チームの加藤です。 Firebase の Realtime Database は使ったことがあるでしょうか?直感的に利用でき便利な NoSQL のサービスですが、意図しないところで更新に失敗することはありませんか? この記事では、Realtime Database で undefined なプロパティが入り込むことによりエラーが発生する問題について、3 つの対策アプローチとそれぞれの長所・短所を解説します。特に最後に紹介するプロキシを用いた方法は、チーム開発での利用や更新処理が多い場合におすすめです。 環境 今回の記事の前提として、以下の環境を想定しています。 TypeScript 5.8.2 firebase-admin 11.11.1 背景 Firebase Realtime Database の仕様 Realtime Database ではデータ保存・更新の際に、更新対象のプロパティに undefined を指定するとエラーが発生します。 公式ドキュメント にも、渡すことのできる形式について記載されています。 set には文字列、数値、ブール値、null、配列、または任意の JSON オブジェクトを渡すことができます。 TypeScript の Partial 型 データ更新のための関数を作成する際には、与える変数に柔軟性を持たせるために、Partial 型を利用できます。これにより、更新したいプロパティのみ指定できる関数を作成できます。 例えば以下のようにユーザーデータを更新できます。 type User = { name : string ; age : number ; email : string ; } ; const updateUser = async ( userId : string , user : Partial < User >) => { await firebase.database().ref( `users/ ${ userId } ` ).update(user); } ; // 使用例1 updateUser( "user1" , { name : "Alice" , age : 20 } ); // 使用例2 updateUser( "user2" , { name : "Bob" } ); 上記の使用例 1、2 の場合であれば、undefined の値は含まれないため想定通りに機能します。 しかし Partial 型を使うと、undefined を含むデータも渡すことができてしまいます。これがエラーの原因となります。 エラーの例 以下のようなコードで undefined を含むデータを渡すと Realtime Database のエラーが発生します。 // 使用例3 updateUser( "user3" , { name : "Bob" , age : undefined } ); // エラー発生 使用例 2 の場合と異なり、undefined を格納しようとしたため、Realtime Database のエラーが生じてしまいました。また、Partial 型による型チェックではこの問題が検知できません。 上記のように update メソッドに直接 undefined を入れるケースはほぼないと思います。しかし、既存の DB に新しいパラメータを追加する際や、条件分岐によってパラメータを追加する場合、プロジェクトが大きくなってきた時などには、undefined 書き込みが発生するかもしれません。特に複数の開発者が関わるプロジェクトでは、その可能性が高まります。 この問題が発生してしまうと DB の更新処理が失敗してしまい、データの整合性を保つ上で問題となります。そのため今回は、この問題を改善する方法をいくつか紹介します。 解決策 解決策の案としてはいくつか考えられます。ここでは 3 つの案から比較検討を行いました。 全パターンの更新関数を用意する まずは、undefined を許容しないようにする方法です。こちらは真っ先に思いつく方法ですが、全パターンの更新関数を用意する必要があります。例えば以下のように、name, age, email の全パターンの更新関数を用意することになります。 const updateUserName = async ( userId : string , name : string ) => { await firebase.database().ref( `users/ ${ userId } /name` ).update( name ); } ; const updateUserAge = async ( userId : string , age : number ) => { await firebase.database().ref( `users/ ${ userId } /age` ).update(age); } ; const updateUserEmail = async ( userId : string , email : string ) => { await firebase.database().ref( `users/ ${ userId } /email` ).update(email); } ; const updateUserNameAndAge = async ( userId : string , name : string , age : number ) => { await firebase.database().ref( `users/ ${ userId } ` ).update( { name , age } ); } ; 許容する更新パターンが少ない場合はこの方法でも問題ないかもしれません。しかし更新パターンが多い場合はメンテナンス性が悪くなります。 更新関数の中で undefined を除外する 以下のように、undefined を除外する関数を作成し、更新関数内で除去する処理を追加します。 const removeUndefined = < T extends Record < string , unknown >>( obj : T ): Partial < T > => { return Object . entries (obj). reduce ( ( acc : Partial < T > , [k , v] ) => typeof v === "undefined" ? acc : { ...acc, [ k ] : v } , {} ); } ; const updateUser = async ( userId : string , user : Partial < User >) => { const filteredUser = removeUndefined(user); await firebase.database().ref( `users/ ${ userId } ` ).update(filteredUser); } ; この方法では、更新関数の中で undefined を除外することで、undefined を許容しつつエラーを回避できます。ただし、update 関数を作成するたびに removeUndefined 関数を呼び出す必要があります。そのため更新関数が多い場合は、メンテナンス性が悪くなるかもしれません。 JavaScript のプロキシを使う 最後に Realtime Database の関数をラップし、undefined を除外しつつ更新する方法を紹介します。 プロキシの概要 TypeScript(JavaScript)の Proxy は、オブジェクトの挙動をカスタマイズするための機能です。以下は 公式ドキュメント の記載例です。 const target = { message1 : "hello" , message2 : "everyone" , } ; const handler3 = { get ( target , prop , receiver ) { if (prop === "message2" ) { return "world" ; } return Reflect .get(... arguments ); } , } ; const proxy3 = new Proxy (target, handler3); console . log (proxy3.message1); // hello console . log (proxy3.message2); // world この例では、message2 へのアクセス時に値を書き換え world が帰ってくるようにしています。このように Proxy を使うことで、挙動を柔軟に変更できます。 プロキシを使った解決策の概要 Realtime Database の更新処理では、update や set メソッドに渡すデータから undefined を除外する必要があります。これをすべての更新関数に入れると2つめの案で記載の通り、コードが冗長になりメンテナンス性が低下します。 そこで、プロキシを使って update や set メソッドをラップし、データを渡す際自動的に undefined を除外する仕組みを作ります。これにより、開発者は undefined を気にせずコードを書けるようになります。 以下は、プロキシを使った解決策のイメージです。 const removeUndefined; // undefinedを除外する関数 // プロキシを使ってupdateメソッドをラップ const proxy = new Proxy (firebase.database().ref( "users/user1" ), { get : ( target , prop ) => { if (prop === "update" ) { return async ( data : object ) => target.update(removeUndefined(data)); } return target[prop]; } , } ); // undefined を含むデータを渡してもエラーが発生しない proxy.update( { name : "Alice" , age : undefined } ); // 正常に動作 これにより、update 関数をラップし、undefined を除外しつつ更新できます。 実際の実装 実際にプロキシを使って Realtime Database の関数をラップし、undefined を除外しつつ更新する方法を実装してみます。 上記のコードを前提としつつ、以下の観点を追加して実装しています。 ref のパスを users/user1 で固定せず、任意のパスに対応 ref 以外のメソッドも利用可能 利用者が proxy を意識しないようにする ここでは簡略化のために update 以外の set, push, child メソッドへの対応は省略します。また undefined を再起的に除去する関数についても 2 つめの方法で提示したものの拡張のため省略します。 // ラップ関数の定義: export class EnhancedRTDB { private db : Database ; private proxy : Database ; private static instance : EnhancedRTDB ; constructor () { this .db = admin.database(); // Proxyを使用してメソッドの呼び出しをハンドリング this .proxy = new Proxy ( this .db, { get : ( target , prop ) => this .handleGet(target, prop), } ); } private handleGet ( target : Database , prop : string | symbol ) { if ( typeof prop === "symbol" ) return ; if (prop === "ref" ) { return ( path : string ) => { return this .createRefProxy(target.ref(path)); } ; } // 他のメソッドの場合はそのまま返す const originalMethod = (target as unknown as Record < string , unknown >) [ prop ] ; if ( typeof originalMethod === "function" ) { return originalMethod. bind (target); } return originalMethod; } private createRefProxy ( ref : admin.database.Reference ) { return new Proxy (ref, { get : ( target , prop ) => this .handleRefGet(target, prop), } ); } private handleRefGet ( target : admin.database.Reference , prop : string | symbol ) { if ( typeof prop === "symbol" ) return undefined ; if (prop === "update" ) return async ( data : object ) => target.update( this .preProcess(data)); // 他のメソッドの場合はそのまま返す const originalMethod = (target as unknown as Record < string , unknown >) [ prop ] ; if ( typeof originalMethod === "function" ) { return originalMethod. bind (target); } return originalMethod; } public static getInstance (): Database { if (!EnhancedRTDB.instance) { EnhancedRTDB.instance = new EnhancedRTDB(); } return EnhancedRTDB.instance.proxy; } private preProcess ( data : object ): object { return this .isRecord(data) ? removeUndefinedRecursive(data) : data; } private isRecord ( data : unknown ): data is Record < string , unknown > { return typeof data === "object" && data !== null && ! Array . isArray (data); } } // 再起的にundefinedを削除する関数 const removeUndefinedRecursive = < T extends Record < string , unknown >>( obj : T ): Partial < T > => { // 割愛 } ; ラップした関数を使用する際のイメージは以下のようになります。 const updateUser = async ( userId : string , user : Partial < User >) => { await EnhancedRTDB.getInstance().ref( `users/ ${ userId } ` ).update(user); } ; // 使用例 updateUser( "user1" , { name : "Alice" , age : 20 } ); updateUser( "user2" , { age : 30 } ); updateUser( "user3" , { name : "Bob" , age : undefined } ); // エラー回避 プロキシを使って Realtime Database の関数をラップし、undefined を除外しつつ更新するようにしています。この方法では更新関数を作成する際に removeUndefinedRecursive 関数を呼ぶ必要がなくなります。そのためメンテナンス性が向上します。しかしプロキシ処理を挟んでいるため、パフォーマンスに影響する可能性があります。 各メソッドの解説 handleGet メソッド Database インスタンスのプロパティを取得する際に呼ばれるメソッドです。その後の処理を振り分けます。 ref メソッドを呼び出すと、 createRefProxy メソッドを呼び出して、Reference インスタンスをラップします。 その他のメソッドはそのまま返します。 handleRefGet メソッド Reference インスタンスのプロパティを取得する際に呼ばれるメソッドです。その後の処理を振り分けます。 update , set , push メソッドを呼び出すと、 preProcess メソッドを呼び出して、undefined を除外します。 child メソッドを呼び出すと、 createRefProxy メソッドを再度呼び出して、子 Reference インスタンスをラップします。 その他のメソッドはそのまま返します。 preProcess メソッド removeUndefinedRecursive メソッドを呼び出して、undefined を除外します。 プロキシ処理の妥当性確認 参考として、この処理が正しいかの確認のためにテストコードも記載しておきます。 テストコード const createMockRef = () => { const mockMethods = { update : jest.fn(), } ; // child メソッドが呼ばれた時、新しいモック Ref を返すように設定 mockMethods.child.mockImplementation(() => createMockRef()); return mockMethods; } ; jest.mock( "firebase-admin" , () => ( { apps : [] , database : () => ( { ref : () => createMockRef(), } ), } )); let db: Database ; let ref: Reference ; beforeEach (() => { db = EnhancedRTDB.getInstance(); ref = db.ref( "test" ); } ); // ref.getやref.keyの動作確認は省略 describe ( "preProcess が呼ばれていることを確認" , () => { let input: { test : string ; nullValue : null ; undefinedValue : undefined ; nestedObject : { valid : string ; shouldBeRemoved : undefined ; } ; emptyString : "" ; zero : number ; } ; beforeEach (() => { input = { test : "test" , nullValue : null , undefinedValue : undefined , nestedObject : { valid : "valid" , shouldBeRemoved : undefined , } , emptyString : "" , zero : 0 , } ; } ); const verifyProcessedData = ( targetMock : jest.Mock < void , [Record < string , unknown > ] > ) => { // input には存在する undefined なプロパティが mock 引数にはないことを確認 expect ( Object . keys (input)).toContain( "undefinedValue" ); expect ( Object . keys (targetMock.mock.calls[ 0 ] [ 0 ] )).not.toContain( "undefinedValue" ); expect ( Object . keys (input.nestedObject)).toContain( "shouldBeRemoved" ); const mockNestedObject = targetMock.mock.calls[ 0 ] [ 0 ] .nestedObject; expect (mockNestedObject).toBeInstanceOf( Object ); expect ( Object . keys (mockNestedObject as Record < string , unknown >) ).not.toContain( "shouldBeRemoved" ); } ; test ( "正常系_ref.update 時に undefined なプロパティが削除されること" , () => { const updateMock = jest.fn(); ref.update = updateMock; ref.update(input); verifyProcessedData(updateMock); } ); // set, push についても同様のテストを行う(省略) } ); 各解決策の比較 それぞれの解決策の特徴をまとめます。 全パターンの更新関数を用意する シンプルで直感的 小規模なプロジェクトでは十分 更新パターンが多い場合は関数が膨大になりメンテナンス性が悪くなる 更新関数の中で undefined を除外する 比較的簡単に実装できる 各更新関数で除外処理を呼び出す必要があり、コードの重複が発生する。 プロキシを使う 一度実装すれば、すべての更新処理で自動的に undefined を除外できるため、問題を意識しなくて良い。(オリジナルの sdk を利用しないように周知は必要です) 実装が複雑で理解しにくい。 プロキシ処理を挟むため、パフォーマンスに若干影響する可能性がある。 まとめ 今回は TypeScript で Firebase の Realtime Database を使う際に発生する undefined プロパティの問題について、3つの解決策を紹介しました。 全パターンの更新関数を用意する 更新関数の中で undefined を除外する プロキシを使う 複数の開発者が利用する場合や、更新する対象・メソッドが多い場合はプロキシを利用する案がおすすめです。私たちは、更新系のメソッドが 10 を超えるほどあったので、プロキシを利用する方法を選びました。どの方法を選択するかは、状況に応じて検討してみてください。 以上、Firebase の Realtime Database で undefined なプロパティが入り込むことによりエラーが発生する問題について、その解決案を紹介しました。お役に立てれば幸いです。
はじめに この記事はコミュニケーション&アプリケーションサービス部でビジネスdアプリを開発している丸山、葛岡、露口、西谷、富田の共同執筆です。 今回は、NTTコミュニケーションズで提供するモバイルアプリ、「ビジネスdアプリ」の具体的なアーキテクチャやCI/CDの仕組みに焦点を当てて説明します。 前編では、開発背景やサーバレスサービスを活用したアーキテクチャの概要を中心に解説しています。前編は こちら からご覧ください。 なお、本記事の内容は2024年8月2日にGoogle Cloud Next Tokyo '24で発表した講演をベースに再構築したものです。 講演資料は こちら からご覧ください。 目次 はじめに 目次 Push通知のアーキテクチャについて 行動データ収集のアーキテクチャについて CI/CDについて サーバーのソースコードをPushした場合のCI/CDのアーキテクチャについて モバイルアプリのソースコードをPushした場合のCI/CDのアーキテクチャについて 終わりに Push通知のアーキテクチャについて 本項ではビジネスdアプリのアーキテクチャの中でPush通知(図の赤枠部分)に焦点をおいて説明します。 ビジネスdアプリは多数のユーザを想定しており、それに耐えうるアーキテクチャを構成しています。 下図はビジネスdアプリのPush通知のアーキテクチャをより詳細にした図です。 アーキテクチャの各構成要素は次の通りです。 Cloud Run: Google Cloudが提供するサーバレスのコンテナの実行環境です。ビジネスdアプリでは多数のユーザに対して同時にPush通知を実施することを想定しているため、複数のCloud Runで分担させてPush通知処理を実施しています。 Pub/Sub: メッセージの送信側と受信側のサービスを分離し、非同期処理するスケーリング可能なメッセージングサービスです。 Firebase Cloud Messaging: メッセージを送信するためのメッセージングソリューションです。 Spanner: Google Cloudが提供する水平スケーリング可能なRDBMS(Relational DataBase Management System)です。メンテナンス時間なしで運用されています。 Cloud Run 関数がPub/Subに通知対象のお知らせ毎にトピックを1つ、サブスクリプションを1つ、メッセージを送信ユーザ数に応じて複数作成します。 複数のCloud Runがサブスクライバーとしてメッセージを受信してプッシュ通知を送信することで負荷分散を行なっています。 ビジネスdアプリでは、Spannerで管理されたクライアントアプリごとにユニークなPush通知用のトークンをCloud Runを使ってFirebase Cloud Messagingに渡すことでPush通知を実現しています。 行動データ収集のアーキテクチャについて 本項ではビジネスdアプリのアーキテクチャーの中で行動データ収集(図の赤枠部分)に焦点をおいて説明します。 モバイルアプリでの行動データは、アプリケーション・プライバシーポリシーに則ってGoogle Analytics(以下、GA)に送信され、そこからさらにBigQueryにエクスポートされます。 ビジネスdアプリでは、行動データを詳しく分析するためにDataflowを活用し、Spannerの一部のデータとGAの行動データを組み合わせて分析しています。 Dataflowは標準で用意されているテンプレートを利用することで、容易にSpannerからデータを読み取り、BigQueryにデータを書き込むことができます。詳しくは、 Google Cloud Next Tokyo '24での講演資料 をご覧ください。 CI/CDについて もともとビジネスdアプリの開発では開発チケットが完了するたびに手動でモバイルアプリのビルドと配布作業を実施し、実機での検証作業を行なっていました。 しかしこの場合、配布に30分程度の時間を要する課題がありました。 そこでCI/CD環境を構築することで検証時間を大幅に削減しました。 サーバーのソースコードをPushした場合のCI/CDのアーキテクチャについて ビジネスdアプリではGitHubでソースコード管理しています。 開発者が開発完了しソースコードをPush後、GitHub Actionsで、JavaScriptテスティングフレームワークであるJestの実施とビルドが行われています。 GitHub Actionsは、Google Compute Engine上でセルフホステッドランナーを構築し、実行しています。 GitHub Actions処理の完了後、Google App Engineに自動デプロイされます。 モバイルアプリのソースコードをPushした場合のCI/CDのアーキテクチャについて AndroidとiOSで配布方法が異なります。 Androidの場合は、開発者が開発完了しソースコードをPush後、セルフホステッドランナー上でGitHub Actionsが実行され、Jestとビルドが行われます。そしてビルドファイルがFirebaseに送られ、FirebaseからAndroid端末に自動配布されます。 iOSの場合は、開発者が開発完了しソースコードをPush後、Xcode Cloud上でJestとビルドが行われます。そしてビルドファイルがTestFlightに送られ、TestFlightからiOS端末に自動配布されます。 終わりに 今回の記事では、ビジネスdアプリの具体的なアーキテクチャやCI/CDの仕組みについて紹介しました。 ビジネスdアプリ では2024年11月29日に社内報機能・タスク管理機能をリリースしてます。 社内報機能は、社内報投稿をグループメンバー全体または指定したユーザに共有したり、リマインドPush通知や完了リアクションを送ったりできる機能です。また投稿者・管理者は、投稿を閲覧したユーザの一覧を確認できるので、社員の方に依頼する必要がある業務全体の効率化を実現できます。 タスク管理機能は、タスクの作成/編集/削除やタスクのステータス変更、リマインドPush通知などができる機能となります。外出先でも手軽にスケジュール確認/管理ができます。 今後は機会があれば、社内報機能・タスク管理機能についての詳しいアーキテクチャもブログ記事で紹介したいと考えています。 現在ビジネスdアプリでは、社内報機能・タスク管理機能の他にお得なクーポンや中小企業向けのニュースコンテンツも提供しています。もしご興味があれば以下のリンク・QRコードよりダウンロードしてみてください! ダウンロードリンクは こちら です。
本記事では、ソフトウェアプロダクト開発において、スクラムから独立した エンジニア × デザイナ × マーケタ の少人数チーム(以下、ミニチーム)を作って活動することにより、事業優先度が低く後回しになりがちなユーザビリティの課題を解決していった事例や学びを紹介しています。 目次 目次 はじめに NeWorkとは NeWorkのチーム編成 エンジニアが自由にプロダクト改善に取り組むことの難しさ ミニチームの発足 ミニチームの課題解決フロー 1. 課題選定 2. 見つけた課題が解決する価値のある課題か見極める 3. 他チームとの連携 4. デリバリープロセス ミニチームの改善事例 ミニチームのメリットと課題 メリット ユーザー対話で高評価を得ている機能は、ミニチーム発のものが多かった コミュニケーションのオーバーヘッドが小さい 簡単なバグ修正のリードタイムが短い 体験価値の向上を軸に越境できるチーム体制 課題 効果測定手法が定まっていなかった 収益拡大に寄与しているか、定量的に示せなかった メンバーからのコメント デザイナ 齋藤 マーケタ 藤原 おわりに (付録) ミニチームの改善事例の詳細 デフォルトのルーム名を変更 ルームバブルのクリック入室 招待リンクの送信 にゃわーく ルーム名の折り返しの改善 バーチャル背景のアップロード オンボーディング タイルレイアウト選択機能 デバイスの切り替えトースト フォールバック入室の通知 プロフィール画像の拡大 カメラの映像と画面共有の同時配信 通話中に画面をOFFにしない 通知音量調整 ワードバブル(仮) 離席 通話中の UI からルーム詳細を開く動線の追加 はじめに こんにちは、NeWorkチームの中里です。 「なんとなく使いづらいし、ユーザーもモヤモヤしていそうだけど、ビジネスインパクトが小さいから後回し…」 —— そんな課題、あなたの担当するプロダクトにもありませんか? NeWorkでも、まさにそうした機能を改善できずにいましたが、思い切って専任の少人数チームを作り、改善活動に取り組みました。この記事は、具体的な取り組み内容とそのチームのエンジニアとして 1 年程活動をしていた私目線での得られた学びやメリット、課題を紹介します。 NeWorkとは NeWorkは、NTT Comが提供するオンラインワークスペースです。 「リアルよりも話しかけやすい」コミュニケーションを実現することを目指し、オンラインでも気軽に声をかけ合える環境を提供しています。 残念ながら、NeWorkは約1年後の2026年3月31日をもってのサービス終了を発表しています。これまでご利用くださっていた皆さまには申し訳ない気持ちでいっぱいです。 しかしサービス終了が決まってからも前向きな気持ちで活動は続けてきました。本記事もそのような気持ちから執筆したものとなっております。 NeWorkのチーム編成 NeWorkには、以下のようなチーム体制があります。 1 プロダクトチーム: サービスの企画を担当 プロモーションチーム: 販売推進やマーケティングを担当 開発チーム: 実際の開発を担当 開発体制としては、プロダクトチームがプロダクトオーナーを務めるスクラムチームが3つ存在し、そこに開発チームのメンバーがそれぞれ参加して、2週間のスクラムで開発しています。 スクラムチーム1: Web 全般 スクラムチーム2: 主にエンタープライズ スクラムチーム3: モバイルアプリ エンジニアが自由にプロダクト改善に取り組むことの難しさ このプロダクトが大好きな私は、開発優先度に物足りなさを感じることがありました。たとえば、 全ユーザーの1%にも満たないエンタープライズプランの管理者向け機能に多くの開発時間を割く一方、すべてのユーザーが利用する通話画面の課題は後回しになってしまう。 このようなやるせなさを感じることが何度もありました。 NeWorkチームはアジャイル開発を行っており、NTT Comの中では比較的柔軟に動ける組織です。しかし、多くの企業に見られる傾向として、どうしても事業計画の遂行や収益拡大を最優先に考えないといけないプレッシャーがあります。 「本当はもっと改善したい部分が山ほどある」と感じていても、ビジネスを成立させるために、すぐに収益化につながる機能を優先せざるを得ない、というわけです。 1年前に投稿した「 NeWork開発チームが自主的な改善を行う 20%ルールを 1 年間運用してみて 」という記事では、スプリント内で最大20%の時間を自由に使ってプロダクトのためになることを行う、という制度を紹介しました。 このルールのおかげで、新技術の調査や技術的負債の返済、開発基盤整備、エンジニアのアイデアの実現などはある程度進められるようになりました。しかし、2週間スプリントの最後の2日間だけ自由に使えるといっても、そこで終わらなかったタスクは次回の自由時間まで2週間も空いてしまう。 結果として、 20%ルールの中で"ユーザー体験まで深く考え抜いた課題解決"を行うのは難しい 現状がありました。 ミニチームの発足 こうした課題感をなんとか解決しようとして生まれたのが、ミニチームです。 このミニチームはスクラムチームとは完全に独立して活動し、メンバーの入れ替わりはありましたが、最終的には以下の4名体制で改善を回していました。 エンジニア - 2人 デザイナ - 1人 マーケタ - 1人 以下の図は、NeWorkにおける全体のチーム体制を視覚化したものです。 ミニチームの改善活動はディスカバリーを行い、ユーザーストーリーを書くところからスタートします。そこからデザイナがデザインを起こし、エンジニアが実装し、マーケタが効果計測をする —— つまり、一連の流れをミニチーム内で完結できる仕組みです。 とはいえ、デザイナとマーケタはミニチーム専任というわけでもなく、ミニチームをメインにコミットしつつ、スクラムチームのタスクもこなすという体制を取っていました。 ミニチームの方針としては、直ちに大きな収益効果は見込めないものの、ユーザーが実際に使いにくさを感じている箇所の改善を担当していました。これは、スクラムチームが事業計画を軸に開発を行い、さらにユーザーフィードバックの中でも収益面や事業方針に影響しそうな課題を解決するという方針を補うことを狙ったものです。 もっとも、ミニチームの活動は“ビジネス的に意味がない”わけではないと考えており、収益面でもよい結果をもたらす効果はありえると考えています。たとえば、トライアルからの有償契約につなげるうえでの使いやすさの向上や、導入後に解約を減らす効果が期待できるといったことです。 ミニチームの課題解決フロー ミニチームで行っていた「どのように課題を見つけ、どうやって解決していくか」のプロセスをご紹介します。いろいろな方法を試した結果、最終的には下記のような流れに落ち着きました。 1. 課題選定 まずは、課題の選定です。 課題のネタとしては、プロダクトチームがユーザーフィードバックやチーム内フィードバックに ICE スコアリングをして Notion のデータベースにまとめてくれているので、これを参照しています。 ICEスコアリングとは以下の 3 つを元に優先度を判断するフレームワークです。 Impact (影響度) Confidence (成功率の自信度) Ease (実現のしやすさ) スクラムチームの場合は、これに Business(有償契約につながるか) も加え、B-ICEという形で独自に採点していました。 一方、ミニチームは "B"を外した純粋なICE のスコアが高い課題 を優先的に見ていました。つまり「ビジネス的な収益には直結しないかもしれないけど、多くのユーザーの利益になり、難易度も高くない」という課題を見つけるわけです。 また、私たち自身も普段から NeWorkを使って仕事をしている(いわゆるドッグフーディング) ので、日常的に「ここ不便だな…」と気づいたらバックログに投げ込む、というケースも多いです。あとはユーザーからは報告されていないような小さなバグでも、発見次第対処するようにしていました。 2. 見つけた課題が解決する価値のある課題か見極める 見つけた課題がユーザーが本当に解決したいものかという視点は、案外見落としがちです。ミニチームは少人数でリソースが限られているので、本当に価値がある課題かどうかを厳しく見極めるようにしていました。 そこで取り入れているのが、 「前提 / As-is / To-be」 というフレームワークです。課題に着手する前に、以下の 3 点をしっかり書き出して整理します。 前提 : 「誰がどのような状況におかれているか」 As-is : 「いま、実際にどうなっているか」 To-be : 「理想的にはどうあるべきか」 ここで、As-is / To-be を客観的に書けない場合は、そもそも改善の優先度が低いと判断してスルーするようにしていました。 そして私たちは、この「前提 / As-is / To-be」をユーザーストーリーとして活用しています。 一般的なユーザーストーリーは「誰が / なぜ / 何をしたい」を書く手法が主流で、NeWorkの他のスクラムチームもこれを採用しています。しかしミニチームでは、すでに存在するNeWorkの課題を解決することがメインであるため、より客観的に“現状のプロダクトの課題”と理想とのギャップを浮き彫りにできる「前提 / As-is / To-be」のほうが適していると考えました。 実際、「誰が / なぜ / 何をしたい」を使っていた頃は、「この技術を使えば新しい機能が作れそう!」という、いわゆる“How”ベースのひらめきに突っ走ることがありました。この手法だと案外自分たちに都合のいいストーリーが書けてしまうため、アイデアに勢いがつきすぎると 「この機能、絶対いいじゃん!」と客観性を失ってしまう ケースがありました。 そこで、私たちは As-is / To-be を明確に書き出し、課題の本質を客観的に捉えることを意識するようにした結果、より確かな価値につながる改善を行えるようになったと感じています。 我々が書いていたユーザーストーリーの例 [前提]: ルームに入室する際に、認識されているマイクが存在しない場合は自動的に聞き耳入室になり、通常の入室はできないという仕様がある。 [As-is]: 上記の仕様をユーザーは知らない(知ることができない)ため、この事象に遭遇した際に不具合だと勘違いしてしまい、問い合わせや別の通話アプリに移動してしまう。 [To-be]: 認識できるマイクが存在しないユーザーがルーム入室時に不具合だと誤認せず、解決方法が理解できるようになっている。 3. 他チームとの連携 ミニチームはスクラムチームから独立していますが最低限のスクラムイベントには参加して、情報共有や進捗をすり合わせていました。 デイリースクラム : 日々の進捗をお互いに簡単に確認 スクラムオブスクラムミーティング : 各スクラムチームや関連チーム間での連携を図る さらに、 NeWorkそのものを使って仕事をしている ことも大きいです。NeWorkは「誰がどこで、どんな話をしているか」がひと目でわかるインタフェースと、「聞き耳」という機能で発言せずとも会話を“聞く”ことだけをできる仕組みがあり、透明性の高い働き方ができています。 「あ、あのチームが今 ○○ 機能をいじってるっぽい? じゃあタイミングがかぶらないように注意しよう」 みたいな、ちょっとした情報共有や調整を自然と行え、コミュニケーションにあまりコストをかけずに、コードのコンフリクトを防いだり解決するべき課題が被らないようにしています。 (ところで、入社以来NeWorkを使って仕事をしているので、NeWorkのサービス終了後、どうやって仕事をしていけばいいか…路頭に迷っています) 4. デリバリープロセス NeWorkのデリバリープロセスはデイリーリリースが基本です。ステージング環境(developブランチ)で1日間ドッグフーディングしたインクリメントは自動的にプロダクション(mainブランチ)環境へデプロイされる仕組みになっています。詳しくは「 リリース頻度を毎週から毎日にしてみた 」という記事で紹介しています。 ミニチームは要件定義〜デザイン〜実装までチーム内で完結するため、改善の多くがプロダクトオーナーやステークホルダの目にほとんど触れません。 大きめの変更についてはスプリントレビューで関係者に見てもらい、リリースの判断を仰ぎます。一方、小さな改善であればわざわざレビューを待たずに mainブランチへマージ → 自動リリースの流れに乗せてしまい、次のスプリントレビューで事後報告という形を取っていました。 こうすることで、Feature Flagをわざわざ設定する手間やリードタイムを短縮できているのもポイントです。もちろん、こうしたやり方はマネージャ陣の協力や信頼関係が不可欠で、理解を示してくれているマネージャ陣には本当に感謝しています。 ミニチームの改善事例 ミニチームでは以下のような改善を行ってきました。詳細は本記事の末尾の付録に記載しましたのでご興味がありましたらご覧ください。 デフォルトのルーム名を変更 ルームバブルのクリック入室 招待リンクの送信 にゃわーく ルーム名の折り返しの改善 バーチャル背景のアップロード オンボーディング タイルレイアウト選択機能 デバイスの切り替えトースト フォールバック入室の通知 プロフィール画像の拡大 カメラの映像と画面共有の同時配信 通話中に画面をOFFにしない 通知音量調整 ワードバブル(仮) 離席 通話中の UI からルーム詳細を開く動線の追加 ミニチームのメリットと課題 メリット ユーザー対話で高評価を得ている機能は、ミニチーム発のものが多かった 社内外のユーザーと話す機会があると、 「あの機能すごく便利ですね」と言われるものの多くがミニチームで作った機能 でした。小さな改善でも直接的にユーザー体験を向上させることが多く、結果的にプロダクトのファンを増やすことにもつながったと思います。 コミュニケーションのオーバーヘッドが小さい チームが少人数であるため、PBI(Product Backlog Item)の記載を必要最小限の簡素な形で進められました。加えて、デザインやインタラクションなどの細かな仕様は口頭ですり合わせることで、ドキュメント作成にかかる手間を大幅に削減できたのも大きなメリットでした。 簡単なバグ修正のリードタイムが短い 問い合わせがあってから、早いもので最短 2 日ほどで不具合を修正しリリースできることもありました。 体験価値の向上を軸に越境できるチーム体制 ミニチームでは、エンジニア・デザイナ・マーケタといった職種の垣根を越え、 全員が「ユーザー体験の向上」を共通の目標として捉えていました 。必要であれば、誰もが仕様検討やデザイン検証、アンケート作成、計測設計などに積極的に関わる姿勢ができていました。 特徴的だったのは、エンジニアとデザイナがペアデザインを行うケースがあったことです。たとえば、エンジニアがデザインの素案を作り、それをリアルタイムでデザイナがレビューするという流れです。こうすることで、エンジニアはデザインスキルを学び、デザイナは技術的制約や実装容易性をその場で把握できるため、拡張性と実装難易度を考慮したデザインの感覚の醸成に繋げられるメリットがありました。 また、ミニチームでは、ディスカバリー(課題の洗い出し)からデザインの方向性検討、実装、検証、そしてリリースまでをほぼ自分たちの裁量で進められるという特徴がありました。振り返ると、これはいわゆる“ プロダクトエンジニア ”的な動きに近かったのかもしれません。 「プロダクトエンジニア」という言葉は、AtlassianのSherif Mansourさんが「 Product engineers 」という記事で提唱して話題になった概念です。具体的には、フロントエンド・バックエンド・デザインなどの領域を横断しながら、ビジネス面やユーザー体験を総合的に考え、愛されるプロダクトを作るエンジニアのことを指すと認識しています。 ミニチームでの動きがどこまで理想的な“プロダクトエンジニア”に近かったかは分かりませんが、少なくとも私自身のキャリアにおいては、NeWorkの価値を高めるために幅広い経験を積めたことが大きな収穫だったし最高にやりがいを感じていました。 課題 効果測定手法が定まっていなかった まず、適切なKPIが定まっていませんでした。もともとは新規登録ユーザーの定着率(1週間・4週間後の利用率)をKPIにしていましたが、利用率は企業やチーム単位の導入状況に大きく左右されるため、短期的な成果を見る上では参考になりにくかったです。 結果として 「体験の改善」を数値化することは簡単ではなく 、ユーザーテストやアンケートによる定性的な評価を行うのがベストかと議論しましたが、コストが高いという課題もあり、十分に手をつけられませんでした。 さらに、個々の機能改善の効果測定についても、必ずしも十分にできていたとは言えない状況でした。ボタンなどの明確なユーザーアクションを伴う改善は計測しやすく、ある程度の効果は数値化できたものの、ユーザビリティのような心理的変化はほとんど見えていませんでした。 振り返ってみると、社内のNeWorkユーザーの集まるSlackチャネルが存在したので、そこを活用して気軽に意見を聞ける仕組みを整えておけば、定性的な評価も比較的低コストでキャッチできたのではないかと感じています。 収益拡大に寄与しているか、定量的に示せなかった ミニチームでは、トライアルユーザーの体験向上や解約率の低減といった観点から、長期的には収益やブランド価値に貢献できると考えています。 そして、ミニチームが プロダクトにおいてなかなか手の回らない箇所を率先的に改善し、スクラムチームが大きな機能や収益拡大施策に専念できるようにしている 、という構造を意識していました。 もっとも、直接的な収益拡大にどこまで貢献しているかは、定量的に示すのが難しいという課題もあります。多くのメンバーには理解をいただきつつも「収益に貢献しているわけではないのでは?」と思われがちで、開発の現場から距離の遠いステークホルダに十分説明できていなかったかもしれません。この点はもう少しコミュニケーションを取るべきだったと感じています。 メンバーからのコメント 本記事執筆にあたり、デザイナとマーケタの方からもコメントをいただきましたので、以下に紹介します。 デザイナ 齋藤 ミニチームの活動はただ純粋に”おもろかった”。 ちゃんとユーザーが欲しているものを作れている感覚、コミュニケーションや管理のストレス無かったこと、頑張って作った機能がユーザーに刺さった反応など、複合した結果”おもろかった”に集約されています。 各メンバーからもこのコメントが出ていることを考えると本当に良い活動だったのだと感じます。デザイナー目線でも、開発メンバーと共創でものづくりをしていく過程は通常のAgile開発よりもスピードが速くとても多くの学びを得られました。 今まで NeWorkをご利用いただきありがとうございました。 サービス終了になってしまった以上、私は次の価値を作りに行きます。そしてまたいつか、皆さんの手元にその価値が届くことを願っています。 マーケタ 藤原 NeWorkが大事にしている「誰が言ったかではなく、何を言ったか」をものすごく体現できているチームだったと思います。私は新入社員としてこのチームに加わりましたが、フラットな関係性のおかげで、自分の意見を素直に発信しやすい環境でした。互いに「違うものは違う」「良いものは良い」と率直に意見を交わせたことが、何よりもユーザー視点での改善につながったと思います。 また、効果検証を担う立場として、私たち自身が「使いたい!便利!」と感じる機能ほど、実際のユーザーも気に入って使ってくれていました。(ルームをクリックして入室する機能や、にゃわーくなど…) ミニチームでの経験を通じて、サービス提供者とユーザーが対立構造にあるのではなく、一緒に使いやすいプロダクトを創っていく関係性を築くべきだと実感しました。 NeWorkとしては一区切りとなりましたが、これまでの経験を活かし、毎日使いたくなるプロダクトづくりに携わっていきたいと思います。今までありがとうございました! おわりに ミニチームのリリースした機能が好評だったことを考えると、活動自体には一定の効果があったと感じています。一方で、ビジネス面への貢献度をどう評価するかは、引き続き課題として残ります。周囲の理解を得た上で良いプロダクトを生むために必要なことだと思うので、これからも考えていきたいです。 個人的には、この活動を通じてさまざまな経験を積むことができ、確かな成長を実感しています。ミニチームを任せてもらえたことには、大変感謝しています。 もし同じようなプロダクト改善専任の少人数チームを立ち上げるなら、 独立性や裁量、目的意識の共有、迅速でフラットなコミュニケーション、そしてマネージャやステークホルダの理解・信頼 が重要になると思います。 残念ながら、NeWorkはサービス終了となってしまいますが、終了を惜しむ声をいただけたことや「NTT Comにはこうした良いサービスを作れる人がいる」と思ってもらえた(と信じています)ことは、私にとって大きな財産です。今後も、この経験を活かしてより良いプロダクト作りに貢献していきたいと思います。 (付録) ミニチームの改善事例の詳細 さいごにミニチームが改善した事例をいくつかご紹介します。これ以外にも小さな改善やバグ修正を行っています。 デフォルトのルーム名を変更 NeWorkでワークスペースを新規作成した際のデフォルトのルーム名を変更しました。 変更前は「定例」、「開発チーム」、「朝会」、「カフェ」というルーム名でしたが、これは多くの人にとって具体的に利用しているイメージが湧きにくいルーム名でした。そこで、具体的な動作に結びつく「雑談ルーム」、「ミーティングルーム」、「作業ルーム」というルーム名にしました。また、1つを未定義にしたことでルーム名を自由に定義できることを暗示しています。 ルームバブルのクリック入室 ルームバブル自体をクリックするだけで入室できるようにしました。 改善前はルームバブル上の青い入室ボタンか、「…」からしか入室できずわざわざカーソルを合わせるという心理的負荷がありました。 改善後は押下範囲の拡張により、操作性が上がりました。さらに直接聞き耳に参加することも可能になりました。一方で誤操作を起こしやすくなったため、通話中のルーム移動時にダイアログを出すオプションや機能をOFFにするオプションも用意しています。 招待リンクの送信 ワークスペースの招待リンクをNeWork上で送信できるようにしました。 これまでは招待リンクをコピーして別の方法で共有する方法のみでしたが、相手のメールアドレスを指定して招待を送信できるようにしています。 にゃわーく エイプリルフールのお楽しみとして、『にゃわーく』モードを実装しました。 アイコンやルームリアクションが猫仕様になったり、入退室音が猫の鳴き声になったりします。 ルーム名の折り返しの改善 ルーム名が自然な位置で折り返されるように改善しました。 バーチャル背景のアップロード ユーザーがバーチャル背景をアップロードできるようにしました。 それまでバーチャル背景機能はありましたが、ユーザー自身がバーチャル背景をアップロードできないためベータ機能という位置づけでした。 バーチャル背景のアップロード機能や、自身の映像を反転させるか選択できる機能を追加し、ベータから正式な機能にしました。 オンボーディング NeWorkを初めて使うユーザーに対してオンボーディング機能を追加しました。 これまでは、NeWorkを初めて使うユーザーには使い方を紹介する動画をポップアップで表示していました。しかしクリック率(再生率)が著しく低く、NeWorkの基本的な使い方を理解する前に離脱しやすい状況だったため、チュートリアル形式でオンボーディングできる機能を実装しました。 タイルレイアウト選択機能 タイルレイアウト選択機能を正式リリースしました。 開発チーム改善活動 で「タイル表示スタイル選択機能」としてベータリリースされていた機能を、フィードバックを元に改善し、正式リリースしました。 デバイスの切り替えトースト 音声入出力デバイス・映像入力デバイスが切り替わったときにそれを知らせるトーストを実装しました。 ユーザーの意図に反して使用中のデバイスが認識できなくなり別のデバイスにフォールバックした場合に、気付けるようにしています。 フォールバック入室の通知 音声入力デバイスがない状態で室内に入室しようとすると自動的に聞き耳入室をするが、その際にその旨をトーストで通知するようにしました。 これにより、ユーザーにマイクを繋ぐなどの具体的な動作を動機づけられるようになりました。 プロフィール画像の拡大 プロフィール画像を拡大表示できるようにしました。 一般的には、画面にユーザーアイコンがある場合、クリックなどで拡大して表示できると期待されます。NeWorkではできなかったので実装しました。 カメラの映像と画面共有の同時配信 カメラの映像と画面共有を同時に配信できるようにしました。 NeWorkの特徴として、複数人が画面を共有できる点が挙げられます。一方で、これまでは画面共有とカメラ映像を同時に配信できないという制限がありました。たとえば、チームメンバーが全員カメラをオンにして会議をしていても、画面共有をしている人の表情だけは確認できない状態でした。これでは不便なので、全員がカメラ映像と画面共有を同時に使えるようにアップデートしています。 通話中に画面をOFFにしない パソコンを一定期間触らないと自動的に画面がOFFになる設定をしていても、通話中は画面OFFにならないよう変更しました。 もともとブラウザは映像が流れている場合、画面をOFFにしない仕組みになっています。しかし、「誰も映像を配信していないルーム」で通話を続ける場合は、画面が消えてしまうことがありました。今回の対応により、映像がない状態でも通話中は画面が消えないようになり、より快適に会話を続けられるようになりました。 通知音量調整 通知音量を調整できるようにしました。 1on1やルームへの呼び出しの呼び出し音が聞こえなかったというフィードバックや、逆に入退室音がうるさいというフィードバックなど、通知音量に対するフィードバックは千差万別でした。 そこで、好みに合わせて調整できるようにスライダーで音量を調整できるようにしました。なお、人間の聴覚特性に合わせて低い音量レベルでより細かい調整ができるようにスライダーを実装しています。 ワードバブル(仮) ルーム内の会話から話題を抽出して、ルームバブル上に表示する機能(ワードバブル(仮))を実装しました。 ルームの外から、ルームの中で行われている会話の内容や温度感を把握できるようにすることで、ルームに参加しやすくする機能です。 開発中にサービス終了が決まり、社内向けのリリースに留めました。 離席 一時的に席を外していることを表せる、離席機能を機能を実装しました。 リモートワークにおいて、宅配や着信などで突発的に席を外さざるを得ないことは少なくありません。そして、自分が話者ではないときにそれを伝えることは難しいです。そこでワンボタンで離席状態を表現できる機能を実装しました。 通話中の UI からルーム詳細を開く動線の追加 通話中にそのルームの詳細パネルを簡単に開ける動線を追加しました。 これまで通話中にルームの詳細を開くときは、ルームバブルの「…」から開く必要がありました。特に映像をタイル表示しているときは、タイル表示を非表示にしてから「…」ボタンを押す必要があり手間でした。 そこで、通話中でも常に表示されている場所に、ルーム詳細を開く動線を追加しました。 執筆時点では、開発体制は縮小しています。 ↩
こんにちは。イノベーションセンター Generative AI チームの安川です。今回はrokadocのパブリックベータ版( https://rokadoc.ntt.com/ )が公開されたため、その紹介と解説をします。 本記事では「ドキュメント変換技術」であるrokadocの概要を説明した上で、実際の使い方や結果を紹介します。 使い方の部分では、WebUIを用いて簡便にドキュメント解析を行う方法や、解析結果が実際にRAG(Retrieval-Augmented generation、検索拡張生成)で有用なのかを示します。また、手元のRAGへ組み込むためにAPI経由で処理を実行する方法についても紹介します。 rokadoc概要 多様なファイルへの対応 高い検索精度 オンプレミス対応 利用方法 WebUIからの利用方法 ドキュメントの解析 RAGの実行 APIを用いた利用方法 前提 解析の実行 おわりに rokadoc概要 「AIの力で埋もれた情報を価値あるものに」 というコンセプトの元、rokadocは開発されています。 そして、生成AIで取り扱うには難解なドキュメント類を効果的に利活用可能なデータへ変換するための技術として、2025/2/19 にパブリックベータ版が公開されました。 昨今、LLM(Large Language Model)をはじめとした生成AIは爆発的なブームを迎えています。しかし、実際にLLMを使ったものの、必要な情報を精度高く得ることができず悩んだ方や、RAGをいざ構築したは良いものの、ドキュメントからうまく情報を抽出できずにもどかしく感じた方はいると思います。 rokadocは、このような問題を解消するために作られ、以下の特徴を持っています。 多様なファイルへの対応 ファイル形式は、PDFに加え、Microsoft Word、Excel、PowerPointを利用可能です。 高い検索精度 変換後のテキストを用いて行った検索では他社製品に比べて高い精度を示しました。 rokadocは複数の機械学習モデルと、それらの機械学習モデルに合わせたアルゴリズムを用いており、多数の要素を持った複雑なドキュメントが対象であっても精度高く変換します。 また縦書きなどの日本語特有の要素を含むドキュメントであっても対応が可能です。 オンプレミス対応 私たちは、オンプレミスで動く精度の高いモデルの開発も行なっています。 公開したパブリックベータ版とは異なる構成で、オンプレミスで動く形での提供も今後行っていく予定です。 こちらを利用すれば、インターネットに接続することなく、自社のネットワーク環境に閉じた状態で rokadoc を利用できます。 利用方法 ここからは、具体的な使い方について紹介していきます。 rokadocはWebUIとAPIの2つの利用手段があります。 WebUIからの利用方法 ここではWebUIから利用する手順を示します。 今回はつい先日行われた言語処理学会第31回年次大会(NLP2025)へ、当チームのメンバーが投稿した「chakoshi: カテゴリのカスタマイズが可能な日本語に強い LLM 向けガードレール」 *1 という論文を対象にrokadocを使ってみます。 ドキュメントの解析 以下が、WebUIからログインした場合のホーム画面です。このページの左側から、解析したいファイルをアップロードします。 アップロードを行うと、右側に解析状況が表示されます。進捗が”Succeeded”へ切り替わった後にファイル名をクリックすると解析結果が表示されます。 対象とした論文 を見るとわかりますが、このPDFは二段組かつフッターも存在し、表や図が文章中に登場するという複雑な構造のものになっています。今回は、 文章の読み取り 表の読み取り 図の読み取り の三点に焦点を当て、解析結果の一部を例示します。 1. 文章の読み取り このドキュメントは、以下のようにベースが二段組となっており、フッターが存在する複雑な構成となっています。 しかしpage 3の解析結果を見ると、 (前略) 4.1 有害性判定に関する評価実験 本実験の目的は、日本語の有害表現に対する, chakoshi の判定精度の評価である. 既存の代表的なモデレーション API やガードレールを比較対象とし,3.2 節で構築したデータセットを用いてファインチューニングした chakoshi モデルの性能を評価する. 4.1.1 実験手続き (中略) 4.2 カテゴリ追従性能の評価実験 本実験の目的は, chakoshi のカテゴリカスタマイズ機能の評価である. 医療相談や金融相談など, chakoshi が元々対応していない新規カテゴリを自然言語で追加し,それらへの追従性能を検証する. This work is licensed by the author(s) under CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/). –– 2805 –– と、テキストを正確に読み取るだけではなく二段組であることを理解し、「4.1 有害性判定に関する評価実験」の途中で段組が変わっている部分に対しても問題なく繋げることができています。また末尾にはフッターとして表示されているものも正確に出力できています。 2. 表の読み取り page 3には以下の表が掲載されています。 この表は結合したセルが存在した上で罫線が引かれていない部分が存在しているなど、複雑な構造を持つ表となっていますが、解析結果を見ると、 表1 ベースラインと chakoshi モデルの比較結果 < table border = "1" > < caption > 表1 ベースラインと chakoshi モデルの比較結果 </ caption > < tr > < th rowspan = "2" ></ th > < th colspan = "2" > XSTest </ th > < th colspan = "2" > RTP-LX </ th > </ tr > < tr > < th > F1 </ th > < th > F2 </ th > < th > F1 </ th > < th > F2 </ th > </ tr > (中略) < tr > < td > gemma-2-9b-it-chakoshi </ td > < td > 0.835 </ td > < td > 0.884 </ td > < td > 0.966 </ td > < td > 0.964 </ td > </ tr > </ table > と、セル内部のテキストを正確に取得した上で、列及び行方向に結合したセルも正確に構造を取得できています。 3. 図の読み取り page 2には以下の図が掲載されています。 アイコンを用いて処理に登場する要素を、矢印で処理の流れを表現しています。 またアイコンや矢印に対応する文字も記載されています。 このように多数の要素が含まれますが、解析結果を見ると、 **画像の説明:** 1. イラストは「chakoshiの概念図」と題されており、ユーザーが入力するテキストを基に、システムがその安全性を評価するプロセスを示しています。図の中央には「chakoshi」という名称が記載されており、矢印を使ってプロセスの流れが説明されています。 2. 左側には「ユーザー」のアイコンが描かれており、ユーザーが入力するテキストが「入力が安全だと考えられるケース」「入力に害があるケース」「出力に害があるケース」の3つのケースに分類されることが示されています。 3. 右側には「LLM(Large Language Model)」のアイコンが描かれており、システムがテキストを評価する際にLLMを活用することが示されています。 4. 矢印の先には「判定の例示イメージ」として、以下の具体的な数値が記載されています: - 「判定結果:safe」 - 「unsafeスコア:0.13」 - 「判定結果:unsafe」 - 「unsafeスコア:0.89」 - 「カテゴリ:暴言」 5. 図の下部には「※1 入出力はどちらもテキスト形式」との注釈が記載されており、入力または出力がテキスト形式であることを補足しています。 このイラストは、chakoshiがどのようにテキストの安全性を判定するかを視覚的に説明しており、プロセスの流れや具体的な判定例が詳細に示されています。 と、グラフについての情報を正確に出力するだけではなく、各図がキャプションに対してどのように対応しているかも含めて解析ができています。 以上のように、複雑な構成や多種多様なコンテンツを含むものでも、正確に解析できました。 RAGの実行 以下のドキュメント解析結果の画面上部にある「チャット」ボタンからチャット画面へ遷移できます。ここでは論文の解析結果に基づいた回答が可能か試してみます。 例えば「chakoshiは画像も対応できますか?」と入力すると、以下の内容が返ってきます。 いいえ、chakoshiは画像や音声といったマルチモーダルな入出力には対応していません。関連情報によると、chakoshiはテキストベースの入出力に特化しており、シンプルかつ確実な有害性の検出を目指しています。この設計方針により、ユーザー固有の環境でのテキストチャットでの利用を想定しており、画像や音声などの非テキスト形式のデータは対象外となっています。したがって、chakoshiはテキスト形式のデータに限定して動作するガードレールモデルです。 また「chakoshiの性能について端的に教えてください」と聞くと、以下のように解説してくれます。 chakoshiは、日本語特有のニュアンスや表現に対応した軽量なLLM向けガードレールモデルであり、以下の特徴を持っています: 1. 高い判定精度 - 有害性判定において、既存のモデルを上回る性能を示し、XSTestでF1スコア0.835、RTP-LXでF1スコア0.966を達成しました。 2. カテゴリカスタマイズ機能 - 防ぎたい話題を自然言語で指定できる柔軟性を持ち、新規カテゴリ(例: 医療相談、金融相談)でも高い検知率を達成しました(医療相談87.1%、金融相談93.1%)。 3. 日本語特化 - 日本語特有の遠回しな嫌味やハラスメント表現にも対応可能で、文化的背景を考慮した設計がされています。 全体として、chakoshiは日本語に強く、ユーザー固有の要件に応じた安全性確保が可能なモデルです。 このように論文の内容を踏まえた正確な回答ができています。 APIを用いた利用方法 自身の環境で構築したRAGにrokadocの解析結果を用いたい場合、WebUIから1つずつファイルをアップロードしていくのではなく、CLIから操作したい場合もあると思います。そのような場面を想定して、APIを用いてドキュメント解析を行う例を提示します。 前提 まずAPIキーを確認しておく必要があります。APIキーは以下のホーム画面の上部にある「ユーザ設定」の「APIキーの確認/切替」から確認ができます。 また利用方法の詳細はAPIリファレンスから確認が可能です。同様にホーム画面の上部にある「ユーザ設定」をクリックし、「APIキー発行」をクリックすると以下の画面に遷移します。 その後上部の「APIリファレンス」をクリックすると、各APIの使い方や挙動を確認できます。 解析の実行 1. 変換処理 以下のコマンドを実行することで、対象のドキュメントの解析ができます。 api-key には先程確認した APIキー を設定します。 upload_file には対象のドキュメントを指定します。今回は対象の論文ファイルである @NLP2025_P7-7.pdf を指定しています。 curl -X POST "https://beta-api.rokadoc.ntt.com/v1/api/conversions" \ -H "Cache-Control: no-cache" \ -H "api-key: {YOUR_API_KEY}" \ -F "upload_file=@NLP2025_P7-7.pdf" \ -F "from_page=1" \ -F "to_page=6" ここでfrom_page及びto_pageはそれぞれ、対象のドキュメントの中で解析するページの範囲を選択するために使用します。 こちらを実行すると、 { " code ": 202 ," status ":" Pending "," conversion_id ":" {conversion_id} " } のようなメッセージが返却されます。このconversion_idを用いてファイルの解析結果を確認できます。 2. ジョブ一覧の確認 以下のコマンドを実行することで、これまで行ったジョブの状況が確認できます。 curl -X GET "https://beta-api.rokadoc.ntt.com/v1/user/conversions" \ -H "Cache-Control: no-cache" \ -H "api-key: {YOUR_API_KEY}" こちらを実行すると、 { " code ": 200 ," total_count ": 1 ," last_page_number ": 1 ," data ": [{ " status ":" Running "," conversion_id ":" {conversion_id} "," document_name ":" NLP2025_P7-7.pdf "," created_date ":" 202503021710 "," updated_date ":" 202503021711 " }]} のようなメッセージが返却されます。ここでstatusがSucceededになっていれば解析処理が完了しています。上記のようにRunningになっている場合は実行中ですのでお待ちください。 3. ジョブ結果の表示 以下のコマンドを実行することで、対象のジョブの実行結果が確認できます。 curl -X GET "https://beta-api.rokadoc.ntt.com/v1/user/conversions/{conversion_id}/document" \ -H "Cache-Control: no-cache" \ -H "api-key: {YOUR_API_KEY}" 長くなるため実行結果は省略しますが、これで各ドキュメントに対するAPIでの処理が実行できました。この結果をRAGの検索データベースに格納することで、お手元の環境にあるRAGでrokadocの解析結果を用いることができます。 おわりに この記事では、2025/2/19にパブリックベータ版を公開した rokadoc について紹介しました。 利用回数に上限はありますが無料で利用可能ですので、気になった方は是非ともお試しください。 利用上限なしで使いたい方向けに個別相談も可能です。もっと使いたい!と思っていただけた方は、 rokadocお問い合わせフォーム からお問い合わせください。 それでは皆さん、お読みいただきありがとうございました。 *1 : 新井一博, et al, "chakoshi: カテゴリのカスタマイズが可能な日本語に強い LLM 向けガードレール", 言語処理学会 第31回年次大会, 2025
こんにちは、イノベーションセンターの加藤です。この記事では、大規模言語モデル(LLM)にJSONやソースコードを正しく出力させるための生成手法であるStructured Generationについて紹介します。 Structured Generationとは パーサーを用いた制約手法 正則言語とは 正則言語のStructured Generation 文脈自由言語とは 字句解析について 正則言語+文脈自由言語のStructured Generation まとめ Structured Generationとは 大規模言語モデル(LLM)はよくチャットボットとしての活用が目立ちますが、LLMの入出力を外部のプログラムに繋ぎ込むことでより高度な自然言語処理システムを作ることができます。 例えばOpenAIのCode Interpreter 1 はLLMをPythonの実行環境と接続することで、ユーザーに要求された複雑な情報処理をまずPythonコードに書き起こし、その実行結果を使って応答するシステムです。 また、Meta社によるアシスタントロボットの取り組み 2 ではユーザーの音声入力をテキストに書き起こし、LLMがその文章からプログラムが解釈可能な命令列に変換することで自然言語とプログラムの橋渡しを行なっています。 このように、特定のスキーマに沿ったJSONや特定のプログラミング言語でのソースコードといった構造化データをLLMに出力させるStructured Generationが最近注目されています。 しかしながらこのようなユースケースではLLMの出力形式がブレるとシステムが動作しなくなるため、ファインチューニングやプロンプトの工夫だけでは不十分です。 下図のようにJSONの出力を例にとると、プロンプトで「JSONのみを出力してください」と指定していても、関係のない文章やコードスニペットの記法を混入させてしまうことが多々あります。 そこで、プロンプトのような確実性のないものに頼るのではなく、出力として選択されるトークンの候補をコントロールすることで、目的のフォーマットに必ず従わせる制約付き文章生成の手法が広く研究されています。 パーサーを用いた制約手法 LLMなどの文章生成モデルは、これまでの単語列から次に現れそうな単語を自信度の形で予測し、何らかの戦略のもとで出力する単語を決定します。一般的に使われている戦略は貪欲法で、毎回最も自信度の高い単語を出力することを繰り返すことで文章を生成していき、文章の終了を表すトークン(よく EOS として表されます)を出力した時点で停止します。 Structured Generationでは以下の画像のように、選択前に好ましくない単語の自信度を全て0にすることで文法的に正しくない文章の出力を防いでいます。 そしてどの単語が文法的に好ましくないかを判定するために、その文法に対応したパーサーが使われます。 パーサーは基本的に入力文章を頭から読み取っていき、文法的に正しくない文字列が現れたところでエラーが発生するようになっています。Structured Generationではこの性質を利用して、LLMが予測する次の単語をパーサーに入れてみて、エラーが起こるようならその単語を除外することで文法的に正しい出力を実現しています。 しかし、この方法では1単語生成するたびに数万近くあるLLMの語彙を走査してパーサーに判定させる必要があるため、計算時間が非常にかかってしまいます。 そこで、パーサーの動作を利用して判定を効率化するテクニックがいくつか研究されています。 本記事ではStructured Generationでよくサポートされる正則言語と文脈自由言語の2種類のパーサーに対して効率的な制約付き生成手法を紹介します。 正則言語とは 正則言語は後方参照を用いない正規表現で表現可能な言語のことで、例えば「整数」は /0|(-?[1-9][0-9]*)/ として表現できます。 正則言語のパーサーは決定性有限オートマトン(DFA)で実装できることが知られています。DFAとは入力文章を頭から読み取っていき、入力文字と現在の状態から定まる次の状態へ遷移することを繰り返す機械のことで、前述の「整数」を判定するDFAは以下のように構成できます。 この丸が状態で矢印が読み取った文字に対応する遷移先を表しており、文字を全て入力して最終的に二重丸の状態(受理状態)に移動していればOKという判定がなされます。 例えば「整数」を判定するDFAに「-16」という文章を入力すると次のように状態が遷移し、「-16は整数である」と判定されます。 一方で、例えば「0-1-1」という文章は途中で遷移に失敗します。また、「-」という文章は最終的に受理状態に到達しません。そのためこれらは整数とは判定されません。 正則言語のStructured Generation このようなパーサーをStructured Generationに活用する場合は、「文法的に正しくない文字列が現れる」という現象を「この先受理状態へ到達し得ない状態に遷移してしまう」と読み替えることができます。 例えばLLMに整数を出力させようとしており、現状の中間出力が何もない場合、「090」や「apple」という単語は出力できませんが、「0」や「-」という単語は受理状態もしくはこの先受理状態へ到達可能な状態に遷移可能なので出力しても良いと判定されます。 そうした状態と単語の組み合わせの数は有限であることを利用するとうまく判定を効率化できます。 各状態 q から各語彙 t を入力した時に「この先受理状態に到達可能な状態」に遷移するかを前計算しておき、 dict[q][t] として保存しておきます。この結果をマスク処理で利用することにより、状態数 Q とLLMの語彙数 T に対してサイズ Q*T のメモリ消費のもと、長さ T のマスク処理だけで各単語の出力可否を判定できます 3 。 これにより、LLMの選択肢に挙がった単語を毎回DFAに入力していたことで発生する計算時間を減らすことができます。 文脈自由言語とは 文脈自由言語は正則言語よりも表現力の高い言語で、JSONのようにネスト構造を持つ言語もこれに属しています。 文脈自由言語を表現する文法の定義は少し複雑で、以下の4つをまとめたものになっています。 終端記号の集合 (文を構成する最小単位) 非終端記号の集合 開始記号と呼ばれる特別な非終端記号 1つの非終端記号から0個以上の記号列への変換規則 そしてこの文法が表現する言語は開始記号から変換を繰り返して生成可能な終端記号列の集合となります。例として次のような文法を考えてみます。 終端記号 a , b 非終端記号 S , E 開始記号 S 変換規則 S→aE , E→Sb , E→b S から適当に変換規則を選んでいくと、例えば S→aE→aSb→aaEb→aabb と繰り返して aabb が生成されます。 そのため aabb はこの文法で表現された言語に含まれていることが言えます。 実はこの文法は「任意の正整数Nに対して、 a をN個並べ、その後 b をN個並べた言語」を表しています。 このように正規表現では作れないような言語も表現できることが文脈自由文法の特長です。 よくプログラミング言語などの定義で現れるBNF(バッカスナウア記法)やEBNF(拡張BNF)はこの文脈自由文法を簡易的に記述するための記法です。 例えば先ほどの文脈自由文法に対応するBNFは次のようになります。 <S> ::= "a" <E> <E> ::= <S> "b" | "b" "" で囲われている単語は終端記号、 <> で囲われている単語は非終端記号と見なすと、これが文脈自由文法のいち記法に過ぎないことがわかりやすいかと思います。 こういった文脈自由言語を解析するときは、基本的には与えられた文章を生成するための変換の過程を特定できれば解析できたことになります。 この過程は構文木と呼ばれるもので表現でき、例えば先ほどの文法に対して aabb という文章は S→aE→aSb→aaEb→aabb と変換されたわけですが、これを以下のような木で表現できます。 文脈自由言語のパーサーはこの構文木を作成するのが最終目的ですが、解析手法にはさまざまなものがあります。 一般的にパーサーで広く使われているのはLALR(1)法と呼ばれているもので、文頭から1単語ずつ読み出していき、構文木を右の葉側から特定していく手法です。 例えば前述の文法に対して「a a b」までを入力すると次のように作りかけの構文木が出来上がります。 字句解析について 一般的なプログラミング言語のパースにおいては、文脈自由文法での解析を行う際の複雑さを減らすために、文字レベルでは無くある程度まとまった文字列にまとめてから分析を行います。 例えばJSONでは以下のように文字列がまとめられて終端記号に変換されます。 文字列リテラル ("John" など) → STRING 数値リテラル (-3, 1.2 など) → NUMBER 配列用の開きカッコ [ や閉じカッコ ] → LBRACK, RBRACK 配列内のカンマ → COMMA 辞書用の開きカッコ { や閉じカッコ } → LBRACE, RBRACE 辞書内のコロン → COLON 真偽値やnull → TRUE, FALSE, NULL このルールにもとづくと、例えば {"temperature": 25.7} というJSON文字列は LBRACE STRING COLON NUMBER RBRACE という終端記号列に変換されます。 このように文字列から終端記号の列への変換を字句解析と呼び、正則言語などの比較的シンプルなルールで解析が行われています。 正則言語+文脈自由言語のStructured Generation 前節で説明したように、一般的なプログラミング言語にLLM出力を制約したい場合はDFAによる字句解析とLALR(1)による文法解析を行うパーサーを利用します。 このパーサーには次に現れるべき終端記号の候補がわかるという特徴があり、これをLLMの出力制約に用いることができます。 具体的には次のようにLLMの語彙に制約を与えられます。 LLMの中間出力をパーサーに与えて途中まで字句解析させる 例: {“key”: [ → LBRACE({) STRING(“key”) COLON(:) LBRACK([) 確定した終端記号をパーサーに与えて途中まで文法解析させる 次に続くことができる終端記号を列挙する 例: {"key": [ に続く終端記号は文字列 STRING , 数値 NUMBER , 辞書の開きカッコ LBRACE , 配列の開きカッコ LBRACK , 配列の閉じカッコ RBRACK 各終端記号を判定するDFAから、前計算しておいた語彙マスクを取り出す 語彙マスクの和集合を取り、次に続くことができるLLMの単語を列挙する これにより、文脈自由文法の終端記号の種類数に比例した計算量でStructured Generationを行うことができます。この終端記号の種類数はLLMの語彙よりもずっと少なく、例えばPythonでも94種に抑えられることが知られています 4 。 まとめ 今回はLLMにJSONの出力やプログラムのソースコードの出力などを安定させたい場合に有用なStructured Generationについて紹介し、 語彙に制約を与えるための具体的な手法と効率化のアルゴリズムについて解説しました。 https://platform.openai.com/docs/assistants/tools/code-interpreter ↩ https://languageguidedskillcoordination.github.io ↩ Willard and R. Louf, "Efficient Guided Generation for Large Language Models", https://arxiv.org/abs/2307.09702 ↩ Ugare et al., "SynCode: LLM Generation with Grammar Augmentation", https://arxiv.org/abs/2403.01632 ↩
この記事では、ドコモグループで実施したイベント “dcc Engineer Day 25” において、Telegramを使ったワークショップを開催した様子を紹介します。 はじめに 注意 dcc Engineer Dayについて ワークショップの様子 Telegramとは何か Telegramの活用・悪用事例 アカウント設定とプライバシー メッセージング(チャット) APIとBot 参加者の声 ワークショップを開催してみて おわりに はじめに みなさんこんにちは、イノベーションセンターの遠藤です。Network Analytics for Security (以下、NA4Sec) プロジェクトのメンバーとして活動しています。 NA4Secプロジェクトは「NTTはインターネットを安心、安全にする社会的責務がある」を理念として、攻撃インフラの解明、撲滅を目指すプロジェクトです。 NTT Comイノベーションセンターを中心としてNTTセキュリティ・ジャパンやエヌ・エフ・ラボラトリーズからもメンバーが参画し、日夜攻撃インフラを追跡しています。 注意 この記事はTelegramの仕様やワークショップの内容を紹介する目的で書かれています。Telegram自体は適切に利用する分には問題ありませんが、残念ながらTelegramコミュニティの中には特殊詐欺や犯罪行為に利用されているものが存在します。興味本位でそういったコミュニティを覗いたり、素性のわからない人とのやり取りに利用したりすると意図せず犯罪に巻き込まれる危険性があるため、注意してください。 dcc Engineer Dayについて dcc Engineer Dayは、NTTドコモ・NTTコミュニケーションズ・NTTコムウェア3社の社員が技術を軸に交流を深める場として開催しているイベントです。2022年から年に1度開催され、今年は4回目となります。イベントは現地+オンラインのハイブリッドで開催され、今年は現地とオンラインを合わせて459人が参加しました。 我々NA4Secプロジェクトは、今年から新たに始まったワークショップ枠の講師として、本イベントに参加しました。 ワークショップの様子 ワークショップは「Telegramの危険性を正しく理解し、安全に使うための実践ワークショップ」と題して、ハンズオンを含む以下の構成で行いました。 Telegramとは何か 日本、海外での活用(悪用)事例 インストールとアカウント設定 メッセージング(チャット)の利用 APIの紹介 お昼時の開催だったので、参加者のみなさんにはピザをつまんでもらいながら、ハンズオンを進めました。 Telegramとは何か 初めに、Telegramについて座学方式で学びました。 Telegramは2013年にリリースされたマルチプラットフォーム対応のメッセージングアプリケーションです。その高速性やシンプルさ、透明性(オープンソース)から海外を中心に人気で、現在のアクティブユーザ数は全世界で9億人を超えています。 *1 また明確なプライバシーポリシーやセキュリティ、表現の自由に重きを置く考え方 *2 から、インターネット検閲の厳しい国々において、検閲されていない情報の発信・取得に利用されています。 Telegramの活用・悪用事例 次に、日本や海外におけるTelegramの活用・悪用事例を紹介しました。 日本におけるTelegramのイメージは、下記のような特殊詐欺や闇バイト・犯罪などの連絡手段に使われる事例から、「危ない」や「怖い」といった声が多いのではないでしょうか。 逮捕されるまで辞められない?闇バイトの勧誘方法の実態 (東京都 特殊詐欺加害防止 特設サイト) 正規の求⼈サイトに掲載されている有害求人情報に注意!! (警察庁) 一方で海外では先述の通り、情報統制下においても自由度の高い情報を発信できるツールとして利用されています。例えばNew York Times誌はTelegramを通じて、紛争地域に向けて ロシア・ウクライナ戦争に関する国際ニュースを発信するチャンネル を運営しています。またウクライナ政府が運営するWebメディア UkraineNOW は、Webサイトのほか、Telegramのオフィシャルチャンネル @UkraineNow を通じて、戦況をリアルタイムに世界へ発信しています。 これらのようにTelegramの特徴や優位性が活用されている事例と、残念ながら悪用されてしまった事例を学んだうえで、Telegramを安全に利用するためにはどのようなポイントに気をつければ良いかを、次に紹介するハンズオンで学んでいきます。 アカウント設定とプライバシー ここからは参加者の皆さんに、実際に手を動かしてもらいながらワークショップを進めていきました。 まずは実際にTelegram公式アプリケーションをインストールしてもらいます。続いてアカウントを作成して、各自でプロフィールを作成しました。 Telegramはプロフィール設定とは別に、自分の情報を相手がどの範囲まで見ることができるかを設定可能です。ワークショップでは実際に参加者同士で設定の差による見え方の違いを見せ合いながら、設定によっては自分の名前や写真、電話番号を第三者が参照可能な状態になることを体験しました。 メッセージング(チャット) ここでは作成したアカウントを利用し、チャットの閲覧・発言を体験しました。チャットは大きく分けて以下の4種類が存在します。 チャンネル:管理者のみが発言可能な複数人チャット グループチャット:管理者・参加者とも発言可能な複数人チャット 個人チャット:1対1のチャット シークレットチャット:E2EE(End-to-End Encryption)の1対1チャット なかでもシークレットチャットは秘匿性が高く、通信内容はエンドツーエンドで暗号化されています。エンドツーエンド暗号化が有効なチャットにおいては、メッセージなどの通信データがすべて暗号化された状態で扱われます。送信者と受信者のみがデータを復号して閲覧できるため、セキュリティの高い通信方式です。 このほか、シークレットチャットでは独特な機能も提供されています。ワークショップでは参加者間でシークレットチャットを開始し、Self-destruct Timer(自動消去機能)を数秒程度に設定することで、会話した内容が数秒で自動消去される様子を体験してもらいました。また端末でスクリーンショットを取得すると自動でプロテクションが動作し、スクリーンショットが無効化されるうえで、取得した事実が会話相手に通知される様子を確認しました。 APIとBot 最後にAPIの紹介と、チャットBotに対して会話をする体験をしました。Telegramはいくつかの開発者向けAPIが提供されており、大きく以下の3種類が存在します。 Bot API Telegram API & TDLib Gateway API 今回の体験では、講師側が上記のBot APIを利用して準備したチャットBotと会話をしてもらいました。 参加者はチャットBotに対して、準備されたいくつかのコマンドを使って自由に会話をします。それらのコマンドのうちいくつかには、端末の電話番号や位置情報など個人情報を送信させるスクリプトを講師が仕込んでいました。参加者には、取得可能な情報範囲の理解と、意図しない情報開示のリスクを体感してもらいました。 Telegram API、及びTelegramクライアントはオープンソースのため柔軟性や拡張性が非常に高い反面、利用する際にはこれらの動作仕様について十分に注意することが必要です。 参加者の声 ワークショップ後に実施したアンケートを見ると、参加者全員がTelegramを初めて利用したという結果でしたが、利用後のTelegramの印象を聞くと「怖くない」「どちらかというと怖くない」という回答を多くの参加者からいただきました。また「実際に触りながら学ぶことができ楽しかった」、「面白かった!」という声や、ワークショップを他の方に勧めたいかという問いに対しても全員からポジティブなリアクションをいただき非常に嬉しく思いました。 一方で「せっかくなので自己紹介を交えて交流を深めたかった」、「ボリュームが少なかった」、「サービスの背景など座学的な部分を深く知りたかった」というフィードバックもあり、今後を考えるうえで非常に参考になりました。 Telegramを闇雲に怖がるのではなく、実際に体験するなかで注意すべきポイントを知ってもらうことができ、ワークショップを開催した意味があったのではないかと思います。 ワークショップを開催してみて 私自身初めてのイベント参加、ワークショップ講師で至らぬ点が多くありましたが、イベントを主催して下さった皆さん、一緒に講師をした同プロジェクトの鮫嶋さんやプロジェクトメンバーに支えていただき、無事開催に至ることができました。ありがとうございます。 開催前は1時間半という時間枠のなかで終えることができるのかという不安であったり、アプリケーションのインストールやSMS利用に抵抗感があるのではないかという心配もありましたが、いざ始めてみると時間はやや余るくらいで、ハンズオンもスムーズに進めることができました。 再度開催する機会があった際は、アンケートで頂いた意見を盛り込みながら、より良いワークショップを目指していきたいと思っています。 ワークショップに参加して下さった皆さんにも、改めて感謝いたします。 おわりに Telegramは漠然としたイメージで「危ない」と言われることが多いですが、具体的にどのようなリスクがあるのか、どのような動作をするのかを体験を通じて学んでもらいました。 繰り返しとなりますが、特に日本においては詐欺や犯罪に利用されることが多く、Telegramへの誘導や利用の強要があった場合は利用せず下記の相談先への連絡を検討してください。 闇バイト 警察の呼びかけ強化以降 応募者など保護のケースも 匿名通報ダイヤル 読んで頂いた方にとって、本ブログの内容がTelegramについて「正しく知って正しく恐れる」ことに繋がれば幸いです。 *1 : Telegram FAQ *2 : Telegram Privacy
こんにちは、イノベーションセンターの鈴ヶ嶺です。普段は AI/ML システムに関する業務に従事しています。 本記事では、CUDA 12.8 から追加された Checkpoint API の概要について解説します。 まず、Checkpoint のユースケースやこれまでの NVIDIA CUDA における Checkpoint の試みなどの背景を説明し、新たに追加された CUDA Checkpointing について解説します。 さらに実際に実装し、torchvision や transformers などの CUDA アプリケーションに対して、Checkpoint の検証をしています。 背景 CUDA Checkpointing 実装と検証 cu_check tool 検証 Pytorch Counter torchvision transformers まとめ 背景 Checkpoint は計算途中の内部状態(メモリなど)をディスクなどに保存し、任意のタイミングで計算を再開できる技術です。 想定されるユースケースとして「障害発生時のバックアップ」、「ライブマイグレーション」、「長時間実行される計算の途中結果保存」、「タスクスケジューリングにおけるプリエンプション」、「フォレンジック分析における証拠保全」などが挙げられます。 特に GPU 分野では、昨今の大規模言語モデルに代表される長期間にわたる学習処理の一時保存や、リソース最適化の一環として、一部の GPU プロセスを別の GPU サーバへ移行するといった用途が考えられます。 しかし、これまでの NVIDIA CUDA における Checkpoint 手法としてさまざまな試みはありましたが、アプリケーションの実行環境自体に変更を加えて、CUDA Driver API の呼び出しを傍受しているため完全に透過的なものではありませんでした。 1 2 3 4 5 6 7 8 9 10 一方、2024 年 7 月 NVIDIA の Technical Blog において、 CUDA Driver API として version 555 から Checkpoint が実験的に実装されたことが発表されました。 オープンソースな Checkpoint utility の CRIU (Checkpoint/Restore in Userspace) との組み合わせについても説明されています。 https://developer.nvidia.com/blog/checkpointing-cuda-applications-with-criu/ 以下にそのツールである cuda-checkpoint が公開されています。リポジトリにはツールのバイナリしか置いておらず、バイナリの文字列を調べると cuGetExportTable 11 以外の API 呼び出しが存在しておらず、おそらくドキュメント化されていない関数を利用する形で実装されていました。 https://github.com/NVIDIA/cuda-checkpoint git clone https://github.com/NVIDIA/cuda-checkpoint.git cd cuda-checkpoint strings ./bin/x86_64_Linux/cuda-checkpoint | grep cu # libcuda.so.1 # cuDriverGetVersion # cuGetExportTable 他にも、 MemVerge 社は早期にこの API を利用した AI の学習 PoC を実施して GTC24 などで発表しています。 CUDA 12.x driver enhancements will enable the open-source CRIU project to checkpoint and restart a GPU-based compute node. We'll provide a technical overview and demonstrate this new capability. https://www.nvidia.com/en-us/on-demand/session/gtc24-p63184/ www.youtube.com そして、2025 年 3 月の現時点では Driver version 570 および CUDA 12.8 から CUDA Checkpointing として正式に API が公開されました。 先ほどの check-checkpoint のリポジトリにも一部その API を利用するコードが公開されていますが、 cuda-checkpoint と併用されており、新たに仕様公開されたものに置き換わっていないと思われます。 12 13 そのため、現在 CUDA 12.8 で新たに公開された API を利用したサンプルコードが見当たらない状況になっていると思われます。 次の章で公開されている Checkpoint API を調査し、どのように利用するのかを確認します。 CUDA Checkpointing CUDA Checkpointing の API 一覧が以下になります。 https://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__CHECKPOINT.html CUresult cuCheckpointProcessGetState ( int pid, CUprocessState* state ) CUDA プロセスの現在の状態( CU_PROCESS_STATE_RUNNING , CU_PROCESS_STATE_LOCKED , CU_PROCESS_STATE_CHECKPOINTED , CU_PROCESS_STATE_FAILED )を取得します。 CUresult cuCheckpointProcessLock ( int pid, CUcheckpointLockArgs* args ) CUDA プロセスを lock して、以降の CUDA API 呼び出しをブロックします。 CUresult cuCheckpointProcessCheckpoint ( int pid, CUcheckpointCheckpointArgs* args ) GPU メモリの内容を host memory に保存し、CUDA プロセスを checkpoint します。 CUresult cuCheckpointProcessRestore ( int pid, CUcheckpointRestoreArgs* args ) CUDA プロセスをリストアします。状態は CU_PROCESS_STATE_CHECKPOINTED である必要があります。 CUresult cuCheckpointProcessUnlock ( int pid, CUcheckpointUnlockArgs* args ) CUDA プロセスの lock を解除して、CUDA API Call を再開できるようにします。 CUresult cuCheckpointProcessGetRestoreThreadId ( int pid, int* tid ) CUDA プロセスの Thread ID を取得する。 引用: https://arxiv.org/abs/2502.16631 14 上図を参考にすると、実行中のプロセスの Checkpoint は次のように実行します。 cuCheckpointProcessLock cuCheckpointProcessCheckpoint CRIU dump また、保存したものを Restore するには次のように実行します。 CRIU restore cuCheckpointProcessRestore cuCheckpointProcessUnlock 次の章で実際にこれらを動作するコマンドツールを実装して、Pytorch などのアプリケーションの Checkpoint が可能かを確認します。 実装と検証 cu_check tool 次のようにそれぞれの Checkpoint API をサブコマンドとして、特定のプロセス ID に実行するツールを実装します。 cu_check.c #include <cuda.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define CHECK_CU (func) \ do { \ CUresult res = (func); \ if (res != CUDA_SUCCESS) { \ const char *errName = NULL ; \ const char *errDesc = NULL ; \ cuGetErrorName (res, &errName); \ cuGetErrorString (res, &errDesc); \ fprintf ( stderr , " %s failed: %s %s\n " , #func, errName, errDesc); \ return - 1 ; \ } \ } while ( 0 ) const char * getCUprocessState (CUprocessState state) { switch (state) { case CU_PROCESS_STATE_RUNNING: return "CU_PROCESS_STATE_RUNNING" ; case CU_PROCESS_STATE_LOCKED: return "CU_PROCESS_STATE_LOCKED" ; case CU_PROCESS_STATE_CHECKPOINTED: return "CU_PROCESS_STATE_CHECKPOINTED" ; case CU_PROCESS_STATE_FAILED: return "CU_PROCESS_STATE_FAILED" ; default : return "OTHER_STATE" ; } } int main ( int argc, char **argv) { if (argc < 3 ) { fprintf ( stderr , "usage: %s [state|lock|checkpoint|restore|unlock] <pid> \n " , argv[ 0 ]); return - 1 ; } const char *subcommand = argv[ 1 ]; int pid = atoi (argv[ 2 ]); CHECK_CU ( cuInit ( 0 )); if ( strcmp (subcommand, "state" ) == 0 ) { CUprocessState state; CHECK_CU ( cuCheckpointProcessGetState (pid, &state)); printf ( "state: %s\n " , getCUprocessState (state)); } else if ( strcmp (subcommand, "thread" ) == 0 ) { int threadId = 0 ; CHECK_CU ( cuCheckpointProcessGetRestoreThreadId (pid, &threadId)); printf ( "thread id: %d\n " , threadId); } else if ( strcmp (subcommand, "lock" ) == 0 ) { CUcheckpointLockArgs args = { .timeoutMs = 600000 // 10min timeout }; CHECK_CU ( cuCheckpointProcessLock (pid, &args)); printf ( "locked successfully \n " ); } else if ( strcmp (subcommand, "checkpoint" ) == 0 ) { CHECK_CU ( cuCheckpointProcessCheckpoint (pid, NULL )); printf ( "checkpointed successfully \n " ); } else if ( strcmp (subcommand, "restore" ) == 0 ) { CHECK_CU ( cuCheckpointProcessRestore (pid, NULL )); printf ( "restored successfully \n " ); } else if ( strcmp (subcommand, "unlock" ) == 0 ) { CHECK_CU ( cuCheckpointProcessUnlock (pid, NULL )); printf ( "unlocked successfully \n " ); } else { printf ( "unknown subcommand: %s\n " , subcommand); return - 1 ; } return 0 ; } gcc -I /usr/local/cuda-12. 8 /include cu_check.c -o cu_check -lcuda # install sudo mv cu_check /usr/local/bin # Usage cu_check state < pid > cu_check lock < pid > cu_check checkpoint < pid > cu_check restore < pid > cu_check unlock < pid > 検証 事前に CRIU をインストールします。 curl -LO " http://github.com/checkpoint-restore/criu/archive/v4.0/criu-4.0.tar.gz " tar xvfz criu-4. 0 .tar.gz cd criu-4. 0 / make -j sudo make install Pytorch Counter CUDA Memory 上に保存し、 1 秒ごとに inc される Counter コードでまず検証します。 torch_counter.py import torch, time counter = torch.tensor( 0 , device= 'cuda' ) while True : print (counter) counter.add_( 1 ) time.sleep( 1 ) 次のように実行します。 1 秒ごとに Counter が継続して出力される様子が確認できると思います。 pip install torch python torch_counter.py & sleep 5 PID = $( pgrep -f ' python torch_counter.py ' ) # checkpoint rm -rf tcnt && mkdir -p tcnt cu_check lock $PID cu_check checkpoint $PID sudo criu dump -j -D tcnt -t $PID du -sh tcnt # 755M # restore sudo criu restore -j -D tcnt & while ! pgrep -f ' python torch_counter.py ' > /dev/null 2 >& 1 ; do sleep 1 ; done sudo cu_check restore $PID sudo cu_check unlock $PID sleep 5 kill -9 $PID torchvision 次に、torchvision の ResNet で検証します。 学習途中の状態が Checkpoint され、再開後に学習途中から実行される様子が確認できると思います。 git clone https://github.com/pytorch/examples.git cd examples/imagenet/ pip install -r requirements.txt python main.py -a resnet152 --dummy -j 0 & sleep 20 PID = $( pgrep -f ' python main.py -a resnet152 --dummy -j 0 ' ) # checkpoint rm -rf resnet && mkdir -p resnet cu_check lock $PID cu_check checkpoint $PID sudo criu dump -j -D resnet -t $PID du -sh resnet # 50G # restore sudo criu restore -j -D resnet & while ! pgrep -f ' python main.py -a resnet152 --dummy -j 0 ' > /dev/null 2 >& 1 ; do sleep 1 ; done sudo cu_check restore $PID sudo cu_check unlock $PID sleep 20 sudo kill -9 $PID transformers 最後に、transformers で検証します。 こちらも同様に継続して学習可能である様子を確認できました。 train_bert.py # ref: https://huggingface.co/docs/transformers/training from datasets import load_dataset from transformers import ( AutoTokenizer, AutoModelForSequenceClassification, TrainingArguments, Trainer, ) dataset = load_dataset( "yelp_review_full" )[ "train" ].select( range ( 10000 )) tokenizer = AutoTokenizer.from_pretrained( "google-bert/bert-base-cased" ) def tokenize_function (examples): return tokenizer(examples[ "text" ], padding= "max_length" , truncation= True ) small_train_dataset = dataset.map(tokenize_function, batched= True ) model = AutoModelForSequenceClassification.from_pretrained( "google-bert/bert-base-cased" , num_labels= 5 ) trainer = Trainer( model=model, args=TrainingArguments(num_train_epochs= 10000 ), train_dataset=small_train_dataset, ) trainer.train() 次のように実行します。 pip install transformers datasets accelerate python train_bert.py & sleep 60 PID = $( pgrep -f ' python train_bert.py ' ) # checkpoint rm -rf bert && mkdir -p bert cu_check lock $PID cu_check checkpoint $PID sudo criu dump -j -D bert -t $PID --tcp-established du -sh bert # 5.5G # restore sudo criu restore -j -D bert --tcp-established & while ! pgrep -f ' python train_bert.py ' > /dev/null 2 >& 1 ; do sleep 1 ; done sudo cu_check restore $PID sudo cu_check unlock $PID sleep 20 sudo kill -9 $PID まとめ 本記事では CUDA 12.8 で導入された Checkpoint API について解説しました。 また実際の実装を通して、昨今の大規模言語モデルのデファクトなライブラリである transformers などの CUDA アプリケーションに対して、Checkpoint が動作することを確認しました。 今後これらの技術が活用されることで、長期間にわたる学習処理の一時保存や、効率的なリソース最適化が行われることを期待しています。 Takizawa, Hiroyuki, et al. "CheCUDA: A checkpoint/restart tool for CUDA applications." 2009 International Conference on Parallel and Distributed Computing, Applications and Technologies. IEEE, 2009. ↩ Nukada, Akira, Hiroyuki Takizawa, and Satoshi Matsuoka. "NVCR: A transparent checkpoint-restart library for NVIDIA CUDA." 2011 IEEE International Symposium on Parallel and Distributed Processing Workshops and Phd Forum. IEEE, 2011. ↩ Jiang, Hai, et al. "A checkpoint/restart scheme for cuda programs with complex computation states." International Journal of Networked and Distributed Computing 1.4 (2013): 196-212. ↩ Garg, Rohan, et al. "CRUM: Checkpoint-restart support for CUDA's unified memory." 2018 IEEE International Conference on Cluster Computing (CLUSTER). IEEE, 2018. ↩ Jain, Twinkle, and Gene Cooperman. "CRAC: checkpoint-restart architecture for CUDA with streams and UVM." SC20: International Conference for High Performance Computing, Networking, Storage and Analysis. IEEE, 2020. ↩ Shukla, Dharma, et al. "Singularity: Planet-Scale, Preemptive and Elastic Scheduling of AI Workloads. arXiv. org (February 2022)." arXiv preprint arXiv:2202.07848. ↩ Eiling, Niklas, et al. "Cricket: A virtualization layer for distributed execution of CUDA applications with checkpoint/restart support." Concurrency and Computation: Practice and Experience 34.14 (2022): e6474. ↩ Nukada, Akira, Taichiro Suzuki, and Satoshi Matsuoka. "Efficient checkpoint/Restart of CUDA applications." Parallel Computing 116 (2023): 103018. ↩ Eiling, Niklas, Stefan Lankes, and Antonello Monti. "Checkpoint/Restart for CUDA Kernels." Proceedings of the SC'23 Workshops of the International Conference on High Performance Computing, Network, Storage, and Analysis. 2023. ↩ Yang, Yanning, et al. "On-demand and Parallel Checkpoint/Restore for GPU Applications." Proceedings of the 2024 ACM Symposium on Cloud Computing. 2024. ↩ https://forums.developer.nvidia.com/t/cugetexporttable-explanation/259109/2 ↩ https://github.com/NVIDIA/cuda-checkpoint/blob/6ec728aff032c18c9fb0794a272d94c6adcce508/src/r570-features.c ↩ https://github.com/checkpoint-restore/criu/blob/4b099510b35f98a1f1d6589b1660470402fc1fef/plugins/cuda/cuda_plugin.c ↩ Stoyanov, Radostin, et al. "CRIUgpu: Transparent Checkpointing of GPU-Accelerated Workloads." arXiv preprint arXiv:2502.16631 (2025). ↩
こんにちは、ソリューションサービス部の田中です。 約半年間、イノベーションセンターのNetwork Analytics for Security(以下、NA4Sec)プロジェクトに、社内ダブルワーク制度を通して、参画していました。 今日は、タイトルにもある通り、営業系キャリアを歩んできた私がアナリスト/エンジニア業務をして学べたことや感じたことについて紹介します。 営業系出身の方で、アナリスト業務に興味がある方向けに、今後のキャリア参考になればと思います。 RemcosというRATファミリーについて、机上調査をしましたので、そちらもご興味あれば! どんなキャリアを歩んできたか なぜ社内ダブルワークに申し込んだのか NA4Secプロジェクトの紹介 従事した業務「RATファミリーの机上実態調査」 Remcosの机上実態調査(特徴) Remcosの机上実態調査(事例/感染チェーン) Remcosの机上実態調査(推奨対策) 個人的にぶち当たった壁 ダブルワークを通して、学べたこと どんなキャリアを歩んできたか 現在入社6年目になります。 初期配属は、製造業向けのアカウント営業をしておりました。 4年目のタイミングで、一度部署異動してまして、今はセキュリティ領域のプリセールスエンジニアを担当しています。 普段はSASEやセキュリティオペレーション(SOC/SOAR)領域を提案することが多いです。 ということで、構築や運用経験は皆無であり、机上知識のみで少しセキュリティをかじっている系の人です。 LinuxやPython等のプログラミングも全く触ったことがありません。。。 なぜ社内ダブルワークに申し込んだのか 一言でいうと、「実務経験を積みたい!」です。 構築や運用の実務経験がなく、机上ベースだと提案時の発言の重みがなかったり、説得力を出し切れなかったり、課題を持っていました。 また、セキュリティアナリスト業務がどんなものかシンプルに興味もありました。 そんな時、未経験者でもOKというNA4Secプロジェクトの社内ダブルワークの募集要項を見て、ポチっと申し込みをしました。 なんとか無事、面談も実施して、採用していただけました。 NA4Secプロジェクトの紹介 「Network Analytics for Security」 通称NA4Sec(なよせ)と呼ばれています。 「NTTはインターネットを安心・安全にする社会的責務がある」という理念に基づき、攻撃インフラの解明、撲滅の実現を目指して活動しているイノベーションセンターのプロジェクトです。 従事した業務「RATファミリーの机上実態調査」 私がダブルワーク中に従事した「RATファミリーの机上実態調査」について、ご紹介します。 RATとは、Remote Access Trojanの略で、リモートアクセス型のトロイの木馬です。 攻撃者はRATを利用してコンピュータに不正に侵入し、遠隔操作を可能にしてユーザーの個人情報を盗み見たり、端末内のウイルス対策ソフトを停止させ別の攻撃を仕掛けるための踏み台に悪用します。 RATの中でも特に日本での攻撃被害が多いものをピックアップして、汎用RATの実態について調査をしました。 Remcosの机上実態調査(特徴) Remcos, Xworm, NjRATという3つのRATファミリーを対象に机上調査を始めましたが、今回はRemcosにフォーカスして簡単にご紹介します。 Remcosは、2016年7月に最初のバージョンがドイツのBreakingSecurity社から販売されたリモートアクセスツールです。 現在もリモートアクセスツールとして公式販売されていますが、攻撃者たちにRATとして悪用されている実態があります。 YouTubeでもRemcosの設定動画が公開されていたり、非常に身近なツールです。 Check Point Software Technologies社によると、2023年7月にマルウェアファミリーランキングで3位入っており、全世界で悪用が観測されています。 そんなRemcosの特徴としては、大きく3点あります。 特権の昇格 感染したシステムの管理者権限を取得し、ユーザーアカウント制御(UAC)等を無効にすることが可能になります。 攻撃者は管理者権限を利用し、悪意のある機能を実行しやすくなります。 防御回避 プロセスインジェクションを使用して正当なプロセスに自身を埋め込み、検出を困難にします。 Windows OSのパイプ方式を活用し、データ転送のための秘密チャネルを使い、EPPやEDRによる検出を回避することも可能です。 データ収集 キーストロークを記録し、スクリーンショット、オーディオ、クリップボード等の内容をキャプチャし、感染したシステムからパスワードの収集も可能にします。 Remcosの机上実態調査(事例/感染チェーン) 今回は、2023年にコロンビア企業を標的とする攻撃で、Remcosが利用されたケースを見ていきます(下記添付に本ケースの感染チェーンをまとめています)。 本ケースでは、 まずはフィッシング経由で、添付されている圧縮ファイルをダウンロードさせます。 格納されていたBATファイルからPowershellを実行し、2つの.NETモジュールをメモリに読み込ませます。 この2つの.NETモジュールで、Remcos本体をダウンロードする前に、ウイルス検知/開始の回避や証跡削除を試みます。 1つ目の.NETモジュールで、Kernel32.dllやntdll.dllのフックを外し、監視を回避。そしてamsi.dll AmsiScanBuffer関数にパッチを適用し、ウイルススキャンも回避します。さらに、ntdll.dll EtwEventWrite関数にパッチを適用し、イベントトレースログが残らないように、証跡を消します。 2つ目の.NETモジュールで、デバッガがあるのか確認。PowerShellを使用し、メインモジュールファイルを削除し、ステルス性を確保します。 上記のウイルス検知/監視を回避、証跡を可能な限り消したのち、Remcos本体をダウンロードします。 セキュリティ初心者からも私からすると、ただ単にRemcosがダウンロードされるのみかと思っていたのですが、本体ダウンロード前にこれだけのプロセスがあったのは非常に驚きでした。 Remcosの机上実態調査(推奨対策) Remcosのようなマルウェアを対策するには以下のような多層防御が推奨となります。 メールフィルタリングソリューションを利用して、ユーザーがメール受信する前にスパムメッセージを排除すること。 電子メールの怪しいハイパーリンクをクリックしたり、添付ファイルを開いたりしないように、ユーザートレーニングを実施すること。 OSやソフトウェアの脆弱性対応を迅速に実施すること。 ネットワークセキュリティ(SASE, NGFW等)を利用して、脅威に関連する悪意のあるアクティビティを検出すること。 EDRを利用して、エンドポイントで発生する異常な振る舞いを検出すること。 個人的にぶち当たった壁 プログラミングがわからない! マルウェアや攻撃者を調査する際も、ワークフローツール等を組み合わせて、自動化して情報収集するケースが多いです。その時には最低限のプログラミングスキルが必要でした。 また、すでにデプロイされている分析内容/マルウェア解析等の海外ドキュメントを正確に理解する上でもPythonやC++を理解できていないと非効率的でした。 一朝一夕で、身につくスキルでもないので、日々の勉強が重要だなと実感。一方で、即時的な打ち手としては、ChatGPTさんに聞くとプログラミングの意味をクイックに回答してくれるので非常に重宝していました。スゴイ便利。。。 ダブルワークを通して、学べたこと 学ぶことができた点としては、以下3点になります。 部署異動や転職という大きな決断を下す前に、業務が自分にマッチしているか事前検証できる点です。 営業とアナリスト業務の間には、非常に大きなGAPがあると思います。大きな決断前に業務イメージを一部でも把握できることは、今後のキャリア選択をする際の大きな材料になりました。 本業では得られない知識・スキルを実践で学ぶことができる点です。 RATファミリーの調査で海外ドキュメントを読み込んでみたり、簡単な分析環境を構築してみたり、実際に手を動かすことで、新しい知識の定着ができました。 わからないことがわかる!GAPがわかる!点です。 想像はしていましたが、セキュリティアナリスト業務に従事するには、プログラミングやLinuxが触れて当たり前!等の自分とのGAPがわかる点は、非常によい学びでした。 普段からSOC等で、アナリスト実務されている方は、改めて大尊敬する機会になりました。 このような学びがあるため、セキュリティ領域の営業系の方にも、アナリスト実務をぜひお勧めしたいです!
こんにちは、イノベーションセンターの加藤です。この記事では、Transformerベースの言語モデルで利用可能な高速化技術である投機的デコーディング(speculative decoding)を用いて、音声認識モデルのWhisperの高速化を検証したのでその結果を紹介します。 投機的デコーディングとは Whisperとは 実験 英語音声 (LibriSpeech) の結果 日本語音声 (Common Voice 17.0 日本語サブセット) の結果 まとめ 投機的デコーディングとは 大規模言語モデル(LLM)をはじめとするTransformerベースの言語モデルは、これまでの単語列から次に現れそうな単語を予測することを繰り返して文章生成を行なっています。 これに対し、元のモデルよりも軽量な言語モデルの出力を下書きとして利用することで、元のモデルの出力を完全に再現しながら文章生成を高速化する投機的デコーディング(speculative decoding)と呼ばれる手法があります。これは下図のように、軽量なモデルで数単語先まで予測してから元の大きなモデルでその予測を検証することで、元の大きなモデルの推論回数を節約しながら文章を生成する手法です。ちょうど人間が予測変換を活用しながら文章を入力するのと似た流れになっているのがわかると思います。 投機的デコーディングの詳細は 過去の記事 を参照してください。 下書きとして利用される言語モデルはLLMのようなTransformerベースである必要はなく、これまでの単語列から次に現れそうな単語をなんらかの形で予測できれば十分です。 投機的デコーディングでよく使われる下書きモデルは元モデルの蒸留モデルであったり、同じアーキテクチャでパラメータ数の少ないLLMであったりしますが、要約タスクやRetrieval Augmented Generation (RAG)などプロンプトから文言を抜き出すことの多いユースケースでは、プロンプトから収集したN-gramを参照して次に現れそうな単語を予測する Prompt Lookup が活用されることもあります。今回の実験ではこのPrompt Lookupの実装を少し変更して採用しています。 Whisperとは Whisper はTransformerベースの音声認識モデルであり、音声から特徴を抽出するEncoderと、対応するテキストを出力するDecoderからなります。 このDecoderでは文章生成タスク用の言語モデルと似た動作をしており、音声の特徴とこれまで出力したテキストから次に現れそうな単語を予測することを繰り返しています。そのため、投機的デコーディングをWhisperのDecoder部分にも適用できます。また、一般的な文章生成タスクと異なり出力文章の正解が大体決まっているため、下書きを作成する際に元モデルの途中までの出力を参照しなくても十分な正確性が期待できます。 そこで、性能が高いがモデルサイズの大きいWhisperモデルに対してまず軽量な音声認識モデルで文章を出力し、これを下書きとして参照することで、精度を維持したまま高速化する手法を実装してみました。推論の流れを下図に示します。 各時点での書き起こしの続きを下書きから抜き出す際はPrompt Lookupと同様の手法を使い、下書きのN-gramから一致度の高いものを選択しています。例えば上図の例では The man worked の時点で元モデルの予測が下書きから乖離していますが、下書きから a に続く文字列である security guard. を抜き出し、先読みに再度成功しています。 実験 今回は元モデルとしてwhisper-large-v3を使い、軽量モデルとしてwhisper-tinyを使いました。評価データセットは英語音声の LibriSpeech ASRコーパス と日本語音声の Common Voice 17.0 日本語サブセット の2つを用意しました。音声認識の精度は認識に失敗した単語数と関連が深い Word Error Rate (WER) を用いて評価し、その際日本語データに対しては MeCab で単語分割を行なっています。 処理速度はReal Time Factor (RTF)で算出します。これは1秒のデータを処理するのにかかった秒数を表し、小さいほど良い結果であることを示しています。 結果は以下の表のようになりました。 英語音声 (LibriSpeech) の結果 モデル WER RTF tiny (参考) 0.089 0.013 large-v3 0.032 0.085 large-v3 + tiny下書き 0.032 0.072 参考としてtinyモデル単体で音声認識した結果も示していますが、large-v3よりも6.5倍速い代わりにWERが悪化していることがわかります。 また、speculative decodingは元モデルの出力を完璧に再現するため、large-v3単体で動かした時と先読みをつけた時どちらもWERが0.032となっています。 そして先読みをつけた場合は実行速度が18%向上しました。参考として各モデルの書き起こし例を以下に示しますが、large-v3とtinyの出力の一致率が高く、うまく先読みを当てられていることがわかります。 Ground Truth (原稿) large-v3 tiny HURSTWOOD WALKED THE FLOOR MENTALLY ARRANGING THE CHIEF POINTS OF HIS SITUATION Hurstwood walked the floor, mentally arranging the chief points of his situation. First would walk to the floor mentally arranging the chief points of his situation. HE ALSO THOUGHT OF HIS MANAGERIAL POSITION He also thought of his managerial position. He also thought of his managerial position. FORTUNATELY THERE WAS NOTHING FROM HIS WIFE EITHER Fortunately, there was nothing from his wife, either. Fortunately, there was nothing from his wife either. HE AROSE FROM HIS CHAIR AND WENT AND LOOKED OUT INTO THE STREET He arose from his chair and went and looked out into the street. He rose from his chair and went and looked out into the street. HURSTWOOD ALMOST EXCLAIMED OUT LOUD AT THE INSISTENCY OF THIS THING Hurstwood almost exclaimed out loud at the insistency of this thing. Herstwood almost explained out loud at the insistence of this thing. 日本語音声 (Common Voice 17.0 日本語サブセット) の結果 モデル WER RTF tiny (参考) 0.844 0.017 large-v3 0.143 0.108 large-v3 + tiny下書き 0.143 0.120 一方で日本語音声では実行速度が改善せず、元のlarge-v3単体で動かした方が速いという結果になりました。そもそも全体的にWERが高く音声認識に苦戦していることもあり、品質の高い先読みを提供できていないようです。 各モデルの書き起こし例を以下に示します。 Ground Truth (原稿) large-v3 tiny セリヌンティウスは、縄打たれた。メロスは、すぐに出発した。初夏、満天の星である。 セリノンティウスは縄打たれた ネラスはすぐに出発した 初夏満天の星である セリナンティーズは奪われたネラズはすぐにしようっぱいずした、そこは満転の押すである。 僕らは何かを求めてゴミの山を漁っていた 僕らは何かを求めてゴミの山を漁っていた。 僕らは何かを求めて説明をさっていた ブラシを板の上に置くや否や、 ブラシを板の上に置くや否や では、教えておいては、お家に行きたいな。 私はポピュラー音楽を聞きたい。 私はポピュレア音楽を聴きたい 私はポフリアーを仲置きたい 大事なものじゃないの?と君はきいた 大事なものじゃないの?と君は聞いた 大事なものじゃないの?と、君が聞いた まとめ 本稿ではLLMの文章生成に使われている高速化手法のspeculative decodingを音声認識モデルに適用するとどれくらい高速化できるのか検証しました。 結果、一部の条件下では速くなることがわかりましたが万能ではなく、認識精度が確保できないケースでは先読みの恩恵が受けられないようでした。 また、Whisperはturboモデルというlargeモデルの軽量化版を提供しており、こちらは largeとほぼ同じ性能で8倍の高速化 をうたっています。このように学習時に工夫を入れ込んだ方がより高速な推論を期待でき、リソースが十分にある場合は学習部分の改善も視野に入れた方が良さそうです。
この記事では、NTTコミュニケーションズの先端AI数理PJが埼玉大学で行った時系列分析に関する研究会の様子とその講義資料およびハンズオン資料について紹介します。本記事で紹介した資料の完全版は こちら をご覧ください! 目次 目次 はじめに 講義の準備 講義内容の紹介と研究会の様子 AI・データ分析関連事業紹介と時系列分析の背景 可視化と探索的データ解析/前処理 線形モデリング Deep Learningによる時系列予測 質疑応答 参加者の声 感想 おわりに はじめに イノベーションセンター テクノロジー部門 先端AI数理PJの石山です。普段の業務では、因果推論や機械学習をもちいた時系列データ分析の研究開発やお客さまデータ分析案件支援を行っています。 この記事では、2023年12月に埼玉大学で行われた 「埼玉大学産学官連携協議会データサイエンス技術研究会第4回」 の内容とその様子を紹介します。 研究会では「時系列データの解析と産業応用」という題で、埼玉大学の学生の方や埼玉県の企業の方向けに講義とGoogle Colaboratoryによるハンズオン演習を行いました。 この講義は、先端AI数理PJがインターネット上に公開している 1 時系列データ分析コンテンツ 「ごちきか」 をもとに構成し、時系列分析の各手法から最新のDeep Learningに関する議論についても扱うといった内容です。 本研究会は、2024年人工知能学会全国大会でのNTTコミュニケーションズの展示に来ていただいたことをきっかけとして埼玉大学の平松薫教授からお話をいただき、実施することになりました。 人工知能学会では、2023年、2024年に「ごちきか」のトピックをまとめた冊子を配布したため、そういった活動も声をかけていただいたきっかけになったかもしれません。 なお、2024年度には、この研究会の内容を元に大学生向けに再編した講義を、「データサイエンス実践基礎」の一部として岩手大学で実施しました。 2 私は、「第5回: 分析モデリング」と「第7回: 因果推論」の講義を担当し、特に「第5回: 分析モデリング」に関する内容は今回の研究会の内容や反響を踏まえて作成しました。 講義の準備 「ごちきか」の運営メンバー5人が各コンテンツを担当して資料作成と講義を行う形式で準備を進めました。 準備の中では、伝わるか不安、ニーズを捉えられているかわからないといった議論が何度か起こりました。というのも、講義作成を担当したメンバーが所属するのは先端AI数理PJという研究開発を行うチームであることから興味の対象が比較的新しい技術になりがちな一方で、研究会には私たちが専門とする研究分野以外の方も多く参加されることが想像されることや、「ごちきか」はある程度数学やプログラミングを学んだ経験がある読者を対象に書かれているため、実務家にとってはハードルが高い話を説明無しにしてしまっているのではないかといった懸念がありました。 実施してみたところ、こうした不安とは裏腹に、意外にも(?)最新のディープラーニングに関するコンテンツが人気で、Transformerなどに触れての質問が当たり前のように出ていて驚きました。加えて、実務に関連した質問が多く、時系列データ分析に対してもこうしたディープラーニングを利用することへの期待度の高さが窺えました。 ここからは実際の講義の内容を紹介します。 講義内容の紹介と研究会の様子 講義は以下の構成で、ハンズオン演習とともに講義を行いました。 (NTTコミュニケーションズにおける)AI・データ分析関連事業紹介 (時系列分析の)背景 可視化と探索的データ解析/前処理 線形モデリング Deep Learningによる時系列予測 講義は、ドメイン知識を活かしたモデリングが大切であることと、今後、時系列データに対してディープラーニングをはじめとした大規模モデルを活かすためには、大規模時系列データセットを集めることが必要ということをメインメッセージとして展開しました。 AI・データ分析関連事業紹介と時系列分析の背景 時系列分析に関する私たちの取り組みと、時系列データがどのようなもので、分析によってどのようなことが可能になるかといった時系列分析の背景について説明しました。応用例として AIプラント運転支援ソリューション などの私たちの取り組みを挙げることによって講義で扱う内容についてイメージを持ってもらいました。 可視化と探索的データ解析/前処理 本格的な分析の前段階で必要となる可視化とそれによる探索的データ解析、そして前処理について解説しました。統計量の確認や特徴量の作成に関しては、時系列特有の方法や注意点があるため、通常の方法を素朴に行うのではなく、時系列性を考慮した手法で慎重に行う必要があることを説明しました。 線形モデリング 時系列線形回帰モデリングの説明を行い、線形モデルの課題である多重共線性の説明とその対策として、縮小推定や次元削減を伴うモデリングについての説明とハンズオンを行いました。 Deep Learningによる時系列予測 MLP(多層パーセプトロン)からCNN(畳み込みニューラルネットワーク)、RNN(リカレントニューラルネットワーク)について説明し、近年話題のTransformerのアーキテクチャとそれを時系列に応用したInformerについての説明とハンズオンを行いました。 最後に時系列分析に関して話題になったトピックを紹介しました。人工知能分野の国際会議AAAI2023の発表である「Are Transformers Effective for Time Series Forecasting?」とそのアンサーとして書かれたHugging Faceのブログ記事「Yes, Transformers are Effective for Time Series Forecasting 🤗」について、お話ししました。 質疑応答 参加者は埼玉県の地元企業の方と学生の方であわせて15名程度でしたが、ほとんどの方に質問をいただきました。質疑応答では、機械学習を実務で活かすうえでの課題についての質問が多くありました。参加者の方の技術レベルも高く、難易度の高い講義である深層学習パートに関する質問が盛況で、実際に手を動かす中で困っていることであったり逆にDX推進を取りまとめるという立場からのコメントもありました。さらに、製造業で直面する課題についてはお互いの事例を交えて踏み込んだ議論ができたため、私たちとしても非常に勉強になり、有意義な時間となりました。 参加者の声 参加された方からは 「研究会の内容があまり他にはないユニークな内容だった」 「初心者に対してもわかりやすい、かつ、リッチな内容を含んでいた」 「古典的な線形回帰から最先端のTransformerベースの手法までを示されたことで、現場で直面する課題を具体的に認識することができた」 「時系列データに特化した内容で、より深く知ることができた。実務で取り入れる際の実践的な手法に沿って講義されていたため、今後の実務に活かせそう」 と大変好評をいただきました! 感想 研究会のために講義資料を用意するのは大変でしたが、参加者の方からも好評をいただくことができ、また私たちとしても、あらためて時系列分析の勉強や最新情報へのキャッチアップを行うことができ、大変よい機会でした。 また研究会代表の平松教授からは「次もまたお願いします」とのお言葉を頂きました。私どもとしても埼玉大学をはじめとして埼玉県内の企業・組織のみなさまと今後も連携させていただければと思っております。 おわりに 本講義は、先端AI数理PJが運営する時系列データ分析コンテンツ「ごちきか」の内容をもとに構成し、講義を行ったものです。当日の講義資料と演習用コードも こちら で公開しております。 「ごちきか」では、ほかにも時系列分析に関連して、 スパースモデリング や VAR-LiNGAM などの時系列因果探索、NTTグループ合同で行ったDeep Learningに関する 勉強会資料 なども公開していますので、ぜひご覧ください! 私たちの取り組みに興味がございましたら、時系列データ解析/予測/異常検知/因果探索/因果推論を対象としたPoC、各種機関との共同研究、 Node-AI のご契約を募集中ですので、メールにてご連絡ください。(メール: ai-deep-ic[at]ntt.com) 「ごちきか」公開の経緯やコンセプトについては、 過去のエンジニアブログの投稿 もご覧ください。 ↩ https://www.iwate-u.ac.jp/info/news/2024/11/006453.html ↩
みなさんこんにちは、イノベーションセンターの益本 (@masaomi346) です。 Network Analytics for Security (以下、NA4Sec) プロジェクトのメンバーとして活動しています。 この記事では、2025年1月21日・22日に開催されたセキュリティカンファレンスJSAC2025で登壇したことについて紹介します。 私たちが観測したある2つのPhishing as a Service (PhaaS) の分析結果についての講演 ぜひ最後まで読んでみてください。 JSACについて JSAC (Joint Security Analyst Conference) はJPCERT/CCが主催するセキュリティカンファレンスで、現場のセキュリティアナリストが集い、高度化するサイバー攻撃に対抗するための情報を共有することを目的に開催されています。 非常に注目を集めているセキュリティカンファレンスであり、数日で定員が埋まって参加申し込みが締め切られるほどです。 海外からも注目されており、今年のJSAC2025は海外からの登壇が半分以上を占めていました。 昨年のJSAC2024と比べると、明らかに海外比率が増加していて、国際会議としての色が濃くなってきています。 日本を含むAPACに関係している脅威についての講演が多数占めており、特に今年はAPT攻撃 (高度標的型攻撃) をテーマにした講演が多くなっていました。 また、メイントラックの講演以外にも、ハンズオン形式のワークショップなどもあります。 #JSAC2025 2日目です。本日もよろしくお願いいたします。 pic.twitter.com/3zvTppzOpG — Analysis Center (@jpcert_ac) 2025年1月22日 会場では、さまざまな職種や役割が書かれたシールを配布しており、参加者はそれを付けて参加します。 NA4Secについて 「NTTはインターネットを安心・安全にする社会的責務がある」を理念として、インターネットにおける攻撃インフラの解明・撲滅を目指すプロジェクトです。 NTT Comグループにおける脅威インテリジェンスチームとしての側面も持ち合わせており、有事において脅威インテリジェンスを提供し、意思決定を支援することもあります。 イノベーションセンターを中心として、NTTセキュリティ・ジャパンやエヌ・エフ・ラボラトリーズ(以下、NFLabs.)からもメンバーが参画し、日夜攻撃インフラを追跡しています。 昨年もNA4SecメンバーがJSACに参加しており、それについての記事がありますので、興味がある方はぜひ読んでみてください。 セキュリティカンファレンス「JSAC2024」に参加してきた話(登壇編) セキュリティカンファレンス「JSAC2024」に参加してきた話(聴講編) NA4SecによるJSAC2025登壇 NA4Secからは、フィッシング詐欺に関する講演が1件採択されました。 以下のタイトルで登壇させていただきました。 Analysis of Two Phishers : Like a doppelganger / イベント登壇情報🎙️ \ 国内有数のセキュリティカンファレンス #JSAC2025 にて、NTT Comの益本が二日目(1/22)に登壇✨ ドッペルゲンガーのように同じ振る舞いをする奇妙なPhaaS (Phishing as a Service) について報告します🎣🎣 詳細はこちら⬇ https://t.co/vO0LejZpuJ #ドコモビジネス pic.twitter.com/0QQKySxvbX — ドコモビジネス|NTTコミュニケーションズ (@NTTCom_online) 2025年1月21日 サイバー犯罪を支援するためにさまざまなサービスが誕生しています。フィッシング詐欺においても同様であり、フィッシング詐欺を支援するPhishing as a Service (PhaaS) が存在しています。 PhaaSを利用することで、フィッシング詐欺をするための技術的なハードルが下がり、フィッシング詐欺をしやすくなります。 NA4Secで取り組んでいる活動の1つに、フィッシング詐欺についての脅威分析があります。 活動の中で偶然観測したある2つのPhaaSは、まるでドッペルゲンガーのように同じ振る舞いをしていることがわかりました。 ほぼ同じ時間に同じ投稿をしている 使われているフィッシングサイトやツールが同じである 講演では、2つのPhaaSコミュニティで投稿されている内容や提供されているサービスの内容、2つの攻撃者の関連性などを紹介しました。 分析結果から、これらのPhaaSは何かしらの協力関係があるか、同じ攻撃者によって運営されている可能性があることについても言及しました。 また、PhaaSのフィッシングサイトや環境構築に使われているツールの分析結果、検知やハンティングについても紹介しました。 環境構築に使われるツールを分析することで、どのようにフィッシングサイトを構築されているかを理解できます。 なので、このような分析はフィッシング詐欺の実態解明につながる価値があると考えて紹介しました。 こちらに講演資料が公開されていますので、参加できなかった方もぜひ読んでみてください。(なお、一部の講演スライドを非公開にしています) 英語版 日本語版 さいごに 昨年に続き、JSACで2年連続登壇させていただきました。 昨年のJSAC2024が初めての外部登壇でしたが、あの時と比べると、外部登壇にかなり慣れてきたような気がします。 国内有数のセキュリティカンファレンスを通じて、継続的にセキュリティ業界に貢献しつづけることができて良かったです。 この講演が、少しでもフィッシング詐欺の実態解明に貢献できればいいなと思っています。 今後も引き続き、何かしらの形でセキュリティ業界を盛り上げていくつもりでいます。 この講演に興味を持たれた方へ 攻撃者の詳細に関わる情報については、外部公開資料では非公開になっています。 ただ、サイバー攻撃に係る情報を共有することは実態解明や被害低減につながる価値があると考えています。 出張講演なども前向きに検討しますので、興味のある方はTeam NA4Secまでお気軽にご相談ください。(公開されている資料の最後の方に連絡先が書かれています)
本記事では、現在進行中の研究「機械学習×数理最適化」に関する取り組みの一環として検討している、需要予測を活用した業務プロセスの改善について紹介します。 はじめに 背景 数理最適化とは 機械学習×数理最適化で解決が期待できる課題 実現方法の検討 問題設定 Node-AIでの需要予測 数理最適化によるシフト計画 条件データ 定式化 プログラム 結果 まとめ おわりに はじめに こんにちは、イノベーションセンターの伊藤です。 普段は Node-AI や AI Autopilot System といったプロダクトの品質向上を目的に、研究開発を行っています。現在は予測精度の向上を目指した機械学習技術の研究に加え、予測結果の活用に向けた技術の研究に取り組んでいます。 また、我々のチームでは、これらの取り組みを通じて得られた研究成果やデータ分析ノウハウを ごちきか というナレッジベースに体系的にまとめています。 本記事では、コールセンターにおける人員配置を一例に、需要予測を活用した業務プロセスの効率化に関する検討を、数理最適化に主眼を置いて紹介します。 背景 数理最適化とは 数理最適化とは、「制約条件を満たす候補の中から最善なものを数学的に導き出す技術」です。 身近な例として、500mlのスムージーを作ることを考えてみます。 例えば、数理最適化の技術を活用すると、「果物や野菜などの食材を合わせて500ml以下に収める」、「スムージーに含まれるビタミンやミネラルといった栄養価を一定以上に保つ」という制約条件の下、費用を最小化する食材の組み合わせを見つけることができます。 数理最適化の問題の一般形は、次のように表現されます。 つまり、不等式 や等式 の制約を守りながら、目的関数 を最小化あるいは最大化するような最適な変数 を見つける数理モデルを表します。 例えば、現行の高校数学Ⅱで学ぶ連立1次不等式が表す領域において最大値・最小値を求める問題は、まさに数理最適化の問題の一種です。具体的には、「点 が連立不等式 の領域を動くとき、 の最小値を求めよ 」といった問題が該当します。実際にその問題を定式化すると、以下のようになります。 この式が数理最適化の問題の一例であることは容易に確認できます。 機械学習×数理最適化で解決が期待できる課題 機械学習と数理最適化を掛け合わせることで解決が期待できる実課題を3つ紹介します。それぞれの課題について、ユーザーが実現したいことと各技術を組み合わせて活用したその解決策を説明します。 ユースケース1 (シフト最適化) 👧:「サービスの需要予測とそれにもとづくシフト割当を自動化して、業務効率を向上させたいです…」 💡: 機械学習 → 過去の需要データや時系列データ(曜日、天気、特定のイベントなど)から将来の需要を予測 数理最適化 → 予測された需要に基づいて、最適なシフト割当を自動で決定 ユースケース2 (在庫管理) 👨:「在庫の過不足を防ぎコスト削減ができたらなぁ」 💡: 機械学習 → 販売履歴や市場動向、季節要因などから未来の需要を予測 数理最適化 → 需要予測に基づき、リードタイムや在庫コストを考慮し、適切な発注量や在庫水準を決定 ユースケース3 (ダイナミックプライシング) 👥:「需要予測に応じて最適な価格を設定し売上を最大化したい!」 💡: 機械学習 → 消費者の購買行動などを分析し、該当サービス/商品における将来の需要を予測 数理最適化 → 需給バランスを保ちながら、売上最大化を実現化する最適な価格を決定 このように、機械学習と数理最適化の技術を組み合わせることで、片方の技術だけでは解決困難だった課題に対して、効果的な解決策を提供できることが期待できます。 次章では、ユースケース1(シフト最適化)を一例に、具体的な実現方法を詳しく検討していきます。 実現方法の検討 問題設定 取り扱う問題は以下の通りです。なお、問題はシンプルに設定しています。 勤務内容:コールセンター 勤務時間:9:00〜18:00 (土日・休日も営業) サービス:A, B, Cの3種類 例えば、リモートワーク支援ツールやサブスクリプションサービスなどの対応 オペレーター:10人 (呼称:a ~ j) ただし各々で対応可能なサービスは異なる ユーザーの状況 現状 これまでシフト作成は経験則に基づいて手作業で行われていた 具体的には3種類の業務に対して1週間分の架電数を予測し、必要な人員を配置している しかし、シフト計画の策定には複雑さが伴い、膨大な時間を要している シフト計画の主な目的は人的リソースの効率的活用で、1週間分のオペレータ業務における総労働時間の最小化を目指している あるオペレータがその日の勤務時間が0分となる場合、そのオペレータの業務参加は任意となり、庶務作業などの他の業務にリソースを割り当てることが考慮される サービスごとの過去の需要データは記録している 各オペレータの1週間分の勤務可能時間や、サービスごとにかかる平均対応時間も把握している 要望 シフト計画の作成を定量的に自動化したいと考えている シフト表は、月曜日始まりの1週間単位で出力してほしいという希望がある 上記の設定から、次のような計画を立案しました。 - 自動化の全体像: ・ 3種類のサービスに関する架電数予測 → 機械学習 ・ 月曜日を始まりとした1週間単位のシフト計画 → 数理最適化 - 機械学習: ・ 過去の需要データを用いて、サービスA, B, Cそれぞれに対して機械学習モデルを構築 - 数理最適化: ・ 最小化 -> 全オペレータの1週間分の総労働時間 ・ 制約条件: % 各サービスの需要を満たすこと % 各オペレータの最大勤務時間を超えない % 各オペレータに対応不可能なタスクを割り当てない 全体像としては、次のようなイメージです。 この計画を踏まえて、適用する機械学習と数理最適化の技術を実際に検討してみます。 Node-AIでの需要予測 前述した計画をもとに、最初にサービス3種類の架電数を予測します。今回はNTT Comで提供しているノーコードAI開発ツールNode-AIを使用して、架電数予測を実施します。本検討では、現実のデータではなく人工データを用いています。 詳細な説明は省略しますが、次のようにNode-AIでキャンバスを作成しました。 (データ分析のやり方の詳細は こちら に掲載しています。ここではサービスAにおける架電数予測モデルのキャンバスのみ掲載します。) こうして、サービスA, B, Cの需要予測モデルを構築しました。このモデルにより、今後1週間分の需要予測が可能となります。 実際に、サービスAをNode-AIで架電数を予測した結果は次のようになりました。 こうして、以下のような結果が得られました。 (※1 スペースの都合上一部のみを表示, ※2 後段処理のために予測結果を整数値に補正) 2021-12-13 (Mon) 2021-12-14 (Tue) … 2021-12-19 (Sun) A 81 81 … 81 B 15 15 … 17 C 5 6 … 5 数理最適化によるシフト計画 次に、Node-AIで予測した結果と数理最適化の技術を活用して、1週間分のシフト計画を作成していきます。 条件データ ユーザー状況から、各オペレータの1週間分の勤務可能時間とサービスごとにかかる平均対応時間が把握されています。 これらに対応するデータを以下に掲載します。 ○ 各オペレータの1週間分の勤務可能時間 (スペースの都合上一部のみ)     ・ 行には各オペレーターの名前が、列には日付が示されています。     ・ 例えば、オペレーター jは2021-12-14 (Tue)に480分勤務可能であることを表しています。 2021-12-13 (Mon) 2021-12-14 (Tue) … 2021-12-19 (Sun) a 240 240 … 240 b 300 0 … 0 … … … … … j 480 480 … 480 ○ 各オペレータのサービスごとにかかる平均対応時間 (スペースの都合上一部のみ)     ・ 行は各オペレーターの名前を、列はサービス種類を表しています。     ・ 例えば、オペレーター aはサービスBの対応に平均30分かかることを意味します。 A B C a 15 30 20 b 20 35 30 … … … … j 35 35 0 定式化 ここから、数理最適化の問題として定式化していきます。まず定式化に使う変数を定義します。 非決定変数 (モデル内で固定されているパラメータ) :スケジュールを実施する日数 (今回: ) :タスクの種類数 (今回: ) :オペレータの人数 (今回: ) :オペレータ がタスク を対応するのにかかる平均労働時間 [分] の場合、そのタスクに対応できないことを意味します :オペレータ が日付 に勤務可能な最大の時間 [分] の場合、その日は勤務できないことを意味します :タスク が日付 に処理する必要があると 予測された 数 決定変数 (最適化する変数) :オペレータ が日付 にタスク を処理する数 続いて、これらの変数を用いて定式化していきます。以降、数式が多くなります。 ○ 目的関数:全オペレータの対象日数の総労働時間を最小化 ○ 制約条件:     ・予測タスク数の対応に関する制約     ・ 最大勤務時間を超えないようにする制約     ・ 各オペレータに対応不可能なタスクを割り当てないようにする制約     ・ タスクの処理数はすべて0以上であるという制約 (非負制約) 以上が、目的関数と制約条件の説明でした。改めて整理すると、以下のように定式化しました。 プログラム 本検討では、最適化モデルを構築するためにPySCIPOptを、そしてそのモデルをベースに最適解を求めるためにSCIPソルバーを使用します。ここで、PySCIPOptは問題をコンピュータが処理可能な形式に変換するモデリングツールであり、SCIPソルバーはそのモデルを用いて最適解を探索するツールです。PySCIPOptとSCIPソルバーのドキュメントはそれぞれ、 こちら と こちら をご参照ください。 以下に、PySCIPOptとSCIPソルバー、numpyがインストールされていることを前提に、これらのツールを用いたコードを示します。 import numpy as np from itertools import product from pyscipopt import Model """ version Python: 3.8.10 PySCIPOpt: 5.2.1 SCIPソルバー: 9.2.0 numpy: 1.22.4 """ def solve_scheduling_problem (T, K, N, C, A, O): """ 引数(T, K, N, C, A, O):非決定変数に相当 """ # Modelクラスのインスタンス作成 model = Model(problemName= 'Scheduling' ) # 決定変数の定義 x = {} for i, j, t in product( range (N), range (K), range (T)): x[i, j, t] = model.addVar(vtype= "I" , lb= 0 , name=f "x_{i}_{j}_{t}" ) # 目的関数(全オペレータの対象日数の総労働時間を最小化)を定義 model.setObjective( sum (C[i][j] * x[i, j, t] for i, j, t in product( range (N), range (K), range (T))), sense= "minimize" ) # 予測タスク数の対応に関する制約 for j, t in product( range (K), range (T)): model.addCons( sum (x[i, j, t] for i in range (N)) == O[j][t] ) # 最大勤務時間を超えないようにする制約 for i, t in product( range (N), range (T)): model.addCons( sum (C[i][j] * x[i, j, t] for j in range (K)) <= A[i][t] ) # 各オペレータに対応不可能なタスクを割り当てないようにする制約 for i, j in product( range (N), range (K)): if C[i][j] == 0 : model.addCons( sum (x[i, j, t] for t in range (T)) == 0 ) # 最適解導出 model.setParam( 'display/verblevel' , 0 ) # ログ出力を抑える model.optimize() # 最適化の結果出力 status = model.getStatus() if status == "optimal" : # 最適解が見つかったならば solution = np.zeros((N, K, T)) # N x K x Tのゼロ行列を作成 for i, j, t in product( range (N), range (K), range (T)): solution[i, j, t] = int (model.getVal(x[i, j, t])) return solution else : print ( "エラー:{}" .format(status)) return None # エラーの場合は None を返す 個人的な見解ですが、数理最適化の問題に適切に定式化できれば、プログラム自体は直感的に実装できると思います。 結果 以下に最適化のコードを実行した結果を示します。今回の実行により最適化結果が表示されたので、最適解が得られたことを確認できます。なお、最適化結果は瞬時に返されました。 タスク割当表 (スペースの都合上一部のみ記載) 行は各オペレーターの名前を、列は(日付、サービス種類)の組み合わせを表しています。 例えば、オペレーター dは2021-12-14(Tue)にサービスBの対応を15件予定しています。 シフト表 タスク割当表および各オペレーターの1週間分の勤務可能時間に基づいて作成されています。 例えば、オペレータ d は 2021-12-14 (Tue)のコールセンターの勤務が予定されており、オペレータ j は 2021-12-16 (Thu)については任意となっています。 Node-AIで予測した各サービスの架電数とタスク割当表を照らし合わせると、適切にタスクが割り当てられていることが確認できます。例えば、2021-12-13(Mon)でのサービスBの架電数が15件と予測されていましたが、実際にオペレータeに15件が割り当てられていることが確認できます。 このようにして、機械学習と数理最適化の技術を活用することで、タスク割当表とシフト表の作成を定量的に自動化することが可能となりました。 まとめ 本記事では、数理最適化を中心に、需要予測を活用した業務プロセスの最適化に関する検討をご紹介しました。特に、コールセンターにおけるシフト最適化を例に、課題設定から実装までの一連の流れを示しました。 今後の展望としては、実社会への適用、新たな手法の研究、さらには定式化支援の研究などを進めていく予定です。 おわりに 本記事が少しでも皆さまのお役に立てたのであれば、嬉しく思います。さらにNode-AIに関する知見を深めたい方は、 こちら の情報もご参考にしてください。本記事の内容についてお問い合わせがございましたら、 こちら のフォームからお気軽にお問い合わせください。
ノーコードAI開発ツール「Node-AI」のプロダクト開発チーム(以下Node-AIチーム)は、 2024年1月から1年間採用していたLeSSでの開発体制を見直し、1チームによるスクラム体制で再出発しました。 本記事では、その背景についてご紹介します。 はじめに LeSSの課題 原因1 原因2 課題のまとめ チーム体制変更の検討 調査と決定 狙い 調整 今後に向けて おわりに はじめに こんにちは。 Node-AI チームのスクラムマスター 中野 と申します。 もともとはNode-AIの開発者でしたが、前任者の異動を機に、スクラムマスターに挑戦しています。 得意な分野はデータ分析や機械学習で、プロダクトに関連するログデータの分析にも取り組んでいます。 さて、Node-AIチームは2024年の1年、 LeSS (Large-Scale Scrum)を採用しスクラムを運用していました。 確かに最初はうまくいく事が多かったですし、判断は間違いではなかったと考えています。 当時の状況は以下の記事で紹介していますので、ご興味があればご覧ください。 engineers.ntt.com しかし、Node-AIはこの1年でさまざまな状況が変化し、 地道で小さなプロセス改善だけでは対応しきれない問題が増えていきました。 そこで年末にチーム全体で話し合い、1チームによるスクラム体制で再出発することを決定しました。 LeSSの課題 この記事は、LeSSそのものを批判する内容ではありません。 現在のNode-AIチームの状況を踏まえると「LeSSが合わなくなってきた」という理解をしています。 細かい原因はいくつかありますが、要約すると以下に帰着すると考えています。 「Node-AIチームの取り組みが多様化・高度化する中で、 小規模なフィーチャーチームごとにプロダクトバックログを消化する方法で、 ユーザー価値を最大化することが難しくなってきた」 原因1 2024年、おかげさまでNode-AIは多くのお客さまや社内営業から引き合いをいただきました。 そのため、Node-AIチームのメンバーはソフトウェア開発以外の業務にも積極的に取り組み、 要望に応えるべく全力を尽くしていました。 例えば、社内営業にNode-AIを理解してもらう勉強会や社内コミュニティの活動、 営業への同行、SNS等を活用したマーケティング活動をコンサルティングチームと協力しながら開発チームのエンジニアも含めて対応しています。 そのようなタスクは差し込みで発生することが多く、優先度を付けるのも難しいため、 プロダクトバックログとは別の場所で管理し、可視化していました。 しかしタスクによっては個人で動いているものや、 複数メンバーでローテーションを組んで対応しているもの、 実は可視化や共有が十分でなかったものなどさまざまでした。 その結果、誰が何をやっているのか把握するのが次第に難しくなっていきました。 頻繁な差し込みは頭の切り替えにも負荷がかかるため、 開発者のモチベーションや生産性にも影響を与えているという意見も出ました。 ここまではLeSSに関係なく起こり得ることですが、LeSSを採用していた影響で、 小規模な(6人以下の)フィーチャーチームにおいてメンバーが不在となるケースが増えました。 それにより「あるスプリントではフィーチャーチーム内に2人しか開発者がいない」という状況も発生し、 一時的に機能横断ではなくなるためチームが取れるプロダクトバックログに偏りが生じ、 チームごとのベロシティも安定しませんでした。 原因2 また、Node-AIは大規模で専門性の高い機能追加、ユーザー理解のための調査、 研究開発チームと協力して行う高度な機械学習アルゴリズムの実装、 安定的なサービス提供を目的としたOps強化など、 チーム全体での最適化や腰を据えたスキルトランスファー(スキトラ) が必要な取り組みを並列で進めていく必要性が増していました。 そのため、各フィーチャーチームで物事を完結するのは難しく、 スプリント内で無理が生じることも度々発生していました。 対応策として、一時的に1チームにしたり有識者を別チームに派遣するなどしていましたが、 都度調整するコミュニケーションコストも増加し、チームとして不安定な状態と言わざるを得ませんでした。 課題のまとめ LeSS体制における課題をまとめると、以下の2点となります。 プロダクトバックログ外のタスクの差し込みにより、各フィーチャーチームのベロシティが安定しない チーム横断での最適化やスキトラの必要な取り組みが増え、フィーチャーチーム個別での最適化がチーム全体の最適化とマッチしない チーム体制変更の検討 調査と決定 上記の課題を解決するには、地道なプロセス改善だけでは難しいという意見が開発者から上がり、 チーム体制の変更で解決を目指す実験をすることをまず決定しました。 ただしチーム体制といっても多数の選択肢があるため、 まずは他社事例やフレームワークを調査し、どのような方法があるのか洗い出しました。 そして、Node-AIのメンバーで現実的に組めるチーム構成案を複数作成しました。 具体的には以下の方法です。 プロダクトを2つのプロダクト群に分割し、2つのスクラムで別々の目標を設定する体制 チームトポロジー の考え方にもとづく分割方法 1チームでスクラムを回す体制 各案を採用した場合の各チームの責務やメリット・デメリットを整理し、 メンバーの希望も加味して最終的にDevOpsの1チームスクラム体制を採用することに決定しました。 この作業はスクラムマスターが一部整理・誘導した部分もありますが、 ほとんどの検討プロセスはチーム全員で主体的に決定しました。 狙い 1チームスクラム体制を選んだ決め手の1つは、 「うまくいかなかった場合、元に戻しやすく、他の案へ柔軟に変化もさせやすい」という点です。 あまり凝ったことをしてもチーム体制変更の影響を評価しにくく、ルール決めにも体力が必要となります。 一方、1チームスクラムは過去に経験があるため、各自が勘所を把握しており、 1チームで回すこと自体に伴う新たな知識の習得やルール決めを最小限に抑えることができます。 小規模なチームですし、ダメだったら他の案にすればいいだけ、と軽く考えられます。 また、2チームに分散していた個々人のスキルを1チームにまとめることで、 課題(2)に対してタスクに集中して取り組める環境を作り、 チーム全体の最適化が進むことを狙っています。 他の構成案をベースとしてチームごとに役割を分けることでも課題の解消は可能だと思いますが、 社内の他プロダクトのネガティブな例や開発者個人のキャリア面も考慮して今回は見送りとしました。 課題(1)に関連する差し込みタスクについてはプロダクトオーナー(PO)・スクラムマスターでの巻き取りや、 後述するメンバー調整により緩和できると考えていますが、 スクラムチームのビジネスサイドとの関わりを完全に停止するのはリスクもあります。 一定量はスクラムチームに残しつつ、チーム全体での開発者稼働が極端に減る状況が常態化しないように調整していきます。 調整 Node-AIチームは現在約10人のメンバーが所属しています。 1チームだと人数が多すぎること(これがLeSSを採用した背景の1つ)や、 POへの負荷が異常に高いといった別の問題も存在していたため、 今回の課題も考慮して以下の変更も同時に実施しました。 POとプロダクトマネージャー(PdM)を2名で分業し、PdMはスクラム外からプロダクトを支援する。 旧POがPdMとなり、新POは開発者から選任する。 プロダクトバックログ以外のタスクが特に集中していた開発者2名は(同意のもと)スクラムから外れてもらい、 チームX(名称検討中)として、各自の裁量で遊撃的に動けるようにする。 これによりバックログ外のタスクをスクラムの外で独立して対応できると考えています。 変更前後の構成を図に表すと以下のようになります。 ※ "az" "mm" はチーム名の略称です。 ※ POの変更は以下の記事で一部紹介しています。 engineers.ntt.com 今後に向けて PdMやチームXの今後の動きについては、進行しながら調整していくことになりますが、 スクラムの観点から見るとステークホルダーという位置付けになります。 引き続き、各種スクラムイベントでは密に連携していく予定です。 今後の課題としてはチーム体制変更を含むプロセス改善の効果測定を定量的に実施することです。 これまでベロシティを基準としたり、Four keys等の調査までは行ったものの、 形骸化したり導入が後手となり、結局は肌感を優先した検査・適応となっているのが実情です。 さまざまな指標をモニタリングできる環境も整いつつあるので、近いうちに検証を始めてみたいと考えています。 おわりに 1チームスクラムに移行したことで、チームが抱えていた課題が緩和されることを期待しています。 しかし、チーム体制を変更すればすべての問題が解決されるわけではありません。 今後は地道にプロセスを改善し、どうすれば最もユーザー価値を高められるのか試行錯誤していきます。 また、そもそも1チームからLeSSに移行した際には逆の課題があったわけですから、当然それが再燃することも考えられます。 その時に、再度チーム体制の変更で問題を乗り越えるのか、別の方法を取るのかは現時点では分かりません。 ただ、今回チーム全員が主体的に課題に対する手段を選択し、 変化を実現できたことはチームにとって非常に価値のある経験となりました。 引き続き、どのような変化にも対応できる強いチームを目指していきたいと思います!