GithubHelp home page GithubHelp logo

mytensor's Introduction

简易tensor语言设计与实现

1.数据结构

class Tensor(object):
    def __init__(self, ndim, shape, data, dtype):
        self.ndim = ndim #维数
        self.shape = shape #形状
        self.data = data #数据
        self.dtype = dtype #类型

包含了基本的属性,ndim代表维度数,shape代表tensor的形状,data代表其中的数据,dtype代表存储的数据类型。

2. 数据初始化

数据生成的方法包括randonezero

def init_tensor(shape, index, data):
    #抽象生成函数
    if index >= len(shape):
        return data
    else:
        temp = []
        for i in range(0, shape[index]):
            temp.append(init_tensor(shape, index+1, data))
        return temp

生成tensor需要的参数包括shapedata,index递归构造过程中传递的参数。

原理:由于是多层结构,因此选择使用递归,目的是为了从底层从下而上构造,每一层通过新构造的[]拼接下一层构造完成的结构,最终就能生成按照shape生成的tensor

def init_random(shape):
    return Tensor(len(shape), shape, random(shape, 0), "Tensor")

def init_one(shape):
    return Tensor(len(shape), shape, init_tensor(shape, 0, 1), "Tensor")

def init_zero(shape):
    return Tensor(len(shape), shape, init_tensor(shape, 0, 0), "Tensor")

通过构造函数包装不同的方法生成对应的tensor

结果测试

shape = [3, 3]
b = init_random(shape)
c = init_one(shape)
d = init_zero(shape)
print("[rand]\nb_ndim:{0}, b_shape:{1}, b_dtype:{2}\nb_data:{3}".format(b.ndim, b.shape, b.dtype, b.data))
print("[one]\nc_ndim:{0}, c_shape:{1}, c_dtype:{2}\nc_data:{3}".format(c.ndim, c.shape, c.dtype, c.data))
print("[zero]\nd_ndim:{0}, d_shape:{1}, d_dtype:{2}\nd_data:{3}".format(d.ndim, d.shape, d.dtype, d.data))

运行结果

[rand]
b_ndim:2, b_shape:[3, 3], b_dtype:Tensor
b_data:[[5, 9, 2], [4, 4, 2], [0, 8, 9]]
[one]
c_ndim:2, c_shape:[3, 3], c_dtype:Tensor
c_data:[[1, 1, 1], [1, 1, 1], [1, 1, 1]]
[zero]
d_ndim:2, d_shape:[3, 3], d_dtype:Tensor

3. 生成指定结构的tensor

由于python支持多层列表的识别,和生成,但是无法得到shape信息,因此需要生成tensor信息还需要分析shape信息。

def analyze_tensor(tensor, shape):
    #分析结构返回shape
    if isinstance(tensor, list):
        shape.append(len(tensor))
        analyze_tensor(tensor[0], shape) 
    return shape

原理:这里同样使用递归来分析,使用isinstance()函数来判断当前遍历层的元素是否为list来判定是否遍历到最底层,在遍历每一层的时候使用len()得到当前层的个数信息,最后返回shape

def init_by_list(data):
    shape = analyze_tensor(data, [])
    return Tensor(len(shape), shape, data, "tensor")

结果测试

z = [[7, 9, 8], [4, 3, 2], [0, 1, 7]]
a = init_by_list(z)
print("[data]\na_ndim:{0}, a_shape:{1}, a_dtype:{2}\na_data:{3}".format(a.ndim, a.shape, a.dtype, a.data))

运行结果:

[data]
a_ndim:2, a_shape:[3, 3], a_dtype:tensor
a_data:[[7, 9, 8], [4, 3, 2], [0, 1, 7]]

语法分析

现在假设通过解析字符串来生成对应的tensor

def analyse_statement(str):
    # 解析输入生成语句
    str_split = str.split('=') # 将输入语句按照等号划分两半
    i = 0
    # 去除分割后数组前后多余空格
    for i in range(len(str_split)):
        str_split[i] = str_split[i].strip()
    # 如果是生成自定义结构,等号后面第一个字符为'['
    if str_split[1][0] == '[':
        print('[生成自定义结构] 变量名:{0}, 参数:{1}'.format(str_split[0], str_split[1]))
        shape = analyze_structure(str_split[1])
        data = "".join(re.split(r"\[|\]| ", str_split[1])) # 去除'[] '
        print('参数:{0}'.format(data))
        data = data.split(',')
        globals()[str_split[0]] = init_by_data(shape, data) # 全局
    # 如果是其他生成
    else:
        if str_split[1][0] == 'r':   
            para = str_split[1][5:-1]
            print('[rand] 变量名:{0}, 形状:{1}'.format(str_split[0], para))
        elif str_split[1][0] == 'o':
            para = str_split[1][4:-1]
            print('[one] 变量名:{0}, 形状:{1}'.format(str_split[0], para))
        elif str_split[1][0] == 'z':
            para = str_split[1][5:-1]
            print('[zero] 变量名:{0}, 形状:{1}'.format(str_split[0], para))
        else:
            print('输入错误')
        shape = para.strip("()").split(',') # 取出参数并存为list
        i = 0
        for i in range(len(shape)): # 去除多余空格并转换为int
             shape[i] = int(shape[i].strip()) 
        
        if str_split[1][0] == 'r': 
            globals()[str_split[0]] = init_random(shape)
        elif str_split[1][0] == 'o':
            globals()[str_split[0]] = init_one(shape)
        elif str_split[1][0] == 'z':
            globals()[str_split[0]] = init_zero(shape)

原理:首先通过等号将语句分成两部分,第一部分为变量名,第二部分为数据部分,将多余的空格去掉后,分析右边的语句,判断是否为按照指定结构生成还是按照函数来生成,分两种情况:

  1. 按照指定结构生成

    需要解析string字符串,通过analyze_structure()方法得到对应的shape信息。

    def analyze_structure(str):
        # 分析指定结构字符串shape
        str = "".join(str.split(" "))
        shape = []
        count = 1 # 最后一维个数
        # 计算维度数和最后一维个数
        for c in str:
            if c == '[':
                shape.append(0)
            elif c == ',':
                count += 1
            elif c == ']':
                shape[len(shape) - 1] = count
                break
        stack = []
        i = 0
        for c in str:
            if c == '[':
                stack.append(c)
            elif c == ']':
                stack.pop()
                if len(stack) > 0:
                    shape[len(stack)-1] += 1
        # 修正shape
        for i in range(1,len(shape)-1):
            shape[len(shape)-1 - i] = shape[len(shape) - 1 - i] / shape[len(shape) - 2 - i]
        print("分析当前字符串形状:{0}".format(shape))
        return shape

    **原理:**主要思路通过括号匹配,可以先通过左中括号得到维度数信息,然后统计第一个遇到的最底层list可以得到最后一个维度的大小,然后重新左到右匹配,遇到左中括号就弹入,遇到右中括号就将中最顶的左中括号弹出,弹出后当前内的左中括号个数代表当前括号组所在的层级,由于这样匹配会统计所有的同级组,因此在统计完之后需要修正,只需要从前到后除以后一个数就可以得到正确的shape信息。

    在得到shape信息后还需要得到生成的具体信息,只需要将所有的括号、空格去掉按照逗号分割,就可以得到data的一维数组信息。然后通过create_tensor_by_structure()生成对应的tensor

    def create_tensor_by_structure(shape, data, index):
        # 根据data和shape生成特定格式tensor
        if index >= len(shape):
            c = int(data[0])
            data.pop(0)
            return c
        else:
            temp = []
            for i in range(0, shape[index]):
                temp.append(create_tensor_by_structure(shape, data, index+1))
            return temp
    def init_by_data(shape, data):
        return Tensor(len(shape), shape, create_tensor_by_structure(shape, data, 0), "tensor")

    **原理:**与前面生成方法类似,不同的是在返回数据的时候通过一维数组中取得,再包装成init_by_data方法生成tensor数据。

  2. 利用函数构建

    这里预先构建的函数包括rand()one()zero(),通过判断首字母的方法判定是使用哪个方法,然后通过split得到shape的list信息,再调用对应的方法构建

在最后的生成语句中,由于要使用变量来创建变量,因此用到globals()函数直接在全局变量表中添加对应的信息。

结果测试

str1 = "test1 = [[[1, 2], [4, 5], [7, 8]]]"
analyse_statement(str1)
print("test1:{0}".format(test1.data))
print("--------------------------------")
str2 = "test2 = one((2,2))"
analyse_statement(str2)
print("test2:{0}".format(test2.data))

运行结果:

[生成自定义结构] 变量名test1, 参数:[[[1, 2], [4, 5], [7, 8]]]
分析得到当前字符串形状:[1, 3, 2]
参数1,2,4,5,7,8
test1:[[[1, 2], [4, 5], [7, 8]]]
--------------------------------
[one] 变量名test2, 形状:(2,2)
test2:[[1, 1], [1, 1]]

算术操作

1. 加法、点乘

Tensor运算合法性:从shape最后一维开始比较,如果相同,则比较前一位,如果不相同,有1则1的一方通过广播拓展成相同值,而维度数少的一方也可以通过广播数增加维度数达成一致。

def operate_tensor(tensor1, tensor2, operator):
    # 运算包装
    # 判断维度时候为0
    if not isinstance(tensor1, list):
        temp = tensor1
        tensor1 = []
        tensor1.append(temp)
    if not isinstance(tensor2, list):
        temp = tensor2
        tensor2 = []
        tensor2.append(temp)
    shape_x = analyze_tensor(tensor1, [])
    shape_y = analyze_tensor(tensor2, [])
    if len(shape_x) < len(shape_y): # 交换
        shape_x, shape_y = shape_y, shape_x
        tensor1, tensor2 = tensor2, tensor1
    print('tensor1: {1} shape: {0}'.format(shape_x, tensor1))
    print('tensor2: {1} shape: {0}'.format(shape_y, tensor2))
    # 如果维度相同
    if shape_x == shape_y: 
        print("【执行运算...】\ntensor1:{1}\ntensor2:{0}\noperator:{2}".format(tensor2,tensor1,operator))
        data = cal_tensor(tensor1, tensor2, [], operator)
    else:
        # 从尾部开始比较
        for i in range(len(shape_y)):
            if shape_x[len(shape_x) - i - 1] != shape_y[len(shape_y) - i - 1]:
                if shape_x[len(shape_x) - i - 1] == 1:
                    # print("【存在1拓展...】")
                    shape_x[len(shape_x) - i - 1] = shape_y[len(shape_y) - i - 1]
                    shape_temp = shape_x[0:len(shape_x) - i]
                    # print("更新shape_1: {0}".format(shape_x))
                    # 取得当前为1的内容
                    tensor_temp = tensor1
                    for j in range(len(shape_x) - i):
                        tensor_temp = tensor_temp[0]
                    tensor1 = init_tensor(shape_temp, 0, tensor_temp)
                    # print("更新tensor1: {0}".format(tensor1))
                elif shape_y[len(shape_y) - i - 1] == 1:
                    # print("【存在1拓展...】")
                    shape_y[len(shape_y) - i - 1] = shape_x[len(shape_x) - i - 1]
                    shape_temp = shape_y[0:len(shape_y) - i]
                    # print("更新shape_2: {0}".format(shape_y))
                     # 取得当前为1的内容
                    tensor_temp = tensor2
                    for j in range(len(shape_y) - i):
                        tensor_temp = tensor_temp[0]
                    tensor2 = init_tensor(shape_temp, 0, tensor_temp)
                    # print("更新tensor2: {0}".format(tensor2))
                else:
                    print("broadcasting failed!")
                    return
        # 确定新shape
        if len(shape_x) != len(shape_y):
            # print("【拓展维度...】")
            # 拓展tensor2
            shape = shape_x[0:(len(shape_x) - len(shape_y))]
            tensor2 = init_tensor(shape, 0, tensor2)
            # print('更新shape_2:{0}\n更新tensor2:{1}'.format(shape_x, tensor2))
        print("【执行运算...】\ntensor1:{1}\ntensor2:{0}\noperator:{2}".format(tensor2,tensor1,operator))
        data = cal_tensor(tensor1, tensor2, [], operator)
        # print("data:{0}".format(data))
    result = create_tensor_by_structure(analyze_tensor(tensor1, []), data, 0)
    print("【结果】:{0}".format(result))
    return result

**原理:**首先判断输入是否具有0维tensor,如果有则广播成1维,如果维度数和维度都相同,则通过cal_tensor()计算结果;如果不同,从尾部开始比较,如果当前比较维度不相同,则判断是否存在1,如果存在1,则广播拓展后再运算。最后通过create_tensor_by_structure()使用shape和一维data构建结果的tensor

广播:以1当前维度为分割线,将1后面维度的部分看成一个整体,shape为shape[0:len(shape) - i],通过init_tensor()构建新的tensor后,则完成拓展;如果是维度数少的情况,则通过将整个tensor看成整体,shape为shape_x[0:(len(shape_x) - len(shape_y))],再通过init_tensor()构建新tensor。

def cal_tensor(tensor1, tensor2, tensor3, operator):
    # 统一操作加减点乘(相同维度)
    if isinstance(tensor1, list):
        i = 0
        for i in range(len(tensor1)):
            cal_tensor(tensor1[i], tensor2[i], tensor3, operator)
    else:
        if operator == '+':
            tensor3.append(tensor1 + tensor2)
        elif operator == '-':
            tensor3.append(tensor1 - tensor2)
        elif operator == '.':
            tensor3.append(tensor1 * tensor2)
    return tensor3

**原理:**通过递归遍历多层list,遍历到数据的时候执行相对应的操作,最后返回一维数组。

结果测试

x = [[1, 2], [3, 4]]
y = [5, 6]
z = operate_tensor(x,y,'.')
print("result:{0}".format(z))

运行结果:

tensor1: [[1, 2], [3, 4]] shape: [2, 2]
tensor2: [5, 6] shape: [2]
【执行运算...】
tensor1:[[1, 2], [3, 4]]
tensor2:[[5, 6], [5, 6]]
operator:.
【结果】:[[5, 12], [15, 24]]
result:[[5, 12], [15, 24]]

2. 叉乘

def tra_tensor(tensor_x, tensor_y, result):
    # 叉乘底层
    temp = [0]
    if isinstance(tensor_x, list):   
        for i in range(len(tensor_x)):
            if isinstance(tensor_x[i], list):
                tra_tensor(tensor_x[i], tensor_y, result)
            else:
                temp_one = operate_tensor(tensor_x[i], tensor_y[i],'.')
                temp = operate_tensor(temp_one, temp, '+')
        if temp != [0]:
            result.append(temp)
    return result

def dot(tensor_x, tensor_y):
    # 叉乘外层
    print("tensor_x:{0}\ntensor_y:{1}".format(tensor_x, tensor_y))
    shape_x = analyze_tensor(tensor_x, [])
    shape_y = analyze_tensor(tensor_y, [])
    if shape_x[-1] == shape_y[0]:
        # x最底层与y顶层相乘
        # 遍历所有x底层
        result = tra_tensor(tensor_x, tensor_y, [])
        shape_z = analyze_tensor(result, [])
        print("shape_x:{0}\nshape_y;{1}\nshape_z:{2}".format(shape_x, shape_y, shape_z))
        print("【叉乘结果】:{0}".format(result))
    else:
        print("输入不合法!")

公式:A[m1*n1*k1] * B[m2,n2,k2] = C[m1][n1][n2][k2]

且k1 = m2

C[i][j][l][r] = sum_{t=1…k1} (A[i][j][t] * B[t][l][r])

**原理:**因此需要将x的所有最底层与y的最顶层相乘,这里递归遍历x最底层,使用之前定义的操作对tensor进行运算,返回结果。

结果测试

x = [[1,2],[3,4]]
y = [[[5,6],[7, 8],[9,10]],[[5,6],[7, 8],[9,10]]] 
dot(x, y)

运行结果:

notebooks git:(master) ✗ python "/Users/welljun06/github/myTensor/tensor.py"
tensor1: [[1, 2], [3, 4]] shape: [2, 2]
tensor2: [5, 6] shape: [2]
【执行运算...】
tensor1:[[1, 2], [3, 4]]
tensor2:[[5, 6], [5, 6]]
operator:.
【结果】:[[5, 12], [15, 24]]
result:[[5, 12], [15, 24]]
tensor_x:[[1, 2], [3, 4]]
tensor_y:[[[5, 6], [7, 8], [9, 10]], [[5, 6], [7, 8], [9, 10]]]
tensor1: [[5, 6], [7, 8], [9, 10]] shape: [3, 2]
tensor2: [1] shape: [1]
【执行运算...】
tensor1:[[5, 6], [7, 8], [9, 10]]
tensor2:[[1, 1], [1, 1], [1, 1]]
operator:.
【结果】:[[5, 6], [7, 8], [9, 10]]
tensor1: [[5, 6], [7, 8], [9, 10]] shape: [3, 2]
tensor2: [0] shape: [1]
【执行运算...】
tensor1:[[5, 6], [7, 8], [9, 10]]
tensor2:[[0, 0], [0, 0], [0, 0]]
operator:+结果】:[[5, 6], [7, 8], [9, 10]]
tensor1: [[5, 6], [7, 8], [9, 10]] shape: [3, 2]
tensor2: [2] shape: [1]
【执行运算...】
tensor1:[[5, 6], [7, 8], [9, 10]]
tensor2:[[2, 2], [2, 2], [2, 2]]
operator:.
【结果】:[[10, 12], [14, 16], [18, 20]]
tensor1: [[10, 12], [14, 16], [18, 20]] shape: [3, 2]
tensor2: [[5, 6], [7, 8], [9, 10]] shape: [3, 2]
【执行运算...】
tensor1:[[10, 12], [14, 16], [18, 20]]
tensor2:[[5, 6], [7, 8], [9, 10]]
operator:+结果】:[[15, 18], [21, 24], [27, 30]]
tensor1: [[5, 6], [7, 8], [9, 10]] shape: [3, 2]
tensor2: [3] shape: [1]
【执行运算...】
tensor1:[[5, 6], [7, 8], [9, 10]]
tensor2:[[3, 3], [3, 3], [3, 3]]
operator:.
【结果】:[[15, 18], [21, 24], [27, 30]]
tensor1: [[15, 18], [21, 24], [27, 30]] shape: [3, 2]
tensor2: [0] shape: [1]
【执行运算...】
tensor1:[[15, 18], [21, 24], [27, 30]]
tensor2:[[0, 0], [0, 0], [0, 0]]
operator:+结果】:[[15, 18], [21, 24], [27, 30]]
tensor1: [[5, 6], [7, 8], [9, 10]] shape: [3, 2]
tensor2: [4] shape: [1]
【执行运算...】
tensor1:[[5, 6], [7, 8], [9, 10]]
tensor2:[[4, 4], [4, 4], [4, 4]]
operator:.
【结果】:[[20, 24], [28, 32], [36, 40]]
tensor1: [[20, 24], [28, 32], [36, 40]] shape: [3, 2]
tensor2: [[15, 18], [21, 24], [27, 30]] shape: [3, 2]
【执行运算...】
tensor1:[[20, 24], [28, 32], [36, 40]]
tensor2:[[15, 18], [21, 24], [27, 30]]
operator:+结果】:[[35, 42], [49, 56], [63, 70]]
shape_x:[2, 2]
shape_y;[2, 3, 2]
shape_z:[2, 3, 2]
【叉乘结果】:[[[15, 18], [21, 24], [27, 30]], [[35, 42], [49, 56], [63, 70]]]

分析:可以看到进行了多次广播操作。

形状操作

1. shape

def get_shape(tensor):
    shape = analyze_tensor(tensor, [])
    print("shape:{0}".format(shape))

直接使用上面实现的analyze_tensor()函数实现

结果测试

x = [[1, 2],[3, 4]]
y=[5,6]
get_shape(x)
get_shape(y)

运行结果:yu

shape:[2, 2]
shape:[2]

2. reshape

def reshape_tensor(tensor, new_shape):
    # 对tensor进行reshape操作
    shape = analyze_tensor(tensor, [])
    size = shape_size(shape)
    new_size = shape_size(new_shape)
    if size != new_size:
        print("error")
    else:
        data = get_tensor_data(tensor, [])
        new_tensor = create_tensor_by_structure(new_shape, data, 0)
        return new_tensor

原理:分析原shapesize大小,与new_shapesize进行比较,如果合法则进行reshape操作,将原有数据一维化,利用create_tensor_by_structure()创建新tensor

结果测试

x = [[1, 2],[3, 4]]
new_shape = [4, 1]
x_new = reshape_tensor(x, new_shape)
print("x_new:{0}".format(x_new))

运行结果

x_new:[[1], [2], [3], [4]]

3. size

def get_tensor_size(tensor):
    # 返回tensor的szie信息
    shape = analyze_tensor(tensor, [])
    size = shape_size(shape)
    print("size:{0}".format(size))
    return size

原理:与之前类似,分析tensor的形状,再根据shape信息得到size大小。

结果测试

x = [[1, 2],[3, 4]]
get_tensor_size(x)

运行结果

size:4

4. slice操作

def tensor_slice(inputs, begin, size):
    # tensor切片操作
    inputs = tensor_begin(begin, inputs)
    print(inputs)
    result_data = tensor_size(size, inputs, 0, [])
    result = init_by_data(size, result_data)
    print(result.data)

原理:slice函数需要三个参数

  • inputs:需要操作的tensor
  • begin:定位到开始切片的位置
  • size:切片后的tensor的shape

首先处理的是begin参数

def tensor_begin(begin, tensor):
    # 定位到开始位置
    for i in range(len(begin)):
        # print(begin[i])
        if begin[i] != 0:
            tensor = tensor[begin[i]:]
            if i != 0:
                tensor = tensor[0]
    return tensor

原理:遍历begin列表,如果当前遍历的begin数据不为0,则从begin[i]行开始取,如果不是第一次操作,则tensor取下一层。最后返回新的tensor,开头即为开始位置。

接下来处理size参数

def tensor_size(shape, tensor, index, list):
    # 按照size遍历tensor
    if index >= len(shape):
        list.append(tensor)
    else:
        for i in range(0, shape[index]):
            print("tensor[i]:{0},index:{1}".format(tensor[i], index))
            tensor_size(shape, tensor[i], index + 1, list)
    return list

原理:即按照size的shape来当前tensor进行遍历,将访问到的元素存储为一维list

最后通过init_by_data()创建新的tensor即为slice后产生的tensor

结果测试

t = [[[1, 1, 1], [2, 2, 2]],[[3, 3, 3], [4, 4, 4]],[[5, 5, 5], [6, 6, 6]]]
begin = [1, 0, 0]
size = [1, 2, 3]
tensor_slice(t, begin, size)

运行结果

[[[3, 3, 3], [4, 4, 4]]]

简易Tensor语言识别

这里定义两种语句,一种是赋值语句,一种执行语句

def analyse_type(str):
    # tensor语言类型检测
    if '=' in str:
        str_split = str.split('=') # 将输入语句按照等号划分两半
        for i in range(len(str_split)):
            str_split[i] = str_split[i].strip()
        if '*' in str:
            # 叉乘
            argv = str_split[1].split('*')
            for i in range(len(argv)):
                argv[i] = argv[i].strip()
                print(argv[i])
            result = eval('dot({0}, {1})'.format(argv[0], argv[1]))
            shape = get_shape(result)
            exec_str = '{2} = dot({0}, {1})'.format(argv[0], argv[1], str_split[0])
            exec(exec_str, globals())
        else:
            exec(str, globals())
            shape = eval('get_shape({0})'.format(str_split[1]))
        print('{0}:tensor{1}'.format(str_split[0], shape))
    else:
        exec(str)  

原理:如果有等号,根据等号将语句划分,如果没有‘*’,则是赋值语句,只需要将语句执行后统计shape信息,这里注意exec()要使用globals()参数才能全局创建变量。如果是叉乘情况,则需要将变量取出后通过eval()调用dot()函数,最后一样统计shape信息即可。

结果测试

str1 = "x = [1, 2]"
analyse_type(str1)

运行结果

x:tensor[2]

结果测试

y = [[3,4,5],[6,7,8]]
str1 = "print(y)"
analyse_type(str1)

运行结果

[[3, 4, 5], [6, 7, 8]]

结果测试

x = [1,2]
y = [[3,4,5],[6,7,8]]
str1 = "z = x * y"
analyse_type(str1)
print("z:{0}".format(z))

运行结果

z:tensor[1, 3]
z:[[15, 18, 21]]

mytensor's People

Contributors

welljun06 avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.