Cocoa

多用户 Docker 环境下 PyPi 源按需加速

这一篇算是接在上一篇Build a super fast on demand local PyPi mirror的后面吧~

这里会以 docker-compose 的方式为例子,详细写一下~不使用docker-compose的话,则也仅仅需要手动指定 pypicache 与需要这个服务的 container 到同一个 docker 网络中,这样就可以不用去找 pypicache 的 IP 地址,对最终用户透明化,不用增加额外的 pip 安装参数,即可轻松享受本地高速缓存,特别是对于大一点的文件效果更明显~

jupyterhub-docker
jupyterhub-docker

Continue reading 多用户 Docker 环境下 PyPi 源按需加速

Build a super fast on demand local PyPi mirror

  • 当公司/局域网里有多人都使用 Python 开发,并且几乎都会用到 pip 来部署环境时,虽然已经有各种镜像源了,但是下载仍受限于与外网的宽带速度,并且同样的包可能被多人下载了多次,在包较大时,重复花的时间并不值
  • 当你使用 Docker 来构建不同的 Python 应用/环境时,在测试 Dockerfile 时可能需要不断的删掉之前 build 的版本,从头开始 build 时,pip 下载与上面面临同样的问题——重复消耗不必要的时间

其一解决方案是公司/局域网内部搞一个 PyPi 的镜像源,实际上维护一个完整的镜像源相当麻烦,占用的储存空间太大,在公司/局域网的情况下,大家开发的东西、使用的技术栈相对比较固定,这就导致完整的镜像源里会有很多包其实几乎没人用。

其二的解决方案可以是预先构建好一个或多个 Docker 镜像,其中包含大家都会用到的包,剩余的一些包则在使用时才被少数需要的人安装。这种方案的缺点则是目前 Docker 服务 + 多用户方案在重启之后会丢掉已经配置过的环境,重启之后依旧需要从镜像源下载包。

那么这里相对一劳永逸的方案则是搭建一个本地的按需下载的 PyPi 镜像源,其原理则是在镜像源与公司/局域网内增加了一个高速缓存,并且由于 PyPi 已经提交分发的whl或者tar.gz是不会变的,因此不用顾虑缓存时间的设置。

最后就像这样~ 182KB/s VS. 36.4MB/s
(cache server为千兆有线链接,MacBook为802.11 AC,测试时链接速度585Mbps)

It's apparently super fast after being cached!
It's apparently super fast after being cached!

Continue reading Build a super fast on demand local PyPi mirror

A brief tutorial on setup an AI lab server for a small team

这个是在之前导师的实验室积累的一些东西,使用场景的话,是适用于2-8人左右的小团队吧,当时有两台机器,一台是放在学校机房的服务器,CPU没注意是什么,印象中是64G内存,4块P20,貌似24G显存?;另一台机器则放在办公室,主要配置的话,一颗AMD Ryzen 2700X,64G内存,再附加两块1080ti 11G,经费肯定是还做不到一人分一块GPU,部分模型的大小也不需要完全独占一块GPU。但是构建一个小型团队使用的AI Lab服务器是没问题了。

当时搭建的AI Lab服务器的主要架构如下

AI Lab Platform Architecture
AI Lab Platform Architecture

系统方面选择了Ubuntu 18.04 LTS,简单方便,毕竟是做AI不是做OS,没有任何必要引入其他方面复杂的操作。然后在这之上则是系统层面的GPU驱动,当时对应的版本为396.26,目前已经有400版本号的驱动了。接下来就是与docker对接的nvidia的runc,由这个runc去给docker内的GPU提供支持。随后当时则是使用了支持多用户的JupyterHub,当然也可以通过分配多个账号解决,这一部分和之后的部分解决方案就很多了。

Continue reading A brief tutorial on setup an AI lab server for a small team

SIGGRAPH 2018 「Semantic Soft Segmentation」复现笔记

santa
santa

SIGGRAPH 2018这篇论文主要分为两大部分,第一部分是 DeepLab v2+ResNet101 训练出来用于获取输入图像的 high-level feature 网络,对于输入的 $I = (h, w, 3)$ 图像,为每一个像素点生成一个 128 维的特征向量,因此该网络的输出是 $F = (h, w, 128)$

接下来,使用 $F$ 和 $I$ 进行引导滤波,$F_{filtered} = imguidedfilter(F, I, 10, 0.01)$,在引导滤波这个地方,OpenCV中的 cv2.ximgproc.guided_filter 与原作者使用的 matlab 中实现的 imguidedfilter 有不小区别,于是我对着 matlab 中 imguidedfilter 的实现重写了一下 OpenCV 版的,imguidedfilter-opencv

在计算完了 $F_{filtered}$ 之后,利用 PCA 将它压缩到 3 维,$F_{PCA} = PCA(F_{filtered}, 3)$,如下图。

santa PCA
santa PCA

Continue reading SIGGRAPH 2018 「Semantic Soft Segmentation」复现笔记

Dijkstra's Shortest Path First algorithm

#include <iostream>
#include <vector>
#include <stack>

using namespace std;

/*
 输入
 6
 9
 0 1 2
 0 2 3
 1 2 2
 1 3 4
 2 3 1
 2 4 3
 3 4 2
 3 5 7
 4 5 4
 */
int main(int argc, const char * argv[]) {
    // 一共有多少个顶点
    int n;
    cin >> n;
    
    // 初始化这个
    int64_t * graph = new int64_t[n * n];
    for (int row = 0; row < n; row++) {
        for (int col = 0; col < n; col++) {
            graph[row * n + col] = INT64_MAX;
        }
    }
    
    // 一共有多少组边
    int p;
    cin >> p;
    for (int i = 0; i < p; i++) {
        // 依次输入每一条边
        int start, end, distance;
        cin >> start >> end >> distance;
        // 假设我们是无向图
        // 无向图具有对称性~
        graph[start * n + end] = distance;
        graph[end * n + start] = distance;
    }
    
    // 保存定点是否被访问过
    vector<bool> visited;
    visited.resize(n);
    
    // 保存源点到各点的最短距离
    vector<int64_t> distance;
    distance.resize(n);
    // 起始点到自己的距离是0
    distance[0] = 0;
    // 其余各点都是正无穷
    for (int i = 1; i < n; i++) {
        distance[i] = INT64_MAX;
    }
    
    // 保存路径的数组
    vector<int> path;
    path.resize(n);
    
    // 从源点开始吧/
    int current = 0;
    while (true) {
        // 标记当前节点已经访问
        visited[current] = true;
        // 找出当前点可以一步访问到的所有的点
        for (int i = 0; i < n; i++) {
            if (visited[i] == false && // i 没有被访问过
                graph[current * n + i] != INT64_MAX // 并且current可以走到i这个顶点
                ) {
                //  0 ->...-> current -> i
                int64_t access_i_via_current = distance[current] + graph[current * n + i];
                //  0 ->...-> i
                int64_t access_i_via_other_path = distance[i];
                
                // 如果经过当前的点
                // 可以使得到 i 的距离变短的话
                if (access_i_via_current < access_i_via_other_path) {
                    // 那么更新路径~下面这个赋值的意思是
                    // 下标 i 这个点
                    // 应该从 current 来更近
                    path[i] = current;
                    // 更新距离
                    distance[i] = access_i_via_current;
                }
            }
        }
        
        int64_t new_minimum = INT64_MAX;
        for (int i = 0; i < n; i++) {
            // 从还没有访问过的点中
            if (visited[i] == false) {
                // 找出有着最小距离的点扩展
                if (distance[i] < new_minimum) {
                    current = i;
                    new_minimum = distance[i];
                }
            }
        }
        
        // 如果所有的点都访问过了
        // 那么这个 new_minimum 就还是初始值 +INF
        if (new_minimum == INT64_MAX) {
            // 此时就说明可以结束了
            break;
        }
    }
    
    cout << "minimum distance 0->" << n-1 << " is " << distance[n - 1] << '\n';
    
    // 保存最短路的路径
    stack<int> minimum_path;
    // 从最后一个点往回看
    current = n - 1;
    minimum_path.push(current);
    while (minimum_path.top() != 0) {
        // 从某个点到 current 是最近的
        // 把那个点放进去~
        minimum_path.push(path[current]);
        // 再来准备看是哪个点  那个点最近
        current = path[current];
    }
    
    // 按顺序输出整条路径
    while (!minimum_path.empty()) {
        cout << minimum_path.top() << ", ";
        minimum_path.pop();
    }
    cout << endl;
}

01背包~四次元背包里都会装上什么呢~\(≧▽≦)/

啊w 01背包呐~给出一堆不可拆分的东西,它们有各自的重量和对你来说相应的价值,但是你的背包能装的最大重量是有限的,这个时候要如何选择装哪些东西使得背包里的东西有着最大的总价值呢~

Continue reading 01背包~四次元背包里都会装上什么呢~\(≧▽≦)/

来下围棋吧~TensorFlow minigo~

TensorFlow 早些天发布了一个名为 minigo 的项目,因为 Google 官方还一直没有开源 AlphaZero,那么就先来看看 minigo 怎么玩(搞事情)吧www

假设乃使用的是 macOS / Ubuntu / debian,当然其他系统也可以,操作大同小异。

首先是安装 Python 3,macOS 下默认是 python2.7,新的 Python 3 需要去 Python 官网下载,https://www.python.org/downloads/。对于 Ubuntu / debian 来说的话,则是直接

apt-get install python3

当然,比较新的 Ubuntu / debian 都会默认安装 python3.6~

Continue reading 来下围棋吧~TensorFlow minigo~

由前序遍历和中序遍历重构二叉树w

#include <iostream>
#include <vector>

using namespace std;

struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

TreeNode * rebin(vector<int> pre, vector<int> in) {
    // 首先看看有米有东西吧
    // 一个元素都没有的话
    // 这个二叉树就只有一个NULL节点
    // 直接返回即可w
    if (pre.size() == 0) return nullptr;
    
    // 根据前序遍历的定义 根节点一定在前序遍历结果的第一个位置上
    // 而中序遍历的话 根结点必然需要等到它的左子树都输出完才会出现
    // 利用这一点我们分开左右子树
    int root_value = pre[0];
    TreeNode * root = new TreeNode(root_value);
    // 找到根节点在中序遍历里的下标
    // 以此分开左右子树
    int root_index_of_in = 0;
    for (int i = 0; i < in.size(); i++) {
        if (in[i] == root_value) {
            root_index_of_in = i;
            break;
        }
    }
    // 分开左右子树
    // 按原始的顺序分别保存左右子树的前序遍历和中序遍历
    vector<int> left_pre, left_in, right_pre, right_in;
    // 先是左子树
    for (int i = 0; i < root_index_of_in; i++) {
        // 这里需要+1
        // 因为前序遍历的话 第一个元素是根节点
        // 所以需要跳过当前(下标为0)的这个元素
        left_pre.emplace_back(pre[i+1]);
        left_in.emplace_back(in[i]);
    }
    // 然后是右子树
    for (int i = root_index_of_in + 1; i < in.size(); i++) {
        right_pre.emplace_back(pre[i]);
        right_in.emplace_back(in[i]);
    }
    // 既然左右子树的前序遍历和中序遍历都有了
    // 和我们一开始拿到的初始条件很相似吧~
    // 那么递归吧w
    root->left = rebin(left_pre, left_in);
    root->right = rebin(right_pre, right_in);
    return root;
}

// 三种遍历方式~/
typedef enum {
    PRE_ORDER,
    IN_ORDER,
    POST_ORDER,
} TreeTraversal;

void print_with_order(TreeNode * root, TreeTraversal order) {
    // 如果根节点是空的
    // 那啥都不用说直接返回吧
    if (root == nullptr) return;
    
    // 看看使用什么序来遍历
    // 本质上仅仅只是输出当前root节点的位置不一样
    if (order == PRE_ORDER) {
        cout << root->val << ' ';
        print_with_order(root->left, order);
        print_with_order(root->right, order);
    } else if (order == IN_ORDER) {
        print_with_order(root->left, order);
        cout << root->val << ' ';
        print_with_order(root->right, order);
    } else {
        print_with_order(root->left, order);
        print_with_order(root->right, order);
        cout << root->val << ' ';
    }
}

int main(int argc, const char * argv[]) {
    vector<int> pre = {1,2,4,7,3,5,6,8};
    vector<int> in = {4,7,2,1,5,3,8,6};
    TreeNode * head = rebin(pre, in);
    
    // 用构建好的树输出前序和中序遍历来验证~
    cout << "pre: ";
    print_with_order(head, PRE_ORDER);
    cout << endl;
    
    cout << "in: ";
    print_with_order(head, IN_ORDER);
    cout << endl;
}

いまが最高!