京橋のバイオインフォマティシャンの日常

データ分析、コマンドライン、プログラミングについての技術資料・自己アップデート・悩み事などをまとめています。現在、DL勉強中。

R/rvestを使って、Google画像検索で画像サムネイルを取得して、機械学習・ディープラーニング用の4次元アレイデータに変換するTips

手作業をできるだけ抑えて、 機械学習・ディープラーニング(DL)用の画像データを手っ取り早く取得したい!!

ふと思いたち、、今回、Google画像検索の結果をスクレイピングして、 画像データ(サムネイル)を取得して、それらをR/Keras用の4次元アレイデータに変換するプログラムを作成してみた。

実務としては、 rvestパッケージとかEBImageパッケージとかを使うことで実装した。

主な用途としては、GANとかで使うDL学習用の画像データを収集するのを想定している。

パッケージの準備

#パッケージの読み込み
library(magrittr)
library(EBImage)
library(rvest)

rvestの使い方の基本は、以下の記事を参照のこと。

skume.net

skume.net

画像の取得・変換を行う関数の定義

今回、GoogleImage2Array関数とdisplay.4dArray関数を以下の通り定義した。

GoogleImage2Array関数のデフォルトのパラメータでは、 64x64ピクセル(wh=64)の画像に変換する。 また、画像をローカルに保存しない(Save=FALSE)。

#Google画像検索で取得した画像サムネイルから4次元アレイデータを作る関数
GoogleImage2Array <- function(Query, wh=64, Save=FALSE){
url <- URLencode(paste0("https://www.google.co.jp/search?q=", Query, "&btnG=Google+Search&tbs=0&safe=off&tbm=isch"))  

imgsrc <- xml2::read_html(url) %>%
        rvest::html_nodes(xpath = '//img') %>% 
        rvest::html_attr('src') %>% 
        .[startsWith(., "https://")]

#アレイデータを格納する変数
Dat <- NULL

for(n in 1:length(imgsrc)){
#n <- 1
#URLを取り出す
imgsrc00 <- imgsrc[n]

#ローカルに保存する場合には、TRUEにする
if(Save){
download.file(imgsrc00, 
              paste0("Image_", formatC(n, width = 3, flag = "0"),".jpg"), mode = "wb")  
}

#画像読み込み
Img <- EBImage::readImage(imgsrc00, type="jpg")
#EBImage::display(Img, method="raster")

#リサイズ
ImgR <- EBImage::resize(Img, w = wh, h=wh)

#str(ImgR)
ImgRArray <- array(ImgR, dim=dim(ImgR))
#str(ImgRArray)
Dat[[n]] <- ImgRArray
}

#listからアレイに変換
Tensor <- base::aperm(base::simplify2array(Dat), c(4, 1, 2, 3))
#str(Tensor)

#出力
return(Tensor)
}

display.4dArray関数のQueryパラメータには、 GoogleImage2Arrayと同じ変数を入れる。 また、デフォルト設定で画像をローカル保存する(Save=TRUE)。

#アレイデータを可視化・画像保存する関数
#Queryには、GoogleImage2Arrayと同じ変数を入れる。画像をローカル保存する(Save=TRUE)。
display.4dArray <- function(x, Query=NULL, Save=TRUE){
par(mfrow=c(4,5), mar=rep(0, 4))
for(n in 1:dim(x)[1]){
  #n <- 1
  plot(t(as.raster(x[n,,,], max=max(x[n,,,]))))
}
if(!is.null(Query)){
if(Save){ quartz.save(paste0("Image_", Query, ".png"), type = "png")  }  
}
}

事例1: 「ペルシャ猫」をクエリとした画像検索の結果

#クエリの準備
Query <- "ペルシャ猫"

#検索&画像取得
images <- GoogleImage2Array(Query)

#アレイの情報
str(images)
#num [1:20, 1:64, 1:64, 1:3] 0.789 1 0.144 0.519 1 ...

#可視化
display.4dArray(images, Query=Query)

f:id:skume:20210921002823p:plain:w400

事例2: 「広末涼子」をクエリとした画像検索の結果

#クエリの準備
Query <- "広末涼子"

#検索&画像取得
images <- GoogleImage2Array(Query)

#アレイの情報
str(images)
#num [1:20, 1:64, 1:64, 1:3] 1 0.735 0.976 0.643 0.855 ...

#可視化
display.4dArray(images, Query=Query)

f:id:skume:20210921002827p:plain:w400

まとめ

さくっと、画像が取得できて、素晴らしい!

Google検索の仕様が変わらない限りは、当分の間、使い続けられそうである。

ただ現状、一度に、20枚の画像しか取得できないのは課題かも。

作成したスクリプト

gist.github.com

English page (Google translate)

参考資料

stackoverflow.com

stackoverflow.com

stackoverflow.com

qiita.com

R/Keras/TensorFlowでやる『ディープラーニング(Deep Learning)』のすゝめ【その3】敵対的生成ネットワーク (GAN)による教師なし画像生成(image generation)をやってみた件

「敵対的生成ネットワーク(GAN: Generative Adversarial Networks)」は、 ディープラーニングの生成モデルの一種であり、 与えたデータから本物と偽物を見分けるように複数の学習器を用いて学習することで、 新たな画像の生成や画風の変更などができる。 このGANモデルは、Generator(生成器)とDiscriminator(識別器)のニューラルネットワーク部に大別される。

生成器では、インプットデータ(ex. ノイズ)から 本物のデータに近い偽物を作ることを目指して、学習を進めていく。 また、識別器では、生成器が作成した偽物を本物のデータと区別するために、学習を行う。

今回、GANモデル(教師なし学習)による手書き文字(7を使用)の画像生成がやってみた。

中間層に、全結合ネットワークだけでなく、 畳み込みニューラルネットワーク(CNN)を使ってるので、 正式には、DCGAN (Deep Convolutional GAN)の分類かもしれないけど。

関連パッケージのインストール・ロード

library(keras)

#Model可視化用関数
source("https://gist.githubusercontent.com/kumeS/41fed511efb45bd55d468d4968b0f157/raw/b7205c6285422e5166f70b770e1e8674d65f5ea2/DL_plot_modi_v1.2.R")

ministデータセットをダウンロードする

#データ準備
minist <- dataset_mnist()
str(minist)

#データの抽出
trainx <- minist$train$x
trainy <- minist$train$y
#testx <- minist$test$x
#testy <- minist$test$y

#or
# %<-% でも変数代入ができる
#c(c(trainx,trainy), c(testx,testy)) %<-% minist

str(trainx)
#int [1:60000, 1:28, 1:28] 0 0 0 0 0 0 0 0 0 0 ...

ここでは、7のみのデータを抽出して、アレイ形式に変換してノーマライズする

#7のみを抽出する
trainx7 <- trainx[trainy==7,,]

#図示
par(mfrow=c(8,8), mar=rep(0,4))
for(n in 1:64){plot(as.raster(trainx7[n,,], max=255))}
quartz.save("Fig_01.png", type = "png")
par(mfrow=c(1,1))

#アレイの変形
trainX <- array_reshape(trainx7, 
                        c(nrow(trainx7), 28, 28,1))

#ノーマライズ
trainX <- trainX/255
str(trainX)
#num [1:6265, 1:28, 1:28, 1] 0 0 0 0 0 0 0 0 0 0 ...

f:id:skume:20210920011046p:plain:w500

Generatorモデル (生成器モデル)の作成(1)

#input
l <- 28

#Generatorモデルの作成
input <- layer_input(shape=l)
gen <- input %>% 
  layer_dense(unit=32*14*14) %>%
  layer_activation_leaky_relu() %>%
  layer_reshape(target_shape = c(14,14,32)) %>%
  layer_conv_2d(filters = 32,
                kernel_size = 5,
                padding = "same") %>%
  layer_activation_leaky_relu() %>%
  layer_conv_2d_transpose(filters = 32,
                          kernel_size = 4,
                          strides = 2,
                          padding = "same") %>%
  layer_activation_leaky_relu() %>%
  layer_conv_2d(filters = 1,
                kernel_size = 5,
                activation = "tanh",
                padding = "same")

#モデル構築
Generator <- keras_model(input, gen)

Generator
#Model
#Model: "model"
#____________________________________________________________________________
#Layer (type)                      Output Shape                  Param #     
#============================================================================
#input_1 (InputLayer)              [(None, 28)]                  0           
#____________________________________________________________________________
#dense (Dense)                     (None, 6272)                  181888      
#____________________________________________________________________________
#leaky_re_lu_2 (LeakyReLU)         (None, 6272)                  0           
#____________________________________________________________________________
#reshape (Reshape)                 (None, 14, 14, 32)            0           
#____________________________________________________________________________
#conv2d_1 (Conv2D)                 (None, 14, 14, 32)            25632       
#____________________________________________________________________________
#leaky_re_lu_1 (LeakyReLU)         (None, 14, 14, 32)            0           
#____________________________________________________________________________
#conv2d_transpose (Conv2DTranspose (None, 28, 28, 32)            16416       
#____________________________________________________________________________
#leaky_re_lu (LeakyReLU)           (None, 28, 28, 32)            0           
#____________________________________________________________________________
#conv2d (Conv2D)                   (None, 28, 28, 1)             801         
#============================================================================
#Total params: 224,737
#Trainable params: 224,737
#Non-trainable params: 0
#____________________________________________________________________________

Discriminatorモデル (識別器モデル)の作成(1)

#input
shape <- c(28, 28, 1)

# Discriminator モデル作成
input0 <- layer_input(shape=shape)
dis <- input0 %>%
  layer_conv_2d(filters = 64,
                kernel_size = 4) %>%
  layer_activation_leaky_relu() %>%
  layer_flatten()  %>%
  layer_dropout(rate = 0.3) %>%
  layer_dense(units = 1,
              activation = "sigmoid")

#モデル構築
Discriminator <- keras_model(input0, dis)

Discriminator
#Model
#Model: "model_1"
#____________________________________________________________________________
#Layer (type)                      Output Shape                  Param #     
#============================================================================
#input_2 (InputLayer)              [(None, 28, 28, 1)]           0           
#____________________________________________________________________________
#conv2d_2 (Conv2D)                 (None, 25, 25, 64)            1088        
#____________________________________________________________________________
#leaky_re_lu_3 (LeakyReLU)         (None, 25, 25, 64)            0           
#____________________________________________________________________________
#flatten (Flatten)                 (None, 40000)                 0           
#____________________________________________________________________________
#dropout (Dropout)                 (None, 40000)                 0           
#____________________________________________________________________________
#dense_1 (Dense)                   (None, 1)                     40001       
#============================================================================
#Total params: 41,089
#Trainable params: 41,089
#Non-trainable params: 0
#____________________________________________________________________________

モデルのコンパイル(1)

#Discriminatorモデルのコンパイル: 学習を可能とする
Discriminator %>% compile(optimizer="rmsprop",
                          loss="binary_crossentropy")
Discriminator$trainable
#[1] TRUE

#次に、Discriminatorモデルの学習を不可とする
freeze_weights(Discriminator)
Discriminator$trainable
#[1] FALSE
#or
#Discriminator$trainable = FALSE

#GANモデルの作成(Discriminatorモデルの学習はしない)
input <- layer_input(shape=l)
gan <- input %>% Generator %>% Discriminator
GAN <- keras_model(input, gan)

#GANモデルのコンパイル
GAN %>% compile(optimizer="rmsprop",
                loss="binary_crossentropy")

#可視化
plot_model_modi(Generator)
plot_model_modi(Discriminator)
plot_model_modi(GAN)

f:id:skume:20210920011054p:plain:w500

モデルのトレーニング (事前準備 + 実行)

#inputのデータ数
b <- 50

#ディレクトリの作成
dir <- "gan_img"
dir.create(dir)

#初期パラメータ
start <- 1
dloss <- NULL
gloss <- NULL

#########################################
#Training step: 120 steps
#########################################
for(i in 1:120){
#i <- 1

#50個のfake dataの作成
noise <- matrix(rnorm(b*l),
                nrow=b,
                ncol=l)

#偽物のデータを生成する
fake <- predict(Generator, noise)
#str(fake)
#num [1:50, 1:28, 1:28, 1] -0.00514 -0.00762 -0.00724 -0.01103 -0.00628 ...

#データ範囲を決める
stop <- start + b -1

#本物のデータを取り出す
real <- trainX[start:stop,,,]
#str(real)
#num [1:50, 1:28, 1:28] 0 0 0 0 0 0 0 0 0 0 ...

#本物のデータのアレイ形状を変換
real <- array_reshape(real, c(nrow(real), 28,28,1))
#str(real)
#num [1:50, 1:28, 1:28, 1] 0 0 0 0 0 0 0 0 0 0 ...

#データ数
rows <- nrow(real)

#偽物と本物のデータを結合する
both <- array(0, dim=c(rows*2, dim(real)[-1]))
#str(both)
#num [1:100, 1:28, 1:28, 1] 0 0 0 0 0 0 0 0 0 0 ...
both[1:rows,,,] <- fake
both[(rows+1):(rows*2),,,] <- real

#ラベルの作成: fake: 0.9-1, real: 0-0.1
Labels <- rbind(matrix(runif(b, 0.9, 1),
                       nrow=b,
                       ncol=1),
                matrix(runif(b, 0, 0.1),
                       nrow=b,
                       ncol=1)
                )

#識別器の学習: 正解データとフェイクを与えて学習する
dloss[i] <- Discriminator %>% train_on_batch(both, Labels)

#GANの学習: フェイクデータに逆のラベル(正解ラベル)を与えて学習させる
fakeAsReal <- array(runif(b, 0, 0.1), dim=c(b,1))
gloss[i] <- GAN %>% train_on_batch(noise, fakeAsReal)

#Save Fake images
#str(fake)
par(mfrow=c(7,7), mar=rep(0,4), omi=c(0,0,0.5,0))
for(n in 1:49){
  f <- fake[n,,,]
  dim(f) <- c(28, 28)
  plot(as.raster(((f-min(f))/max(f-min(f)))*255, max=255))
}
mtext(side = 3, line=1, outer=T, cex=2, 
      text = paste0("i = ", i))
quartz.save(file.path(dir, paste0("f_", formatC(i, width = 4, flag = "0"), ".png")), 
            type = "png")

#startを更新する
start <- start + b
}

ここで、train_on_batch関数は、1batchのサンプルでのシングル勾配更新またはモデル評価する関数である

画像生成の結果をgifアニメーションとして可視化(1)

# 動画生成: 事前にImageMagickをインストールしておく
system(paste0("convert -delay 20 -loop 10 ./", dir, "/*.png ./Fig_03_20ms.gif"))

f:id:skume:20210920011250g:plain:w600

Lossの結果を可視化する

par(mfrow=c(1,1))
x <- 1:120
plot(x, dloss, col="red", type="l",
     ylim=c(0,3), xlab="Interations",
     ylab="Loss")
lines(x, gloss, col="black", type="l")
legend("topright", legend=c("Discriminator", "GAN Loss"),
       col=c("red", "black"), lwd=1)
quartz.save("Fig_04.png", type = "png")

f:id:skume:20210920011253p:plain:w500

次に、少し改善したモデルで実行してみる。

各モデルに、layer_conv_2dレイヤーを1つずつ追加した。

Generatorモデル (生成器モデル)の作成(2)

#input
l <- 28

#Generatorモデルの作成
input <- layer_input(shape=l)
gen <- input %>% 
  layer_dense(unit=32*14*14) %>%
  layer_activation_leaky_relu() %>%
  layer_reshape(target_shape = c(14,14,32)) %>%
  layer_conv_2d(filters = 32,
                kernel_size = 5,
                padding = "same") %>%
  layer_activation_leaky_relu() %>%
  layer_conv_2d_transpose(filters = 32,
                          kernel_size = 4,
                          strides = 2,
                          padding = "same") %>%
  layer_activation_leaky_relu() %>%
  layer_conv_2d(filters = 64,
                kernel_size = 5,
                padding = "same") %>%
  layer_activation_leaky_relu() %>%
  layer_conv_2d(filters = 1,
                kernel_size = 5,
                activation = "tanh",
                padding = "same")

#モデル構築
Generator <- keras_model(input, gen)  

Generator
#Model
#Model: "model_5"
#____________________________________________________________________________
#Layer (type)                      Output Shape                  Param #     
#============================================================================
#input_6 (InputLayer)              [(None, 28)]                  0           
#____________________________________________________________________________
#dense_4 (Dense)                   (None, 6272)                  181888      
#____________________________________________________________________________
#leaky_re_lu_13 (LeakyReLU)        (None, 6272)                  0           
#____________________________________________________________________________
#reshape_2 (Reshape)               (None, 14, 14, 32)            0           
#____________________________________________________________________________
#conv2d_10 (Conv2D)                (None, 14, 14, 32)            25632       
#____________________________________________________________________________
#leaky_re_lu_12 (LeakyReLU)        (None, 14, 14, 32)            0           
#____________________________________________________________________________
#conv2d_transpose_2 (Conv2DTranspo (None, 28, 28, 32)            16416       
#____________________________________________________________________________
#leaky_re_lu_11 (LeakyReLU)        (None, 28, 28, 32)            0           
#____________________________________________________________________________
#conv2d_9 (Conv2D)                 (None, 28, 28, 64)            51264       
#____________________________________________________________________________
#leaky_re_lu_10 (LeakyReLU)        (None, 28, 28, 64)            0           
#____________________________________________________________________________
#conv2d_8 (Conv2D)                 (None, 28, 28, 1)             1601        
#============================================================================
#Total params: 276,801
#Trainable params: 276,801
#Non-trainable params: 0
#____________________________________________________________________________

Discriminatorモデル (識別器モデル)の作成(1)

#input
shape <- c(28, 28, 1)

# Discriminator モデル作成
input0 <- layer_input(shape=shape)
dis <- input0 %>%
  layer_conv_2d(filters = 64,
                kernel_size = 4) %>%
  layer_activation_leaky_relu() %>%
  layer_conv_2d(filters = 64,
                kernel_size = 4,
                strides = 2) %>%
  layer_activation_leaky_relu() %>%
  layer_flatten()  %>%
  layer_dropout(rate = 0.3) %>%
  layer_dense(units = 1,
              activation = "sigmoid")

#モデル構築
Discriminator <- keras_model(input0, dis)

Discriminator
#Model
#Model: "model_3"
#____________________________________________________________________________
#Layer (type)                      Output Shape                  Param #     
#============================================================================
#input_4 (InputLayer)              [(None, 28, 28, 1)]           0           
#____________________________________________________________________________
#conv2d_4 (Conv2D)                 (None, 25, 25, 64)            1088        
#____________________________________________________________________________
#leaky_re_lu_5 (LeakyReLU)         (None, 25, 25, 64)            0           
#____________________________________________________________________________
#conv2d_3 (Conv2D)                 (None, 11, 11, 64)            65600       
#____________________________________________________________________________
#leaky_re_lu_4 (LeakyReLU)         (None, 11, 11, 64)            0           
#____________________________________________________________________________
#flatten_1 (Flatten)               (None, 7744)                  0           
#____________________________________________________________________________
#dropout_1 (Dropout)               (None, 7744)                  0           
#____________________________________________________________________________
#dense_2 (Dense)                   (None, 1)                     7745        
#============================================================================
#Total params: 74,433
#Trainable params: 74,433
#Non-trainable params: 0
#____________________________________________________________________________

モデルのコンパイル(2)

#Discriminatorモデルのコンパイル: 学習を可能とする
Discriminator %>% compile(optimizer="rmsprop",
                          loss="binary_crossentropy")
Discriminator$trainable
#[1] TRUE

#次に、Discriminatorモデルの学習を不可とする
freeze_weights(Discriminator)
Discriminator$trainable
#[1] FALSE
#or
#Discriminator$trainable = FALSE

#GANモデルの作成(Discriminatorモデルの学習はしない)
input <- layer_input(shape=l)
gan <- input %>% Generator %>% Discriminator
GAN <- keras_model(input, gan)

#GANモデルのコンパイル
GAN %>% compile(optimizer="rmsprop",
                loss="binary_crossentropy")

モデルのトレーニング (事前準備 + 再実行)

#inputのデータ数
b <- 50

#ディレクトリの作成
dir <- "gan_img2"
dir.create(dir)

#初期パラメータ
start <- 1
dloss <- NULL
gloss <- NULL

#########################################
#Training step: 120 steps
#########################################
for(i in 1:120){
#i <- 1

#50個のfake dataの作成
noise <- matrix(rnorm(b*l),
                nrow=b,
                ncol=l)

#偽物のデータを生成する
fake <- predict(Generator, noise)
#str(fake)
#num [1:50, 1:28, 1:28, 1] -0.00514 -0.00762 -0.00724 -0.01103 -0.00628 ...

#データ範囲を決める
stop <- start + b -1

#本物のデータを取り出す
real <- trainX[start:stop,,,]
#str(real)
#num [1:50, 1:28, 1:28] 0 0 0 0 0 0 0 0 0 0 ...

#本物のデータのアレイ形状を変換
real <- array_reshape(real, c(nrow(real), 28,28,1))
#str(real)
#num [1:50, 1:28, 1:28, 1] 0 0 0 0 0 0 0 0 0 0 ...

#データ数
rows <- nrow(real)

#偽物と本物のデータを結合する
both <- array(0, dim=c(rows*2, dim(real)[-1]))
#str(both)
#num [1:100, 1:28, 1:28, 1] 0 0 0 0 0 0 0 0 0 0 ...
both[1:rows,,,] <- fake
both[(rows+1):(rows*2),,,] <- real

#ラベルの作成: fake: 0.9-1, real: 0-0.1
Labels <- rbind(matrix(runif(b, 0.9, 1),
                       nrow=b,
                       ncol=1),
                matrix(runif(b, 0, 0.1),
                       nrow=b,
                       ncol=1)
                )

#識別器の学習: 正解データとフェイクを与えて学習する
dloss[i] <- Discriminator %>% train_on_batch(both, Labels)

#GANの学習: フェイクデータに逆のラベル(正解ラベル)を与えて学習させる
fakeAsReal <- array(runif(b, 0, 0.1), dim=c(b,1))
gloss[i] <- GAN %>% train_on_batch(noise, fakeAsReal)

#Save Fake images
#str(fake)
par(mfrow=c(7,7), mar=rep(0,4), omi=c(0,0,0.5,0))
for(n in 1:49){
  f <- fake[n,,,]
  dim(f) <- c(28, 28)
  plot(as.raster(((f-min(f))/max(f-min(f)))*255, max=255))
}
mtext(side = 3, line=1, outer=T, cex=2, 
      text = paste0("i = ", i))
quartz.save(file.path(dir, paste0("f_", formatC(i, width = 4, flag = "0"), ".png")), 
            type = "png")

#startを更新する
start <- start + b
}

画像生成の結果をgifアニメーションとして可視化(2)

# 動画生成: 事前にImageMagickをインストールしておく
system(paste0("convert -delay 20 -loop 10 ./", dir, "/*.png ./Fig_05_20ms.gif"))

f:id:skume:20210920011321g:plain:w600

Lossの結果を可視化する

par(mfrow=c(1,1))
x <- 1:120
plot(x, dloss, col="red", type="l",
     ylim=c(0,3), xlab="Interations",
     ylab="Loss")
lines(x, gloss, col="black", type="l")
legend("topright", legend=c("Discriminator", "GAN Loss"),
       col=c("red", "black"), lwd=1)
quartz.save("Fig_06.png", type = "png")

f:id:skume:20210920011324p:plain:w500

参考資料

本記事は、Dr. Bharatendra Raiが提供するYouTubeチャネル内の  「Generative Adversarial Networks (GANs) with R」の実行スクリプトを参考に作成した。

Generative Adversarial Networks (GANs) with R | 1. Introduction & Overview - YouTube

clean-copy-of-onenote.hatenablog.com

www.imagazine.co.jp

Google検索で、rpubs.com サイト内を検索するTips

rpubs.comは、 RのMarkdownドキュメントなどをWeb共有するサイトである。

rpubs.com自体には、検索機能が実装されていないので、 サードパーティアプリから検索する必要がある。。。

Google検索で、rpubs.com サイト内を検索する場合には、 例えば、「site:rpubs.com keras」と入力して検索すればよい。

f:id:skume:20210919182058p:plain:w500

また、検索の条件(例えば、{keras AND deep learning}とか)もかける。

f:id:skume:20210919182148p:plain:w500

この要領で、他のサイトを対象にした検索もできそうだ。

Google検索しか勝たん。