分类 编程语言 下的文章

最近再考虑用Mathematica实现一些基本的机器学习功能。因为在初步接触到科学计算工具的时候,显然会发现Mathematica的语言更友好,至少详细的离线文档和中文的即时函数名显示已经让我觉得很好了。Matlab没怎么用过新版本的,不过似乎好像周围人用的都是英文版。。这就导致我下定决心,同为多功能的科学计算工具,一定要在Mathematica上实现Matlab上的,至少大部分功能。

然后问题出在了循环上。在执行代码的时候,卡在For循环这里只执行一遍。自己试了很久,发现跟之间建立的矩阵变量有关,只要一使用这些变量,不但只执行一遍,甚至还会修改循环变量的显示值。

大致代码:

data := {{x1, x2, x3, y}, ...}
dataX := data[[1;;3]]
dataY := data[[4]]

For[a = 1, a <= 10,
  dataX;
  Print[a];
  a += 1;
]

其中只有dataX的一行用于省略表示含有该数据输入的参数更新过程。很奇怪,在我用的时候(版本10.4),只要在循环里使用了dataX或者dataY的时候,循环只会执行一次,出来的结果也十分奇怪(不可描述),且输出a的值固定变为了10。包括自己debug和在某度上查找的结果,都无法解释这个问题。

然后我又来推销stackoverflow了: https://mathematica.stackexchange.com/questions/134609/why-should-i-avoid-the-for-loop-in-mathematica

For循环语法:

For[循环变量初值,循环结束条件,循环体(分号分隔多语句)]

Do循环语法:

Do[循环体(分号分隔多语句),{循环变量名,初值,结束值(包含)}]

这里提到说,Mathematica里的For循环的循环变量的作用域是开放的,和传统的C/C++不一样,在外部也能调用。要达到相同的效果,需要使用Module函数将其声明为局部变量。然后就推荐了Do循环。Do循环有着类似于我们常用的C语言for循环的功能,以变量的初始值和结束值为条件,执行循环体,而且还可以直接支持多变量循环。相比于For循环,Do循环的循环变量是局部的。

然后就有了:

...
Do[
  dataX;
  Print[a],
  {a, 1, 10}
]

换了循环语句之后,似乎问题就解决了。。

最近看到rpcs3中用到了asmjit,就去其官方项目上看了一下,根据描述是个即时生成目标平台汇编代码的库,但是看了一下样例之后发现有些东西还是没法完全理解。其中之一就是ret指令。在样例代码中是这么写的:

  ...
  x86::Assembler a(&code);
  a.mov(x86::eax, 2);
  a.add(x86::eax, -3);
  a.ret();
  ...

然后就想,既然Assembler模拟的是汇编指令,那么ret指令返回EAX的值会不会是某种约定呢? 然后打算查阅Intel的汇编指令手册,官方名叫Intel® 64 and IA-32 Architectures Software Developer’s Manual,里面有对指令的详细介绍。看到ret指令对应的伪代码有整整几页,有点慌啊。。 其中有这么几句:

IF top 4 bytes of stack not within stack limits
    THEN #SS(0); FI;
EIP ← Pop();

当时就对这个#SS(0);有点迷惑。然后在谷歌上查到了相似的使用方法,但是仍然没看到正式的定义。但是在一个网站上可能看到了类似的定义(http://www.scs.stanford.edu/05au-cs240c/lab/i386/RET.htm,惊了,是斯坦福):

Protected Mode Exceptions #GP, #NP, or #SS, as described under "Operation" above; #PF(fault-code) for a page fault

然后便开始猜测#<abbr>(<val>)的用法为带返回码的异常抛出:其中PF对应page fault的话,根据搜索,SS对应的为stack smashing,即栈溢出;GP对应general peotection,该机制旨在确保程序访问有效的地址区域;但是NP这个not present就有点搞不懂了。。似乎是指什么不存在?

虽然还是有对不上的。。但是大部分意义都是可以对上的,因而认为猜测正确。

总而言之,这个格式的意义是明白了:Intel使用的伪代码中的异常抛出语句。

virtualenv的设计还是让人有些摸不着头脑的。。使用virtualenv命令配置好venv之后,实际上是相当于进行了安装的操作。安装的绝对路径被写入了文件中。有些版本(linux?)的venv使用的是.py脚本格式的pip,而新版Windows使用的似乎是exe文件。脚本格式的很好改,把VIRTUAL_ENV改为目标目录路径即可。Windows下exe格式的pip,把路径信息写在了文件末尾,因而需要使用WinHex一类的工具,在不破坏文件原本结构的情况下,对附加数据进行修改。对于easy_install也是一样的操作。

改编自 https://blog.csdn.net/Vipbinn/article/details/82978003 ->来源追踪:https://blog.csdn.net/chengshuhao1991/article/details/78545723

在通过TensorFlow中文社区TensorFly对TensorFlow框架进行了解的时候,对于入门的第一个例子进行了逐行的学习,对于数据的生成、表示,以及各个函数的作用有了一定的认识。其中reduce_mean()函数的定义和作用让我觉得很有意思,但又无法确认对其的理解,在网络上浏览了一些博客,发现有不少与我的理解是相同的,因此记录下来。

使用任意具有代码补全功能的IDE,在完成导入工作(即import tensorflow as tf)后,输入tf.reduce_,会得到大量reduce开头的函数补全提示,其中就包含reduce_mean()

文档中列出的所有操作为: reduce_all(...) 逻辑与 reduce_any(...) 逻辑或 reduce_euclidean_norm(...) 欧几里得范数(Euclidean norm of elements)->\sqrt{\sigma_{i=1}^{n} x_{i}^{2}} reduce_logsumexp(...) 如同名字,先取指数幂,求和后取对数 -> \log_{10} \sigma_{i=1}^{n} e^{x} reduce_max(...) 最大值 reduce_mean(...) 平均值 reduce_min(...) 最小值 reduce_prod(...) 乘积 reduce_std(...) 标准差 reduce_sum(...) 求和 reduce_variance(...) 方差

其中关于reduce_mean(),官方给出的解释如下:

tf.math.reduce_mean(
    input_tensor,
    axis=None,
    keepdims=None,
    name=None,
    reduction_indices=None,
    keep_dims=None
)

Reduces input_tensor along the dimensions given in axis. Unless keepdims is true, the rank of the tensor is reduced by 1 for each entry in axis. If keepdims is true, the reduced dimensions are retained with length 1.

If axis is None, all dimensions are reduced, and a tensor with a single element is returned.

其中,axis参数指定了计算过程中需要“降维”的维度,若是不传值的话,默认对全部维度采取降维操作,返回的是一个单独的值(维度为0),否则将按照指定的维度执行降维操作。而keepdims参数则用于申明要求在操作过程中不降低维度,由于各类reduce操作均属于聚合操作,因而该参数的实际含义为在计算完成后,为其保留一对方括号(即一个维度)。

咕果的开发团队把之前版本的参数名keep_dims改为了keepdims,所以如果使用的是比较老的版本的话,可能需要考虑改一下函数名= =(还算良心地没有直接去除对老参数名的支持)

在文档给出的例子中,使用输入张量

x = tf.constant(
  [[1., 1.],
   [2., 2.]]
)

对于两个常用参数(axis, keepdims)的测试,结果分别如下: 不传值的情况下:

>>> op = tf.reduce_mean(x)
>>> sess.run(op)
1.5

指定axis的情况下:

>>> op = tf.reduce_mean(x, axis = 0)
>>> sess.run(op)
array([1.5, 1.5], dtype=float32)

>>> op = tf.reduce_mean(x, axis = [1])
>>> sess.run(op)
array([1., 2.], dtype=float32)

>>> op = tf.reduce_mean(x, axis = [0, 1])
>>> sess.run(op)
1.5

指定keepdims=True,即保留维度的情况下:

>>> op = tf.reduce_mean(x, keepdims = True)
>>> sess.run(op)
array([[1.5]], dtype=float32)

>>> op = tf.reduce_mean(x, axis = [1], keepdims = True)
>>> sess.run(op)
array([[1.],
       [2.]], dtype=float32)

由此,两个常用参数与函数本身的作用就能够比较清晰地展示出来了。

那么,回到标题:既然都是聚合函数,为什么要在函数名前加一个reduce_前缀呢? 根据上文的描述,答案应该是比较清晰了:在进行了这些聚合操作之后,TensorFlow会默认将结果作为数据返回,也就是说,不论你的axis参数填了没,填了什么,在输入张量的至少某一个维度,一定会进行聚合操作,而聚合操作之后,数据合而为一,降低了输入张量的维度。而在英语中,reduce作为动词,有着减少,缩小(尺寸、数量、价格等)的意思,在此可以引申为在维度数量上的缩小。这样理解之后,看着函数名,对于其记忆和功能的推测就轻松多了。

  1. 图像的读取/展示/保存

    cv2.imread(file: str)
    cv2.imshow(windowName: str, data: numpy.ndarray) 
    cv2.imwrite(data: numpy.ndarray, file: str)

    其中cv2.imwrite需要与cv2.waitKey()以及cv2.destroyWindow(windowName)配合使用。

  2. 使用cv2.imshow函数时,可能出现的问题:src_depth != CV_16F && src_depth != CV_32S in function 'convertToShow' 就我个人而言,这是由于待展示的图像数据中使用了非法的数据格式造成的。在进行图像拼接并且加入自定义色块的时候,可使用numpy.asarray创建满足要求的图像数据,但是一定要记得指定数据类型dtype=np.uint8,否则虽然可以存储,但是在展示的时候无法通过数据类型检查。

  3. 图像拼接
    np.concatenate(data: tuple, axis=0)

    将需要拼接的图像在非拼接维度转化为相同的大小,然后指定拼接维度(axis)。默认为0,即正数第一个维度。 需要注意的是opencv和numpy所使用的坐标格式相反,numpy同数学矩阵一样以行作为第一坐标(即y值),列作为第二坐标(即x值),与图像,亦即通常使用的笛卡尔直角坐标系相反。