読者です 読者をやめる 読者になる 読者になる

機械学習の種類とR言語でのパッケージ

機械学習の種類とR言語でのパッケージをざっくりと整理してみました。
自分の理解に役に立つ形態で整理したため、書籍に出るようなまともなものとは違うところがあります。
また、間違った部分もありえますので、気付き次第、修正していくつもりです。

予測

分類(Classification)

データを決まったカテゴリにグループ分けすることです。

kNNアルゴリズム

classパッケージのknn関数

ナイーブベイズアルゴリズム

e1071ペッケージのnaiveBayes()関数

決定木

C5.0アルゴリズムの実装では、C50パッケージのC5.0()関数

分類規則

One Ruleアルゴリズムの実装では、RWekaパッケージのOneR()関数。
RIPPERアルゴリズムの実装では、RWekaパッケージのJRip()関数。

回帰(Regression)

数値の予測を行うことです。

線形回帰

statsパッケージのlm(関数)

回帰木

CARTアルゴリズムの実装では、rpartパッケージのrpart()関数。
M5'(prime)アルゴリズムの実装では、RWekaパッケージのM5P関数。

複数用途

神経網

人間の脳を構成するニューロンの仕組みをコンピューターで真似て予測を行います。
Neuralnetパッケージのneuralnet()関数。

SVM(Support Vector Machine)

kernlabパッケージのksvm()関数。

パターン検索

複数のものとの間にどんな関係があるかを識別します。
小売業界ではバスケット分析として使われます(この商品を買った人が一緒に買った商品)。
アプリオリ(aprioi)アルゴリズムの実装では、arulesパッケージのapriori関数。

クラスタリング

分類(Classification)と似ていますが、分類はデータを決まったカテゴリにグループ分けするのに対し、
クラスタリングは決まったカテゴリはなく、ただ単に類似したもの同士でグループ分けします。
クラスタリングで得たグループから特徴を把握し、ビジネスに使えるパターンを捕らえます。

K-Meansアルゴリズムの実装では、statsパッケージのkmeans関数。

Apache POIのExcelファイルで日本語セルの列幅を自動調整

Apache POIで列幅を自動で調整してくれる
Sheet#autoSizeColumnメソッドがあります。

数字や英語の場合、ちゃんと列幅を調整してくれるようですが
日本語の場合、Sheet#autoSizeColumnメソッドを呼び出しても、
幅がうまく調整されません。
幅が微妙に足りなかったりします。

こういう場合、セルにフォントを明示的に指定すると、
列幅が問題なく調整されます。

// ...中略...
// 日本語での列幅の自動調整のため、フォントを明示的に指定する。
CellStyle cellStyle = workbook.createCellStyle();
Font font = workbook.createFont();
font.setFontName("Serif");
cellStyle.setFont(font);

// ... 中略 ...
// 列幅を自動調整する。ここでは最初(0番目)の列の幅を自動調整する。
sheet.autoSizeColumn(0, true);

JDKのJavascript(nashron)で外部のJavaクラスを呼び出す方法

自分が作ったクラスや外部のライブラリを呼び出して、
機能を作りたい時があります。
普通にJavaで作ってもいいですが、
コンパイルが必要ですし、
早く作りたいのにJavaの厳格な文法がちょっと面倒です。

ScalaやGroovyなどを使って
軽くスクリプトを作るのもいいですが、
JDKに標準で入っていないから、やはり面倒。
それでJDK(バージョン8)に標準で入っているJavascriptエンジンnashronを
使ってみました。

しかし、nashronはなかなか外部クラスを認識してくれませんでした。
jrunscriptコマンド(またはjjsコマンド)の実行時に
classpathや-cpにクラスパスをjavaコマンドと同じ方法で指定したのにも関わらず、
いつもClassNotFoundExceptionが発生して困っていました。

色々と試した結果、やっと外部のJavaクラスを認識してくれたので、その方法をメモします。

まず、jrunscriptの実行時に以下のようにクラスパスを指定します。
Linux(Ubuntu)を基準に説明します。

例えば、/dukelab/libに10個のjarファイルがあって、それらをjrunscriptにクラスパスとして通したいなら、以下のように起動します。

jrunscript -cp `echo /dukelab/lib/* | tr ' ' ':'` 

ちょっと複雑で、これ以外の方法もあると思いますが、
今のところ、これで行けました。

単純にjrunscript -cp /dukelab/lib/*とすると、うまく行きません。
Linuxのシェルで"/dukelab/lib/*"が式として評価されて、
jrunscript -cp /dukelab/lib/a.jar /dukelab/lib/b.jar... のように
展開されて実行されます。
コマンドプロンプトでは空白文字で引数を分離していますから、
/dukelab/lib/b.jarがスクリプトファイルとして認識され、jrunscriptが実行されます。
当然/dukelab/lib/b.jarはスクリプトと関係ないバイナリのファイルですから、エラーになってしまいます。
それで上記の`echo /dukelab/lib/* | tr ' ' ':'`のように/dukelab/lib/*の展開後に、
trコマンドで空白文字をクラスパスの区切り文字である:に変換する処理を追加しました。
こうすると、実際には

jrunscript -cp /dukelab/lib/a.jar:/dukelab/lib/b.jar...

のようになるので、クラスパスとしてうまく認識できるようになります。

もし、jarファイルでなく、classファイルが置かれているディレクトリなら、もっと簡単です。
例えば、/dukelab/classes配下にJavaクラスがあるなら、
以下のように指定します。

jrunscript -cp /dukelab/classes

クラスパスを複数指定する時は:(コロン。Windowsでは;(セミコロン))で区切って下さい。

これで準備が終わり、Javascriptの話になりますが、
dukelab.js.test1パッケージにあるTestOneクラスと
dukelab.js.test2パッケージにあるTestTwoクラスをJavascriptで呼び出してみます。
2つのクラスはSystem.out.printlnを呼び出すごく単純なクラスなので、内容は割愛します。

var pkgs = new JavaImporter( 
    Packages.dukelab.js.test1, 
    Packages.dukelab.js.test2
);
with(pkgs) {
    print("TestOne : " + TestOne.getString());
    print("TestTwo : " + TestTwo.getInt());
}

実行すると、以下のように表示されます。

TestOne : wow!
TestTwo : great!

つまりいうと、Packagesオブジェクトの次に使うJavaクラスの完全修飾名を指定して呼び出すということです。
ただ、いつもPackages.dukelab.js.test1.TestOne.getString()...ように呼び出すのは辛いから
上記のようにwith文を使ってパッケージを省略して使うのがいいでしょう。
with文はJavascriptではスコープが曖昧になってしまうため、使わない方がいいと言われていますが、
JavascriptJavaクラスを使う時は便宜上、必須ではないかと思います。
ということで少なくともJavaの中のJavascriptでは、with文は当分健在だろうと勝手に思っています。
そのうち、新しい文法ができるかもしれませんが...

最後にJavascriptにおけるJavaクラスの呼出方法をまとめておきます。

// 完全修飾名で呼出し
print(Packages.dukelab.js.test1.TestOne.getString());

// with文でパッケージ(一つ)を省略
with (Packages.dukelab.js.test1) { 
    print(TestOne.getString());
}

// with文でパッケージ(一つ以上)を省略
with (new JavaImporter(Packages.dukelab.js.test1, Packages.dukelab.js.test2)) { 
    print(TestOne.getString());
    print(TestTwo.getInt());
}

Eclipseでマージしたくないファイルへの対処

Eclipse + Git環境で、マージに対するメモです。

マスタブランチからブランチX(Xはブランチの名前)へマージを行った時、
マージしたくないファイルだけと、
マスタブランチとブランチXとの間で、
違いが出てコンフリクトする場合があります。
例えば、環境ファイルの内容に相違な部分がある場合などです。

その時、コンフリクトしたファイルに対して、Team Synchronizing Perspectiveで、
Mark as Mergedし、コンフリクトを解消し、
中身もブランチXのHEAD、つまり元の内容にしても、
依然と米印のアイコンがつきます。

この状態でコミットすると、
コンフリクトしたファイルの中身は変わっていないのに
新しい履歴ができてしまいます。
それでも構わないなら、いいですが、
後で以前のソースを探す時、不便になるのは間違いないです。

これを解消するためには、マージしたファイル全てをコミットする前に、
マージしたくないコンフリクトしたファイルの中身を元に戻した(Replace WithでHEADに戻す)後、
Team > Advanced > Untrackします。

それからコミットが終わると、
該当ファイルに対してTeam > Add to Indexします。

こうすると、マージしたくないファイルへは何の影響を与えず、
マージを終えることができます。

コンフリクトしたファイルの数が多いと、手間かかりますが、
そういう場合は、コンフリクトが多いこと自体を解消した方がいいですね。

ErlangとJavaをつなぐ

この記事では、ErlangJavaの連携方法を説明します。

使い道

Erlangで出来ているプログラムを使いたいが、今Erlangが分かる人がいない。
でも、Javaが分かる人はいる。こういった状況でErlang + Javaが使えるのではないでしょうか。

また、Erlangがネットワークや並行処理に強い反面、
他の言語に比べてパフォーマンスがよくないところ(演算など)があるようで、
そういった部分はJavaに担当させるのもいいでしょう。

GUIRDB操作はJavaに任せて、分散処理はErlangに担当させるなどもありだと思います。

実際、Erlang + Javaではありませんが、
ある商用のメール配信エンジン(?)は、
メインをErlangにして、計算速度が必要な部分は
C言語で開発したとの記事を見たことがあります。

Jinterface

JavaからErlang プログラムを呼び出すための、Erlang 公式Java APIパッケージです。
APIは実際通信を行う部分とErlangデータタイプをJavaクラスとして実装した部分で構成されています。
このAPIパッケージを使えば、Erlangプログラムと連携できます。

jinterfaceのjarファイルは、${ERLANG_HOME}/lib/jinterface(又はjinterface-x.y.z)/privディレクトリ配下にOtpErlang.jarという名前で配置されています。

サンプル

受け取ったメッセージをそのまま返すエコサーバのようなErlangプログラムと、
そのエコサーバを呼び出すJavaプログラムを作成してみます。

まず、Erlangプログラムです。
Erlang 基礎ポイント7 - 分散処理 - DukeLabに出たサンプルソースと同じです。

-module(echo).
-export([start/0, send/2]).

start() -> register(echo, spawn(fun() -> loop() end)).

send(Msg, Receiver) ->
    Receiver ! {self(), {server, Msg}},
    receive
        {client, Res} ->
            Res
    end.

loop() ->
    receive
        {From, {server, Msg}} ->
            From ! {client, string:concat("Echo response : ", Msg)},
            loop()
    end.   

このErlangプログラムをコンパイルして、実行しておきます。
今回は、VirtualBox上に動くUbuntu 12.04で実行しました。
REPL環境上でテストもしてみます。
Javaからアクセスする時、識別に必要なため、erl起動時にnameとsetcookieの指定が必要です。
nameとsetcookieはメモしておいて下さい。

$ erl -name echoserver@vpc1.com -setcookie echocookie
(echoserver@vpc1.com)1> c(echo).
{ok,echo}
(echoserver@vpc1.com)2> echo:start().
true
(echoserver@vpc1.com)3> echo:send("Hello!!", echo). % 自分自身への呼出でテスト。
"Echo response : Hello!!"

次は、Javaプログラムです。
ビルドパスにjinterfaceのjarファイル(OtpErlang.jar)を追加して下さい。
今回は、Windows 7環境とEclipseで作成しました。
こちら側のPCにもErlangがインストールされていなければなりません。
jinterfaceのjarだけでは動かないので、注意して下さい。

package dukelab.erlangjavatest;

import java.io.IOException;

import com.ericsson.otp.erlang.OtpErlangAtom;
import com.ericsson.otp.erlang.OtpErlangDecodeException;
import com.ericsson.otp.erlang.OtpErlangExit;
import com.ericsson.otp.erlang.OtpErlangObject;
import com.ericsson.otp.erlang.OtpErlangString;
import com.ericsson.otp.erlang.OtpErlangTuple;
import com.ericsson.otp.erlang.OtpMbox;
import com.ericsson.otp.erlang.OtpNode;

public class ErlangJavaTest {
    public static void main(String[] args) throws IOException, OtpErlangDecodeException, OtpErlangExit {
        // javanode : Javaプログラム側のホスト名です。任意で指定します。
        // echocookie : Erlangプログラム側でerl実行時、指定したクッキー名と同じです。
        OtpNode node = new OtpNode("javanode", "echocookie");
        OtpMbox mBox = node.createMbox();

        // メッセージをErlangプログラム側に送ります。
        // これをErlangプログラムで表現すると、
        // { echo, 'echoserver@vpc1.com' } ! { self(), {server, "Hello Erlang, I am Java."} }になります。
        {
            OtpErlangTuple tuple = createTuple(
                mBox.self(),
                createTuple(
                    new OtpErlangAtom("server"),
                    new OtpErlangString("Hello Erlang, I am Java.")
                )
            );
            // echo : Erlangプログラム側で登録したプロセス名です(register(echo, spawn(fun() -> loop() end)))。
            // echoserver@vpc1.com : Erlangプログラム側でerl実行時、指定したホスト名です。
            mBox.send("echo", "echoserver@vpc1.com", tuple);
        }

        // 応答を受け取ります。
        {
            OtpErlangObject result = mBox.receive();
            OtpErlangTuple tuple = (OtpErlangTuple) result;
            OtpErlangAtom atom = (OtpErlangAtom) tuple.elementAt(0);
            OtpErlangString str = (OtpErlangString) tuple.elementAt(1);

            System.out.println("Response from Erlang : " + str);
        }
    }

    private static OtpErlangTuple createTuple(OtpErlangObject... msg) {
        return new OtpErlangTuple(msg);
    }
}

その後、JavaプログラムのPCにあるhostsファイルに、
先ほどのErlang実行時にnameに指定したドメイン名とIPアドレス(Erlangを実行したPC)の対応を追加します。
例えば、以下のようにです。

192.168.56.101  vpc1.com

さあ、Javaプログラムを実行してみましょう。

Response from Erlang : "Echo response : Hello Erlang, I am Java."

できました!

整理

jinterfaceを使うと、Erlangプログラムと同じく動作するプログラムを
Javaで作成することができます。
サーバ役割のプログラムもクライアント役割のプログラムもできます。
いくつかのErlang機能に対応するJavaクラスを以下に並べます。

  • Erlang起動(Erlang仮想マシン) : OtpNode
  • メッセージをやりとりする(Erlangでの!やreceiveなど) : OtpMbox
  • タプル : OtpErlangTuple
  • 文字列 : OtpErlangString
  • アトム : OtpErlangAtom

この記事が理解できたら、後は、参考資料を読みながら、掘り下げていけばいいと思います。

Erlang 基礎ポイント7 - 分散処理

久しぶりのErlangですね。
今日は分散処理について勉強したいと思います。

Erlangで分散処理?

今までは一つのサーバで一つのErlangシェルを立ち上げて勉強しましたが、
実はErlang複数のサーバがネットワークを作って、
大規模な動作をするのに適した仕組みを持っています。
簡単に複数のサーバをつなげて何かができるってことは楽しいですね。

Erlangノード

今マシンで動いている一つのErlangプログラムをErlangノードといいます。
正確にいうと、Erlangプログラムを動かしているErlang仮想マシンをいいます。
JavaJVMのようなものが立ち上がっていると考えればいいでしょう。

JVMは同じマシンで複数実行できますね。
同様にErlangも同じマシンで複数実行できます。
同じマシンでErlangプログラム2個実行した?
つまり、erlコマンドを2個実行したといったら、
"このマシンにはErlangノードが2つ立ち上がっている"といえます。

但し、erlコマンド実行し、Erlangプロセスが終了せず、ずっと動いていなければ、
立ち上がっているとはいえません。あくまで実行中のことが前提です。

Erlangノードについて説明したので、分散処理ノードの形態について
説明します。

分散処理ノードの形態

Erlangノードがどのネットワークのどのマシンにあるかによって、
分散処理ノードの形態は以下のように三つあります。

Erlangノードが同じマシン上にある

一つのマシン上に複数Erlangを実行して分散処理を行います。
一つマシンのリソースを分けて処理をするので、あまりパワーは出せないでしょう。
テスト用に分散処理を確認したい時にいいと思います。

Erlangノードがプライベートネットーワーク(LAN)上にある

社内ネットワークなので、セキュリティが安全ですし、複数のマシンを有効に活用できるので、
これが一番実用性があるのではないかと思います。

Erlangノードがインターネット上にある

インターネット上にある無数のマシンを束ねられることは魅力的ですが、
セキュリティの確保が難しいと思います。
でも、セキュリティにあまり気をつけなくてもいいすごく時間がかかる計算を
小さい処理単位に分けて、インターネット上のマシンで計算して結果を結合するといった用途に
合うかもしれない...と思います。

サンプル

準備

ここでは、プライベートネットーワーク(LAN)上にErlangノードをおくことを前提で、サンプルを作ってみます。
私は実PCを一台しか持っていないので、無料の仮想化ソフト「VirtualBox」を利用し、
Ubuntu 12.05が入った仮想PCを二台作ってから、サンプルを作成及び実行します。

Ubuntu 12.05はHomepage | Ubuntu Japanese Teamでダウンロードできます。
VirtualBox用のイメージが既にあるので、自分でインストールしなくていいです。
仮想PCを二つ作るにはVirtualBoxのクローン機能を使って、入手したイメージをコピーすればすぐです。

以下のように仮想PCを二つ作りました。
f:id:jeongman7:20140831161913j:plain

しかし、ソースをそれぞれの仮想PCで編集すると不便なので、
SSHで各仮想PCに接続してソースの編集を行います。
SSHクライアントはRLoginを使いました。
f:id:jeongman7:20140831200919j:plain

理解しやすくするため、サンプルは、echoサーバのようなものにします。
具体的には送信側がメッセージを受信側に送ると、
受信側は受信したメッセージに特定文字列を付加して受信側に返すものです。

以下、ソースです。

-module(echo).
-export([start/0, send/1]).

start() -> register(echo, spawn(fun() -> loop() end)).

send(Msg) ->
    echo ! {self(), {server, Msg}},
    receive
        {client, Res} ->
            Res
    end.

loop() ->
    receive
        {From, {server, Msg}} ->
            From ! {client, string:concat("Echo response : ", Msg)},
            loop()
    end.

分散処理を行う前にローカルでstart()関数とsend()関数を呼び出して動作確認をしてみましょう。

6> duke1@duke1-VirtualBox:~/erlang/blog$ erl
Erlang R14B04 (erts-5.8.5) [source] [rq:1] [async-threads:0] [kernel-poll:false]

Eshell V5.8.5  (abort with ^G)
1> c(echo).
{ok,echo}
2> echo:start().
true
3> echo:send("Hello Erlang").
"Echo response : Hello Erlang"
4> echo:send("Hello Erlang").
"Echo response : Hello Erlang"
5> echo:send("Hello Erlang1").
"Echo response : Hello Erlang1"
6> 

問題ないですね。
次は別々の仮想PCでErlangを動かして、分散処理をやってみます。

別々のマシンで分散処理

分散処理するすべてのErlangノードは同じバージョンのErlangと同じプログラムコードを
持つ必要があります。Erlangのバージョンを合わせるためにはそれぞれのマシンに同じバージョンのErlangをインストールするしかないでしょう。
しかし、プログラムコードの共有方法は以下のように色々あるようです。

  1. いちいちプログラムコードをそれぞれのサーバにコピーする。
  2. NFSなどの共有ディスクを使う。
  3. コードサーバを利用する。
  4. Erlangシェルのコマンドnlを使う。

ここでは、NFSを利用して上記のechoプログラムを2台の仮想PCで共有します。
NFSについては、ググればすぐ出ます。
機会があれば、コードサーバに関してブログを書いてみたいと思います。

まず、仮想PC 1号機・2号機のhostsファイルにそれぞれのホスト名とIPアドレスの対応を追加します。
例えば、1号機をvpc1.com(192.168.56.101)、2号機をvpc2.com(192.168.56.103)とするなら、以下のようにします。

192.168.56.101  vpc1.com
192.168.56.103  vpc2.com

次に仮想PC 1号機で以下のようにerlを起動します。
(物理PCや仮想PCに関わらず、ネットワーク状況によって以下の起動オプションでサンプルが動かないかも知れません。私の環境でしか確認していません。)

$ erl -name echoserver@vpc1.com -setcookie echocookie
% -name サーバ名 : 呼出側がこのマシンで動くErlangプログラムを実行する時、識別のため、必要です。
%                  @の前にはErlangノード同士で識別する名前を書きます。
%                  @の後ろはhostsファイルに追加した名前(vpc1.com)を書きます。
% -setcookie クッキー名 : 分散処理をするErlang同士の認証に使われます。適当でいいと思いますが、すべてのErlangノードは同じクッキー名を使う必要があります。

Erlang R14B04 (erts-5.8.5) [source] [rq:1] [async-threads:0] [kernel-poll:false]

Eshell V5.8.5  (abort with ^G)
(echoserver@duke1-VirtualBox)1> c(echo). % コンパイルし、
{ok,echo}
(echoserver@duke1-VirtualBox)2> echo:start(). % エコーサーバを起動します。
true

仮想PC 2号機では、以下のようにerlを起動します。

erl -name echoclient@vpc2.com -setcookie echocookie
Erlang R14B04 (erts-5.8.5) [source] [rq:1] [async-threads:0] [kernel-poll:false]

Eshell V5.8.5  (abort with ^G)
(echoclient@vpc2.com)1> c(echo). % 2号機でもコンパイルします。
{ok,echo}

準備ができました。
仮想PC 2号機で仮想PC 1号機に対してリモート呼出をしてみましょう。

(echoclient@vpc2.com)1> rpc:call(echoserver@vpc1.com, echo, send, ["Hello"]). 
"Echo response : Hello"

できました!
他のマシンにあるErlangプログラムを呼び出す時は、rpcモジュールのcall関数を使います。

rpc:call(相手側のErlangノードのnameに指定した名前@相手側のErlangノードが入っているマシンのホスト名, モジュール名, 関数名, [引数1, 引数x...]). 

他の言語にもあるSOAPやRPCのようで、大したものではないように見えるかも知れませんが、
プログラムや手順が簡単で無駄がないのが分かります。
この簡潔さなら、本来の処理にもっと集中できるのではないでしょうか。

追加(2014/09/26)

rpc:callを使うと、呼び出しが煩雑ですが、
以下のようにsend関数を修正すれば、もっと簡単にリモート呼出ができます。

send(Msg, Receiver) ->
    Receiver ! {self(), {server, Msg}},
    receive
        {client, Res} ->
            Res
    end.

仮想PC 2号機から仮想PC 1号機への呼出は以下のようにすればいいです。

(echoclient@vpc2.com)2> echo:send("Hi VPC1", { echo, 'echoserver@vpc1.com' }).
"Echo response : Hi VPC1"
(echoclient@vpc2.com)3> 

自分自身へはただのechoでいいですね。
もちろん、自分のところでもecho:start()を呼び出す必要があります。

(echoclient@vpc2.com)3> echo:start().
true
(echoclient@vpc2.com)4> echo:send("Hi VPC1", echo).                           
"Echo response : Hi VPC1"

要は、メッセージを送る時、プロセス名 ! メッセージを使いますが、
メッセージの受け取り側がリモートの場合は、プロセス名が{ プロセス名, 'サーバ名' }のように
タプルになるということです。

今日はここまで。

他にソケット通信やファイル、OTPなどがありますが、
これらはAPIの使い方みたいなものなので、基礎ポイントでは取り上げないつもりです。
別のタイトルで勉強しようかと思います。

次回はちょっと面白そうな...ErlangJavaの接続についてやってみたいと思います。

参考

プログラミングErlang

プログラミングErlang

複数のファイルの名前を一気に変更するバッチスクリプト。

ファイル名が似ている複数のファイルの名前を変更したいが、
ファイルの数が多くて面倒なことがありますね。

そういう時はツールをダウンロードして解決したり、
自分の得意なプログラミング言語
プログラムを作って解決したりします。

解決する方法は色々あると思いますが、
Windowsの場合、バッチスクリプトを使えば、
簡単です。

以下のバッチスクリプトを、拡張子をbat(又はcmd)に
して保存し、実行すれば、簡単に複数のファイルの名前を
一気に変更することができます。

但し、変数の設定を先に行わなければなりません。
下記スクリプトから、pattern, filename, ext, targetを変更する必要があります。

以下のスクリプトの場合、
sample 01 document.txt
sample 02 document.txt
sample 03 document.txt
のようなファイルを
test 01.txt
test 02.txt
test 03.txt
のように変更してくれます。

詳細はスクリプト内のコメント(rem部分)を参照して下さい。
スクリプトの修正が必要ですが、色々な場面で使えると思います。

@echo off

rem utf-8を使いたい場合、コメントを解除して下さい。
rem chcp 65001

rem 変数の遅延評価(!を使う部分)を可能にします。
setlocal ENABLEDELAYEDEXPANSION

rem 変更対象のファイルを抽出するためのパターンです。ワイルドカード形式で指定します。
set pattern="sample ?? document.*"

for %%i in (%pattern%) do (    
    set source=%%i

    rem 変更後のファイル名に使う部分を変更前のファイル名から抽出する変数です。
    rem ~7,2はファイル名の7番目から2文字を切り取るとの意味です。
    rem 例えば、sample 01 document.txtの場合、01になります。
    set filename=!source:~7,2!

    rem 変更後のファイルの拡張子に使う部分を変更前のファイル名から抽出する変数です。
    rem ~1919番目の文字から最後の文字までを切り取るとの意味です。
    rem 例えば、sample 01 document.txtの場合、txtになります。
    set ext=!source:~19!

    rem 変更後のファイル名を指定した変数です。
    rem 例えば、sample 01 document.txtの場合、test 01.txtになります。
    set target=test !filename!.!ext!

    echo !source! is being renamed to !target!...
    rename "!source!" "!target!"
   
    if exist !target! (
        echo [!source!] was renamed to [!target!].
    ) else (
        echo [!source!] could not be renamed to [!target!].
    )
)
endlocal