美文网首页
网络程序设计课程总结

网络程序设计课程总结

作者: neverSoon | 来源:发表于2017-01-08 10:30 被阅读0次

sa15226142 王振西

前言

本学年网络程序设计课程项目是血常规报告的OCR识别和基于识别到的数据进行年龄和性别的预测。孟宁老师一直以开放式教学而深受同学们的喜爱,主张学生的自主性和主观能动性。利用线上的git服务器同学们共同完成整个项目。由孟宁老师给出框架与阶段任务,对pull request的代码进行严格的筛选,监督进度,给出完成项目的指导意见与需求。完全模拟了实际项目开发过程中的方方面面。同学们在课程中,不仅对课程本身知识内容有很好的吸收,在完成项目的过程中,增强了团队合作意识,学习到正确规范的代码格式,培养了写文档的习惯,同时,取长补短,在各自擅长的领域,对项目进行了完善。相比于其他课程追求大而全的思路不同,孟宁老师更愿意让同学们在某一点上发散思考,深度研究。这也更能让同学们在开发过程中找到自己的“技能点”。

项目介绍

本课程的目标是通过神经网络等深度学习的知识对血常规检测报告进行OCR识别以及对患者性别和年龄的预测。
该项目主要分成两个模块,一个是对血常规检测报告图片的OCR识别,另一个是用已经训练好的神经网络模型,对识别的数据进行性别和年龄的预测。
对血常规检测报告的OCR识别
模块功能

1.接收从前端上传的血常规检测报告图片至服务器
2.服务器对上传的图片进行预处理,使用透视算法对图片进行剪切调整后,进行OCR识别,将数据作为JSON格式的对象存储在MongoDB数据库中
3.将识别完成后的数据显示在前端

对数据进行性别和年龄的预测
模块功能

1.在前端点击predict事件后,可通过训练完成的模型,对数据进行性别和年龄的预测 。

安装及运行

# 安装numpy,
sudo apt-get install python-numpy # http://www.numpy.org/
# 安装opencv
sudo apt-get install python-opencv # http://opencv.org/

##安装OCR和预处理相关依赖
sudo apt-get install tesseract-ocr
sudo pip install pytesseract
sudo apt-get install python-tk
sudo pip install pillow

# 安装Flask框架、mongo
sudo pip install Flask
sudo apt-get install mongodb # 如果找不到可以先sudo apt-get update
sudo service mongodb started
sudo pip install pymongo

运行

cd  BloodTestReportOCR
python view.py # upload图像,在浏览器打开http://yourip:8080

演示Demo

运行view.py后,打开浏览器,访问localhost:8080,进入页面:
上传图片
上传图片.jpg
生成报告
生成报告.jpg
结果预测
结果预测.jpg

性别和年龄预测的实现

对于性别和年龄的预测,不同的同学采用了不同方案,这里作者给出自己的实现。借助Theano完成网络的搭建。
数据处理

由于在训练集中包含无效数据,所以在进行预测前要筛选掉包含空和非法字符的行,并在有效的数据中选择一部分当做验证集,预测集。

import pandas as pd 
import numpy as np
import os
import csv

inputfilepath = "./train2.csv"

if not os.path.exists("./processed2"):
    os.makedirs("./processed2")
train_set_Path = "./processed2/trainset.csv"
validate_set_Path = "./processed2/validateset.csv"
predict_set_Path = "./processed2/predictset.csv"

dataf = pd.read_csv(inputfilepath)
# dorp 5 empty columns
dataf = dataf.dropna(axis=1,how='all')
# drop all items which has nan, remaining 6500 items
dataf1 = dataf.dropna()

entity_num = 6500 
validate_num = 650
validate_indice = np.random.random_integers(0,entity_num - 1,validate_num)

train_set = []
validate_set = []
predict_set = []
indexp = 0
for index, row in dataf1.iterrows():
    temp = []
    for i in range(2,29):
        temp.append(row[i])
    temp.append(row[0])             # sex
    temp.append(row[1])             # age
    if indexp in validate_indice:
        validate_set.append(temp)
    else:
        train_set.append(temp)
    indexp += 1
predict_set = train_set[-201:-1]
train_set = train_set[0:-201]
print len(train_set), len(predict_set),len(validate_set)

f1 = open(train_set_Path, 'a')
writer1 = csv.writer(f1)
for item in train_set:
    writer1.writerow(item)
f1.close()

f2 = open(validate_set_Path, 'a')
writer2 = csv.writer(f2)
for item in validate_set:
    writer2.writerow(item)
f2.close()

f3 = open(predict_set_Path, 'a')
writer3 = csv.writer(f3)
for item in predict_set:
    writer3.writerow(item)
f3.close()
神经网络搭建
class HiddenLayer(object):
    def __init__(self, rng, input, n_in, n_out, W=None, b=None,
                    activation=None):
        """
        A multiple layer perceptron hidden layer.

        :type rng: numpy.random.RandomState
        :param rng: a random number generator used to initialize weights

        :type input: theano.tensor.dmatrix
        :param input: a symbolic tensor of shape (n_examples, n_in)

        :type n_in: int
        :param n_in: dimensionality of input

        :type n_out: int
        :param n_out: number of hidden units

        :type activation: theano.Op or function
        :param activation: Non linearity to be applied in the hidden
                           layer
        """

        self.input = input


        # initialiaze W from a Gaussian distribution, the prior distribution
        # of W is given by Gaussian.
        """
        if W is None:
            W_values = np.asarray(
                rng.normal(
                    loc = 0.0,
                    scale = 1.0,
                    size = (n_in, n_out)
                ),
                dtype = theano.config.floatX
            )
            W = theano.shared(value=W_values, name='W', borrow=True)
        """
        if W is None:
            W_values = np.asarray(
                rng.uniform(
                    low=-np.sqrt(6. / (n_in + n_out)),
                    high=np.sqrt(6. / (n_in + n_out)),
                    size=(n_in, n_out)
                ),
                dtype=theano.config.floatX
            )
            if activation == theano.tensor.nnet.sigmoid:
                W_values *= 4

            W = theano.shared(value=W_values, name='W', borrow=True)
        
        if b is None:
            b_values = np.zeros((n_out,), dtype=theano.config.floatX)
            b = theano.shared(value=b_values, name='b', borrow=True)

        self.W = W
        self.b=b

        _output = T.dot(input, self.W) + self.b
        self.output = (
            _output if activation is None
            else activation(_output)
        )

        self.parameters = [self.W, self.b]

class LogisticRegression(object):
    def __init__(self, input, n_in, n_out):
        """ Initialize the parameters of the logistic regression

        :type input: theano.tensor.TensorType
        :param input: symbolic variable that describes the input of the
                      architecture (one minibatch)

        :type n_in: int
        :param n_in: number of input units, the dimension of the space in
                     which the datapoints lie

        :type n_out: int
        :param n_out: number of output units, the dimension of the space in
                      which the labels lie

        """

        # initialize the weights W with 0 as a matrix of shape(n_in, n_out)
        self.W = theano.shared(
            value=np.zeros(
                (n_in, n_out),
                dtype=theano.config.floatX
            ),
            name='W',
            borrow=True
        )

        #initialize the biased b as vector of n_out 0s
        self.b = theano.shared(
            value=np.zeros(
                (n_out,),
                dtype=theano.config.floatX
            ),
            name='b',
            borrow=True
        )

        # symbolic expression for computing the matrix of class-membership
        # probabilities
        # Where:
        # W is a matrix where column-k represent the separation hyperplane for
        # class-k
        # x is a matrix where row-j  represents input training sample-j
        # b is a vector where element-k represent the free parameter of
        # hyperplane-k
        self.p_y_given_x = T.nnet.softmax(T.dot(input, self.W) + self.b)

        # symbolic description of how to compute prediction as class whose
        # probability is maximal
        self.y_pred = T.argmax(self.p_y_given_x, axis=1)
        # end-snippet-1

        # parameters of the model
        self.parameters = [self.W, self.b]

        # keep track of model input
        self.input = input

    def negative_log_likelihood(self, y):
        """Return the mean of the negative log-likelihood of the prediction
        of this model under a given target distribution.

        :type y: theano.tensor.TensorType
        :param y: corresponds to a vector that gives for each example the
                  correct label

        Note: we use the mean instead of the sum so that
              the learning rate is less dependent on the batch size
        """

        return -T.mean(T.log(self.p_y_given_x)[T.arange(y.shape[0]), y])

    def errorsforSex(self, y):
        """Return a float representing the number of errors in the minibatch
        over the total number of examples of the minibatch ; zero one
        loss over the size of the minibatch

        :type y: theano.tensor.TensorType
        :param y: corresponds to a vector that gives for each example the
                  correct label
        """

        # check if y has same dimension of y_pred
        if y.ndim != self.y_pred.ndim:
            raise TypeError(
                'y should have the same shape as self.y_pred',
                ('y', y.type, 'y_pred', self.y_pred.type)
            )
        # check if y is of the correct datatype
        if y.dtype.startswith('int'):
            # the T.neq operator returns a vector of 0s and 1s, where 1
            # represents a mistake in prediction
            return T.mean(T.neq(self.y_pred, y)), self.y_pred, y
        else:
            raise NotImplementedError()
    

class MLPForSex(object):
    """
    The MLP model for predict sex.
    """

    def __init__(self, rng, input, n_in, n_out):
        """Initialize the parameters for the multilayer perceptron

        :type rng: numpy.random.RandomState
        :param rng: a random number generator used to initialize weights

        :type input: theano.tensor.TensorType
        :param input: symbolic variable that describes the input of the
        architecture (one minibatch)

        :type n_in: int
        :param n_in: number of input units, the dimension of the space in
        which the datapoints lie

        :type n_out: int
        :param n_out: number of output units, the dimension of the space in
        which the labels lie

        """
        
        self.hiddenLayer1 = HiddenLayer(
            rng=rng,
            input=input,
            n_in=n_in,
            n_out=64,
            activation=T.tanh
        )
        self.hiddenLayer2 = HiddenLayer(
            rng=rng,
            input=self.hiddenLayer1.output,
            n_in=64,
            n_out=128,
            activation=T.tanh
        )
        self.hiddenLayer3 = HiddenLayer(
            rng=rng,
            input=self.hiddenLayer2.output,
            n_in=128,
            n_out=16,
            activation=T.tanh
        )
        self.outputLayer = LogisticRegression(
            input=self.hiddenLayer3.output,
            n_in=16,
            n_out=n_out
        )

        self.L1 = (
            abs(self.hiddenLayer1.W).sum()
            + abs(self.hiddenLayer2.W).sum()
            + abs(self.hiddenLayer3.W).sum()
            + abs(self.outputLayer.W).sum()
        )

        self.L2 = (
            (self.hiddenLayer1.W ** 2).sum()
            + (self.hiddenLayer2.W ** 2).sum()
            + (self.hiddenLayer3.W ** 2).sum()
            + (self.outputLayer.W ** 2).sum()
        )

        self.negative_log_likelihood = (
            self.outputLayer.negative_log_likelihood
        )

        self.errors = self.outputLayer.errorsforSex

        self.parameters = self.hiddenLayer1.parameters + self.hiddenLayer2.parameters +\
        self.hiddenLayer3.parameters + self.outputLayer.parameters

        self.input = input

class AgeOutput(object):
    def __init__(self, input, n_in, n_out, W=None, b=None, activation=None):
        """ Initialize the parameters of the logistic regression

        :type input: theano.tensor.TensorType
        :param input: symbolic variable that describes the input of the
                      architecture (one minibatch)

        :type n_in: int
        :param n_in: number of input units, the dimension of the space in
                     which the datapoints lie

        :type n_out: int
        :param n_out: number of output units, the dimension of the space in
                      which the labels lie

        """

        # initialize the weights W with 0 as a matrix of shape(n_in, n_out)
        rng = np.random.RandomState(1234)
        if W is None:
            W_values = np.asarray(
                rng.uniform(
                    low=-np.sqrt(6. / (n_in + n_out)),
                    high=np.sqrt(6. / (n_in + n_out)),
                    size=(n_in, n_out)
                ),
                dtype=theano.config.floatX
            )
            if activation == theano.tensor.nnet.sigmoid:
                W_values *= 4

            W = theano.shared(value=W_values, name='W', borrow=True)
        
        if b is None:
            b_values = np.zeros((n_out,), dtype=theano.config.floatX)
            b = theano.shared(value=b_values, name='b', borrow=True)

        self.W = W
        self.b=b

        # symbolic expression for computing the matrix of class-membership
        # probabilities
        # Where:
        # W is a matrix where column-k represent the separation hyperplane for
        # class-k
        # x is a matrix where row-j  represents input training sample-j
        # b is a vector where element-k represent the free parameter of
        # hyperplane-k
        gaussian = np.random.normal(0,25,n_out)
        self.output = T.dot(input, self.W) + self.b
        #self.p_y_given_x = T.nnet.softmax(T.dot(input, self.W) + self.b)

        # symbolic description of how to compute prediction as class whose
        # probability is maximal
        #self.y_pred = T.argmax(self.p_y_given_x, axis=1)
        # end-snippet-1

        # parameters of the model
        self.parameters = [self.W, self.b]

        # keep track of model input
        self.input = input

    def cost(self, y):
        """Return the mean of the negative log-likelihood of the prediction
        of this model under a given target distribution.

        :type y: theano.tensor.TensorType
        :param y: corresponds to a vector that gives for each example the
                  correct label

        Note: we use the mean instead of the sum so that
              the learning rate is less dependent on the batch size
        """
        return 0.5 * T.dot(T.transpose(self.output - y), self.output - y)
        return -T.mean(T.log(self.p_y_given_x)[T.arange(y.shape[0]), y])

    def errorsforAge(self, y):
        """Return a float representing the number of errors in the minibatch
        over the total number of examples of the minibatch ; zero one
        loss over the size of the minibatch

        :type y: theano.tensor.TensorType
        :param y: corresponds to a vector that gives for each example the
                  correct label
        """

        # check if y has same dimension of y_pred
        
        if y.ndim != self.output.ndim:
            raise TypeError(
                'y should have the same shape as self.y_pred',
                ('y', y.type, 'y_pred', self.output.type)
            )
        # check if y is of the correct datatype
        if y.dtype.startswith('int'):
            # the T.neq operator returns a vector of 0s and 1s, where 1
            # represents a mistake in prediction
            def notEqualbyFive(a, b):
                if T.le(T.abs_(a-b),5):
                    return 0
                else:
                    return 1
            return T.mean(T.le(5,T.abs_(self.output-y))), self.output, y
            #return T.mean(T.neq(self.outputLayer.y_pred, y)), self.outputLayer.y_pred, y
        else:
            raise NotImplementedError()

class MLPForAge(object):
    """
    The MLP model for predict age.
    """

    def __init__(self, rng, input, n_in):
        """Initialize the parameters for the multilayer perceptron

        :type rng: numpy.random.RandomState
        :param rng: a random number generator used to initialize weights

        :type input: theano.tensor.TensorType
        :param input: symbolic variable that describes the input of the
        architecture (one minibatch)

        :type n_in: int
        :param n_in: number of input units, the dimension of the space in
        which the datapoints lie

        """
        
        self.hiddenLayer1 = HiddenLayer(
            rng=rng,
            input=input,
            n_in=n_in,
            n_out=128,
            activation=None
        )
        self.hiddenLayer2 = HiddenLayer(
            rng=rng,
            input=self.hiddenLayer1.output,
            n_in=128,
            n_out=128,
            activation=None
        )
        self.hiddenLayer3 = HiddenLayer(
            rng=rng,
            input=self.hiddenLayer2.output,
            n_in=128,
            n_out=256,
            activation=None
        )
        self.hiddenLayer4 = HiddenLayer(
            rng=rng,
            input=self.hiddenLayer3.output,
            n_in=256,
            n_out=512,
            activation=None
        )
        self.hiddenLayer5 = HiddenLayer(
            rng=rng,
            input=self.hiddenLayer4.output,
            n_in=512,
            n_out=256,
            activation=None
        )
        self.hiddenLayer6 = HiddenLayer(
            rng=rng,
            input=self.hiddenLayer5.output,
            n_in=256,
            n_out=256,
            activation=None
        )
        self.outputLayer = AgeOutput(
            input=self.hiddenLayer6.output,
            n_in=256,
            n_out=1
        )

        self.L1 = (
            abs(self.hiddenLayer1.W).sum()
            + abs(self.hiddenLayer2.W).sum()
            + abs(self.hiddenLayer3.W).sum()
            + abs(self.hiddenLayer4.W).sum()
            + abs(self.hiddenLayer5.W).sum()
            + abs(self.hiddenLayer6.W).sum()
            + abs(self.outputLayer.W).sum()
        )

        self.L2 = (
            (self.hiddenLayer1.W ** 2).sum()
            + (self.hiddenLayer2.W ** 2).sum()
            + (self.hiddenLayer3.W ** 2).sum()
            + (self.hiddenLayer4.W ** 2).sum()
            + (self.hiddenLayer5.W ** 2).sum()
            + (self.hiddenLayer6.W ** 2).sum()
            + (self.outputLayer.W ** 2).sum()
        )

        self.cost = (
            self.outputLayer.cost
        )
        self.errors = self.errorsforAge

        self.parameters = self.hiddenLayer1.parameters + self.hiddenLayer2.parameters +\
        self.hiddenLayer3.parameters + self.hiddenLayer4.parameters + self.hiddenLayer5.parameters +\
        self.hiddenLayer6.parameters + self.outputLayer.parameters

        self.input = input

    def errorsforAge(self, y):
        """Return a float representing the number of errors in the minibatch
        over the total number of examples of the minibatch ; zero one
        loss over the size of the minibatch

        :type y: theano.tensor.TensorType
        :param y: corresponds to a vector that gives for each example the
                  correct label
        """

        # check if y has same dimension of y_pred
        
        if y.ndim != self.outputLayer.output.ndim:
            raise TypeError(
                'y should have the same shape as self.y_pred',
                ('y', y.type, 'y_pred', self.outputLayer.output.type)
            )
        # check if y is of the correct datatype
        if y.dtype.startswith('float'):
            # the T.neq operator returns a vector of 0s and 1s, where 1
            # represents a mistake in prediction
            def notEqualbyFive(a, b):
                if T.le(T.abs_(a-b),5):
                    return 0
                else:
                    return 1
            return T.mean(T.le(5,T.abs_(self.outputLayer.output-y))), self.outputLayer.output, y
            #return T.mean(T.neq(self.outputLayer.y_pred, y)), self.outputLayer.y_pred, y
        else:
            raise NotImplementedError()

def load_data(dataset, id):
    """
    loads the dataset.

    :type dataset: string
    :param dataset: the path to the dataset

    :type id: string
    :param id: generate the dataset for predicting age or gender. 'age'
    for age, 'gender' for gender
    """

    gender = ['BAS', 'EOS%', 'HGB', 'LIC%', 'PDW', 'PLT', 'WBC']
    age = ['BAS%', 'HCT', 'LIC%', 'LYM', 'MCH', 'MCV', 'MPV']
    data_set = []
    def findIndice(original, info):
        indice = []
        for item in info:
            if item in original:
                indice.append(original.index(item))
        return indice
    with open(dataset, 'rb') as f:
        rows = csv.reader(f)
        index = 0
        indice = []
        for row in rows:
            temp = []
            if index == 0:
                index += 1
                if id == 'age':
                    indice = findIndice(row, age)
                elif id == 'gender':
                    indice = findIndice(row, gender)
                print indice
                continue
            if len(indice) == 7:
                for i in indice:
                    temp.append(row[i])
            else:
                for i in indice:
                    temp.append(row[i])
                for i in range(7 - len(indice)):
                    temp.append(0)
            if row[-2] == 'Ů':      # not know male or female
                temp.append(0)
            else:
                temp.append(1)
            temp.append(row[-1])
            #print temp
            data_set.append(temp)
    f.close()

    def convertStrToFloat(dataset):
        resultX = []
        resultY1 = []
        resultY2 = []
        for i in range(0,len(dataset)):
            tempX = []
            for j in range(0,7):
                #print dataset[i][j]
                tempX.append(float(dataset[i][j]))
            resultY1.append(float(dataset[i][-2]))
            resultY2.append(float(dataset[i][-1]))
            resultX.append(tempX)
        #for i in range(0,len(dataset)):
        #    print resultX[i], resultY1[i], resultY2[i]
        return (resultX, resultY1, resultY2)

    def shared_dataset(data_xy1y2, borrow=True):
        data_x, data_y1, data_y2 = data_xy1y2
        shared_x = theano.shared(np.asarray(data_x,
                                               dtype=theano.config.floatX),
                                 borrow=borrow)
        shared_y1 = theano.shared(np.asarray(data_y1,
                                               dtype=theano.config.floatX),
                                 borrow=borrow)
        shared_y2 = theano.shared(np.asarray(data_y2,
                                               dtype=theano.config.floatX),
                                 borrow=borrow)
        return shared_x, T.cast(shared_y1, 'int32'), T.cast(shared_y2, 'int32')
    dataset_x, dataset_y1, dataset_y2 = shared_dataset(convertStrToFloat(data_set))
    
    result = (dataset_x, dataset_y1, dataset_y2)
    #print result
    return result

def load_data1(dataset, id):
    """
    loads the dataset.

    :type dataset: string
    :param dataset: the path to the dataset

    :type id: string
    :param id: generate the dataset for predicting age or gender. 'age'
    for age, 'gender' for gender
    """

    gender = ['BAS', 'EOS%', 'HGB', 'LIC%', 'PDW', 'PLT', 'WBC']
    age = ['BAS%', 'HCT', 'LIC%', 'LYM', 'MCH', 'MCV', 'MPV']
    data_set = []
    def findIndice(original, info):
        indice = []
        for item in info:
            if item in original:
                indice.append(original.index(item))
        return indice
    with open(dataset, 'rb') as f:
        rows = csv.reader(f)
        index = 0
        indice = []
        for row in rows:
            temp = []
            for i in range(0,29):
                temp.append(row[i])
            data_set.append(temp)
    f.close()

    def convertStrToFloat(dataset):
        resultX = []
        resultY1 = []
        resultY2 = []
        for i in range(0,len(dataset)):
            tempX = []
            for j in range(0,27):
                #print dataset[i][j]
                tempX.append(float(dataset[i][j]))
            resultY1.append(float(dataset[i][-2]) - 1)    # sex
            resultY2.append(float(dataset[i][-1]))    # age
            resultX.append(tempX)
        #for i in range(0,len(dataset)):
        #    print resultX[i], resultY1[i], resultY2[i]
        return (resultX, resultY1, resultY2)

    def shared_dataset(data_xy1y2, borrow=True):
        data_x, data_y1, data_y2 = data_xy1y2
        shared_x = theano.shared(np.asarray(data_x,
                                               dtype=theano.config.floatX),
                                 borrow=borrow)
        shared_y1 = theano.shared(np.asarray(data_y1,
                                               dtype=theano.config.floatX),
                                 borrow=borrow)
        shared_y2 = theano.shared(np.asarray(data_y2,
                                               dtype=theano.config.floatX),
                                 borrow=borrow)
        return shared_x, T.cast(shared_y1, 'int32'), T.cast(shared_y2, 'float64')
    dataset_x, dataset_y1, dataset_y2 = shared_dataset(convertStrToFloat(data_set))
    
    result = (dataset_x, dataset_y1, dataset_y2)
    #print result
    return result
if __name__ == '__main__':
    load_data('../data/processed/validateset.csv')

相关文章

  • 网络程序设计课程总结

    sa15226142 王振西 前言 本学年网络程序设计课程项目是血常规报告的OCR识别和基于识别到的数据进行年龄和...

  • 网络课程总结

    在2018年暑假我有幸加入互加计划“兴成长计划”学习,然后在本学期开学初我有幸把互加美丽乡村网络公益课程和彩虹...

  • 网络课程总结

    近日,病毒肆虐,给我们带来了许多困难,因而延迟了开学时间,为了不耽误课程,我们开启了“网络课程”。 网络课程既给我...

  • 第9+ 全课程总结和学习展望

    Python语言程序设计_中国大学MOOC(慕课) 1.全课程总结 2.学习展望

  • 《网络程序设计》学习总结

    前言 《网络程序设计》的课程已经结束了,一如孟宁老师以往的风格,继高软的“kingke”后,老师给出了“np201...

  • 网络程序设计学习总结

    学号:SA16225140姓名:李豪俊 前言:为期7周的“网络程序设计”课程即将告一段落,在此提笔写下对于这门课的...

  • 网络课程总结感悟

    转眼间,一个学期的互加美丽乡村网络课结束了。这学期我们班共参与彩虹花高段晨读和生命教育两门网络课。 一个学期来,我...

  • 200912.《Python语言程序设计》-0.1-课程导学

    《Python语言程序设计》-0.1-课程导学 《Python语言程序设计》所有教学视频 课程基本内容 课程导学视...

  • 全课程总结与学习展望-Python语言程序设计(学习笔记)

    文章原创,最近更新:2018-05-16 1.全课程总结2.课程考核及证书3.学习展望原链接 语言程序设计北京理工...

  • 20170404话题:网络课程总结

    你报过多少种网络课程?学习情况如何? 刚刚说今天梳理一下最近的学习内容,就看到了今天的话题,二者归一。总结加分享,...

网友评论

      本文标题:网络程序设计课程总结

      本文链接:https://www.haomeiwen.com/subject/sdjqbttx.html