ERRANTを使用するための下準備

博論関係のメモ。
GitHubERRANTというプログラムがある。

正式名称は、

ERRor ANnotation Toolkit: Automatically extract and classify grammatical errors in parallel original and corrected sentences.

ということで、例えば、学習者の作文とそれを添削した母語話者の添削文の2つを持っていれば、自動で文法的なエラーを検出し分類してくれるというもの。つまり、学習者の作文だけでなく、その学習者の作文に対して母語話者によって添削文が付与されている学習者コーパスなどがあれば、エラータグ等つけることなく、このプログラムを走らせれば文法的なエラーを検出できるという優れもの。

これは、一度使ってみようということで、まずは下準備が必要。
まずは、学習者コーパス。母語話者の添削文が付与されている学習者コーパスといえば、NICER: the Nagoya Interlanguage Corpus of English Rebornということで、今回はNICERを使ってみる。

NICERは、CHAT形式で以下のように、学習者の文と母語話者による添削文がパラレルに収録されている。

*JPN513:	education of teachers
%NTV:	Education of Teachers
%COM:	
%par:
*JPN513:	Let's think about education of teachers in Japan.
%NTV:	Let's think about the education of teachers in Japan.
%COM:	
%par:
*JPN513:	Firstly, in Japan how do people become teachers?
%NTV:	First, in Japan, how do people become teachers?
%COM:	The "-ly" ending after numbers is old-fashioned. Just say "First", "Second", etc.
*JPN513:	I know two ways.
%NTV:	OK
%COM:	
*JPN513:	One is to enter school for growing teachers.
%NTV:	One is to enter a teacher's college.
%COM:	
*JPN513:	The other is to study education besides the major.
%NTV:	The other is to study education in addition to another major.
%COM:	
*JPN513:	Near here Aichi education college are there.
%NTV:	NG
%COM:	

後略

ERRANTのreadmeを参照すると、学習者の作文と母語話者の作文は別々のファイルに保存されていなければならないらしい。しかし、NICERの収録形式では、学習者の作文の直後にそれに対応する母語話者の作文が付与されている。このままではERRANTを使用することができないので、学習者の作文は学習者用のテキストファイルに保存し、母語話者の作文は母語話者用のテキストファイルに保存することにした。もちろん手作業でやっていると(できないこともないが。。)莫大な時間がかかってしまうので、Pythonでプログラムを作成してサクッと処理することにした。

学習者の作文は、

*JPN513:	Let's think about education of teachers in Japan.
*JPN513:	Firstly, in Japan how do people become teachers?

といったように、文の先頭に学習者のIDが付与されているので、これを見つけ出して、抽出すれば良い。
母語話者の作文も同様に、

%NTV:	Let's think about the education of teachers in Japan.
%NTV:	First, in Japan, how do people become teachers?

といったように、「%NTV」という記号が先頭に付与されているので、これを見つけ出して抽出すれば良い。

案外簡単そうに思える。実際これだけなら簡単。抜き出したそれぞれの作文は、別々のファイルに保存したいので、学習者の作文を抽出したファイルは、末尾に「_ori」、母語話者の作文を抽出したファイルは、「_cor」を末尾につけることにした。
実際に、Pythonでプログラムを書いてみた。

#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys,re,glob

argvs = sys.argv
argc = len(argvs)

file_lists = sorted(glob.glob(sys.argv[1] + '*.txt'))

for filename in file_lists:
	fn = filename.strip("test/"".txt")
	fw = open(str(fn) + "_ori.txt", 'w')
	fw2 = open(str(fn) + "_cor.txt", 'w')
	with open(filename, 'r') as fn:
		files = fn.read()
		for i in files:
			text = re.findall('(\*.+)', files, re.IGNORECASE)
			text_ori = "\n".join(text)
			text2 = re.findall('(%NTV.+)', files, re.IGNORECASE)
			text_cor = "\n".join(text2)
		fw.write(str(text_ori))
		fw2.write(str(text_cor))
	fw.close()
	fw2.close()

学習者の作文と母語話者作文の抽出は、以下のような感じ。

学習者
*JPN513:	education of teachers
*JPN513:	Let's think about education of teachers in Japan.
*JPN513:	Firstly, in Japan how do people become teachers?
*JPN513:	I know two ways.
*JPN513:	One is to enter school for growing teachers.
*JPN513:	The other is to study education besides the major.
*JPN513:	Near here Aichi education college are there.
母語話者添削文
%NTV:	Education of Teachers
%NTV:	Let's think about the education of teachers in Japan.
%NTV:	First, in Japan, how do people become teachers?
%NTV:	OK
%NTV:	One is to enter a teacher's college.
%NTV:	The other is to study education in addition to another major.
%NTV:	NG

これでやりたいことはできた、、かのように見えるが、問題がある。
話者記号の「*JPN513」の部分と「%NTV」の部分が要らない。これだけなら、プログラムを少し修正すれば対応可能(正規表現のグループ化を話者記号の部分だけ「?:」を使って無効にする)だが、文法的に問題のない作文には、母語話者が「OK」と書いている。これが厄介。
ERRANTでは、母語話者の添削文と学習者の文を比較してエラーを検出するので、学習者が文法的に問題のない文を書いているのであれば、文法的なエラーがないということを認識させるために、母語話者の作文も「OK」ではなく、学習者の作文と全く同じでなければならない。

というわけで、母語話者が添削文を付与する箇所に「OK」と書いていれば、その箇所に学習者の文をそのまま挿入するという処理を付け加えることにした。さらに、上で書いたプログラムでは、カレントディレクトリに結果のファイルが保存されるので、カレントディレクトリではなくて、新しいディレクトリを作成して、そこに結果のファイルを保存することにした。

#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys,os,re,glob

argvs = sys.argv
argc = len(argvs)

os.makedirs(sys.argv[1], exist_ok = True)

for fn in sorted(glob.glob(sys.argv[2] + '*.txt')):
	fn2 = fn.strip(sys.argv[2] + "txt")
	fn3 = fn2.strip("\.")
	fw_ori = open(sys.argv[1] + fn3 + "_ori.txt", 'w')
	fw_cor = open(sys.argv[1] + fn3 + "_cor.txt", 'w')
	with open(fn, 'r') as fn4:
		files = fn4.readlines()
	for i in files:
		original = re.findall(r'(?:\*JAN\d\d\d\d:\t)(.+\n)', i, re.IGNORECASE)
		if len(original) != 0:
			result_ori = original[0]
			fw_ori.write(result_ori)
		correct = re.findall(r'(?:%NTV:\t)(.+\n)', i, re.IGNORECASE)
		if len(correct) != 0:
			cor_rep = [j.replace('OK\s?\n', result_ori) for j in correct]
			fw_cor.write(cor_rep[0])
	fw_ori.close()
	fw_cor.close()

使い方は、MacならTerminal上で、WindowsならCygwin上などで

python3 プログラム名 保存先ディレクトリ名 抽出対象ディレクトリ名

なので、上のプログラムをextraction.pyという名前で保存するとし、保存先のディレクトリ名を「result」、NICERの保存場所が「NICER」とすると、

python3 extraction.py result/ NICER/

これで走るはず。

学習者の作文と母語話者作文の抽出は、以下のような感じ。

学習者
education of teachers
Let's think about education of teachers in Japan.
Firstly, in Japan how do people become teachers?
I know two ways.
One is to enter school for growing teachers.
The other is to study education besides the major.
Near here Aichi education college are there.
母語話者添削文
Education of Teachers
Let's think about the education of teachers in Japan.
First, in Japan, how do people become teachers?
I know two ways.
One is to enter a teacher's college.
The other is to study education in addition to another major.
NG

最初に書いたプログラムだと、「I know two ways」の部分が、母語話者の添削文だと「OK」だったのが、新しいプログラムでは、しっかりと学習者が書いた正しい文に書き換わっている。
これで下準備は完了したので、後はERRANTを使っていろいろやってみることにする。

添削文の「NG」については、これはもうどうしようもないのでとりあえずそのままにしておこう。。

catコマンドで改行記号確認

WindowsとMacintoshの両方で、テキストファイルをいじっていると改行記号が異なるためWindowsとMacintoshでテキストファイルの表示が異なる場合があり困ることが多い。
Macの方では、ちゃんと改行されているのに、Windowsの方のテキストエディタで開くと思ったように改行されていなかったり。

今回も博論の関係で、英文を添削業者にテキストファイルで提出したのだが、こちらがMacであちらがWindowsだったため、業者が開いたテキストファイルが、こちらの思ったように改行されていなくて困った。
なので、Macの改行記号をWindowsの改行記号に変更し、ちゃんと変更されているのかを確認する方法を備忘録のため書いておく。

テキストエディタで改行記号を確認できるエディタは、例えば、Windowsだとサクラエディタがあるが、Terminal上で改行記号を確認するときは、いつもcatコマンドを使う。

cat -e filename.txt

これだけで、改行記号を確認できる。
改行コードと改行記号、説明、対応する正規表現は以下の通り

改行コード 改行記号 説明 正規表現
LF $ Line Feed(Unix系全般) \n
CR ^M Carriage Return(Mac OS 9以前) \r
CRLF ^M$ 上記2つの合わせ技(Windows系) \r\n

こんな感じでTerminal上で確認できる。この例だと、改行記号はすべて^$Mになっているので、CRLF、つまりWindows対応の改行である。
改行記号の変換方法は、いろいろあるようだが、perlで正規表現を使って置換してやれば良い。

CRLFから、LF

perl -pi -e 's/\r\n/\n/g' *.txt

LFから、CRLF

perl -pi -e 's/\n/\r\n/g' *.txt

案外簡単。

参考リンク
https://en.wikipedia.org/wiki/Newline

NICERのテキストファイルをplain化

NICER: the Nagoya Interlanguage Corpus of English Reborn 1.0には、英語母語話者と英語学習者が執筆したエッセイファイルが収録されている。
エッセイが収録されているテキストファイルは、単に英文が収録されているわけではなく、CHAT形式で執筆者のDemographic情報やエッセイの評定、母語話者の添削、コメントなども収録されている。以下のような感じ。

@Begin
@Participants:	JPN501
@PID:	PIDJP501
@Age:	21
@Sex:	F
@YearInSchool:	U2
@Major:	agriculture
@StudyHistory:	8
@OtherLanguage:	Chinese=1.0;none=
@Qualification:	TOEIC=590(2013);none=;none=
@Abroad:	none=;none=
@Reading:	3
@Writing:	2
@Listening:	2
@Speaking:	1
@JapaneseEssay:	4
@EnglishEssayEx:	3
@EnglishEssay:	2
@Difficulty:	
@EssayTraining:	3
@SelfEval:	2
@TopicEase:	4
@Topic:	sports
@Criterion:	4
@Proctor:	1
@Comments:	
@Date:	2013-12-17
@Version:	1.0
*JPN501:	What kind of sports do you like?
%NTV:	OK
%COM:	
*JPN501:	Do you like soccer, base ball or swimming?
%NTV:	Do you like soccer, baseball, or swimming?
%COM:	"Baseball" is one word. In lists with three or more items, put a comma between each item, including one before the final "and".
*JPN501:	There are many and variety sports around the world.
%NTV:	There are many varieties of sports around the world.
%COM:	
*JPN501:	A country has some traditional sports.
%NTV:	Most countries have some traditional sports.
%COM:	
*JPN501:	Of course, there are some traditional sports in Japan.
%NTV:	OK
%COM:	
*JPN501:	They are called "BUDO".
%NTV:	They are called budo.
%COM:	This word does not require capitalization.
*JPN501:	BUDO are JYUDO, KENDO, KYUDO and so on.
%NTV:	Budo include judo, kendo, kyudo, and so on.
%COM:	These words do not require capitalization.
*JPN501:	If you play BUDO, there is an important thing that you must remember.
%NTV:	If you play budo, there is one important thing you must remember. 
%COM:	
*JPN501:	It is "REI".
%NTV:	It is rei.
%COM:	
%par:

中略

*JPN501:	We Japanese should be proud of and teach more many people around the world about this traditional sports "BUDO".
%NTV:	We Japanese should be proud of and teach many more people around the world about our traditional sports, budo.
%COM:	
@End

これはこれで有益な情報なのだが、本文を構文解析等をしたい場合は、これらの情報は不要になる。必要な情報は、

*JPN501:	What kind of sports do you like?
*JPN501:	Do you like soccer, base ball or swimming?
*JPN501:	There are many and variety sports around the world.

といった*JPN501:の本文の部分だけ。後は不要。
というわけで、CHAT形式で収録されている本文をplainテキストにする手順を備忘録として書いておく。

まず、1行目から28行目までは、執筆者のDemographic情報が収録されているので、1行目から28行目までは削除してしまえば良い。いろいろ方法があると思うが、いつもsedコマンドを使用しているので、sedコマンドで行数指定して、削除する。一つのファイルだけじゃなく、ディレクトリ内の全てに対して処理したいので、forループも使う。Terminal上で、以下を実行すると1行目から28行目まですべて削除できる。

Windowsの場合

for file in *.txt; do sed -Ei "1,28d" ${file}; done

Macの場合

for file in *.txt; do sed -Ei "" "1,28d" ${file}; done

次に、本文前のID(*JPN501の部分)や、%NTV、%COMといった、英語母語話者の添削文やコメント、最後の@Endの部分が不要。これも消したい。これは、正規表現を使用して、置換してやれば良い。

perl -pi -e 's/(%NTV:\t.+|%COM:\t.+|%COM:|\@End|\*JPN\d\d\d:\t)//g' *.txt

こうすることで、不要な情報が削除されるが、削除された場所は、空白行として残るので、その空白行も消したい。

perl -pi -e 's/^\n//g' *.txt

これで、空白行が消える。
最後に不要なのは、%par:の部分。これは、段落の改行を意味するので、最後に消さなければならない。%NTVなどと一緒に削除してしまうと、段落の部分がなくなってしまうので、段落は残しておきたい。ので、最後に%par:の部分を削除して、空白行として置き換えることで、段落を維持する。

perl -pi -e 's/%par://g' *.txt

これで、段落情報は維持したまま、不要な文字等をすべて削除し、本文だけのplainファイルが完成する。

テキストの任意の箇所に文字列挿入

博論に使うテキストデータの下処理の関係で、備忘録。

以下のようなCHAT形式のテキストファイルがあるとする。

@Begin
@Participants:	JAN0180
*JAN0180:	I do not agree with having broad knowledge of many academic subjects .
*JAN0180:	I think it is no use to learn them .
*JAN0180:	Have you ever use many academic subjects to work or live ?
%P
*JAN0180:	We learn something that is useless for us when we are students .
*JAN0180:	In fuct , I did not use knowledge that is history , biology and math to live or develope my ideas .
*JAN0180:	In addition , the thing I learned when I was a high school student is different from the things I am learning now in university .
%P
@End

やりたいことは、@Begin、@Participants、%P、@End以外の改行直後に文字列を挿入したい。例えば、今回の例だと%INSERT:という文字列を挿入したい。
この例だと、「*JAN0180:本文」という文字列の改行直後にそれぞれ%INSERTを挿入していきたい。

@Begin
@Participants:	JAN0180
*JAN0180:	I do not agree with having broad knowledge of many academic subjects .
%INSERT:
*JAN0180:	I think it is no use to learn them .
%INSERT:
*JAN0180:	Have you ever use many academic subjects to work or live ?
%INSERT:
%P
*JAN0180:	We learn something that is useless for us when we are students .
%INSERT:
*JAN0180:	In fuct , I did not use knowledge that is history , biology and math to live or develope my ideas .
%INSERT:
*JAN0180:	In addition , the thing I learned when I was a high school student is different from the things I am learning now in university .
%INSERT:
%P
@End

こんな感じで。

処理したいテキストの本文の前には、「*JAN0180」という形で、アスタリスクがついているので、そこを指定して置換すればうまくいくはず。

置換前:(\*.+\n)
置換後:$1%INSERT:\t\n

単に、アスタリスクを指定して、後方参照するだけでうまく行く。
テストでうまく行ったので、後は対象のディレクトリ内すべてのテキストファイルに対して同じ処理をする。Terminal上でperlの1行スクリプトを書いて置換してやれば一瞬で500ファイルの置換が終わった。

perl -pi -e 's/(\*.+\n)/$1%INSERT:\t\n/g' *.txt

こんな感じで。