自分のツイートにラ抜き言葉が何個あるか確かめたくなったのでやっつけでアルゴリズムを考えてみました。形態素解析の知識や文法の知識はこのために仕入れた物が多いため、厳密とは言えないし正確さの保証もないです。今後の参考までに書き残す程度で。最後に使用した C# コードを載せてあります。

ラ抜き言葉とは

「上一段・下一段・カ変活用の動詞に可能の意の助動詞「られる」が付いたものから「ら」が脱落した語。「見られる」「食べられる」「来られる」に対する「見れる」「食べれる」「来れる」など」(『広辞苑』)

形態素解析

NMeCab という MeCab の .NET 移植版(良い表現が思いつかなかった。ラッパーではないみたい)を使った。辞書は UniDic を用いた。
この方法だと「来られない」が「きたられない」と解析されたり「来れる」が「カ行五段活用」として解析されたりして大変そうだったので、今回「来れる」は可能動詞として認め、ついでにカ変についてひとまずスルーすることにした。

フロー1

1.一つのテキストを形態素解析 

2.文末でない →(偽)1.に戻る
↓(真)
3.一段活用動詞 →(偽)2.に戻る
↓(真)
4.次の単語が「れる」の活用形 →(偽)2.に戻る
↓(真)
5.ラ抜き言葉判定

6.1.に戻る

フロー1 結果

擬音や誤字に対する誤検出が多い。

フロー2 

フロー1 の 3. の条件に活用形が未然形であることを加える

フロー2 結果

フロー1 とほぼ同じだが、ほんの少し誤検出が減った

フロー3(おまけ)

フロー2 の 3. に OR 条件として「カ変」を加える

フロー3 結果

「これる」は検出できたが、「来れる」は検出できなかった(おそらく可能動詞として辞書に登録されているため)

全体としてまとめ

感想

  • 単純なやり方でわりとサクッと検出できたのは良かった。C# 便利。ライブラリ偉大。
  • 「こ(来)られる」についてはもう「こ(来)れる」を可能動詞として正しく認めちゃって良い気がした

課題

  • 検出漏れを確かめていないので、正確さが不明
  • てれれれ~等の擬音(?)がラ抜き言葉として検出されてしまう
  • カ変検出は今後の課題
  • ラ抜き言葉以前の文法的な問題(「取れれる」など)がラ抜き言葉として検出できたが、これの扱いをどうするか

使用した C# コード

使ったもの

using LINQtoCSV;
using NMeCab;
using System.Collections.Generic;
namespace RanukiCheckerConsole
{
class Program
{
static void Main(string[] args)
{
var fileName = "tweets.csv";
var inputFileDescription = new CsvFileDescription
{
SeparatorChar = ',',
EnforceCsvColumnAttribute = true,
FirstLineHasColumnNames = true
};
var context = new CsvContext();
var tweets = context.Read<TweetsCsv>(fileName, inputFileDescription);
// RT を削除
// tweets = tweets.Where(s => !string.IsNullOrWhiteSpace(RetweetedStatusId));
var mecab = MeCabTagger.Create();
var ranukiList = new List<TweetsCsv>();
/*
* UniDic フォーマット
*
* 1.品詞大分類
* 2.品詞中分類
* 3.品詞小分類
* 4.品詞細分類
* 5.活用型
* 6.活用形
* 7.語彙素読み
* 8.語彙素(語彙素表記 + 語彙素細分類)
* 9.書字形出現形
* 10.発音形出現形
* 11.書字形基本形
* 12.発音形基本形
* 13.語種
*
*/
// 特徴(Feature)を分割した配列のどこに何が入るかを示す定数
// 出力フォーマットによって変える
const int InflectedForm = 4; //活用型
const int Conjugate = 5; // 活用形
const int LexemeReading = 6; // 語彙素読み
foreach (var tweet in tweets)
{
var node = mecab.ParseToNode(tweet.Text);
bool checkFlag = false;
while (node != null)
{
var features = node.Feature.Split(',');
if (checkFlag && features[LexemeReading] == "れる")
{
ranukiList.Add(tweet);
break;
}
if (features[InflectedForm] == "一段" && features[Conjugate] == "未然形")
{
checkFlag = true;
}
// カ変も判定しようとしてみる
// else if (features[InflectedForm].StartsWith("カ変") && features[Conjugate] == "未然形")
// {
// checkFlag = true;
// }
else
{
checkFlag = false;
}
node = node.Next;
}
checkFlag = false;
}
context.Write(ranukiList, "ranuki_tweets.csv");
}
}
}

using LINQtoCSV;
namespace RanukiCheckerConsole
{
public class TweetsCsv
{
[CsvColumn(FieldIndex = 1, Name = "tweet_id")]
public string Id { get; set; }
[CsvColumn(FieldIndex = 2, Name = "in_reply_to_status_id", CanBeNull = true)]
public string InReplyToStatusId { get; set; }
[CsvColumn(FieldIndex = 3, Name = "in_reply_to_user_id", CanBeNull = true)]
public string InReplyToUserId { get; set; }
[CsvColumn(FieldIndex = 4, Name = "timestamp", CanBeNull = true)]
public string TimeStamp { get; set; }
[CsvColumn(FieldIndex = 5, Name = "source", CanBeNull = true)]
public string Source { get; set; }
[CsvColumn(FieldIndex = 6, Name = "text", CanBeNull = true)]
public string Text { get; set; }
[CsvColumn(FieldIndex = 7, Name = "retweeted_status_id", CanBeNull = true)]
public string RetweetedStatusId { get; set; }
[CsvColumn(FieldIndex = 8, Name = "retweeted_status_user_id", CanBeNull = true)]
public string RetweetedStatusUserId { get; set; }
[CsvColumn(FieldIndex = 9, Name = "retweeted_status_timestamp", CanBeNull = true)]
public string RetweetedStatusUserIdTimeStamp { get; set; }
[CsvColumn(FieldIndex = 10, Name = "expanded_urls", CanBeNull = true)]
public string ExpandedUrls { get; set; }
}
}

view raw
TweetsCsv.cs
hosted with ❤ by GitHub

スポンサーリンク