Androidでtensorflowを使う時に、はまった話

#概要
Pythonで作ったtensorflowのモデルをAndroidでも使いたいと思って、色々試したが結構躓いたので、経緯を記録しておく。最終的に無事にAndroid上でtensorflowを動作させることができたが、原因はまとまってない。

<環境>
PC:mac
開発環境：AndroidStudio
tensorflowのプログラム：パラメータが格納されたmodel.pbと、Tensor.create( )、session.runner( )などが書かれたLSTM.javaがあるのみ

#初動
Bazelとか使いたくなかったので、以下を参考に進めた。その上で、問題が発生してつまづいた部分を以下に記録する。
https://qiita.com/icchi_h/items/a1df9f27569714edfc5e

https://qiita.com/tchkwkzk/items/aa481db14126b6abdaa1

#問題
いくつか問題が出てきたので、個別に対処していった。
問題１：model.pbが読めない
問題２：tensorflowがimportできない
問題３：アプリが以下のエラー吐いて強制終了する
  No implementation found for native Lorg/tensorflow/TensorFlow;.version:()Ljava/lang/String;
  Exception Ljava/lang/UnsatisfiedLinkError; thrown while initializing Lorg/tensorflow/TensorFlow;
  FATAL EXCEPTION: main
  java.lang.UnsatisfiedLinkError: Cannot find TensorFlow native library for OS: linux, architecture: i686. 

                                                                                 
##問題１：model.pbが読み込めない
普段iOSのアプリを開発しているとこういうのは結構不便なのだが、AndroidStudioではassetsフォルダから、ローカルファイルを読み込むようになっている。

１：app/src/mainにassetsフォルダを追加(追加の仕方に注意）
　　追加の仕方：mainを右クリック→「New」→「Folder」→「Assets Folder」

２：作ったassetsフォルダにmodel.pbファイルを配置

３：MainActivity.javaのonCreate内で以下のコードを呼ぶ
　　`byte[] graphDef = readByteFile(String.valueOf("model.pb"))`

　　readFileToByteは自作関数で以下のように記述した。

```
   byte[] readByteFile(String filePath) {
        try {
            byte[] b = new byte[1];
            InputStream fis = this.getAssets().open(filePath);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            while (fis.read(b) > 0) {
                baos.write(b);
            }
            baos.close();
            fis.close();
            b = baos.toByteArray();

            return b;
        }catch(Exception ex){
            System.out.println(ex.toString());
            return null;
        }
    }
```


##問題２：tensorflowがimportできない
今回使用したLSTM.javaには以下のimportが記述してあった。

```
import org.tensorflow.Graph;
import org.tensorflow.Session;
import org.tensorflow.Tensor;
```

これを読むためにapp内の「build.gradle」に以下の記述を追加した。
ちなみに、「1.2.0」と「1.2.0-preview」があるらしく、「copyTo」がpreviewしか対応していなかったため、previewを記述。その後、AndroidStudioのSyncボタンを押下。

```
dependencies {
    
    ...
 
    compile 'org.tensorflow:tensorflow-android:1.2.0-preview'
    //compile 'org.tensorflow:tensorflow:1.2.0'

   ...
   
}
```

##問題３：エラーを吐いて強制終了する
  No implementation found for native Lorg/tensorflow/TensorFlow;.version:()Ljava/lang/String;
  Exception Ljava/lang/UnsatisfiedLinkError; thrown while initializing Lorg/tensorflow/TensorFlow;
  FATAL EXCEPTION: main
  java.lang.UnsatisfiedLinkError: Cannot find TensorFlow native library for OS: linux, architecture: i686. 

というエラーだが、これに関する解決策が驚くほど出てこなくて困った。（私の勉強不足でもあるが）

いくつかやった事があるので以下に記すが、いらない操作もあるかもしれない。

###１. soファイルを追加する
以下のリンクから、「最新成功ビルドの成果物 → out → native → (すべてのファイルをzipで)」という操作で、ファイルをダウンロードする。
http://ci.tensorflow.org/view/Nightly/job/nightly-android/

AndroidStudioでappの直下にlibsフォルダを追加する。
libsフォルダ内に先ほどダウンロードしたnativeフォルダ内の「libtensorflow_inference.so」内の全てのフォルダをlibsフォルダに追加する。ディレクトリ構成は以下のようになるはず。
-app
--libs
---arm64-v8a
----libtensorflow_inference.so
---armeabi-v7a
----libtensorflow_inference.so
---x86
----libtensorflow_inference.so
---x86_64
----libtensorflow_inference.so

###2. MainAcitivityにloadLibraryの追記をする

```
public class MainActivity extends AppCompatActivity {

    ....

    static {
        System.loadLibrary("tensorflow_inference");
    }

    ....

}
```

###3. app内のbuild.gradleのsourceSetsを変更する
```
android {
    
    ...

    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }

    ...

}
```


とりあえず、これでなんとか動いた。
結局何が正解なのかは分からないが、備忘録として残しておく。