by Jason Han

如何使用C#调用Microsoft App Store 中下载的应用

Win10之后,许多之前常用的应用,包括录音机、照相机等,都被归为了 Microsoft App Store 所管理的范畴,无法直接通过诸如 Process.Start(“video.exe”)、Process.Start(“camera.exe”)等命令调用。目前较为可行的方法是,通过调用 cmd,再将自动填入命令,最终实现 App 的调用。示例代码如下:

Process p = new Process();
//设置要启动的应用程序
p.StartInfo.FileName = "cmd.exe";
//是否使用操作系统shell启动
p.StartInfo.UseShellExecute = false;
// 接受来自调用程序的输入信息
p.StartInfo.RedirectStandardInput = true;
//输出信息
p.StartInfo.RedirectStandardOutput = true;
// 输出错误
p.StartInfo.RedirectStandardError = true;
//不显示程序窗口
p.StartInfo.CreateNoWindow = true;
//启动程序
p.Start();
// shell 后面的link可能因app的版本而异
p.StandardInput.WriteLine(@"explorer.exe shell:AppsFolder\2568CaiJunhong.CompassOne_nnrr2ryxcqq94!App");
p.Close();

该示例调用的是“指北针”,其所对应的链接2568CaiJunhong.CompassOne_nnrr2ryxcqq94!App可通过如下方式获取1

  1. 先打开 cmd,输入explorer.exe shell:AppsFolder\,此时打开了应用文件夹,如图所示:

    image-20201230145443759

  2. 选中其中一个应用,右键创建快捷方式至桌面;

  3. 右击桌面的快捷方式,选择属性,即可获得上述链接,如图所示:

    image-20201230145616474

参考


  1. https://answers.microsoft.com/en-us/windows/forum/windows_10-windows_store/starting-windows-10-store-app-from-the-command/836354c5-b5af-4d6c-b414-80e40ed14675 ↩︎

by Jason Han

如何使用C#调用已训练好的 pb 模型

在深度学习领域,Python占据着举足轻重的位置,然而,由于其脚本编程的特点,在业界并没有较大的优势。相比之下,C++、Java、C#等编程语言则能更好的满足业界的各种需求。本人长期从事 C# 开发,本文的内容是探讨如何在C#编程中调用已训练好的 .pb 深度学习模型。

目前已存在多种 .Net 平台下的深度学习框架,如 Tensorflow.Net、SciSharp、TensorflowSharp等。本文以 TensorflowSharp 为例进行讲解。

1. 安装 TensorflowSharp

可直接在 Nuget 中搜索 TensorflowSharp,选择相应版本下载。下载时注意不同版本对应不同的 .Net Framework。

2. 具体代码

调用模型并预测图片的代码为:

TFGraph graph = new TFGraph();
//重点是下面的这句,把训练好的pb文件给读出来字节,然后导入
string modelPath = Directory.GetCurrentDirectory() + "\\model.pb";
byte[] model = File.ReadAllBytes(modelPath);
graph.Import(model);

var tensor = ImageUtil.CreateTensorFromImageFile(picturePath);

using (var sess = new TFSession(graph))
{
    // 计算类别概率
    var runner = sess.GetRunner();
    runner.AddInput(graph["Mul"][0], tensor);
    var r = runner.Run(graph.Softmax(graph["final_result"][0]));
    var v = (float[,])r.GetValue();
}

其中,ImageUtil 代码如下:

public static class ImageUtil
{
    public static TFTensor CreateTensorFromImageFile(byte[] contents, TFDataType destinationDataType = TFDataType.Float)
    {
        var tensor = TFTensor.CreateString(contents);

        TFOutput input, output;

        // Construct a graph to normalize the image
        using (var graph = ConstructGraphToNormalizeImage(out input, out output, destinationDataType))
        {
            // Execute that graph to normalize this one image
            using (var session = new TFSession(graph))
            {
                var normalized = session.Run(
                inputs: new[] { input },
                inputValues: new[] { tensor },
                outputs: new[] { output });

                return normalized[0];
            }
        }
    }
    // Convert the image in filename to a Tensor suitable as input to the Inception model.
    public static TFTensor CreateTensorFromImageFile(string file, TFDataType destinationDataType = TFDataType.Float)
    {
        //Thread.Sleep(500);
        var contents = File.ReadAllBytes(file);

        // DecodeJpeg uses a scalar String-valued tensor as input.
        var tensor = TFTensor.CreateString(contents);

        TFOutput input, output;

        // Construct a graph to normalize the image
        using (var g = ConstructGraphToNormalizeImage(out input, out output, destinationDataType))
        {
            // Execute that graph to normalize this one image
            using (var sess = new TFSession(g))
            {
            var normalized = sess.Run(
            inputs: new[] { input },
            inputValues: new[] { tensor },
            outputs: new[] { output });

            return normalized[0];
            }
        }
    }
    private static TFGraph ConstructGraphToNormalizeImage(out TFOutput input, out TFOutput output, TFDataType destinationDataType = TFDataType.Float)
    {
        // 以下四个参数,根据模型的输入层确定
        const int W = 299; 
        const int H = 299; 
        const float Mean = 128;
        const float Scale = 128;

        var graph = new TFGraph();
        input = graph.Placeholder(TFDataType.String);

        output = graph.Cast(
        graph.Div(x: graph.Sub(x: graph.ResizeBilinear(images: graph.ExpandDims(input: graph.Cast(graph.DecodeJpeg(contents: input, channels: 3), DstT: TFDataType.Float),
        dim: graph.Const(0, "make_batch")),
        size: graph.Const(new int[] { W, H }, "INPUT_SIZE")),
        y: graph.Const(Mean, "IMAGE_MEAN")),
        y: graph.Const(Scale, "IMAGE_STD")), destinationDataType);

        return graph;
    }

}

3. 注意事项

  1. graph.Import()函数有可能报错,如果是 netstandard相关的错误,可通过将项目的 .Net Framework升至更新版解决。

  2. 对于该行代码:

    runner.AddInput(graph["Mul"][0], tensor);
    var r = runner.Run(graph.Softmax(graph["final_result"][0]));
    

    其中的 Mul 和 final_result 是模型的输入层和输出层的 layername,因此因模型而异。对于所使用的模型,可通过:

    List<TFOperation> op_list = new List<TFOperation>(graph.GetEnumerator());
    

    获取所有层的名称。

  3. 在调试过程中,

  4. 垃圾回收机制导致的报错’CallbackOnCollectedDelegate' ,升级到1.3以后就没有了

参考

by Jason Han

理解 Softmax

在机器学习尤其是深度学习中,Softmax 是个非常常用而且比较重要的函数,尤其在多分类的场景中使用广泛。通常,Softmax 函数被置于最后一个全连接层之后,把全连接层的输出映射为 0-1 之间的实数,并且归一化保证和为 1。

1. Softmax 定义

$$ S(a):\left[\begin{matrix} a_1 \\ a_2 \\ \cdots \\ a_N \end{matrix} \right] \rightarrow \left[\begin{matrix} S_1 \\ S_2 \\ \cdots \\ S_N \end{matrix} \right] \tag{1} $$
$$ S_i=\frac{e^{a_j}}{\sum_{k=1}^{N}e^{a_k}} \tag{2} $$ ## 2. 为什么要使用 Softmax

Softmax 函数主要用于分类,与之对应的我们姑且称之为 Hardmax。对于一个分类结果,如 $$ [1,2,3] $$ 如果使用 Hardmax,那就意味着选出的是其中的最大值,即3;而 Softmax 则是以一定的概率选出最终的值,根据公式(2),上面三个值得概率分别为: $$ [0.09, 0.24, 0.67] $$ 相对于 Softmax,Hardmax 在进行梯度计算时有着明显的缺陷:只有在被选中的那个变量上面才有梯度,其他都是没有的。Softmax 则不存在这一问题,具体的梯度求解公式可见下一节。

其实,最常拿来与 Softmax 比较的不是 Hardmax,而是标准的归一化方法,即将所有输出值分别处以这些值的总和。根据这种方法,上面的结果的到的三个概率为: $$ [0.17, 0.33, 0.50] $$ 至于为什么 Softmax 更好,参考文献 1,2中各路大神讲解的较为深入。就个人理解而言,其主要原因在于平衡概率分布3以及配合交叉熵使用。

3. 交叉熵

(待更新)

参考


  1. https://zhuanlan.zhihu.com/p/52064877 ↩︎

  2. https://www.zhihu.com/question/310506852 ↩︎

  3. https://blog.csdn.net/Flying_sfeng/article/details/80927098 ↩︎

by Jason Han

Hugo 问题汇总

1. 如何在Hugo中插入Mermaid流程图

Mermaid是Markdown中最常用的绘制流程图的方式之一。不过,在某些Hugo模板中,并不能直接正常显示Mermaid流程图。解决办法如下1,2

  1. themes/(你的模板名称)/layouts/中增加shortcodes文件夹,并创建文件mermaid.html
<script async src="/js/mermaid.js"></script>
<div class="mermaid">
{{ .Inner }}
</div>

此处对于mermaid.js脚本设置的async,即流程图与网页的其余部分异步执行,提高整个页面的显示效果

  1. 下载 mermaid.js (与上述代码片段中的文件名对应),并添加至themes/(你的模板名称)/static/js/文件夹。
  2. 在绘制流程图时,不再```mermai开头和```结尾;而是以{{<mermaid>}}开头,以{{</mermaid>}}结尾。
  3. 需要注意的是,在设置图类型的一行的末尾,要加上分号,如 graph TD;。不过这一点据说也因mermaid 的版本而异。

2. 如何在Hugo中插入公式

利用Hugo写的文章无法直接正常的显示数学公式,不过Hugo支持使用MathJax对数学公式进行渲染,Hugo官方提供了相应的配置方法3。不过,经过测试,这种方法很多情况下不能奏效,经多方尝试,文献 4 提供的方法是可行的,总结如下:

  1. 在模板的 layouts/partials/ 路径下创建 mathjax.html 文件,内容为:
<script type="text/javascript"
async
src="https://cdn.bootcss.com/mathjax/2.7.3/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
MathJax.Hub.Config({
tex2jax: {
inlineMath: [['$','$'], ['\\(','\\)']],
displayMath: [['$$','$$'], ['\[\[','\]\]']],
processEscapes: true,
processEnvironments: true,
skipTags: ['script', 'noscript', 'style', 'textarea', 'pre'],
TeX: { equationNumbers: { autoNumber: "AMS" },
extensions: ["AMSmath.js", "AMSsymbols.js"] }
}
});

MathJax.Hub.Queue(function() {
// Fix <code> tags after MathJax finishes running. This is a
// hack to overcome a shortcoming of Markdown. Discussion at
// https://github.com/mojombo/jekyll/issues/199
var all = MathJax.Hub.getAllJax(), i;
for(i = 0; i < all.length; i += 1) {
all[i].SourceElement().parentNode.className += ' has-jax';
}
});
</script>

<style>
code.has-jax {
font: inherit;
font-size: 100%;
background: inherit;
border: inherit;
color: #515151;
}
</style>
  1. 在模板的 layouts/partials/footer.html 文件最后追加一句:
{{ partial "mathjax.html" . }}

3. 在Hugo中插入矩阵不能正常显示

虽然在 Hugo 能插入公式,但是当插入矩阵时,其显示可能出问题,如:

当输入以下代码时:

$$
\begin{matrix}
1 & x & x^2 \\
1 & y & y^2 \\
1 & z & z^2 \\
\end{matrix}
$$

显示可能为: $$ \left[\begin{matrix} 1 & x & x^2 \
1 & y & y^2 \
1 & z & z^2 \
\end{matrix} \right] $$ 尝试了参考 5 的办法,在上述代码片段前后分别加上<div></div>可正常显示,如下:

$$ \left[\begin{matrix} 1 & x & x^2 \\ 1 & y & y^2 \\ 1 & z & z^2 \\ \end{matrix} \right] $$

4. 点击标题连接后显示未找到页面

可能是因为文件命名时是存在特殊符号,如“#”,在写C#相关的文章是,需要注意可以把文件名称中的#替换成“Sharp”。

参考


  1. https://osgav.run/lab/hugo-mermaid-diagrams.html ↩︎

  2. https://www.hairizuan.com/rendering-diagrams-in-hugo/ ↩︎

  3. https://www.gohugo.org/doc/tutorials/mathjax/ ↩︎

  4. https://note.qidong.name/2018/03/hugo-mathjax/ ↩︎

  5. https://github.com/gohugoio/hugo/issues/2893 ↩︎

by Jason Han

从 GPS 坐标到工程坐标

测量是所有建筑施工的基础,而实现坐标信息从地理坐标系到工程坐标系的转换是整个测量工作的前提。坐标的原始信息来自 GPS,其格式为 BLH 经纬度。然而,实际工程中所使用的是 XYH 平面工程坐标系,本文的主要内容就是介绍如何实现从做原始的 GPS 坐标到工程坐标的转换。

1. 总体流程

将原始的GPS经纬度信息转换为可用的工程坐标信息的总体流程如下:

flowchart TD; GPS1["1. 获取 GPS 坐标,此时为经纬度"]-->TranE["2. 空间坐标系转换(七参数法,可选)"] TranE-->Proj["3. 投影转换,得到XYH工程坐标"]-->Trans4P["4. 平面坐标系转换(四参数法,可选),得到当前工程下的局部坐标"] Trans4P-->Second["5. 坐标的二次校核"]
图1 总体流程图

上述过程的主体是步骤 1 和 3,即把空间的经纬度坐标(BLH)投影成为工程可用的平面坐标(XYH)。目前国内常用的投影方式为高斯-克吕格投影,国际中较为常用的是 UTM 投影,具体都有相关的公式,本文不再赘述。下面对其余步骤做一些说明。

2. 基准转换

比较需要注意的是步骤 2 和 4,这两个步骤所做的都是一件事——基准转换。基准转换是指空间点在不同椭球的坐标转换,其有两种方式,分别为空间坐标转换(以七参数法为代表)和平面坐标转换方法(以四参数法为代表)。一般这两种方式不会同时采用:当工程区域较大时,倾向于直接进行空间坐标转换;当工程区域较小时,习惯上采用平面坐标转换并配合高程拟合。

2.1. 大地基准

大地基准是建立国家大地坐标系统和推算国家大地控制网中各点大地坐标的基本依据 1。建立大地基准就是用一个椭球代表地球形体,就是建立椭球系统。

在椭球系统中描述地理位置用的就是经纬度。我们常说:两对数值相同的经纬度在地理上可能代表的是两个完全不同的位置。其原因就在于它们所使用的椭球系统可能不同。仅在我国,所涉及的椭球系统就包括北京54、西安80和国家2000,而目前在世界上应用最为广泛的是美国的 WGS84。不同椭球系统之间的差别在于椭球的形状参数。

你可能会问,地球的形状是固定的,为什么要造出来这么多种椭球系统呢?答案可参见 2。简而简之,一是随着测绘技术(包括数学、物理学、光学等)的发展,人们描述地球的方式慢慢进步,得出了越来越精确的椭球系统;二是不同的椭球系统的侧重点各有不同。比如,我国的北京54是1954年在苏联的协助下建立起来的,其所设定的是以他们国家为中心。后来我国的测绘技术逐渐成熟,在1980年提出了西安80,建立了以我们自己为中心的坐标系。再到后来,随着技术的进一步完善,我国建立了误差更小的国家2000坐标系。目前定位设备中大部分都采用是美国的 WGS84,而我国近年来发展起来的北斗采用的则为国家2000,不过两种椭球参数之间的差别非常微小。

2.2. 地球坐标系

地球坐标系可分为大地坐标系和空间直角坐标系。在大地坐标系(也可以称之为椭球坐标系)中描述地理位置使用的是经纬度 (B, L, H),在空间直角坐标系中则使用 (X, Y, Z),两套描述方式间有明确的数学关系。

2.3. 空间坐标转换

空间坐标转换的常用方法为七参数法,7个参数包括3个平移参数、3个旋转参数和1个尺度参数。实现空间坐标转换的步骤为:

graph TB 椭球系统A下的BLH-->空间直角坐标系A中的XYZ--七参数法转换-->空间直角坐标系B中的XYZ-->椭球系统B下的BLH

图2 椭球坐标系转换流程

简而言之,七参数法的作用是实现两个空间直角坐标系之间的仿射变换。另外,在精度要求不高时,也可以将七参数法换成三参数法,3个参数即平移参数。

2.4. 平面坐标转换

平面坐标转换最常用的方法为四参数法,4个参数包括2个平移参数、1个旋转参数和1个尺度因子。需要注意的是,平面坐标转换的过程并不涉及高程,仅仅是对投影后的(X, Y)坐标进行变换。因此,通常要配合高程拟合,如固定差拟合、一次线性拟合和二次线性拟合。

3. 坐标的二次校核

在完成基准转换之后,为进一步提高精度,需要再利用一个额外的控制点进行坐标的二次校核:将一个额外控制点的工程坐标 (X, Y, H) 与定位设备采集到的该点的坐标(X', Y', H')相减,得到二次校核的参数 ($\Delta$X, $\Delta$Y, $\Delta$Z)。

另外需要注意一点,对于同一工程,二次校核的参数也是有可能不同的。因为,出于各种原因,整个差分网络有可能需要重启,重启后的差分网络将形成新的虚拟参考站。在这种情况下,新的测量值与之前的测量值会有一定的误差,需要重新进行二次校核。

4. 总结

本文主要的主要目的是梳理将定位设备中采集到的 GPS 经纬度信息转化为工程平面坐标的整个流程。其中每个步骤均涉及一系列公式和方法,不过这些并非本文的重点,读者可通过关键词自行搜索。

参考文献


  1. https://baike.baidu.com/item/%E5%A4%A7%E5%9C%B0%E5%9F%BA%E5%87%86 ↩︎

  2. https://www.zhihu.com/question/65799701 ↩︎

by Jason Han

一些裂隙相关指标的含义与计算方法

1. P32

单位体积岩体内裂隙的总面积。3代表三维 ,指岩体体积;2代表二维,指裂隙的面积。主要有以下两种计算方式:

(1) 根据钻孔数据计算

钻孔中裂隙示意图.png

计算公式为:

$ P_{32}=\frac{\sum_i^n\pi\sigma^2/4/<\omega_i,\alpha>}{L\times\pi\sigma^2/4}=\sum_i^n\frac{1}{L\times<\omega_i,\alpha>} $

(2) 根据出露面的迹线计算

2. P31

单位体积岩体内裂隙的总数。3代表三维,指岩体体积;1代表一维,指裂隙的个数。主要有以下两种计算方式:

(1) 根据钻孔数据计算

计算公式为: $ P_{31}=\frac{n}{L\times \pi\sigma^2/4} $

(2) 根据出露面的迹线计算

by Jason Han

重新理解MLE,MAP和贝叶斯估计

1. MLE,极大似然估计

MLE把带估计的参数看做确定的量,其目标函数是使得以观察到的样本的概率最大: $$ \operatorname{argmax}p(X \mid \theta)=\operatorname{argmax}\prod_{x_1}^{x_n} p(x_i \mid \theta) \tag{1} $$ 对数化处理后为: $$ \operatorname{argmax}\prod_{x_1}^{x_n} p(x_i \mid \theta)=\operatorname{argmax}\sum_{x_1}^{x_n} \log p(x_i \mid \theta) \tag{2} $$ 即:

$$ \left\{ \begin{aligned} &L(\theta) = \sum_{x_1}^{x_n} \log p(x_i \mid \theta)\\ &\frac{\partial L}{\partial \theta} =0\\ \end{aligned} \right. \tag{3} $$

2. MAP,最大后验估计

MAP寻求的是能使后验概率$ P(\theta \mid X) $最大的 $ \theta $ 值:

$$ \begin{aligned} \operatorname{argmax}p(\theta \mid X) &=\operatorname{argmax} \frac{p(X \mid \theta) p(\theta)}{p(X)} \\ &=\operatorname{argmax}p(X \mid \theta) p(\theta)\\ &=\operatorname{argmax}\left(\prod_{x_1}^{x_n} p(x_i \mid \theta)\right) p(\theta) \end{aligned} \tag{4} \ $$
省略$P(X)$是因为$X$与$\theta$无关。对数化处理后,上式可表达为: $$ \operatorname{argmax}\left(\sum_{x_1}^{x_n} \log p(x_i \mid \theta)+\log p(\theta)\right) \tag{5} \ $$ 相比于式(2),式(4)仅仅是多了一项 $p(\theta)$ ,这就是参数$\theta$的先验分布 。在实际中,当人们已经接受或知道的普遍的规律时,可以对预先给出这一规律。比如在扔硬币的试验中,每次抛出正面发生的概率应该服从一个概率分布,且基本在0.5处去最大值,这就是先验分布。对于先验分布,一般会给出一个超参数: $$ p(\theta)=p(\theta \mid \alpha) \tag{6} $$ 式(7)中超参数的求解,与极大似然估计一样,求导等于0。

3. 贝叶斯估计

在MLE和MAP中,都是通过对函数求极值确定参数,都不会考虑$p(X)$;而在贝叶斯估计中,不再考虑极值,而是使用$$p(X)$$ 直接取出$p(\theta \mid X)$: $$ p(\theta \mid X)=\frac{p(X \mid \theta) p(\theta)}{p(X)} \tag{7} $$ 然后对于一个样本$x$,可计算其概率: $$ p(x \mid X)=\int_{\theta \in \Theta}p(x \mid \theta)p(\theta \mid X)\operatorname{d}\theta \tag{8} $$

参考

[1] https://blog.csdn.net/pipisorry/article/details/51471222

This page and its contents are copyright © 2021, Jason Han.