音声、文字、画像認識

1.文字認識

(1)形態素解析

形態素(morpheme)は、最小の要素。単語よりもさらに細かく分割されている。
形態素解析(Morphological Analysis)では、文章を、その最小の品詞に分ける。
JanomeというPython用の形態素解析器を使う。
❶Janomeインストール
Google colabの場合、まずは以下を実行する。1回だけでいい。

!pip install janome

❷形態素解析の実行
tokenize関数を使って単語に分割する。
tokenizeはトークン化する、という意味。
s(sentense)に入れた文字を解析する。

from janome.tokenizer import Tokenizer
t = Tokenizer()
s = '吾輩は猫である。名前はまだない。'
for word in t.tokenize(s):
    print(word)

結果は以下である。

吾輩	名詞,代名詞,一般,*,*,*,吾輩,ワガハイ,ワガハイ
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
猫	名詞,一般,*,*,*,*,猫,ネコ,ネコ
で	助動詞,*,*,*,特殊・ダ,連用形,だ,デ,デ
ある	助動詞,*,*,*,五段・ラ行アル,基本形,ある,アル,アル
。	記号,句点,*,*,*,*,。,。,。
名前	名詞,一般,*,*,*,*,名前,ナマエ,ナマエ
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
まだ	副詞,助詞類接続,*,*,*,*,まだ,マダ,マダ
ない	形容詞,自立,*,*,形容詞・アウオ段,基本形,ない,ナイ,ナイ
。	記号,句点,*,*,*,*,。,。,。

❸必要な要素だけ抜き出す
たとえば、以下にすると、単語と読みだけを表示する。

    print(word.surface,word.reading)

結果はこんな感じ。

吾輩 ワガハイ
は ハ
猫 ネコ
で デ
ある アル
。 。
名前 ナマエ
は ハ
まだ マダ
ない ナイ
。 。
(2)テキスト解析

・たとえば、予測する際に、SNSの情報も加味したい。だが、テキストを機械学習はできないので、テキストをベクトル表現(数字)に変換する。
・その方法だが、まずはJanomeなどによる単語分割が必要。
・IDや記号の場合は、そのまま数字ではんく、one-hotベクトルも活用する。
・コサイン類似度で文字列の関連度を予測する。

■トピック分析
・tf-idf法で頻出単語を明らかにする。
・tf-idf法は、単に頻出単語だけで分析するわけではない。「私」「的」「こと」などのどの文書でも単語として頻出のものは対象にしない。
・出現回数が1回などのものは処理対処外とする。たとえば出現回数を1万回で切るなどすることで、学習効率を改善できるようにする。

■類似度分析
・Word2vec
https://qiita.com/yoppe/items/512c7c072d08c64afa7e

また、たとえば、ある文章があって、その中に、(政府、法律、国会)などの言葉が何回登場するかという値(5,10,2)などの数字データにすれば、その文章に対する、国会や政府との関連度合いを数字化することができる。Word2vecはそんなことができると思う。

■BERT
Googleが発表した汎用モデル。Word Embedding

(3)形態素解析を使い、ワードクラウドの生成

・いろいろなサイトを参考にさせていただいた。
・言葉のリストをa.csvとして、GoogleColabにアップロードしておく
janomeをインストール。1回だけでいいはず

!pip install janome

ここから

import sys
import csv
import collections

if 'google.colab' in sys.modules:
#フォントをインストール
    !apt-get -y install fonts-ipafont-gothic
from janome.tokenizer import Tokenizer
t = Tokenizer()
#単語リストを作成する
list=[]
with open('text/a.csv') as file1:
    for data in file1:
        for word in t.tokenize(data):
#part_of_speechには、品詞以外の言葉もあるので、カンマでスプリットした先頭だけを取得し、品詞のみにする
          if word.part_of_speech.split(',')[0] in ['名詞','形容詞','副詞','感動詞']:
            list.append(word.surface)
#単語リストを表示
print(list)
x = collections.Counter(list)

%matplotlib inline
from wordcloud import WordCloud
import matplotlib.pyplot as plt

result = WordCloud(background_color="white",font_path='/usr/share/fonts/truetype/fonts-japanese-gothic.ttf',width=800,height=640).generate_from_frequencies(x)

fig = plt.figure(figsize=(12,8))
plt.imshow(result)
plt.axis("off")
fig.tight_layout() 
#ファイルに保存
plt.savefig('wordcloud.png') 

ネットワークスペシャリスト試験の合格体験談でやってみると、こんな感じ。雰囲気はわかるが、イマイチな感じだ。
f:id:seeeko:20210121153327p:plain

2.音声認識

・アナログ音は連続値なので、それを離散化してデジタル化する。具体的にはサンプリングして量子化する。
・拡張子wavは非圧縮。mp3は圧縮していて、非可逆。
・高い音は周波数が高い。音を1オクターブ上げると、周波数は2倍になる。
・amplitude(振幅)
・音量について
音量は相対的なもの。なぜなら同じ音源ファイルでも、再生する機械の音量を上げれば音が大きくなる。相対的な音の大きさを表すのがdB(デシベル)。具体的には20×log10が使われる。
人間が聞こえる最小音は20μPaとされている。
A特性やC特性などの基準をもとに、音(騒音)の大きさを図る。
・波は振幅、位相、周波数の要素がある。人間は、位相の情報は理解できない。
周波数が音の高さで、振幅が大きさと思って、とりあえずはいいだろう。

(1)言葉の整理

❶チャネル数
モノラルなら 1 、ステレオなら 2
ステレオの場合、イヤホンで両耳から聞こえる。
❷サンプリングレート(単位:Hz,=回/秒?)
1秒あたりのサンプリング回数。サンプリング=標本化と考えていいだろう。
情報処理試験を参考にすると、標本化定理(サンプリング定理)により、電話のアナログ周波数(4kHz)の少なくとも2倍で標本化する必要がある。4kHzというのは、1秒間に4k(=4000)回振動するとい言う意味である。よって、2倍の1秒間に8k(=8000)回標本化する。
人間の耳だと、最低16kHzは欲しいらしく、人間が聞こえる能力を考えると、48kHzであれば十分らしい。ちなみに、CDは44.1kHzと決まっている。mp3も44.1kHzが多い。
高い音は、高周波であるが、サンプリングレートが低いと、高い音が拾えなくなってしまう。
❸ビット深度 (サンプルサイズとも言う?) (単位:バイト or bit) 
音声データの1サンプルあたりのサイズ。量子化のときに、どのサイズで量子化するか。
情報処理試験だと、「音声信号を8ビットでディジタル符号化」とあるので、1バイト。一般的には2バイト(16bit)が多いだろう。
2の16乗だと、65536階調。2の8乗だと256階調しかない。うまく説明できないが、誤差がノイズになって聞こえることになる。
❹フレーム
フレーム数=時間(秒)×サンプリングレート 
※1フレーム当たりのデータサイズなどは考慮しない、単純なフレーム数だと思う。ただ、フレームという言葉が少し理解しにくい。パケットなんだろうけど。
❺ビットレート
1秒あたりのデータ量
ビットレート = サンプリングレート × ビット深度 × チャネル数(ステレオなら2)

(❻フレームレート(単位:fps))
よくあるテレビや動画は、1秒間に30フレーム(正しくは29.97)の表示回数。
画像のきめ細やかさがわかる。
音声と動画では考え方が別ではないかと思うので、要確認。

(2)波を書いてみる

❶正弦定理
正弦定理というのがあったと思うが、単純に、x軸に角度α、y軸にsinαを取る。
ちなみに、sin0=0, sin(π/2)=1, sin(π)=0, sin(2π)=0 である。
グラフを書いてみる。

import matplotlib.pyplot as plt
import numpy as np
x = np.arange(0,2*np.pi,0.1) #サンプル間隔を0.1に指定。なめらかな曲線に。
print(x) # ==>[0.  0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9・・・
plt.plot(x,np.sin(x))
plt.show()

f:id:seeeko:20201125170841p:plain
❷周波数1、高さ1のグラフ
周波数が1ということは、1秒間で波を1回打つということ。また、波の高さは1とする。
先の正弦定理のグラフは、波長が2πである。なので、x軸を2πで割ればいい。このとき、サンプリングするxを2πで割ってしまうと、角度が変な値になってしまう。なので、プロットするx軸のxの値を2πで割る。

import matplotlib.pyplot as plt
import numpy as np
x = np.arange(0,2*np.pi,0.1) 
plt.plot(x/(2*np.pi),np.sin(x)) #x軸を2πで割る
plt.show()

❸別の書き方
上の❷の書き方は、正しくないと思って、以下にしてみた。

import matplotlib.pyplot as plt
import numpy as np
x = np.arange(0,1,0.01)  #x軸は、単純に0から1にした。サンプリング周期は、0.01なので、1秒間に100回(つまり100Hz)
plt.plot(x,np.sin(x*2*np.pi)) 
plt.show()

❹周波数とサンプリングレートも加える
周波数s=5、サンプリングレートsam_rate=100とする。

import matplotlib.pyplot as plt
import numpy as np
s = 5 #単純に周波数を5Hzとする。つまり、1秒間に5回の波を打つ
sam_rate=100
x = np.arange(0,1,1/sam_rate) 
plt.plot(x,np.sin(x*s*2*np.pi)) 
plt.show()

❺標本化定理の証明
サンプリングレートを変えてみる。標本化定理では、周波数の2倍が無いと、正しく取れないとあった。
sam_rateを変化させながら、グラフの変化をみてみよう。

import matplotlib.pyplot as plt
import numpy as np
s = 5 #単純に周波数を5Hzとする。つまり、1秒間に5回の波を打つ
sam_rate=100
x = np.arange(0,1,1/sam_rate) 
plt.plot(x,np.sin(x*s*2*np.pi)) 
plt.show()

グラフの整形がうまくいっていないので、直す場合は以下を参照いただきたい。
https://openbook4.me/sections/1396
以下のように、サンプリングレートが下がると、グラフがかなり厳しい。正直、2倍(=10)だと少ない気がする。
f:id:seeeko:20201127200648p:plain

(1)WAVEファイルの読み込み

waveモジュールを使う。wavファイルを読み込んでみよう。

import wave  #waveモジュールをインポート
wavfile = wave.open('music.wav', 'rb')  #wavファイルの読み込み。rbはread(読み込み)モード。書込みはwbとする。

以下に書いたが、ファイルのオープンfile1 = open('file1.txt', 'r') などと同じで、最後に閉じる file1.close 必要がある。
https://python3.hatenadiary.com/entry/2020/10/10/082523#1%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%AA%E3%83%BC%E3%83%97%E3%83%B3%E3%81%A8%E3%82%AF%E3%83%AD%E3%83%BC%E3%82%BA

(2)音声ファイルのパラメータ

いろいろな情報を取得することができる。
詳しくは以下
https://docs.python.org/ja/3/library/wave.html

print(wavfile.getnchannels()) #モノラルなら 1 、ステレオなら 2
print(wavfile.getsampwidth()) #ビット深度。音声データの1サンプルあたりのサイズ(バイト数)
print(wavfile.getframerate()) #サンプリングレート。1秒あたりのサンプリング回数
print(wavfile.getnframes()) #フレーム数。サンプリングレート×時間 にもなると思う。
print(float(wavfile.getnframes())/ wavfile.getframerate()) # 時間=フレーム数/サンプリングレート
print(wavfile.getnchannels() * wavfile.getsampwidth() * wavfile.getnframes()) # ファイルサイズ=フレーム数×ビット深度×チャネル数(モノラルなら1)
(3)実際に取り込む

もう少し深く進める

%matplotlib inline

import wave
import numpy as np
import matplotlib.pyplot as plt
#音声ファイルmusic.wavを開く
wavfile = wave.open('music.wav', 'rb') 
#読み込んだデータをdataに入れる。wavfile.getnframes()はフレーム数なので、以下は全行を読み込むという意味
data = wavfile.readframes(wavfile.getnframes())
#バイナリデータなので、intに変換
data = np.frombuffer(data, dtype="int16") 

どんな値が入っているのか、それが興味がある。
WAVファイルは、RIFF(RIFF waveform Audio Format)という形式らしい。
大量のデータだとメモリがエラーになるので、フレーム数を指定してみた data = wavfile.readframes(500)

%matplotlib inline
import wave
import numpy as np
import matplotlib.pyplot as plt
wavfile = wave.open('music.wav', 'rb') 
data = wavfile.readframes(500)
data = np.frombuffer(data, dtype="int16") 
print(data)

すると、以下のように生データが見える。

[ 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  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  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  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  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  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  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  0  0  0  0  0  0  0  0  0  0  0  0  0
  0  0  0  0  0  0 -1  0  0  0  0  0  0  0  0  0 -1 -1  0  0  0  0  0  0
 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
 -1 -1 -1 -1 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2
 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2
 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -3 -2 -2 -2 -2 -2
 -3 -3 -2 -2 -3 -3 -3 -3 -2 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3
 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3
 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3
 -3 -3 -4 -3 -3 -3 -4 -3 -3 -3 -3 -3 -3 -3 -4 -4 -4 -4 -4 -4]
(4)波のグラフを描いてみる

Q.なぜ塗りつぶされるのか。波にならないの?
なぜ上下に出力される?

A.周波数が音の高さで、振幅は音の大きさと考えていいだろう。
音は、振動板が波を打って、音がでる。外部から受けた音が振動板を揺らし、このとき大きな音は大きく揺らす。その揺れをデータとして取得する。
なので、揺れて、伸びたり縮んだりするので、プラスもゼロもマイナスの値もとる。だから、amplitude(振幅)はプラスもゼロもマイナスも必ず通るので、図にすると塗りつぶされているように見える。

では、手順を解説する。
❶(参考)音声ファイルの録音
Windows10ではできないが、Windows8の場合、標準のサウンドレコーダをCMDから実行することで、wavで録音できる。
こんな感じ。今回は私が録音した「あ」という言葉である。ファイル名はa.wavとした。

SoundRecorder /FILE a.wav

❷Google colabに音声ファイルをUPloadする
以下にも書いたが、音声ファイル(.wavファイル)をGoogle colabにUploadする。
https://python3.hatenadiary.com/entry/2020/12/02/115753

❸音波形を描く
前半はすでに記載したプログラムであるが、音声に関する知識がないと、少し理解が難しいと思う。
とりあえず、コピペすれば動くだろう。

%matplotlib inline

import wave
import numpy as np
import matplotlib.pyplot as plt
wavfile = wave.open('a.wav', 'rb') 
data = wavfile.readframes(wavfile.getnframes())
data = np.frombuffer(data, dtype="int16") 
sam_rate=wavfile.getframerate()

#計算式は複雑であるが、8バイト×サンプルサイズだけ2の階乗をした値が、表現可能な階調で、その半分とする。
data = data / (2**(8 * wavfile.getsampwidth() ) / 2)

x = np.arange(0, len(data)/sam_rate, 1.0/sam_rate)

plt.xlabel("time")
plt.ylabel("amplitude")
plt.grid()
plt.plot(x,data)
plt.show()

以下が(私の)「あ」の声
f:id:seeeko:20201202133641p:plain
参考までに、以下が「い」の声。開始タイミングがずれたが、その点は無視してほしい。
f:id:seeeko:20201202133740p:plain

3.画像認識

(1)OpenCVを使った画像処理

Pythonでは、「OpenCV」という画像処理のライブラリが利用できる。
使い方はとても簡単。import cv2でインポートし、
cv2.imread()メソッドで画像を読み込む。
cv2.imwrite()で画像をファイルに保存

(2)モノクロ画像(黒と白)

255が白で、0が黒である。この2色だけ。
❶画像を読み込んで、出力
小さくて見えないだろうが、以下のカタカナの「ウ」という画像を作成した。photoshopにて、画像サイズは10px、10px
f:id:seeeko:20201221050957j:plain

%matplotlib inline
import matplotlib.pyplot as plt
import cv2

gazou = cv2.imread('u.bmp',0) #0を指定することで、モノクロを指定。これを指定しないと、gazou.shapeが3次元の(10, 10,3)になってしまった。
plt.imshow(gazou, 'gray') #grayを指定しないと、黄色と紫になってしまう。

print('画像サイズ(高さ、幅、色)=', gazou.shape) #==>  (10, 10) モノクロの場合は色は無し 
print(type(gazou)) #==><class 'numpy.ndarray'>
print(gazou)

f:id:seeeko:20201221052545p:plain

❷数字データから画像を出力
逆に、gazouに数字データを入れて、それを画像ファイルにすることも可能。
このとき、numpyのarrayなので、gazou = np.array([ という形で、カンマ区切りでデータを入れる。
一部だけであるが、以下のような感じ。

%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
gazou = np.array([
[255,255,255,255,255,0,255,255,255,255],
[255,255,255,255,255,0,255,255,255,255],
[0,0,0,0,0,0,0,0,0,0]])
plt.imshow(gazou, 'gray') #grayを指定しないと、黄色と紫になってしまう。

f:id:seeeko:20201221052739p:plain

(3)グレースケール(黒と白以外に、灰色がある)

255が白で、0が黒であるし、その間の灰色も数字で表される。8ビットなので0~255。
❶画像を読み込んで、出力

%matplotlib inline
import matplotlib.pyplot as plt
import cv2

gazou = cv2.imread('u2.bmp', 0) 
plt.imshow(gazou, 'gray') 

print('画像サイズ(高さ、幅、色)=', gazou.shape) 
print(type(gazou))
print(gazou[:1]) #1行目だけを表示

f:id:seeeko:20201221053119p:plain

(4)カラー

以下の花を読み込んでみる。
f:id:seeeko:20201221054754j:plain
❶表示してみよう

%matplotlib inline
import matplotlib.pyplot as plt
import cv2

gazou = cv2.imread('flower.jpg') 
gazou= cv2.cvtColor(gazou, cv2.COLOR_BGR2RGB) # そのままだと、BGRとなっていて、色がおかしい。RGBに変換。※なぜBGRになっているかは不明
plt.imshow(gazou) 

print('画像サイズ(高さ、幅、色)=', gazou.shape) #モノクロの場合は色は無し
print(type(gazou))
print(gazou[:1])

f:id:seeeko:20201221054828p:plain

❷カラー画像の加工
・色を抜いてみる。以下により、RとGをどちらも0にし、Bだけにする
gazou[:, :, (0, 1)] = 0

・色を逆にする。
255から引くと、ネガの画像になる。
gazou = np.uint8(255 * np.ones(gazou.shape) - gazou)

%matplotlib inline
import matplotlib.pyplot as plt
import cv2

gazou = cv2.imread('flower.jpg') 
gazou= cv2.cvtColor(gazou, cv2.COLOR_BGR2RGB) # そのままだと、BGRとなっていて、色がおかしい。RGBに変換。※なぜBGRになっているかは不明
gazou = np.uint8(255 * np.ones(gazou.shape) - gazou) 
plt.imshow(gazou) 

print('画像サイズ(高さ、幅、色)=', gazou.shape)
print(type(gazou))
print(gazou[:1])

f:id:seeeko:20201221055523p:plain
❸グレースケールにしてファイルに出力
cv2.imwrite('flower2.jpg', gazou)にて、ファイル名をflower2.jpgとして保存する。

%matplotlib inline
import matplotlib.pyplot as plt
import cv2

gazou = cv2.imread('flower.jpg') 
#グレースケールに変換。cv2.cvtColorを使い、変換する方法としてcv2.COLOR_RGB2GRAYを指定する。
gazou = cv2.cvtColor(gazou,cv2.COLOR_RGB2GRAY) 
plt.imshow(gazou, 'gray')

print('画像サイズ(高さ、幅、色)=', gazou.shape) #==> (1835, 1672) なので、2色になっている。
print(type(gazou))
print(gazou[:5])
#ファイルに保存。すると、グレースケールのファイルが保存される。
cv2.imwrite('flower2.jpg', gazou)

f:id:seeeko:20201221060437p:plain

4.高度な画像処理:顔認識

スマホやデジカメでも、顔の部分だけがフォーカスされる。これは、Viola-Jones法が使われる。ViolaさんとJonesさんが発見したからこのように言われる。
やり方は単純で、白と黒の領域を組み合わせたパターンと、顔が一致するかを確認する。顔であれば、目のあたりが黒く、その周りは白いなどの特徴がある。
ただ、そんな単純なやり方だから、精度はとても悪い。それを、数多くの方法で、パターンにマッチするかを判断して、精度を高める。アンサンブル学習(Ensemble Learning)というキーワードがあるはずだ。