Skip to content

Latest commit

 

History

History
831 lines (458 loc) · 15 KB

File metadata and controls

831 lines (458 loc) · 15 KB

第1章: 準備運動

Copyright (c) 2020 NLP100-MATLAB

clear

00. 文字列の逆順

文字列”stressed”の文字を逆に(末尾から先頭に向かって)並べた文字列を得よ.

まずは string 型を使う場合。reverse メソッドが使えそうです。

str = "stressed";
reverse(str)
ans = "desserts"

char 型を使った場合は各文字を要素とする行列っぽく操作できます。

str = 'stressed';
disp(fliplr(str))
desserts

char 型の場合、インデックスを使って逆順に並べ替えることも可。

str(end:-1:1)
ans = 'desserts'

01. 「パタトクカシーー」

「パタトクカシーー」という文字列の1,3,5,7文字目を取り出して連結した文字列を得よ.

これは string 型より char 型の方が使いやすそう。

str = 'パタトクカシーー';
disp(str([1,3,5,7]))
パトカー

02. 「パトカー」+「タクシー」=「パタトクカシーー」

「パトカー」+「タクシー」の文字を先頭から交互に連結して文字列「パタトクカシーー」を得よ.

これも char 型の方がよさそう。同じ文字数なので、行列操作でやってみましょう。

str1 = 'パトカー';
str2 = 'タクシー';
str = [str1; str2]
str = 
    'パトカー'
    'タクシー'

str(:)'
ans = 'パタトクカシーー'

1x4 と 1x4 の行列を連結させて、2x4 の行列を作った後に、コラムメジャーで並べ替えました。

03. 円周率

“Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.”という文を単語に分解し,各単語の(アルファベットの)文字数を先頭から出現順に並べたリストを作成せよ.

ここも string 型でやってみます。スペースとコンマで分解します。

str = "Now I need a drink, alcoholic of course, after " + ...
    "the heavy lectures involving quantum mechanics";
str = strsplit(str,{' ',','})'
str = 15x1 string    
"Now"          
"I"            
"need"         
"a"            
"drink"        
"alcoholic"    
"of"           
"course"       
"after"        
"the"          

文字数は strlength メソッドですね。

numchars = strlength(str)'
numchars = 1x15    
     3     1     4     1     5     9     2     6     5     3     5     8     9     7     9

1x15 の数値ベクトルが出てきました。ちょっと見づらいので、文字列に変えて連結してみます。delimiter (区切り文字)に "" を指定することで間に空白が入らず文字連結。

join(string(numchars),"")
ans = "314159265358979"

04. 元素記号

“Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can.”という文を単語に分解し,1, 5, 6, 7, 8, 9, 15, 16, 19番目の単語は先頭の1文字,それ以外の単語は先頭に2文字を取り出し,取り出した文字列から単語の位置(先頭から何番目の単語か)への連想配列(辞書型もしくはマップ型)を作成せよ.

先頭の1文字、2文字を取り出すとなると char 型の方がやり易そう。strsplit 関数は同じように使えます。

str = ['Hi He Lied Because Boron Could Not Oxidize Fluorine '  ...
    'New Nations Might Also Sign Peace Security Clause. Arthur King Can'];
str = strsplit(str)'
str = 20x1 cell    
'Hi'          
'He'          
'Lied'        
'Because'     
'Boron'       
'Could'       
'Not'         
'Oxidize'     
'Fluorine'    
'New'         

分割しました。

先頭の1文字、2文字を取り出します。for ループで回してもいいですが、せっかくなので cellfun を使います。

idx1 = [1,5,6,7,8,9,15,16]'; % 1 文字を取り出す index
idx2 = true(length(str),1); idx2(idx1) = false; idx2 = find(idx2); % 2 文字を取り出す index
% これは以下と同じ・・
% idx2 = [2,3,4,10,11,12,13,14,17,18,19,20];

matchStr1 = cellfun(@(x) x(1), str(idx1), "UniformOutput", false)
matchStr1 = 8x1 cell    
'H'          
'B'          
'C'          
'N'          
'O'          
'F'          
'P'          
'S'          

matchStr2 = cellfun(@(x) x(1:2), str(idx2), "UniformOutput", false)
matchStr2 = 12x1 cell    
'He'         
'Li'         
'Be'         
'Ne'         
'Na'         
'Mi'         
'Al'         
'Si'         
'Cl'         
'Ar'         

matchStr1matchStr2 ともにセル配列になっている点は要注意。

少しかっこつけて正規表現を使うなら・・

matchStr1 = regexp(str(idx1),'([a-zA-Z]{1}).*','tokens'); % 最初の1文字
matchStr2 = regexp(str(idx2),'([a-zA-Z]{2}).*','tokens'); % 最初の2文字

こんな感じ。

連想配列(辞書型もしくはマップ型)とのことなので、containers.Map を使ってみます。

keySet = [string(matchStr1); string(matchStr2)];
valueSet = [idx1; idx2];
M = containers.Map(keySet,valueSet);
M('Be')
ans = 
     4

Be は4番目。

table 型の方が見やすいかな?

t = table(valueSet,keySet);
sortrows(t,'valueSet') % 出現順にソート
valueSet keySet
1 1 "H"
2 2 "He"
3 3 "Li"
4 4 "Be"
5 5 "B"
6 6 "C"
7 7 "N"
8 8 "O"
9 9 "F"
10 10 "Ne"
11 11 "Na"
12 12 "Mi"
13 13 "Al"
14 14 "Si"

05. n-gram

与えられたシーケンス(文字列やリストなど)からn-gramを作る関数を作成せよ.この関数を用い,”I am an NLPer”という文から単語bi-gram,文字bi-gramを得よ.

単語 bi-gram とは文章内で連続する単語2つ、文字 bi-gram とは各単語内での連続する文字2つと理解します。Text Analytics Toolbox だと単語 n-gram はこんな感じ。

if license('checkout','Text_Analytics_Toolbox') % Toolbox が使える場合実行されます。
    doc = tokenizedDocument("I am an NLPer"); % トークン化
    bag = bagOfNgrams(doc,'NgramLengths',2);
    bag.Ngrams
end
ans = 3x2 string    
"I"          "am"         
"am"         "an"         
"an"         "NLPer"      

tokenizedDocument を使わなくても単語単位で下と同じ処理をすればできますね。

次は文字 bi-gram 作ってみます。こんな関数を作ってみました。char ベクトルを入れたら、文字 n-gram を作って string 型配列で返す。文字数が少ないとそのまま返します。

function ngram = n_gram(word,NgramLength)
% word は char ベクトルを想定

N = length(word); % 文字の長さ

if N <= NgramLength
    ngram = string(word);
else
    ngram = strings(N-NgramLength,1);
    for ii = 1:N-NgramLength+1
        ngram(ii) = string(word(ii:ii+NgramLength-1));
    end
    ngram = unique(ngram); % 重複はいらない。
end

end

これを、cellfun を使って適用してみると・・

str = "I am an NLPer";
str = split(str);
str = cellstr(str);
cellfun(@(x) n_gram(x,2), str, 'UniformOutput', false)
1
1 'I'
2 'am'
3 'an'
4 4x1 string

4 つ目だけ見難いですが、中身をみると

ans{4}
ans = 4x1 string    
"LP"         
"NL"         
"Pe"         
"er"         

06. 集合

“paraparaparadise”と”paragraph”に含まれる文字bi-gramの集合を,それぞれ, XとYとして求め,XとYの和集合,積集合,差集合を求めよ.さらに,’se’というbi-gramがXおよびYに含まれるかどうかを調べよ.

X = n_gram('paraparaparadise',2)
X = 8x1 string    
"ad"         
"ap"         
"ar"         
"di"         
"is"         
"pa"         
"ra"         
"se"         

Y = n_gram('paragraph',2)
Y = 7x1 string    
"ag"         
"ap"         
"ar"         
"gr"         
"pa"         
"ph"         
"ra"         

bi-gram 取れていますね。

集合演算は https://jp.mathworks.com/help/matlab/set-operations.html に詳細がありますが、和集合(union)、積集合(intersect)そして差集合(setdiff)を使います。

union(X,Y)'
ans = 1x11 string    
"ad"         "ag"         "ap"         "ar"         "di"         "gr"         "is"         "pa"         "ph"         "ra"         "se"         

intersect(X,Y)'
ans = 1x4 string    
"ap"         "ar"         "pa"         "ra"         

setdiff(X,Y)'
ans = 1x4 string    
"ad"         "di"         "is"         "se"         

特定の文字が含まれているかどうかは contains も使えそうですが、ここは単純に = でやります。

どれか1つでも "se" と一致しますか? という論理変数を any で求めます。

any(X == "se") | any(Y == "se")
ans = 
   1

'se' は存在しています。

07. テンプレートによる文生成

引数x, y, zを受け取り「x時のyはz」という文字列を返す関数を実装せよ.さらに,x=12, y=”気温”, z=22.4として,実行結果を確認せよ.

function sentence = fromTemplate(x,y,z)

sentence = x + "時の" + y + "は" + z;

end

引数のデータ型として何が使われるか多少不安ではありますが・・・

fromTemplate(12,"気温",22.4)
ans = "12時の気温は22.4"

08. 暗号文

与えられた文字列の各文字を,以下の仕様で変換する関数cipherを実装せよ.

  • 英小文字ならば(219 - 文字コード)の文字に置換
  • その他の文字はそのまま出力

この関数を用い,英語のメッセージを暗号化・復号化せよ.

まずは char 型から始めてみます。

isstrprop で小文字がどこにあるかを調べられるので使ってみます。

str = 'We are the borg. Resistance is futile.';
idxLower = isstrprop(str,'lower');

小文字だけ (219 - 文字コード) に対応する文字に変換します。string 型に使える文字を置き換える replace を試します。

まずは置換する文字を見つけてきて、double に与えると・・文字コードになりますので、219 - 文字コードを計算して文字に戻してみます。

unique(str(idxLower))
ans = 'abcefghilnorstu'
219-double(ans)
ans = 1x15    
   122   121   120   118   117   116   115   114   111   109   108   105   104   103   102

char に与えると元に戻ります。

char(ans)
ans = 'zyxvutsromlihgf'

上の方法を使って置換してみると、こんな感じ。

str = 'We are the borg. Resistance is futile.';
idxLower = isstrprop(str,'lower');
str(idxLower) = char(219-double(str(idxLower)))
str = 'Wv ziv gsv ylit. Rvhrhgzmxv rh ufgrov.'

同じ処理で元に戻りますので、これを cipher として実装すればOK.

idxLower = isstrprop(str,'lower');
str(idxLower) = char(219-double(str(idxLower)))
str = 'We are the borg. Resistance is futile.'

09. Typoglycemia

スペースで区切られた単語列に対して,各単語の先頭と末尾の文字は残し,それ以外の文字の順序をランダムに並び替えるプログラムを作成せよ.ただし,長さが4以下の単語は並び替えないこととする.適当な英語の文(例えば”I couldn’t believe that I could actually understand what I was reading : the phenomenal power of the human mind .”)を与え,その実行結果を確認せよ.

char 型の方がやり易いかな。

sentence = ['I couldn''t believe that I could actually understand ' ...
    'what I was reading : the phenomenal power of the human mind .'];
words = strsplit(sentence)' % 各単語が1つずつセルに入ります。
words = 21x1 cell    
'I'             
'couldn't'      
'believe'       
'that'          
'I'             
'could'         
'actually'      
'understand'    
'what'          
'I'             

それぞれの単語について、5文字以上であれば間の文字をランダムに入れかえる処理をします。

こんな関数を作ってみました。各単語の入力は char 型とします。

function word2 = randomizeWord(word1)

if length(word1) <= 4
    word2 = word1;
else
    
    word2 = word1;
    tmp = word2(2:end-1);
    idx = randperm(length(tmp)); % tmp の文字数
    word2(2:end-1) = tmp(idx);
    
end
end

randperm については tmp の文字数を N とすると、1 から N までの数字をランダムに入れ替えた数列を返します。その数値をインデックスとすれば入れ替え可能!

ここでは cellfun を使ってセル内にある各単語にそれぞれ適用します。

tmp = cellfun(@randomizeWord, words, 'UniformOutput', false);
join(tmp) % 単語をつなぐ。
ans = 
    {'I cd'onlut beelive that I colud alacutly udntanesrd what I was ranideg : the pohenenaml power of the human mind .'}

できました。

Appendix: 関数

05. n-gram

    function ngram = n_gram(word,NgramLength)
        % word は char ベクトルを想定
        N = length(word); % 文字の長さ
        
        if N <= NgramLength
            ngram = word;
        else
            ngram = strings(N-NgramLength,1);
            for ii = 1:N-NgramLength+1
                ngram(ii) = word(ii:ii+NgramLength-1);
            end
            ngram = unique(ngram); % 重複はいらない。
        end
        
    end

07. テンプレートによる文生成

    function sentence = fromTemplate(x,y,z)
        
        sentence = x + "時の" + y + "は" + z;
        
    end

09. Typoglycemia

function word2 = randomizeWord(word1)

if length(word1) <= 4
    word2 = word1;
else
    
    word2 = word1;
    tmp = word2(2:end-1);
    idx = randperm(length(tmp));
    word2(2:end-1) = tmp(idx);
    
end
end