プログラミングを上達させたい

情報学専攻の大学院→放送局でCMの営業など@大阪→舞台俳優&IT営業@東京

Kaggleの画像認識のやつをjuliaでやってみたよの巻

なんか最近プログラミング関係何もしてなかったので、
一念発起してKaggleを開き、画像認識に手を出してみようと思いました。
そしたらばKaggleに"First Steps With Julia"という練習コンペがあり、
それをチュートリアル見ながら一旦やってみることにしました。
が!!!!
チュートリアルで紹介されるコードを実行してみてもエラーに次ぐエラーで
さっぱり動かせず・・・
今までこういう感じになったら、その言語や分野まで放り出してたんですが、
なぜか今回はやる気が持続し、なんとかコードが動くようになるまで粘りました。
普段は本当にすぐ諦めるので、コードのドキュメントとか探して読みまくったのは初めての経験でした。
勉強になったので、後の自分のためにも、今回チュートリアルのコードから変更していった点を書いていきたいと思います。
動かすのを優先したので、汚いコードなのは悪しからず。
コードの全容は記事の最後に載せています。



Kaggleの今回のチュートリアルのページはコチラ
「juliaで画像認識のベタなコードを書いてみよう」という趣旨です。
この記事は、このチュートリアルページをベースに話すので、まずはこちらをご一読ください。
この中で紹介されているコードを(方針は変えずに)動くように変えていきます。
ちなみに僕の環境は"julia version 0.4.5"です。

では、変えた点を上から順に羅列していきます。

まず冒頭から。後に使うので以下のパッケージをjuliaに入れ、コードの頭でusingで宣言します。

using Images
using Colors
using FixedPointNumbers
using QuartzImageIO
using DataFrames
using DecisionTree

次に、read_dataという関数。
まず、今回はやりやすさのため、pathという引数はなくしました。
ほんで、なにより上手くいかないのが

img = imread(nameFile)

のところ。
画像をカラーで読み込む、というやり方をひたすら探しまくって、次のようなコードでなんとか動きました。

img = load(nameFile)

しかし、これで画像を読み込んでると、たまに画像として読み込めてないものがあります(trainResized/284.Bmpとかがそうでした)。
それに関しては、理由がさっぱり分かりません。
読み込みできないのではなく、読み込んだもののファイルがVoid型になっている、という動作になります。
よって、次のように書いて、無視することにしました。

if isa(img, Void)
	continue
end

よって、作成する行列も大きさが変わります。
具体的には、以下のコードで「ちゃんと読み込める画像の数」をカウントして、そのサイズが行数になります。

 for i in labelsInfo[1,1]:labelsInfo[1,1] + size(labelsInfo, 1) - 1
 	nameFile = "./$(typeData)Resized/$(i).Bmp"
 	img = load(nameFile)
 	if isa(img, Void)
 		continue
 	end
 	count += 1
 end

ほんで次。
読み込み方が違うからか、以下のコードは必要ありませんでした。

  #Convert img to float values 
  temp = float32sc(img)

で、この部分がまぁ動かない。グレースケールに変更するところですね。

  #Convert color images to gray images
  #by taking the average of the color scales. 
  if ndims(temp) == 3
   temp = mean(temp.data, 1)
  end

これに関しては、汚いコードではありますが、このように書くことで同じ計算ができました。

#celのR,G,Bを足して3で割る  		
temp[20 * (i - 1) + j] = (cel.r + cel.g + cel.b) / 3

また、reshapeのところも上手くいかなかったので、愚直に各要素に代入していきます。



さて、ここまでがデータ読み込み、ここからはモデル作成・予想作成です。
ここ以降は、割と順調に解決できたと思います(時間はかかった)。

まず、charをintに変更するところ。
チュートリアルのコードはint(x)という関数を使っていますが、これはInt(x)に変えます。
対話環境で色々試してたら、こんな感じでアドバイスしてくれました。

julia> int('a')
WARNING: int(x) is deprecated, use Int(x) instead.
 in depwarn at deprecated.jl:73
 in int at deprecated.jl:50
while loading no file, in expression starting on line 0
97

julia> Int('a')
97

あとは、最後の、予想ででてきた数字を文字に直すところ。
チュートリアルのページでは数字を文字に直すだけのコードを書いてますが、
僕のコード内ではstringに直す必要がありました。
intに直すときと違い、この場合は小文字始まりのstring(x)という関数を使いました。
対話環境では以下のようになりました。

julia> String('a')
WARNING: Base.String is deprecated, use AbstractString instead.
  likely near no file:0
WARNING: Base.String is deprecated, use AbstractString instead.
  likely near no file:0
ERROR: MethodError: `convert` has no method matching convert(::Type{AbstractString}, ::Char)
This may have arisen from a call to the constructor AbstractString(...),
since type constructors fall back to convert methods.
Closest candidates are:
  call{T}(::Type{T}, ::Any)
  convert{T<:AbstractString,S<:Union{Char,Int32,UInt32}}(::Type{T<:AbstractString}, ::AbstractArray{S<:Union{Char,Int32,UInt32},1})
  convert{S<:AbstractString}(::Type{S<:AbstractString}, ::Base.UTF8proc.GraphemeIterator{S<:AbstractString})
  ...
 in call at essentials.jl:56

julia> string('a')
"a"

そんなこんなで、なんとか動くコードに持って行くも、
提出してみたらかなり順位は低く・・・
loadで読み込めなかった訓練画像を無視していること、読み込めなかったテスト画像は全て'A'で返してること、が
まず思いつく改良すべき点ですが・・・
他にも直すべきところはいっぱいあるんでしょうね。

まあ画像認識の分野の勉強はこれからということで、今回は『動かないコードを諦めずに改良していく』という経験を積めたのが一番の収穫かと思います。
これからこの分野をやるのか、そもそもjuliaを使うのかも分かりませんが、これからのやる気にもいい影響を与える勉強になったと思います。

今日はここまで!
コードの全容書いて終わりとします!また!

using Images
using Colors
using FixedPointNumbers
using QuartzImageIO
using DataFrames
using DecisionTree

# 上記Pkgは全て、juliaを起動してPkg.add("~~~")で入れた

function read_data(typeData, labelsInfo, imageSize)
 #いくつか、loadしたら型が"Void"になるやつがある。なんでや。
 #そいつらは、無視することにする。

 count = 0

 for i in labelsInfo[1,1]:labelsInfo[1,1] + size(labelsInfo, 1) - 1
 	nameFile = "./$(typeData)Resized/$(i).Bmp"
 	img = load(nameFile)
 	if isa(img, Void)
 		continue
 	end
 	count += 1
 end

 x = zeros(count, imageSize)

 indexcount = 1

 for (index, idImage) in enumerate(labelsInfo[1]) 
  idImage = index + labelsInfo[1,1] - 1
  #Read image file 
  nameFile = "./$(typeData)Resized/$(idImage).Bmp"
  img = load(nameFile)
  if isa(img, Void)
  	continue
  end

  temp = [1.0:400.0]

  for i in 1:20
  	for j in 1:20
  		cel = img[i,j]
  		temp[20 * (i - 1) + j] = (cel.r + cel.g + cel.b) / 3
  	end
  end

  for i in 1:imageSize
  	x[indexcount, i] = temp[i]
  end
  indexcount += 1

 end 
 return x
end

#-------------------------------

imageSize = 400 # 20 x 20 pixel

#Read information about training data , IDs.
labelsInfoTrain = readtable("trainLabels.csv")

#Read training matrix
xTrain = read_data("train", labelsInfoTrain, imageSize)

#Read information about test data ( IDs ).
labelsInfoTest = readtable("sampleSubmission.csv")

#Read test matrix
xTest = read_data("test", labelsInfoTest, imageSize)
#-------------------------------

 traincount = 0
 for i in 1:size(labelsInfoTrain, 1)
 	nameFile = "trainResized/$(i).Bmp"
 	img = load(nameFile)
 	if isa(img, Void)
 		continue
 	end
 	traincount += 1
 end

 testcount = 0
 for i in 1:size(labelsInfoTest, 1)
 	nameFile = "testResized/$(i + labelsInfoTest[1,1] - 1).Bmp"
 	img = load(nameFile)
 	if isa(img, Void)
 		continue
 	end
 	testcount += 1
 end

yTrain = map(x -> Int(x[1]), labelsInfoTrain[2])
#小文字の"int(x)"だとダメ。大文字の"Int(x)"を使うと上手くいく。

yTraining = [1:traincount]

trainindex = 1
for i in 1:size(labelsInfoTrain, 1)
 	nameFile = "trainResized/$(i).Bmp"
 	img = load(nameFile)
 	if isa(img, Void)
 		continue
 	else 
 		yTraining[trainindex] = yTrain[i]
 		trainindex += 1
 	end
end

#Train random forest with
randomsplit = 30
treenum = 60
subsamplingratio = 1.0

model = build_forest(yTraining, xTrain, randomsplit, treenum, subsamplingratio)

#Get predictions for test data
predTest = apply_forest(model, xTest)

#--------------------------------
#↓ここ重複してますよね。ほんまに分かりにくい。
# # Get predictions for test data
# predTest = apply_forest(model, xTest)

#Convert integer predictions to 
predind = 1
# println(size(xTest, 1))
for i in 1:testcount
 	nameFile = "testResized/$(i + labelsInfoTest[1,1] - 1).Bmp"
 	img = load(nameFile)
 	if isa(img, Void)
 		#読み込めんやつは適当にAと答えるようにする
 		labelsInfoTest[i, 2] = "A"
 	else 
 		labelsInfoTest[i, 2] = string(Char(Int(predTest[predind])))
 		predind += 1
 	end
end
#String型の変数に文字を代入するのは可能
#ただ、String[]型の1要素に文字を代入するのは不可能

#Save predictions
writetable("juliaSubmission_$(randomsplit)_$(treenum).csv", labelsInfoTest, separator=',', header=true)