テキスト内のパラグラフ数と各パラグラフ内の文数を算出

表題の通り、あるコーパス内に収録されているテキストファイルのパラグラフ数と各パラグラフ内に含まれている文の数を算出する必要が生じたので、いつものようにpythonで処理スクリプトを書いてみた。
テキストファイルは、一文一行で収録されており、パラグラフは空白行で区別されている。したがって、テキストファイル内の空白行の数を数え、そこに1を足すことで、そのテキストファイルのパラグラフの数が算出できる。
たとえば、以下のようなテキストファイル(コーパスから部分抜粋)の場合は、空白行が2つなので、パラグラフの数は、3つとなる。

Today the trend of teaching English to small children is spreading in Japan.
Do you think whether it is good or not?
There are some merits and demerits in this point.
Begin with considering three merits of teaching English early.

First, it is often said that it is better to learning foreign language in early stage.
Children have a good ability to learn anything and the older they get, the less they can memorize things.
Regard with learning language, this is most prominent.
In addition that, English is easy language in the world.
So, it is good for children to learn English as early as they can.

Second, the needs of English has become more strong.
These days, a lot of foreigners have come to Japan and the chance for Japanese children to communicate with them is increasing.
If you meet a classmate who can only speak English and you want to make friends with him, you must speak English.

次に、各パラグラフに含まれている文の数を算出したい。
1パラグラフ目の文の数は、最初の空白行が何行目にあるか特定し(上の例の場合だと5行目)、そこから1を引けば1パラグラフ目に含まれている文の数が算出できる。
2パラグラフ目の文の数も同様に、2つ目の空白行から1つ目の空白行の差を出し、そこから1を引けば、2つ目のパラグラフの文の数が算出できる。上の例の場合だと、2つ目の空白行が11行目にあるので、11から5の差を出し、そこから1を引けば5という数字が得られので、2パラグラフ目の文の数は5ということになる。

というわけで、パラグラフの数と各パラグラフに含まれている文の数を算出するプログラムをpythonで書いてみた。パラグラフが一つしかないテキストの場合は、単に文の総数を記録して、パラグラフの数は1とした。

#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys,re,glob,numpy as np

argvs = sys.argv
argc = len(argvs)

fw = open(sys.argv[2] + ".csv", 'w')
fw.write("Filename" + "," + "Sum of Sentences" + "," + "Sum of Paragraphs" + "," + "S in 1st" + ","+ "S in 2nd" + ","+ "S in 3rd" + ","+ "S in 4th" + ","+ "S in 5th" + ","+ "S in 6th" + ","+ "S in 7th" + ","+ "S in 8th" + "," + "S in 9th" + "," + "S in 10th" + "," + "S in 11th" + ","+ "S in 12th" + ","+ "S in 13th" + ","+ "S in 14th" + ","+ "S in 15th" + ","+ "S in 16th" + ","+ "S in 17th" + ","+ "S in 18th" + "," + "S in 19th" + "," + "S in 20th" + "\n")

for fn in sorted(glob.glob(sys.argv[1] + "*.txt")):
	filename = fn.split('/')[-1]
	with open(fn, 'r') as fn:
		files = fn.read()
		div = len(re.findall(r'^\n', files, flags = re.MULTILINE))
		if div != 0:
			regrep = re.sub('^\n', '%P\n', files, flags = re.MULTILINE).split("\n")
			numP = np.array([i for i, x in enumerate(regrep) if x == "%P"])
			numPS = [j for j in numP][0]
			numSEP = np.diff(numP) - 1
			L = [k for k in numSEP]
			L.insert(0, numPS)
			sumsens = sum(L)
			L.insert(0, div)
			result = ",".join(repr(l) for l in L)
			fw.write(filename + "," + str(sumsens) + "," + str(result) + "\n")
		else:
			sumsens2 = len([m.strip() for m in files.split("\n")])
			fw.write(filename + "," + str(sumsens2) + "," + str(1) + "," + str(sumsens2) + "\n")
fw.close()

もう少し短く簡潔に書けそうだが、ほしい数字を算出できたので良しとする。

引数は、対象フォルダ名と結果出力先(csv形式)の2つ。
出力形式は、ファイル名、総文数、総パラグラフ数、1パラ目の文数、2パラ目の文数、3パラ目の文数、、、という具合(見出しがちょっと雑。。だけどわかればいいのでとりあえず気にしないことにして、とりあえず20段落目までは見出しをつけた)。

出力例は、以下の通り。

Filename,Sum of Sentences,Sum of Paragraphs,S in 1st,S in 2nd,S in 3rd,S in 4th,S in 5th,S in 6th,S in 7th,S in 8th,S in 9th,S in 10th,S in 11th,S in 12th,S in 13th,S in 14th,S in 15th,S in 16th,S in 17th,S in 18th,S in 19th,S in 20th
JPN501.txt,25,4,9,5,3,8
JPN502.txt,29,5,7,10,6,0,6
JPN503.txt,8,2,4,4
JPN504.txt,23,4,8,4,7,4
JPN505.txt,19,2,13,6
JPN506.txt,18,4,1,3,6,8
JPN507.txt,24,4,1,10,4,9
JPN508.txt,16,4,1,7,4,4
JPN509.txt,18,5,1,4,3,4,6
JPN510.txt,1,1,1
JPN511.txt,25,8,1,4,5,3,3,1,4,4
JPN512.txt,22,4,1,4,8,9
JPN513.txt,30,8,1,1,10,4,1,3,6,4
JPN514.txt,6,2,1,5
JPN515.txt,22,5,1,4,8,4,5
JPN516.txt,18,4,1,3,8,6
JPN517.txt,29,7,1,3,7,6,4,4,4
JPN518.txt,24,5,1,7,5,7,4
JPN519.txt,27,4,1,8,9,9
JPN520.txt,19,4,1,4,8,6

こんな感じで、まぁ、ええか。
平成元年生まれなので、平成最後の日にブログを書こうと急いでプログラムを作成したので、結構雑になってしまったがひとまず思い取りに走ったので一安心。
令和でもマイペースにブログを更新していくことにしよう。。

ERRANTを使ってみる

前回の投稿では、GitHubで配布されているERRANTを使うための下準備について書いたが、下準備ができたので実際に使ってみる。Python3系とpipがインストールされている前提で話を進める。

ERRANTのreadmeを読むと、Python3spaCynltkが必要らしい。新たにインストールが必要なのはspaCynltkの2つ。spaCyとnltkはTerminal上からインストールできるので、以下のコマンドでサクッとインストールする。

pip3 install -U spacy==1.9.0
python3 -m spacy download en
pip3 install -U nltk

上の方法でエラーが出る場合は、AnacondaでPython3系をインストールして、以下を実行すれば回避できるはず。

sudo conda install -c conda-forge spacy
sudo python3 -m spacy download en
pip3 install -U nltk

ERRANTでは学習者の作文とそれを添削した母語話者の作文を使用する。前回の投稿で、NICERから学習者の作文と母語話者の添削文をそれぞれ抜き出して、別々のファイルに保存する方法を紹介したので、今回はそれらのファイルがある前提で話を進める。前回の例では、JPN513.txtから、学習者だけの作文が収録されたJPN513_ori.txtと母語話者の添削文だけが収録されたJPN513_cor.txtを作成したので、これらのファイルを使用する。母語話者の添削文は最低一つあれば大丈夫なので、今回は一つだけ使う。
Terminal上で以下を実行する。

python3 parallel_to_m2.py -orig JPN513_ori.txt -cor JPN_cor.txt -out result.m2

結果ファイルのresult.m2を開くと、m2フォーマットで以下のような感じで結果が収録されている。

S education of teachers
A 0 1|||R:ORTH|||Education|||REQUIRED|||-NONE-|||0
A 2 3|||R:ORTH|||Teachers|||REQUIRED|||-NONE-|||0

S Let's think about education of teachers in Japan.
A 3 3|||M:DET|||the|||REQUIRED|||-NONE-|||0

S Firstly, in Japan how do people become teachers?
A 0 1|||R:VERB|||First,|||REQUIRED|||-NONE-|||0
A 2 3|||R:NOUN|||Japan,|||REQUIRED|||-NONE-|||0

S I know two ways.
A -1 -1|||noop|||-NONE-|||REQUIRED|||-NONE-|||0

S One is to enter school for growing teachers.
A 4 8|||R:OTHER|||a teacher's college.|||REQUIRED|||-NONE-|||0

中略

S I think some teachers should teach subjects they are good at, some teachers support students in the aspects of health of hearts,  and others support schools in the aspects of the economy.
A 13 13|||M:VERB:TENSE|||should|||REQUIRED|||-NONE-|||0
A 15 16|||R:PREP|||with|||REQUIRED|||-NONE-|||0
A 16 22|||R:OTHER|||their emotional health,|||REQUIRED|||-NONE-|||0
A 24 24|||M:VERB:TENSE|||should|||REQUIRED|||-NONE-|||0
A 25 27|||U:OTHER||||||REQUIRED|||-NONE-|||0
A 28 29|||R:OTHER|||actual running|||REQUIRED|||-NONE-|||0
A 31 32|||R:NOUN|||school.|||REQUIRED|||-NONE-|||0

この中でほしい情報は、エラーの種類が書いてある部分。例えば、2行目の

A 0 1|||R:ORTH|||Education|||REQUIRED|||-NONE-|||0

「R:ORTH」の部分がほしい(R:ORTHは、Orthographyのエラー)。結果ファイルを1行ずつ読み込んでいって、正規表現を使ってエラーの種類が書いてある箇所のみを抜き出せばほしい部分は手に入るので、その処理を行うPythonのスクリプトを書いた。

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

import sys,re,glob

argvs = sys.argv
argc = len(argvs)

fw_result = open(sys.argv[2] + ".csv", 'w')
fw_result.write("Filename" + "," + "ErrorType" + "," + "Frequency" + "\n")
for fn in sorted(glob.glob(sys.argv[1] + "*.m2")):
	fn2 = fn.strip(sys.argv[1] + ".m2")
	with open(fn, 'r') as fn:
		files = fn.readlines()
	for i in files:
		err = re.findall(r'(^A.+)', i)
		if len(err) != 0 and '|||noop|||' not in err[0]:
			err_cou = len(err)
			err_typ = re.findall(r'(?:^A.+\|+)(\w:\w+:?\w+)', err[0])
			err.append(err_typ)
			err.append(err_cou)
			fw_result.write(str(fn2) + "," + str(err[1]) + "," + str(err[2]) + "\n")
fw_result.close()

結果の出力は、csv形式。

Filename,ErrorType,Frequency
JPN513,['R:ORTH'],1
JPN513,['R:ORTH'],1
JPN513,['M:DET'],1
JPN513,['R:VERB'],1
JPN513,['R:NOUN'],1
JPN513,['R:OTHER'],1
JPN513,['R:OTHER'],1

中略

JPN513,['M:VERB:TENSE'],1
JPN513,['R:PREP'],1
JPN513,['R:OTHER'],1
JPN513,['M:VERB:TENSE'],1
JPN513,['U:OTHER'],1
JPN513,['R:OTHER'],1
JPN513,['R:NOUN'],1

こんな感じでcsv形式で出力されるので、これをExcelに貼り付けてPivotTableを使えばそれぞれのエラータイプごとの数が算出できる、、と思ったが、一々PivotTableを使うのは面倒なので、Pythonのスクリプトの中でファイルごとにエラータイプの数を数えて、それをcsvとして出力するスクリプトを書いてみた。

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

import os,sys,re,glob,collections,pandas

argvs = sys.argv
argc = len(argvs)

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

#カウント処理
for fn in sorted(glob.glob(sys.argv[1] + "*.m2")):
	err_typ_list = []
	fn2 = fn.strip(sys.argv[1] + ".m2")
	with open(fn, 'r') as fn:
		files = fn.readlines()
	for i in files:
		err_typ = re.findall(r'(?:^A.+\|+)(\w:\w+:?\w+)', i)
		err_typ_list.append(err_typ)
	tmp = [j for j in err_typ_list if j]
	typ_tup = list(map(tuple, tmp))
	tup_fre = collections.Counter(typ_tup)
	result = pandas.DataFrame(tup_fre, index=[fn2]).sort_index(axis = 1)
	result.to_csv(sys.argv[2] + fn2 + ".csv")
	
#結果出力
csv_list = []
for k in sorted(glob.glob(sys.argv[2] + "*.csv")):
	csv_list.append(pandas.read_csv(k))
	os.remove(k)
fin = pandas.concat(csv_list, sort = False).fillna(0).rename(columns = {'Unnamed: 0': 'Filename'})
fin.to_csv(sys.argv[2] + sys.argv[3] + ".csv", index = False)

forループの処理が一ファイル終わるごとに、pandasでDataFrameを作っていき、最後にまとめた結果をcsvに書き出しという処理をやりたかったが、うまくできなかったので、ファイルを一回処理するごとにそのファイルの結果をcsvに出力して、処理が最後まで終わればcsvファイルを結合し、一ファイルごとのcsvファイルを削除してまとめのcsvを出力するという手順になった。
使用する引数は、m2ファイルが入っているディレクトリ名、結果出力先のディレクトリ名、結果出力先のファイル名の3つ。

python3 error_detection.py m2files/ result_dir/ result

こんな感じでやればサクッと結果が出力される。試しに、NICERに収録されているJPN513からJPN520までの学習者の作文と母語話者の作文を抽出して、ERRANTを使用し、エラーの数をタイプ別に数えてみた。
結果は、

Filename,M:DET,M:NOUN,M:OTHER,M:PREP,M:VERB:TENSE,R:ADJ,R:DET,R:MORPH,R:NOUN,R:NOUN:NUM,R:ORTH,R:OTHER,R:PREP,R:PRON,R:VERB,R:VERB:FORM,R:VERB:SVA,R:VERB:TENSE,U:DET,U:NOUN,U:OTHER,U:VERB,M:ADJ,M:VERB,M:VERB:FORM,R:PART,U:VERB:TENSE,U:PREP,R:ADV,U:VERB:FORM,M:ADV,R:WO,U:PRON,M:CONJ,R:CONJ,U:ADV
JPN513,4,1.0,3.0,2.0,2.0,1.0,3.0,1.0,6,2.0,2,23.0,1,1.0,4.0,1.0,5.0,1.0,1.0,1.0,1.0,1.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0
JPN514,2,0.0,2.0,0.0,0.0,0.0,0.0,0.0,1,0.0,3,0.0,1,0.0,1.0,0.0,1.0,0.0,0.0,0.0,1.0,1.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0
JPN515,8,0.0,1.0,1.0,0.0,2.0,2.0,1.0,3,2.0,2,15.0,3,0.0,0.0,0.0,1.0,1.0,1.0,1.0,2.0,0.0,1.0,1.0,1.0,1.0,1.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0
JPN516,6,0.0,5.0,2.0,0.0,3.0,1.0,0.0,12,1.0,1,58.0,1,2.0,12.0,1.0,1.0,1.0,0.0,0.0,2.0,1.0,0.0,1.0,0.0,1.0,1.0,1.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0
JPN517,7,0.0,0.0,3.0,1.0,0.0,0.0,0.0,3,3.0,4,4.0,6,3.0,1.0,1.0,1.0,4.0,1.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,1.0,0.0,0.0,0.0,0.0,0.0,0.0
JPN518,8,0.0,2.0,1.0,0.0,1.0,2.0,0.0,2,4.0,5,14.0,1,1.0,3.0,1.0,0.0,0.0,3.0,1.0,1.0,2.0,0.0,1.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,1.0,1.0,0.0,0.0,0.0
JPN519,5,1.0,0.0,2.0,0.0,0.0,8.0,0.0,7,5.0,1,25.0,5,1.0,9.0,1.0,4.0,4.0,8.0,0.0,3.0,2.0,1.0,0.0,0.0,1.0,0.0,5.0,1.0,0.0,1.0,0.0,0.0,0.0,0.0,0.0
JPN520,1,1.0,2.0,2.0,0.0,0.0,1.0,1.0,5,0.0,1,26.0,1,5.0,6.0,2.0,3.0,1.0,4.0,1.0,3.0,1.0,0.0,2.0,0.0,0.0,0.0,2.0,0.0,1.0,2.0,0.0,0.0,1.0,2.0,1.0

こんな感じ。csvファイルに出力されるので、分析もしやすい。
前回問題になった母語話者の添削文「NG」については、「R:OTHER」に分類されるようで、readmeを見ると「R:OTHER」は、

Edits that are not captured by any rules are classified as OTHER. They are typically edits such as [*at* (PREP) -> *the* (DET)], which have perhaps been improperly aligned, or else multi-token edits such as [*at his best* -> *well*] which are much more semantic in nature.

とのことなので、そのままでもいいか。。
エラーコードの詳細は、Bryant et al. (2017)を参照。エラーコードの先頭には、M、R、Uという記号がついているが、それぞれ、Missing、Replacement、Unnecessaryの意味。

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")
	fw_ori = open(sys.argv[1] + fn2 + "_ori.txt", 'w')
	fw_cor = open(sys.argv[1] + fn2 + "_cor.txt", 'w')
	with open(fn, 'r') as fn3:
		files = fn3.readlines()
	for i in files:
		original = re.findall(r'(?:\*JPN\d\d\d:\t)(\w.+\n)', i, re.IGNORECASE)
		if len(original) != 0:
			result_ori = original[0]
			fw_ori.write(result_ori)
		correct = re.findall(r'(?:%NTV:\t)(\w.+\n)', i, re.IGNORECASE)
		if len(correct) != 0:
			cor_rep = [j.replace('OK\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ファイルが完成する。