企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
[TOC] > [参考](https://tonybai.com/2024/06/10/go-and-nn-part2-linear-regression) ## 概述 说明一下人工智能、机器学习、神经网络以及深度学习的关系 ![](https://img.kancloud.cn/e5/ae/e5aebad94a6d56af0efacc225897ec1d_561x411.png) 详细图 ![](https://img.kancloud.cn/e4/e2/e4e285d0d8c8c60b5a4841edc226c50b_800x853.png) ## 概念 ### 特征、标签与模型 ![](https://img.kancloud.cn/51/7e/517eb973897ccda8049b7644b4d6ffa6_1361x611.png) - 左上角的表格就是“喂给”机器学习训练的**训练数据集(training dataset )** - 表中每条数据有三个影响房价的“因子”:居住面积、离市中心距离和建成时间(也就是房龄),这些因子共同决定了房子的价格。在机器学习中,我们称这些“因子”为**特征(feature)**。而房价则被称为**标签(label)**。 ![](https://img.kancloud.cn/fa/53/fa539c266efb6fa2c3fcd4fac97a4ea8_761x401.png) 机器学习的目的就是找到通过特征预测标签的函数(即**模型**),然后将得到的函数应用于生产中进行标签预测。特征是机器学习模型的输入,标签是机器学习模型的输出。无论是在训练阶段,还是在预测阶段。特征的个数称为特征的维度,维度越高,数据集越复杂 ### 数据收集与预处理 数据收集渠道有多种,有爬取互联网的数据,有开源数据集(Image Net、Kaggle、Google Public Data Explorer),有购买的,还有客户积攒的海量历史数据等 数据的预处理是十分重要的工作,预处理的好坏直接决定了训练出来的机器学习模型的有效性 * 可视化:用Excel表和各种数据分析工具(如Matplotlib等)从各种角度(如列表、直方图、散点图等)探索一下数据。对数据有了基本的了解后,才方便进一步分析判断,即为后续的模型选择奠定基础。 * [数据向量化](https://tonybai.com/2024/05/09/text-vectorization-using-ollama-and-go-based-on-text-embedding-models/):把原始数据格式化,使其变得机器可以读取。例如,将原始图片转换为机器可以读取的数字矩阵,将文字转换为one-hot编码,将文本类别(如男、女)转换成0、1这样的数值。 * 处理坏数据和空数据:一条数据可不是全部都能用,要利用数据处理工具来把“捣乱”的“坏数据”(冗余数据、离群数据、错误数据)处理掉,把缺失值补充上。 * 特征缩放:可以显著提升模型的性能和训练效率。许多机器学习算法,例如梯度下降法,依赖于特征之间的距离计算。如果特征的尺度差异很大,会导致算法在不同特征方向上以不同的速度进行更新,从而降低收敛速度。特征缩放可以将所有特征缩放到相同的尺度,使算法能够更快地收敛到最优解。特征尺度差异过大可能导致数值计算不稳定,例如出现梯度爆炸或梯度消失现象,影响模型训练效果。特征缩放还可以使模型的权重更加可解释。当特征尺度差异很大时,模型的权重可能无法反映特征的实际重要性。特征缩放可以使权重更加反映特征的真实贡献。 特征缩放适用于大多数机器学习算法,包括线性回归、逻辑回归、支持向量机、神经网络等。常见的特征缩放方法包括如下几种: * 标准化 (Standardization):对数据特征分布的转换,目标是使其符合正态分布(均值为0,标准差为1)。在实践中,会去除特征的均值来转换数据, 使其居中,然后除以特征的标准差来对其进行缩放。 * 归一化/规范化 (Normalization):将特征数据缩放到特定范围,通常是0到1之间。归一化不会改变数据的分布形态。 ### 选择机器学习模型 在前期的传统机器学习阶段,不同的数据和问题需要采用不同的机器学习方法和模型。 影响机器学习模型选择的一些关键的因素包括: * 数据类型和特征:比如图像数据和文本数据一般需要不同的模型。数据的维度、稀疏程度等也会影响选择的模型。 * 任务类型:分类、回归、聚类等任务适合不同的模型。有监督学习和无监督学习也需要不同的方法。 * 数据规模:对于大规模数据,可扩展性强的模型如深度学习效果更好。小样本数据可能更适合传统的机器学习算法。 * 领域知识:某些领域问题需要结合专业领域知识,不能单纯依赖通用的机器学习模型。 #### 有监督学习和无监督学习 **有监督学习** 有监督学习是一种通过使用已标注的数据(即如前面图中的训练数据集那样,样本数据包含特征与对应的标签)来训练模型的方法。在这种方法中,每个训练样本都是一个输入-输出对,模型通过学习这些对的关系来预测新的输入数据的输出。有监督学习擅长的任务类型包括下面这几个: * 分类任务:将输入数据分类到预定义的类别中,例如垃圾邮件检测、图像分类。 * 回归任务:预测连续的数值输出,例如房价预测(前面图中的示例)、股票价格预测。 * 标注任务:为输入数据中的每个元素分配一个标签。例如:命名实体识别(NER):在文本中识别出人名、地名、组织名等。 * 排序任务:根据某种标准对项目进行排序。例如:信息检索、推荐系统。 * 序列预测任务:根据时间序列数据进行预测。例如,销售额预测、天气预报等。 **无监督学习** 而无监督学习则是一种通过使用未标注的数据来训练模型的方法。在这种方法中,模型试图从数据中发现结构或模式,而无需使用明确的输入-输出对。无监督学习擅长的任务类型包括下面几个: * 聚类任务:将相似的样本归为一类,比如给定一组照片,模型能把它们分成风景照片、狗、婴儿、猫和山峰。同样,给定一组用户的网页浏览记录,模型能将具有相似行为的用户聚类。 * 降维任务:减少数据的维度,同时保持其重要特征,例如主成分分析(PCA)问题,模型能否找到少量的参数来准确地捕捉数据的线性相关属性?比如,一个球的运动轨迹可以用球的速度、直径和质量来描述。 * 异常检测:识别数据中的异常或异常模式,例如欺诈检测、设备故障检测。 把特征数据放到图形中,为一个直线函数 ![](https://img.kancloud.cn/d4/90/d490c212efef55c90b93f47fab33ce40_1487x737.png) 这个函数叫做假设函数 ![](https://img.kancloud.cn/b5/a2/b5a2d7497b0c25a380e94d209ad267ea_571x61.png) (也叫预测函数),其中的w1、w2和w3称为权重,权重决定了每个特征对我们预测值的影响。b称为偏置(bias)、 偏移量(offset)或截距(intercept)。偏置是指当所有特征都取值为0时,预测值应该为多少 现在权重w1、w2、w3和偏置b的值都是未知的,它们也被称为模型内的参数,直接影响模型的预测结果。 首先就是**权重和偏置参数的初始值**,对于这个示例我们可采用**随机初始化**的方式,即将参数随机地设置在一个合理的范围内。这种方法简单快捷,但对于复杂的模型,可能会导致收敛速度慢或陷入局部最优 我们要确定一个y’计算结果与训练数据集中标签值的差距计算方法。机器学习领域称这个计算方法为**损失函数(Loss function)**。损失也就是 误差,也称为成本(cost)或代价,用于体现当前预测值和真实值之间的差距 用于回归的损失函数就有:均方误差(Mean Square Error,MSE)函数、平均绝对误差(Mean Absolute Error,MAE)函数和平均偏差误差(mean bias error)函数。用于分类的损失函数有交叉熵损失(cross-entropy loss)函数和多分类SVM损失(hinge loss)函数等 在此示例中,可使用均方差函数L ![](https://img.kancloud.cn/47/43/474357c498110bb12283a70df9378bab_331x101.png) ``` x1 = 55, x2 = 11, x3 = 5, y = 210 y' = 0.1 * 55 + 0.1 * 11 + 0.1 * 5 + 0.1 = 7.2 L = 1/2 * (7.2 - 210)^2 = 20563.920000000002 ``` 可以看出损失函数值过大,优化算法通常基于一种基本方法–**梯度下降(gradient descent)**进行调整,简而言之,在每个步骤中,梯度下降法都会检查每个参数,看看如果仅对该参数进行少量变动,训练集损失会朝哪个方向移动。然后,它在可以减少损失的方向上优化参数。 偏导数公式的推导过程: ![](https://img.kancloud.cn/e5/21/e521796dc55a7a4b18a87fab2eafb0c8_1581x1191.png) 针对每个样本,我们计算其损失值(y’-y)与该样本特征(x1)的乘积。取这些乘积的平均值就得到了L对w1的偏导值 ![](https://img.kancloud.cn/14/97/1497b619c5a70a48b93fd95e2d19314f_491x541.png) 偏导数为我们指定了参数调整方向,下面是w1、w2、w3和b的更新公式: ![](https://img.kancloud.cn/46/43/464380e17b457ec10ac3e0ca1d12a321_401x491.png) 更新公式中有一个新的变量α,该变量代表的是学习率(learning rate)。是一个**超参数**,它控制着模型参数更新的步伐大小,太大参数更新步伐过大,可能导致模型无法收敛甚至发散,参数更新步伐过小,训练时间会过长且可能陷入局部最小值 #### 超参调试与性能优化 学习率(learning rate)、训练轮数(Epochs)等,是模型外部的可以通过人工调节的参数,这样的参数称为**超参数(Hyperparameters)**。大多数机器学习从业者真正花费相当多的时间来调试的正是这类超参数 在实际应用中,选择合适的学习率和训练轮数等超参数通常需要结合以下方法: * 经验法则:基于先前经验和领域知识设定初始值。 * 交叉验证:通过交叉验证选择一组最优的超参数。 * 网格搜索:在多个可能的超参数组合上进行搜索,找到效果最好的参数组合。 * 学习率调度:动态调整学习率,比如在训练过程中逐渐减小学习率。 ### 线性回归:机器学习的Hello, World 数据 ``` $cat train.csv 面积,距离,房价 50,10,200 60,12,220 70,15,250 80,20,300 90,25,330 100,30,360 110,35,390 120,40,420 130,45,450 140,50,480 $cat test.csv 面积,距离,房价 55,11,210 65,13,230 75,17,260 85,22,310 95,27,340 105,32,370 115,37,400 125,42,430 135,47,460 145,52,490 ``` 通过编码来实现对csv文件的读取 ``` / go-and-nn/linear-regression/main.go func readCSV(filePath string) ([][]float64, error) { file, err := os.Open(filePath) if err != nil { return nil, err } defer file.Close() reader := csv.NewReader(file) records, err := reader.ReadAll() if err != nil { return nil, err } data := make([][]float64, len(records)-1) for i := 1; i < len(records); i++ { data[i-1] = make([]float64, len(records[i])) for j := range records[i] { data[i-1][j], err = strconv.ParseFloat(records[i][j], 64) if err != nil { return nil, err } } } return data, nil } ``` 拿到数据后,标准化后的数据会使模型训练更加稳定和快速,从而可能提高模型的预测性能 ``` // go-and-nn/linear-regression/main.go func standardize(data [][]float64) ([][]float64, []float64, []float64) { mean := make([]float64, len(data[0])-1) std := make([]float64, len(data[0])-1) for i := 0; i < len(data[0])-1; i++ { for j := 0; j < len(data); j++ { mean[i] += data[j][i] } mean[i] /= float64(len(data)) } for i := 0; i < len(data[0])-1; i++ { for j := 0; j < len(data); j++ { std[i] += math.Pow(data[j][i]-mean[i], 2) } std[i] = math.Sqrt(std[i] / float64(len(data))) } standardizedData := make([][]float64, len(data)) for i := 0; i < len(data); i++ { standardizedData[i] = make([]float64, len(data[i])) for j := 0; j < len(data[i])-1; j++ { standardizedData[i][j] = (data[i][j] - mean[j]) / std[j] } standardizedData[i][len(data[i])-1] = data[i][len(data[i])-1] } return standardizedData, mean, std } ``` tandardize中的mean和std分别用于存储每个特征的均值和标准差(标准差是反应一组数据离散程度最常用的一种量化形式,累加每个样本的特征值与均值的平方差,然后除以样本数量,再开平方,便可得到该特征的标准差)。有了均值和标准差后,我们用原始特征值减去均值,然后除以标准差,得到标准化后的特征值。标签无需标准化 **训练** 训练代码 ``` // go-and-nn/linear-regression/main.go func trainModel(data [][]float64, learningRate float64, epochs int) ([]float64, float64) { features := len(data[0]) - 1 weights := make([]float64, features) bias := 0.0 for epoch := 0; epoch < epochs; epoch++ { gradW := make([]float64, features) gradB := 0.0 mse := 0.0 for i := 0; i < len(data); i++ { prediction := bias for j := 0; j < features; j++ { prediction += weights[j] * data[i][j] } error := prediction - data[i][features] mse += error * error for j := 0; j < features; j++ { gradW[j] += error * data[i][j] } gradB += error } mse /= float64(len(data)) // 更新权重 for j := 0; j < features; j++ { gradW[j] /= float64(len(data)) weights[j] -= learningRate * gradW[j] } gradB /= float64(len(data)) // 更新偏置 bias -= learningRate * gradB // Output the current weights, bias and loss fmt.Printf("Epoch %d: Weights: %v, Bias: %f, MSE: %f\n", epoch+1, weights, bias, mse) } return weights, bias } ``` 驱动训练函数的代码 ``` // go-and-nn/linear-regression/main.go func main() { // Read training data trainData, err := readCSV("train.csv") if err != nil { log.Fatalf("failed to read training data: %v", err) } // Read testing data testData, err := readCSV("test.csv") if err != nil { log.Fatalf("failed to read testing data: %v", err) } // Standardize training data standardizedTrainData, mean, std := standardize(trainData) // Train model learningRate := 0.0001 epochs := 1000 weights, bias := trainModel(standardizedTrainData, learningRate, epochs) fmt.Printf("Trained Weights: %v\n", weights) fmt.Printf("Trained Bias: %f\n", bias) // Evaluate model on test data predictAndEvaluate2(testData, weights, bias, mean, std) } ``` 这里我们设置超参学习率为0.0001,设置epochs为1000,即进行1000轮完整的训练 我们基于训练后的模型以及测试数据集进行模型效果评估 ``` // go-and-nn/linear-regression/main.go func predictAndEvaluate(data [][]float64, weights []float64, bias float64, mean []float64, std []float64) { features := len(data[0]) - 1 mse := 0.0 for i := 0; i < len(data); i++ { // Standardize the input features using the training mean and std standardizedFeatures := make([]float64, features) for j := 0; j < features; j++ { standardizedFeatures[j] = (data[i][j] - mean[j]) / std[j] } // Calculate the prediction prediction := bias for j := 0; j < features; j++ { prediction += weights[j] * standardizedFeatures[j] } // Calculate the error and accumulate the MSE error := prediction - data[i][features] mse += error * error // Print the prediction and the actual value fmt.Printf("Sample %d: Predicted Value: %f, Actual Value: %f\n", i+1, prediction, data[i][features]) } // Calculate the final MSE mse /= float64(len(data)) fmt.Printf("Mean Squared Error: %f\n", mse) } ``` 输出 ``` $go build $./demo Epoch 1: Weights: [0.009191300234460844 0.009159461537409297], Bias: 0.034000, MSE: 124080.000000 Epoch 2: Weights: [0.018380768863390594 0.01831709148135162], Bias: 0.067997, MSE: 124053.513977 Epoch 3: Weights: [0.02756840625241513 0.027472890197452842], Bias: 0.101990, MSE: 124027.033923 Epoch 4: Weights: [0.03675421276708735 0.036626858051265865], Bias: 0.135980, MSE: 124000.559834 Epoch 5: Weights: [0.04593818877288719 0.0457789954082706], Bias: 0.169966, MSE: 123974.091710 ... ... Epoch 997: Weights: [8.311660331200889 8.279923139396109], Bias: 32.264505, MSE: 100432.407457 Epoch 998: Weights: [8.319195610465172 8.287426591989], Bias: 32.295278, MSE: 100411.202067 Epoch 999: Weights: [8.326729388699432 8.294928543563927], Bias: 32.326049, MSE: 100390.001368 Epoch 1000: Weights: [8.334261666203304 8.302428994420524], Bias: 32.356816, MSE: 100368.805359 Trained Weights: [8.334261666203304 8.302428994420524] Trained Bias: 32.356816 Sample 1: Predicted Value: 10.081607, Actual Value: 210.000000 Sample 2: Predicted Value: 14.223776, Actual Value: 230.000000 Sample 3: Predicted Value: 19.606495, Actual Value: 260.000000 Sample 4: Predicted Value: 25.609490, Actual Value: 310.000000 Sample 5: Predicted Value: 31.612486, Actual Value: 340.000000 Sample 6: Predicted Value: 37.615481, Actual Value: 370.000000 Sample 7: Predicted Value: 43.618476, Actual Value: 400.000000 Sample 8: Predicted Value: 49.621471, Actual Value: 430.000000 Sample 9: Predicted Value: 55.624466, Actual Value: 460.000000 Sample 10: Predicted Value: 61.627461, Actual Value: 490.000000 Mean Squared Error: 104949.429046 ``` Predicted Valu 与Actual Value 相差太差 **超参调试和优化** ``` $go build $./demo Epoch 1: Weights: [0.009191300234460844 0.009159461537409297], Bias: 0.034000, MSE: 124080.000000 Epoch 2: Weights: [0.018380768863390594 0.01831709148135162], Bias: 0.067997, MSE: 124053.513977 Epoch 3: Weights: [0.02756840625241513 0.027472890197452842], Bias: 0.101990, MSE: 124027.033923 Epoch 4: Weights: [0.03675421276708735 0.036626858051265865], Bias: 0.135980, MSE: 124000.559834 Epoch 5: Weights: [0.04593818877288719 0.0457789954082706], Bias: 0.169966, MSE: 123974.091710 Epoch 6: Weights: [0.055120334635221604 0.05492930263387402], Bias: 0.203949, MSE: 123947.629550 ... ... Epoch 996: Weights: [47.520035679041236 44.407936879025506], Bias: 339.984720, MSE: 44.287037 Epoch 997: Weights: [47.521568654779436 44.406403906767075], Bias: 339.984872, MSE: 44.286092 Epoch 998: Weights: [47.523101572396406 44.404870992560404], Bias: 339.985024, MSE: 44.285147 Epoch 999: Weights: [47.524634431895045 44.40333813640399], Bias: 339.985174, MSE: 44.284203 Epoch 1000: Weights: [47.52616723327823 44.401805338296306], Bias: 339.985322, MSE: 44.283259 Trained Weights: [47.52616723327823 44.401805338296306] Trained Bias: 339.985322 Sample 1: Predicted Value: 216.742422, Actual Value: 210.000000 Sample 2: Predicted Value: 239.923439, Actual Value: 230.000000 Sample 3: Predicted Value: 269.738984, Actual Value: 260.000000 Sample 4: Predicted Value: 302.871794, Actual Value: 310.000000 Sample 5: Predicted Value: 336.004604, Actual Value: 340.000000 Sample 6: Predicted Value: 369.137414, Actual Value: 370.000000 Sample 7: Predicted Value: 402.270225, Actual Value: 400.000000 Sample 8: Predicted Value: 435.403035, Actual Value: 430.000000 Sample 9: Predicted Value: 468.535845, Actual Value: 460.000000 Sample 10: Predicted Value: 501.668655, Actual Value: 490.000000 Mean Squared Error: 54.966611 ``` 这回我们看懂,训练后的模型在测试集上的预测结果与实际标签值非常接近,可以看到对超参learningRate的调整见效了!