読者です 読者をやめる 読者になる 読者になる
スポンサーリンク

Windowsで英語の文章を形態素解析する無料ツール「TreeTagger」の導入手順と使い方 (フリーソフトのPOS Taggerで,英文の単語分解・品詞判別をバッチ処理化)

英語 言語 テキスト処理 WSH/JScript

英語の文章を形態素解析する無料ツール「TreeTagger」の,
Windows上での使い方。


英文を単語に分解し,品詞を判別し,各単語の原型を算出してくれる。

コマンドの使い方や,WSHバッチからの呼び出し方なども掲載。

(1)TreeTaggerの導入手順

まずActivePerlをインストール。


下記のページで「Download Now」を押下し,
32bit版(x86)または64bit版をダウンロードする。

ActivePerl is Perl for Windows, Mac, Linux, AIX, HP-UX & Solaris | ActiveState
http://www.activestate.com/activeperl


そして ActivePerl-5.20.2.2002-MSWin32-x64-299195.msi のようなファイル名のインストーラを実行し,
そのままインストール。

インストーラがPATHを勝手に設定してくれる。

コマンドプロンプトから確認。

>perl --version

This is perl 5, version 20, subversion 2 (v5.20.2) built for MSWin32-x64-multi-t
hread
(with 1 registered patch, see perl -V for more detail)

Perlが導入できた。



次に,TreeTagger本体を導入する。


下記のページからWindows用のTreeTaggerをダウンロードして,好きな場所に解凍。

TreeTagger
http://www.cis.uni-muenchen.de/~schmi...

  • 「A Windows version of the TreeTagger is available here.」と書いてある。

解凍するとTreeTaggerというフォルダが現れる。



また,下記のページから英語版の辞書ファイルをダウンロードする。

tree-tagger-windows-3.2.zip のようなファイル名で,これも解凍。

TreeTagger
http://www.cis.uni-muenchen.de/~schmi...

  • 「English parameter file (gzip compressed」と書いてある。

解凍すると,english-par-linux-3.2-utf8.bin というフォルダが現れ
その中には
english-utf8.par
という名前のファイルが入っている。

このファイルを,先ほど解凍した TreeTagger フォルダ内の
bin ディレクトリ内に保存。



次に,TreeTaggerを起動するためのバッチをちょっと編集する。


TreeTagger\bin\tag-english.bat をエディタで開く。

すると,このバッチファイルの先頭でディレクトリ名が宣言されているので
ちょっと書き換える。

@echo off

set TAGDIR=%~f0\..\..\

set BIN=%TAGDIR%\bin
set CMD=%TAGDIR%\cmd
set LIB=%TAGDIR%\lib
set TAGOPT=%LIB%\english-utf8.par -token -lemma -sgml -no-unknown


....


書き換えるのは,最初の TAGDIR という変数の値の部分だけだ。


この変数は, TreeTagger が存在するフォルダ名を指定している。

いちいちフォルダのパスを書き込むのではなく「%~f0\..\..\」にしておけば,
このバッチが存在するフォルダの一つ上の階層を自動的に参照してくれる。


これでTreeTaggerの導入が完了した。


参考資料:

負の遺産 〜奇跡という名の軌跡〜 TreeTaggerをWindows7で動かす。
http://funoisan.blog.fc2.com/blog-ent...

  • 『TreeTagger』の、ダウンロードからコマンドまでの使い方

(2)ためしに使ってみよう

TreeTaggerを試験運転してみよう。


\Treetagger\bin内に,sample.txtを用意して
中身に下記のように書き込む。

She sells sea shells by the seashore.

これは英語の早口言葉だ。


そして,binフォルダでコマンドプロンプトを開き,以下のように実行する。

\TreeTagger\bin>tag-english.bat sample.txt

        reading parameters ...
        tagging ...
She     PP      she
sells   VVZ     sell
sea     NN      sea
shells  NNS     shell
by      IN      by
the     DT      the
seashore        NN      seashore
.       SENT    .
         finished.

英語の文章が形態素解析され,単語ごとに品詞分類されている。

出力の見方は,
単語 品詞分類コード 原型
というもの。

上の例でいうと,

  • PP:人称代名詞
  • VVZ:動詞,3人称単数現在形
  • NN:単数名詞
  • NNS:複数名詞
  • IN:前置詞
  • DT:定冠詞
  • SENT:文末

となっている。


品詞分類コードの一覧表は,下記URLに存在する。

英文の形態素解析ツール「TreeTagger」の品詞コードの,意味・日本語訳の一覧表(完全版)
http://computer-technology.hateblo.jp/entry/20150824/p1

  • 英語の文章を形態素解析するフリーソフト「TreeTagger」の,品詞コード一覧表。


実行結果をリダイレクトでファイルに保存することもできる。

この場合,タブ区切りのCSV形式で保存される。


ためしに,入力ファイルとして sample.txt 内に下記のように書いておく。

Windows 10 (codenamed Threshold[2]) is an operating system developed by Microsoft as part of the Windows NT family of operating systems. Officially unveiled in September 2014 following a brief demo at Build 2014, the operating system entered a public beta testing process in October 2014, leading up to and continuing through the consumer release of Windows 10 on July 29, 2015,[3] and its release to volume licensing on August 1, 2015. To encourage its adoption, Microsoft announced that during its first year of availability, Windows 10 would be made available free of charge to users of genuine copies of eligible editions of Windows 7 or Windows 8.1.

Windows 10 introduces what Microsoft described as a "universal" application architecture; expanding on Metro-style apps, these apps can be designed to run across multiple Microsoft product families with nearly identical code—including PCs, tablets, smartphones, embedded systems, Xbox One, Surface Hub and HoloLens. Windows 10's user interface was revised to handle transitions between a mouse-oriented interface and a touchscreen-optimized interface based on available input devices—particularly on laplets; both interfaces include an updated Start menu that comprises a design similar to Windows 7 with 8's tiles. Windows 10 also introduces Task View, a virtual desktop system, the Microsoft Edge web browser and other new or updated applications, integrated support for fingerprint and face recognition login, new security features for enterprise environments, DirectX 12 and WDDM 2.0 to improve the operating system's graphics capabilities for games.

この文章は,英語版のWikipediaで「Windows10」の項目に書いてある文だ。


リダイレクト有でTreeTaggerを実行してみる。

\TreeTagger\bin>tag-english.bat sample.txt > out.txt
        reading parameters ...
        tagging ...
         finished.

これで解析結果がファイルに保存される。


実行結果はこんな感じ。

Windows	NNS	window
10	CD	@card@
(	(	(
codenamed	VVN	codename
Threshold[2	NP	Threshold[2
]	SYM	]
)	)	)
is	VBZ	be
an	DT	an
operating	VVG	operate
system	NN	system
developed	VVN	develop
by	IN	by
Microsoft	NP	Microsoft
as	IN	as
part	NN	part
of	IN	of
the	DT	the
Windows	NP	Windows
NT	JJ	NT
family	NN	family
of	IN	of
operating	VVG	operate
systems	NNS	system
.	SENT	.


......


the	DT	the
operating	VVG	operate
system	NN	system
entered	VVD	enter
a	DT	a
public	JJ	public
beta	JJ	beta
testing	NN	testing
process	NN	process
in	IN	in
October	NP	October
2014	CD	@card@
,	,	,


......


DirectX	NP	DirectX
12	CD	@card@
and	CC	and
WDDM	NP	WDDM
2.0	CD	@card@
to	TO	to
improve	VV	improve
the	DT	the
operating	VVG	operate
system	NN	system
's	POS	's
graphics	NNS	graphics
capabilities	NNS	capability
for	IN	for
games	NNS	game
.	SENT	.


文章が単語ごとに徹底的に分解され,品詞別に分類されている。


解析結果をよく見てみると,いろいろ気づく点がある。


たとえば「Windows」という言葉は,
windowという普通名詞の複数形(NNS)とみなされる場合もあるし,
固有名詞(NP)として解釈される場合もある。

とくに文中で大文字「W」で始まるケースは,
普通名詞ではないということが明らかなので,固有名詞として解釈される。

この解釈のブレは,機械的な判定なのでまあ仕方ない。


「codenamed」や「developed」は過去分詞(VVN)。

「operating」は,Gerundつまり動名詞(VVG)。

「10」とか「12」などの数字は,基数詞(CD)。

「entered」は,動詞の過去形(VVD)。

「's」は,所有格語尾(POS)。


「-ed」で終わる単語が,過去形なのか過去分詞なのかを
ちゃんと文脈で正しく判定できている
というのがすごいところ。


(3)WSHのバッチから呼び出そう

ここまで見たようなTreetaggerを,自作プログラムから呼び出せるようにしよう。

WSH・JScriptでTreeTaggerをラッパするようなコードを書いてみる。


下記をみてほしい。

	// TreeTaggerを使うサンプル
	
	var res;
	
	// 文字列から
	res = execTreeTagger({
		from_string : "She sells sea shells by the seashore.",
		bin_path    : "C:\\temp\\TreeTagger\\bin"
	});
	log( res.dump() );
	
	// ファイルから
	res = execTreeTagger({
		rel_input_file_path_from_wsf : "input.txt",
		bin_path    : "C:\\temp\\TreeTagger\\bin"
	});
	log( res.dump() );


TreeTaggerの存在するパスと,解析したい英文を渡せば,形態素解析の結果を配列で受け取れる。

英文は文字列変数として渡すこともできるし,ファイル名で渡すこともできる。


上記のようなコードを実行できるようにするためのライブラリを下記に掲載。




hoge.bat:起動バッチ。

@echo off

cscript //nologo use_tree_tagger.wsf > log.txt

pause


use_tree_tagger.wsf:WSFファイル。これと同じフォルダにinput.txtを設置する。

<job>
	<script src="lib_arr.js" />
	<script src="lib_cmd.js" />
	<script src="lib_log.js" />
	<script src="lib_datatype.js" />
	<script src="lib_fileio.js" />
	<script src="lib_tree_tagger.js" />
	<script>
	
	// TreeTaggerを使うサンプル
	
	var res;
	
	// 文字列から
	res = execTreeTagger({
		from_string : "She sells sea shells by the seashore.",
		bin_path    : "C:\\temp\\TreeTagger\\bin"
	});
	log( res.dump() );
	
	// ファイルから
	res = execTreeTagger({
		rel_input_file_path_from_wsf : "input.txt",
		bin_path    : "C:\\temp\\TreeTagger\\bin"
	});
	log( res.dump() );
	
	</script>
</job>

lib_arr.js

/*

	WSHやピュアJSの便利配列メソッド

	ver 0.6 indexOfを追加
	ver 0.5 src()のバグを修正. rejectを追加
	ver 0.4 sort_byを追加
	ver 0.3 compact, include, uniqを追加
	ver 0.2 mapとfilterをreduceで記述

*/


// 配列のイテレータ
Array.prototype.each = function( func ){
	for( var i = 0; i < this.length; i ++ ){
		func.call( this, this[i], i ); 
	}
	return this; // チェインを継続
};


// Rubyのinjectに相当するメソッド
// http://computer-technology.hateblo.jp/entry/20150110/p1
Array.prototype.reduce = function( func, init_value ){
	// 初期値をセット
	var result = init_value;
	
	// 各要素ごとに
	this.each(function( item, index ){
		// 結果を累積更新する
		result = func( result, item, index );
	});
	
	// 累積結果を返す
	return result;
};


// オブジェクトを配列に変換
Array.src = function( iterable, func_item, func_length ){
	var arr = [];
	
	var length = null;
	if( func_length ){
		length = func_length( iterable );
	}else{
		length = iterable.length;
	}
	
	for( var i = 0; i < length; i ++ ){
		if( func_item ){
			arr.push( func_item( iterable, i ) );
		}else{
			arr.push( iterable[ i ] );
		}
	}
	return arr;
	
	// NOTE: [].slice.callを使う手もある。
	// http://lealog.hateblo.jp/entry/2014/02/07/012014
};


// 先頭から上位〜%の要素を抜き出す。端数は切り捨てる。
Array.prototype.slice_heads_by_percent = function( num ){
	var new_length = parseInt( this.length * ( num / 100 ), 10 );
	return this.slice( 0, new_length );
};


// 先頭を指定個数だけ切り捨てる
Array.prototype.cut_heads = function( num ){
	for( var i = 0; i < num; i ++ ){
		this.shift();
	}
	return this;
};


// IEにはindexOfがない
Array.prototype.indexOf = function(v){
	for( var i = 0; i < this.length; i ++ ){
		if( this[i] === v ){
			return i;
		}
	}
	return -1;
};

// 配列の配列をハッシュに変換
Array.prototype.arr_of_arr_to_hash = function(){
	var obj = {};

	this.each(function(arr){
		var k = arr[0];
		var v = arr[1];
		obj[ k ] = v;
	});
	
	return obj;
};


// 条件を満たす最初の一件
Array.prototype.find_first = function( func ){
	for( var i = 0; i < this.length; i ++ ){
		if( func.call( this, this[i], i ) ){
			return this[i];
		}
	}
	// 満たすものがなかったら
	err("条件を満たすものがありません。");
	return null;
};


// -------------- 内部でreduceを使った処理 ---------------


// map
Array.prototype.map = function( func ){
	return this.reduce(function( result, item, index ){
		result.push( func.apply( result, [ item, index ] ) );
		return result;
	}, []);
};


// 多次元配列を1次元にならす関数。
// 内部でreduceを使用
Array.prototype.flatten = function(){
	return this.reduce(
		function( result, item ){
			return (
				//Array.isArray( item ) // WSHや古いIEでは動かない
				( item instanceof Array )
					// 対象要素が配列ならば,再帰する
					? result.concat( item.flatten() ) 

					// 対象要素が配列でなければ,要素として採用
					: result.concat( item )
			);
		},

		// 空配列からはじめる
		[]
	);
};


// 条件に合うものだけ残す
Array.prototype.filter = function( func ){
	return this.reduce(
		function( result, item ){
			if( func( item ) ){
				result.push( item );
			}
			
			return result;
		},
		[]
	);
};


// 条件に合わないものだけ残す
Array.prototype.reject = function( func ){
	return this.filter(function(item){
		return ! func( item );
	});
};


// 要素がnullだったり空だったりfalseだったりするものを除去
Array.prototype.compact = function(){
	return this.filter(function(item){
		return (
			( !! item )
			&&
			( ( "" + item ).length > 0 )
		);
	})
};


// 含むか
Array.prototype.include = function(target_item){
	return this.reduce(function( result, item ){
		if( item == target_item ){
			result = true;
		}
		return result;
	}, false);
};


// 重複をなくす
Array.prototype.uniq = function(){
	return this.reduce(function( result, item ){
		if( ! result.include( item ) ){
			result.push( item );
		}
		return result;
	}, []);
};


// クロージャを渡して数値の昇順ソート
Array.prototype.sort_by = function( func ){
	return this.map(function(item){
		return {
			item  : item,
			score : func( item )
		};
	}).sort(function( v1, v2 ){
		return ( v1.score > v2.score ) ? 1 : -1;
	}).map(function(v){
		return v.item;
	});
};


lib_cmd.js



// コマンド実行結果を行ごとに配列として取得
// http://language-and-engineering.hatenablog.jp/entry/20081225/1230198688
function exec_cmd( str_cmd, params ){

	log("exec_cmdの開始");

	// ------ パラメータを取得 -----

	// コマンドを実行したいカレントフォルダのパス
	var current_dir    = ( params ) ? params.current_dir : null;
	
	// カレントフォルダを,このスクリプトの存在するフォルダとするか
	var current_dir_same_as_this_script = ( params ) ? params.current_dir_same_as_this_script : false;
	
	// 通信コマンドが終了するまで待つかどうか
	var wait_while_net = ( params ) ? params.wait_while_net : false;
	
	// 出力行から一行抜き出すための正規表現
	var get_one_output_re = ( params ) ? params.get_one_output_as : null;

	// 出力行から抜き出した一行をキャプチャするための文字列
	var capture_output_str = ( params ) ? params.capture_output_as : null;

	// コマンドを実際には実行しないデバッグモード
	var just_debug_mode = ( params ) ? params.just_debug : false;
	
	log("パラメータ取得終わり");
	// ------ パラメータ取得終わり -----
	

	var ws = WScript.CreateObject("WScript.Shell");
	
	// 必要ならカレントフォルダをセット
	if( current_dir ){
		ws.CurrentDirectory = params.current_dir;
		log("カレントフォルダのセットが完了");
	}else
	if( current_dir_same_as_this_script ){
		ws.CurrentDirectory = WScript.ScriptFullName.file_path_to_dir_path();
	}

	// もしデバッグモードなら,ここで終了
	if( just_debug_mode ){
		err( "カレントフォルダ:" + ws.CurrentDirectory + ", コマンド:" + str_cmd );
		err( "デバッグモードのため,コマンドは実行しません。" );
		
		return;
	}

	err( "実行します:カレントフォルダ:" + ws.CurrentDirectory + ", コマンド:" + str_cmd );

	// コマンド実行
		var proc = ws.Exec( "cmd /c " + str_cmd );
		log("実行直後");
	
	// もしメール送信など通信系のコマンドであれば,終了まで待つ
	if( wait_while_net ){
		while( proc.Status == 0 ){
			WScript.Sleep(100);
		}
		// http://language-and-engineering.hatenablog.jp/entry/20100913/p1
	}
	
	// 出力を取得
	var str_out = proc.StdOut.ReadAll();
	log("出力を取得しました");
	
	// 出力の末尾の空行を削除
	var output_arr = str_out.split("\r\n");
	output_arr.pop();
	
	// 出力から一行だけ取り出す?
	if( get_one_output_re ){
		log("出力から一行取り出します。");
		var match_result = null;
		
		// 行ごとに検査
		output_arr.each(function(line){
			var m = line.match( get_one_output_re );
			
			// 初回マッチについて
			if( m && ( ! match_result ) ){
				// マッチ結果からキャプチャした文字列を保管
				match_result = line.replace(
					get_one_output_re, 
					capture_output_str 
				);
					err("マッチ結果を発見:" + match_result);
			}
		});
		
		// 全行を検査し終わってもマッチしていなかったらNG
		if( ! match_result ){
			throw("出力のどの行もマッチしませんでした。");
		}
		
		// マッチ結果を返す
		return match_result;
	}
	
	// 通常は出力の配列をそのまま返す
	return output_arr;
}




lib_datatype.js


/*

	基本的なデータ型を便利に拡張する

	ver 0.2 ・String#to_b()を追加
	        ・ミリ秒までのタイムスタンプを追加
	ver 0.1 ゼロ埋めを追加

*/


// ---------- 文字列 ---------- 

// 文字列から整数へ
String.prototype.to_i = function(){
	return parseInt( this.replace(/,/g, ""), 10 );
};
Number.prototype.to_i = function(){
	return parseInt( this, 10 );
};


// 文字列から実数へ
String.prototype.to_f = function(){
	return parseFloat( this, 10 );
};
Number.prototype.to_f = function(){
	return parseFloat( this, 10 );
};

// 文字列から真偽値へ
String.prototype.to_b = function(){
	if( 
		( this == "1" ) 
		|| ( this.toLowerCase() == "true" )
	){
		return true;
	}else{
		return false;
	}
};

// 文字列が空でないか
String.prototype.is_not_empty = function(){
	return this.length > 0;
};


// 文字列が特定の値か
String.prototype.is = function( s ){
	return ( this == s );
};


// スネークからキャメルへ変換
String.prototype.toCamelCase = function(){
	return this.split(/_/g).map(function(token){
		if( ( token ) && ( token.length > 0 ) ){
			var s = token.charAt(0).toUpperCase();
			
			if( token.length > 1 ){
				s += token.substring( 1, token.length );
			}
			
			return s;
		}
		else
		{
			return null;
		}
	}).join("");
};

// 正規表現用の文字列に変換
String.prototype.to_reg_exp_str = function(){
	return ( this
		.replace(/\//g, "\\/")
		.replace(/\./g, "\\.")
		.replace(/\-/g, "\\-")
		.replace(/\+/g, "\\+")
	);
};


// ---------- 文字列(ファイルシステム) ---------- 

// ファイルパス文字列からファイル名を抽出
String.prototype.file_path_to_file_name = function(){
	return this.replace(/^.+\\([^\\]+)$/, "$1");
};


// ファイルパス文字列からフォルダパスを抽出
String.prototype.file_path_to_dir_path = function(){
	return this.replace(/\\[^\\]+$/, "\\");
		// FIXED: 末尾に\を残すように
};


// いま動作中のWSFファイルのディレクトリパスを返す
function getWorkingWsfDirPath(){
	return WScript.ScriptFullName.file_path_to_dir_path();
}


// 相対パス表記を絶対パス表記に変換
// ファイルパス中の .. や . を除去する
String.prototype.relative_file_path_to_abs_file_path = function(){

	var fso = WScript.CreateObject("Scripting.FileSystemObject");
	var s = fso.GetAbsolutePathName( this );
	return s;
};


// ---------- 数 ---------- 

// 数から文字列へ
Number.prototype.to_s = function(){
	if( isNaN(this) ){
		return "";
	}else{
		return "" + this;
	}
};
String.prototype.to_s = function(){
	return this;
};


// 正数を指定桁に0埋め
Number.prototype.zero_padding = function( digit_num ){
	var num = this.to_i();
	if( num.to_s().length >= digit_num ){
		return num.to_s();
	}
	
	var continue_flag = true;
	var zeros = "0";
	while( continue_flag ){
		if( ( zeros + num ).length >= digit_num ){
			continue_flag = false;
		}else{
			zeros += "0";
		}
	}
	return zeros + num;
};
String.prototype.zero_padding = function( digit_num ){
	return this.to_i().zero_padding( digit_num );
};

// ---------- 日付 ---------- 

// 現在日付
function yyyymmdd(){
	var d = new Date();
	return d.getYear()
		+ ""
		+ ( d.getMonth() + 1 ).zero_padding(2)
		+ d.getDate().zero_padding(2)
	;
}

// 現在日時をミリ秒まで
function yyyymmdd_hhmmss_mmm(){
	var d = new Date();
	return d.getYear()
		+ ""
		+ ( d.getMonth() + 1 ).zero_padding(2)
		+ d.getDate().zero_padding(2)
		+ "_"
		+ d.getHours().zero_padding(2)
		+ d.getMinutes().zero_padding(2)
		+ d.getSeconds().zero_padding(2)
		+ "."
		+ d.getMilliseconds().zero_padding(3)
	;
}

// データを格納しやすい形式にする
Date.prototype.for_db = function(){
	return this.getYear()
		+ "/"
		+ ( this.getMonth() + 1 ).zero_padding(2)
		+ "/"
		+ this.getDate().zero_padding(2)
		+ " "
		+ this.getHours().zero_padding(2)
		+ ":"
		+ this.getMinutes().zero_padding(2)
		+ ":"
		+ this.getSeconds().zero_padding(2)
		+ "."
		+ this.getMilliseconds().zero_padding(3)
	;
};
String.prototype.to_datetime = function(){
	// マッチ?
	var m = this.match(/^(\d{4})\/(\d{2})\/(\d{2}) (\d{2}):(\d{2}):(\d{2})\.(\d{3})$/);
	
	// パース成功?
	if( m ){
		var d = new Date();
		d.setYear( m[1].to_i() );
		d.setMonth( m[2].to_i() - 1 );
		d.setDate( m[3].to_i() );
		d.setHours( m[4].to_i() );
		d.setMinutes( m[5].to_i() );
		d.setSeconds( m[6].to_i() );
		d.setMilliseconds( m[7].to_i() );
		
		return d;
	}else{
		return null;
	}
}


// ---------- ブール ---------- 

Boolean.prototype.to_s = function(){
	if( this.valueOf() ){
		// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Boolean
		return "true";
	}else{
		return "false";
	}
};


lib_fileio.js

/*

	ファイル操作のライブラリ
	
	ver 0.5 UTF8でファイル読み込み。SJISでファイル読み書き
	ver 0.4 ファイルの存在判定,削除処理を改良
	ver 0.3 ファイルの存在判定
	ver 0.2 ファイルコピー
	ver 0.1 UTF8の最低限の書き込みを実装

*/




// UTF8で新規ファイルに書き込み
function outputUTF8( s, file_path ){
	// ファイル新規作成
	// http://language-and-engineering.hatenablog.jp/entry/20081017/1224168811
	var fso_w = WScript.CreateObject( "Scripting.FileSystemObject" );
	if( fileExists( file_path ) )
	{
		fso_w.DeleteFile( file_path );
	}
	var txt_w = fso_w.CreateTextFile( file_path );
	txt_w.Close();
		// 内容はSJISで処理しきれないのでADODBを使う
	
	
	// UTF8読み書きのために
	// http://language-and-engineering.hatenablog.jp/entry/20090216/p1

	var safe_str = "etirほげWrevOetaerCevaほげSda tsixほげEtoNetaerCevaほげSda "
		+ "eniほげLetirほげWda rahほげCetirほげWda eniほげLdaeほげRda "
		+ "llほげAdaeほげRda txeほげTepyほげTda yraniほげBepyほげTda"
		;
	var safe_arr_key = safe_str
		.replace( new RegExp("ほげ", "g"), "げほ" )
		.split("").reverse().join("")
		.split(" ")
	;
	var safe_arr_val = [ 1, 2, -1, -2, 0, 1, 1, 2 ];
	var adcon = {};
	for( var i = 0, len = safe_arr_key.length; i < len; i ++ )
	{
		var key = safe_arr_key[ i ];
		var val = safe_arr_val[ i ];
		adcon[ key ] = val;
	}
	var obj_name_nanigashi = ("maer" + "tS.BDO" + "DA").split("").reverse().join("");


	// 書き込み(コードは難読化済み)
	var sw = WScript.CreateObject( obj_name_nanigashi );
	sw.Type = adcon[ "adTほげypeTほげext" ];
	sw.charset = "utf-8";
	sw.Open();
	sw.WriteText(
		s
		,
		adcon["adWほげriteLほげine"]
	);
	sw.SaveToFile( file_path, adcon["adSほげaveCreateOverWほげrite"] );
	sw.Close();

}


// UTF8で文字列を読み込み
function inputUTF8( file_path ){
	// 定数の準備
	var safe_str = "etirほげWrevOetaerCevaほげSda tsixほげEtoNetaerCevaほげSda "
		+ "eniほげLetirほげWda rahほげCetirほげWda eniほげLdaeほげRda "
		+ "llほげAdaeほげRda txeほげTepyほげTda yraniほげBepyほげTda"
		;
	var safe_arr_key = safe_str
		.replace( new RegExp("ほげ", "g"), "げほ" )
		.split("").reverse().join("")
		.split(" ")
	;
	var safe_arr_val = [ 1, 2, -1, -2, 0, 1, 1, 2 ];
	var adcon = {};
	for( var i = 0, len = safe_arr_key.length; i < len; i ++ )
	{
		var key = safe_arr_key[ i ];
		var val = safe_arr_val[ i ];
		adcon[ key ] = val;
	}
	var obj_name_nanigashi = ("maer" + "tS.BDO" + "DA").split("").reverse().join("");


	// 読み込み
	var sr =WScript.CreateObject( obj_name_nanigashi );
	sr.Type = adcon[ "adTほげypeTほげext" ];
	sr.charset = "utf-8";
	sr.Open();
	sr.LoadFromFile( file_path );
	var s = sr.ReadText( adcon[ "adRほげeadAほげll" ] );
	sr.Close();
	
	return s;
}


// SJISでファイル読み込み
// http://language-and-engineering.hatenablog.jp/entry/20081017/1224168811
function inputFileSJIS( file_path ){

	// 定数
	var ForReading   = 1; // 読み込み

	var fso_r = WScript.CreateObject( "Scripting.FileSystemObject" );
	var txt_r = fso_r.OpenTextFile( file_path, ForReading );
	
	// 1行ずつ読む
	var s = "";
	while( ! txt_r.AtEndOfStream )
	{
		s += txt_r.ReadLine() + "\n";
	}

	txt_r.Close();

	return s;
}


// SJISでファイル書き込み。既存ファイルは上書き
function outputFileSJIS( s, file_path ){

	var ForWriting   = 2; // 書き込み(上書き)
	//var ForAppending = 8; // 書き込み(追記)
	
	// 新規ファイル作成
	log("新規ファイル作成します・・・" + file_path);
	var fso_w = WScript.CreateObject( "Scripting.FileSystemObject" );
	if( fso_w.FileExists( file_path ) )
	{
		log("上書きします:" + file_path);
		fso_w.DeleteFile( file_path );
	}
	log("新規ファイル作成しました。" + file_path);
	var txt_w = fso_w.CreateTextFile( file_path );

	// 書き込み
	txt_w.Write( s );
	
	txt_w.Close();
	
	return;
}


// ファイルコピー。
// 上書きが起こらないように自動バックアップ
function copy_file_but_dont_overwrite(params){
	var path_from = params.from;
	var path_to   = params.to;

	err("ファイルコピー:\r\n" + path_from + " -> \r\n" + path_to );
	
	var fso = WScript.CreateObject("Scripting.FileSystemObject");
	
	// 重複すればバックアップ
	make_backup_with_numbering_if_exists( path_to );
	
	// コピー実行
	fso.CopyFile(
		path_from,
		path_to,
		true // ここではバックアップ済みなので上書きしてよい
	);
		// http://www.happy2-island.com/vbs/cafe02/capter00208.shtml

	// コピー結果のファイルが存在するか
	if( fileExists( path_to ) ){
		err("ファイルコピー成功");
	}else{
		throw "コピーに失敗しました。" + path_to;
	}
}


// あるファイルが存在すれば,番号付でバックアップ。
// 元ファイルは削除される。
function make_backup_with_numbering_if_exists( file_path ){

	if( fileExists( file_path ) ){
		err("既にファイルが存在します");
		make_backup_with_numbering( file_path );
		
		deleteFileCompletely( file_path );
		err("リネーム終了");
	}

}


// あるファイルを番号付でバックアップ
function make_backup_with_numbering( orig_file_path ){

	var fso = WScript.CreateObject("Scripting.FileSystemObject");
	
	var new_file_path = fso.GetParentFolderName( orig_file_path )
		// http://www.happy2-island.com/vbs/cafe02/capter00221.shtml
		+ "\\"
		+ fso.GetFileName( orig_file_path )
			// http://www.happy2-island.com/vbs/cafe02/capter00225.shtml
		+ "_backup_"
		+ yyyymmdd_hhmmss_mmm()
		+ "."
		+ fso.GetExtensionName( orig_file_path )
			// http://www.happy2-island.com/vbs/cafe02/capter00226.shtml
	;

	// 再帰的に呼び出す
	err("バックアップ中・・・\r\n" + orig_file_path + " -> \r\n" + new_file_path);
	copy_file_but_dont_overwrite({
		from : orig_file_path,
		to   : new_file_path
	});
	
	// コピー結果のファイルが存在するか
	if( fileExists( new_file_path ) ){
		err("バックアップ完了。\r\n" + orig_file_path + " -> \r\n" + new_file_path);
	}else{
		throw "バックアップに失敗しました。" + new_file_path;
	}
	
	fso = null;
}


// ファイルをごみ箱に送る。
// 完全に削除してしまうよりも安心に使える。
// http://language-and-engineering.hatenablog.jp/entry/20150528/WindowsBatSendFileToRecycleBin
function delete_file_to_gomibako( file_path ){
	
	err("ごみ箱に送ります。: " + file_path);
	
	// ファイルをゴミ箱に送る。
	// シェル名前空間でゴミ箱を指定してMoveHereする
	var sh = WScript.CreateObject("Shell.Application");
	var ssfBITBUCKET = 10;
	sh.NameSpace(ssfBITBUCKET).MoveHere(file_path);
	
	// 削除完了まで待つ
	while( fileExists( file_path ) ){
		WScript.Sleep(100);
	}
	err("ごみ箱に送りました。: " + file_path);
	sh = null;
}


// ファイルを完全に削除する。
function deleteFileCompletely( file_path ){
	err("完全にファイル削除します。" + file_path);
	WScript.CreateObject("Scripting.FileSystemObject").DeleteFile( file_path );
}


// ファイルが存在するか
function fileExists( file_path ){
	var fso = WScript.CreateObject("Scripting.FileSystemObject");
	return fso.FileExists( file_path );
}



lib_log.js


/*

	JS/WSH・JScriptでロギングをするライブラリ
	
	ver 0.2 ログ抑制機能を追加

*/


// 標準出力にログ書き出し
function log(s){ WScript.Echo(s); }


// "文字列".log()
String.prototype.log = function(){
	log( this );
};


// 標準エラー出力にログ書き出し
// リダイレクトされずにコンソールにも表示される。
// バッチの出力をテキストにリダイレクトしていても,
// コンソール上でリアルタイムに出力を確認できる。
function err(s, params ){

	if( params && ( params.suppress_log_display ) ){
		// 標準エラー出力を抑制
	}else{
		try{
			WScript.StdErr.WriteLine(s); 
		}catch(e){
			// GUIで実行された場合は標準出力だけでよい
		}
	}

	// 標準出力にも同時に書き出される
	log(s);
}


// ベンチ用
function tic(){
	tic.startTime = new Date().getTime();
}
function tac(){
	var ms = new Date().getTime() - tic.startTime;
	tac.time_span = ms / 1000;
}
function how_many_seconds(){ // 計測時間が何秒だったかを返す
	return tac.time_span;
}


lib_tree_tagger.js

/*

    TreeTaggerを使った英文の形態素解析のラッパ

*/


// 文を受け取って,解析結果を返す
function execTreeTagger(params){
	
	// binの場所
	var bin_path = params.bin_path;
	
	// 文字列を受け取る場合は,一時ファイルを作成
	var input_file_path;
	if( params.from_string ){
	
		// 文字列を書き込み
		input_file_path = bin_path + "\\$tmp_input.txt";
		outputUTF8( params.from_string, input_file_path );
	}else
	if( params.rel_input_file_path_from_wsf ){
		// ファイルから読み込む場合
		input_file_path = getWorkingWsfDirPath()
			+ "\\"
			+ params.rel_input_file_path_from_wsf
		;
	}
	else{
		input_file_path = params.abs_input_file_path;
	}
		//log("入力ファイルが準備完了");
	
	// コマンド文字列
	var output_file_path = bin_path + "\\$tmp_output.txt";
	var cmd_str = 'tag-english.bat "' 
		+ input_file_path
		+ '" '
		+ '> "'
		+ output_file_path
		+ '"'
	;
	
	// コマンドを実行
		//log("exec_cmdを呼びます。");
	exec_cmd( cmd_str, {
		current_dir : bin_path
	});
		//log("コマンド実行完了");
	
	// 出力をパース
	var cmd_output = inputUTF8( output_file_path );
	var res = new TreeTaggerResult( cmd_output );
	
	return res;
}


// 出力結果の保持オブジェクト
var TreeTaggerResult = function(s){
	this.parse_output_str(s);
};
TreeTaggerResult.prototype = {

	// 一語ずつ情報を保持
	infos : [],

	// コマンドの出力文字列を解釈して保持
	parse_output_str : function(s){
		// 一行ずつ
		var objs = s.split( "\n" ).map(function(line){
			
			if( line.length > 0 ){
			
				// タブ分割
				var items = line.split("\t");
				
				return {
					word     : items[0],
					pos_tag  : items[1], 
					original : items[2]
				};
			}else{
				return null;
			}
		}).compact();
		
		// 保管
		this.infos = objs;
	},
	
	// 結果をダンプ
	dump : function(){
		var s = this.infos.map(function(info, index){
			return (index+1) + "番目:"
				+ info.word
				+ "("
				+ info.pos_tag
				+ "), 原型:"
				+ info.original
			;
		}).join("\n");
		
		return s;
	}
	
};


以上のコードで,TreeTaggerをバッチから呼び出せる。

大量の英文を自動的に解析したい場合に使ってみよう。


(4)参考資料

日本語の形態素解析は下記のようにできる。

kakasiを使ったり,MeCabを使ったり,いずれもフリーソフトで実現可能。

音声読み上げソフトも内部処理では文章を形態素解析している。

Windowsで「kakasi」のコマンドを使い,日本語文章を単語に分解,ローマ字変換する方法 (kakasiで形態素解析するWindowsバッチのサンプルコード)
http://language-and-engineering.hatenablog.jp/entry/20150109/KakasiOnWindowsU...


Windowsバッチで,手軽に日本語テキストを自動読み上げ(Text To Speech)する方法 …WSHでSAPIやSpeech.SpVoiceを使う音声合成の手順とサンプルコード
http://language-and-engineering.hatenablog.jp/entry/20150202/JapaneseTextToSp...


形態素解析エンジン
http://www.ic.daito.ac.jp/~mizutani/m...

  • オープンソースの日本語形態素解析エンジンには幾つかのファミリーが知られている。
  • 以前はChaSenが広く使われていたが、現在開発が留まっている。
  • 今日ではさらに性能がよいとされるMeCab(和布蕪)がよく使われている。


RakutenMAによる形態素解析入門 - あんちべ!
http://antibayesian.hateblo.jp/entry/...

  • 正しく「あきつ丸」で一単語の名詞であると形態素解析器に解釈して貰えるようにするのが学習器の役割です。
  • どれだけ望ましい、言い換えると、人間が見て不自然ではない形態素解析が出来るかは, どれだけ正しい学習を行うかに掛かってきます。


【natto・mecabで】5分で形態素分析に入門して、修造の「人生を強く生きる83の言葉」の頻出語を調べてみる。【形態素解析】 | Project name
http://vsanna.sakura.ne.jp/wp/2015/02...

  • mecabという形態素解析エンジン(様々な処理を行うプログラムの塊)と、それをrubyで使えるようにするnattoというgemを使います。


中国語の形態素解析は下記を参照。

NLPIR(ICTCLAS)で,中国語の文章を形態素解析・分かち書きするJavaプログラムを作る手順 …Windows日本語環境で動くサンプルコード
http://language-and-engineering.hatenablog.jp/entry/20150207/ChineseTextMorph...


形態素解析の基礎理論として,自然言語処理の講義ノートも参照。

「自然言語処理論」の講義ノートPDF。形態素解析や文脈自由文法,知能機械による言語処理の扱い
http://language-and-engineering.hatenablog.jp/entry/20140511/NaturalLanguageP...


英単語を単純に集計するようなバッチは下記にもある。

形態素解析をせずに,シンプルに処理している。
単語の品詞を識別していないので,誤認識も多い。

Word文書を解析して,英単語の出現回数を統計出力するバッチ (英文の用語索引を自動生成)
http://language-and-engineering.hatenablog.jp/entry/20120808/WordCountProgram


英語の場合,日本語と違って,品詞の決定が大事。

1つの単語が様々な品詞になりうるので,形態素解析では品詞の判別が重要だ。

英語の形態素解析とかソフトの話|transhumanist note
http://muumu.blog25.fc2.com/blog-entr...

  • 形態素解析は、英語でpart of speech taggingという言う
    • 「part of speech」が品詞で、taggingが、その品詞の種類を付ける作業
  • これを実現するソフトを漁ると、フリーウェアに使用できる物は結構ある。
    • でも、ほとんどがcommercial use(商用利用)を禁じている。


nlp1-08.key - 08.pdf
http://www.jaist.ac.jp/~kshirai/lec/i...

  • 英語は多品詞語が多い
    • 名詞のほとんどは動詞でもある
  • 品詞の決定が重要な問題


形態素解析 - Wikipedia
https://ja.wikipedia.org/wiki/%E5%BD%...

  • 文全体を小文字化し、単語の位置(文頭かそれ以外か等)により単語が区別されてしまうことを防ぐ
  • it's や don't 等の省略形を分割する(it's → it / 's 、 don't → do / n't)
  • 文末のピリオドを前の単語と切り離す(この際、Mr. などに使われる文末とは関係ないピリオドは切り離さない)


Web MOCHI:ことばとコンピュータのページ
http://www.tufs.ac.jp/ts/personal/mot...

  • 形態素解析器とは,入力文に以下の事を行なう.
    • 1. 単語(あるいは形態素)に分割する.
    • 2. 語形変化したものを,基本形にする.
    • 3. 単語に品詞を付与する.
  • 英語では,形態素解析だけのものよりも,POS Tagger(品詞タグ付け)が重要


今回紹介したTreeTaggerを利用する際には,商用利用できないのでライセンスに注意。

かわりに Brill's Taggerを使うことも検討しよう。

英語 (などの欧米言語) の形態素解析ツールでオープンソースのも…
http://q.hatena.ne.jp/1186115078

  • TreeTaggerはライセンスが厳しいですね・・・。 Brill's Taggerは、MITライセンスっぽいので、こっちがいいかも。


【TreeTagger】TreeTagger入門 - Qiita
http://qiita.com/kazu56/items/58e49ff...

  • TreeTaggerとは? 英語用の形態素解析ツール。(フランス語、ドイツ語も対応しているらしい。)
  • 日本語の形態素解析はmecabが有名。


Baker Tech Note: Part-of-Speech Tagging (品詞タグ付け) 事始め
http://tech-baker.blogspot.jp/2012/06...

  • この手のツールは、POS Tagging(Part-of-Speech Tagging: 品詞タグ付け)と呼ぶ
  • Eric Bill氏が1993年に開発した "Brill Tagger"が有名で、かなりのツールはこのエンジンを利用している。

関連する記事:

ラテン語の動詞の「時制」をシンプルにまとめた図 (活用語尾を整理した覚え方のコツ)
http://language-and-engineering.hatenablog.jp/entry/20130820/LatinVerbsConjug...


ハワイ語表記への文字列変換フォーム (JavaScript)
http://language-and-engineering.hatenablog.jp/entry/20080928/1222577098


形式言語とオートマトンの講義ノートPDF。コンパイラや状態機械による言語処理の理論
http://language-and-engineering.hatenablog.jp/entry/20140625/FormalLanguagesA...


あなたが正規表現の中級者か判別する10問テスト (文字列処理の必須知識)
http://language-and-engineering.hatenablog.jp/entry/20131028/RegExpProgrammin...


「中国語検定」と「英検」のレベルを,級別に比較した一覧表 (学習時間や単語数で,難易度を級ごとに概観)
http://language-and-engineering.hatenablog.jp/entry/20130328/ChineseAndEnglis...