Linux环境下编译TensorFlow C++ API和测试方法总结(完美版)

目录

前言

1、软件下载

2、不同平台版本对应要求

3、环境配置

4、配置TensorFlow安装选项

5、使用bazel进行编译生成动态库

6、编译其他依赖

7、测试一:简单测试编译好的TensorFlow C++ API

8、测试二:Tensorflow C++ API调用Python预训练模型


前言

TensorFlow C++ API的编译是线上使用TensorFlow C++ API调用预先训练好的模型完成预测项目的必经之路,但是TensorFlow C++ API的编译并不是这么顺利,期间遇到了很多错误,它对版本要求非常严格,版本不对应会出现很多问题,尤其protobuf是最重要的一个。本篇是记录TensorFlow C++ API顺利编译和测试的过程,为线上使用TensorFlow C++ API的使用打下基础。

1、软件下载

(1)tensorflow-1.13.1源码(tensorflow 1.13.1

(2)bazel-0.19.2-installer-linux-x86_64.sh(bazel-0.19.2-installer-linux-x86_64.sh

(3)Anaconda3-4.3.1-Linux-x86_64.sh(Anaconda3-4.3.1-Linux-x86_64.sh

(4)protobuf-2.6.1.tar.gz(protobuf-2.6.1.tar.gz

2、不同平台版本对应要求

注意:TensorFlow C++ API的编译对于bazel等工具的对应版本有要求,版本需要匹配,不然会有很多想不到的错误。

(1)Windows-CPU

版本Python 版本编译器编译工具
tensorflow-1.13.03.5-3.6MSVC 2015 update 3Cmake v3.6.3
tensorflow-1.12.03.5-3.6MSVC 2015 update 3Cmake v3.6.3
tensorflow-1.11.03.5-3.6MSVC 2015 update 3Cmake v3.6.3
tensorflow-1.10.03.5-3.6MSVC 2015 update 3Cmake v3.6.3
tensorflow-1.9.03.5-3.6MSVC 2015 update 3Cmake v3.6.3
tensorflow-1.8.03.5-3.6MSVC 2015 update 3Cmake v3.6.3
tensorflow-1.7.03.5-3.6MSVC 2015 update 3Cmake v3.6.3
tensorflow-1.6.03.5-3.6MSVC 2015 update 3Cmake v3.6.3
tensorflow-1.5.03.5-3.6MSVC 2015 update 3Cmake v3.6.3
tensorflow-1.4.03.5-3.6MSVC 2015 update 3Cmake v3.6.3
tensorflow-1.3.03.5-3.6MSVC 2015 update 3Cmake v3.6.3
tensorflow-1.2.03.5-3.6MSVC 2015 update 3Cmake v3.6.3
tensorflow-1.1.03.5MSVC 2015 update 3Cmake v3.6.3
tensorflow-1.0.03.5MSVC 2015 update 3Cmake v3.6.3

(2)Linux-CPU

版本Python 版本编译器编译工具
tensorflow-1.13.12.7、3.3-3.6GCC 4.8Bazel 0.19.2
tensorflow-1.12.02.7、3.3-3.6GCC 4.8Bazel 0.15.0
tensorflow-1.11.02.7、3.3-3.6GCC 4.8Bazel 0.15.0
tensorflow-1.10.02.7、3.3-3.6GCC 4.8Bazel 0.15.0
tensorflow-1.9.02.7、3.3-3.6GCC 4.8Bazel 0.11.0
tensorflow-1.8.02.7、3.3-3.6GCC 4.8Bazel 0.10.0
tensorflow-1.7.02.7、3.3-3.6GCC 4.8Bazel 0.10.0
tensorflow-1.6.02.7、3.3-3.6GCC 4.8Bazel 0.9.0
tensorflow-1.5.02.7、3.3-3.6GCC 4.8Bazel 0.8.0
tensorflow-1.4.02.7、3.3-3.6GCC 4.8Bazel 0.5.4
tensorflow-1.3.02.7、3.3-3.6GCC 4.8Bazel 0.4.5
tensorflow-1.2.02.7、3.3-3.6GCC 4.8Bazel 0.4.5
tensorflow-1.1.02.7、3.3-3.6GCC 4.8Bazel 0.4.2
tensorflow-1.0.02.7、3.3-3.6GCC 4.8Bazel 0.4.2

 (3)MacOS-CPU

版本Python 版本编译器编译工具
tensorflow-1.13.12.7、3.3-3.6XCode 中的 ClangBazel 0.19.2
tensorflow-1.12.02.7、3.3-3.6XCode 中的 ClangBazel 0.15.0
tensorflow-1.11.02.7、3.3-3.6XCode 中的 ClangBazel 0.15.0
tensorflow-1.10.02.7、3.3-3.6XCode 中的 ClangBazel 0.15.0
tensorflow-1.9.02.7、3.3-3.6XCode 中的 ClangBazel 0.11.0
tensorflow-1.8.02.7、3.3-3.6XCode 中的 ClangBazel 0.10.1
tensorflow-1.7.02.7、3.3-3.6XCode 中的 ClangBazel 0.10.1
tensorflow-1.6.02.7、3.3-3.6XCode 中的 ClangBazel 0.8.1
tensorflow-1.5.02.7、3.3-3.6XCode 中的 ClangBazel 0.8.1
tensorflow-1.4.02.7、3.3-3.6XCode 中的 ClangBazel 0.5.4
tensorflow-1.3.02.7、3.3-3.6XCode 中的 ClangBazel 0.4.5
tensorflow-1.2.02.7、3.3-3.6XCode 中的 ClangBazel 0.4.5
tensorflow-1.1.02.7、3.3-3.6XCode 中的 ClangBazel 0.4.2
tensorflow-1.0.02.7、3.3-3.6XCode 中的 ClangBazel 0.4.2

(4) Windows-GPU

版本Python 版本编译器编译工具cuDNNCUDA
tensorflow_gpu-1.13.03.5-3.6MSVC 2015 update 3Bazel 0.15.079
tensorflow_gpu-1.12.03.5-3.6MSVC 2015 update 3Bazel 0.15.079
tensorflow_gpu-1.11.03.5-3.6MSVC 2015 update 3Bazel 0.15.079
tensorflow_gpu-1.10.03.5-3.6MSVC 2015 update 3Cmake v3.6.379
tensorflow_gpu-1.9.03.5-3.6MSVC 2015 update 3Cmake v3.6.379
tensorflow_gpu-1.8.03.5-3.6MSVC 2015 update 3Cmake v3.6.379
tensorflow_gpu-1.7.03.5-3.6MSVC 2015 update 3Cmake v3.6.379
tensorflow_gpu-1.6.03.5-3.6MSVC 2015 update 3Cmake v3.6.379
tensorflow_gpu-1.5.03.5-3.6MSVC 2015 update 3Cmake v3.6.379
tensorflow_gpu-1.4.03.5-3.6MSVC 2015 update 3Cmake v3.6.368
tensorflow_gpu-1.3.03.5-3.6MSVC 2015 update 3Cmake v3.6.368
tensorflow_gpu-1.2.03.5-3.6MSVC 2015 update 3Cmake v3.6.35.18
tensorflow_gpu-1.1.03.5MSVC 2015 update 3Cmake v3.6.35.18
tensorflow_gpu-1.0.03.5MSVC 2015 update 3Cmake v3.6.35.18

(5)Linux-GPU

版本Python 版本编译器编译工具cuDNNCUDA
tensorflow_gpu-1.13.12.7、3.3-3.6GCC 4.8Bazel 0.19.27.410.0
tensorflow_gpu-1.12.02.7、3.3-3.6GCC 4.8Bazel 0.15.079
tensorflow_gpu-1.11.02.7、3.3-3.6GCC 4.8Bazel 0.15.079
tensorflow_gpu-1.10.02.7、3.3-3.6GCC 4.8Bazel 0.15.079
tensorflow_gpu-1.9.02.7、3.3-3.6GCC 4.8Bazel 0.11.079
tensorflow_gpu-1.8.02.7、3.3-3.6GCC 4.8Bazel 0.10.079
tensorflow_gpu-1.7.02.7、3.3-3.6GCC 4.8Bazel 0.9.079
tensorflow_gpu-1.6.02.7、3.3-3.6GCC 4.8Bazel 0.9.079
tensorflow_gpu-1.5.02.7、3.3-3.6GCC 4.8Bazel 0.8.079
tensorflow_gpu-1.4.02.7、3.3-3.6GCC 4.8Bazel 0.5.468
tensorflow_gpu-1.3.02.7、3.3-3.6GCC 4.8Bazel 0.4.568
tensorflow_gpu-1.2.02.7、3.3-3.6GCC 4.8Bazel 0.4.55.18
tensorflow_gpu-1.1.02.7、3.3-3.6GCC 4.8Bazel 0.4.25.18
tensorflow_gpu-1.0.02.7、3.3-3.6GCC 4.8Bazel 0.4.25.18

(6)MacOS-GPU

版本Python 版本编译器编译工具cuDNNCUDA
tensorflow_gpu-1.1.02.7、3.3-3.6XCode 中的 ClangBazel 0.4.25.18
tensorflow_gpu-1.0.02.7、3.3-3.6XCode 中的 ClangBazel 0.4.25.18

3、环境配置

(1)安装Anaconda

/mnt/f/linux$ ./Anaconda3-4.3.1-Linux-x86_64.sh

安装过程中如果忘记将Anaconda添加环境变量,请使用以下方法添加环境变量。

方法一: 将Anaconda添加环境变量(该方法退出Linux重启后无效)

$ export PATH=/home/asialee/anaconda3/bin:$PATH
$ source ~/.bashrc

方法二:将Anaconda永久添加环境变量(该方法退出Linux重启后仍有效,配置稍微麻烦点)

$sudo vim /etc/profile

然后在打开的profile文本最后一行添加:export PATH=~/anaconda3/bin:$PATH 

最后注入环境变量,让其立即生效,重启也有效

source /etc/profile

添加环境变量完成后,输入python后得到显示,即配置完成。

(2)安装部分软件

安装bazel或者以下编译操作可能需要安装的部分软件如下:

$ sudo apt install unzip 
$ sudo apt install make
$ sudo apt install g++ 
$ sudo apt install gcc 
$ sudo apt install cmake

sudo apt-get install autoconf
sudo apt-get install automake
sudo apt-get install libtool
sudo apt install curl(used to download gmock)
sudo apt-get install zlib1g-dev
sudo apt-get install liblzma-dev

如果安装不成功,则需要更新sudo后再尝试安装,即:sudo apt update

(3)安装bazel

$ ./bazel-0.19.2-installer-linux-x86_64.sh --user

(4)安装protobuf

protobuf是最重要的一个,对版本要求非常严格,版本不对应会出现很多问题;本次使用的版本是protobuf 2.6.1,比较稳定。

解压protobuf-2.6.1.tar.gz

/mnt/f/linux$ tar -zxvf protobuf-2.6.1.tar.gz

进入解压后的protobuf-2.6.1文件目录,执行以下命令

/mnt/f/linux/protobuf-2.6.1$ ./configure
/mnt/f/linux/protobuf-2.6.1$ make
/mnt/f/linux/protobuf-2.6.1$ make install

注意:如果输入make install 时会出现make: *** [install-recursive] Error 1问题,则输入sudo make install 即可解决。

方法一:将protobuf加入环境变量(该方法重启后无效)

$ export LD_LIBRARY_PATH=/usr/local/lib

方法二: 将protobuf永久加入环境变量(重启后仍有效)

  • $sudo vim /etc/profile
  • 然后在打开的profile文本最后一行添加:export LD_LIBRARY_PATH=/usr/local/lib
  • 最后注入环境变量,让其立即生效,重启也有效:source /etc/profile

查看版本以验证安装

$ protoc --version

4、配置TensorFlow安装选项

进入源码根目录,运行 ./configure 进行配置。可参考 官网 -> Build from source -> View sample configuration session 设置,如果只需要配置cpu环境就一直回车。

/mnt/f/linux/tensorflow-1.13.1$ ./configure

5、使用bazel进行编译生成动态库

#编译C++ API,生成.so文件,Tensorflow调用CUDA

/mnt/f/linux/tensorflow-1.13.1$ bazel build --config=opt --config=cuda //tensorflow:libtensorflow_cc.so

#编译C++ API,生成.so文件,Tensorflow不调用CUDA

/mnt/f/linux/tensorflow-1.13.1$ bazel build --config=opt //tensorflow:libtensorflow_cc.so

等待编译大约半个多小时,就成功编译:

编译成功后,tensorflow-1.13.1目录下会出现 bazel-xxx 的几个文件,在tensorflow-1.13.1/bazel-bin/tensorflow文件下会出现 libtensorflow_cc.so libtensorflow_framework.so 动态库文件。动态库文件下载

6、编译其他依赖

cd /mnt/f/linux/tensorflow-1.13.1/tensorflow/contrib/makefile$ ./build_all_linux.sh

执行成功后,在tensorflow-1.13.1/tensorflow/contrib/makefile目录下:downloads文件夹下存放第三方依赖的一些头文件和静态库,比如nsync、Eigen、protobuf等。

说明:Eigen 是一个高层次的C ++库,有效支持线性代数,矩阵和矢量运算,数值分析及其相关的算法;Protocol Buffers (简称 Protobuf)是 Google 开源的一款跨语言,跨平台,扩展性好的序列化工具,相比于 XML 和 JSON 等流行的编码格式,这种数据结构化语言需要使用protoc进行编译。

7、测试一:简单测试编译好的TensorFlow C++ API

(1)创建一个demo文件夹,demo文件夹下具体内容目录如下

├── src
| └── test.cpp
├── CMakeLists.txt

其中,test.cpp为C++测试代码,具体如下:

#include <tensorflow/core/platform/env.h>
#include <tensorflow/core/public/session.h>
#include <iostream>
 
using namespace std;
using namespace tensorflow;
 
int main()
{
    Session* session;
    Status status = NewSession(SessionOptions(), &session);
    if (!status.ok()) {
        cout << status.ToString() << "\n";
        return 1;
    }
    cout << "Session successfully created.\n";
}

CMakeLists.txt文件内容如下:

#指定 cmake 的最小版本
cmake_minimum_required(VERSION 2.8.8)
#项目名称/工程名
project(demo)
#设置c++编译器
# Set C++14 as standard for the whole project
set(CMAKE_CXX_STANDARD 14)
#设置TENSORFLOW_DIR变量,变量内容为安装的tensorflow文件夹路径
set(TENSORFLOW_DIR /mnt/f/linux/tensorflow-1.13.1)
# 将源码目录保存到变量中
aux_source_directory(./src DIR_SRCS)  # 搜索当前目录下的所有.cpp文件  
#add_library(demo ${SRC_LIST})   #明确指定包含哪些源文件                 
#设置包含的目录,项目中的include路径,换成自己的路径即可
include_directories( 
    ${CMAKE_CURRENT_SOURCE_DIR}
	/mnt/f/linux/tensorflow-1.13.1
	/mnt/f/linux/tensorflow-1.13.1/bazel-genfiles
	/mnt/f/linux/tensorflow-1.13.1/bazel-bin/tensorflow
	/mnt/f/linux/tensorflow-1.13.1/tensorflow/contrib/makefile/downloads/nsync/public
 	/mnt/f/linux/tensorflow-1.13.1/tensorflow/contrib/makefile/downloads/eigen
   	/mnt/f/linux/tensorflow-1.13.1/tensorflow/contrib/makefile/downloads/absl
   ) 
#设置链接库搜索目录,项目中lib路径
link_directories(${TENSORFLOW_DIR}/tensorflow/contrib/makefile/downloads/nsync/builds/default.linux.c++11)
link_directories(${TENSORFLOW_DIR}/mnt/f/linux/tensorflow-1.13.1/bazel-bin/tensorflow)  #动态链接库目录
#添加要编译的可执行文件
#add_executable(demo test.cpp)
add_executable(demo ${DIR_SRCS})    ## 生成可执行文件
#设置 target 需要链接的库
#添加可执行文件所需要的库,连接libtensorflow_cc.so和libtensorflow_framework库,链接动态链接库
#target_link_libraries(demo tensorflow_cc tensorflow_framework)
target_link_libraries(demo /mnt/f/linux/tensorflow-1.13.1/bazel-bin/tensorflow/libtensorflow_cc.so /mnt/f/linux/tensorflow-1.13.1/bazel-bin/tensorflow/libtensorflow_framework.so)

(2)编译和运行

mkdir build   #创建build文件,是为了将编译程序放到build文件中
cd build
cmake ..   #使用cmake构建生成make文件
make       #使用make编译
./demo    #运行可执行文件

运行结果如下:

8、测试二:Tensorflow C++ API调用Python预训练模型

线下使用TensorFlow Python API训练模型,线上使用TensorFlow C++ API调用预先训练好的模型完成预测。

测试流程:

  • 使用TensorFlow Python API编写和训练模型,训练完成后,使用tensorflow saver 将模型保存下来。
  • 使用TensorFlow C++ API构建新的session,读取Python版本保存的模型,然后使用session->run()获得模型的输出。
  • 编译和运行基于TensorFlow C++ API写的代码

(1)使用Tensorflow Python API线下定义模型和训练

# -*-coding:utf-8 -*-
import tensorflow as tf
import os

# 使用Tensorflow Python API线下定义模型和训练
def tain_model():
    train_dir = os.path.join('data/demo', "demo")
    a = tf.compat.v1.placeholder(dtype=tf.int32, shape=None, name='a')
    b = tf.compat.v1.placeholder(dtype=tf.int32, shape=None, name='b')
    y = tf.Variable(tf.ones(shape=[1], dtype=tf.int32), dtype=tf.int32, name='y')
    res = tf.add(tf.multiply(a, b), y, name='res')
    with tf.Session() as sess:
        feed_dict = dict()
        feed_dict[a] = 2
        feed_dict[b] = 3
        fetch_list = [res]
        sess.run(tf.compat.v1.global_variables_initializer())
        saver = tf.compat.v1.train.Saver()
        # 训练和保存模型
        res = sess.run(feed_dict=feed_dict, fetches=fetch_list)
        saver.save(sess, train_dir)
        print("result: ", res[0])

#使用Tensorflow Python API 载入和运行模型
def load_model():
    with tf.Session() as sess:
        saver = tf.compat.v1.train.import_meta_graph('data/demo/demo.meta')
        saver.restore(sess, tf.train.latest_checkpoint('data/demo/'))
        # sess.run()
        graph = tf.compat.v1.get_default_graph()
        a = graph.get_tensor_by_name("a:0")
        b = graph.get_tensor_by_name("b:0")
        feed_dict = {a: 2, b: 3}
        op_to_restore = graph.get_tensor_by_name("res:0")
        print(sess.run(fetches=op_to_restore, feed_dict=feed_dict))

if __name__ == '__main__':
    #先训练模型
    tain_model()
    #导入训练好的模型测试结果
    load_model()


运行结果如下:

result:  [7]
[7]

生成的模型保存在demo文件夹下:

checkpoint  #模型checkpoint中的一些文件名的信息
demo.data-00000-of-00001  #模型中保存的各个权重
demo.index  #可能是保存的各个权重的索引
demo.meta  #模型构造的图的拓扑结构

(2)使用Tensorflow C++ API读入预训练模型

Tensorflow C++代码写入test.cpp文件中:

#include <iostream>
#include "tensorflow/core/public/session.h"
#include "tensorflow/core/protobuf/meta_graph.pb.h"
#include "tensorflow/cc/client/client_session.h"
#include "tensorflow/cc/ops/standard_ops.h"
#include "tensorflow/core/framework/tensor.h"

using namespace std;
using namespace tensorflow;

int main()
{
    const string pathToGraph = "/mnt/f/linux/demo/demo_model/demo.meta";
    const string checkpointPath = "/mnt/f/linux/demo/demo_model/demo";
    auto session = NewSession(SessionOptions());
    if (session == nullptr)
    {
        throw runtime_error("Could not create Tensorflow session.");
    }

    Status status;

// 读入我们预先定义好的模型的计算图的拓扑结构
    MetaGraphDef graph_def;
    status = ReadBinaryProto(Env::Default(), pathToGraph, &graph_def);
    if (!status.ok())
    {
        throw runtime_error("Error reading graph definition from " + pathToGraph + ": " + status.ToString());
    }

// 利用读入的模型的图的拓扑结构构建一个session
    status = session->Create(graph_def.graph_def());
    if (!status.ok())
    {
        throw runtime_error("Error creating graph: " + status.ToString());
    }

// 读入预先训练好的模型的权重
    Tensor checkpointPathTensor(DT_STRING, TensorShape());
    checkpointPathTensor.scalar<std::string>()() = checkpointPath;
    status = session->Run(
            {{ graph_def.saver_def().filename_tensor_name(), checkpointPathTensor },},
            {},
            {graph_def.saver_def().restore_op_name()},
            nullptr);
    if (!status.ok())
    {
        throw runtime_error("Error loading checkpoint from " + checkpointPath + ": " + status.ToString());
    }

//  构造模型的输入,相当与python版本中的feed
    std::vector<std::pair<string, Tensor>> input;
    tensorflow::TensorShape inputshape;
    inputshape.InsertDim(0,1);
    Tensor a(tensorflow::DT_INT32,inputshape);
    Tensor b(tensorflow::DT_INT32,inputshape);
    auto a_map = a.tensor<int,1>();
    a_map(0) = 2;
    auto b_map = b.tensor<int,1>();
    b_map(0) = 3;
    input.emplace_back(std::string("a"), a);
    input.emplace_back(std::string("b"), b);

//   运行模型,并获取输出
    std::vector<tensorflow::Tensor> answer;
    status = session->Run(input, {"res"}, {}, &answer);

    Tensor result = answer[0];
    auto result_map = result.tensor<int,1>();
    cout<<"result: "<<result_map(0)<<endl;

    return 0;
}

(3)编译和运行

编写CMakeLists.txt文件,文件内容如下:

#指定 cmake 的最小版本
cmake_minimum_required(VERSION 2.8.8)

#项目名称/工程名
project(demo)

#设置c++编译器
# Set C++14 as standard for the whole project
set(CMAKE_CXX_STANDARD 14)

#设置TENSORFLOW_DIR变量,变量内容为安装的tensorflow文件夹路径
set(TENSORFLOW_DIR /mnt/f/linux/tensorflow-1.13.1)

# 将源码目录保存到变量中
aux_source_directory(./src DIR_SRCS)  # 搜索当前目录下的所有.cpp文件  
#add_library(demo ${SRC_LIST})   #明确指定包含哪些源文件                 

#设置包含的目录,项目中的include路径,换成自己的路径即可
include_directories( 
    ${CMAKE_CURRENT_SOURCE_DIR}
	/mnt/f/linux/tensorflow-1.13.1
	/mnt/f/linux/tensorflow-1.13.1/bazel-genfiles
	/mnt/f/linux/tensorflow-1.13.1/bazel-bin/tensorflow
	/mnt/f/linux/tensorflow-1.13.1/tensorflow/contrib/makefile/downloads/nsync/public
 	/mnt/f/linux/tensorflow-1.13.1/tensorflow/contrib/makefile/downloads/eigen
   	/mnt/f/linux/tensorflow-1.13.1/tensorflow/contrib/makefile/downloads/absl
   ) 

#设置链接库搜索目录,项目中lib路径
link_directories(${TENSORFLOW_DIR}/tensorflow/contrib/makefile/downloads/nsync/builds/default.linux.c++11)
link_directories(${TENSORFLOW_DIR}/mnt/f/linux/tensorflow-1.13.1/bazel-bin/tensorflow)  #动态链接库目录

#添加要编译的可执行文件
#add_executable(demo test.cpp)
add_executable(demo ${DIR_SRCS})    ## 生成可执行文件

#设置 target 需要链接的库
#添加可执行文件所需要的库,连接libtensorflow_cc.so和libtensorflow_framework库,链接动态链接库
#target_link_libraries(demo tensorflow_cc tensorflow_framework)
target_link_libraries(demo /mnt/f/linux/tensorflow-1.13.1/bazel-bin/tensorflow/libtensorflow_cc.so /mnt/f/linux/tensorflow-1.13.1/bazel-bin/tensorflow/libtensorflow_framework.so)

编译及结果如下:

 

 

 

注意:在测试的的时候可能会遇到的protobuf版本错误问题,如果遇到了该问题,则需要解决protobuf版本问题,重新编译后再测试。

错误描述:

#if 2006001 < GOOGLE_PROTOBUF_MIN_PROTOC_VERSION#error This file was generated by an older version of protoc which is#error incompatible with your Protocol Buffer headers.  Please#error regenerate this file with a newer version of protoc.#endif

错误原因:

问题可能在于,系统上已安装的标头(在/ usr / include / google / protobuf或/ usr / local / include / google / protobuf中)来自于协议缓冲区的较新版本protoc。可能是两个版本都安装在不同的位置,并且使用了错误的版本。

©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页